diff --git a/config/data.py b/config/data.py index a693010..22ae13c 100644 --- a/config/data.py +++ b/config/data.py @@ -5,6 +5,30 @@ import os import sys import maya.cmds as cmds +def Qt(): + try: + from PySide2 import QtCore, QtGui, QtWidgets + print("成功加载PySide2") + return QtCore, QtGui, QtWidgets + except ImportError as e: + print(f"PySide2加载失败: {str(e)}") + try: + from PySide import QtCore, QtGui + QtWidgets = QtGui + print("成功加载PySide") + return QtCore, QtGui, QtWidgets + except ImportError as e: + cmds.warning(f"PySide加载失败: {str(e)}") + try: + from PySide6 import QtCore, QtGui, QtWidgets + print("成功加载PySide6") + return QtCore, QtGui, QtWidgets + except ImportError as e: + cmds.warning(f"PySide加载失败: {str(e)}") + return None, None, None + +QtCore, QtGui, QtWidgets = Qt() + #===================================== 2. Global Variables ===================================== try: ROOT_PATH = os.path.dirname(INSTALL_PATH).replace("\\", "/") @@ -27,25 +51,33 @@ STYLES_PATH = os.path.join(ROOT_PATH, "resources", "styles").replace("\\", "/") DNA_PATH = os.path.join(ROOT_PATH, "resources", "dna").replace("\\", "/") DNA_IMG_PATH = os.path.join(ROOT_PATH, "resources", "img").replace("\\", "/") -MAYA_VERSION = cmds.about(version=True) -SYSTEM_OS = cmds.about(os=True) -if MAYA_VERSION in ["2022", "2023", "2024", "2025"]: - PLUGIN_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, MAYA_VERSION).replace("\\", "/") -else: - print(f"MetaFusion is not supported on Maya {MAYA_VERSION}") +# 统一系统信息获取 +SYSTEM_OS = "Windows" if cmds.about(os=True).lower().startswith("win") else "Linux" +MAYA_VERSION = str(int(cmds.about(version=True).split(".")[0])) # 直接获取最新版本 -PYTHON_VERSION = sys.version_info.major -if PYTHON_VERSION == 3: - PYDNA_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "pydna", f"python{sys.version_info.major}{sys.version_info.minor}").replace("\\", "/") -elif PYTHON_VERSION == 3.11: - PYDNA_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "python311").replace("\\", "/") -elif PYTHON_VERSION == 3.9: - PYDNA_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "python397").replace("\\", "/") -elif PYTHON_VERSION == 3.10: - PYDNA_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "python3108").replace("\\", "/") -else: - print(f"MetaFusion is not supported on Python {PYTHON_VERSION}") +#===================================== 获取Python版本路径 ===================================== +# 必须先定义PYTHON_VERSION +PYTHON_VERSION = sys.version +# 去掉小数点,比如3.10.8 → 3108 +PYTHON_VERSION = PYTHON_VERSION.replace(".", "") +# 获取主版本号和次版本号 +major_version = int(PYTHON_VERSION[0]) +minor_version = int(PYTHON_VERSION[1:3]) if len(PYTHON_VERSION) > 1 else None + +# 创建版本元组 +version_tuple = (major_version,) if minor_version is None else (major_version, minor_version) + +# 调整版本映射表 +PYTHON_VERSION_MAP = { + (3,): "python3", # 所有Python3主版本 + (3, 9): "python397", # 3.9.x → python397 + (3, 10): "python3108", # 3.10.x → python3108 + (3, 11): "python311" # 3.11.x → python311 +} + +# 按照映射表获取PYTHON_VERSION_DIR +PYTHON_VERSION_DIR = PYTHON_VERSION_MAP.get(version_tuple, "python3") # 如果找不到对应版本,默认使用 python3 #===================================== 3. Files ===================================== # FILES @@ -55,35 +87,17 @@ TOOL_ICON = os.path.join(ICONS_PATH, f"{TOOL_NAME}Logo.png").replace("\\", "/") TOOL_COMMAND_ICON = os.path.join(ICONS_PATH, "CommandButton.png").replace("\\", "/") TOOL_MOD_FILENAME = f"{TOOL_NAME}.mod" -#===================================== 4. Qt ===================================== -# Qt -def Qt(): - try: - from PySide import QtCore, QtGui, QtWidgets - return QtCore, QtGui, QtWidgets - except ImportError: - try: - from PySide2 import QtCore, QtGui, QtWidgets - return QtCore, QtGui, QtWidgets - except ImportError: - try: - from PySide3 import QtCore, QtGui, QtWidgets - return QtCore, QtGui, QtWidgets - except ImportError: - try: - from PySide4 import QtCore, QtGui, QtWidgets - return QtCore, QtGui, QtWidgets - except ImportError: - try: - from PySide5 import QtCore, QtGui, QtWidgets - return QtCore, QtGui, QtWidgets - except ImportError: - try: - from PySide6 import QtCore, QtGui, QtWidgets - return QtCore, QtGui, QtWidgets - except ImportError: - print("未找到 Qt 模块") - return None, None, None +# 生成最终路径 +PLUGIN_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, MAYA_VERSION).replace("\\", "/") +PYDNA_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "pydna", PYTHON_VERSION_DIR).replace("\\", "/") + +#===================================== 4. 新增工具路径 ===================================== +# 新增工具路径 +BUILDER_PATH = os.path.join(SCRIPTS_PATH, "builder").replace("\\", "/") +DNALIB_PATH = os.path.join(SCRIPTS_PATH, "dnalib").replace("\\", "/") + + + diff --git a/dnacalib/CMakeLists.txt b/dnacalib/CMakeLists.txt new file mode 100644 index 0000000..4ae91f0 --- /dev/null +++ b/dnacalib/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.14) +project(dnacalib) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMakeModulesExtra") + +enable_testing() + +add_subdirectory(DNACalib) +add_subdirectory(SPyUS) +add_subdirectory(PyDNA) +add_subdirectory(PyDNACalib) + +################################################ +# Package build artifacts +set(CPACK_GENERATOR "ZIP") +set(CPACK_ARCHIVE_COMPONENT_INSTALL ON) +set(CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY OFF) +set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE) +string(CONCAT CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}" + "-${CMAKE_PROJECT_VERSION}" + "-${CMAKE_SYSTEM_NAME}" + "-${CMAKE_SYSTEM_PROCESSOR}" + "-${CMAKE_CXX_COMPILER_ID}${CMAKE_CXX_COMPILER_VERSION}" + "-${CMAKE_BUILD_TYPE}" + "-${PYTHON3_EXACT_VERSION}" + "-SHARED") +include(CPack) diff --git a/dnacalib/CMakeModulesExtra/CMakeModulesExtra.cmake b/dnacalib/CMakeModulesExtra/CMakeModulesExtra.cmake new file mode 100644 index 0000000..07a5365 --- /dev/null +++ b/dnacalib/CMakeModulesExtra/CMakeModulesExtra.cmake @@ -0,0 +1,5 @@ +set(CMakeModulesExtra_DIRS "${CMAKE_CURRENT_LIST_DIR}/install" + "${CMAKE_CURRENT_LIST_DIR}/symbols" + "${CMAKE_CURRENT_LIST_DIR}/utilities" + "${CMAKE_CURRENT_LIST_DIR}/version") +list(APPEND CMAKE_MODULE_PATH ${CMakeModulesExtra_DIRS}) \ No newline at end of file diff --git a/dnacalib/CMakeModulesExtra/install/Config.cmake.in b/dnacalib/CMakeModulesExtra/install/Config.cmake.in new file mode 100644 index 0000000..01c210f --- /dev/null +++ b/dnacalib/CMakeModulesExtra/install/Config.cmake.in @@ -0,0 +1,12 @@ +@PACKAGE_INIT@ + +set(@PROJECT_NAME@_VERSION @PROJECT_VERSION@) + +if(NOT TARGET @REPRESENTATIVE_TARGET_NAME@) + include(${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake) +endif() + +set_and_check(@PROJECT_NAME@_INCLUDE_DIR "@PACKAGE_INCLUDE_DIR@") +set_and_check(@PROJECT_NAME@_LIB_DIR "@PACKAGE_LIB_DIR@") + +check_required_components(@PROJECT_NAME@) diff --git a/dnacalib/CMakeModulesExtra/install/InstallLibrary.cmake b/dnacalib/CMakeModulesExtra/install/InstallLibrary.cmake new file mode 100644 index 0000000..d0659f8 --- /dev/null +++ b/dnacalib/CMakeModulesExtra/install/InstallLibrary.cmake @@ -0,0 +1,104 @@ +# Standard CMake library installation procedure +# +# Usage: +# include(InstallLibrary) +# install_library(my_target) +# +# Module dependencies: +# CMakePackageConfigHelpers +# GNUInstallDirs +# +# Preconditions: +# Assumes that standard, global CMake variables are accessible, such as PROJECT_NAME + +set(INSTALL_LIBRARY_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}") + +macro(install_library target_name) + # component_name is an optional argument, and will default to the given target_name + set(COMPONENT_NAME ${target_name}) + set(extra_args ${ARGN}) + list(LENGTH extra_args num_extra_args) + if(${num_extra_args} GREATER 0) + list(GET extra_args 0 COMPONENT_NAME) + endif() + + include(GNUInstallDirs) + set(REPRESENTATIVE_TARGET_NAME ${target_name}) + set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) + + # Set install destinations and associate installed target files with an export + install(TARGETS ${REPRESENTATIVE_TARGET_NAME} + EXPORT ${PROJECT_NAME}-targets + COMPONENT ${COMPONENT_NAME} + RUNTIME + DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT ${COMPONENT_NAME} + LIBRARY + DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT ${COMPONENT_NAME} + NAMELINK_COMPONENT ${COMPONENT_NAME} + ARCHIVE + DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT ${COMPONENT_NAME} + PUBLIC_HEADER + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT ${COMPONENT_NAME} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + + # Write build-tree targets + export(TARGETS ${REPRESENTATIVE_TARGET_NAME} + FILE ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake + NAMESPACE ${PROJECT_NAME}::) + # Allow find_package to locate package without installing it (find it's build-tree) + export(PACKAGE ${PROJECT_NAME}) + + # Write install-tree targets + install(EXPORT ${PROJECT_NAME}-targets + FILE ${PROJECT_NAME}Targets.cmake + NAMESPACE ${PROJECT_NAME}:: + DESTINATION ${INSTALL_CONFIGDIR} + COMPONENT ${COMPONENT_NAME}) + + include(CMakePackageConfigHelpers) + + # Generate build-tree configuration + set(INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) + set(LIB_DIR ${CMAKE_CURRENT_BINARY_DIR}) + configure_package_config_file("${INSTALL_LIBRARY_SOURCE_DIR}/Config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" + INSTALL_DESTINATION ${CMAKE_CURRENT_BINARY_DIR} + PATH_VARS INCLUDE_DIR LIB_DIR + INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR}) + + # Generate install-tree configuration + set(INCLUDE_DIR ${CMAKE_INSTALL_INCLUDEDIR}) + set(LIB_DIR ${CMAKE_INSTALL_LIBDIR}) + configure_package_config_file("${INSTALL_LIBRARY_SOURCE_DIR}/Config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.install.cmake" + INSTALL_DESTINATION ${INSTALL_CONFIGDIR} + PATH_VARS INCLUDE_DIR LIB_DIR) + + # Generate package version file + write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion) + + # Install the install-tree configuration + install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.install.cmake" + DESTINATION ${INSTALL_CONFIGDIR} + RENAME "${PROJECT_NAME}Config.cmake" + COMPONENT ${COMPONENT_NAME}) + + # Install package version file + install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" + DESTINATION ${INSTALL_CONFIGDIR} + COMPONENT ${COMPONENT_NAME}) + + # Install include files + install(DIRECTORY include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT ${COMPONENT_NAME}) +endmacro() diff --git a/dnacalib/CMakeModulesExtra/symbols/Defs.h.in b/dnacalib/CMakeModulesExtra/symbols/Defs.h.in new file mode 100644 index 0000000..880bada --- /dev/null +++ b/dnacalib/CMakeModulesExtra/symbols/Defs.h.in @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if defined(_WIN32) || defined(__CYGWIN__) + #if defined(__GNUC__) + #define DLL_EXPORT __attribute__((dllexport)) + #define DLL_IMPORT __attribute__((dllimport)) + #else + #define DLL_EXPORT __declspec(dllexport) + #define DLL_IMPORT __declspec(dllimport) + #endif +#elif defined(__GNUC__) + #define DLL_EXPORT __attribute__((visibility("default"))) + #define DLL_IMPORT DLL_EXPORT +#endif + +#if defined(@DEF_BUILD_SHARED_NAME@) + // Build shared library + #define @DEF_EXPORT_ATTR_NAME@ DLL_EXPORT +#elif defined(@DEF_USE_SHARED_NAME@) + // Use shared library + #define @DEF_EXPORT_ATTR_NAME@ DLL_IMPORT +#else + // Build or use static library + #define @DEF_EXPORT_ATTR_NAME@ +#endif diff --git a/dnacalib/CMakeModulesExtra/symbols/Symbols.cmake b/dnacalib/CMakeModulesExtra/symbols/Symbols.cmake new file mode 100644 index 0000000..c2639aa --- /dev/null +++ b/dnacalib/CMakeModulesExtra/symbols/Symbols.cmake @@ -0,0 +1,25 @@ +# Generate header file that contains preprocessor definitions for exporting symbols from shared libraries. +# +# Usage: +# include(Symbols) +# generate_export_definitions( +# OUTPUT_FILE /abs/path/to/include/mylib/Defs.h +# EXPORT_ATTR_NAME MLAPI +# BUILD_SHARED_NAME ML_BUILD_SHARED +# USE_SHARED_NAME ML_SHARED) +# +# Module dependencies: +# CMakeParseArguments + +set(SYMBOLS_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}) + +function(generate_export_definitions) + set(options) + set(one_value_args OUTPUT_FILE EXPORT_ATTR_NAME BUILD_SHARED_NAME USE_SHARED_NAME) + set(multi_value_args) + cmake_parse_arguments(DEF "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN}) + configure_file("${SYMBOLS_SOURCE_DIR}/Defs.h.in" + "${DEF_OUTPUT_FILE}" + @ONLY + NEWLINE_STYLE LF) +endfunction() diff --git a/dnacalib/CMakeModulesExtra/utilities/SupportedCompileOptions.cmake b/dnacalib/CMakeModulesExtra/utilities/SupportedCompileOptions.cmake new file mode 100644 index 0000000..d4f2b56 --- /dev/null +++ b/dnacalib/CMakeModulesExtra/utilities/SupportedCompileOptions.cmake @@ -0,0 +1,43 @@ +# From the list of passed in compiler flags, add only those to the target +# which are supported by the selected compiler +# +# Usage: +# include(SupportedCompileOptions) +# set(CXX_FLAGS -Wall -Wextra -Wpedantic -Wduplicated-branches -Wunused) +# target_supported_compile_options(target_name PUBLIC "${CXX_FLAGS}") +# +# Module dependencies: +# CheckCXXCompilerFlag + +include(CheckCXXCompilerFlag) + +function(target_add_cxx_flag_if_supported target_name visibility flag_name) + # Check if flag was already checked whether it's supported or not + if(${flag_name} IN_LIST SUPPORTED_FLAGS_CACHE) + set(has_flag_${flag_name} TRUE) + elseif(${flag_name} IN_LIST UNSUPPORTED_FLAGS_CACHE) + set(has_flag_${flag_name} FALSE) + endif() + # If not found in cache, perform the check now and cache the result + if(NOT DEFINED has_flag_${flag_name}) + check_cxx_compiler_flag("${flag_name}" has_flag_${flag_name}) + # It's safe to rely on cache variables as the only parameter that may affect the validity + # of their content is the chosen compiler itself, and changing a compiler forces CMake to + # automatically purge the cache anyway. + if(has_flag_${flag_name}) + set(SUPPORTED_FLAGS_CACHE "${SUPPORTED_FLAGS_CACHE};${flag_name}" CACHE INTERNAL "") + else() + set(UNSUPPORTED_FLAGS_CACHE "${UNSUPPORTED_FLAGS_CACHE};${flag_name}" CACHE INTERNAL "") + endif() + endif() + # Enable flag is supported + if(has_flag_${flag_name}) + target_compile_options(${target_name} ${visibility} ${flag_name}) + endif() +endfunction() + +function(target_supported_compile_options target_name visibility flags) + foreach(flag_name IN LISTS flags) + target_add_cxx_flag_if_supported(${target_name} ${visibility} ${flag_name}) + endforeach() +endfunction() diff --git a/dnacalib/CMakeModulesExtra/version/Version.rc.in b/dnacalib/CMakeModulesExtra/version/Version.rc.in new file mode 100644 index 0000000..56f6864 --- /dev/null +++ b/dnacalib/CMakeModulesExtra/version/Version.rc.in @@ -0,0 +1,64 @@ +#include "winres.h" + +#define VER_APPLICATION_TYPE 0 +#define VER_SHARED_LIBRARY_TYPE 1 +#define VER_STATIC_LIBRARY_TYPE 2 + +#define VER_TARGET_TYPE @VI_TARGET_TYPE@ +#if VER_TARGET_TYPE == VER_APPLICATION_TYPE + #define VER_FILETYPE VFT_APP + #define VER_FILEEXT ".exe\0" +#elif VER_TARGET_TYPE == VER_SHARED_LIBRARY_TYPE + #define VER_FILETYPE VFT_DLL + #define VER_FILEEXT ".dll\0" +#elif VER_TARGET_TYPE == VER_STATIC_LIBRARY_TYPE + #define VER_FILETYPE VFT_STATIC_LIB + #define VER_FILEEXT ".lib\0" +#else + #define VER_FILETYPE VFT_UNKNOWN + #define VER_FILEEXT "\0" +#endif + +#define VER_NAME_STR "@VI_NAME@\0" +#define VER_FILENAME_STR "@VI_FILENAME@" VER_FILEEXT +#define VER_VERSION @VI_MAJOR_VERSION@, @VI_MINOR_VERSION@, @VI_PATCH_VERSION@, 0 +#define VER_VERSION_STR "@VI_MAJOR_VERSION@.@VI_MINOR_VERSION@.@VI_PATCH_VERSION@\0" +#define VER_DESCRIPTION_STR "@VI_NAME@ v@VI_MAJOR_VERSION@.@VI_MINOR_VERSION@.@VI_PATCH_VERSION@\0" +#define VER_COMPANY_NAME_STR "@VI_COMPANY_NAME@\0" +#define VER_COPYRIGHT_STR "@VI_COPYRIGHT@\0" + +#ifndef DEBUG + #define VER_DEBUG 0 +#else + #define VER_DEBUG VS_FF_DEBUG +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VER_VERSION + PRODUCTVERSION VER_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VER_DEBUG + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "CompanyName", VER_COMPANY_NAME_STR + VALUE "FileDescription", VER_DESCRIPTION_STR + VALUE "FileVersion", VER_VERSION_STR + VALUE "InternalName", VER_FILENAME_STR + VALUE "LegalCopyright", VER_COPYRIGHT_STR + VALUE "OriginalFilename", VER_FILENAME_STR + VALUE "ProductName", VER_NAME_STR + VALUE "ProductVersion", VER_VERSION_STR + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/dnacalib/CMakeModulesExtra/version/VersionHeader.cmake b/dnacalib/CMakeModulesExtra/version/VersionHeader.cmake new file mode 100644 index 0000000..3940899 --- /dev/null +++ b/dnacalib/CMakeModulesExtra/version/VersionHeader.cmake @@ -0,0 +1,26 @@ +# Generate a Version.h file populated with the supplied values +# +# Usage: +# include(VersionHeader) +# generate_version_header( +# OUTPUT_FILE "/path/to/lib/include/name/version/Version.h" +# PREFIX "MYLIB" +# MAJOR 1 +# MINOR 0 +# PATCH 3) +# +# Module dependencies: +# CMakeParseArguments + +set(VERSION_HEADER_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}) + +function(generate_version_header) + set(options) + set(one_value_args OUTPUT_FILE PREFIX MAJOR MINOR PATCH) + set(multi_value_args) + cmake_parse_arguments(VERSION "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN}) + configure_file("${VERSION_HEADER_SOURCE_DIR}/VersionHeader.h.in" + ${VERSION_OUTPUT_FILE} + @ONLY + NEWLINE_STYLE LF) +endfunction() \ No newline at end of file diff --git a/dnacalib/CMakeModulesExtra/version/VersionHeader.h.in b/dnacalib/CMakeModulesExtra/version/VersionHeader.h.in new file mode 100644 index 0000000..9b87de7 --- /dev/null +++ b/dnacalib/CMakeModulesExtra/version/VersionHeader.h.in @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#define @VERSION_PREFIX@_MAJOR_VERSION @VERSION_MAJOR@ +#define @VERSION_PREFIX@_MINOR_VERSION @VERSION_MINOR@ +#define @VERSION_PREFIX@_PATCH_VERSION @VERSION_PATCH@ +#define @VERSION_PREFIX@_VERSION_STRING "@VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@" diff --git a/dnacalib/CMakeModulesExtra/version/VersionInfo.cmake b/dnacalib/CMakeModulesExtra/version/VersionInfo.cmake new file mode 100644 index 0000000..7848064 --- /dev/null +++ b/dnacalib/CMakeModulesExtra/version/VersionInfo.cmake @@ -0,0 +1,51 @@ +# Generate a Version.rc file populated with the supplied values and add the +# generated file to the target's list of sources +# +# Usage: +# include(VersionInfo) +# add_version_info( +# target_name +# NAME "Product name" +# FILENAME "SomeFile.dll" +# MAJOR_VERSION 1 +# MINOR_VERSION 0 +# PATCH_VERSION 3 +# COMPANY_NAME "Some Company" +# COPYRIGHT "Copyright notice") +# +# Module dependencies: +# CMakeParseArguments + +set(VERSION_INFO_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}) + +function(add_version_info target_name) + set(options) + set(one_value_args NAME + FILENAME + MAJOR_VERSION + MINOR_VERSION + PATCH_VERSION + COMPANY_NAME + COPYRIGHT) + set(multi_value_args) + cmake_parse_arguments(VI "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN}) + + get_target_property(TARGET_TYPE ${target_name} TYPE) + if(TARGET_TYPE STREQUAL EXECUTABLE) + set(VI_TARGET_TYPE 0) + elseif(TARGET_TYPE STREQUAL SHARED_LIBRARY) + set(VI_TARGET_TYPE 1) + elseif(TARGET_TYPE STREQUAL STATIC_LIBRARY) + set(VI_TARGET_TYPE 2) + else() + set(VI_TARGET_TYPE 3) + endif() + + get_target_property(TARGET_BINARY_DIR ${target_name} BINARY_DIR) + set(version_output_name "${TARGET_BINARY_DIR}/Version.rc") + configure_file("${VERSION_INFO_SOURCE_DIR}/Version.rc.in" + ${version_output_name} + @ONLY) + target_sources(${target_name} PRIVATE ${version_output_name}) + +endfunction() \ No newline at end of file diff --git a/dnacalib/DNACalib/CMakeLists.txt b/dnacalib/DNACalib/CMakeLists.txt new file mode 100644 index 0000000..f76450e --- /dev/null +++ b/dnacalib/DNACalib/CMakeLists.txt @@ -0,0 +1,278 @@ +cmake_minimum_required(VERSION 3.13) + +################################################ +# Project setup +set(DNAC dnacalib) +set(DNAC_VERSION 6.8.0) +# Prevent in-source-tree builds +set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) +# Create compilation database +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "" FORCE) + +project(DNACalib VERSION ${DNAC_VERSION} LANGUAGES CXX) + +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +set(CMAKE_MODULES_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +list(APPEND CMAKE_MODULE_PATH ${CMAKE_MODULES_ROOT_DIR}) + +include(FetchContent) +function(module_exists module_name) + include(${module_name} OPTIONAL RESULT_VARIABLE found) + set("${module_name}_FOUND" ${found} PARENT_SCOPE) +endfunction() + +# Make custom cmake modules available +module_exists(CMakeModulesExtra) +if(NOT CMakeModulesExtra_FOUND) + include(CMakeModulesExtraLoader) +endif() + +include(CMakeModulesExtra) +list(APPEND CMAKE_MODULE_PATH ${CMakeModulesExtra_DIRS}) + +option(READ_ONLY_SOURCE_TREE "Prevent autogeneration of files in source tree" OFF) + +################################################ +# Generate version information header +if (NOT READ_ONLY_SOURCE_TREE) + include(VersionHeader) + generate_version_header( + OUTPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/include/dnacalib/version/Version.h" + PREFIX "DNAC" + MAJOR ${PROJECT_VERSION_MAJOR} + MINOR ${PROJECT_VERSION_MINOR} + PATCH ${PROJECT_VERSION_PATCH}) +endif() + +################################################ +# Generate export definitions header +if (NOT READ_ONLY_SOURCE_TREE) + include(Symbols) + generate_export_definitions( + OUTPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/include/dnacalib/Defs.h" + EXPORT_ATTR_NAME DNACAPI + BUILD_SHARED_NAME DNAC_BUILD_SHARED + USE_SHARED_NAME DNAC_SHARED) +endif() + +################################################ +# Source code +set(HEADERS + include/dnacalib/DNACalib.h + include/dnacalib/Command.h + include/dnacalib/Defs.h + + include/dnacalib/commands/CalculateMeshLowerLODsCommand.h + include/dnacalib/commands/ClearBlendShapesCommand.h + include/dnacalib/commands/CommandSequence.h + include/dnacalib/commands/ConditionalCommand.h + include/dnacalib/commands/PruneBlendShapeTargetsCommand.h + include/dnacalib/commands/RemoveAnimatedMapCommand.h + include/dnacalib/commands/RemoveBlendShapeCommand.h + include/dnacalib/commands/RemoveJointAnimationCommand.h + include/dnacalib/commands/RemoveJointCommand.h + include/dnacalib/commands/RemoveMeshCommand.h + include/dnacalib/commands/RenameAnimatedMapCommand.h + include/dnacalib/commands/RenameBlendShapeCommand.h + include/dnacalib/commands/RenameJointCommand.h + include/dnacalib/commands/RenameMeshCommand.h + include/dnacalib/commands/RotateCommand.h + include/dnacalib/commands/ScaleCommand.h + include/dnacalib/commands/SetBlendShapeTargetDeltasCommand.h + include/dnacalib/commands/SetLODsCommand.h + include/dnacalib/commands/SetNeutralJointRotationsCommand.h + include/dnacalib/commands/SetNeutralJointTranslationsCommand.h + include/dnacalib/commands/SetSkinWeightsCommand.h + include/dnacalib/commands/SetVertexPositionsCommand.h + include/dnacalib/commands/TranslateCommand.h + include/dnacalib/commands/VectorOperations.h + + include/dnacalib/dna/DNACalibDNAReader.h + + include/dnacalib/types/Aliases.h + + include/dnacalib/version/Version.h + include/dnacalib/version/VersionInfo.h) +set(SOURCES + src/dnacalib/Command.cpp + src/dnacalib/CommandImplBase.h + src/dnacalib/TypeDefs.h + + src/dnacalib/commands/CalculateMeshLowerLODsCommand.cpp + src/dnacalib/commands/CalculateMeshLowerLODsCommandImpl.cpp + src/dnacalib/commands/CalculateMeshLowerLODsCommandImpl.h + src/dnacalib/commands/ClearBlendShapesCommand.cpp + src/dnacalib/commands/CommandSequence.cpp + src/dnacalib/commands/PruneBlendShapeTargetsCommand.cpp + src/dnacalib/commands/RemoveAnimatedMapCommand.cpp + src/dnacalib/commands/RemoveBlendShapeCommand.cpp + src/dnacalib/commands/RemoveJointAnimationCommand.cpp + src/dnacalib/commands/RemoveJointCommand.cpp + src/dnacalib/commands/RemoveMeshCommand.cpp + src/dnacalib/commands/RenameAnimatedMapCommand.cpp + src/dnacalib/commands/RenameBlendShapeCommand.cpp + src/dnacalib/commands/RenameJointCommand.cpp + src/dnacalib/commands/RenameMeshCommand.cpp + src/dnacalib/commands/RenameResourceCommand.h + src/dnacalib/commands/RotateCommand.cpp + src/dnacalib/commands/ScaleCommand.cpp + src/dnacalib/commands/SetBlendShapeTargetDeltasCommand.cpp + src/dnacalib/commands/SetLODsCommand.cpp + src/dnacalib/commands/SetNeutralJointRotationsCommand.cpp + src/dnacalib/commands/SetNeutralJointTranslationsCommand.cpp + src/dnacalib/commands/SetSkinWeightsCommand.cpp + src/dnacalib/commands/SetVertexPositionsCommand.cpp + src/dnacalib/commands/TranslateCommand.cpp + src/dnacalib/commands/SupportFactories.h + + src/dnacalib/dna/DNACalibDNAReaderImpl.cpp + src/dnacalib/dna/DNACalibDNAReaderImpl.h + src/dnacalib/dna/BaseImpl.h + src/dnacalib/dna/DenormalizedData.h + src/dnacalib/dna/DNA.h + src/dnacalib/dna/LODConstraint.cpp + src/dnacalib/dna/LODConstraint.h + src/dnacalib/dna/LODMapping.cpp + src/dnacalib/dna/LODMapping.h + src/dnacalib/dna/ReaderImpl.h + src/dnacalib/dna/SurjectiveMapping.h + src/dnacalib/dna/WriterImpl.h + src/dnacalib/dna/filters/AnimatedMapFilter.cpp + src/dnacalib/dna/filters/AnimatedMapFilter.h + src/dnacalib/dna/filters/BlendShapeFilter.cpp + src/dnacalib/dna/filters/BlendShapeFilter.h + src/dnacalib/dna/filters/JointFilter.cpp + src/dnacalib/dna/filters/JointFilter.h + src/dnacalib/dna/filters/MeshFilter.cpp + src/dnacalib/dna/filters/MeshFilter.h + src/dnacalib/dna/filters/Remap.h + + src/dnacalib/types/BoundingBox.h + src/dnacalib/types/Triangle.cpp + src/dnacalib/types/Triangle.h + src/dnacalib/types/UVBarycentricMapping.cpp + src/dnacalib/types/UVBarycentricMapping.h + + src/dnacalib/utils/Algorithm.h + src/dnacalib/utils/Extd.h + src/dnacalib/utils/ScopedEnumEx.h + + src/dnacalib/version/VersionInfo.cpp) + +################################################ +# Add target and build options +option(BUILD_SHARED_LIBS "Build shared libraries" OFF) + +set(DNAC_DEFAULT_LIBRARY_TYPE STATIC) + +if(BUILD_SHARED_LIBS) + set(DNAC_DEFAULT_LIBRARY_TYPE SHARED) +endif() + +set(DNAC_LIBRARY_TYPE ${DNAC_DEFAULT_LIBRARY_TYPE} CACHE STRING "Build DNACalib as a library of type") +set_property(CACHE DNAC_LIBRARY_TYPE PROPERTY STRINGS STATIC SHARED MODULE) + +# Python wrapper builds a shared object (e.g. .pyd, .so), so DNACalib library has to be built with -fPIC flag. +option(DNAC_BUILD_PIC "Build with position independent code" ON) + +add_library(${DNAC} ${DNAC_LIBRARY_TYPE}) +add_library(DNACalib::dnacalib ALIAS ${DNAC}) + +set_target_properties(${DNAC} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED NO + CXX_EXTENSIONS NO + CXX_VISIBILITY_PRESET hidden + POSITION_INDEPENDENT_CODE ${DNAC_BUILD_PIC} + SOURCE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/src" + SOVERSION ${PROJECT_VERSION_MAJOR} + VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}) + +if (DNAC_LIBRARY_TYPE STREQUAL SHARED) + target_compile_definitions(${DNAC} PUBLIC DNAC_SHARED PRIVATE DNAC_BUILD_SHARED) +endif() + +include(GNUInstallDirs) + +set(DNAC_PUBLIC_INCLUDE_DIRS + $ + $) + +target_include_directories(${DNAC} + PUBLIC + ${DNAC_PUBLIC_INCLUDE_DIRS} + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src) + +option(TREAT_WARNINGS_AS_ERRORS "Treat warnings as errors" OFF) +if(MSVC) + set(CXX_FLAGS /W4 /w14061 /w14062 /w14121 /w14242 /w14245 + /w14254 /w14263 /w14265 /w14266 /w14287 /w14289 /w14296 + /w14302 /w14311 /w14365 /w14388 /w14545 /w14546 /w14547 + /w14549 /w14555 /w14619 /w14640 /w14826 /w14905 /w14906 + /w14928 /w14987 /w14946) + if(TREAT_WARNINGS_AS_ERRORS) + list(APPEND CXX_FLAGS /WX) + endif() + set(CXX_EXTRA_FLAGS /permissive-) +else() + set(CXX_FLAGS -Wall -Wextra -Wpedantic) + if(TREAT_WARNINGS_AS_ERRORS) + list(APPEND CXX_FLAGS -Werror) + endif() + set(CXX_EXTRA_FLAGS -Wcast-align -Wconversion -Wduplicated-branches + -Wduplicated-cond -Wexit-time-destructors + -Wglobal-constructors -Wlogical-op -Wmissing-noreturn + -Wnon-virtual-dtor -Wnull-dereference -Wold-style-cast + -Woverloaded-virtual -Wshadow -Wsign-conversion -Wunreachable-code + -Wunused -Wuseless-cast -Wweak-vtables -Wno-unknown-pragmas) +endif() + +target_compile_options(${DNAC} PRIVATE "${CXX_FLAGS}") +include(SupportedCompileOptions) +target_supported_compile_options(${DNAC} PRIVATE "${CXX_EXTRA_FLAGS}") + + +################################################ +# Dependencies +include(DNACDependencies) + +set(ADAPTABLE_HEADERS) +foreach(hdr IN LISTS HEADERS) + list(APPEND ADAPTABLE_HEADERS $ $) +endforeach() + +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${HEADERS} ${SOURCES}) +target_sources(${DNAC} PRIVATE ${SOURCES} ${OBJECT_SOURCES} PUBLIC ${ADAPTABLE_HEADERS}) +target_link_libraries(${DNAC} PUBLIC ${DNAC_PUBLIC_DEPENDENCIES} PRIVATE ${DNAC_PRIVATE_DEPENDENCIES}) + +################################################ +# VersionInfo +if(WIN32 AND (DNAC_LIBRARY_TYPE STREQUAL SHARED OR DNAC_LIBRARY_TYPE STREQUAL MODULE)) + include(VersionInfo) + add_version_info(${DNAC} + NAME ${PROJECT_NAME} + FILENAME "${DNAC}.dll" + MAJOR_VERSION ${PROJECT_VERSION_MAJOR} + MINOR_VERSION ${PROJECT_VERSION_MINOR} + PATCH_VERSION ${PROJECT_VERSION_PATCH} + COMPANY_NAME "Epic Games" + COPYRIGHT "Copyright Epic Games, Inc. All Rights Reserved.") +endif() + +################################################ +# Export and install target +include(InstallLibrary) +install_library(${DNAC}) + +################################################ +# Examples +option(DNAC_BUILD_EXAMPLES "Build examples" ON) +if(DNAC_BUILD_EXAMPLES) + set(COPY_LIB_TO_EXAMPLES OFF) + if(BUILD_SHARED_LIBS) + set(COPY_LIB_TO_EXAMPLES ON) + endif() + add_subdirectory(examples) +endif() diff --git a/dnacalib/DNACalib/README.md b/dnacalib/DNACalib/README.md new file mode 100644 index 0000000..7840b57 --- /dev/null +++ b/dnacalib/DNACalib/README.md @@ -0,0 +1,17 @@ +# DNACalib + +## Build + +Use [CMake](https://cmake.org/) to generate the build-scripts required for building _DNACalib_, e.g. by executing the following commands from the project root directory: + +``` +mkdir build +cd build +cmake .. +``` + +And subsequently, to start the build process: + +``` +cmake --build . +``` \ No newline at end of file diff --git a/dnacalib/DNACalib/cmake/DNACDependencies.cmake b/dnacalib/DNACalib/cmake/DNACDependencies.cmake new file mode 100644 index 0000000..123e46d --- /dev/null +++ b/dnacalib/DNACalib/cmake/DNACDependencies.cmake @@ -0,0 +1,239 @@ +set(INCLUDES + include/dna/BinaryStreamReader.h + include/dna/BinaryStreamWriter.h + include/dna/DataLayer.h + include/dna/Defs.h + include/dna/JSONStreamReader.h + include/dna/JSONStreamWriter.h + include/dna/Reader.h + include/dna/StreamReader.h + include/dna/StreamWriter.h + include/dna/Writer.h + include/dna/layers/BehaviorReader.h + include/dna/layers/BehaviorWriter.h + include/dna/layers/DefinitionReader.h + include/dna/layers/DefinitionWriter.h + include/dna/layers/Descriptor.h + include/dna/layers/DescriptorReader.h + include/dna/layers/DescriptorWriter.h + include/dna/layers/Geometry.h + include/dna/layers/GeometryReader.h + include/dna/layers/GeometryWriter.h + include/dna/types/Aliases.h + include/dna/types/ArrayView.h + include/dna/types/StringView.h + include/dna/types/Vector3.h + include/dna/version/Version.h + include/dnacalib/Command.h + include/dnacalib/DNACalib.h + include/dnacalib/Defs.h + include/dnacalib/commands/CalculateMeshLowerLODsCommand.h + include/dnacalib/commands/ClearBlendShapesCommand.h + include/dnacalib/commands/CommandSequence.h + include/dnacalib/commands/ConditionalCommand.h + include/dnacalib/commands/PruneBlendShapeTargetsCommand.h + include/dnacalib/commands/RemoveAnimatedMapCommand.h + include/dnacalib/commands/RemoveBlendShapeCommand.h + include/dnacalib/commands/RemoveJointAnimationCommand.h + include/dnacalib/commands/RemoveJointCommand.h + include/dnacalib/commands/RemoveMeshCommand.h + include/dnacalib/commands/RenameAnimatedMapCommand.h + include/dnacalib/commands/RenameBlendShapeCommand.h + include/dnacalib/commands/RenameJointCommand.h + include/dnacalib/commands/RenameMeshCommand.h + include/dnacalib/commands/RotateCommand.h + include/dnacalib/commands/ScaleCommand.h + include/dnacalib/commands/SetBlendShapeTargetDeltasCommand.h + include/dnacalib/commands/SetLODsCommand.h + include/dnacalib/commands/SetNeutralJointRotationsCommand.h + include/dnacalib/commands/SetNeutralJointTranslationsCommand.h + include/dnacalib/commands/SetSkinWeightsCommand.h + include/dnacalib/commands/SetVertexPositionsCommand.h + include/dnacalib/commands/TranslateCommand.h + include/dnacalib/commands/VectorOperations.h + include/dnacalib/dna/DNACalibDNAReader.h + include/dnacalib/types/Aliases.h + include/dnacalib/version/Version.h + include/dnacalib/version/VersionInfo.h + include/pma/Defs.h + include/pma/MemoryResource.h + include/pma/PolyAllocator.h + include/pma/ScopedPtr.h + include/pma/TypeDefs.h + include/pma/resources/AlignedMemoryResource.h + include/pma/resources/ArenaMemoryResource.h + include/pma/resources/DefaultMemoryResource.h + include/pma/utils/ManagedInstance.h + include/pma/version/Version.h + include/status/Defs.h + include/status/Provider.h + include/status/Status.h + include/status/StatusCode.h + include/status/version/Version.h + include/trio/Concepts.h + include/trio/Defs.h + include/trio/Stream.h + include/trio/streams/FileStream.h + include/trio/streams/MemoryMappedFileStream.h + include/trio/streams/MemoryStream.h + include/trio/types/Aliases.h + include/trio/types/Parameters.h + include/trio/utils/StreamScope.h + include/trio/version/Version.h) +set(SOURCES + src/dna/BaseImpl.h + src/dna/DNA.h + src/dna/DataLayerBitmask.h + src/dna/DenormalizedData.h + src/dna/LODConstraint.cpp + src/dna/LODConstraint.h + src/dna/LODMapping.cpp + src/dna/LODMapping.h + src/dna/Reader.cpp + src/dna/ReaderImpl.h + src/dna/SurjectiveMapping.h + src/dna/TypeDefs.h + src/dna/Writer.cpp + src/dna/WriterImpl.h + src/dna/filters/AnimatedMapFilter.cpp + src/dna/filters/AnimatedMapFilter.h + src/dna/filters/BlendShapeFilter.cpp + src/dna/filters/BlendShapeFilter.h + src/dna/filters/JointFilter.cpp + src/dna/filters/JointFilter.h + src/dna/filters/MeshFilter.cpp + src/dna/filters/MeshFilter.h + src/dna/filters/Remap.h + src/dna/stream/BinaryStreamReaderImpl.cpp + src/dna/stream/BinaryStreamReaderImpl.h + src/dna/stream/BinaryStreamWriterImpl.cpp + src/dna/stream/BinaryStreamWriterImpl.h + src/dna/stream/FilteredInputArchive.cpp + src/dna/stream/FilteredInputArchive.h + src/dna/stream/JSONStreamReaderImpl.cpp + src/dna/stream/JSONStreamReaderImpl.h + src/dna/stream/JSONStreamWriterImpl.cpp + src/dna/stream/JSONStreamWriterImpl.h + src/dna/stream/StreamReader.cpp + src/dna/stream/StreamWriter.cpp + src/dna/types/Limits.h + src/dna/utils/Extd.h + src/dna/utils/ScopedEnumEx.h + src/dnacalib/Command.cpp + src/dnacalib/CommandImplBase.h + src/dnacalib/TypeDefs.h + src/dnacalib/commands/CalculateMeshLowerLODsCommand.cpp + src/dnacalib/commands/CalculateMeshLowerLODsCommandImpl.cpp + src/dnacalib/commands/CalculateMeshLowerLODsCommandImpl.h + src/dnacalib/commands/ClearBlendShapesCommand.cpp + src/dnacalib/commands/CommandSequence.cpp + src/dnacalib/commands/PruneBlendShapeTargetsCommand.cpp + src/dnacalib/commands/RemoveAnimatedMapCommand.cpp + src/dnacalib/commands/RemoveBlendShapeCommand.cpp + src/dnacalib/commands/RemoveJointAnimationCommand.cpp + src/dnacalib/commands/RemoveJointCommand.cpp + src/dnacalib/commands/RemoveMeshCommand.cpp + src/dnacalib/commands/RenameAnimatedMapCommand.cpp + src/dnacalib/commands/RenameBlendShapeCommand.cpp + src/dnacalib/commands/RenameJointCommand.cpp + src/dnacalib/commands/RenameMeshCommand.cpp + src/dnacalib/commands/RenameResourceCommand.h + src/dnacalib/commands/RotateCommand.cpp + src/dnacalib/commands/ScaleCommand.cpp + src/dnacalib/commands/SetBlendShapeTargetDeltasCommand.cpp + src/dnacalib/commands/SetLODsCommand.cpp + src/dnacalib/commands/SetNeutralJointRotationsCommand.cpp + src/dnacalib/commands/SetNeutralJointTranslationsCommand.cpp + src/dnacalib/commands/SetSkinWeightsCommand.cpp + src/dnacalib/commands/SetVertexPositionsCommand.cpp + src/dnacalib/commands/SupportFactories.h + src/dnacalib/commands/TranslateCommand.cpp + src/dnacalib/dna/BaseImpl.h + src/dnacalib/dna/DNA.h + src/dnacalib/dna/DNACalibDNAReaderImpl.cpp + src/dnacalib/dna/DNACalibDNAReaderImpl.h + src/dnacalib/dna/DenormalizedData.h + src/dnacalib/dna/LODConstraint.cpp + src/dnacalib/dna/LODConstraint.h + src/dnacalib/dna/LODMapping.cpp + src/dnacalib/dna/LODMapping.h + src/dnacalib/dna/ReaderImpl.h + src/dnacalib/dna/SurjectiveMapping.h + src/dnacalib/dna/WriterImpl.h + src/dnacalib/dna/filters/AnimatedMapFilter.cpp + src/dnacalib/dna/filters/AnimatedMapFilter.h + src/dnacalib/dna/filters/BlendShapeFilter.cpp + src/dnacalib/dna/filters/BlendShapeFilter.h + src/dnacalib/dna/filters/JointFilter.cpp + src/dnacalib/dna/filters/JointFilter.h + src/dnacalib/dna/filters/MeshFilter.cpp + src/dnacalib/dna/filters/MeshFilter.h + src/dnacalib/dna/filters/Remap.h + src/dnacalib/types/BoundingBox.h + src/dnacalib/types/Triangle.cpp + src/dnacalib/types/Triangle.h + src/dnacalib/types/UVBarycentricMapping.cpp + src/dnacalib/types/UVBarycentricMapping.h + src/dnacalib/utils/Algorithm.h + src/dnacalib/utils/Extd.h + src/dnacalib/utils/FormatString.h + src/dnacalib/utils/ScopedEnumEx.h + src/dnacalib/version/VersionInfo.cpp + src/pma/MemoryResource.cpp + src/pma/resources/AlignedMemoryResource.cpp + src/pma/resources/ArenaMemoryResource.cpp + src/pma/resources/DefaultMemoryResource.cpp + src/status/PredefinedCodes.h + src/status/Provider.cpp + src/status/Registry.cpp + src/status/Registry.h + src/status/Status.cpp + src/status/Storage.cpp + src/status/Storage.h + src/tdm/Computations.h + src/tdm/Mat.h + src/tdm/TDM.h + src/tdm/Transforms.h + src/tdm/Types.h + src/tdm/Vec.h + src/tdm/version/Version.h + src/terse/Archive.h + src/terse/archives/Common.h + src/terse/archives/Traits.h + src/terse/archives/binary/InputArchive.h + src/terse/archives/binary/OutputArchive.h + src/terse/archives/json/InputArchive.h + src/terse/archives/json/OutputArchive.h + src/terse/types/Anchor.h + src/terse/types/ArchiveOffset.h + src/terse/types/ArchiveSize.h + src/terse/types/Blob.h + src/terse/types/CharInputStreamBuf.h + src/terse/types/CharOutputStreamBuf.h + src/terse/types/DynArray.h + src/terse/types/Transparent.h + src/terse/utils/Base64.h + src/terse/utils/ByteSwap.h + src/terse/utils/Endianness.h + src/terse/utils/VirtualSerializerProxy.h + src/terse/version/Version.h + src/trio/Concepts.cpp + src/trio/Stream.cpp + src/trio/streams/FileStreamImpl.cpp + src/trio/streams/FileStreamImpl.h + src/trio/streams/MemoryMappedFileStream.cpp + src/trio/streams/MemoryMappedFileStreamFallback.cpp + src/trio/streams/MemoryMappedFileStreamFallback.h + src/trio/streams/MemoryMappedFileStreamUnix.cpp + src/trio/streams/MemoryMappedFileStreamUnix.h + src/trio/streams/MemoryMappedFileStreamWindows.cpp + src/trio/streams/MemoryMappedFileStreamWindows.h + src/trio/streams/MemoryStreamImpl.cpp + src/trio/streams/MemoryStreamImpl.h + src/trio/streams/StreamStatus.cpp + src/trio/streams/StreamStatus.h + src/trio/utils/NativeString.h + src/trio/utils/PlatformWindows.h + src/trio/utils/ScopedEnumEx.h) +set(TESTS + ) diff --git a/dnacalib/DNACalib/examples/CMakeLists.txt b/dnacalib/DNACalib/examples/CMakeLists.txt new file mode 100644 index 0000000..441c37c --- /dev/null +++ b/dnacalib/DNACalib/examples/CMakeLists.txt @@ -0,0 +1,26 @@ +set(SOURCES + CommandSequence.cpp + SingleCommand.cpp) + +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES}) + +foreach(example IN LISTS SOURCES) + get_filename_component(filename ${example} NAME_WE) + string(TOLOWER ${filename} example_target_name) + add_executable(${example_target_name} ${example}) + target_link_libraries(${example_target_name} PRIVATE ${DNAC}) + set_target_properties(${example_target_name} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED NO + CXX_EXTENSIONS NO + FOLDER examples) + list(APPEND EXAMPLE_TARGETS ${example_target_name}) +endforeach() + +set(DNAC_EXAMPLES ${EXAMPLE_TARGETS} PARENT_SCOPE) + +if(COPY_LIB_TO_EXAMPLES) + list(GET EXAMPLE_TARGETS 0 EXAMPLE_TARGET) + add_custom_command(TARGET ${EXAMPLE_TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $) +endif() diff --git a/dnacalib/DNACalib/examples/CommandSequence.cpp b/dnacalib/DNACalib/examples/CommandSequence.cpp new file mode 100644 index 0000000..0cef032 --- /dev/null +++ b/dnacalib/DNACalib/examples/CommandSequence.cpp @@ -0,0 +1,65 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/DNACalib.h" + +#include +#include + +static const char* usage = "Usage: commandsequence.exe \n"; + +int main(int argc, char** argv) { + if (argc < 2) { + std::cout << "Provide input dna file!" << std::endl; + std::cout << usage << std::endl; + return -1; + } + + const char* inputDNA = argv[1]; + auto inStream = dnac::makeScoped(inputDNA, + dnac::FileStream::AccessMode::Read, + dnac::FileStream::OpenMode::Binary); + auto reader = dnac::makeScoped(inStream.get()); + reader->read(); + + if (!dnac::Status::isOk()) { + std::cout << "Could not read input DNA file!\n"; + return -1; + } + + auto dnaReader = dnac::makeScoped(reader.get()); + + // Create command sequence instance + dnac::CommandSequence cmdSeq; + + // Prepare a bunch of commands + std::vector positions; + std::vector masks; + dnac::SetVertexPositionsCommand setMeshAPos{2, dnac::ConstArrayView{positions}, + dnac::ConstArrayView{masks}, + dnac::VectorOperation::Interpolate}; + dnac::RenameJointCommand renameJointA("clavicle_l", "cubicle_l"); + dnac::RenameJointCommand renameJointB(10, "upperarm_corrosiveRoot_l"); + + // Add commands to the command sequence + cmdSeq.add(&setMeshAPos, &renameJointA); + cmdSeq.add(&renameJointB); + + // Execute command sequence + cmdSeq.run(dnaReader.get()); + + // Reconfigure individual commands that are already in the command sequence + renameJointB.setName("FACIAL_L_12IPV_NeckBackB2", "FACIAL_L_12IPTV_NickelBackB52"); + + // Modify command sequence (turn an unconditional command into a conditional command) + cmdSeq.remove(&renameJointA); + auto guardedRenameJointA = + dnac::makeConditional(&renameJointA, [](dnac::RenameJointCommand* command, dnac::DNACalibDNAReader* output) { + return (output->getJointCount() > 6); + }); + cmdSeq.add(&guardedRenameJointA); + + // Execute modified command sequence + cmdSeq.run(dnaReader.get()); + + return 0; +} diff --git a/dnacalib/DNACalib/examples/SingleCommand.cpp b/dnacalib/DNACalib/examples/SingleCommand.cpp new file mode 100644 index 0000000..254d853 --- /dev/null +++ b/dnacalib/DNACalib/examples/SingleCommand.cpp @@ -0,0 +1,36 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/DNACalib.h" + +#include +#include + +static const char* usage = "Usage: singlecommand.exe \n"; + +int main(int argc, char** argv) { + if (argc < 2) { + std::cout << "Provide input dna file!" << std::endl; + std::cout << usage << std::endl; + return -1; + } + + const char* inputDNA = argv[1]; + auto inStream = dnac::makeScoped(inputDNA, + dnac::FileStream::AccessMode::Read, + dnac::FileStream::OpenMode::Binary); + auto reader = dnac::makeScoped(inStream.get()); + reader->read(); + + if (!dnac::Status::isOk()) { + std::cout << "Could not read input DNA file!\n"; + return -1; + } + + auto dnaReader = dnac::makeScoped(reader.get()); + + // Execute a one-off single command + dnac::RenameBlendShapeCommand renameBlendShapeA("brow_lateral_L", "wow"); + renameBlendShapeA.run(dnaReader.get()); + + return 0; +} diff --git a/dnacalib/DNACalib/include/dna/BinaryStreamReader.h b/dnacalib/DNACalib/include/dna/BinaryStreamReader.h new file mode 100644 index 0000000..6f45d00 --- /dev/null +++ b/dnacalib/DNACalib/include/dna/BinaryStreamReader.h @@ -0,0 +1,133 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/DataLayer.h" +#include "dna/Defs.h" +#include "dna/StreamReader.h" +#include "dna/types/Aliases.h" + +namespace dna { + +class DNAAPI BinaryStreamReader : public StreamReader { + public: + /** + @brief Factory method for creation of BinaryStreamReader + @param stream + Source stream from which data is going to be read. + @param layer + Specify the layer up to which the data needs to be loaded. + @note + The Definition data layer depends on and thus implicitly loads the Descriptor layer. + The Behavior data layer depends on and thus implicitly loads the Definition layer. + The Geometry data layer depends on and thus also implicitly loads the Definition layer. + @param maxLOD + The maximum level of details to be loaded. + @note + A value of zero indicates to load all LODs. + @warning + The maxLOD value must be less than the value returned by getLODCount. + @see getLODCount + @param memRes + Memory resource to be used for allocations. + @note + If a memory resource is not given, a default allocation mechanism will be used. + @warning + User is responsible for releasing the returned pointer by calling destroy. + @see destroy + */ + static BinaryStreamReader* create(BoundedIOStream* stream, + DataLayer layer = DataLayer::All, + std::uint16_t maxLOD = 0u, + MemoryResource* memRes = nullptr); + /** + @brief Factory method for creation of BinaryStreamReader + @param stream + Source stream from which data is going to be read. + @param layer + Specify the layer up to which the data needs to be loaded. + @note + The Definition data layer depends on and thus implicitly loads the Descriptor layer. + The Behavior data layer depends on and thus implicitly loads the Definition layer. + The Geometry data layer depends on and thus also implicitly loads the Definition layer. + @param maxLOD + The maximum level of details to be loaded. + @param minLOD + The minimum level of details to be loaded. + @note + A range of [0, LOD count - 1] for maxLOD / minLOD respectively indicates to load all LODs. + @warning + Both maxLOD and minLOD values must be less than the value returned by getLODCount. + @see getLODCount + @param memRes + Memory resource to be used for allocations. + @note + If a memory resource is not given, a default allocation mechanism will be used. + @warning + User is responsible for releasing the returned pointer by calling destroy. + @see destroy + */ + static BinaryStreamReader* create(BoundedIOStream* stream, + DataLayer layer, + std::uint16_t maxLOD, + std::uint16_t minLOD, + MemoryResource* memRes = nullptr); + /** + @brief Factory method for creation of BinaryStreamReader + @param stream + Source stream from which data is going to be read. + @param layer + Specify the layer up to which the data needs to be loaded. + @note + The Definition data layer depends on and thus implicitly loads the Descriptor layer. + The Behavior data layer depends on and thus implicitly loads the Definition layer. + The Geometry data layer depends on and thus also implicitly loads the Definition layer. + @param lods + An array specifying which exact lods to load. + @warning + All values in the array must be less than the value returned by getLODCount. + @see getLODCount + @param lodCount + The number of elements in the lods array. + @warning + There cannot be more elements in the array than the value returned by getLODCount. + @see getLODCount + @param memRes + Memory resource to be used for allocations. + @note + If a memory resource is not given, a default allocation mechanism will be used. + @warning + User is responsible for releasing the returned pointer by calling destroy. + @see destroy + */ + static BinaryStreamReader* create(BoundedIOStream* stream, + DataLayer layer, + std::uint16_t* lods, + std::uint16_t lodCount, + MemoryResource* memRes = nullptr); + /** + @brief Method for freeing a BinaryStreamReader instance. + @param instance + Instance of BinaryStreamReader to be freed. + @see create + */ + static void destroy(BinaryStreamReader* instance); + + ~BinaryStreamReader() override; +}; + +} // namespace dna + +namespace pma { + +template<> +struct DefaultInstanceCreator { + using type = pma::FactoryCreate; +}; + +template<> +struct DefaultInstanceDestroyer { + using type = pma::FactoryDestroy; +}; + +} // namespace pma diff --git a/dnacalib/DNACalib/include/dna/BinaryStreamWriter.h b/dnacalib/DNACalib/include/dna/BinaryStreamWriter.h new file mode 100644 index 0000000..8a685f5 --- /dev/null +++ b/dnacalib/DNACalib/include/dna/BinaryStreamWriter.h @@ -0,0 +1,51 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/Defs.h" +#include "dna/StreamWriter.h" +#include "dna/types/Aliases.h" + +namespace dna { + +class DNAAPI BinaryStreamWriter : public StreamWriter { + public: + /** + @brief Factory method for creation of BinaryStreamWriter + @param stream + Stream into which the data is going to be written. + @param memRes + Memory resource to be used for allocations. + @note + If a memory resource is not given, a default allocation mechanism will be used. + @warning + User is responsible for releasing the returned pointer by calling destroy. + @see destroy + */ + static BinaryStreamWriter* create(BoundedIOStream* stream, MemoryResource* memRes = nullptr); + /** + @brief Method for freeing a BinaryStreamWriter instance. + @param instance + Instance of BinaryStreamWriter to be freed. + @see create + */ + static void destroy(BinaryStreamWriter* instance); + + ~BinaryStreamWriter() override; +}; + +} // namespace dna + +namespace pma { + +template<> +struct DefaultInstanceCreator { + using type = pma::FactoryCreate; +}; + +template<> +struct DefaultInstanceDestroyer { + using type = pma::FactoryDestroy; +}; + +} // namespace pma diff --git a/dnacalib/DNACalib/include/dna/DataLayer.h b/dnacalib/DNACalib/include/dna/DataLayer.h new file mode 100644 index 0000000..8375183 --- /dev/null +++ b/dnacalib/DNACalib/include/dna/DataLayer.h @@ -0,0 +1,17 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +namespace dna { + +enum class DataLayer { + Descriptor, + Definition, // Includes Descriptor + Behavior, // Includes Descriptor and Definition + Geometry, // Includes Descriptor and Definition + GeometryWithoutBlendShapes, // Includes Descriptor and Definition + AllWithoutBlendShapes, // Includes everything except blend shapes from Geometry + All +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/Defs.h b/dnacalib/DNACalib/include/dna/Defs.h new file mode 100644 index 0000000..b10034e --- /dev/null +++ b/dnacalib/DNACalib/include/dna/Defs.h @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if defined(_WIN32) || defined(__CYGWIN__) + #if defined(__GNUC__) + #define DLL_EXPORT __attribute__((dllexport)) + #define DLL_IMPORT __attribute__((dllimport)) + #else + #define DLL_EXPORT __declspec(dllexport) + #define DLL_IMPORT __declspec(dllimport) + #endif +#elif defined(__GNUC__) + #define DLL_EXPORT __attribute__((visibility("default"))) + #define DLL_IMPORT DLL_EXPORT +#endif + +#if defined(DNAC_BUILD_SHARED) + // Build shared library + #define DNAAPI DLL_EXPORT +#elif defined(DNAC_SHARED) + // Use shared library + #define DNAAPI DLL_IMPORT +#else + // Build or use static library + #define DNAAPI +#endif diff --git a/dnacalib/DNACalib/include/dna/JSONStreamReader.h b/dnacalib/DNACalib/include/dna/JSONStreamReader.h new file mode 100644 index 0000000..c3d29d5 --- /dev/null +++ b/dnacalib/DNACalib/include/dna/JSONStreamReader.h @@ -0,0 +1,51 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/Defs.h" +#include "dna/StreamReader.h" +#include "dna/types/Aliases.h" + +namespace dna { + +class DNAAPI JSONStreamReader : public StreamReader { + public: + /** + @brief Factory method for creation of JSONStreamReader + @param stream + Source stream from which data is going to be read. + @param memRes + Memory resource to be used for allocations. + @note + If a memory resource is not given, a default allocation mechanism will be used. + @warning + User is responsible for releasing the returned pointer by calling destroy. + @see destroy + */ + static JSONStreamReader* create(BoundedIOStream* stream, MemoryResource* memRes = nullptr); + /** + @brief Method for freeing a JSONStreamReader instance. + @param instance + Instance of JSONStreamReader to be freed. + @see create + */ + static void destroy(JSONStreamReader* instance); + + ~JSONStreamReader() override; +}; + +} // namespace dna + +namespace pma { + +template<> +struct DefaultInstanceCreator { + using type = pma::FactoryCreate; +}; + +template<> +struct DefaultInstanceDestroyer { + using type = pma::FactoryDestroy; +}; + +} // namespace pma diff --git a/dnacalib/DNACalib/include/dna/JSONStreamWriter.h b/dnacalib/DNACalib/include/dna/JSONStreamWriter.h new file mode 100644 index 0000000..0219ea8 --- /dev/null +++ b/dnacalib/DNACalib/include/dna/JSONStreamWriter.h @@ -0,0 +1,54 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/Defs.h" +#include "dna/StreamWriter.h" +#include "dna/types/Aliases.h" + +namespace dna { + +class DNAAPI JSONStreamWriter : public StreamWriter { + public: + /** + @brief Factory method for creation of JSONStreamWriter + @param stream + Stream into which the data is going to be written. + @param indentWidth + Number of spaces to use for indentation. + @param memRes + Memory resource to be used for allocations. + @note + If a memory resource is not given, a default allocation mechanism will be used. + @warning + User is responsible for releasing the returned pointer by calling destroy. + @see destroy + */ + static JSONStreamWriter* create(BoundedIOStream* stream, std::uint32_t indentWidth = 4u, + MemoryResource* memRes = nullptr); + /** + @brief Method for freeing a JSONStreamWriter instance. + @param instance + Instance of JSONStreamWriter to be freed. + @see create + */ + static void destroy(JSONStreamWriter* instance); + + ~JSONStreamWriter() override; +}; + +} // namespace dna + +namespace pma { + +template<> +struct DefaultInstanceCreator { + using type = pma::FactoryCreate; +}; + +template<> +struct DefaultInstanceDestroyer { + using type = pma::FactoryDestroy; +}; + +} // namespace pma diff --git a/dnacalib/DNACalib/include/dna/Reader.h b/dnacalib/DNACalib/include/dna/Reader.h new file mode 100644 index 0000000..0ed0547 --- /dev/null +++ b/dnacalib/DNACalib/include/dna/Reader.h @@ -0,0 +1,32 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/Defs.h" +#include "dna/DataLayer.h" +#include "dna/layers/BehaviorReader.h" +#include "dna/layers/GeometryReader.h" + +namespace dna { + +/** + @brief The abstract Reader which its implementations are expected to inherit. + @note + This class combines the various different reader interfaces into a single interface. + The artificial separation into multiple interfaces mirrors the DNA file structure that + is separated into matching layers under the same names. As these layers can be + selectively loaded, it might be convenient to slice-off interfaces which layers were + not loaded. +*/ +class DNAAPI Reader : public BehaviorReader, public GeometryReader { + public: + ~Reader() override; + /** + @brief Unload all data of the specified layer and all layers dependent on it. + @param layer + Layer which data should be unloaded. + */ + virtual void unload(DataLayer layer) = 0; +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/StreamReader.h b/dnacalib/DNACalib/include/dna/StreamReader.h new file mode 100644 index 0000000..bad3d7e --- /dev/null +++ b/dnacalib/DNACalib/include/dna/StreamReader.h @@ -0,0 +1,26 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/DataLayer.h" +#include "dna/Defs.h" +#include "dna/Reader.h" +#include "dna/types/Aliases.h" + +namespace dna { + +class DNAAPI StreamReader : public Reader { + public: + static const sc::StatusCode SignatureMismatchError; + static const sc::StatusCode VersionMismatchError; + static const sc::StatusCode InvalidDataError; + + public: + ~StreamReader() override; + /** + @brief read data from stream into internal structures. + */ + virtual void read() = 0; +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/StreamWriter.h b/dnacalib/DNACalib/include/dna/StreamWriter.h new file mode 100644 index 0000000..87ba14e --- /dev/null +++ b/dnacalib/DNACalib/include/dna/StreamWriter.h @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/Defs.h" +#include "dna/Writer.h" +#include "dna/types/Aliases.h" + +namespace dna { + +class DNAAPI StreamWriter : public Writer { + public: + ~StreamWriter() override; + /** + @brief Write data to stream from internal structures. + */ + virtual void write() = 0; +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/Writer.h b/dnacalib/DNACalib/include/dna/Writer.h new file mode 100644 index 0000000..452cafe --- /dev/null +++ b/dnacalib/DNACalib/include/dna/Writer.h @@ -0,0 +1,44 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/DataLayer.h" +#include "dna/Defs.h" +#include "dna/layers/BehaviorWriter.h" +#include "dna/layers/GeometryWriter.h" +#include "dna/types/Aliases.h" + +namespace dna { + +class Reader; + +/** + @brief The abstract Writer which its implementations are expected to inherit. + @note + This class combines the various different writer interfaces into a single interface. + The artificial separation into multiple interfaces in this case just mirrors the + structure of the Reader hierarchy, as it's not possible to selectively write only + specific layers. +*/ +class DNAAPI Writer : public BehaviorWriter, public GeometryWriter { + public: + ~Writer() override; + /** + @brief Initialize the Writer from the given Reader. + @note + This function copies all the data from the given Reader into the Writer instance, + by calling each getter function of the Reader, and passing the return values to + the matching setter functions in the Writer. + It is implemented in the abstract class itself to provide the functionality for + all DNA Writers. + @param source + The source DNA Reader from which the data needs to be copied. + @param layer + Limit which layers should be taken over from the given source reader. + @param memRes + Optional memory resource to use for temporary allocations during copying. + */ + void setFrom(const Reader* source, DataLayer layer = DataLayer::All, MemoryResource* memRes = nullptr); +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/layers/BehaviorReader.h b/dnacalib/DNACalib/include/dna/layers/BehaviorReader.h new file mode 100644 index 0000000..5032907 --- /dev/null +++ b/dnacalib/DNACalib/include/dna/layers/BehaviorReader.h @@ -0,0 +1,216 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/Defs.h" +#include "dna/layers/DefinitionReader.h" +#include "dna/types/Aliases.h" + +#include + +namespace dna { + +/** + @brief Read-only accessors for DNA attributes that define the rig's evaluation. + @warning + Implementors should inherit from Reader itself and not this class. + @see Reader +*/ +class DNAAPI BehaviorReader : public virtual DefinitionReader { + protected: + virtual ~BehaviorReader(); + + public: + /** + @brief Input indices used for mapping gui to raw controls. + @return View over the array of input indices. + */ + virtual ConstArrayView getGUIToRawInputIndices() const = 0; + /** + @brief Output indices used for mapping gui to raw controls. + @return View over the array of output indices. + */ + virtual ConstArrayView getGUIToRawOutputIndices() const = 0; + /** + @brief Filter values(lower-bounds) used to decide whether a particular + entry should be evaluated or not during gui to raw control mapping. + @return View over the array of filter values. + */ + virtual ConstArrayView getGUIToRawFromValues() const = 0; + /** + @brief Filter values(upper-bounds) used to decide whether a particular + entry should be evaluated or not during gui to raw control mapping. + @return View over the array of filter values. + */ + virtual ConstArrayView getGUIToRawToValues() const = 0; + /** + @brief Computational values(slope/gradient) used for calculating the + output value during gui to raw control mapping. + @return View over the array of computational values. + */ + virtual ConstArrayView getGUIToRawSlopeValues() const = 0; + /** + @brief Computational values(vertical intercept) used for calculating the + output value during gui to raw control mapping. + @return View over the array of computational values. + */ + virtual ConstArrayView getGUIToRawCutValues() const = 0; + /** + @brief The number of distinct PSD expressions. + */ + virtual std::uint16_t getPSDCount() const = 0; + /** + @brief PSD(input) indices. + @return View over the array of PSD indices. + */ + virtual ConstArrayView getPSDRowIndices() const = 0; + /** + @brief Control(input) indices. + @return View over the array of control indices. + */ + virtual ConstArrayView getPSDColumnIndices() const = 0; + /** + @brief Weights associated with each PSD row and column pair. + @return View over the array of weights. + */ + virtual ConstArrayView getPSDValues() const = 0; + /** + @brief Number of rows in the entire, uncompressed joint matrix. + */ + virtual std::uint16_t getJointRowCount() const = 0; + /** + @brief Number of columns in the entire, uncompressed joint matrix. + */ + virtual std::uint16_t getJointColumnCount() const = 0; + /** + @brief Joint attribute indices (output indices) for the requested LOD. + @return View over the array of joint indices. + */ + virtual ConstArrayView getJointVariableAttributeIndices(std::uint16_t lod) const = 0; + /** + @brief Number of joint groups present in the entire joint matrix. + */ + virtual std::uint16_t getJointGroupCount() const = 0; + /** + @brief Number of rows per each level of detail for the requested joint group. + @note + Each element's position represents the level itself, while the value denotes + the number of rows within the joint group belonging to that level. e.g.: + [12, 9, 3] + | | + LOD-2 contains first 3 rows + | + LOD-1 contains first 9 rows + + LOD-0 contains first 12 rows + @param jointGroupIndex + A joint group's position in the zero-indexed array of joint groups. + @warning + jointGroupIndex must be less than the value returned by getJointGroupCount. + @return View over the array of LOD bounds. + */ + virtual ConstArrayView getJointGroupLODs(std::uint16_t jointGroupIndex) const = 0; + /** + @brief Column indices that the requested joint group contains. + @note + The column indices point into the entire, uncompressed joint matrix. + @param jointGroupIndex + A joint group's position in the zero-indexed array of joint groups. + @warning + jointGroupIndex must be less than the value returned by getJointGroupCount. + @return View over the array of column indices. + */ + virtual ConstArrayView getJointGroupInputIndices(std::uint16_t jointGroupIndex) const = 0; + /** + @brief Row indices that the requested joint group contains. + @note + The row indices point into the entire, uncompressed joint matrix. + @param jointGroupIndex + A joint group's position in the zero-indexed array of joint groups. + @warning + jointGroupIndex must be less than the value returned by getJointGroupCount. + @return View over the array of row indices. + */ + virtual ConstArrayView getJointGroupOutputIndices(std::uint16_t jointGroupIndex) const = 0; + /** + @brief Values that the requested joint group contains. + @param jointGroupIndex + A joint group's position in the zero-indexed array of joint groups. + @warning + jointGroupIndex must be less than the value returned by getJointGroupCount. + @return View over the array of values. + */ + virtual ConstArrayView getJointGroupValues(std::uint16_t jointGroupIndex) const = 0; + /** + @brief Joint indices that the requested joint group contains. + @note + These joint indices can be used to get the joint names through DefinitionReader::getJointName. + @param jointGroupIndex + A joint group's position in the zero-indexed array of joint groups. + @warning + jointGroupIndex must be less than the value returned by getJointGroupCount. + @return View over the array of joint indices. + @see DefinitionReader + */ + virtual ConstArrayView getJointGroupJointIndices(std::uint16_t jointGroupIndex) const = 0; + /** + @brief Input index count per each level of detail for blend shape channels. + @note + Each element's position represents the level itself (e.g. [0,1,2,3,4,5] Value 0 is LOD with highest of details, + value 5 is LOD with lowest details), while the value denotes the number of input indices belonging to that level. + @warning + These LOD values are not interchangeable with the LOD indices from DefinitionReader::getBlendShapeChannelIndicesForLOD. + @return View over the array of LOD bounds. + */ + virtual ConstArrayView getBlendShapeChannelLODs() const = 0; + /** + @brief Input indices used to index into the input vector. + @return View over the array of input indices. + */ + virtual ConstArrayView getBlendShapeChannelInputIndices() const = 0; + /** + @brief Output indices specify the positions of blend shape channel output values. + @return View over the array of output indices. + */ + virtual ConstArrayView getBlendShapeChannelOutputIndices() const = 0; + /** + @brief Row count per each level of detail for animated maps. + @note + Each element's position represents the level itself (e.g. [0,1,2,3,4,5] Value 0 is LOD with highest of details, + value 5 is LOD with lowest details), while the value denotes the number of rows (within the conditional table), + belonging to that level. + @return View over the array of LOD bounds. + */ + virtual ConstArrayView getAnimatedMapLODs() const = 0; + /** + @brief Input indices used to index into the array of input values. + @return View over the array of input indices. + */ + virtual ConstArrayView getAnimatedMapInputIndices() const = 0; + /** + @brief Output indices that specify the computed output value's position. + @return View over the array of output indices. + */ + virtual ConstArrayView getAnimatedMapOutputIndices() const = 0; + /** + @brief Filter values(lower-bounds) used to decide whether a particular + entry should be evaluated or not. + @return View over the array of filter values. + */ + virtual ConstArrayView getAnimatedMapFromValues() const = 0; + /** + @brief Filter values(upper-bounds) used to decide whether a particular + entry should be evaluated or not. + @return View over the array of filter values. + */ + virtual ConstArrayView getAnimatedMapToValues() const = 0; + /** + @brief Computational values(slope/gradient) used for calculating the output value. + @return View over the array of computational values. + */ + virtual ConstArrayView getAnimatedMapSlopeValues() const = 0; + /** + @brief Computational values(vertical intercept) used for calculating the output value. + @return View over the array of computational values. + */ + virtual ConstArrayView getAnimatedMapCutValues() const = 0; +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/layers/BehaviorWriter.h b/dnacalib/DNACalib/include/dna/layers/BehaviorWriter.h new file mode 100644 index 0000000..fec0803 --- /dev/null +++ b/dnacalib/DNACalib/include/dna/layers/BehaviorWriter.h @@ -0,0 +1,300 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/Defs.h" +#include "dna/layers/DefinitionWriter.h" +#include "dna/types/Aliases.h" + +#include + +namespace dna { + +/** + @brief Write-only accessors for DNA attributes that define the rig's evaluation. + @warning + Implementors should inherit from Writer itself and not this class. + @see Writer +*/ +class DNAAPI BehaviorWriter : public virtual DefinitionWriter { + protected: + virtual ~BehaviorWriter(); + + public: + /** + @brief Input indices used for mapping gui to raw controls. + @param inputIndices + The source address from which the input indices are to be copied. + @param count + The number of input indices to copy. + */ + virtual void setGUIToRawInputIndices(const std::uint16_t* inputIndices, std::uint16_t count) = 0; + /** + @brief Output indices used for mapping gui to raw controls. + @param outputIndices + The source address from which the output indices are to be copied. + @param count + The number of output indices to copy. + */ + virtual void setGUIToRawOutputIndices(const std::uint16_t* outputIndices, std::uint16_t count) = 0; + /** + @brief Filter values(lower-bounds) used to decide whether a particular + entry should be evaluated or not during gui to raw control mapping. + @param fromValues + The source address from which the filter values are to be copied. + @param count + The number of filter values to copy. + */ + virtual void setGUIToRawFromValues(const float* fromValues, std::uint16_t count) = 0; + /** + @brief Filter values(upper-bounds) used to decide whether a particular + entry should be evaluated or not during gui to raw control mapping. + @param toValues + The source address from which the filter values are to be copied. + @param count + The number of filter values to copy. + */ + virtual void setGUIToRawToValues(const float* toValues, std::uint16_t count) = 0; + /** + @brief Computational values(slope/gradient) used for calculating the + output value during gui to raw control mapping. + @param slopeValues + The source address from which the computational values are to be copied. + @param count + The number of computational values to copy. + */ + virtual void setGUIToRawSlopeValues(const float* slopeValues, std::uint16_t count) = 0; + /** + @brief Computational values(vertical intercept) used for calculating the + output value during gui to raw control mapping. + @param cutValues + The source address from which the computational values are to be copied. + @param count + The number of computational values to copy. + */ + virtual void setGUIToRawCutValues(const float* cutValues, std::uint16_t count) = 0; + /** + @brief The number of distinct PSD expressions. + */ + virtual void setPSDCount(std::uint16_t count) = 0; + /** + @brief PSD(input) indices which will become the rows of the PSD matrix. + @param rowIndices + The source address from which the PSD indices are to be copied. + @param count + The number of PSD indices to copy. + */ + virtual void setPSDRowIndices(const std::uint16_t* rowIndices, std::uint16_t count) = 0; + /** + @brief Control(input) indices which will become the columns of the PSD matrix. + @param columnIndices + The source address from which the control indices are to be copied. + @param count + The number of control indices to copy. + */ + virtual void setPSDColumnIndices(const std::uint16_t* columnIndices, std::uint16_t count) = 0; + /** + @brief Weights associated with each PSD row and column pair. + @param weights + The source address from which the weight values are to be copied. + @param count + The number of weight values to copy. + */ + virtual void setPSDValues(const float* weights, std::uint16_t count) = 0; + /** + @brief Number of rows in the entire, uncompressed joint matrix. + */ + virtual void setJointRowCount(std::uint16_t rowCount) = 0; + /** + @brief Number of columns in the entire, uncompressed joint matrix. + */ + virtual void setJointColumnCount(std::uint16_t columnCount) = 0; + /** + @brief Delete all joint groups. + */ + virtual void clearJointGroups() = 0; + /** + @brief Delete the specified joint group. + @param jointGroupIndex + A joint group's position in the zero-indexed array of joint groups. + @warning + jointGroupIndex must be less than the value returned by getJointGroupCount. + */ + virtual void deleteJointGroup(std::uint16_t jointGroupIndex) = 0; + /** + @brief Number of rows per each level of detail for the specified joint group. + @note + Each element's position represents the level itself, while the value denotes + the number of rows within the joint group belonging to that level. e.g.: + [12, 9, 3] + | | + LOD-2 contains first 3 rows + | + LOD-1 contains first 9 rows + + LOD-0 contains first 12 rows + @param jointGroupIndex + A joint group's position in the zero-indexed array of joint groups. + @note + The joint group storage will be implicitly resized (if needed) to provide + storage for the number of joint groups that is inferred from the specified index. + @param lods + The source address from which the lod bounds are to be copied. + @param count + The number of lod bounds to copy. + */ + virtual void setJointGroupLODs(std::uint16_t jointGroupIndex, const std::uint16_t* lods, std::uint16_t count) = 0; + /** + @brief Column indices that the specified joint group contains. + @note + The column indices point into the entire, uncompressed joint matrix. + @param jointGroupIndex + A joint group's position in the zero-indexed array of joint groups. + @note + The joint group storage will be implicitly resized (if needed) to provide + storage for the number of joint groups that is inferred from the specified index. + @param inputIndices + The source address from which the column indices are to be copied. + @param count + The number of column indices to copy. + */ + virtual void setJointGroupInputIndices(std::uint16_t jointGroupIndex, + const std::uint16_t* inputIndices, + std::uint16_t count) = 0; + /** + @brief Row indices that the specified joint group contains. + @note + The row indices point into the entire, uncompressed joint matrix. + @param jointGroupIndex + A joint group's position in the zero-indexed array of joint groups. + @note + The joint group storage will be implicitly resized (if needed) to provide + storage for the number of joint groups that is inferred from the specified index. + @param outputIndices + The source address from which the row indices are to be copied. + @param count + The number of row indices to copy. + */ + virtual void setJointGroupOutputIndices(std::uint16_t jointGroupIndex, + const std::uint16_t* outputIndices, + std::uint16_t count) = 0; + /** + @brief Values that the specified joint group contains. + @param jointGroupIndex + A joint group's position in the zero-indexed array of joint groups. + @note + The joint group storage will be implicitly resized (if needed) to provide + storage for the number of joint groups that is inferred from the specified index. + @param values + The source address from which the values are to be copied. + @param count + The number of values to copy. + */ + virtual void setJointGroupValues(std::uint16_t jointGroupIndex, const float* values, std::uint32_t count) = 0; + /** + @brief Joint indices that the specified joint group contains. + @param jointGroupIndex + A joint group's position in the zero-indexed array of joint groups. + @note + The joint group storage will be implicitly resized (if needed) to provide + storage for the number of joint groups that is inferred from the specified index. + @param jointIndices + The source address from which the joint indices are to be copied. + @param count + The number of joint indices to copy. + */ + virtual void setJointGroupJointIndices(std::uint16_t jointGroupIndex, + const std::uint16_t* jointIndices, + std::uint16_t count) = 0; + /** + @brief Input index count per each level of detail for blend shapes. + @note + Each element's position represents the level itself (e.g. [0,1,2,3,4,5] Value 0 is LOD with highest of details, + value 5 is LOD with lowest details), while the value denotes the number of input indices belonging to that level. + @param lods + The source address from which the lod bounds are to be copied. + @param count + The number of lod bounds to copy. + @warning + The LOD values set here are not interchangeable with the LOD indices set in DefinitionWriter::setBlendShapeNameIndices + and DefinitionWriter::setLODBlendShapeMapping + */ + virtual void setBlendShapeChannelLODs(const std::uint16_t* lods, std::uint16_t count) = 0; + /** + @brief Input indices used to index into the input vector. + @param inputIndices + The source address from which the input indices are to be copied. + @param count + The number of input indices to copy. + */ + virtual void setBlendShapeChannelInputIndices(const std::uint16_t* inputIndices, std::uint16_t count) = 0; + /** + @brief Output indices specify the positions of blend shape output values. + @param outputIndices + The source address from which the output indices are to be copied. + @param count + The number of output indices to copy. + */ + virtual void setBlendShapeChannelOutputIndices(const std::uint16_t* outputIndices, std::uint16_t count) = 0; + /** + @brief Row count per each level of detail for animated maps. + @note + Each element's position represents the level itself (e.g. [0,1,2,3,4,5] Value 0 is LOD with highest of details, + value 5 is LOD with lowest details), while the value denotes the number of rows (within the conditional table), + belonging to that level. + @param lods + The source address from which the lod bounds are to be copied. + @param count + The number of lod bounds to copy. + */ + virtual void setAnimatedMapLODs(const std::uint16_t* lods, std::uint16_t count) = 0; + /** + @brief Input indices used to index into the array of input values. + @param inputIndices + The source address from which the input indices are to be copied. + @param count + The number of input indices to copy. + */ + virtual void setAnimatedMapInputIndices(const std::uint16_t* inputIndices, std::uint16_t count) = 0; + /** + @brief Output indices that specify the computed output value's position. + @param outputIndices + The source address from which the output indices are to be copied. + @param count + The number of output indices to copy. + */ + virtual void setAnimatedMapOutputIndices(const std::uint16_t* outputIndices, std::uint16_t count) = 0; + /** + @brief Filter values(lower-bounds) used to decide whether a particular + entry should be evaluated or not. + @param fromValues + The source address from which the filter values are to be copied. + @param count + The number of filter values to copy. + */ + virtual void setAnimatedMapFromValues(const float* fromValues, std::uint16_t count) = 0; + /** + @brief Filter values(upper-bounds) used to decide whether a particular + entry should be evaluated or not. + @param toValues + The source address from which the filter values are to be copied. + @param count + The number of filter values to copy. + */ + virtual void setAnimatedMapToValues(const float* toValues, std::uint16_t count) = 0; + /** + @brief Computational values(slope/gradient) used for calculating the output value. + @param slopeValues + The source address from which the computational values are to be copied. + @param count + The number of computational values to copy. + */ + virtual void setAnimatedMapSlopeValues(const float* slopeValues, std::uint16_t count) = 0; + /** + @brief Computational values(vertical intercept) used for calculating the output value. + @param cutValues + The source address from which the computational values are to be copied. + @param count + The number of computational values to copy. + */ + virtual void setAnimatedMapCutValues(const float* cutValues, std::uint16_t count) = 0; +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/layers/DefinitionReader.h b/dnacalib/DNACalib/include/dna/layers/DefinitionReader.h new file mode 100644 index 0000000..b77b35c --- /dev/null +++ b/dnacalib/DNACalib/include/dna/layers/DefinitionReader.h @@ -0,0 +1,284 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/Defs.h" +#include "dna/layers/DescriptorReader.h" +#include "dna/types/Aliases.h" +#include "dna/types/Vector3.h" + +#include + +namespace dna { + +/** + @brief Mapping that associates a blend shape channel to it's mesh. +*/ +struct MeshBlendShapeChannelMapping { + std::uint16_t meshIndex; + std::uint16_t blendShapeChannelIndex; +}; + +/** + @brief Read-only accessors for DNA attributes that represent the rig's static data. + @warning + Implementors should inherit from Reader itself and not this class. + @see Reader +*/ +class DNAAPI DefinitionReader : public DescriptorReader { + protected: + virtual ~DefinitionReader(); + + public: + virtual std::uint16_t getGUIControlCount() const = 0; + /** + @brief Name of the requested GUI control. + @param index + A name's position in the zero-indexed array of GUI control names. + @warning + The index must be less than the value returned by getGUIControlCount. + @return View over the GUI control name string. + */ + virtual StringView getGUIControlName(std::uint16_t index) const = 0; + + virtual std::uint16_t getRawControlCount() const = 0; + /** + @brief Name of the requested raw control. + @param index + A name's position in the zero-indexed array of raw control names. + @warning + The index must be less than the value returned by getRawControlCount. + @return View over the control name string. + */ + virtual StringView getRawControlName(std::uint16_t index) const = 0; + + virtual std::uint16_t getJointCount() const = 0; + /** + @brief Name of the requested joint. + @param index + A name's position in the zero-indexed array of joint names. + @warning + The index must be less than the value returned by getJointCount. + @return View over the joint name string. + */ + virtual StringView getJointName(std::uint16_t index) const = 0; + /** + @brief Number of joint index lists. + @note + This value is useful only in the context of DefinitionWriter. + */ + virtual std::uint16_t getJointIndexListCount() const = 0; + /** + @brief List of joint indices for the specified LOD. + @param lod + The level of detail which joints are being requested. + @warning + The lod index must be less than the value returned by getLODCount. + @return View over the joint indices. + @see getLODCount + @see getJointName + */ + virtual ConstArrayView getJointIndicesForLOD(std::uint16_t lod) const = 0; + /** + @brief Index of the requested joint's parent. + @note + The joint hierarchy may be traversed and reconstructed using this function. Example: + Joint names: [A, B, C, D, E, F, G, H, I] + Hierarchy: [0, 0, 0, 1, 1, 4, 2, 6, 2] + Describes the following hierarchy: + A + + B + | + D + | + E + | + F + + C + + G + | + H + + I + + Requesting the parent index of joint 5 (joint name: F) would return 4 (joint name: E). + Requesting the parent index of the root joint: 0 (joint name: A) would return the same index 0. + An out of bounds request (an index greater than the number of joints returns UINT16_MAX). + @param index + The joint index which parent is being requested. + */ + virtual std::uint16_t getJointParentIndex(std::uint16_t index) const = 0; + + virtual std::uint16_t getBlendShapeChannelCount() const = 0; + /** + @brief Name of the requested blend shape channel. + @param index + A name's position in the zero-indexed array of blend shape channel names. + @warning + The index must be less than the value returned by BlendShapeChannelExtentReader::getBlendShapeChannelCount. + @return View over the blend shape channel name string. + */ + virtual StringView getBlendShapeChannelName(std::uint16_t index) const = 0; + /** + @brief Number of blend shape channel index lists. + @note + This value is useful only in the context of DefinitionWriter. + */ + virtual std::uint16_t getBlendShapeChannelIndexListCount() const = 0; + /** + @brief List of blend shape channel indices for the specified LOD. + @param lod + The level of detail which blend shape channels are being requested. + @warning + The lod index must be less than the value returned by getLODCount. + @return View over the blend shape channel indices. + @warning + These LOD indices are not interchangeable with the LOD values from BehaviorReader::getBlendShapeChannelLODs. + @see getLODCount + @see getBlendShapeChannelName + */ + virtual ConstArrayView getBlendShapeChannelIndicesForLOD(std::uint16_t lod) const = 0; + + virtual std::uint16_t getAnimatedMapCount() const = 0; + /** + @brief Name of the requested animated map. + @param index + A name's position in the zero-indexed array of animated map names. + @warning + The index must be less than the value returned by getAnimatedMapCount. + @return View over the animated map name string. + */ + virtual StringView getAnimatedMapName(std::uint16_t index) const = 0; + /** + @brief Number of animated map index lists. + @note + This value is useful only in the context of DefinitionWriter. + */ + virtual std::uint16_t getAnimatedMapIndexListCount() const = 0; + /** + @brief List of animated map indices for the specified LOD. + @param lod + The level of detail which animated maps are being requested. + @warning + The lod index must be less than the value returned by getLODCount. + @return View over the animated map indices. + @see getLODCount + @see getAnimatedMapName + */ + virtual ConstArrayView getAnimatedMapIndicesForLOD(std::uint16_t lod) const = 0; + + virtual std::uint16_t getMeshCount() const = 0; + /** + @brief Name of the requested mesh. + @param index + A name's position in the zero-indexed array of mesh names. + @warning + The index must be less than the value returned by getMeshCount. + @return View over the mesh name string. + */ + virtual StringView getMeshName(std::uint16_t index) const = 0; + /** + @brief Number of mesh index lists. + @note + This value is useful only in the context of DefinitionWriter. + */ + virtual std::uint16_t getMeshIndexListCount() const = 0; + /** + @brief List of mesh indices for the specified LOD. + @param lod + The level of detail which meshes are being requested. + @warning + The lod index must be less than the value returned by getLODCount. + @return View over the mesh indices. + @see getLODCount + @see getMeshName + */ + virtual ConstArrayView getMeshIndicesForLOD(std::uint16_t lod) const = 0; + /** + @brief Number of mesh-blend shape channel mapping items. + */ + virtual std::uint16_t getMeshBlendShapeChannelMappingCount() const = 0; + /** + @param index + A mapping's position in the zero-indexed array of mesh-blend shape channel mappings. + @warning + The index must be less than the value returned by getMeshBlendShapeChannelMappingCount. + @return A structure holding the mesh index and the associated blend shape channel index. + */ + virtual MeshBlendShapeChannelMapping getMeshBlendShapeChannelMapping(std::uint16_t index) const = 0; + /** + @brief List of mesh-blend shape channel mapping indices for the specified LOD. + @note + The indices from this list can be used with the getMeshBlendShapeChannelMapping API + to retrieve individual mapping items. + @param lod + The level of detail which meshes are being requested. + @warning + The lod index must be less than the value returned by getLODCount. + @return View over the mesh blend shape channel mapping indices. + @see getLODCount + @see getMeshBlendShapeChannelMapping + */ + virtual ConstArrayView getMeshBlendShapeChannelMappingIndicesForLOD(std::uint16_t lod) const = 0; + /** + @param index + A joint's position in the zero-indexed array of joint translations. + @warning + The index must be less than the value returned by getJointCount. + @return The joint's translation (x, y, z). + */ + virtual Vector3 getNeutralJointTranslation(std::uint16_t index) const = 0; + /** + @brief List of all translation X values. + @note + This is an advanced API for performance critical access, for more convenient usage see getNeutralJointTranslation. + @return View over all X values. + @see getNeutralJointTranslation + */ + virtual ConstArrayView getNeutralJointTranslationXs() const = 0; + /** + @brief List of all translation Y values. + @note + This is an advanced API for performance critical access, for more convenient usage see getNeutralJointTranslation. + @return View over all Y values. + @see getNeutralJointTranslation + */ + virtual ConstArrayView getNeutralJointTranslationYs() const = 0; + /** + @brief List of all translation Z values. + @note + This is an advanced API for performance critical access, for more convenient usage see getNeutralJointTranslation. + @return View over all Z values. + @see getNeutralJointTranslation + */ + virtual ConstArrayView getNeutralJointTranslationZs() const = 0; + /** + @param index + A joint's position in the zero-indexed array of joint rotations. + @warning + The index must be less than the value returned by getJointCount. + @return The joint's rotation (x, y, z). + */ + virtual Vector3 getNeutralJointRotation(std::uint16_t index) const = 0; + /** + @brief List of all rotation X values. + @note + This is an advanced API for performance critical access, for more convenient usage see getNeutralJointRotation. + @return View over all X values. + @see getNeutralJointRotation + */ + virtual ConstArrayView getNeutralJointRotationXs() const = 0; + /** + @brief List of all rotation Y values. + @note + This is an advanced API for performance critical access, for more convenient usage see getNeutralJointRotation. + @return View over all Y values. + @see getNeutralJointRotation + */ + virtual ConstArrayView getNeutralJointRotationYs() const = 0; + /** + @brief List of all rotation Z values. + @note + This is an advanced API for performance critical access, for more convenient usage see getNeutralJointRotation. + @return View over all Z values. + @see getNeutralJointRotation + */ + virtual ConstArrayView getNeutralJointRotationZs() const = 0; +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/layers/DefinitionWriter.h b/dnacalib/DNACalib/include/dna/layers/DefinitionWriter.h new file mode 100644 index 0000000..db4dc84 --- /dev/null +++ b/dnacalib/DNACalib/include/dna/layers/DefinitionWriter.h @@ -0,0 +1,328 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/Defs.h" +#include "dna/layers/DescriptorWriter.h" +#include "dna/types/Aliases.h" +#include "dna/types/Vector3.h" + +#include + +namespace dna { + +/** + @brief Write-only accessors for DNA attributes that represent the rig's static data. + @warning + Implementors should inherit from Writer itself and not this class. + @see Writer +*/ +class DNAAPI DefinitionWriter : public DescriptorWriter { + protected: + virtual ~DefinitionWriter(); + + public: + /** + @brief Delete all stored GUI control names. + */ + virtual void clearGUIControlNames() = 0; + /** + @brief Name of the specified GUI control. + @param index + A name's position in the zero-indexed array of GUI control names. + @note + The control name storage will be implicitly resized (if needed) to provide + storage for the number of names that is inferred from the specified index. + @param name + A null-terminated string. + @note + The passed in name is copied, which will involve an additional allocation. + */ + virtual void setGUIControlName(std::uint16_t index, const char* name) = 0; + /** + @brief Delete all stored raw control names. + */ + virtual void clearRawControlNames() = 0; + /** + @brief Name of the specified raw control. + @param index + A name's position in the zero-indexed array of raw control names. + @note + The control name storage will be implicitly resized (if needed) to provide + storage for the number of names that is inferred from the specified index. + @param name + A null-terminated string. + @note + The passed in name is copied, which will involve an additional allocation. + */ + virtual void setRawControlName(std::uint16_t index, const char* name) = 0; + /** + @brief Delete all stored joint names. + */ + virtual void clearJointNames() = 0; + /** + @brief Name of the specified joint. + @param index + A name's position in the zero-indexed array of joint names. + @note + The joint name storage will be implicitly resized (if needed) to provide + storage for the number of names that is inferred from the specified index. + @param name + A null-terminated string. + @note + The passed in name is copied, which will involve an additional allocation. + */ + virtual void setJointName(std::uint16_t index, const char* name) = 0; + /** + @brief Delete all stored joint indices. + */ + virtual void clearJointIndices() = 0; + /** + @brief Store a list of joint indices onto a specified index. + @param index + A position in a zero-indexed array where joint indices are stored. + @note + The index denotes the position of an entire joint index list, + not the position of it's individual elements, i.e. the row index in a 2D + matrix of joint indices. + @note + The joint index storage will be implicitly resized (if needed) to provide + storage for the number of joint indices that is inferred from the specified index. + @param jointIndices + The source address from which the joint indices are to be copied. + @note + These indices can be used to access joint names through DefinitionReader::getJointName. + @param count + The number of joint indices to copy. + */ + virtual void setJointIndices(std::uint16_t index, const std::uint16_t* jointIndices, std::uint16_t count) = 0; + /** + @brief Delete all stored LOD to joint list index mapping entries. + */ + virtual void clearLODJointMappings() = 0; + /** + @brief Set which joints belong to which level of detail. + @param lod + The actual level of detail to which the joints are being associated. + @param index + The index onto which joints indices were assigned using setJointIndices. + @see setJointIndices + */ + virtual void setLODJointMapping(std::uint16_t lod, std::uint16_t index) = 0; + /** + @brief Delete all stored blend shape channel names. + */ + virtual void clearBlendShapeChannelNames() = 0; + /** + @brief Name of the specified blend shape channel. + @param index + A name's position in the zero-indexed array of blend shape channel names. + @note + The blend shape channel name storage will be implicitly resized (if needed) to provide + storage for the number of names that is inferred from the specified index. + @param name + A null-terminated string. + @note + The passed in name is copied, which will involve an additional allocation. + */ + virtual void setBlendShapeChannelName(std::uint16_t index, const char* name) = 0; + /** + @brief Delete all stored blend shape channel indices. + */ + virtual void clearBlendShapeChannelIndices() = 0; + /** + @brief Store a list of blend shape channel name indices onto a specified index. + @param index + A position in a zero-indexed array where blend shape channel name indices are stored. + @note + The index denotes the position of an entire blend shape channel index list, + not the position of it's individual elements, i.e. the row index in a 2D + matrix of blend shape channel indices. + @note + The blend shape channel index storage will be implicitly resized (if needed) to provide storage + for the number of blend shape channel name indices that is inferred from the specified index. + @param blendShapeChannelIndices + The source address from which the blend shape channel name indices are to be copied. + @note + These indices can be used to access blend shape channel names through DefinitionReader::getBlendShapeChannelName. + @param count + The number of blend shape channel name indices to copy. + */ + virtual void setBlendShapeChannelIndices(std::uint16_t index, + const std::uint16_t* blendShapeChannelIndices, + std::uint16_t count) = 0; + /** + @brief Delete all stored LOD to blend shape channel list index mapping entries. + */ + virtual void clearLODBlendShapeChannelMappings() = 0; + /** + @brief Set which blend shape channels belong to which level of detail. + @param lod + The actual level of detail to which the blend shape channels are being associated. + @param index + The index onto which blend shape channel name indices were assigned using setBlendShapeChannelIndices. + @warning + The LOD indices set here are not interchangeable with the LOD values set in BehaviorWriter::setBlendShapeChannelLODs. + @see setBlendShapeChannelIndices + */ + virtual void setLODBlendShapeChannelMapping(std::uint16_t lod, std::uint16_t index) = 0; + /** + @brief Delete all stored animated map names. + */ + virtual void clearAnimatedMapNames() = 0; + /** + @brief Name of the specified animated map. + @param index + A name's position in the zero-indexed array of animated map names. + @note + The animated map name storage will be implicitly resized (if needed) to provide + storage for the number of names that is inferred from the specified index. + @param name + A null-terminated string. + @note + The passed in name is copied, which will involve an additional allocation. + */ + virtual void setAnimatedMapName(std::uint16_t index, const char* name) = 0; + /** + @brief Delete all stored animated map indices. + */ + virtual void clearAnimatedMapIndices() = 0; + /** + @brief Store a list of animated map name indices onto a specified index. + @param index + A position in a zero-indexed array where animated map name indices are stored. + @note + The index denotes the position of an entire animated map index list, + not the position of it's individual elements, i.e. the row index in a 2D + matrix of animated map indices. + @note + The animated map index storage will be implicitly resized (if needed) to provide storage + for the number of animated map name indices that is inferred from the specified index. + @param animatedMapIndices + The source address from which the animated map name indices are to be copied. + @note + These indices can be used to access animated map names through DefinitionReader::getAnimatedMapName. + @param count + The number of animated map name indices to copy. + */ + virtual void setAnimatedMapIndices(std::uint16_t index, const std::uint16_t* animatedMapIndices, std::uint16_t count) = 0; + /** + @brief Delete all stored LOD to animated map list index mapping entries. + */ + virtual void clearLODAnimatedMapMappings() = 0; + /** + @brief Set which animated maps belong to which level of detail. + @param lod + The actual level of detail to which the animated maps are being associated. + @param index + The index onto which animated map indices were assigned using setAnimatedMapIndices. + @see setAnimatedMapIndices + */ + virtual void setLODAnimatedMapMapping(std::uint16_t lod, std::uint16_t index) = 0; + /** + @brief Delete all stored mesh names. + */ + virtual void clearMeshNames() = 0; + /** + @brief Name of the specified mesh. + @param index + A name's position in the zero-indexed array of mesh names. + @note + The mesh name storage will be implicitly resized (if needed) to provide + storage for the number of names that is inferred from the specified index. + @param name + A null-terminated string. + @note + The passed in name is copied, which will involve an additional allocation. + */ + virtual void setMeshName(std::uint16_t index, const char* name) = 0; + /** + @brief Delete all stored mesh indices. + */ + virtual void clearMeshIndices() = 0; + /** + @brief Store a list of mesh name indices onto a specified index. + @param index + A position in a zero-indexed array where mesh name indices are stored. + @note + The index denotes the position of an entire mesh index list, + not the position of it's individual elements, i.e. the row index in a 2D + matrix of mesh indices. + @note + The mesh index storage will be implicitly resized (if needed) to provide storage + for the number of mesh name indices that is inferred from the specified index. + @param meshIndices + The source address from which the mesh name indices are to be copied. + @note + These indices can be used to access mesh names through DefinitionReader::getMeshName. + @param count + The number of mesh name indices to copy. + */ + virtual void setMeshIndices(std::uint16_t index, const std::uint16_t* meshIndices, std::uint16_t count) = 0; + /** + @brief Delete all stored LOD to mesh list index mapping entries. + */ + virtual void clearLODMeshMappings() = 0; + /** + @brief Set which meshes belong to which level of detail. + @param lod + The actual level of detail to which the meshes are being associated. + @param index + The index onto which mesh indices were assigned using setMeshIndices. + @see setMeshIndices + */ + virtual void setLODMeshMapping(std::uint16_t lod, std::uint16_t index) = 0; + /** + @brief Delete all stored mesh to blend shape channel mapping entries. + */ + virtual void clearMeshBlendShapeChannelMappings() = 0; + /** + @brief Associate a blend shape channel with it's mesh. + @param index + A mapping's position in the zero-indexed array of mesh-blend shape channel mappings. + @param meshIndex + A mesh's position in the zero-indexed array of mesh names. + @param blendShapeChannelIndex + A blend shape channel's position in the zero-indexed array of blend shape channel names. + */ + virtual void setMeshBlendShapeChannelMapping(std::uint32_t index, std::uint16_t meshIndex, std::uint16_t blendShapeChannelIndex) = 0; + /** + @brief A simple array describing the parent-child relationships between joints. + @note + Example: + Joint names: [A, B, C, D, E, F, G, H] + Hierarchy: [0, 0, 0, 1, 1, 4, 2, 2] + Describes the following hierarchy: + A + + B + | + D + | + E + | + F + + C + + G + + H + @param jointIndices + The source address from which the joint indices are to be copied. + @note + These indices can be used to access joint names through DefinitionReader::getJointName. + @param count + The number of joint indices to copy. + */ + virtual void setJointHierarchy(const std::uint16_t* jointIndices, std::uint16_t count) = 0; + /** + @param translations + The source address from which the translations are to be copied. + @param count + The number of translation values to copy. + */ + virtual void setNeutralJointTranslations(const Vector3* translations, std::uint16_t count) = 0; + /** + @param rotations + The source address from which the rotations are to be copied. + @param count + The number of rotation values to copy. + */ + virtual void setNeutralJointRotations(const Vector3* rotations, std::uint16_t count) = 0; +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/layers/Descriptor.h b/dnacalib/DNACalib/include/dna/layers/Descriptor.h new file mode 100644 index 0000000..976278c --- /dev/null +++ b/dnacalib/DNACalib/include/dna/layers/Descriptor.h @@ -0,0 +1,47 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +namespace dna { + +enum class Archetype { + asian, + black, + caucasian, + hispanic, + alien, + other +}; + +enum class Gender { + male, + female, + other +}; + +enum class TranslationUnit { + cm, + m +}; + +enum class RotationUnit { + degrees, + radians +}; + +enum class Direction { + left, + right, + up, + down, + front, + back +}; + +struct CoordinateSystem { + Direction xAxis; + Direction yAxis; + Direction zAxis; +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/layers/DescriptorReader.h b/dnacalib/DNACalib/include/dna/layers/DescriptorReader.h new file mode 100644 index 0000000..b3ebc62 --- /dev/null +++ b/dnacalib/DNACalib/include/dna/layers/DescriptorReader.h @@ -0,0 +1,80 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/Defs.h" +#include "dna/layers/Descriptor.h" +#include "dna/types/Aliases.h" + +#include + +namespace dna { + +/** + @brief Read-only accessors for various metadata about the character and the rig. + @warning + Implementors should inherit from Reader itself and not this class. + @see Reader +*/ +class DNAAPI DescriptorReader { + protected: + virtual ~DescriptorReader(); + + public: + virtual StringView getName() const = 0; + virtual Archetype getArchetype() const = 0; + virtual Gender getGender() const = 0; + virtual std::uint16_t getAge() const = 0; + virtual std::uint32_t getMetaDataCount() const = 0; + /** + @param index + A position in the zero-indexed array of key-value pairs. + @warning + The index must be less than the value returned by getMetaDataCount. + @return View over the key name string. + */ + virtual StringView getMetaDataKey(std::uint32_t index) const = 0; + /** + @brief Stored metadata value associated with the given key. + @note + If no value is associated with the given key, the returned view + will contain nullptr and will have a size of 0. + @param key + A unique-known key that has a value associated to it. + @warning + The key must be null-terminated. + @return View over the metadata value string. + */ + virtual StringView getMetaDataValue(const char* key) const = 0; + virtual TranslationUnit getTranslationUnit() const = 0; + virtual RotationUnit getRotationUnit() const = 0; + virtual CoordinateSystem getCoordinateSystem() const = 0; + /** + @brief Available levels of detail (e.g. 6 which means the following levels are available: + [0,1,2,3,4,5], where 0 is the LOD with the highest details, and 5 is the LOD with + lowest details). + */ + virtual std::uint16_t getLODCount() const = 0; + /** + @brief The maximum level of detail stored in the DNA data for this character. + @note + The value is relative to LOD-0 from the database. + */ + virtual std::uint16_t getDBMaxLOD() const = 0; + /** + @brief Name of the input control interface used to drive this character rig. + @note + This parameter denotes the character's input control complexity. + */ + virtual StringView getDBComplexity() const = 0; + /** + @brief Name of the database from which the character originates. + @note + All characters from the same database must have the same Definition, but may + have different complexity or LOD. + */ + virtual StringView getDBName() const = 0; + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/layers/DescriptorWriter.h b/dnacalib/DNACalib/include/dna/layers/DescriptorWriter.h new file mode 100644 index 0000000..7aae5ca --- /dev/null +++ b/dnacalib/DNACalib/include/dna/layers/DescriptorWriter.h @@ -0,0 +1,83 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/Defs.h" +#include "dna/layers/Descriptor.h" +#include "dna/types/Aliases.h" + +#include + +namespace dna { + +/** + @brief Write-only accessors to various metadata about the character and the rig. + @warning + Implementors should inherit from Writer itself and not this class. + @see Writer +*/ +class DNAAPI DescriptorWriter { + protected: + virtual ~DescriptorWriter(); + + public: + /** + @param name + A null-terminated string. + @note + The passed in name is copied, which will involve an allocation. + */ + virtual void setName(const char* name) = 0; + virtual void setArchetype(Archetype archetype) = 0; + virtual void setGender(Gender gender) = 0; + virtual void setAge(std::uint16_t age) = 0; + /** + @brief Empties the metadata storage, delete all key-value pairs. + */ + virtual void clearMetaData() = 0; + /** + @brief Associate the metadata value with the given key. + @param key + A unique, null-terminated key, to which the given value will be assigned. + @param value + A null-terminated, metadata value, which is to be assigned to the given key. + @note + Consecutive calls using the same key will overwrite any existing data. + @note + Passing nullptr as the value argument will cause the associated key to be deleted. + */ + virtual void setMetaData(const char* key, const char* value) = 0; + virtual void setTranslationUnit(TranslationUnit unit) = 0; + virtual void setRotationUnit(RotationUnit unit) = 0; + virtual void setCoordinateSystem(CoordinateSystem system) = 0; + /** + @brief Available levels of detail (e.g. 6 which means the following levels are available: + [0,1,2,3,4,5], where 0 is the LOD with the highest details, and 5 is the LOD with + lowest details). + @param lodCount + The number of levels available. + */ + virtual void setLODCount(std::uint16_t lodCount) = 0; + /** + @brief The maximum level of detail stored in the DNA data for this character. + */ + virtual void setDBMaxLOD(std::uint16_t lod) = 0; + /** + @brief Name of the input control interface used to drive this character rig. + @param name + A null-terminated string. + @note + The passed in name is copied, which will involve an additional allocation. + */ + virtual void setDBComplexity(const char* name) = 0; + /** + @brief Name of the database from which the character originates. + @param name + A null-terminated string. + @note + The passed in name is copied, which will involve an additional allocation. + */ + virtual void setDBName(const char* name) = 0; +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/layers/Geometry.h b/dnacalib/DNACalib/include/dna/layers/Geometry.h new file mode 100644 index 0000000..6cb34ab --- /dev/null +++ b/dnacalib/DNACalib/include/dna/layers/Geometry.h @@ -0,0 +1,26 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/types/Vector3.h" + +#include + +namespace dna { + +struct TextureCoordinate { + float u; + float v; +}; + +using Position = Vector3; +using Normal = Vector3; +using Delta = Vector3; + +struct VertexLayout { + std::uint32_t position; + std::uint32_t textureCoordinate; + std::uint32_t normal; +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/layers/GeometryReader.h b/dnacalib/DNACalib/include/dna/layers/GeometryReader.h new file mode 100644 index 0000000..38674f9 --- /dev/null +++ b/dnacalib/DNACalib/include/dna/layers/GeometryReader.h @@ -0,0 +1,442 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/Defs.h" +#include "dna/layers/DefinitionReader.h" +#include "dna/layers/Geometry.h" +#include "dna/types/Aliases.h" + +#include + +namespace dna { + +/** + @brief Read-only accessors to the geometry data associated with a rig. + @warning + Implementors should inherit from Reader itself and not this class. +*/ +class DNAAPI GeometryReader : public virtual DefinitionReader { + protected: + virtual ~GeometryReader(); + + public: + /** + @brief Number of vertex positions in the entire mesh. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + */ + virtual std::uint32_t getVertexPositionCount(std::uint16_t meshIndex) const = 0; + /** + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @param vertexIndex + The index of the vertex position in the zero-indexed array of vertex positions. + @warning + vertexIndex must be less than the value returned by getVertexPositionCount. + @note + The vertices are sorted by the vertex ID. + @return The vertex position. + */ + virtual Position getVertexPosition(std::uint16_t meshIndex, std::uint32_t vertexIndex) const = 0; + /** + @brief List of all vertex position X values for the referenced mesh. + @note + This is an advanced API for performance critical access, for more convenient usage see getVertexPosition. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @return View over all X values. + @see getVertexPosition + */ + virtual ConstArrayView getVertexPositionXs(std::uint16_t meshIndex) const = 0; + /** + @brief List of all vertex position Y values for the referenced mesh. + @note + This is an advanced API for performance critical access, for more convenient usage see getVertexPosition. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @return View over all Y values. + @see getVertexPosition + */ + virtual ConstArrayView getVertexPositionYs(std::uint16_t meshIndex) const = 0; + /** + @brief List of all vertex position Z values for the referenced mesh. + @note + This is an advanced API for performance critical access, for more convenient usage see getVertexPosition. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @return View over all Z values. + @see getVertexPosition + */ + virtual ConstArrayView getVertexPositionZs(std::uint16_t meshIndex) const = 0; + /** + @brief Number of texture coordinates in the entire mesh. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + */ + virtual std::uint32_t getVertexTextureCoordinateCount(std::uint16_t meshIndex) const = 0; + /** + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @param textureCoordinateIndex + The index of the texture coordinate in the zero-indexed array of texture coordinates. + @warning + textureCoordinateIndex must be less than the value returned by getVertexTextureCoordinateCount. + @return The texture coordinate. + */ + virtual TextureCoordinate getVertexTextureCoordinate(std::uint16_t meshIndex, + std::uint32_t textureCoordinateIndex) const = 0; + /** + @brief List of all texture coordinate U values for the referenced mesh. + @note + This is an advanced API for performance critical access, for more convenient usage see getVertexTextureCoordinate. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @return View over all U values. + @see getVertexTextureCoordinate + */ + virtual ConstArrayView getVertexTextureCoordinateUs(std::uint16_t meshIndex) const = 0; + /** + @brief List of all texture coordinate V values for the referenced mesh. + @note + This is an advanced API for performance critical access, for more convenient usage see getVertexTextureCoordinate. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @return View over all V values. + @see getVertexTextureCoordinate + */ + virtual ConstArrayView getVertexTextureCoordinateVs(std::uint16_t meshIndex) const = 0; + /** + @brief Number of vertex normals in the entire mesh. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + */ + virtual std::uint32_t getVertexNormalCount(std::uint16_t meshIndex) const = 0; + /** + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @param normalIndex + The index of the vertex normal in the zero-indexed array of vertex normals. + @warning + normalIndex must be less than the value returned by getVertexNormalCount. + @return The vertex normal. + */ + virtual Normal getVertexNormal(std::uint16_t meshIndex, std::uint32_t normalIndex) const = 0; + /** + @brief List of all normal X values for the referenced mesh. + @note + This is an advanced API for performance critical access, for more convenient usage see getVertexNormal. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @return View over all X values. + @see getVertexNormal + */ + virtual ConstArrayView getVertexNormalXs(std::uint16_t meshIndex) const = 0; + /** + @brief List of all normal Y value for the referenced meshs. + @note + This is an advanced API for performance critical access, for more convenient usage see getVertexNormal. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @return View over all Y values. + @see getVertexNormal + */ + virtual ConstArrayView getVertexNormalYs(std::uint16_t meshIndex) const = 0; + /** + @brief List of all normal Z values for the referenced mesh. + @note + This is an advanced API for performance critical access, for more convenient usage see getVertexNormal. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @return View over all Z values. + @see getVertexNormal + */ + virtual ConstArrayView getVertexNormalZs(std::uint16_t meshIndex) const = 0; + /** + @brief Number of vertex layouts in the entire mesh. + @note + A vertex layout is a collection of vertex attributes. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + */ + virtual std::uint32_t getVertexLayoutCount(std::uint16_t meshIndex) const = 0; + /** + @brief Vertex layouts contain only attribute indices which can be used to query + the actual attributes, such as positions, texture coordinates and normals, + which are associated with the vertex. + @note + The indices from a layout are usable with the above defined APIs. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @param layoutIndex + The index of the layout in the zero-indexed array of vertex layouts. + @warning + layoutIndex must be less than the value returned by getVertexLayoutCount. + @see getVertexPosition + @see getVertexTextureCoordinate + @see getVertexNormal + */ + virtual VertexLayout getVertexLayout(std::uint16_t meshIndex, std::uint32_t layoutIndex) const = 0; + /** + @brief Position indices for each vertex of the referenced mesh. + @note + This is an advanced API for performance critical access, for more convenient usage see getVertexLayout. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @return View over all vertex position indices values. + @see getVertexLayout + */ + virtual ConstArrayView getVertexLayoutPositionIndices(std::uint16_t meshIndex) const = 0; + /** + @brief Texture coordinate indices for each vertex of the referenced mesh. + @note + This is an advanced API for performance critical access, for more convenient usage see getVertexLayout. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @return View over all vertex texture coordinate indices. + @see getVertexLayout + */ + virtual ConstArrayView getVertexLayoutTextureCoordinateIndices(std::uint16_t meshIndex) const = 0; + /** + @brief Normal indices for each vertex of the referenced mesh. + @note + This is an advanced API for performance critical access, for more convenient usage see getVertexLayout. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @return View over all vertex normal indices. + @see getVertexLayout + */ + virtual ConstArrayView getVertexLayoutNormalIndices(std::uint16_t meshIndex) const = 0; + /** + @brief Number of faces that belong to the specified mesh. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + */ + virtual std::uint32_t getFaceCount(std::uint16_t meshIndex) const = 0; + /** + @brief List of vertex layout indices the belong to a face on the specified mesh. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @param faceIndex + A face's position in the zero-indexed array of faces that belong to + the above referenced mesh. + @warning + faceIndex must be less than the value returned by getFaceCount. + @return View over the list of vertex layout indices. + @see getVertexLayout + */ + virtual ConstArrayView getFaceVertexLayoutIndices(std::uint16_t meshIndex, + std::uint32_t faceIndex) const = 0; + /** + @brief The maximum number of joints that may influence any single vertex. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + */ + virtual std::uint16_t getMaximumInfluencePerVertex(std::uint16_t meshIndex) const = 0; + /** + @brief Number of skin weights associated with the specified mesh. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + */ + virtual std::uint32_t getSkinWeightsCount(std::uint16_t meshIndex) const = 0; + /** + @brief List of skin weights influencing the requested vertex. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @param vertexIndex + A position in the zero-indexed array of vertices. + @warning + vertexIndex must be less than the value returned by getVertexPositionCount. + @return View over the list of skin weights. + */ + virtual ConstArrayView getSkinWeightsValues(std::uint16_t meshIndex, std::uint32_t vertexIndex) const = 0; + /** + @brief List of joint indices associated with each skin weight for the specified vertex. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @param vertexIndex + A position in the zero-indexed array of vertices. + @warning + vertexIndex must be less than the value returned by getVertexPositionCount. + @note + The joint indices are stored in the same order as the weights they + are associated with. + @return View over the list of joint indices. + */ + virtual ConstArrayView getSkinWeightsJointIndices(std::uint16_t meshIndex, + std::uint32_t vertexIndex) const = 0; + /** + @brief Number of blend shapes that belong to the specified mesh. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + */ + virtual std::uint16_t getBlendShapeTargetCount(std::uint16_t meshIndex) const = 0; + /** @brief The matching blend shape channel index of the requested blend shape target. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @param blendShapeTargetIndex + A position in the zero-indexed array of blend shape targets within the specified mesh. + @warning + blendShapeTargetIndex must be less than the value returned by getBlendShapeTargetCount. + @see DefinitionReader::getBlendShapeChannelName + */ + virtual std::uint16_t getBlendShapeChannelIndex(std::uint16_t meshIndex, std::uint16_t blendShapeTargetIndex) const = 0; + /** + @brief Number of deltas that belong to the specified blend shape. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @param blendShapeTargetIndex + A position in the zero-indexed array of blend shape targets within the specified mesh. + @warning + blendShapeTargetIndex must be less than the value returned by getBlendShapeTargetCount. + */ + virtual std::uint32_t getBlendShapeTargetDeltaCount(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const = 0; + /** + @brief List of deltas for each affected vertex. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @param blendShapeTargetIndex + A position in the zero-indexed array of blend shape targets within the specified mesh. + @warning + blendShapeTargetIndex must be less than the value returned by getBlendShapeTargetCount. + @param deltaIndex + A position in the zero-indexed array of blend shapes deltas. + @warning + deltaIndex must be less than the value returned by getBlendShapeTargetDeltaCount. + */ + virtual Delta getBlendShapeTargetDelta(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + std::uint32_t deltaIndex) const = 0; + /** + @brief List of all delta X values for the referenced blend shape target. + @note + This is an advanced API for performance critical access, for more convenient usage see getBlendShapeTargetDelta. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @param blendShapeTargetIndex + A position in the zero-indexed array of blend shape targets within the specified mesh. + @warning + blendShapeTargetIndex must be less than the value returned by getBlendShapeTargetCount. + @return View over all X values. + @see getBlendShapeTargetDelta + */ + virtual ConstArrayView getBlendShapeTargetDeltaXs(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const = 0; + /** + @brief List of all delta Y values for the referenced blend shape target. + @note + This is an advanced API for performance critical access, for more convenient usage see getBlendShapeTargetDelta. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @param blendShapeTargetIndex + A position in the zero-indexed array of blend shape targets within the specified mesh. + @warning + blendShapeTargetIndex must be less than the value returned by getBlendShapeTargetCount. + @return View over all Y values. + @see getBlendShapeTargetDelta + */ + virtual ConstArrayView getBlendShapeTargetDeltaYs(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const = 0; + /** + @brief List of all delta Z values for the referenced blend shape target. + @note + This is an advanced API for performance critical access, for more convenient usage see getBlendShapeTargetDelta. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @param blendShapeTargetIndex + A position in the zero-indexed array of blend shape targets within the specified mesh. + @warning + blendShapeTargetIndex must be less than the value returned by getBlendShapeTargetCount. + @return View over all Z values. + @see getBlendShapeTargetDelta + */ + virtual ConstArrayView getBlendShapeTargetDeltaZs(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const = 0; + /** + @brief Vertex position indices affected by the referenced blend shape target. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + @param blendShapeTargetIndex + A position in the zero-indexed array of blend shape targets within the specified mesh. + @warning + blendShapeTargetIndex must be less than the value returned by getBlendShapeTargetCount. + @note + The vertex position indices are stored in the same order as the deltas they + are associated with. + These indices can be used to query the associated vertices themselves through getVertexPosition. + @see getVertexPosition + @return View over the list of vertex position indices. + */ + virtual ConstArrayView getBlendShapeTargetVertexIndices(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const = 0; + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/layers/GeometryWriter.h b/dnacalib/DNACalib/include/dna/layers/GeometryWriter.h new file mode 100644 index 0000000..9c51677 --- /dev/null +++ b/dnacalib/DNACalib/include/dna/layers/GeometryWriter.h @@ -0,0 +1,248 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/Defs.h" +#include "dna/layers/DefinitionWriter.h" +#include "dna/layers/Geometry.h" + +#include + +namespace dna { + +/** + @brief Write-only accessors for the geometry data associated with a rig. + @warning + Implementors should inherit from Writer itself and not this class. + @see Writer +*/ +class DNAAPI GeometryWriter : public virtual DefinitionWriter { + protected: + virtual ~GeometryWriter(); + + public: + /** + @brief Delete all meshes. + */ + virtual void clearMeshes() = 0; + /** + @brief Delete the specified mesh. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + */ + virtual void deleteMesh(std::uint16_t meshIndex) = 0; + /** + @brief List of vertex positions. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @param positions + The source address from which the vertex positions are to be copied. + @param count + The number of vertex positions to copy. + @note + The mesh storage will be implicitly resized (if needed) to provide + storage for the number of meshes that is inferred from the specified index. + */ + virtual void setVertexPositions(std::uint16_t meshIndex, const Position* positions, std::uint32_t count) = 0; + /** + @brief List of vertex texture coordinates. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @param textureCoordinates + The source address from which the texture coordinates are to be copied. + @param count + The number of texture coordinates to copy. + @note + The mesh storage will be implicitly resized (if needed) to provide + storage for the number of meshes that is inferred from the specified index. + */ + virtual void setVertexTextureCoordinates(std::uint16_t meshIndex, + const TextureCoordinate* textureCoordinates, + std::uint32_t count) = 0; + /** + @brief List of vertex normals. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @param normals + The source address from which the normals are to be copied. + @param count + The number of normals to copy. + @note + The mesh storage will be implicitly resized (if needed) to provide + storage for the number of meshes that is inferred from the specified index. + */ + virtual void setVertexNormals(std::uint16_t meshIndex, const Normal* normals, std::uint32_t count) = 0; + /** + @brief List of vertex layouts the belong to the specified mesh. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @param layouts + The source address from which the layouts are to be copied. + @param count + The number of layouts to copy. + @note + The mesh storage will be implicitly resized (if needed) to provide + storage for the number of meshes that is inferred from the specified index. + */ + virtual void setVertexLayouts(std::uint16_t meshIndex, const VertexLayout* layouts, std::uint32_t count) = 0; + /** + @brief Delete all lists of vertex layout indices for the specified mesh. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + */ + virtual void clearFaceVertexLayoutIndices(std::uint16_t meshIndex) = 0; + /** + @brief Vertex layout indices that belong to the specified face. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @param faceIndex + A face's position in the zero-indexed array of faces that belong to + the above referenced mesh. + @param layoutIndices + The source address from which the layout indices are to be copied. + @note + The layout indices point into the array that is set through setVertexLayouts + @param count + The number of vertices to copy. + @note + Both the mesh storage itself and it's face storage will be implicitly + resized (if needed) to provide storage for the number of meshes and/or + faces that are inferred from the specified indexes. + */ + virtual void setFaceVertexLayoutIndices(std::uint16_t meshIndex, + std::uint32_t faceIndex, + const std::uint32_t* layoutIndices, + std::uint32_t count) = 0; + /** + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @param maxInfluenceCount + The maximum number of joints that may influence any single vertex. + */ + virtual void setMaximumInfluencePerVertex(std::uint16_t meshIndex, std::uint16_t maxInfluenceCount) = 0; + /** + @brief Delete all skin weights for the specified mesh. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + */ + virtual void clearSkinWeights(std::uint16_t meshIndex) = 0; + /** + @brief List of skin weights influencing the referenced vertex. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @param vertexIndex + A position in the zero-indexed array of vertex positions. + @param weights + The source address from which the weights are to be copied. + @param count + The number of weights to copy. + @note + Both the mesh storage itself and it's skin weight storage will be implicitly + resized (if needed) to provide storage for the number of meshes and/or + skin-weight lists that are inferred from the specified indexes. + @warning + The sum of weights must add up to 1. + */ + virtual void setSkinWeightsValues(std::uint16_t meshIndex, + std::uint32_t vertexIndex, + const float* weights, + std::uint16_t count) = 0; + /** + @brief List of joint indices associated with each skin weight for the specified vertex. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @param vertexIndex + A position in the zero-indexed array of vertex positions. + @param jointIndices + The source address from which the joint indices are to be copied. + @param count + The number of joint indices to copy. + @note + Both the mesh storage itself and it's joint index list storage will be implicitly + resized (if needed) to provide storage for the number of meshes and/or + joint index lists that are inferred from the specified indexes. + @warning + The joint indices must be stored in the same order as the weights they + are associated with. + */ + virtual void setSkinWeightsJointIndices(std::uint16_t meshIndex, + std::uint32_t vertexIndex, + const std::uint16_t* jointIndices, + std::uint16_t count) = 0; + /** + @brief Delete all blend shape targets for the specified mesh. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @warning + meshIndex must be less than the value returned by getMeshCount. + */ + virtual void clearBlendShapeTargets(std::uint16_t meshIndex) = 0; + /** @brief The matching blend shape channel index of the specified blend shape target. + @note + Associate the mesh-local blend shape target index with the absolute blend shape channel + index as found in the Definition layer. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @param blendShapeTargetIndex + A position in the zero-indexed array of blend shape targets within the specified mesh. + @param blendShapeChannelIndex + The index of the specified blend shape channel in the Definition layer. + @note + Both the mesh storage itself and it's blend shape target storage will be implicitly + resized (if needed) to provide storage for the number of meshes and/or + blend shape targets that are inferred from the specified indexes. + */ + virtual void setBlendShapeChannelIndex(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + std::uint16_t blendShapeChannelIndex) = 0; + /** + @brief List of deltas for each affected vertex. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @param blendShapeTargetIndex + A position in the zero-indexed array of blend shape targets within the specified mesh. + @param deltas + The source address from which the blend shape target deltas are to be copied. + @param count + The number of blend shape target deltas to copy. + @note + Both the mesh storage itself and it's blend shape target storage will be implicitly + resized (if needed) to provide storage for the number of meshes and/or + blend shape targets that are inferred from the specified indexes. + */ + virtual void setBlendShapeTargetDeltas(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + const Delta* deltas, + std::uint32_t count) = 0; + /** + @brief Vertex position indices affected by the specified blend shape target. + @param meshIndex + A mesh's position in the zero-indexed array of meshes. + @param blendShapeTargetIndex + A position in the zero-indexed array of blend shape targets within the specified mesh. + @param vertexIndices + The source address from which the vertex position indices are to be copied. + @param count + The number of vertex position indices to copy. + @note + Both the mesh storage itself and it's blend shape target storage will be implicitly + resized (if needed) to provide storage for the number of meshes and/or + blend shape targets that are inferred from the specified indexes. + @warning + The vertex position indices must be stored in the same order as the deltas + they are associated with. + */ + virtual void setBlendShapeTargetVertexIndices(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + const std::uint32_t* vertexIndices, + std::uint32_t count) = 0; + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/types/Aliases.h b/dnacalib/DNACalib/include/dna/types/Aliases.h new file mode 100644 index 0000000..cd5a305 --- /dev/null +++ b/dnacalib/DNACalib/include/dna/types/Aliases.h @@ -0,0 +1,33 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/types/ArrayView.h" +#include "dna/types/StringView.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dna { + +template +using ArrayView = trust::ArrayView; + +template +using ConstArrayView = trust::ConstArrayView; + +using trio::BoundedIOStream; +using trio::FileStream; +using trio::MemoryMappedFileStream; +using trio::MemoryStream; +using sc::Status; + +using namespace pma; + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/types/ArrayView.h b/dnacalib/DNACalib/include/dna/types/ArrayView.h new file mode 100644 index 0000000..4d163e6 --- /dev/null +++ b/dnacalib/DNACalib/include/dna/types/ArrayView.h @@ -0,0 +1,241 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +// *INDENT-OFF* +#ifndef TRUST_ARRAYVIEW_H +#define TRUST_ARRAYVIEW_H + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace trust { + +template +struct ArrayViewTraits { + using value_type = T; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; +}; + +template +struct ArrayViewTraits { + using value_type = const T; + using reference = const T&; + using const_reference = const T&; + using pointer = const T*; + using const_pointer = const T*; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; +}; + +template +struct IsCompatible { + static constexpr bool value = std::is_same::type, typename std::remove_cv::type>::value && + (std::is_const::type>::value || + !std::is_const::type>::value); +}; + +/** + @brief A view over a continuous sequence of objects. + @tparam T element type + Provides a view over a continuous sequence of objects owned by some other object. + Contains a count of elements and a pointer to a location where they are stored. + ArrayView does not own the memory it points to - it does not perform any allocation + and deallocation. It can be constructed given a pointer and element count, or a container + type argument. The class provides helper methods for creating subviews over the objects, + and methods for by-value comparison with containers. ConstArrayView represents an immutable view. +*/ +template +class ArrayView { + public: + using value_type = typename ArrayViewTraits::value_type; + using reference = typename ArrayViewTraits::reference; + using const_reference = typename ArrayViewTraits::const_reference; + using const_pointer = typename ArrayViewTraits::const_pointer; + using pointer = typename ArrayViewTraits::pointer; + using size_type = typename ArrayViewTraits::size_type; + using difference_type = typename ArrayViewTraits::difference_type; + + ArrayView() = default; + ~ArrayView() noexcept = default; + + ArrayView(const ArrayView&) = default; + ArrayView& operator=(const ArrayView&) = default; + + ArrayView(ArrayView&&) = default; + ArrayView& operator=(ArrayView&&) = default; + + ArrayView(pointer src, size_type size) : + ptr{src}, + sz{size} { + } + + template::value, int>::type = 0> + ArrayView(ArrayView& src) : ArrayView{src.data(), src.size()} { + } + + template::value, int>::type = 0> + ArrayView(const ArrayView& src) : ArrayView{src.data(), src.size()} { + } + + template::value, int>::type = 0> + ArrayView(ArrayView&& src) : ArrayView{src.data(), src.size()} { + } + + template::value && + IsCompatible::type::value_type>::value, + int>::type = 0> + ArrayView(U&& src) : ArrayView{src.data(), static_cast(src.size())} { + } + + size_type size() const { + return sz; + } + + pointer data() { + return ptr; + } + + const_pointer data() const { + return ptr; + } + + pointer begin() { + return ptr; + } + + pointer end() { + return ptr + sz; + } + + const_pointer cbegin() const { + return ptr; + } + + const_pointer cend() const { + return ptr + sz; + } + + const_pointer begin() const { + return cbegin(); + } + + const_pointer end() const { + return cend(); + } + + reference operator[](std::size_t index) { + assert(index < sz); + return ptr[index]; + } + + const_reference operator[](std::size_t index) const { + assert(index < sz); + return ptr[index]; + } + + reference at(std::size_t index) { + return this->operator[](index); + } + + const_reference at(std::size_t index) const { + return this->operator[](index); + } + + ArrayView subview(std::size_t offset, std::size_t count) const { + assert(offset <= sz); + assert((offset + count) <= sz); + return {ptr + offset, count}; + } + + ArrayView first(std::size_t count) const { + assert(count <= sz); + return {ptr, count}; + } + + ArrayView last(std::size_t count) const { + assert(count <= sz); + return {ptr + (sz - count), count}; + } + + private: + pointer ptr{nullptr}; + size_type sz{}; +}; + +template +bool operator==(const ArrayView& lhs, const ArrayView& rhs) { + if (lhs.size() != rhs.size()) { + return false; + } + if (lhs.data() == rhs.data()) { + return true; + } + #if __cplusplus >= 201402L || (defined(_MSC_VER) && _MSC_VER >= 1900) + // Under Visual Studio 2015, the overload of std::equal accepting 4 parameters must be used, + // because the 3-parameter version causes insuppressible warnings + return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); + #else + return std::equal(lhs.begin(), lhs.end(), rhs.begin()); + #endif +} + +template +bool operator!=(const ArrayView& lhs, const ArrayView& rhs) { + return !(lhs == rhs); +} + +template +typename std::enable_if, TContainer>::value, bool>::type operator==(const ArrayView& lhs, + const TContainer& rhs) { + if (lhs.size() != rhs.size()) { + return false; + } + #if __cplusplus >= 201402L || (defined(_MSC_VER) && _MSC_VER >= 1900) + // Under Visual Studio 2015, the overload of std::equal accepting 4 parameters must be used, + // because the 3-parameter version causes insuppressible warnings + return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); + #else + return std::equal(lhs.begin(), lhs.end(), rhs.begin()); + #endif +} + +template +typename std::enable_if, TContainer>::value, bool>::type operator!=(const ArrayView& lhs, + const TContainer& rhs) { + return !(lhs == rhs); +} + +template +typename std::enable_if, TContainer>::value, bool>::type operator==(const TContainer& lhs, + const ArrayView& rhs) { + return (rhs == lhs); +} + +template +typename std::enable_if, TContainer>::value, bool>::type operator!=(const TContainer& lhs, + const ArrayView& rhs) { + return !(lhs == rhs); +} + +template +using ConstArrayView = ArrayView; + +} // namespace trust + +#endif // TRUST_ARRAYVIEW_H +// *INDENT-ON* diff --git a/dnacalib/DNACalib/include/dna/types/StringView.h b/dnacalib/DNACalib/include/dna/types/StringView.h new file mode 100644 index 0000000..c573dc5 --- /dev/null +++ b/dnacalib/DNACalib/include/dna/types/StringView.h @@ -0,0 +1,35 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/types/ArrayView.h" + +namespace dna { + +class StringView : public trust::ConstArrayView { + public: + using Base = trust::ConstArrayView; + + public: + using Base::ArrayView; + + const char* c_str() const { + return dataOrEmpty(); + } + + operator const char*() const { + return dataOrEmpty(); + } + + const char* operator*() const { + return dataOrEmpty(); + } + + private: + const char* dataOrEmpty() const { + return (data() == nullptr ? "" : data()); + } + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/types/Vector3.h b/dnacalib/DNACalib/include/dna/types/Vector3.h new file mode 100644 index 0000000..ee9c280 --- /dev/null +++ b/dnacalib/DNACalib/include/dna/types/Vector3.h @@ -0,0 +1,110 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +namespace dna { + +struct Vector3 { + float x; + float y; + float z; + + Vector3& operator+=(const Vector3& rhs) { + x += rhs.x; + y += rhs.y; + z += rhs.z; + return *this; + } + + Vector3& operator-=(const Vector3& rhs) { + x -= rhs.x; + y -= rhs.y; + z -= rhs.z; + return *this; + } + + Vector3& operator*=(const Vector3& rhs) { + x *= rhs.x; + y *= rhs.y; + z *= rhs.z; + return *this; + } + + Vector3& operator/=(const Vector3& rhs) { + x /= rhs.x; + y /= rhs.y; + z /= rhs.z; + return *this; + } + + Vector3& operator+=(float rhs) { + x += rhs; + y += rhs; + z += rhs; + return *this; + } + + Vector3& operator-=(float rhs) { + x -= rhs; + y -= rhs; + z -= rhs; + return *this; + } + + Vector3& operator*=(float rhs) { + x *= rhs; + y *= rhs; + z *= rhs; + return *this; + } + + Vector3& operator/=(float rhs) { + x /= rhs; + y /= rhs; + z /= rhs; + return *this; + } + +}; + +inline Vector3 operator+(Vector3 lhs, const Vector3& rhs) { + return (lhs += rhs); +} + +inline Vector3 operator-(Vector3 lhs, const Vector3& rhs) { + return (lhs -= rhs); +} + +inline Vector3 operator*(Vector3 lhs, const Vector3& rhs) { + return (lhs *= rhs); +} + +inline Vector3 operator/(Vector3 lhs, const Vector3& rhs) { + return (lhs /= rhs); +} + +inline Vector3 operator+(Vector3 lhs, float rhs) { + return (lhs += rhs); +} + +inline Vector3 operator-(Vector3 lhs, float rhs) { + return (lhs -= rhs); +} + +inline Vector3 operator*(Vector3 lhs, float rhs) { + return (lhs *= rhs); +} + +inline Vector3 operator/(Vector3 lhs, float rhs) { + return (lhs /= rhs); +} + +inline bool operator==(const Vector3& lhs, const Vector3& rhs) { + return (lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z); +} + +inline bool operator!=(const Vector3& lhs, const Vector3& rhs) { + return !(lhs == rhs); +} + +} // namespace dna diff --git a/dnacalib/DNACalib/include/dna/version/Version.h b/dnacalib/DNACalib/include/dna/version/Version.h new file mode 100644 index 0000000..17178b6 --- /dev/null +++ b/dnacalib/DNACalib/include/dna/version/Version.h @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#define DNA_MAJOR_VERSION 7 +#define DNA_MINOR_VERSION 1 +#define DNA_PATCH_VERSION 0 +#define DNA_VERSION_STRING "7.1.0" diff --git a/dnacalib/DNACalib/include/dnacalib/Command.h b/dnacalib/DNACalib/include/dnacalib/Command.h new file mode 100644 index 0000000..ebe3eca --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/Command.h @@ -0,0 +1,22 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief Command is an abstract class whose implementations are expected to modify the DNA provided in the run() method in some way. +*/ +class DNACAPI Command { + public: + virtual ~Command(); + virtual void run(DNACalibDNAReader* output) = 0; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/DNACalib.h b/dnacalib/DNACalib/include/dnacalib/DNACalib.h new file mode 100644 index 0000000..e203889 --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/DNACalib.h @@ -0,0 +1,29 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/commands/CalculateMeshLowerLODsCommand.h" +#include "dnacalib/commands/ClearBlendShapesCommand.h" +#include "dnacalib/commands/CommandSequence.h" +#include "dnacalib/commands/ConditionalCommand.h" +#include "dnacalib/commands/PruneBlendShapeTargetsCommand.h" +#include "dnacalib/commands/RemoveAnimatedMapCommand.h" +#include "dnacalib/commands/RemoveBlendShapeCommand.h" +#include "dnacalib/commands/RemoveJointAnimationCommand.h" +#include "dnacalib/commands/RemoveJointCommand.h" +#include "dnacalib/commands/RemoveMeshCommand.h" +#include "dnacalib/commands/RenameAnimatedMapCommand.h" +#include "dnacalib/commands/RenameBlendShapeCommand.h" +#include "dnacalib/commands/RenameJointCommand.h" +#include "dnacalib/commands/RenameMeshCommand.h" +#include "dnacalib/commands/RotateCommand.h" +#include "dnacalib/commands/ScaleCommand.h" +#include "dnacalib/commands/SetBlendShapeTargetDeltasCommand.h" +#include "dnacalib/commands/SetLODsCommand.h" +#include "dnacalib/commands/SetNeutralJointTranslationsCommand.h" +#include "dnacalib/commands/SetNeutralJointRotationsCommand.h" +#include "dnacalib/commands/SetSkinWeightsCommand.h" +#include "dnacalib/commands/SetVertexPositionsCommand.h" +#include "dnacalib/commands/TranslateCommand.h" +#include "dnacalib/dna/DNACalibDNAReader.h" +#include "dnacalib/types/Aliases.h" diff --git a/dnacalib/DNACalib/include/dnacalib/Defs.h b/dnacalib/DNACalib/include/dnacalib/Defs.h new file mode 100644 index 0000000..aeb759c --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/Defs.h @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if defined(_WIN32) || defined(__CYGWIN__) + #if defined(__GNUC__) + #define DLL_EXPORT __attribute__((dllexport)) + #define DLL_IMPORT __attribute__((dllimport)) + #else + #define DLL_EXPORT __declspec(dllexport) + #define DLL_IMPORT __declspec(dllimport) + #endif +#elif defined(__GNUC__) + #define DLL_EXPORT __attribute__((visibility("default"))) + #define DLL_IMPORT DLL_EXPORT +#endif + +#if defined(DNAC_BUILD_SHARED) + // Build shared library + #define DNACAPI DLL_EXPORT +#elif defined(DNAC_SHARED) + // Use shared library + #define DNACAPI DLL_IMPORT +#else + // Build or use static library + #define DNACAPI +#endif diff --git a/dnacalib/DNACalib/include/dnacalib/commands/CalculateMeshLowerLODsCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/CalculateMeshLowerLODsCommand.h new file mode 100644 index 0000000..1ed0c42 --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/CalculateMeshLowerLODsCommand.h @@ -0,0 +1,46 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief CalculateMeshLowerLODsCommand is used to recalculate vertex positions for lower LOD meshes of the specified mesh. + @note + The calculation is done based on vertex positions of the specified mesh and vertex texture coordinates of its lower LOD meshes. +*/ +class CalculateMeshLowerLODsCommand : public Command { + public: + DNACAPI explicit CalculateMeshLowerLODsCommand(MemoryResource* memRes = nullptr); + + DNACAPI explicit CalculateMeshLowerLODsCommand(std::uint16_t meshIndex, MemoryResource* memRes = nullptr); + + DNACAPI ~CalculateMeshLowerLODsCommand(); + + CalculateMeshLowerLODsCommand(const CalculateMeshLowerLODsCommand&) = delete; + CalculateMeshLowerLODsCommand& operator=(const CalculateMeshLowerLODsCommand&) = delete; + + DNACAPI CalculateMeshLowerLODsCommand(CalculateMeshLowerLODsCommand&&); + DNACAPI CalculateMeshLowerLODsCommand& operator=(CalculateMeshLowerLODsCommand&&); + + /** + @brief Method for setting the index of the mesh to calculate lower LOD meshes from. + @param meshIndex + The index of the mesh. + */ + DNACAPI void setMeshIndex(std::uint16_t meshIndex); + + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/ClearBlendShapesCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/ClearBlendShapesCommand.h new file mode 100644 index 0000000..e087b96 --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/ClearBlendShapesCommand.h @@ -0,0 +1,39 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief ClearBlendShapesCommand is used to clear all blend shapes data from a DNA. + @note This command clears blend shape target deltas and blend shape animation data. By doing so, it transforms the DNA to be "joints only". +*/ +class ClearBlendShapesCommand : public Command { + public: + DNACAPI explicit ClearBlendShapesCommand(MemoryResource* memRes = nullptr); + + DNACAPI ~ClearBlendShapesCommand(); + + ClearBlendShapesCommand(const ClearBlendShapesCommand&) = delete; + ClearBlendShapesCommand& operator=(const ClearBlendShapesCommand&) = delete; + + DNACAPI ClearBlendShapesCommand(ClearBlendShapesCommand&&); + DNACAPI ClearBlendShapesCommand& operator=(ClearBlendShapesCommand&&); + + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/CommandSequence.h b/dnacalib/DNACalib/include/dnacalib/commands/CommandSequence.h new file mode 100644 index 0000000..6b7c43e --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/CommandSequence.h @@ -0,0 +1,98 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief CommandSequence is used to run a sequence of commands on the same DNA. + @note + Commands will be run in the order in which they were added to the sequence. + @note + CommandSequence holds pointers to commands, but does not own them. +*/ +class CommandSequence : public Command { + public: + DNACAPI explicit CommandSequence(MemoryResource* memRes = nullptr); + + DNACAPI ~CommandSequence(); + + CommandSequence(const CommandSequence&) = delete; + CommandSequence& operator=(const CommandSequence&) = delete; + + DNACAPI CommandSequence(CommandSequence&&); + DNACAPI CommandSequence& operator=(CommandSequence&&); + + DNACAPI void run(DNACalibDNAReader* output) override; + + /** + @brief Method for adding a command to a sequence of commands to run. + @param command + The command to add. + */ + DNACAPI void add(Command* command); + + /** + @brief Method for adding multiple commands to a sequence of commands to run. + @param commands + The commands to add. + */ + DNACAPI void add(ArrayView commands); + + template + void add(Commands... commands) { + static_assert(sizeof...(commands) > 0, "At least one command must be passed."); + Command* commandList[] = {commands ...}; + for (auto cmd : commandList) { + add(cmd); + } + } + + /** + @brief Method for removing a command from the sequence of commands to run. + @param command + The command to remove. + */ + DNACAPI void remove(Command* command); + + /** + @brief Method for removing an array of commands from the sequence of commands to run. + @param commands + The commands to remove. + */ + DNACAPI void remove(ArrayView commands); + + template + void remove(Commands... commands) { + static_assert(sizeof...(commands) > 0, "At least one command must be passed."); + Command* commandList[] = {commands ...}; + for (auto cmd : commandList) { + remove(cmd); + } + } + + /** + @brief Method for checking if the provided command is part of the command sequence. + @param command + The command to check. + */ + DNACAPI bool contains(Command* command) const; + + /** + @brief Number of commands in command sequence. + */ + DNACAPI std::size_t size() const; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/ConditionalCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/ConditionalCommand.h new file mode 100644 index 0000000..d6d8adf --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/ConditionalCommand.h @@ -0,0 +1,74 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief ConditionalCommand is used to run a command if the specified condition is met. +*/ +template +class ConditionalCommand : public Command { + private: + using CommandType = TCommand; + using ConditionType = TCondition; + + public: + ConditionalCommand() : + command{nullptr}, + condition{} { + } + + ConditionalCommand(CommandType* command_, ConditionType condition_) : + command{command_}, + condition{condition_} { + } + + ~ConditionalCommand() = default; + + ConditionalCommand(const ConditionalCommand&) = delete; + ConditionalCommand& operator=(const ConditionalCommand&) = delete; + + ConditionalCommand(ConditionalCommand&&) = default; + ConditionalCommand& operator=(ConditionalCommand&&) = default; + + /** + @brief Method for setting the command to run. + @param command_ + The command to run. + */ + void setCommand(Command* command_) { + command = command_; + } + + /** + @brief Method for setting the condition under which the command should run. + @param condition_ + The condition that should be met. + */ + void setCondition(ConditionType condition_) { + condition = condition_; + } + + void run(DNACalibDNAReader* output) override { + if (command && condition(command, output)) { + command->run(output); + } + } + + private: + CommandType* command; + ConditionType condition; + +}; + +template +ConditionalCommand makeConditional(TCommand* command, TCondition condition) { + return ConditionalCommand{command, condition}; +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/PruneBlendShapeTargetsCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/PruneBlendShapeTargetsCommand.h new file mode 100644 index 0000000..10e20a6 --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/PruneBlendShapeTargetsCommand.h @@ -0,0 +1,45 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief PruneBlendShapeTargetsCommand is used to prune blend shape target deltas whose absolute magnitude is less than or equal to the specified threshold. +*/ +class PruneBlendShapeTargetsCommand : public Command { + public: + DNACAPI explicit PruneBlendShapeTargetsCommand(MemoryResource* memRes = nullptr); + DNACAPI explicit PruneBlendShapeTargetsCommand(float threshold, MemoryResource* memRes = nullptr); + + DNACAPI ~PruneBlendShapeTargetsCommand(); + + PruneBlendShapeTargetsCommand(const PruneBlendShapeTargetsCommand&) = delete; + PruneBlendShapeTargetsCommand& operator=(const PruneBlendShapeTargetsCommand&) = delete; + + DNACAPI PruneBlendShapeTargetsCommand(PruneBlendShapeTargetsCommand&&); + DNACAPI PruneBlendShapeTargetsCommand& operator=(PruneBlendShapeTargetsCommand&&); + + /** + @brief Method for setting the threshold for pruning blend shape target deltas. + @param threshold + The threshold to use. + */ + DNACAPI void setThreshold(float threshold); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/RemoveAnimatedMapCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/RemoveAnimatedMapCommand.h new file mode 100644 index 0000000..849f455 --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/RemoveAnimatedMapCommand.h @@ -0,0 +1,54 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief RemoveAnimatedMapCommand is used to remove animated maps. +*/ +class RemoveAnimatedMapCommand : public Command { + public: + DNACAPI explicit RemoveAnimatedMapCommand(MemoryResource* memRes = nullptr); + DNACAPI RemoveAnimatedMapCommand(std::uint16_t animatedMapIndex, MemoryResource* memRes = nullptr); + DNACAPI RemoveAnimatedMapCommand(ConstArrayView animatedMapIndices, MemoryResource* memRes = nullptr); + + DNACAPI ~RemoveAnimatedMapCommand(); + + RemoveAnimatedMapCommand(const RemoveAnimatedMapCommand&) = delete; + RemoveAnimatedMapCommand& operator=(const RemoveAnimatedMapCommand&) = delete; + + DNACAPI RemoveAnimatedMapCommand(RemoveAnimatedMapCommand&&); + DNACAPI RemoveAnimatedMapCommand& operator=(RemoveAnimatedMapCommand&&); + + /** + @brief Method for setting the index of the animated map to remove. + @param animatedMapIndex + The index of the animated map. + @note Call to either setter overwrites previous setter calls. When running the command, the last set animated map(s) will be removed. + */ + DNACAPI void setAnimatedMapIndex(std::uint16_t animatedMapIndex); + /** + @brief Method for setting the indices of animated maps to remove. + @param animatedMapIndices + The animated map indices. + @note Call to either setter overwrites previous setter calls. When running the command, the last set animated map(s) will be removed. + */ + DNACAPI void setAnimatedMapIndices(ConstArrayView animatedMapIndices); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/RemoveBlendShapeCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/RemoveBlendShapeCommand.h new file mode 100644 index 0000000..b87285d --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/RemoveBlendShapeCommand.h @@ -0,0 +1,54 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief RemoveBlendShapeCommand is used to remove blend shapes. +*/ +class RemoveBlendShapeCommand : public Command { + public: + DNACAPI explicit RemoveBlendShapeCommand(MemoryResource* memRes = nullptr); + DNACAPI RemoveBlendShapeCommand(std::uint16_t blendShapeIndex, MemoryResource* memRes = nullptr); + DNACAPI RemoveBlendShapeCommand(ConstArrayView blendShapeIndices, MemoryResource* memRes = nullptr); + + DNACAPI ~RemoveBlendShapeCommand(); + + RemoveBlendShapeCommand(const RemoveBlendShapeCommand&) = delete; + RemoveBlendShapeCommand& operator=(const RemoveBlendShapeCommand&) = delete; + + DNACAPI RemoveBlendShapeCommand(RemoveBlendShapeCommand&&); + DNACAPI RemoveBlendShapeCommand& operator=(RemoveBlendShapeCommand&&); + + /** + @brief Method for setting the index of the blend shape to remove. + @param blendShapeIndex + The index of the blend shape. + @note Call to either setter overwrites previous setter calls. When running the command, the last set blend shape(s) will be removed. + */ + DNACAPI void setBlendShapeIndex(std::uint16_t blendShapeIndex); + /** + @brief Method for setting the indices of blend shapes to remove. + @param blendShapeIndices + The blend shape indices. + @note Call to either setter overwrites previous setter calls. When running the command, the last set blend shape(s) will be removed. + */ + DNACAPI void setBlendShapeIndices(ConstArrayView blendShapeIndices); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/RemoveJointAnimationCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/RemoveJointAnimationCommand.h new file mode 100644 index 0000000..5509b03 --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/RemoveJointAnimationCommand.h @@ -0,0 +1,54 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief RemoveJointAnimationCommand is used to remove joint animation data. +*/ +class RemoveJointAnimationCommand : public Command { + public: + DNACAPI explicit RemoveJointAnimationCommand(MemoryResource* memRes = nullptr); + DNACAPI RemoveJointAnimationCommand(std::uint16_t jointIndex, MemoryResource* memRes = nullptr); + DNACAPI RemoveJointAnimationCommand(ConstArrayView jointIndices, MemoryResource* memRes = nullptr); + + DNACAPI ~RemoveJointAnimationCommand(); + + RemoveJointAnimationCommand(const RemoveJointAnimationCommand&) = delete; + RemoveJointAnimationCommand& operator=(const RemoveJointAnimationCommand&) = delete; + + DNACAPI RemoveJointAnimationCommand(RemoveJointAnimationCommand&&); + DNACAPI RemoveJointAnimationCommand& operator=(RemoveJointAnimationCommand&&); + + /** + @brief Method for setting the index of a joint whose animation data to remove. + @param jointIndex + The index of the joint. + @note Call to either setter overwrites previous setter calls. When running the command, the last set joint animation(s) will be removed. + */ + DNACAPI void setJointIndex(std::uint16_t jointIndex); + /** + @brief Method for setting the indices of joints whose animation data to remove. + @param jointIndices + The joint indices. + @note Call to either setter overwrites previous setter calls. When running the command, the last set joint animation(s) will be removed. + */ + DNACAPI void setJointIndices(ConstArrayView jointIndices); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/RemoveJointCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/RemoveJointCommand.h new file mode 100644 index 0000000..c25f67b --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/RemoveJointCommand.h @@ -0,0 +1,54 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief RemoveJointCommand is used to remove joints. +*/ +class RemoveJointCommand : public Command { + public: + DNACAPI explicit RemoveJointCommand(MemoryResource* memRes = nullptr); + DNACAPI RemoveJointCommand(std::uint16_t jointIndex, MemoryResource* memRes = nullptr); + DNACAPI RemoveJointCommand(ConstArrayView jointIndices, MemoryResource* memRes = nullptr); + + DNACAPI ~RemoveJointCommand(); + + RemoveJointCommand(const RemoveJointCommand&) = delete; + RemoveJointCommand& operator=(const RemoveJointCommand&) = delete; + + DNACAPI RemoveJointCommand(RemoveJointCommand&&); + DNACAPI RemoveJointCommand& operator=(RemoveJointCommand&&); + + /** + @brief Method for setting the index of the joint to remove. + @param jointIndex + The index of the joint. + @note Call to either setter overwrites previous setter calls. When running the command, the last set joint(s) will be removed. + */ + DNACAPI void setJointIndex(std::uint16_t jointIndex); + /** + @brief Method for setting the indices of joints to remove. + @param jointIndices + The joint indices. + @note Call to either setter overwrites previous setter calls. When running the command, the last set joint(s) will be removed. + */ + DNACAPI void setJointIndices(ConstArrayView jointIndices); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/RemoveMeshCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/RemoveMeshCommand.h new file mode 100644 index 0000000..d152d36 --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/RemoveMeshCommand.h @@ -0,0 +1,54 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief RemoveMeshCommand is used to remove meshes. +*/ +class RemoveMeshCommand : public Command { + public: + DNACAPI explicit RemoveMeshCommand(MemoryResource* memRes = nullptr); + DNACAPI RemoveMeshCommand(std::uint16_t meshIndex, MemoryResource* memRes = nullptr); + DNACAPI RemoveMeshCommand(ConstArrayView meshIndices, MemoryResource* memRes = nullptr); + + DNACAPI ~RemoveMeshCommand(); + + RemoveMeshCommand(const RemoveMeshCommand&) = delete; + RemoveMeshCommand& operator=(const RemoveMeshCommand&) = delete; + + DNACAPI RemoveMeshCommand(RemoveMeshCommand&&); + DNACAPI RemoveMeshCommand& operator=(RemoveMeshCommand&&); + + /** + @brief Method for setting the index of the mesh to remove. + @param meshIndex + The index of the mesh. + */ + DNACAPI void setMeshIndex(std::uint16_t meshIndex); + /** + @brief Method for setting the indices of meshes to remove. + @param meshIndices + The mesh indices. + @note Call to either setter overwrites previous setter calls. When running the command, the last set mesh(es) will be removed. + */ + DNACAPI void setMeshIndices(ConstArrayView meshIndices); + + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/RenameAnimatedMapCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/RenameAnimatedMapCommand.h new file mode 100644 index 0000000..eb26ab0 --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/RenameAnimatedMapCommand.h @@ -0,0 +1,59 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief RenameAnimatedMapCommand is used to rename an animated map. +*/ +class RenameAnimatedMapCommand : public Command { + public: + DNACAPI explicit RenameAnimatedMapCommand(MemoryResource* memRes = nullptr); + DNACAPI RenameAnimatedMapCommand(std::uint16_t animatedMapIndex, const char* newName, MemoryResource* memRes = nullptr); + DNACAPI RenameAnimatedMapCommand(const char* oldName, const char* newName, MemoryResource* memRes = nullptr); + + DNACAPI ~RenameAnimatedMapCommand(); + + RenameAnimatedMapCommand(const RenameAnimatedMapCommand&) = delete; + RenameAnimatedMapCommand& operator=(const RenameAnimatedMapCommand&) = delete; + + DNACAPI RenameAnimatedMapCommand(RenameAnimatedMapCommand&&); + DNACAPI RenameAnimatedMapCommand& operator=(RenameAnimatedMapCommand&&); + + /** + @brief Method for setting a new name for animated map with given index. + @param animatedMapIndex + The index of the animated map whose name to change. + @param newName + The new name for the animated map. + */ + DNACAPI void setName(std::uint16_t animatedMapIndex, const char* newName); + + /** + @brief Method for setting a new name for animated map with given name. + @note + The renaming will not happen if there is no animated map with given current name. + @param oldName + The current name of the animated map whose name to change. + @param newName + The new name for the animated map. + */ + DNACAPI void setName(const char* oldName, const char* newName); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/RenameBlendShapeCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/RenameBlendShapeCommand.h new file mode 100644 index 0000000..3634e88 --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/RenameBlendShapeCommand.h @@ -0,0 +1,59 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief RenameBlendShapeCommand is used to rename a blend shape channel. +*/ +class RenameBlendShapeCommand : public Command { + public: + DNACAPI explicit RenameBlendShapeCommand(MemoryResource* memRes = nullptr); + DNACAPI RenameBlendShapeCommand(std::uint16_t blendShapeIndex, const char* newName, MemoryResource* memRes = nullptr); + DNACAPI RenameBlendShapeCommand(const char* oldName, const char* newName, MemoryResource* memRes = nullptr); + + DNACAPI ~RenameBlendShapeCommand(); + + RenameBlendShapeCommand(const RenameBlendShapeCommand&) = delete; + RenameBlendShapeCommand& operator=(const RenameBlendShapeCommand&) = delete; + + DNACAPI RenameBlendShapeCommand(RenameBlendShapeCommand&&); + DNACAPI RenameBlendShapeCommand& operator=(RenameBlendShapeCommand&&); + + /** + @brief Method for setting a new name for blend shape channel with given index. + @param blendShapeIndex + The index of the blend shape channel whose name to change. + @param newName + The new name for the blend shape channel. + */ + DNACAPI void setName(std::uint16_t blendShapeIndex, const char* newName); + + /** + @brief Method for setting a new name for blend shape channel with given name. + @note + The renaming will not happen if there is no blend shape channel with given current name. + @param oldName + The current name of the blend shape channel whose name to change. + @param newName + The new name for the blend shape channel. + */ + DNACAPI void setName(const char* oldName, const char* newName); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/RenameJointCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/RenameJointCommand.h new file mode 100644 index 0000000..2fe2685 --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/RenameJointCommand.h @@ -0,0 +1,59 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief RenameJointCommand is used to rename a joint. +*/ +class RenameJointCommand : public Command { + public: + DNACAPI explicit RenameJointCommand(MemoryResource* memRes = nullptr); + DNACAPI RenameJointCommand(std::uint16_t jointIndex, const char* newName, MemoryResource* memRes = nullptr); + DNACAPI RenameJointCommand(const char* oldName, const char* newName, MemoryResource* memRes = nullptr); + + DNACAPI ~RenameJointCommand(); + + RenameJointCommand(const RenameJointCommand&) = delete; + RenameJointCommand& operator=(const RenameJointCommand&) = delete; + + DNACAPI RenameJointCommand(RenameJointCommand&&); + DNACAPI RenameJointCommand& operator=(RenameJointCommand&&); + + /** + @brief Method for setting a new name for joint with given index. + @param jointIndex + The index of the joint whose name to change. + @param newName + The new name for the joint. + */ + DNACAPI void setName(std::uint16_t jointIndex, const char* newName); + + /** + @brief Method for setting a new name for joint with given name. + @note + The renaming will not happen if there is no joint with given current name. + @param oldName + The current name of the joint whose name to change. + @param newName + The new name for the joint. + */ + DNACAPI void setName(const char* oldName, const char* newName); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/RenameMeshCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/RenameMeshCommand.h new file mode 100644 index 0000000..280d994 --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/RenameMeshCommand.h @@ -0,0 +1,59 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief RenameMeshCommand is used to rename a mesh. +*/ +class RenameMeshCommand : public Command { + public: + DNACAPI explicit RenameMeshCommand(MemoryResource* memRes = nullptr); + DNACAPI RenameMeshCommand(std::uint16_t meshIndex, const char* newName, MemoryResource* memRes = nullptr); + DNACAPI RenameMeshCommand(const char* oldName, const char* newName, MemoryResource* memRes = nullptr); + + DNACAPI ~RenameMeshCommand(); + + RenameMeshCommand(const RenameMeshCommand&) = delete; + RenameMeshCommand& operator=(const RenameMeshCommand&) = delete; + + DNACAPI RenameMeshCommand(RenameMeshCommand&&); + DNACAPI RenameMeshCommand& operator=(RenameMeshCommand&&); + + /** + @brief Method for setting a new name for mesh with given index. + @param meshIndex + The index of the mesh whose name to change. + @param newName + The new name for the mesh. + */ + DNACAPI void setName(std::uint16_t meshIndex, const char* newName); + + /** + @brief Method for setting a new name for mesh with given name. + @note + The renaming will not happen if there is no mesh with given current name. + @param oldName + The current name of the mesh whose name to change. + @param newName + The new name for the mesh. + */ + DNACAPI void setName(const char* oldName, const char* newName); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/RotateCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/RotateCommand.h new file mode 100644 index 0000000..495647e --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/RotateCommand.h @@ -0,0 +1,54 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief RotateCommand is used to rotate neutral joints and vertex positions around given origin. + @note + Joint rotations are represented in parent space, so it is enough to rotate only root joints, as that rotation will be propagated to the rest of the joints. + @note + If the origin is not set, the assumed origin is (0, 0, 0). +*/ +class RotateCommand : public Command { + public: + DNACAPI explicit RotateCommand(MemoryResource* memRes = nullptr); + DNACAPI RotateCommand(Vector3 degrees, Vector3 origin, MemoryResource* memRes = nullptr); + + DNACAPI ~RotateCommand(); + + RotateCommand(const RotateCommand&) = delete; + RotateCommand& operator=(const RotateCommand&) = delete; + + DNACAPI RotateCommand(RotateCommand&&); + DNACAPI RotateCommand& operator=(RotateCommand&&); + + /** + @brief Method for setting the rotation angles. + @param degrees + Rotation angles in degrees. + */ + DNACAPI void setRotation(Vector3 degrees); + + /** + @brief Method for setting the rotation origin. + @param origin + Origin coordinates. + */ + DNACAPI void setOrigin(Vector3 origin); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/ScaleCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/ScaleCommand.h new file mode 100644 index 0000000..80e205c --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/ScaleCommand.h @@ -0,0 +1,53 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief ScaleCommand is used to scale neutral joints, vertex positions and joint and blendshape deltas by a factor. + @note + Only translation attributes of neutral joints and joint deltas are scaled. +*/ +class ScaleCommand : public Command { + public: + DNACAPI explicit ScaleCommand(MemoryResource* memRes = nullptr); + DNACAPI ScaleCommand(float scale, Vector3 origin, MemoryResource* memRes = nullptr); + + DNACAPI ~ScaleCommand(); + + ScaleCommand(const ScaleCommand&) = delete; + ScaleCommand& operator=(const ScaleCommand&) = delete; + + DNACAPI ScaleCommand(ScaleCommand&&); + DNACAPI ScaleCommand& operator=(ScaleCommand&&); + + /** + @brief Method for setting the scale factor to multiply with. + @param scale + Scale factor. + */ + DNACAPI void setScale(float scale); + + /** + @brief Method for setting the origin. + @note The origin is used to properly scale position values (vertex positions and neutral joint translations). + @param origin + Origin coordinates. + */ + DNACAPI void setOrigin(Vector3 origin); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/SetBlendShapeTargetDeltasCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/SetBlendShapeTargetDeltasCommand.h new file mode 100644 index 0000000..bc1c31f --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/SetBlendShapeTargetDeltasCommand.h @@ -0,0 +1,137 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/commands/VectorOperations.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief SetBlendShapeTargetDeltasCommand is used to change blend shape target deltas. +*/ +class SetBlendShapeTargetDeltasCommand : public Command { + public: + DNACAPI static const sc::StatusCode VertexIndicesOutOfBoundsError; + DNACAPI static const sc::StatusCode NoVertexIndicesSetError; + DNACAPI static const sc::StatusCode DeltasVertexIndicesCountMismatch; + DNACAPI static const sc::StatusCode DeltasMasksCountMismatch; + + public: + DNACAPI explicit SetBlendShapeTargetDeltasCommand(MemoryResource* memRes = nullptr); + DNACAPI SetBlendShapeTargetDeltasCommand(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + ConstArrayView deltas, + ConstArrayView vertexIndices, + VectorOperation operation, + MemoryResource* memRes = nullptr); + DNACAPI SetBlendShapeTargetDeltasCommand(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs, + ConstArrayView vertexIndices, + VectorOperation operation, + MemoryResource* memRes = nullptr); + DNACAPI SetBlendShapeTargetDeltasCommand(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + ConstArrayView deltas, + ConstArrayView vertexIndices, + ConstArrayView masks, + VectorOperation operation, + MemoryResource* memRes = nullptr); + DNACAPI SetBlendShapeTargetDeltasCommand(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs, + ConstArrayView vertexIndices, + ConstArrayView masks, + VectorOperation operation, + MemoryResource* memRes = nullptr); + + DNACAPI ~SetBlendShapeTargetDeltasCommand(); + + SetBlendShapeTargetDeltasCommand(const SetBlendShapeTargetDeltasCommand&) = delete; + SetBlendShapeTargetDeltasCommand& operator=(const SetBlendShapeTargetDeltasCommand&) = delete; + + DNACAPI SetBlendShapeTargetDeltasCommand(SetBlendShapeTargetDeltasCommand&&); + DNACAPI SetBlendShapeTargetDeltasCommand& operator=(SetBlendShapeTargetDeltasCommand&&); + + /** + @brief Method for setting the index of the mesh whose blend shape target to change. + @param meshIndex + The mesh index. + */ + DNACAPI void setMeshIndex(std::uint16_t meshIndex); + /** + @brief Method for setting the index of the blend shape target to change. + @param blendShapeTargetIndex + The blend shape target index. + */ + DNACAPI void setBlendShapeTargetIndex(std::uint16_t blendShapeTargetIndex); + + /** + @brief Method for setting the values used to calculate new deltas for blend shape target. + @param deltas + The values used in calculation. + */ + DNACAPI void setDeltas(ConstArrayView deltas); + + /** + @brief Method for setting the values used to calculate new deltas for blend shape target. + @param xs + The X values for each delta. + @param ys + The Y values for each delta. + @param zs + The Z values for each delta. + */ + DNACAPI void setDeltas(ConstArrayView xs, ConstArrayView ys, ConstArrayView zs); + + /** + @brief Method for setting the vertex indices that correspond to new deltas. + @param vertexIndices + The vertexIndices. + */ + DNACAPI void setVertexIndices(ConstArrayView vertexIndices); + + /** + @brief Method for setting masks used to calculate new deltas for blend shape target. + @note + If no masks are set, default weight value of 1 is used for each delta. + @param masks + The weights for each delta. + */ + DNACAPI void setMasks(ConstArrayView masks); + + /** + @brief Method for setting the type of operation used to calculate new deltas for blend shape target. + @note + Available operations are: Interpolate, Add, Subtract and Multiply. Each delta is calculated based on the provided operation type in the following way: + + Interpolate: \f$newValue = previousValue * (1 - weight) + setValue * weight\f$\n + Add: \f$newValue = previousValue + (setValue * weight)\f$\n + Subtract: \f$newValue = previousValue - (setValue * weight)\f$\n + Multiply: \f$newValue = previousValue * (setValue * weight)\f$\n + + setValue is the value from new deltas that were set, and weight is the value from masks array. + @param operation + The operation to use. + */ + DNACAPI void setOperation(VectorOperation operation); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/SetLODsCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/SetLODsCommand.h new file mode 100644 index 0000000..9725e09 --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/SetLODsCommand.h @@ -0,0 +1,45 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief SetLODsCommand is used to specify LODs to use. Joints, blend shapes, animated maps and meshes that are not in specified LODs are removed from the DNA. +*/ +class SetLODsCommand : public Command { + public: + DNACAPI explicit SetLODsCommand(MemoryResource* memRes = nullptr); + DNACAPI SetLODsCommand(ConstArrayView lods, MemoryResource* memRes = nullptr); + + DNACAPI ~SetLODsCommand(); + + SetLODsCommand(const SetLODsCommand&) = delete; + SetLODsCommand& operator=(const SetLODsCommand&) = delete; + + DNACAPI SetLODsCommand(SetLODsCommand&&); + DNACAPI SetLODsCommand& operator=(SetLODsCommand&&); + + /** + @brief Method for setting the LODs to keep. + @param lods + New LODs to be used. + */ + DNACAPI void setLODs(ConstArrayView lods); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/SetNeutralJointRotationsCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/SetNeutralJointRotationsCommand.h new file mode 100644 index 0000000..c9852d4 --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/SetNeutralJointRotationsCommand.h @@ -0,0 +1,60 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief SetNeutralJointRotationsCommand is used to set new rotation values to neutral joints. +*/ +class SetNeutralJointRotationsCommand : public Command { + public: + DNACAPI explicit SetNeutralJointRotationsCommand(MemoryResource* memRes = nullptr); + DNACAPI SetNeutralJointRotationsCommand(ConstArrayView rotations, MemoryResource* memRes = nullptr); + DNACAPI SetNeutralJointRotationsCommand(ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs, + MemoryResource* memRes = nullptr); + + DNACAPI ~SetNeutralJointRotationsCommand(); + + SetNeutralJointRotationsCommand(const SetNeutralJointRotationsCommand&) = delete; + SetNeutralJointRotationsCommand& operator=(const SetNeutralJointRotationsCommand&) = delete; + + DNACAPI SetNeutralJointRotationsCommand(SetNeutralJointRotationsCommand&&); + DNACAPI SetNeutralJointRotationsCommand& operator=(SetNeutralJointRotationsCommand&&); + + /** + @brief Method for setting the neutral joint rotations. + @param rotations + Rotation values for each joint. + */ + DNACAPI void setRotations(ConstArrayView rotations); + + /** + @brief Method for setting the neutral joint rotations. + @param xs + The X rotation value for each joint. + @param ys + The Y rotation value for each joint. + @param zs + The Z rotation value for each joint. + */ + DNACAPI void setRotations(ConstArrayView xs, ConstArrayView ys, ConstArrayView zs); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/SetNeutralJointTranslationsCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/SetNeutralJointTranslationsCommand.h new file mode 100644 index 0000000..3b5cc14 --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/SetNeutralJointTranslationsCommand.h @@ -0,0 +1,60 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief SetNeutralJointTranslationsCommand is used to set new translation values to neutral joints. +*/ +class SetNeutralJointTranslationsCommand : public Command { + public: + DNACAPI explicit SetNeutralJointTranslationsCommand(MemoryResource* memRes = nullptr); + DNACAPI SetNeutralJointTranslationsCommand(ConstArrayView translations, MemoryResource* memRes = nullptr); + DNACAPI SetNeutralJointTranslationsCommand(ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs, + MemoryResource* memRes = nullptr); + + DNACAPI ~SetNeutralJointTranslationsCommand(); + + SetNeutralJointTranslationsCommand(const SetNeutralJointTranslationsCommand&) = delete; + SetNeutralJointTranslationsCommand& operator=(const SetNeutralJointTranslationsCommand&) = delete; + + DNACAPI SetNeutralJointTranslationsCommand(SetNeutralJointTranslationsCommand&&); + DNACAPI SetNeutralJointTranslationsCommand& operator=(SetNeutralJointTranslationsCommand&&); + + /** + @brief Method for setting the neutral joint translations. + @param translations + Translation values for each joint. + */ + DNACAPI void setTranslations(ConstArrayView translations); + + /** + @brief Method for setting the neutral joint translations. + @param xs + The X translation value for each joint. + @param ys + The Y translation value for each joint. + @param zs + The Z translation value for each joint. + */ + DNACAPI void setTranslations(ConstArrayView xs, ConstArrayView ys, ConstArrayView zs); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/SetSkinWeightsCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/SetSkinWeightsCommand.h new file mode 100644 index 0000000..812fcfc --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/SetSkinWeightsCommand.h @@ -0,0 +1,70 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief SetSkinWeightsCommand is used to set new skin weights for a vertex in a mesh. +*/ +class SetSkinWeightsCommand : public Command { + public: + DNACAPI explicit SetSkinWeightsCommand(MemoryResource* memRes = nullptr); + DNACAPI SetSkinWeightsCommand(std::uint16_t meshIndex, + std::uint32_t vertexIndex, + ConstArrayView weights, + ConstArrayView jointIndices, + MemoryResource* memRes = nullptr); + + DNACAPI ~SetSkinWeightsCommand(); + + SetSkinWeightsCommand(const SetSkinWeightsCommand&) = delete; + SetSkinWeightsCommand& operator=(const SetSkinWeightsCommand&) = delete; + + DNACAPI SetSkinWeightsCommand(SetSkinWeightsCommand&&); + DNACAPI SetSkinWeightsCommand& operator=(SetSkinWeightsCommand&&); + + /** + @brief Method for setting the index of the targeted mesh. + @param meshIndex + The mesh index. + */ + DNACAPI void setMeshIndex(std::uint16_t meshIndex); + + /** + @brief Method for setting the index of the vertex to change. + @param vertexIndex + The vertex index. + */ + DNACAPI void setVertexIndex(std::uint32_t vertexIndex); + + /** + @brief Method for setting the weights with which joints influence the vertex in question. + @param weights + Weights for each joint that has an influence on the vertex. + */ + DNACAPI void setWeights(ConstArrayView weights); + + /** + @brief Method for setting the joint indices of joints that influence the vertex in question. + @param jointIndices + Joint indices of joints that have an influence on the vertex. + */ + DNACAPI void setJointIndices(ConstArrayView jointIndices); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/SetVertexPositionsCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/SetVertexPositionsCommand.h new file mode 100644 index 0000000..22391a8 --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/SetVertexPositionsCommand.h @@ -0,0 +1,114 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/commands/VectorOperations.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief SetVertexPositionsCommand is used to change vertex positions values. +*/ +class SetVertexPositionsCommand : public Command { + public: + DNACAPI static const sc::StatusCode PositionsMasksCountMismatch; + + public: + DNACAPI explicit SetVertexPositionsCommand(MemoryResource* memRes = nullptr); + + DNACAPI SetVertexPositionsCommand(std::uint16_t meshIndex, + ConstArrayView positions, + VectorOperation operation, + MemoryResource* memRes = nullptr); + DNACAPI SetVertexPositionsCommand(std::uint16_t meshIndex, + ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs, + VectorOperation operation, + MemoryResource* memRes = nullptr); + DNACAPI SetVertexPositionsCommand(std::uint16_t meshIndex, + ConstArrayView positions, + ConstArrayView masks, + VectorOperation operation, + MemoryResource* memRes = nullptr); + DNACAPI SetVertexPositionsCommand(std::uint16_t meshIndex, + ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs, + ConstArrayView masks, + VectorOperation operation, + MemoryResource* memRes = nullptr); + + DNACAPI ~SetVertexPositionsCommand(); + + SetVertexPositionsCommand(const SetVertexPositionsCommand&) = delete; + SetVertexPositionsCommand& operator=(const SetVertexPositionsCommand&) = delete; + + DNACAPI SetVertexPositionsCommand(SetVertexPositionsCommand&&); + DNACAPI SetVertexPositionsCommand& operator=(SetVertexPositionsCommand&&); + + /** + @brief Method for setting the index of the mesh to change. + @param meshIndex + The mesh index. + */ + DNACAPI void setMeshIndex(std::uint16_t meshIndex); + + /** + @brief Method for setting the vertex positions used to calculate new values. + @param positions + The vertex positions. + */ + DNACAPI void setPositions(ConstArrayView positions); + + /** + @brief Method for setting the vertex positions used to calculate new values. + @param xs + The X coordinates for each vertex. + @param ys + The Y coordinates for each vertex. + @param zs + The Z coordinates for each vertex. + */ + DNACAPI void setPositions(ConstArrayView xs, ConstArrayView ys, ConstArrayView zs); + + /** + @brief Method for setting vertex masks used to calculate new vertex position values. + @note + If no masks are set, default weight value of 1 is used for each vertex. + @param masks + The weights for each vertex. + */ + DNACAPI void setMasks(ConstArrayView masks); + + /** + @brief Method for setting the type of operation used to calculate new vertex position values. + @note + Available operations are: Interpolate, Add, Subtract and Multiply. Each position is calculated based on the provided operation type in the following way: + + Interpolate: \f$newValue = previousValue * (1 - weight) + setValue * weight\f$\n + Add: \f$newValue = previousValue + (setValue * weight)\f$\n + Subtract: \f$newValue = previousValue - (setValue * weight)\f$\n + Multiply: \f$newValue = previousValue * (setValue * weight)\f$\n + + setValue is the value from new positions that were set, and weight is the value from masks array. + @param operation + The operation to use. + */ + DNACAPI void setOperation(VectorOperation operation); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/TranslateCommand.h b/dnacalib/DNACalib/include/dnacalib/commands/TranslateCommand.h new file mode 100644 index 0000000..a0b791a --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/TranslateCommand.h @@ -0,0 +1,45 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class DNACalibDNAReader; + +/** + @brief TranslateCommand is used to translate neutral joints and vertex positions. + @note + Joint translations are represented in parent space, so it is enough to translate only root joints, as that translation will be propagated to the rest of the joints. +*/ +class TranslateCommand : public Command { + public: + DNACAPI explicit TranslateCommand(MemoryResource* memRes = nullptr); + DNACAPI TranslateCommand(Vector3 translation, MemoryResource* memRes = nullptr); + + DNACAPI ~TranslateCommand(); + + TranslateCommand(const TranslateCommand&) = delete; + TranslateCommand& operator=(const TranslateCommand&) = delete; + + DNACAPI TranslateCommand(TranslateCommand&&); + DNACAPI TranslateCommand& operator=(TranslateCommand&&); + + /** + @brief Method for setting the translation vector. + @param translation + The translation vector. + */ + DNACAPI void setTranslation(Vector3 translation); + DNACAPI void run(DNACalibDNAReader* output) override; + + private: + class Impl; + ScopedPtr pImpl; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/commands/VectorOperations.h b/dnacalib/DNACalib/include/dnacalib/commands/VectorOperations.h new file mode 100644 index 0000000..4c8df5f --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/commands/VectorOperations.h @@ -0,0 +1,14 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +namespace dnac { + +enum class VectorOperation { + Interpolate, + Add, + Subtract, + Multiply +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/dna/DNACalibDNAReader.h b/dnacalib/DNACalib/include/dnacalib/dna/DNACalibDNAReader.h new file mode 100644 index 0000000..2e19003 --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/dna/DNACalibDNAReader.h @@ -0,0 +1,36 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class DNACAPI DNACalibDNAReader : public dna::Reader { + public: + static DNACalibDNAReader* create(MemoryResource* memRes = nullptr); + static DNACalibDNAReader* create(const dna::Reader* reader, MemoryResource* memRes = nullptr); + static void destroy(DNACalibDNAReader* instance); + + protected: + virtual ~DNACalibDNAReader(); +}; + +} // namespace dnac + +namespace pma { + +template<> +struct DefaultInstanceCreator { + using type = FactoryCreate; +}; + +template<> +struct DefaultInstanceDestroyer { + using type = FactoryDestroy; +}; + +} // namespace pma diff --git a/dnacalib/DNACalib/include/dnacalib/types/Aliases.h b/dnacalib/DNACalib/include/dnacalib/types/Aliases.h new file mode 100644 index 0000000..aabbc59 --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/types/Aliases.h @@ -0,0 +1,51 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dnac { + +using sc::Status; +using trio::BoundedIOStream; +using trio::FileStream; +using trio::MemoryMappedFileStream; +using trio::MemoryStream; +using dna::DataLayer; +using dna::BinaryStreamReader; +using dna::BinaryStreamWriter; +using dna::JSONStreamReader; +using dna::JSONStreamWriter; +using dna::StreamReader; +using dna::StreamWriter; +using dna::StringView; +using dna::Vector3; + +template +using ArrayView = dna::ArrayView; + +template +using ConstArrayView = dna::ConstArrayView; + +using namespace pma; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/dnacalib/version/Version.h b/dnacalib/DNACalib/include/dnacalib/version/Version.h new file mode 100644 index 0000000..5f6178d --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/version/Version.h @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#define DNAC_MAJOR_VERSION 6 +#define DNAC_MINOR_VERSION 8 +#define DNAC_PATCH_VERSION 0 +#define DNAC_VERSION_STRING "6.8.0" diff --git a/dnacalib/DNACalib/include/dnacalib/version/VersionInfo.h b/dnacalib/DNACalib/include/dnacalib/version/VersionInfo.h new file mode 100644 index 0000000..35d91bf --- /dev/null +++ b/dnacalib/DNACalib/include/dnacalib/version/VersionInfo.h @@ -0,0 +1,17 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/Defs.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +struct DNACAPI VersionInfo { + static int getMajorVersion(); + static int getMinorVersion(); + static int getPatchVersion(); + static StringView getVersionString(); +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/include/pma/Defs.h b/dnacalib/DNACalib/include/pma/Defs.h new file mode 100644 index 0000000..cff926a --- /dev/null +++ b/dnacalib/DNACalib/include/pma/Defs.h @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if defined(_WIN32) || defined(__CYGWIN__) + #if defined(__GNUC__) + #define DLL_EXPORT __attribute__((dllexport)) + #define DLL_IMPORT __attribute__((dllimport)) + #else + #define DLL_EXPORT __declspec(dllexport) + #define DLL_IMPORT __declspec(dllimport) + #endif +#elif defined(__GNUC__) + #define DLL_EXPORT __attribute__((visibility("default"))) + #define DLL_IMPORT DLL_EXPORT +#endif + +#if defined(DNAC_BUILD_SHARED) + // Build shared library + #define PMAAPI DLL_EXPORT +#elif defined(DNAC_SHARED) + // Use shared library + #define PMAAPI DLL_IMPORT +#else + // Build or use static library + #define PMAAPI +#endif diff --git a/dnacalib/DNACalib/include/pma/MemoryResource.h b/dnacalib/DNACalib/include/pma/MemoryResource.h new file mode 100644 index 0000000..98c3c2e --- /dev/null +++ b/dnacalib/DNACalib/include/pma/MemoryResource.h @@ -0,0 +1,24 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "pma/Defs.h" + +#include + +namespace pma { + +/** + @brief MemoryResource is an abstract class that allows the implementation of polymorphic allocators. + @note + It's purpose is to allow passing arbitrary allocators through API boundaries, without requiring changes in the + signatures and types involved. +*/ +class PMAAPI MemoryResource { + public: + virtual ~MemoryResource(); + virtual void* allocate(std::size_t size, std::size_t alignment) = 0; + virtual void deallocate(void* ptr, std::size_t size, std::size_t alignment) = 0; +}; + +} // namespace pma diff --git a/dnacalib/DNACalib/include/pma/PolyAllocator.h b/dnacalib/DNACalib/include/pma/PolyAllocator.h new file mode 100644 index 0000000..45c7201 --- /dev/null +++ b/dnacalib/DNACalib/include/pma/PolyAllocator.h @@ -0,0 +1,171 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "pma/MemoryResource.h" +#include "pma/resources/DefaultMemoryResource.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) + #pragma warning(disable : 4068) +#endif + +namespace pma { + +namespace impl { + +template +struct max_align_of { + using type = typename std::conditional<(alignof(T) > alignof(U)), T, U>::type; +}; + +template +class PolyAllocator { + public: + using value_type = T; + using traits_type = std::allocator_traits; + + template + friend class PolyAllocator; + + public: + PolyAllocator() { + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wexit-time-destructors" + #endif + static TDefaultMemoryResource dmr; + #ifdef __clang__ + #pragma clang diagnostic pop + #endif + pMemRes = &dmr; + } + + PolyAllocator(MemoryResource* memRes) : PolyAllocator{} { + if (memRes != nullptr) { + pMemRes = memRes; + } + } + + PolyAllocator(std::nullptr_t /*unused*/) : PolyAllocator{} { + } + + template + PolyAllocator(const PolyAllocator& rhs) : pMemRes{rhs.pMemRes} { + } + + void* allocateBytes(std::size_t size, std::size_t alignment = Alignment) { + return pMemRes->allocate(size, alignment); + } + + void deallocateBytes(void* ptr, std::size_t size, std::size_t alignment = Alignment) { + pMemRes->deallocate(ptr, size, alignment); + } + + template + typename std::enable_if::value, U*>::type allocateObject(std::size_t count, + std::size_t alignment = Alignment) { + return static_cast(allocateBytes(count * sizeof(U), alignment)); + } + + template + typename std::enable_if::value>::type deallocateObject(U* ptr, + std::size_t count, + std::size_t alignment = Alignment) { + deallocateBytes(static_cast(ptr), count * sizeof(U), alignment); + } + + template + U* newObject(Args&& ... args) { + auto ptr = traits_type::allocate(*this, 1ul); + assert(ptr != nullptr); + traits_type::construct(*this, ptr, std::forward(args)...); + return ptr; + } + + template + void deleteObject(U* ptr) { + traits_type::destroy(*this, ptr); + traits_type::deallocate(*this, ptr, 1ul); + } + + // Allocation function as requested by standard-library containers + value_type* allocate(std::size_t count) { + return allocateObject(count); + } + + // Deallocation function as requested by standard-library containers + void deallocate(value_type* ptr, std::size_t count) { + deallocateObject(ptr, count); + } + + static std::size_t getAlignment() { + return Alignment; + } + + MemoryResource* getMemoryResource() const { + return pMemRes; + } + + private: + MemoryResource* pMemRes; +}; + +} // namespace impl + +template::type), + class TDefaultMemoryResource = DefaultMemoryResource> +class PolyAllocator : public std::scoped_allocator_adaptor > { + private: + using Impl = impl::PolyAllocator; + using Base = std::scoped_allocator_adaptor; + + public: + template + struct rebind { + using other = PolyAllocator; + }; + + PolyAllocator() = default; + + PolyAllocator(MemoryResource* memRes) : Base{Impl{memRes}} { + } + + template + PolyAllocator(const PolyAllocator& rhs) : Base{rhs} { + } + + template + PolyAllocator(const impl::PolyAllocator& rhs) : Base{rhs} { + } + +}; + +template +bool operator==(const PolyAllocator& lhs, const PolyAllocator& rhs) +{ + return (TAlignment == UAlignment && lhs.getMemoryResource() == rhs.getMemoryResource()); +} + +template +bool operator!=(const PolyAllocator& lhs, const PolyAllocator& rhs) +{ + return !(lhs == rhs); +} + +} // namespace pma diff --git a/dnacalib/DNACalib/include/pma/ScopedPtr.h b/dnacalib/DNACalib/include/pma/ScopedPtr.h new file mode 100644 index 0000000..975c646 --- /dev/null +++ b/dnacalib/DNACalib/include/pma/ScopedPtr.h @@ -0,0 +1,274 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace pma { + +template +struct New { + template + B* operator()(Args&& ... args) { + return new T{std::forward(args)...}; + } + +}; + +template +struct Delete { + void operator()(B* ptr) { + // Calling delete on an incomplete type is undefined behavior. + // This check will result in a compile error for incomplete types, rather than allow UB. + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wsign-conversion" + #endif + using complete_type_checker = char[sizeof(T) ? 1 : -1]; + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + static_cast(sizeof(complete_type_checker)); + delete ptr; + } + +}; + +template +struct New { + T* operator()(std::size_t size) { + return new T[size]{}; + } + +}; + +template +struct Delete { + void operator()(T* ptr) { + // Calling delete on an incomplete type is undefined behavior. + // This check will result in a compile error for incomplete types, rather than allow UB. + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wsign-conversion" + #endif + using complete_type_checker = char[sizeof(T) ? 1 : -1]; + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + static_cast(sizeof(complete_type_checker)); + delete[] ptr; + } + +}; + +template +struct FactoryCreate { + template + B* operator()(Args&& ... args) { + return T::create(std::forward(args)...); + } + +}; + +template +struct FactoryDestroy { + void operator()(B* ptr) { + T::destroy(static_cast(ptr)); + } + +}; + +template +struct DefaultInstanceCreator { + using type = New; +}; + +template +struct DefaultInstanceDestroyer { + using type = Delete; +}; + +/** + @brief Takes ownership over the given pointer and handles it's lifetime. + @note + As ScopedPtr inherits the specified destroyer type, stateless lifetime + managers are zero-cost, but it's also possible to use stateful lifetime + managers (such as lambdas with captures and what-not). + For stateful lifetime managers, a dedicated constructor exists that + receives the destroyer instance and initializes the inherited destroyer + type with it. + @see makeScoped + @see New + @see Delete + @see FactoryCreate + @see FactoryDestroy + @see DefaultInstanceCreator + @see DefaultInstanceDestroyer +*/ +template::type> +class ScopedPtr : private TDestroyer { + private: + template + struct inspect { + using element_type = U; + using pointer_type = element_type*; + using is_array = std::false_type; + }; + + template + struct inspect { + using element_type = U; + using pointer_type = element_type*; + using is_array = std::true_type; + }; + + public: + using pointer = typename inspect::pointer_type; + using element_type = typename inspect::element_type; + using destroyer_type = TDestroyer; + + template + friend class ScopedPtr; + + public: + ScopedPtr() : ptr{nullptr} { + } + + explicit ScopedPtr(pointer ptr_) : ptr{ptr_} { + } + + ScopedPtr(pointer ptr_, destroyer_type&& destroyer) : destroyer_type{std::move(destroyer)}, ptr{ptr_} { + } + + ~ScopedPtr() { + if (ptr) { + destroyer_type::operator()(ptr); + ptr = pointer{}; + } + } + + ScopedPtr(std::nullptr_t) : ptr{nullptr} { + } + + ScopedPtr& operator=(std::nullptr_t) { + reset(); + return *this; + } + + ScopedPtr(const ScopedPtr&) = delete; + ScopedPtr& operator=(const ScopedPtr&) = delete; + + ScopedPtr(ScopedPtr&& rhs) noexcept : ptr{nullptr} { + rhs.swap(*this); + } + + ScopedPtr& operator=(ScopedPtr&& rhs) noexcept { + rhs.swap(*this); + return *this; + } + + template + ScopedPtr(ScopedPtr&& rhs) noexcept : ptr{nullptr} { + ScopedPtr tmp{rhs.release(), static_cast(rhs)}; + tmp.swap(*this); + } + + template + ScopedPtr& operator=(ScopedPtr&& rhs) noexcept { + ScopedPtr tmp{rhs.release(), static_cast(rhs)}; + tmp.swap(*this); + return *this; + } + + template::is_array> + typename std::enable_if::type operator[](std::size_t index) const noexcept { + return ptr[index]; + } + + template::is_array> + typename std::enable_if::type operator*() const noexcept { + return *ptr; + } + + pointer operator->() const noexcept { + return ptr; + } + + operator bool() const noexcept { + return ptr != nullptr; + } + + pointer get() const noexcept { + return ptr; + } + + pointer release() noexcept { + pointer result = nullptr; + std::swap(result, ptr); + return result; + } + + void reset(pointer rhs = pointer()) noexcept { + pointer old = release(); + ptr = rhs; + if (old) { + destroyer_type::operator()(old); + } + } + + void swap(ScopedPtr& rhs) noexcept { + std::swap(static_cast(*this), static_cast(rhs)); + std::swap(ptr, rhs.ptr); + } + + private: + pointer ptr; +}; + +/** + @brief Syntactic sugar for creating instances wrapped in a ScopedPtr. + @note + The default behavior is to rely on the New / Delete pair of lifetime + managers, because it's sensible to do so. + However, because a significant portion of our abstractions follow + the convention of exposing a create / destroy pair of factory functions + (where create always returns a raw pointer), there also exists a dedicated + FactoryCreate / FactoryDestroy pair of lifetime managers. + To change the default behavior in order to utilize a specific lifetime + manager pair, specialize the DefaultInstanceCreator and DefaultInstanceDestroyer + traits for the types that need different handling. + Alternately, it's also possible to pass a custom creator / destroyer on each + invocation. + */ +template()...)) > ::type> +ScopedPtr makeScoped(Args&& ... args) { + static_assert(std::is_same::value || + std::is_base_of::value || + std::is_convertible::type>::value, + "Incompatible types."); + return ScopedPtr{TCreator{} (std::forward(args)...)}; +} + +template class TCreatorTemplate, template class TDestroyerTemplate, typename ... Args> +ScopedPtr > makeScoped(Args&& ... args) { + using TCreator = TCreatorTemplate; + using TDestroyer = TDestroyerTemplate; + return makeScoped(std::forward(args)...); +} + +template +ScopedPtr::type> makeScoped(Args&& ... args) { + using TCreator = typename DefaultInstanceCreator::type; + using TDestroyer = typename DefaultInstanceDestroyer::type; + return makeScoped(std::forward(args)...); +} + +} // namespace pma diff --git a/dnacalib/DNACalib/include/pma/TypeDefs.h b/dnacalib/DNACalib/include/pma/TypeDefs.h new file mode 100644 index 0000000..4ac5b61 --- /dev/null +++ b/dnacalib/DNACalib/include/pma/TypeDefs.h @@ -0,0 +1,49 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "pma/PolyAllocator.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace pma { + +template > +using String = std::basic_string, Allocator>; + +template > +using Vector = std::vector; + +template > > +using Matrix = Vector, Allocator>; + +template > +using List = std::list; + +template > +using Set = std::set, Allocator>; + +template > +using UnorderedSet = std::unordered_set, std::equal_to, Allocator>; + +template > > +using Map = std::map, Allocator>; + +template > > +using UnorderedMap = std::unordered_map, std::equal_to, Allocator>; + +} // namespace pma diff --git a/dnacalib/DNACalib/include/pma/resources/AlignedMemoryResource.h b/dnacalib/DNACalib/include/pma/resources/AlignedMemoryResource.h new file mode 100644 index 0000000..c8ea64e --- /dev/null +++ b/dnacalib/DNACalib/include/pma/resources/AlignedMemoryResource.h @@ -0,0 +1,22 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "pma/Defs.h" +#include "pma/MemoryResource.h" + +#include + +namespace pma { + +/** + @brief A MemoryResource that honors alignment requirements. + @see MemoryResource +*/ +class PMAAPI AlignedMemoryResource : public MemoryResource { + public: + void* allocate(std::size_t size, std::size_t alignment) override; + void deallocate(void* ptr, std::size_t size, std::size_t alignment) override; +}; + +} // namespace pma diff --git a/dnacalib/DNACalib/include/pma/resources/ArenaMemoryResource.h b/dnacalib/DNACalib/include/pma/resources/ArenaMemoryResource.h new file mode 100644 index 0000000..fd39160 --- /dev/null +++ b/dnacalib/DNACalib/include/pma/resources/ArenaMemoryResource.h @@ -0,0 +1,93 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "pma/Defs.h" +#include "pma/MemoryResource.h" +#include "pma/ScopedPtr.h" + +#include + +namespace pma { + +/** + @brief Serves allocations from a preallocated memory region. + @see MemoryResource +*/ +class ArenaMemoryResource : public MemoryResource { + public: + /** + @brief Constructor + @param initialSize + The size of the first allocated region from which allocation requests are served. + @param regionSize + When a memory region backing allocation requests has not enough free space to serve an + allocation, an additional region is allocated for both the current and all subsequent allocations. + This parameter denotes the size of these additionally allocated regions. + @param growthFactor + It describes by which factor should each subsequently allocated region be scaled, + relative to the previous region. A list of possible region allocation would look like: + + regions = {initialSize, regionSize, regions[1] * growthFactor, regions[2] * growthFactor, ... , regions[n - 1] * growthFactor} + + @param upstream + The backing memory region will be allocated using the given upstream MemoryResource. + */ + PMAAPI ArenaMemoryResource(std::size_t initialSize, std::size_t regionSize, float growthFactor, MemoryResource* upstream); + /** + @brief Constructor + @param regionSize + When a memory region backing allocation requests has not enough free space to serve an + allocation, an additional region is allocated for both the current and all subsequent allocations. + This parameter denotes the size of the initial and all subsequently allocated regions. + @param growthFactor + It describes by which factor should each subsequently allocated region be scaled, + relative to the previous region. A list of possible region allocation would look like: + + regions = {initialSize, regionSize, regions[1] * growthFactor, regions[2] * growthFactor, ... , regions[n - 1] * growthFactor} + + @param upstream + The backing memory region will be allocated using the given upstream MemoryResource. + */ + PMAAPI ArenaMemoryResource(std::size_t regionSize, float growthFactor, MemoryResource* upstream); + /** + @brief Constructor + @param regionSize + When a memory region backing allocation requests has not enough free space to serve an + allocation, an additional region is allocated for both the current and all subsequent allocations. + This parameter denotes the size of the initial and all subsequently allocated regions. + @note + The growth factor in this case will be 1.0, i.e. no growth. + @param upstream + The backing memory region will be allocated using the given upstream MemoryResource. + */ + PMAAPI ArenaMemoryResource(std::size_t regionSize, MemoryResource* upstream); + + PMAAPI ~ArenaMemoryResource(); + + ArenaMemoryResource(const ArenaMemoryResource&) = delete; + ArenaMemoryResource& operator=(const ArenaMemoryResource&) = delete; + + PMAAPI ArenaMemoryResource(ArenaMemoryResource&&); + PMAAPI ArenaMemoryResource& operator=(ArenaMemoryResource&&); + + /** + @brief All allocations will be served from the currently active memory region. + */ + PMAAPI void* allocate(std::size_t size, std::size_t alignment) override; + /** + @brief This is a no-op, and the regions are only freed when the arena itself is destroyed. + */ + PMAAPI void deallocate(void* ptr, std::size_t size, std::size_t alignment) override; + /** + @brief The upstream memory resource was passed through the constructor and is backing all arena allocations. + */ + PMAAPI MemoryResource* getUpstreamMemoryResource() const; + + private: + class Impl; + ScopedPtr > pImpl; + +}; + +} // namespace pma diff --git a/dnacalib/DNACalib/include/pma/resources/DefaultMemoryResource.h b/dnacalib/DNACalib/include/pma/resources/DefaultMemoryResource.h new file mode 100644 index 0000000..7e8ef27 --- /dev/null +++ b/dnacalib/DNACalib/include/pma/resources/DefaultMemoryResource.h @@ -0,0 +1,22 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "pma/Defs.h" +#include "pma/MemoryResource.h" + +#include + +namespace pma { + +/** + @brief A MemoryResource that delegates to malloc / free. + @see MemoryResource +*/ +class PMAAPI DefaultMemoryResource : public MemoryResource { + public: + void* allocate(std::size_t size, std::size_t alignment) override; + void deallocate(void* ptr, std::size_t size, std::size_t alignment) override; +}; + +} // namespace pma diff --git a/dnacalib/DNACalib/include/pma/utils/ManagedInstance.h b/dnacalib/DNACalib/include/pma/utils/ManagedInstance.h new file mode 100644 index 0000000..a7dfaf2 --- /dev/null +++ b/dnacalib/DNACalib/include/pma/utils/ManagedInstance.h @@ -0,0 +1,60 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "pma/PolyAllocator.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace pma { + +class MemoryResource; + +namespace impl { + +template +class ManagedInstance { + public: + using PointerType = TPointer; + + private: + explicit ManagedInstance(MemoryResource* memRes) : pMemRes{memRes} { + } + + public: + static ManagedInstance with(MemoryResource* memRes) { + return ManagedInstance{memRes}; + } + + template + PointerType create(Args&& ... args) { + pma::PolyAllocator alloc{pMemRes}; + auto deleter = [alloc](TBase* ptr) mutable { + alloc.deleteObject(static_cast(ptr)); + }; + return {alloc.newObject(std::forward(args)...), deleter}; + } + + private: + MemoryResource* pMemRes; + +}; + +} // namespace impl + +template +using UniqueInstance = impl::ManagedInstance >, TTarget, TBase>; + +template +using SharedInstance = impl::ManagedInstance, TTarget, TBase>; + +} // namespace pma diff --git a/dnacalib/DNACalib/include/pma/version/Version.h b/dnacalib/DNACalib/include/pma/version/Version.h new file mode 100644 index 0000000..ace767c --- /dev/null +++ b/dnacalib/DNACalib/include/pma/version/Version.h @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#define PMA_MAJOR_VERSION 1 +#define PMA_MINOR_VERSION 3 +#define PMA_PATCH_VERSION 3 +#define PMA_VERSION_STRING "1.3.3" diff --git a/dnacalib/DNACalib/include/status/Defs.h b/dnacalib/DNACalib/include/status/Defs.h new file mode 100644 index 0000000..f654e5e --- /dev/null +++ b/dnacalib/DNACalib/include/status/Defs.h @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if defined(_WIN32) || defined(__CYGWIN__) + #if defined(__GNUC__) + #define DLL_EXPORT __attribute__((dllexport)) + #define DLL_IMPORT __attribute__((dllimport)) + #else + #define DLL_EXPORT __declspec(dllexport) + #define DLL_IMPORT __declspec(dllimport) + #endif +#elif defined(__GNUC__) + #define DLL_EXPORT __attribute__((visibility("default"))) + #define DLL_IMPORT DLL_EXPORT +#endif + +#if defined(DNAC_BUILD_SHARED) + // Build shared library + #define SCAPI DLL_EXPORT +#elif defined(DNAC_SHARED) + // Use shared library + #define SCAPI DLL_IMPORT +#else + // Build or use static library + #define SCAPI +#endif diff --git a/dnacalib/DNACalib/include/status/Provider.h b/dnacalib/DNACalib/include/status/Provider.h new file mode 100644 index 0000000..7bd9a60 --- /dev/null +++ b/dnacalib/DNACalib/include/status/Provider.h @@ -0,0 +1,51 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "status/Defs.h" +#include "status/StatusCode.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include + +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace sc { + +class SCAPI StatusProvider { + public: + explicit StatusProvider(std::initializer_list statuses); + + static void reset(); + static StatusCode get(); + static bool isOk(); + static void set(StatusCode status); + + template + static void set(StatusCode status, Args&& ... args) { + std::array buffer{}; + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wformat-security" + #endif + // The returned number of bytes to be written does not include the null terminator + const auto neededSize = snprintf(nullptr, 0ul, status.message, args ...) + 1; + const auto size = std::min(buffer.size(), static_cast(neededSize)); + snprintf(buffer.data(), size, status.message, args ...); + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + status.message = buffer.data(); + set(status); + } + +}; + +} // namespace sc diff --git a/dnacalib/DNACalib/include/status/Status.h b/dnacalib/DNACalib/include/status/Status.h new file mode 100644 index 0000000..3e80c4e --- /dev/null +++ b/dnacalib/DNACalib/include/status/Status.h @@ -0,0 +1,16 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "status/Defs.h" +#include "status/StatusCode.h" + +namespace sc { + +class SCAPI Status { + public: + static bool isOk(); + static StatusCode get(); +}; + +} // namespace sc diff --git a/dnacalib/DNACalib/include/status/StatusCode.h b/dnacalib/DNACalib/include/status/StatusCode.h new file mode 100644 index 0000000..eebad79 --- /dev/null +++ b/dnacalib/DNACalib/include/status/StatusCode.h @@ -0,0 +1,24 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "status/Defs.h" + +#include + +namespace sc { + +struct SCAPI StatusCode { + int code; + const char* message; +}; + +inline bool operator==(const StatusCode& lhs, const StatusCode& rhs) { + return (lhs.code == rhs.code); +} + +inline bool operator!=(const StatusCode& lhs, const StatusCode& rhs) { + return !(lhs == rhs); +} + +} // namespace sc diff --git a/dnacalib/DNACalib/include/status/version/Version.h b/dnacalib/DNACalib/include/status/version/Version.h new file mode 100644 index 0000000..9afb430 --- /dev/null +++ b/dnacalib/DNACalib/include/status/version/Version.h @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#define SC_MAJOR_VERSION 1 +#define SC_MINOR_VERSION 1 +#define SC_PATCH_VERSION 4 +#define SC_VERSION_STRING "1.1.4" diff --git a/dnacalib/DNACalib/include/trio/Concepts.h b/dnacalib/DNACalib/include/trio/Concepts.h new file mode 100644 index 0000000..9d1b925 --- /dev/null +++ b/dnacalib/DNACalib/include/trio/Concepts.h @@ -0,0 +1,158 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "trio/Defs.h" + +#include +#include + +namespace trio { + +class Writable; + +class TRIOAPI Readable { + public: + /** + @brief Read bytes from stream into the given buffer. + @param destination + Destination buffer into which the data is going to be read from the stream. + @param size + Number of bytes to read from stream. + @return + Number of bytes read. + */ + virtual std::size_t read(char* destination, std::size_t size) = 0; + /** + @brief Read bytes from this stream into the given stream. + @param destination + Destination stream into which the data is going to be read from this stream. + @param size + Number of bytes to read from stream. + @return + Number of bytes read. + */ + virtual std::size_t read(Writable* destination, std::size_t size) = 0; + + protected: + virtual ~Readable(); + +}; + +class TRIOAPI Writable { + public: + /** + @brief Writes bytes from the given buffer to the stream. + @param source + Source buffer from which the data is going to be written to the stream. + @param size + Number of bytes to write to the stream. + @return + Number of bytes written. + */ + virtual std::size_t write(const char* source, std::size_t size) = 0; + /** + @brief Writes bytes from the given stream to this stream. + @param source + Source stream from which the data is going to be written into this stream. + @param size + Number of bytes to write to the stream. + @return + Number of bytes written. + */ + virtual std::size_t write(Readable* source, std::size_t size) = 0; + + protected: + virtual ~Writable(); + +}; + +class TRIOAPI Seekable { + public: + /** + @brief Get the current position in the stream. + @return + Position in the stream relative to it's start, with 0 denoting the start position. + */ + virtual std::uint64_t tell() = 0; + /** + @brief Set the current position in the stream. + @param position + Position in the stream relative to it's start, with 0 denoting the start position. + */ + virtual void seek(std::uint64_t position) = 0; + + protected: + virtual ~Seekable(); + +}; + +class TRIOAPI Openable { + public: + /** + @brief Open access to the stream. + */ + virtual void open() = 0; + + protected: + virtual ~Openable(); + +}; + +class TRIOAPI Closeable { + public: + /** + @brief Close access to the stream. + */ + virtual void close() = 0; + + protected: + virtual ~Closeable(); + +}; + +class TRIOAPI Controllable : public Openable, public Closeable { + protected: + virtual ~Controllable(); + +}; + +class TRIOAPI Bounded { + public: + /** + @brief Obtain size of stream in bytes. + @return + Size in bytes. + */ + virtual std::uint64_t size() = 0; + + protected: + virtual ~Bounded(); + +}; + +class TRIOAPI Buffered { + public: + /** + @brief Flush the changes to filesystem. + */ + virtual void flush() = 0; + + protected: + virtual ~Buffered(); + +}; + +class TRIOAPI Resizable { + public: + /** + @brief Resize file to the requested size. + */ + virtual void resize(std::uint64_t size) = 0; + + protected: + virtual ~Resizable(); + +}; + +} // namespace trio diff --git a/dnacalib/DNACalib/include/trio/Defs.h b/dnacalib/DNACalib/include/trio/Defs.h new file mode 100644 index 0000000..ba94b54 --- /dev/null +++ b/dnacalib/DNACalib/include/trio/Defs.h @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if defined(_WIN32) || defined(__CYGWIN__) + #if defined(__GNUC__) + #define DLL_EXPORT __attribute__((dllexport)) + #define DLL_IMPORT __attribute__((dllimport)) + #else + #define DLL_EXPORT __declspec(dllexport) + #define DLL_IMPORT __declspec(dllimport) + #endif +#elif defined(__GNUC__) + #define DLL_EXPORT __attribute__((visibility("default"))) + #define DLL_IMPORT DLL_EXPORT +#endif + +#if defined(DNAC_BUILD_SHARED) + // Build shared library + #define TRIOAPI DLL_EXPORT +#elif defined(DNAC_SHARED) + // Use shared library + #define TRIOAPI DLL_IMPORT +#else + // Build or use static library + #define TRIOAPI +#endif diff --git a/dnacalib/DNACalib/include/trio/Stream.h b/dnacalib/DNACalib/include/trio/Stream.h new file mode 100644 index 0000000..9d0d181 --- /dev/null +++ b/dnacalib/DNACalib/include/trio/Stream.h @@ -0,0 +1,29 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "trio/Concepts.h" +#include "trio/Defs.h" +#include "trio/types/Aliases.h" +#include "trio/types/Parameters.h" + +#include + +namespace trio { + +class TRIOAPI BoundedIOStream : public Controllable, public Readable, public Writable, public Seekable, public Bounded { + public: + using AccessMode = trio::AccessMode; + using OpenMode = trio::OpenMode; + + static const sc::StatusCode OpenError; + static const sc::StatusCode ReadError; + static const sc::StatusCode WriteError; + static const sc::StatusCode AlreadyOpenError; + static const sc::StatusCode SeekError; + + public: + virtual ~BoundedIOStream(); +}; + +} // namespace trio diff --git a/dnacalib/DNACalib/include/trio/streams/FileStream.h b/dnacalib/DNACalib/include/trio/streams/FileStream.h new file mode 100644 index 0000000..6580096 --- /dev/null +++ b/dnacalib/DNACalib/include/trio/streams/FileStream.h @@ -0,0 +1,65 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "trio/Defs.h" +#include "trio/Stream.h" + +namespace trio { + +/** + @brief Standard file stream. +*/ +class TRIOAPI FileStream : public BoundedIOStream { + public: + /** + @brief Factory method for creation of a FileStream instance. + @param path + UTF-8 encoded path to file to be opened. + @param accessMode + Control whether the file is opened for reading or writing. + @param openMode + Control whether the file is opened in binary or textual mode. + @param memRes + The memory resource to be used for the allocation of the FileStream instance. + @note + If a custom memory resource is not given, a default allocation mechanism will be used. + @warning + User is responsible for releasing the returned pointer by calling destroy. + @see destroy + */ + static FileStream* create(const char* path, AccessMode accessMode, OpenMode openMode, MemoryResource* memRes = nullptr); + /** + @brief Method for freeing a FileStream instance. + @param instance + Instance of FileStream to be freed. + @see create + */ + static void destroy(FileStream* instance); + + FileStream() = default; + ~FileStream() override; + + FileStream(const FileStream&) = delete; + FileStream& operator=(const FileStream&) = delete; + + FileStream(FileStream&&) = default; + FileStream& operator=(FileStream&&) = default; + +}; + +} // namespace trio + +namespace pma { + +template<> +struct DefaultInstanceCreator { + using type = FactoryCreate; +}; + +template<> +struct DefaultInstanceDestroyer { + using type = FactoryDestroy; +}; + +} // namespace pma diff --git a/dnacalib/DNACalib/include/trio/streams/MemoryMappedFileStream.h b/dnacalib/DNACalib/include/trio/streams/MemoryMappedFileStream.h new file mode 100644 index 0000000..8df8328 --- /dev/null +++ b/dnacalib/DNACalib/include/trio/streams/MemoryMappedFileStream.h @@ -0,0 +1,65 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "trio/Defs.h" +#include "trio/Stream.h" + +#include + +namespace trio { + +/** + @brief Memory mapped file stream. +*/ +class TRIOAPI MemoryMappedFileStream : public BoundedIOStream, public Buffered, public Resizable { + public: + /** + @brief Factory method for creation of a MemoryMappedFileStream instance. + @param path + UTF-8 encoded path to file to be opened. + @param accessMode + Control whether the file is opened for reading or writing. + @param memRes + The memory resource to be used for the allocation of the MemoryMappedFileStream instance. + @note + If a custom memory resource is not given, a default allocation mechanism will be used. + @warning + User is responsible for releasing the returned pointer by calling destroy. + @see destroy + */ + static MemoryMappedFileStream* create(const char* path, AccessMode accessMode, MemoryResource* memRes = nullptr); + /** + @brief Method for freeing a MemoryMappedFileStream instance. + @param instance + Instance of MemoryMappedFileStream to be freed. + @see create + */ + static void destroy(MemoryMappedFileStream* instance); + + MemoryMappedFileStream() = default; + ~MemoryMappedFileStream() override; + + MemoryMappedFileStream(const MemoryMappedFileStream&) = delete; + MemoryMappedFileStream& operator=(const MemoryMappedFileStream&) = delete; + + MemoryMappedFileStream(MemoryMappedFileStream&&) = default; + MemoryMappedFileStream& operator=(MemoryMappedFileStream&&) = default; + +}; + +} // namespace trio + +namespace pma { + +template<> +struct DefaultInstanceCreator { + using type = FactoryCreate; +}; + +template<> +struct DefaultInstanceDestroyer { + using type = FactoryDestroy; +}; + +} // namespace pma diff --git a/dnacalib/DNACalib/include/trio/streams/MemoryStream.h b/dnacalib/DNACalib/include/trio/streams/MemoryStream.h new file mode 100644 index 0000000..6fe9262 --- /dev/null +++ b/dnacalib/DNACalib/include/trio/streams/MemoryStream.h @@ -0,0 +1,73 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "trio/Defs.h" +#include "trio/Stream.h" + +#include + +namespace trio { + +/** + @brief In-memory stream. +*/ +class TRIOAPI MemoryStream : public BoundedIOStream { + public: + /** + @brief Factory method for creation of a MemoryStream instance. + @param memRes + The memory resource to be used for the allocation of the MemoryStream instance. + @note + If a custom memory resource is not given, a default allocation mechanism will be used. + @warning + User is responsible for releasing the returned pointer by calling destroy. + @see destroy + */ + static MemoryStream* create(MemoryResource* memRes = nullptr); + /** + @brief Factory method for creation of a MemoryStream instance. + @param initialSize + Initial size of the memory stream. + @param memRes + The memory resource to be used for the allocation of the MemoryStream instance. + @note + If a custom memory resource is not given, a default allocation mechanism will be used. + @warning + User is responsible for releasing the returned pointer by calling destroy. + @see destroy + */ + static MemoryStream* create(std::size_t initialSize, MemoryResource* memRes = nullptr); + /** + @brief Method for freeing a MemoryStream instance. + @param instance + Instance of MemoryStream to be freed. + @see create + */ + static void destroy(MemoryStream* instance); + + MemoryStream() = default; + ~MemoryStream() override; + + MemoryStream(const MemoryStream&) = delete; + MemoryStream& operator=(const MemoryStream&) = delete; + + MemoryStream(MemoryStream&&) = default; + MemoryStream& operator=(MemoryStream&&) = default; +}; + +} // namespace trio + +namespace pma { + +template<> +struct DefaultInstanceCreator { + using type = FactoryCreate; +}; + +template<> +struct DefaultInstanceDestroyer { + using type = FactoryDestroy; +}; + +} // namespace pma diff --git a/dnacalib/DNACalib/include/trio/types/Aliases.h b/dnacalib/DNACalib/include/trio/types/Aliases.h new file mode 100644 index 0000000..ec541af --- /dev/null +++ b/dnacalib/DNACalib/include/trio/types/Aliases.h @@ -0,0 +1,16 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include +#include +#include +#include + +namespace trio { + +using sc::Status; + +using namespace pma; + +} // namespace trio diff --git a/dnacalib/DNACalib/include/trio/types/Parameters.h b/dnacalib/DNACalib/include/trio/types/Parameters.h new file mode 100644 index 0000000..77c8e43 --- /dev/null +++ b/dnacalib/DNACalib/include/trio/types/Parameters.h @@ -0,0 +1,18 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +namespace trio { + +enum class AccessMode { + Read = 1, + Write = 2, + ReadWrite = 3 +}; + +enum class OpenMode { + Binary = 4, + Text = 8 +}; + +} // namespace trio diff --git a/dnacalib/DNACalib/include/trio/utils/StreamScope.h b/dnacalib/DNACalib/include/trio/utils/StreamScope.h new file mode 100644 index 0000000..5ee0a87 --- /dev/null +++ b/dnacalib/DNACalib/include/trio/utils/StreamScope.h @@ -0,0 +1,39 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "trio/Stream.h" + +#include + +namespace trio { + +class StreamScope { + public: + explicit StreamScope(Controllable* stream_) : stream{stream_} { + stream->open(); + } + + ~StreamScope() { + if (stream != nullptr) { + stream->close(); + } + } + + StreamScope(const StreamScope&) = delete; + StreamScope& operator=(const StreamScope&) = delete; + + StreamScope(StreamScope&& rhs) noexcept : stream{nullptr} { + std::swap(stream, rhs.stream); + } + + StreamScope& operator=(StreamScope&& rhs) noexcept { + std::swap(stream, rhs.stream); + return *this; + } + + private: + Controllable* stream; +}; + +} // namespace trio diff --git a/dnacalib/DNACalib/include/trio/version/Version.h b/dnacalib/DNACalib/include/trio/version/Version.h new file mode 100644 index 0000000..f3225e5 --- /dev/null +++ b/dnacalib/DNACalib/include/trio/version/Version.h @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#define TRIO_MAJOR_VERSION 4 +#define TRIO_MINOR_VERSION 0 +#define TRIO_PATCH_VERSION 4 +#define TRIO_VERSION_STRING "4.0.4" diff --git a/dnacalib/DNACalib/src/dna/BaseImpl.h b/dnacalib/DNACalib/src/dna/BaseImpl.h new file mode 100644 index 0000000..53499d6 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/BaseImpl.h @@ -0,0 +1,36 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/DNA.h" +#include "dna/types/Aliases.h" + +namespace dna { + +class BaseImpl { + protected: + explicit BaseImpl(MemoryResource* memRes_) : + memRes{memRes_}, + dna{memRes} { + } + + ~BaseImpl() = default; + + BaseImpl(const BaseImpl&) = delete; + BaseImpl& operator=(const BaseImpl&) = delete; + + BaseImpl(BaseImpl&& rhs) = delete; + BaseImpl& operator=(BaseImpl&&) = delete; + + public: + MemoryResource* getMemoryResource() { + return memRes; + } + + protected: + MemoryResource* memRes; + DNA dna; + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/DNA.h b/dnacalib/DNACalib/src/dna/DNA.h new file mode 100644 index 0000000..a2b0ac6 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/DNA.h @@ -0,0 +1,892 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/LODMapping.h" +#include "dna/SurjectiveMapping.h" +#include "dna/TypeDefs.h" + +#include + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dna { + +template +struct RawSurjectiveMapping : public SurjectiveMapping { + using SurjectiveMapping::SurjectiveMapping; + + template + void serialize(Archive& archive) { + archive.label("from"); + archive(this->from); + archive.label("to"); + archive(this->to); + } + +}; + +template +struct ExpectedValue { + T expected; + T got; + + explicit ExpectedValue(const T& value) : expected{value}, got{} { + } + + template + void load(Archive& archive) { + archive.label("value"); + archive(got); + } + + template + void save(Archive& archive) { + archive.label("value"); + archive(expected); + } + + bool matches() const { + return (expected == got); + } + +}; + +template +struct Signature { + using SignatureValueType = std::array; + + ExpectedValue value; + + explicit Signature(SignatureValueType bytes) : value{bytes} { + } + + template + void serialize(Archive& archive) { + archive.label("data"); + archive(value); + } + + bool matches() const { + return value.matches(); + } + +}; + +struct Version { + ExpectedValue generation; + ExpectedValue version; + + Version(std::uint16_t generation_, std::uint16_t version_) : + generation{generation_}, + version{version_} { + } + + template + void serialize(Archive& archive) { + archive.label("generation"); + archive(generation); + archive.label("version"); + archive(version); + } + + bool matches() const { + return (generation.matches() && version.matches()); + } + +}; + +struct SectionLookupTable { + terse::ArchiveOffset descriptor; + terse::ArchiveOffset definition; + terse::ArchiveOffset behavior; + terse::ArchiveOffset controls; + terse::ArchiveOffset joints; + terse::ArchiveOffset blendShapeChannels; + terse::ArchiveOffset animatedMaps; + terse::ArchiveOffset geometry; + + template + void serialize(Archive& archive) { + archive.label("descriptor"); + archive(descriptor); + archive.label("definition"); + archive(definition); + archive.label("behavior"); + archive(behavior); + archive.label("controls"); + archive(controls); + archive.label("joints"); + archive(joints); + archive.label("blendShapeChannels"); + archive(blendShapeChannels); + archive.label("animatedMaps"); + archive(animatedMaps); + archive.label("geometry"); + archive(geometry); + } + +}; + +struct RawCoordinateSystem { + std::uint16_t xAxis; + std::uint16_t yAxis; + std::uint16_t zAxis; + + template + void serialize(Archive& archive) { + archive.label("xAxis"); + archive(xAxis); + archive.label("yAxis"); + archive(yAxis); + archive.label("zAxis"); + archive(zAxis); + } + +}; + +struct RawLODMapping : public LODMapping { + using LODMapping::LODMapping; + + template + void serialize(Archive& archive) { + archive.label("lods"); + archive(lods); + archive.label("indices"); + archive(indices); + } + +}; + +struct RawDescriptor { + using StringPair = std::tuple, String >; + + terse::ArchiveOffset::Proxy marker; + String name; + std::uint16_t archetype; + std::uint16_t gender; + std::uint16_t age; + Vector metadata; + std::uint16_t translationUnit; + std::uint16_t rotationUnit; + RawCoordinateSystem coordinateSystem; + std::uint16_t lodCount; + std::uint16_t maxLOD; + String complexity; + String dbName; + + RawDescriptor(terse::ArchiveOffset& markerTarget, MemoryResource* memRes) : + marker{markerTarget}, + name{memRes}, + archetype{}, + gender{}, + age{}, + metadata{memRes}, + translationUnit{}, + rotationUnit{}, + coordinateSystem{}, + lodCount{}, + maxLOD{}, + complexity{memRes}, + dbName{memRes} { + } + + template + void serialize(Archive& archive) { + archive(marker); + archive.label("name"); + archive(name); + archive.label("archetype"); + archive(archetype); + archive.label("gender"); + archive(gender); + archive.label("age"); + archive(age); + archive.label("metadata"); + archive(metadata); + archive.label("translationUnit"); + archive(translationUnit); + archive.label("rotationUnit"); + archive(rotationUnit); + archive.label("coordinateSystem"); + archive(coordinateSystem); + archive.label("lodCount"); + archive(lodCount); + archive.label("maxLOD"); + archive(maxLOD); + archive.label("complexity"); + archive(complexity); + archive.label("dbName"); + archive(dbName); + } + +}; + +struct RawVector3Vector { + AlignedDynArray xs; + AlignedDynArray ys; + AlignedDynArray zs; + + explicit RawVector3Vector(MemoryResource* memRes) : + xs{memRes}, + ys{memRes}, + zs{memRes} { + } + + RawVector3Vector(std::size_t size_, float initial, MemoryResource* memRes) : + xs{size_, initial, memRes}, + ys{size_, initial, memRes}, + zs{size_, initial, memRes} { + } + + RawVector3Vector(ConstArrayView xs_, ConstArrayView ys_, ConstArrayView zs_, MemoryResource* memRes) : + xs{xs_.begin(), xs_.end(), memRes}, + ys{ys_.begin(), ys_.end(), memRes}, + zs{zs_.begin(), zs_.end(), memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("xs"); + archive(xs); + archive.label("ys"); + archive(ys); + archive.label("zs"); + archive(zs); + } + + std::size_t size() const { + assert(xs.size() == ys.size() && ys.size() == zs.size()); + return xs.size(); + } + + void reserve(std::size_t count) { + xs.resize_uninitialized(count); + ys.resize_uninitialized(count); + zs.resize_uninitialized(count); + } + + void resize(std::size_t count) { + xs.resize(count); + ys.resize(count); + zs.resize(count); + } + + void resize(std::size_t count, float value) { + xs.resize(count, value); + ys.resize(count, value); + zs.resize(count, value); + } + + void clear() { + xs.clear(); + ys.clear(); + zs.clear(); + } + + template + void assign(Iterator start, Iterator end) { + reserve(static_cast(std::distance(start, end))); + std::size_t i{}; + for (auto it = start; it != end; ++it, ++i) { + xs[i] = it->x; + ys[i] = it->y; + zs[i] = it->z; + } + } + +}; + +struct RawDefinition { + terse::ArchiveOffset::Proxy marker; + RawLODMapping lodJointMapping; + RawLODMapping lodBlendShapeMapping; + RawLODMapping lodAnimatedMapMapping; + RawLODMapping lodMeshMapping; + Vector > guiControlNames; + Vector > rawControlNames; + Vector > jointNames; + Vector > blendShapeChannelNames; + Vector > animatedMapNames; + Vector > meshNames; + RawSurjectiveMapping meshBlendShapeChannelMapping; + DynArray jointHierarchy; + RawVector3Vector neutralJointTranslations; + RawVector3Vector neutralJointRotations; + + RawDefinition(terse::ArchiveOffset& markerTarget, MemoryResource* memRes) : + marker{markerTarget}, + lodJointMapping{memRes}, + lodBlendShapeMapping{memRes}, + lodAnimatedMapMapping{memRes}, + lodMeshMapping{memRes}, + guiControlNames{memRes}, + rawControlNames{memRes}, + jointNames{memRes}, + blendShapeChannelNames{memRes}, + animatedMapNames{memRes}, + meshNames{memRes}, + meshBlendShapeChannelMapping{memRes}, + jointHierarchy{memRes}, + neutralJointTranslations{memRes}, + neutralJointRotations{memRes} { + } + + template + void serialize(Archive& archive) { + archive(marker); + archive.label("lodJointMapping"); + archive(lodJointMapping); + archive.label("lodBlendShapeMapping"); + archive(lodBlendShapeMapping); + archive.label("lodAnimatedMapMapping"); + archive(lodAnimatedMapMapping); + archive.label("lodMeshMapping"); + archive(lodMeshMapping); + archive.label("guiControlNames"); + archive(guiControlNames); + archive.label("rawControlNames"); + archive(rawControlNames); + archive.label("jointNames"); + archive(jointNames); + archive.label("blendShapeChannelNames"); + archive(blendShapeChannelNames); + archive.label("animatedMapNames"); + archive(animatedMapNames); + archive.label("meshNames"); + archive(meshNames); + archive.label("meshBlendShapeChannelMapping"); + archive(meshBlendShapeChannelMapping); + archive.label("jointHierarchy"); + archive(jointHierarchy); + archive.label("neutralJointTranslations"); + archive(neutralJointTranslations); + archive.label("neutralJointRotations"); + archive(neutralJointRotations); + } + +}; + +struct RawConditionalTable { + DynArray inputIndices; + DynArray outputIndices; + DynArray fromValues; + DynArray toValues; + DynArray slopeValues; + DynArray cutValues; + + explicit RawConditionalTable(MemoryResource* memRes) : + inputIndices{memRes}, + outputIndices{memRes}, + fromValues{memRes}, + toValues{memRes}, + slopeValues{memRes}, + cutValues{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("inputIndices"); + archive(inputIndices); + archive.label("outputIndices"); + archive(outputIndices); + archive.label("fromValues"); + archive(fromValues); + archive.label("toValues"); + archive(toValues); + archive.label("slopeValues"); + archive(slopeValues); + archive.label("cutValues"); + archive(cutValues); + } + +}; + +struct RawPSDMatrix { + DynArray rows; + DynArray columns; + DynArray values; + + explicit RawPSDMatrix(MemoryResource* memRes) : + rows{memRes}, + columns{memRes}, + values{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("rows"); + archive(rows); + archive.label("columns"); + archive(columns); + archive.label("values"); + archive(values); + } + +}; + +struct RawControls { + std::uint16_t psdCount; + RawConditionalTable conditionals; + RawPSDMatrix psds; + + explicit RawControls(MemoryResource* memRes) : + psdCount{}, + conditionals{memRes}, + psds{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("psdCount"); + archive(psdCount); + archive.label("conditionals"); + archive(conditionals); + archive.label("psds"); + archive(psds); + } + +}; + +struct RawJointGroup { + // Row count of each LOD + // 12, 9, 3, + // | | + LOD-2 contains first 3 rows + // | + LOD-1 contains first 9 rows + // + LOD-0 contains first 12 rows + DynArray lods; + // Sub-matrix col -> input vector + DynArray inputIndices; + // Sub-matrix row -> output vector + DynArray outputIndices; + // Non-zero values of all sub-matrices + AlignedDynArray values; + + DynArray jointIndices; + + explicit RawJointGroup(MemoryResource* memRes) : + lods{memRes}, + inputIndices{memRes}, + outputIndices{memRes}, + values{memRes}, + jointIndices{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("lods"); + archive(lods); + archive.label("inputIndices"); + archive(inputIndices); + archive.label("outputIndices"); + archive(outputIndices); + archive.label("values"); + archive(values); + archive.label("jointIndices"); + archive(jointIndices); + } + +}; + +struct RawJoints { + std::uint16_t rowCount; + std::uint16_t colCount; + Vector jointGroups; + + explicit RawJoints(MemoryResource* memRes) : + rowCount{}, + colCount{}, + jointGroups{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("rowCount"); + archive(rowCount); + archive.label("colCount"); + archive(colCount); + archive.label("jointGroups"); + archive(jointGroups); + } + +}; + +struct RawBlendShapeChannels { + DynArray lods; + DynArray inputIndices; + DynArray outputIndices; + + explicit RawBlendShapeChannels(MemoryResource* memRes) : + lods{memRes}, + inputIndices{memRes}, + outputIndices{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("lods"); + archive(lods); + archive.label("inputIndices"); + archive(inputIndices); + archive.label("outputIndices"); + archive(outputIndices); + } + +}; + +struct RawAnimatedMaps { + DynArray lods; + RawConditionalTable conditionals; + + explicit RawAnimatedMaps(MemoryResource* memRes) : + lods{memRes}, + conditionals{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("lods"); + archive(lods); + archive.label("conditionals"); + archive(conditionals); + } + +}; + +struct RawBehavior { + terse::ArchiveOffset::Proxy marker; + + terse::ArchiveOffset::Proxy controlsMarker; + RawControls controls; + + terse::ArchiveOffset::Proxy jointsMarker; + RawJoints joints; + + terse::ArchiveOffset::Proxy blendShapeChannelsMarker; + RawBlendShapeChannels blendShapeChannels; + + terse::ArchiveOffset::Proxy animatedMapsMarker; + RawAnimatedMaps animatedMaps; + + RawBehavior(terse::ArchiveOffset& markerTarget, + terse::ArchiveOffset& controlsMarkerTarget, + terse::ArchiveOffset& jointsMarkerTarget, + terse::ArchiveOffset& blendShapeChannelsMarkerTarget, + terse::ArchiveOffset& animatedMapsMarkerTarget, + MemoryResource* memRes) : + marker{markerTarget}, + controlsMarker{controlsMarkerTarget}, + controls{memRes}, + jointsMarker{jointsMarkerTarget}, + joints{memRes}, + blendShapeChannelsMarker{blendShapeChannelsMarkerTarget}, + blendShapeChannels{memRes}, + animatedMapsMarker{animatedMapsMarkerTarget}, + animatedMaps{memRes} { + } + + template + void serialize(Archive& archive) { + archive(marker, controlsMarker); + archive.label("controls"); + archive(controls); + archive(jointsMarker); + archive.label("joints"); + archive(joints); + archive(blendShapeChannelsMarker); + archive.label("blendShapeChannels"); + archive(blendShapeChannels); + archive(animatedMapsMarker); + archive.label("animatedMaps"); + archive(animatedMaps); + } + +}; + +struct RawTextureCoordinateVector { + DynArray us; + DynArray vs; + + explicit RawTextureCoordinateVector(MemoryResource* memRes) : + us{memRes}, + vs{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("us"); + archive(us); + archive.label("vs"); + archive(vs); + } + + std::size_t size() const { + assert(us.size() == vs.size()); + return us.size(); + } + + void clear() { + us.clear(); + vs.clear(); + } + +}; + +struct RawVertexLayoutVector { + DynArray positions; + DynArray textureCoordinates; + DynArray normals; + + explicit RawVertexLayoutVector(MemoryResource* memRes) : + positions{memRes}, + textureCoordinates{memRes}, + normals{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("positions"); + archive(positions); + archive.label("textureCoordinates"); + archive(textureCoordinates); + archive.label("normals"); + archive(normals); + } + + std::size_t size() const { + assert(positions.size() == textureCoordinates.size() && textureCoordinates.size() == normals.size()); + return positions.size(); + } + + void clear() { + positions.clear(); + textureCoordinates.clear(); + normals.clear(); + } + +}; + +struct RawFace { + DynArray layoutIndices; + + explicit RawFace(MemoryResource* memRes) : + layoutIndices{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("layoutIndices"); + archive(layoutIndices); + } + +}; + +struct RawVertexSkinWeights { + AlignedDynArray weights; + DynArray jointIndices; + + explicit RawVertexSkinWeights(MemoryResource* memRes) : + weights{memRes}, + jointIndices{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("weights"); + archive(weights); + archive.label("jointIndices"); + archive(jointIndices); + } + +}; + +struct RawBlendShapeTarget { + RawVector3Vector deltas; + DynArray vertexIndices; + std::uint16_t blendShapeChannelIndex; + + explicit RawBlendShapeTarget(MemoryResource* memRes) : + deltas{memRes}, + vertexIndices{memRes}, + blendShapeChannelIndex{} { + } + + template + void serialize(Archive& archive) { + archive.label("deltas"); + archive(deltas); + archive.label("vertexIndices"); + archive(vertexIndices); + archive.label("blendShapeChannelIndex"); + archive(blendShapeChannelIndex); + } + +}; + +struct RawMesh { + terse::ArchiveOffset offset; + RawVector3Vector positions; + RawTextureCoordinateVector textureCoordinates; + RawVector3Vector normals; + RawVertexLayoutVector layouts; + Vector faces; + std::uint16_t maximumInfluencePerVertex; + Vector skinWeights; + Vector blendShapeTargets; + terse::ArchiveOffset::Proxy marker; + + explicit RawMesh(MemoryResource* memRes) : + offset{}, + positions{memRes}, + textureCoordinates{memRes}, + normals{memRes}, + layouts{memRes}, + faces{memRes}, + maximumInfluencePerVertex{}, + skinWeights{memRes}, + blendShapeTargets{memRes}, + marker{offset} { + } + + template + void serialize(Archive& archive) { + archive.label("offset"); + archive(offset); + archive.label("positions"); + archive(positions); + archive.label("textureCoordinates"); + archive(textureCoordinates); + archive.label("normals"); + archive(normals); + archive.label("layouts"); + archive(layouts); + archive.label("faces"); + archive(faces); + archive.label("maximumInfluencePerVertex"); + archive(maximumInfluencePerVertex); + archive.label("skinWeights"); + archive(skinWeights); + archive.label("blendShapeTargets"); + archive(blendShapeTargets); + archive(marker); + } + +}; + +struct RawGeometry { + terse::ArchiveOffset::Proxy marker; + Vector meshes; + + RawGeometry(terse::ArchiveOffset& markerTarget, MemoryResource* memRes) : + marker{markerTarget}, + meshes{memRes} { + } + + template + void serialize(Archive& archive) { + archive(marker); + archive.label("meshes"); + archive(meshes); + } + +}; + +struct DNA { + MemoryResource* memRes; + Signature<3> signature{{'D', 'N', 'A'}}; + Version version{2, 1}; + SectionLookupTable sections; + RawDescriptor descriptor; + RawDefinition definition; + RawBehavior behavior; + RawGeometry geometry; + Signature<3> eof{{'A', 'N', 'D'}}; + + explicit DNA(MemoryResource* memRes_) : + memRes{memRes_}, + sections{}, + descriptor{sections.descriptor, memRes}, + definition{sections.definition, memRes}, + behavior{sections.behavior, + sections.controls, + sections.joints, + sections.blendShapeChannels, + sections.animatedMaps, + memRes}, + geometry{sections.geometry, memRes} { + } + + template + void load(Archive& archive) { + archive.label("signature"); + archive(signature); + archive.label("version"); + archive(version); + if (signature.matches() && version.matches()) { + archive.label("sections"); + archive(sections); + archive.label("descriptor"); + archive(descriptor); + archive.label("definition"); + archive(definition); + archive.label("behavior"); + archive(behavior); + archive.label("geometry"); + archive(geometry); + archive.label("eof"); + archive(eof); + assert(eof.matches()); + } + } + + template + void save(Archive& archive) { + archive.label("signature"); + archive(signature); + archive.label("version"); + archive(version); + archive.label("sections"); + archive(sections); + archive.label("descriptor"); + archive(descriptor); + archive.label("definition"); + archive(definition); + archive.label("behavior"); + archive(behavior); + archive.label("geometry"); + archive(geometry); + archive.label("eof"); + archive(eof); + } + + void unloadDefinition() { + definition = RawDefinition{sections.definition, memRes}; + } + + void unloadBehavior() { + behavior = + RawBehavior{sections.behavior, sections.controls, sections.joints, sections.blendShapeChannels, sections.animatedMaps, + memRes}; + } + + void unloadGeometry() { + geometry = RawGeometry{sections.geometry, memRes}; + } + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/DataLayerBitmask.h b/dnacalib/DNACalib/src/dna/DataLayerBitmask.h new file mode 100644 index 0000000..4285dcd --- /dev/null +++ b/dnacalib/DNACalib/src/dna/DataLayerBitmask.h @@ -0,0 +1,45 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/DataLayer.h" +#include "dna/utils/ScopedEnumEx.h" + +namespace dna { + +enum class DataLayerBitmask { + Descriptor = 1, + Definition = 2, + Behavior = 4, + GeometryBlendShapesOnly = 8, + GeometryRest = 16, +}; + +inline DataLayerBitmask computeDataLayerBitmask(DataLayer layer) { + DataLayerBitmask result = DataLayerBitmask::Descriptor; + if (layer == DataLayer::Definition) { + result |= DataLayerBitmask::Definition; + } else if (layer == DataLayer::Behavior) { + result |= DataLayerBitmask::Definition; + result |= DataLayerBitmask::Behavior; + } else if (layer == DataLayer::Geometry) { + result |= DataLayerBitmask::Definition; + result |= DataLayerBitmask::GeometryBlendShapesOnly; + result |= DataLayerBitmask::GeometryRest; + } else if (layer == DataLayer::GeometryWithoutBlendShapes) { + result |= DataLayerBitmask::Definition; + result |= DataLayerBitmask::GeometryRest; + } else if (layer == DataLayer::AllWithoutBlendShapes) { + result |= DataLayerBitmask::Definition; + result |= DataLayerBitmask::Behavior; + result |= DataLayerBitmask::GeometryRest; + } else if (layer == DataLayer::All) { + result |= DataLayerBitmask::Definition; + result |= DataLayerBitmask::Behavior; + result |= DataLayerBitmask::GeometryBlendShapesOnly; + result |= DataLayerBitmask::GeometryRest; + } + return result; +} + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/DenormalizedData.h b/dnacalib/DNACalib/src/dna/DenormalizedData.h new file mode 100644 index 0000000..824df52 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/DenormalizedData.h @@ -0,0 +1,79 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/LODMapping.h" +#include "dna/types/Aliases.h" +#include "dna/utils/Extd.h" + +#include +#include + +namespace dna { + +template +struct DenormalizedData { + LODMapping jointVariableAttributeIndices; + LODMapping meshBlendShapeMappingIndices; + + explicit DenormalizedData(MemoryResource* memRes) : + jointVariableAttributeIndices{memRes}, + meshBlendShapeMappingIndices{memRes} { + } + + void populate(const Reader* source) { + populateJointVariableAttributeIndices(source, jointVariableAttributeIndices); + populateMeshBlendShapeMappingIndices(source, meshBlendShapeMappingIndices); + } + + private: + void populateJointVariableAttributeIndices(const Reader* source, LODMapping& destination) { + // Prepare storage for all available LODs + const auto lodCount = source->getLODCount(); + destination.setLODCount(lodCount); + // Concatenate all output indices for each LOD + for (std::uint16_t i = 0u; i < source->getJointGroupCount(); ++i) { + const auto outputIndices = source->getJointGroupOutputIndices(i); + const auto lodSizes = source->getJointGroupLODs(i); + assert(lodSizes.size() == lodCount); + for (std::uint16_t lod = 0u; lod < lodCount; ++lod) { + // In this case, each LOD has a distinct set of indices, so the LOD and Index parameters + // are the same for all LODs + destination.addIndices(lod, outputIndices.data(), lodSizes[lod]); + destination.associateLODWithIndices(lod, lod); + } + } + } + + void populateMeshBlendShapeMappingIndices(const Reader* source, LODMapping& destination) { + // Prepare storage for all available LODs + const auto lodCount = source->getLODCount(); + destination.setLODCount(lodCount); + // Include only those mapping indices which are present in the already filtered + // mesh and blendshape LOD mapping + for (std::uint16_t lod = 0u; lod < lodCount; ++lod) { + const auto meshIndices = source->getMeshIndicesForLOD(lod); + const auto blendShapeIndices = source->getBlendShapeChannelIndicesForLOD(lod); + + auto isMappingNeeded = [&meshIndices, &blendShapeIndices](const MeshBlendShapeChannelMapping& mapping) { + const bool meshNeeded = extd::contains(meshIndices, mapping.meshIndex); + const bool blendShapeNeeded = extd::contains(blendShapeIndices, mapping.blendShapeChannelIndex); + return (meshNeeded && blendShapeNeeded); + }; + + for (std::uint16_t i = 0u; i < source->getMeshBlendShapeChannelMappingCount(); ++i) { + const auto mapping = source->getMeshBlendShapeChannelMapping(i); + if (isMappingNeeded(mapping)) { + // In this case, each LOD has a distinct set of indices, so the LOD and Index parameters + // are the same for all LODs + destination.addIndices(lod, &i, static_cast(1)); + } + } + + destination.associateLODWithIndices(lod, lod); + } + } + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/LODConstraint.cpp b/dnacalib/DNACalib/src/dna/LODConstraint.cpp new file mode 100644 index 0000000..b4c457d --- /dev/null +++ b/dnacalib/DNACalib/src/dna/LODConstraint.cpp @@ -0,0 +1,65 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dna/LODConstraint.h" + +#include "dna/utils/Extd.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dna { + +LODConstraint::LODConstraint(std::uint16_t maxLOD, std::uint16_t minLOD, MemoryResource* memRes) : lods{memRes} { + assert(maxLOD <= minLOD); + lods.resize(static_cast(minLOD - maxLOD) + 1ul); + std::iota(lods.begin(), lods.end(), maxLOD); +} + +LODConstraint::LODConstraint(ConstArrayView lods_, MemoryResource* memRes) : lods{lods_.begin(), lods_.end(), + memRes} { + std::sort(lods.begin(), lods.end()); +} + +bool LODConstraint::hasImpactOn(std::uint16_t lodCount) const { + std::uint16_t lod = {}; + for (auto it = lods.begin(); (it != lods.end()) && (lod < lodCount); ++it) { + lod = static_cast(lod + static_cast(lod == *it)); + } + return (lod != lodCount); +} + +std::uint16_t LODConstraint::getMaxLOD() const { + return (lods.empty() ? std::uint16_t{} : lods.front()); +} + +std::uint16_t LODConstraint::getMinLOD() const { + return (lods.empty() ? std::uint16_t{} : lods.back()); +} + +std::uint16_t LODConstraint::getLODCount() const { + return static_cast(lods.size()); +} + +void LODConstraint::clampTo(std::uint16_t lodCount) { + extd::filter(lods, [lodCount](std::uint16_t lod, std::size_t /*unused*/) { + return lod < lodCount; + }); +} + +void LODConstraint::applyTo(Vector& unconstrainedLODs) const { + extd::filter(unconstrainedLODs, extd::byPosition(lods)); +} + +void LODConstraint::applyTo(DynArray& unconstrainedLODs) const { + extd::filter(unconstrainedLODs, extd::byPosition(lods)); +} + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/LODConstraint.h b/dnacalib/DNACalib/src/dna/LODConstraint.h new file mode 100644 index 0000000..0b47c83 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/LODConstraint.h @@ -0,0 +1,29 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/TypeDefs.h" + +#include + +namespace dna { + +class LODConstraint { + public: + LODConstraint(std::uint16_t maxLOD, std::uint16_t minLOD, MemoryResource* memRes); + LODConstraint(ConstArrayView lods, MemoryResource* memRes); + + bool hasImpactOn(std::uint16_t lodCount) const; + std::uint16_t getMaxLOD() const; + std::uint16_t getMinLOD() const; + std::uint16_t getLODCount() const; + void clampTo(std::uint16_t lodCount); + void applyTo(Vector& unconstrainedLODs) const; + void applyTo(DynArray& unconstrainedLODs) const; + + private: + Vector lods; + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/LODMapping.cpp b/dnacalib/DNACalib/src/dna/LODMapping.cpp new file mode 100644 index 0000000..95f6a6f --- /dev/null +++ b/dnacalib/DNACalib/src/dna/LODMapping.cpp @@ -0,0 +1,137 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dna/LODMapping.h" + +#include "dna/LODConstraint.h" +#include "dna/TypeDefs.h" +#include "dna/utils/Extd.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dna { + +LODMapping::LODMapping(MemoryResource* memRes_) : + lods{memRes_}, + indices{memRes_} { +} + +std::uint16_t LODMapping::getLODCount() const { + return static_cast(lods.size()); +} + +void LODMapping::resetIndices() { + indices.clear(); +} + +void LODMapping::resetLODs() { + lods.clear(); +} + +void LODMapping::reset() { + lods.clear(); + indices.clear(); +} + +void LODMapping::setLODCount(std::uint16_t lodCount) { + reset(); + lods.resize(lodCount); + indices.resize(lodCount); +} + +void LODMapping::discardLODs(const LODConstraint& lodConstraint) { + lodConstraint.applyTo(lods); + cleanupIndices(); +} + +void LODMapping::cleanupIndices() { + for (std::size_t i = indices.size(); i > 0ul; --i) { + const auto idx = (i - 1ul); + if (std::find(lods.begin(), lods.end(), idx) == lods.end()) { + indices.erase(extd::advanced(indices.begin(), idx)); + for (auto& l2i : lods) { + if (l2i > idx) { + --l2i; + } + } + } + } +} + +ConstArrayView LODMapping::getIndices(std::uint16_t lod) const { + if (lod >= lods.size()) { + return {}; + } + assert(lods[lod] < indices.size()); + const auto it = extd::advanced(indices.cbegin(), lods[lod]); + return (it == indices.cend() ? ConstArrayView{} : ConstArrayView{it->data(), it->size()}); +} + +std::uint16_t LODMapping::getIndexListCount() const { + return static_cast(indices.size()); +} + +void LODMapping::clearIndices(std::uint16_t index) { + if (index < indices.size()) { + indices[index].clear(); + } else { + indices.resize(index + 1ul); + } +} + +void LODMapping::addIndices(std::uint16_t index, const std::uint16_t* source, std::uint16_t count) { + if (index >= indices.size()) { + indices.resize(index + 1ul); + } + indices[index].reserve(count); + indices[index].insert(indices[index].end(), source, source + count); +} + +void LODMapping::mapIndices(std::function mapper) { + for (auto& row : indices) { + for (auto& value : row) { + value = mapper(value); + } + } +} + +void LODMapping::filterIndices(std::function filterer) { + for (auto& row : indices) { + for (auto it = row.begin(); it != row.end();) { + if (filterer(*it)) { + ++it; + } else { + it = row.erase(it); + } + } + } +} + +void LODMapping::associateLODWithIndices(std::uint16_t lod, std::uint16_t index) { + if (lod >= lods.size()) { + lods.resize(lod + 1ul); + } + if (index >= indices.size()) { + indices.resize(index + 1ul); + } + lods[lod] = index; +} + +UnorderedSet LODMapping::getCombinedDistinctIndices(MemoryResource* memRes) const { + UnorderedSet distinctIndices{memRes}; + for (const auto& row : indices) { + distinctIndices.insert(row.begin(), row.end()); + } + return distinctIndices; +} + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/LODMapping.h b/dnacalib/DNACalib/src/dna/LODMapping.h new file mode 100644 index 0000000..7b6b787 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/LODMapping.h @@ -0,0 +1,57 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/TypeDefs.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dna { + +class LODConstraint; + +class LODMapping { + public: + explicit LODMapping(MemoryResource* memRes_); + + std::uint16_t getLODCount() const; + void resetIndices(); + void resetLODs(); + void reset(); + void setLODCount(std::uint16_t lodCount); + void discardLODs(const LODConstraint& lodConstraint); + ConstArrayView getIndices(std::uint16_t lod) const; + std::uint16_t getIndexListCount() const; + void clearIndices(std::uint16_t index); + void addIndices(std::uint16_t index, const std::uint16_t* source, std::uint16_t count); + void associateLODWithIndices(std::uint16_t lod, std::uint16_t index); + void mapIndices(std::function mapper); + void filterIndices(std::function filterer); + UnorderedSet getCombinedDistinctIndices(MemoryResource* memRes) const; + + private: + void cleanupIndices(); + + protected: + // Map indices to rows of the below defined matrix, e.g.: + // lods: [0, 0, 1, 1] + // indices: {[10, 15, 12], [9, 7, 43, 67]} + // expands into: + // 0: [10, 15, 12] + // 1: [10, 15, 12] + // 2: [9, 7, 43, 67] + // 3: [9, 7, 43, 67] + Vector lods; + Matrix indices; + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/Reader.cpp b/dnacalib/DNACalib/src/dna/Reader.cpp new file mode 100644 index 0000000..094fa24 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/Reader.cpp @@ -0,0 +1,13 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dna/Reader.h" + +namespace dna { + +DescriptorReader::~DescriptorReader() = default; +DefinitionReader::~DefinitionReader() = default; +BehaviorReader::~BehaviorReader() = default; +GeometryReader::~GeometryReader() = default; +Reader::~Reader() = default; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/ReaderImpl.h b/dnacalib/DNACalib/src/dna/ReaderImpl.h new file mode 100644 index 0000000..a1d17b8 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/ReaderImpl.h @@ -0,0 +1,968 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/BaseImpl.h" +#include "dna/DenormalizedData.h" +#include "dna/TypeDefs.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dna { + +template +class ReaderImpl : public TReaderBase, public virtual BaseImpl { + public: + explicit ReaderImpl(MemoryResource* memRes_); + + // DescriptorReader methods start + StringView getName() const override; + Archetype getArchetype() const override; + Gender getGender() const override; + std::uint16_t getAge() const override; + std::uint32_t getMetaDataCount() const override; + StringView getMetaDataKey(std::uint32_t index) const override; + StringView getMetaDataValue(const char* key) const override; + TranslationUnit getTranslationUnit() const override; + RotationUnit getRotationUnit() const override; + CoordinateSystem getCoordinateSystem() const override; + std::uint16_t getLODCount() const override; + std::uint16_t getDBMaxLOD() const override; + StringView getDBComplexity() const override; + StringView getDBName() const override; + + // DefinitionReader methods start + std::uint16_t getGUIControlCount() const override; + StringView getGUIControlName(std::uint16_t index) const override; + std::uint16_t getRawControlCount() const override; + StringView getRawControlName(std::uint16_t index) const override; + std::uint16_t getJointCount() const override; + StringView getJointName(std::uint16_t index) const override; + std::uint16_t getJointIndexListCount() const override; + ConstArrayView getJointIndicesForLOD(std::uint16_t lod) const override; + std::uint16_t getJointParentIndex(std::uint16_t index) const override; + std::uint16_t getBlendShapeChannelCount() const override; + StringView getBlendShapeChannelName(std::uint16_t index) const override; + std::uint16_t getBlendShapeChannelIndexListCount() const override; + ConstArrayView getBlendShapeChannelIndicesForLOD(std::uint16_t lod) const override; + std::uint16_t getAnimatedMapCount() const override; + StringView getAnimatedMapName(std::uint16_t index) const override; + std::uint16_t getAnimatedMapIndexListCount() const override; + ConstArrayView getAnimatedMapIndicesForLOD(std::uint16_t lod) const override; + std::uint16_t getMeshCount() const override; + StringView getMeshName(std::uint16_t index) const override; + std::uint16_t getMeshIndexListCount() const override; + ConstArrayView getMeshIndicesForLOD(std::uint16_t lod) const override; + std::uint16_t getMeshBlendShapeChannelMappingCount() const override; + MeshBlendShapeChannelMapping getMeshBlendShapeChannelMapping(std::uint16_t index) const override; + ConstArrayView getMeshBlendShapeChannelMappingIndicesForLOD(std::uint16_t lod) const override; + Vector3 getNeutralJointTranslation(std::uint16_t index) const override; + ConstArrayView getNeutralJointTranslationXs() const override; + ConstArrayView getNeutralJointTranslationYs() const override; + ConstArrayView getNeutralJointTranslationZs() const override; + Vector3 getNeutralJointRotation(std::uint16_t index) const override; + ConstArrayView getNeutralJointRotationXs() const override; + ConstArrayView getNeutralJointRotationYs() const override; + ConstArrayView getNeutralJointRotationZs() const override; + + // BehaviorReader methods start + ConstArrayView getGUIToRawInputIndices() const override; + ConstArrayView getGUIToRawOutputIndices() const override; + ConstArrayView getGUIToRawFromValues() const override; + ConstArrayView getGUIToRawToValues() const override; + ConstArrayView getGUIToRawSlopeValues() const override; + ConstArrayView getGUIToRawCutValues() const override; + std::uint16_t getPSDCount() const override; + ConstArrayView getPSDRowIndices() const override; + ConstArrayView getPSDColumnIndices() const override; + ConstArrayView getPSDValues() const override; + std::uint16_t getJointRowCount() const override; + std::uint16_t getJointColumnCount() const override; + ConstArrayView getJointVariableAttributeIndices(std::uint16_t lod) const override; + std::uint16_t getJointGroupCount() const override; + ConstArrayView getJointGroupLODs(std::uint16_t jointGroupIndex) const override; + ConstArrayView getJointGroupInputIndices(std::uint16_t jointGroupIndex) const override; + ConstArrayView getJointGroupOutputIndices(std::uint16_t jointGroupIndex) const override; + ConstArrayView getJointGroupValues(std::uint16_t jointGroupIndex) const override; + ConstArrayView getJointGroupJointIndices(std::uint16_t jointGroupIndex) const override; + ConstArrayView getBlendShapeChannelLODs() const override; + ConstArrayView getBlendShapeChannelOutputIndices() const override; + ConstArrayView getBlendShapeChannelInputIndices() const override; + ConstArrayView getAnimatedMapLODs() const override; + ConstArrayView getAnimatedMapInputIndices() const override; + ConstArrayView getAnimatedMapOutputIndices() const override; + ConstArrayView getAnimatedMapFromValues() const override; + ConstArrayView getAnimatedMapToValues() const override; + ConstArrayView getAnimatedMapSlopeValues() const override; + ConstArrayView getAnimatedMapCutValues() const override; + + // GeometryReader methods start + std::uint32_t getVertexPositionCount(std::uint16_t meshIndex) const override; + Position getVertexPosition(std::uint16_t meshIndex, std::uint32_t vertexIndex) const override; + ConstArrayView getVertexPositionXs(std::uint16_t meshIndex) const override; + ConstArrayView getVertexPositionYs(std::uint16_t meshIndex) const override; + ConstArrayView getVertexPositionZs(std::uint16_t meshIndex) const override; + std::uint32_t getVertexTextureCoordinateCount(std::uint16_t meshIndex) const override; + TextureCoordinate getVertexTextureCoordinate(std::uint16_t meshIndex, + std::uint32_t textureCoordinateIndex) const override; + ConstArrayView getVertexTextureCoordinateUs(std::uint16_t meshIndex) const override; + ConstArrayView getVertexTextureCoordinateVs(std::uint16_t meshIndex) const override; + std::uint32_t getVertexNormalCount(std::uint16_t meshIndex) const override; + Normal getVertexNormal(std::uint16_t meshIndex, std::uint32_t normalIndex) const override; + ConstArrayView getVertexNormalXs(std::uint16_t meshIndex) const override; + ConstArrayView getVertexNormalYs(std::uint16_t meshIndex) const override; + ConstArrayView getVertexNormalZs(std::uint16_t meshIndex) const override; + std::uint32_t getFaceCount(std::uint16_t meshIndex) const override; + ConstArrayView getFaceVertexLayoutIndices(std::uint16_t meshIndex, std::uint32_t faceIndex) const override; + std::uint32_t getVertexLayoutCount(std::uint16_t meshIndex) const override; + VertexLayout getVertexLayout(std::uint16_t meshIndex, std::uint32_t layoutIndex) const override; + ConstArrayView getVertexLayoutPositionIndices(std::uint16_t meshIndex) const override; + ConstArrayView getVertexLayoutTextureCoordinateIndices(std::uint16_t meshIndex) const override; + ConstArrayView getVertexLayoutNormalIndices(std::uint16_t meshIndex) const override; + std::uint16_t getMaximumInfluencePerVertex(std::uint16_t meshIndex) const override; + std::uint32_t getSkinWeightsCount(std::uint16_t meshIndex) const override; + ConstArrayView getSkinWeightsValues(std::uint16_t meshIndex, std::uint32_t vertexIndex) const override; + ConstArrayView getSkinWeightsJointIndices(std::uint16_t meshIndex, + std::uint32_t vertexIndex) const override; + std::uint16_t getBlendShapeTargetCount(std::uint16_t meshIndex) const override; + std::uint16_t getBlendShapeChannelIndex(std::uint16_t meshIndex, std::uint16_t blendShapeTargetIndex) const override; + std::uint32_t getBlendShapeTargetDeltaCount(std::uint16_t meshIndex, std::uint16_t blendShapeTargetIndex) const override; + Delta getBlendShapeTargetDelta(std::uint16_t meshIndex, std::uint16_t blendShapeTargetIndex, + std::uint32_t deltaIndex) const override; + ConstArrayView getBlendShapeTargetDeltaXs(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const override; + ConstArrayView getBlendShapeTargetDeltaYs(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const override; + ConstArrayView getBlendShapeTargetDeltaZs(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const override; + ConstArrayView getBlendShapeTargetVertexIndices(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const override; + + protected: + mutable DenormalizedData cache; + +}; + + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4589) +#endif +template +inline ReaderImpl::ReaderImpl(MemoryResource* memRes_) : BaseImpl{memRes_}, cache{memRes_} { +} + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4505) +#endif +template +inline StringView ReaderImpl::getName() const { + return {dna.descriptor.name.data(), dna.descriptor.name.size()}; +} + +template +inline Archetype ReaderImpl::getArchetype() const { + return static_cast(dna.descriptor.archetype); +} + +template +inline Gender ReaderImpl::getGender() const { + return static_cast(dna.descriptor.gender); +} + +template +inline std::uint16_t ReaderImpl::getAge() const { + return dna.descriptor.age; +} + +template +inline std::uint32_t ReaderImpl::getMetaDataCount() const { + return static_cast(dna.descriptor.metadata.size()); +} + +template +inline StringView ReaderImpl::getMetaDataKey(std::uint32_t index) const { + if (index < dna.descriptor.metadata.size()) { + const auto& key = std::get<0>(dna.descriptor.metadata[index]); + return {key.data(), key.size()}; + } + return {}; +} + +template +inline StringView ReaderImpl::getMetaDataValue(const char* key) const { + for (const auto& data: dna.descriptor.metadata) { + if (std::get<0>(data) == key) { + const auto& value = std::get<1>(data); + return {value.data(), value.size()}; + } + } + return {}; +} + +template +inline TranslationUnit ReaderImpl::getTranslationUnit() const { + return static_cast(dna.descriptor.translationUnit); +} + +template +inline RotationUnit ReaderImpl::getRotationUnit() const { + return static_cast(dna.descriptor.rotationUnit); +} + +template +inline CoordinateSystem ReaderImpl::getCoordinateSystem() const { + return { + static_cast(dna.descriptor.coordinateSystem.xAxis), + static_cast(dna.descriptor.coordinateSystem.yAxis), + static_cast(dna.descriptor.coordinateSystem.zAxis) + }; +} + +template +inline std::uint16_t ReaderImpl::getLODCount() const { + return dna.descriptor.lodCount; +} + +template +inline std::uint16_t ReaderImpl::getDBMaxLOD() const { + return dna.descriptor.maxLOD; +} + +template +inline StringView ReaderImpl::getDBComplexity() const { + return {dna.descriptor.complexity.data(), dna.descriptor.complexity.size()}; +} + +template +inline StringView ReaderImpl::getDBName() const { + return {dna.descriptor.dbName.data(), dna.descriptor.dbName.size()}; +} + +template +inline std::uint16_t ReaderImpl::getGUIControlCount() const { + return static_cast(dna.definition.guiControlNames.size()); +} + +template +inline StringView ReaderImpl::getGUIControlName(std::uint16_t index) const { + if (index < dna.definition.guiControlNames.size()) { + const auto& guiControlName = dna.definition.guiControlNames[index]; + return {guiControlName.data(), guiControlName.size()}; + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getRawControlCount() const { + return static_cast(dna.definition.rawControlNames.size()); +} + +template +inline StringView ReaderImpl::getRawControlName(std::uint16_t index) const { + if (index < dna.definition.rawControlNames.size()) { + const auto& rawControlName = dna.definition.rawControlNames[index]; + return {rawControlName.data(), rawControlName.size()}; + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getJointCount() const { + return static_cast(dna.definition.jointNames.size()); +} + +template +inline StringView ReaderImpl::getJointName(std::uint16_t index) const { + if (index < dna.definition.jointNames.size()) { + const auto& jointName = dna.definition.jointNames[index]; + return {jointName.data(), jointName.size()}; + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getJointIndexListCount() const { + return dna.definition.lodJointMapping.getIndexListCount(); +} + +template +inline ConstArrayView ReaderImpl::getJointIndicesForLOD(std::uint16_t lod) const { + return dna.definition.lodJointMapping.getIndices(lod); +} + +template +inline std::uint16_t ReaderImpl::getJointParentIndex(std::uint16_t index) const { + if (index < dna.definition.jointHierarchy.size()) { + return dna.definition.jointHierarchy[index]; + } + return std::numeric_limits::max(); +} + +template +inline std::uint16_t ReaderImpl::getBlendShapeChannelCount() const { + return static_cast(dna.definition.blendShapeChannelNames.size()); +} + +template +inline StringView ReaderImpl::getBlendShapeChannelName(std::uint16_t index) const { + if (index < dna.definition.blendShapeChannelNames.size()) { + const auto& blendShapeName = dna.definition.blendShapeChannelNames[index]; + return {blendShapeName.data(), blendShapeName.size()}; + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getBlendShapeChannelIndexListCount() const { + return dna.definition.lodBlendShapeMapping.getIndexListCount(); +} + +template +inline ConstArrayView ReaderImpl::getBlendShapeChannelIndicesForLOD(std::uint16_t lod) const { + return dna.definition.lodBlendShapeMapping.getIndices(lod); +} + +template +inline std::uint16_t ReaderImpl::getAnimatedMapCount() const { + return static_cast(dna.definition.animatedMapNames.size()); +} + +template +inline StringView ReaderImpl::getAnimatedMapName(std::uint16_t index) const { + if (index < dna.definition.animatedMapNames.size()) { + const auto& animatedMapName = dna.definition.animatedMapNames[index]; + return {animatedMapName.data(), animatedMapName.size()}; + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getAnimatedMapIndexListCount() const { + return dna.definition.lodAnimatedMapMapping.getIndexListCount(); +} + +template +inline ConstArrayView ReaderImpl::getAnimatedMapIndicesForLOD(std::uint16_t lod) const { + return dna.definition.lodAnimatedMapMapping.getIndices(lod); +} + +template +inline std::uint16_t ReaderImpl::getMeshCount() const { + return static_cast(dna.definition.meshNames.size()); +} + +template +inline StringView ReaderImpl::getMeshName(std::uint16_t index) const { + if (index < dna.definition.meshNames.size()) { + const auto& meshName = dna.definition.meshNames[index]; + return {meshName.data(), meshName.size()}; + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getMeshIndexListCount() const { + return dna.definition.lodMeshMapping.getIndexListCount(); +} + +template +inline ConstArrayView ReaderImpl::getMeshIndicesForLOD(std::uint16_t lod) const { + return dna.definition.lodMeshMapping.getIndices(lod); +} + +template +inline std::uint16_t ReaderImpl::getMeshBlendShapeChannelMappingCount() const { + return static_cast(dna.definition.meshBlendShapeChannelMapping.size()); +} + +template +inline MeshBlendShapeChannelMapping ReaderImpl::getMeshBlendShapeChannelMapping(std::uint16_t index) const { + const auto mapping = dna.definition.meshBlendShapeChannelMapping.get(index); + return {mapping.from, mapping.to}; +} + +template +inline ConstArrayView ReaderImpl::getMeshBlendShapeChannelMappingIndicesForLOD(std::uint16_t lod) +const { + if (cache.meshBlendShapeMappingIndices.getLODCount() == static_cast(0)) { + cache.populate(this); + } + return cache.meshBlendShapeMappingIndices.getIndices(lod); +} + +template +inline Vector3 ReaderImpl::getNeutralJointTranslation(std::uint16_t index) const { + const auto& translations = dna.definition.neutralJointTranslations; + if (index < translations.xs.size()) { + return {translations.xs[index], translations.ys[index], translations.zs[index]}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getNeutralJointTranslationXs() const { + const auto& xs = dna.definition.neutralJointTranslations.xs; + return {xs.data(), xs.size()}; +} + +template +inline ConstArrayView ReaderImpl::getNeutralJointTranslationYs() const { + const auto& ys = dna.definition.neutralJointTranslations.ys; + return {ys.data(), ys.size()}; +} + +template +inline ConstArrayView ReaderImpl::getNeutralJointTranslationZs() const { + const auto& zs = dna.definition.neutralJointTranslations.zs; + return {zs.data(), zs.size()}; +} + +template +inline Vector3 ReaderImpl::getNeutralJointRotation(std::uint16_t index) const { + const auto& rotations = dna.definition.neutralJointRotations; + if (index < rotations.size()) { + return {rotations.xs[index], rotations.ys[index], rotations.zs[index]}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getNeutralJointRotationXs() const { + const auto& xs = dna.definition.neutralJointRotations.xs; + return {xs.data(), xs.size()}; +} + +template +inline ConstArrayView ReaderImpl::getNeutralJointRotationYs() const { + const auto& ys = dna.definition.neutralJointRotations.ys; + return {ys.data(), ys.size()}; +} + +template +inline ConstArrayView ReaderImpl::getNeutralJointRotationZs() const { + const auto& zs = dna.definition.neutralJointRotations.zs; + return {zs.data(), zs.size()}; +} + +template +inline ConstArrayView ReaderImpl::getGUIToRawInputIndices() const { + const auto& inputIndices = dna.behavior.controls.conditionals.inputIndices; + return {inputIndices.data(), inputIndices.size()}; +} + +template +inline ConstArrayView ReaderImpl::getGUIToRawOutputIndices() const { + const auto& outputIndices = dna.behavior.controls.conditionals.outputIndices; + return {outputIndices.data(), outputIndices.size()}; +} + +template +inline ConstArrayView ReaderImpl::getGUIToRawFromValues() const { + const auto& fromValues = dna.behavior.controls.conditionals.fromValues; + return {fromValues.data(), fromValues.size()}; +} + +template +inline ConstArrayView ReaderImpl::getGUIToRawToValues() const { + const auto& toValues = dna.behavior.controls.conditionals.toValues; + return {toValues.data(), toValues.size()}; +} + +template +inline ConstArrayView ReaderImpl::getGUIToRawSlopeValues() const { + const auto& slopeValues = dna.behavior.controls.conditionals.slopeValues; + return {slopeValues.data(), slopeValues.size()}; +} + +template +inline ConstArrayView ReaderImpl::getGUIToRawCutValues() const { + const auto& cutValues = dna.behavior.controls.conditionals.cutValues; + return {cutValues.data(), cutValues.size()}; +} + +template +inline std::uint16_t ReaderImpl::getPSDCount() const { + return dna.behavior.controls.psdCount; +} + +template +inline ConstArrayView ReaderImpl::getPSDRowIndices() const { + const auto& rows = dna.behavior.controls.psds.rows; + return {rows.data(), rows.size()}; +} + +template +inline ConstArrayView ReaderImpl::getPSDColumnIndices() const { + const auto& columns = dna.behavior.controls.psds.columns; + return {columns.data(), columns.size()}; +} + +template +inline ConstArrayView ReaderImpl::getPSDValues() const { + const auto& values = dna.behavior.controls.psds.values; + return {values.data(), values.size()}; +} + +template +inline std::uint16_t ReaderImpl::getJointRowCount() const { + return dna.behavior.joints.rowCount; +} + +template +inline std::uint16_t ReaderImpl::getJointColumnCount() const { + return dna.behavior.joints.colCount; +} + +template +inline ConstArrayView ReaderImpl::getJointVariableAttributeIndices(std::uint16_t lod) const { + if (cache.jointVariableAttributeIndices.getLODCount() == static_cast(0)) { + cache.populate(this); + } + return cache.jointVariableAttributeIndices.getIndices(lod); +} + +template +inline std::uint16_t ReaderImpl::getJointGroupCount() const { + return static_cast(dna.behavior.joints.jointGroups.size()); +} + +template +inline ConstArrayView ReaderImpl::getJointGroupLODs(std::uint16_t jointGroupIndex) const { + if (jointGroupIndex < dna.behavior.joints.jointGroups.size()) { + const auto& lods = dna.behavior.joints.jointGroups[jointGroupIndex].lods; + return {lods.data(), lods.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getJointGroupInputIndices(std::uint16_t jointGroupIndex) const { + if (jointGroupIndex < dna.behavior.joints.jointGroups.size()) { + const auto& inputIndices = dna.behavior.joints.jointGroups[jointGroupIndex].inputIndices; + return {inputIndices.data(), inputIndices.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getJointGroupOutputIndices(std::uint16_t jointGroupIndex) const { + if (jointGroupIndex < dna.behavior.joints.jointGroups.size()) { + const auto& outputIndices = dna.behavior.joints.jointGroups[jointGroupIndex].outputIndices; + return {outputIndices.data(), outputIndices.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getJointGroupValues(std::uint16_t jointGroupIndex) const { + if (jointGroupIndex < dna.behavior.joints.jointGroups.size()) { + const auto& values = dna.behavior.joints.jointGroups[jointGroupIndex].values; + return {values.data(), values.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getJointGroupJointIndices(std::uint16_t jointGroupIndex) const { + if (jointGroupIndex < dna.behavior.joints.jointGroups.size()) { + const auto& jointIndices = dna.behavior.joints.jointGroups[jointGroupIndex].jointIndices; + return {jointIndices.data(), jointIndices.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getBlendShapeChannelLODs() const { + const auto& lods = dna.behavior.blendShapeChannels.lods; + return {lods.data(), lods.size()}; +} + +template +inline ConstArrayView ReaderImpl::getBlendShapeChannelInputIndices() const { + const auto& inputIndices = dna.behavior.blendShapeChannels.inputIndices; + return {inputIndices.data(), inputIndices.size()}; +} + +template +inline ConstArrayView ReaderImpl::getBlendShapeChannelOutputIndices() const { + const auto& outputIndices = dna.behavior.blendShapeChannels.outputIndices; + return {outputIndices.data(), outputIndices.size()}; +} + +template +inline ConstArrayView ReaderImpl::getAnimatedMapLODs() const { + const auto& lods = dna.behavior.animatedMaps.lods; + return {lods.data(), lods.size()}; +} + +template +inline ConstArrayView ReaderImpl::getAnimatedMapInputIndices() const { + const auto& inputIndices = dna.behavior.animatedMaps.conditionals.inputIndices; + return {inputIndices.data(), inputIndices.size()}; +} + +template +inline ConstArrayView ReaderImpl::getAnimatedMapOutputIndices() const { + const auto& outputIndices = dna.behavior.animatedMaps.conditionals.outputIndices; + return {outputIndices.data(), outputIndices.size()}; +} + +template +inline ConstArrayView ReaderImpl::getAnimatedMapFromValues() const { + const auto& fromValues = dna.behavior.animatedMaps.conditionals.fromValues; + return {fromValues.data(), fromValues.size()}; +} + +template +inline ConstArrayView ReaderImpl::getAnimatedMapToValues() const { + const auto& toValues = dna.behavior.animatedMaps.conditionals.toValues; + return {toValues.data(), toValues.size()}; +} + +template +inline ConstArrayView ReaderImpl::getAnimatedMapSlopeValues() const { + const auto& slopeValues = dna.behavior.animatedMaps.conditionals.slopeValues; + return {slopeValues.data(), slopeValues.size()}; +} + +template +inline ConstArrayView ReaderImpl::getAnimatedMapCutValues() const { + const auto& cutValues = dna.behavior.animatedMaps.conditionals.cutValues; + return {cutValues.data(), cutValues.size()}; +} + +template +inline std::uint32_t ReaderImpl::getVertexPositionCount(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + return static_cast(dna.geometry.meshes[meshIndex].positions.xs.size()); + } + return 0u; +} + +template +inline Position ReaderImpl::getVertexPosition(std::uint16_t meshIndex, std::uint32_t vertexIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& positions = dna.geometry.meshes[meshIndex].positions; + if (vertexIndex < positions.size()) { + return {positions.xs[vertexIndex], positions.ys[vertexIndex], positions.zs[vertexIndex]}; + } + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexPositionXs(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& xPositions = dna.geometry.meshes[meshIndex].positions.xs; + return {xPositions.data(), xPositions.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexPositionYs(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& yPositions = dna.geometry.meshes[meshIndex].positions.ys; + return {yPositions.data(), yPositions.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexPositionZs(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& zPositions = dna.geometry.meshes[meshIndex].positions.zs; + return {zPositions.data(), zPositions.size()}; + } + return {}; +} + +template +inline std::uint32_t ReaderImpl::getVertexTextureCoordinateCount(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + return static_cast(dna.geometry.meshes[meshIndex].textureCoordinates.us.size()); + } + return 0u; +} + +template +inline TextureCoordinate ReaderImpl::getVertexTextureCoordinate(std::uint16_t meshIndex, + std::uint32_t textureCoordinateIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& textureCoordinates = dna.geometry.meshes[meshIndex].textureCoordinates; + if (textureCoordinateIndex < textureCoordinates.size()) { + return {textureCoordinates.us[textureCoordinateIndex], textureCoordinates.vs[textureCoordinateIndex]}; + } + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexTextureCoordinateUs(std::uint16_t meshIndex) const { + const auto& uTextureCoordinates = dna.geometry.meshes[meshIndex].textureCoordinates.us; + return {uTextureCoordinates.data(), uTextureCoordinates.size()}; +} + +template +inline ConstArrayView ReaderImpl::getVertexTextureCoordinateVs(std::uint16_t meshIndex) const { + const auto& vTextureCoordinates = dna.geometry.meshes[meshIndex].textureCoordinates.vs; + return {vTextureCoordinates.data(), vTextureCoordinates.size()}; +} + +template +inline std::uint32_t ReaderImpl::getVertexNormalCount(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + return static_cast(dna.geometry.meshes[meshIndex].normals.xs.size()); + } + return 0u; +} + +template +inline Normal ReaderImpl::getVertexNormal(std::uint16_t meshIndex, std::uint32_t normalIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& normals = dna.geometry.meshes[meshIndex].normals; + if (normalIndex < normals.size()) { + return {normals.xs[normalIndex], normals.ys[normalIndex], normals.zs[normalIndex]}; + } + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexNormalXs(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& xNormals = dna.geometry.meshes[meshIndex].normals.xs; + return {xNormals.data(), xNormals.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexNormalYs(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& yNormals = dna.geometry.meshes[meshIndex].normals.ys; + return {yNormals.data(), yNormals.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexNormalZs(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& zNormals = dna.geometry.meshes[meshIndex].normals.zs; + return {zNormals.data(), zNormals.size()}; + } + return {}; +} + +template +inline std::uint32_t ReaderImpl::getFaceCount(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + return static_cast(dna.geometry.meshes[meshIndex].faces.size()); + } + return 0u; +} + +template +inline ConstArrayView ReaderImpl::getFaceVertexLayoutIndices(std::uint16_t meshIndex, + std::uint32_t faceIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (faceIndex < meshes[meshIndex].faces.size())) { + const auto& layoutIndices = meshes[meshIndex].faces[faceIndex].layoutIndices; + return {layoutIndices.data(), layoutIndices.size()}; + } + return {}; +} + +template +inline std::uint32_t ReaderImpl::getVertexLayoutCount(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + return static_cast(dna.geometry.meshes[meshIndex].layouts.positions.size()); + } + return 0u; +} + +template +inline VertexLayout ReaderImpl::getVertexLayout(std::uint16_t meshIndex, std::uint32_t layoutIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& layouts = dna.geometry.meshes[meshIndex].layouts; + if (layoutIndex < layouts.size()) { + return {layouts.positions[layoutIndex], layouts.textureCoordinates[layoutIndex], layouts.normals[layoutIndex]}; + } + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexLayoutPositionIndices(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& positions = dna.geometry.meshes[meshIndex].layouts.positions; + return {positions.data(), positions.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexLayoutTextureCoordinateIndices(std::uint16_t meshIndex) +const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& textureCoordinated = dna.geometry.meshes[meshIndex].layouts.textureCoordinates; + return {textureCoordinated.data(), textureCoordinated.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexLayoutNormalIndices(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& normals = dna.geometry.meshes[meshIndex].layouts.normals; + return {normals.data(), normals.size()}; + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getMaximumInfluencePerVertex(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + return dna.geometry.meshes[meshIndex].maximumInfluencePerVertex; + } + return {}; +} + +template +inline std::uint32_t ReaderImpl::getSkinWeightsCount(std::uint16_t meshIndex) const { + const auto& meshes = dna.geometry.meshes; + if (meshIndex < meshes.size()) { + return static_cast(meshes[meshIndex].skinWeights.size()); + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getSkinWeightsValues(std::uint16_t meshIndex, + std::uint32_t vertexIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (vertexIndex < meshes[meshIndex].skinWeights.size())) { + const auto& weights = meshes[meshIndex].skinWeights[vertexIndex].weights; + return {weights.data(), weights.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getSkinWeightsJointIndices(std::uint16_t meshIndex, + std::uint32_t vertexIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (vertexIndex < meshes[meshIndex].skinWeights.size())) { + const auto& jointIndices = meshes[meshIndex].skinWeights[vertexIndex].jointIndices; + return {jointIndices.data(), jointIndices.size()}; + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getBlendShapeTargetCount(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + return static_cast(dna.geometry.meshes[meshIndex].blendShapeTargets.size()); + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getBlendShapeChannelIndex(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (blendShapeTargetIndex < meshes[meshIndex].blendShapeTargets.size())) { + return meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].blendShapeChannelIndex; + } + return {}; +} + +template +inline std::uint32_t ReaderImpl::getBlendShapeTargetDeltaCount(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (blendShapeTargetIndex < meshes[meshIndex].blendShapeTargets.size())) { + return static_cast(meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas.xs.size()); + } + return {}; +} + +template +inline Delta ReaderImpl::getBlendShapeTargetDelta(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + std::uint32_t deltaIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (blendShapeTargetIndex < meshes[meshIndex].blendShapeTargets.size()) && + (deltaIndex < meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas.size())) { + const auto& deltas = meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas; + return {deltas.xs[deltaIndex], deltas.ys[deltaIndex], deltas.zs[deltaIndex]}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getBlendShapeTargetDeltaXs(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (blendShapeTargetIndex < meshes[meshIndex].blendShapeTargets.size())) { + const auto& xDeltas = meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas.xs; + return {xDeltas.data(), xDeltas.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getBlendShapeTargetDeltaYs(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (blendShapeTargetIndex < meshes[meshIndex].blendShapeTargets.size())) { + const auto& yDeltas = meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas.ys; + return {yDeltas.data(), yDeltas.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getBlendShapeTargetDeltaZs(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (blendShapeTargetIndex < meshes[meshIndex].blendShapeTargets.size())) { + const auto& zDeltas = meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas.zs; + return {zDeltas.data(), zDeltas.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getBlendShapeTargetVertexIndices(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) +const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (blendShapeTargetIndex < meshes[meshIndex].blendShapeTargets.size())) { + const auto& vertexIndices = meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].vertexIndices; + return {vertexIndices.data(), vertexIndices.size()}; + } + return {}; +} + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/SurjectiveMapping.h b/dnacalib/DNACalib/src/dna/SurjectiveMapping.h new file mode 100644 index 0000000..049c495 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/SurjectiveMapping.h @@ -0,0 +1,101 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/TypeDefs.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dna { + +template +struct SurjectiveMapping { + public: + struct Pair { + TFrom from; + TTo to; + }; + + public: + explicit SurjectiveMapping(MemoryResource* memRes) : + from{memRes}, + to{memRes} { + } + + Pair get(std::size_t index) const { + assert(index < size()); + return {from[index], to[index]}; + } + + void add(TFrom from_, TTo to_) { + from.push_back(from_); + to.push_back(to_); + } + + void set(std::size_t index, TFrom from_, TTo to_) { + if (index >= size()) { + from.resize(index + 1ul); + to.resize(index + 1ul); + } + from[index] = from_; + to[index] = to_; + } + + void removeIf(std::function predicate) { + assert(from.size() == to.size()); + + auto itFrom = from.begin(); + auto itTo = to.begin(); + + while (itFrom != from.end()) { + if (predicate(*itFrom, *itTo)) { + itFrom = from.erase(itFrom); + itTo = to.erase(itTo); + } else { + ++itFrom; + ++itTo; + } + } + } + + void updateFrom(const UnorderedMap& mapping) { + update(from, mapping); + } + + void updateTo(const UnorderedMap& mapping) { + update(to, mapping); + } + + std::size_t size() const { + assert(from.size() == to.size()); + return from.size(); + } + + void clear() { + from.clear(); + to.clear(); + } + + private: + template + void update(Vector& target, const UnorderedMap& mapping) { + std::transform(target.begin(), target.end(), target.begin(), [&mapping](U oldValue) { + return mapping.at(oldValue); + }); + } + + protected: + Vector from; + Vector to; + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/TypeDefs.h b/dnacalib/DNACalib/src/dna/TypeDefs.h new file mode 100644 index 0000000..a946c5e --- /dev/null +++ b/dnacalib/DNACalib/src/dna/TypeDefs.h @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/types/Aliases.h" + +#include +#include +#include +#include +#include +#include + +namespace dna { + +using namespace pma; + +template +using AlignedAllocator = PolyAllocator; + +template +using DynArray = terse::DynArray >; + +template +using AlignedDynArray = terse::DynArray >; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/Writer.cpp b/dnacalib/DNACalib/src/dna/Writer.cpp new file mode 100644 index 0000000..8c6367d --- /dev/null +++ b/dnacalib/DNACalib/src/dna/Writer.cpp @@ -0,0 +1,434 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dna/Writer.h" + +#include "dna/Reader.h" +#include "dna/DataLayerBitmask.h" +#include "dna/TypeDefs.h" +#include "dna/types/Vector3.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dna { + +namespace { + +using Vector3Vector = Vector; +using TextureCoordinateVector = Vector; +using VertexLayoutVector = Vector; + +} // namespace + +DescriptorWriter::~DescriptorWriter() = default; +DefinitionWriter::~DefinitionWriter() = default; +BehaviorWriter::~BehaviorWriter() = default; +GeometryWriter::~GeometryWriter() = default; +Writer::~Writer() = default; + +template +static TVector collect_n(std::size_t count, TGetter getter, MemoryResource* memRes) { + TVector retval{memRes}; + retval.reserve(count); + for (std::size_t i = 0ul; i < count; ++i) { + retval.push_back(getter(i)); + } + return retval; +} + +static void copyDescriptor(const DescriptorReader* source, DescriptorWriter* destination, MemoryResource* /*unused*/) { + destination->setName(source->getName()); + destination->setArchetype(source->getArchetype()); + destination->setGender(source->getGender()); + destination->setAge(source->getAge()); + destination->clearMetaData(); + for (std::uint32_t i = 0u; i < source->getMetaDataCount(); ++i) { + const auto key = source->getMetaDataKey(i); + const auto value = source->getMetaDataValue(key); + destination->setMetaData(key, value); + } + destination->setTranslationUnit(source->getTranslationUnit()); + destination->setRotationUnit(source->getRotationUnit()); + destination->setCoordinateSystem(source->getCoordinateSystem()); + destination->setLODCount(source->getLODCount()); + destination->setDBMaxLOD(source->getDBMaxLOD()); + destination->setDBComplexity(source->getDBComplexity()); + destination->setDBName(source->getDBName()); +} + +static std::pair findIndices(const Matrix& source, ConstArrayView indices) { + // In the common scenario each LOD has it's unique set of indices + for (std::size_t i = 0ul; i < source.size(); ++i) { + ConstArrayView candidate{source[i].data(), source[i].size()}; + if (indices == candidate) { + // Unless the indices are the same between multiple LODs, in which case use the + // already registered index + return {true, static_cast(i)}; + } + } + return {false, static_cast(0)}; +} + +using IndicesGetter = std::function(std::uint16_t)>; +using IndicesSetter = std::function; +using LODMappingSetter = std::function; + +static void copyNameIndices(IndicesGetter getIndices, + IndicesSetter setIndices, + LODMappingSetter setLODMapping, + std::uint16_t lodCount, + MemoryResource* memRes) { + Matrix allIndices{memRes}; + std::uint16_t index = 0u; + for (std::uint16_t lod = 0u; lod < lodCount; ++lod) { + auto indices = getIndices(lod); + // Check if these same indices were perhaps already used for previous LODs + auto found = findIndices(allIndices, indices); + if (!found.first) { + setIndices(index, indices.data(), static_cast(indices.size())); + setLODMapping(lod, index); + allIndices.emplace_back(indices.begin(), indices.end()); + ++index; + } else { + // Already used so do not replicate the same data twice + setLODMapping(lod, found.second); + } + } +} + +static void copyDefinition(const DefinitionReader* source, DefinitionWriter* destination, MemoryResource* memRes) { + destination->clearGUIControlNames(); + destination->clearRawControlNames(); + destination->clearJointNames(); + destination->clearBlendShapeChannelNames(); + destination->clearAnimatedMapNames(); + destination->clearMeshNames(); + destination->clearJointIndices(); + destination->clearLODJointMappings(); + destination->clearBlendShapeChannelIndices(); + destination->clearLODBlendShapeChannelMappings(); + destination->clearAnimatedMapIndices(); + destination->clearLODAnimatedMapMappings(); + destination->clearMeshIndices(); + destination->clearLODMeshMappings(); + destination->clearMeshBlendShapeChannelMappings(); + + const auto lodCount = source->getLODCount(); + + for (std::uint16_t i = source->getGUIControlCount(); i > 0u; --i) { + const auto idx = static_cast(i - 1u); + destination->setGUIControlName(idx, source->getGUIControlName(idx).data()); + } + for (std::uint16_t i = source->getRawControlCount(); i > 0u; --i) { + const auto idx = static_cast(i - 1u); + destination->setRawControlName(idx, source->getRawControlName(idx).data()); + } + for (std::uint16_t i = source->getJointCount(); i > 0u; --i) { + const auto idx = static_cast(i - 1u); + destination->setJointName(idx, source->getJointName(idx).data()); + } + for (std::uint16_t i = source->getBlendShapeChannelCount(); i > 0u; --i) { + const auto idx = static_cast(i - 1u); + destination->setBlendShapeChannelName(idx, source->getBlendShapeChannelName(idx).data()); + } + for (std::uint16_t i = source->getAnimatedMapCount(); i > 0u; --i) { + const auto idx = static_cast(i - 1u); + destination->setAnimatedMapName(idx, source->getAnimatedMapName(idx)); + } + for (std::uint16_t i = source->getMeshCount(); i > 0u; --i) { + const auto idx = static_cast(i - 1u); + destination->setMeshName(idx, source->getMeshName(idx).data()); + } + + using namespace std::placeholders; + copyNameIndices( + std::bind(&DefinitionReader::getJointIndicesForLOD, source, _1), + std::bind(&DefinitionWriter::setJointIndices, destination, _1, _2, _3), + std::bind(&DefinitionWriter::setLODJointMapping, destination, _1, _2), + lodCount, + memRes); + copyNameIndices( + std::bind(&DefinitionReader::getBlendShapeChannelIndicesForLOD, source, _1), + std::bind(&DefinitionWriter::setBlendShapeChannelIndices, destination, _1, _2, _3), + std::bind(&DefinitionWriter::setLODBlendShapeChannelMapping, destination, _1, _2), + lodCount, + memRes); + copyNameIndices( + std::bind(&DefinitionReader::getAnimatedMapIndicesForLOD, source, _1), + std::bind(&DefinitionWriter::setAnimatedMapIndices, destination, _1, _2, _3), + std::bind(&DefinitionWriter::setLODAnimatedMapMapping, destination, _1, _2), + lodCount, + memRes); + copyNameIndices( + std::bind(&DefinitionReader::getMeshIndicesForLOD, source, _1), + std::bind(&DefinitionWriter::setMeshIndices, destination, _1, _2, _3), + std::bind(&DefinitionWriter::setLODMeshMapping, destination, _1, _2), + lodCount, + memRes); + + Vector jointHierarchy{memRes}; + jointHierarchy.reserve(source->getJointCount()); + for (std::uint16_t i = 0u; i < source->getJointCount(); ++i) { + jointHierarchy.push_back(source->getJointParentIndex(i)); + } + destination->setJointHierarchy(jointHierarchy.data(), static_cast(jointHierarchy.size())); + + for (std::uint16_t i = 0u; i < source->getMeshBlendShapeChannelMappingCount(); ++i) { + auto mapping = source->getMeshBlendShapeChannelMapping(i); + destination->setMeshBlendShapeChannelMapping(i, mapping.meshIndex, mapping.blendShapeChannelIndex); + } + + auto jointCount = source->getJointCount(); + + auto translations = collect_n(jointCount, [source](std::size_t index) { + return source->getNeutralJointTranslation(static_cast(index)); + }, memRes); + destination->setNeutralJointTranslations(translations.data(), static_cast(translations.size())); + + auto rotations = collect_n(jointCount, [source](std::size_t index) { + return source->getNeutralJointRotation(static_cast(index)); + }, memRes); + destination->setNeutralJointRotations(rotations.data(), static_cast(rotations.size())); +} + +static void copyBehavior(const BehaviorReader* source, BehaviorWriter* destination, MemoryResource* /*unused*/) { + destination->clearJointGroups(); + + auto guiToRawInputIndices = source->getGUIToRawInputIndices(); + destination->setGUIToRawInputIndices(guiToRawInputIndices.data(), static_cast(guiToRawInputIndices.size())); + + auto guiToRawOutputIndices = source->getGUIToRawOutputIndices(); + destination->setGUIToRawOutputIndices(guiToRawOutputIndices.data(), static_cast(guiToRawOutputIndices.size())); + + auto guiToRawFromValues = source->getGUIToRawFromValues(); + destination->setGUIToRawFromValues(guiToRawFromValues.data(), static_cast(guiToRawFromValues.size())); + + auto guiToRawToValues = source->getGUIToRawToValues(); + destination->setGUIToRawToValues(guiToRawToValues.data(), static_cast(guiToRawToValues.size())); + + auto guiToRawSlopeValues = source->getGUIToRawSlopeValues(); + destination->setGUIToRawSlopeValues(guiToRawSlopeValues.data(), static_cast(guiToRawSlopeValues.size())); + + auto guiToRawCutValues = source->getGUIToRawCutValues(); + destination->setGUIToRawCutValues(guiToRawCutValues.data(), static_cast(guiToRawCutValues.size())); + + destination->setPSDCount(source->getPSDCount()); + + auto psdRowIndices = source->getPSDRowIndices(); + destination->setPSDRowIndices(psdRowIndices.data(), static_cast(psdRowIndices.size())); + + auto psdColumnIndices = source->getPSDColumnIndices(); + destination->setPSDColumnIndices(psdColumnIndices.data(), static_cast(psdColumnIndices.size())); + + auto psdValues = source->getPSDValues(); + destination->setPSDValues(psdValues.data(), static_cast(psdValues.size())); + + destination->setJointRowCount(source->getJointRowCount()); + destination->setJointColumnCount(source->getJointColumnCount()); + + for (std::uint16_t jointGroupIndexPlusOne = source->getJointGroupCount(); + jointGroupIndexPlusOne > 0u; + --jointGroupIndexPlusOne) { + const auto jointGroupIndex = static_cast(jointGroupIndexPlusOne - 1u); + + auto jointGroupLODs = source->getJointGroupLODs(jointGroupIndex); + destination->setJointGroupLODs(jointGroupIndex, jointGroupLODs.data(), static_cast(jointGroupLODs.size())); + + auto jointGroupInputIndices = source->getJointGroupInputIndices(jointGroupIndex); + destination->setJointGroupInputIndices(jointGroupIndex, jointGroupInputIndices.data(), + static_cast(jointGroupInputIndices.size())); + + auto jointGroupOutputIndices = source->getJointGroupOutputIndices(jointGroupIndex); + destination->setJointGroupOutputIndices(jointGroupIndex, jointGroupOutputIndices.data(), + static_cast(jointGroupOutputIndices.size())); + + auto jointGroupValues = source->getJointGroupValues(jointGroupIndex); + destination->setJointGroupValues(jointGroupIndex, jointGroupValues.data(), + static_cast(jointGroupValues.size())); + + auto jointGroupJointIndices = source->getJointGroupJointIndices(jointGroupIndex); + destination->setJointGroupJointIndices(jointGroupIndex, jointGroupJointIndices.data(), + static_cast(jointGroupJointIndices.size())); + } + + auto blendShapeLODs = source->getBlendShapeChannelLODs(); + destination->setBlendShapeChannelLODs(blendShapeLODs.data(), static_cast(blendShapeLODs.size())); + + auto blendShapeInputIndices = source->getBlendShapeChannelInputIndices(); + destination->setBlendShapeChannelInputIndices(blendShapeInputIndices.data(), + static_cast(blendShapeInputIndices.size())); + + auto blendShapeOutputIndices = source->getBlendShapeChannelOutputIndices(); + destination->setBlendShapeChannelOutputIndices(blendShapeOutputIndices.data(), + static_cast(blendShapeOutputIndices.size())); + + auto animatedMapLODs = source->getAnimatedMapLODs(); + destination->setAnimatedMapLODs(animatedMapLODs.data(), static_cast(animatedMapLODs.size())); + + auto animatedMapInputIndices = source->getAnimatedMapInputIndices(); + destination->setAnimatedMapInputIndices(animatedMapInputIndices.data(), + static_cast(animatedMapInputIndices.size())); + + auto animatedMapOutputIndices = source->getAnimatedMapOutputIndices(); + destination->setAnimatedMapOutputIndices(animatedMapOutputIndices.data(), + static_cast(animatedMapOutputIndices.size())); + + auto animatedMapFromValues = source->getAnimatedMapFromValues(); + destination->setAnimatedMapFromValues(animatedMapFromValues.data(), static_cast(animatedMapFromValues.size())); + + auto animatedMapToValues = source->getAnimatedMapToValues(); + destination->setAnimatedMapToValues(animatedMapToValues.data(), static_cast(animatedMapToValues.size())); + + auto animatedMapSlopeValues = source->getAnimatedMapSlopeValues(); + destination->setAnimatedMapSlopeValues(animatedMapSlopeValues.data(), + static_cast(animatedMapSlopeValues.size())); + + auto animatedMapCutValues = source->getAnimatedMapCutValues(); + destination->setAnimatedMapCutValues(animatedMapCutValues.data(), static_cast(animatedMapCutValues.size())); +} + +static bool hasGeometry(const GeometryReader* source) { + // Heuristic for determining whether source DNA actually has any geometry data, or mesh count is non-zero only + // because of mesh names stored in definition layer + std::uint32_t totalVertexCount = {}; + std::uint32_t totalVertexNormalCount = {}; + std::uint32_t totalTextureCoordCount = {}; + std::uint32_t totalVertexLayoutCount = {}; + std::uint32_t totalSkinWeightCount = {}; + for (std::uint16_t meshIndex = {}; meshIndex < source->getMeshCount(); ++meshIndex) { + totalVertexCount += source->getVertexPositionCount(meshIndex); + totalVertexNormalCount += source->getVertexNormalCount(meshIndex); + totalTextureCoordCount += source->getVertexTextureCoordinateCount(meshIndex); + totalVertexLayoutCount += source->getVertexLayoutCount(meshIndex); + totalSkinWeightCount += source->getSkinWeightsCount(meshIndex); + } + return ((totalVertexCount != 0u) || (totalVertexNormalCount != 0u) || (totalTextureCoordCount != 0u) || + (totalVertexLayoutCount != 0u) || (totalSkinWeightCount != 0u)); +} + +static void copyGeometry(const GeometryReader* source, GeometryWriter* destination, MemoryResource* memRes) { + destination->clearMeshes(); + + if (!hasGeometry(source)) { + // Source DNA was loaded without geometry layer + return; + } + + for (std::uint16_t meshIndexPlusOne = source->getMeshCount(); meshIndexPlusOne > 0u; --meshIndexPlusOne) { + const auto meshIndex = static_cast(meshIndexPlusOne - 1u); + auto vertexCount = source->getVertexPositionCount(meshIndex); + auto positions = collect_n(vertexCount, [source, meshIndex](std::size_t index) { + return source->getVertexPosition(meshIndex, static_cast(index)); + }, memRes); + destination->setVertexPositions(meshIndex, positions.data(), static_cast(positions.size())); + + auto textureCoordinateCount = source->getVertexTextureCoordinateCount(meshIndex); + auto textureCoordinates = collect_n(textureCoordinateCount, + [source, meshIndex](std::size_t index) { + return source->getVertexTextureCoordinate(meshIndex, static_cast(index)); + }, memRes); + destination->setVertexTextureCoordinates(meshIndex, textureCoordinates.data(), + static_cast(textureCoordinates.size())); + + auto normalCount = source->getVertexNormalCount(meshIndex); + auto normals = collect_n(normalCount, [source, meshIndex](std::size_t index) { + return source->getVertexNormal(meshIndex, static_cast(index)); + }, memRes); + destination->setVertexNormals(meshIndex, normals.data(), static_cast(normals.size())); + + auto layoutCount = source->getVertexLayoutCount(meshIndex); + auto layouts = collect_n(layoutCount, [source, meshIndex](std::size_t index) { + return source->getVertexLayout(meshIndex, static_cast(index)); + }, memRes); + destination->setVertexLayouts(meshIndex, layouts.data(), static_cast(layouts.size())); + + for (std::uint32_t faceIndexPlusOne = source->getFaceCount(meshIndex); faceIndexPlusOne > 0u; --faceIndexPlusOne) { + const auto faceIndex = faceIndexPlusOne - 1u; + auto faceVertices = source->getFaceVertexLayoutIndices(meshIndex, faceIndex); + destination->setFaceVertexLayoutIndices(meshIndex, faceIndex, faceVertices.data(), + static_cast(faceVertices.size())); + } + + destination->setMaximumInfluencePerVertex(meshIndex, source->getMaximumInfluencePerVertex(meshIndex)); + + const auto skinWeightsCount = source->getSkinWeightsCount(meshIndex); + for (std::uint32_t skinWeightsIndexPlusOne = skinWeightsCount; skinWeightsIndexPlusOne > 0u; --skinWeightsIndexPlusOne) { + const auto skinWeightsIndex = skinWeightsIndexPlusOne - 1u; + auto skinWeights = source->getSkinWeightsValues(meshIndex, skinWeightsIndex); + destination->setSkinWeightsValues(meshIndex, skinWeightsIndex, skinWeights.data(), + static_cast(skinWeights.size())); + + auto skinWeightsJoints = source->getSkinWeightsJointIndices(meshIndex, skinWeightsIndex); + destination->setSkinWeightsJointIndices(meshIndex, skinWeightsIndex, skinWeightsJoints.data(), + static_cast(skinWeightsJoints.size())); + } + } +} + +static bool hasBlendShapeTargets(const GeometryReader* source) { + // Heuristic for determining whether source DNA actually has any blend shape target data, or mesh count is non-zero only + // because of mesh names stored in definition layer + std::uint32_t totalBlendShapeTargetCount = {}; + for (std::uint16_t meshIndex = {}; meshIndex < source->getMeshCount(); ++meshIndex) { + totalBlendShapeTargetCount += source->getBlendShapeTargetCount(meshIndex); + } + return (totalBlendShapeTargetCount != 0u); +} + +static void copyBlendShapeTargets(const GeometryReader* source, GeometryWriter* destination, MemoryResource* memRes) { + if (!hasBlendShapeTargets(source)) { + // Source DNA was loaded without blend shape targets + return; + } + + for (std::uint16_t meshIndexPlusOne = source->getMeshCount(); meshIndexPlusOne > 0u; --meshIndexPlusOne) { + const auto meshIndex = static_cast(meshIndexPlusOne - 1u); + for (std::uint16_t blendShapeTargetIndexPlusOne = source->getBlendShapeTargetCount(meshIndex); + blendShapeTargetIndexPlusOne > 0u; + --blendShapeTargetIndexPlusOne) { + const auto blendShapeTargetIndex = static_cast(blendShapeTargetIndexPlusOne - 1u); + auto channelIndex = source->getBlendShapeChannelIndex(meshIndex, blendShapeTargetIndex); + destination->setBlendShapeChannelIndex(meshIndex, blendShapeTargetIndex, channelIndex); + auto deltaCount = source->getBlendShapeTargetDeltaCount(meshIndex, blendShapeTargetIndex); + auto deltas = collect_n(deltaCount, [source, meshIndex, blendShapeTargetIndex](std::size_t index) { + return source->getBlendShapeTargetDelta(meshIndex, blendShapeTargetIndex, + static_cast(index)); + }, memRes); + destination->setBlendShapeTargetDeltas(meshIndex, blendShapeTargetIndex, deltas.data(), + static_cast(deltas.size())); + + auto deltasVertices = source->getBlendShapeTargetVertexIndices(meshIndex, blendShapeTargetIndex); + destination->setBlendShapeTargetVertexIndices(meshIndex, blendShapeTargetIndex, deltasVertices.data(), + static_cast(deltasVertices.size())); + } + } +} + +void Writer::setFrom(const Reader* source, DataLayer layer, MemoryResource* memRes) { + if (source == nullptr) { + return; + } + + const auto bitmask = computeDataLayerBitmask(layer); + copyDescriptor(source, this, memRes); + if (contains(bitmask, DataLayerBitmask::Definition)) { + copyDefinition(source, this, memRes); + } + if (contains(bitmask, DataLayerBitmask::Behavior)) { + copyBehavior(source, this, memRes); + } + if (contains(bitmask, DataLayerBitmask::GeometryRest)) { + copyGeometry(source, this, memRes); + } + if (contains(bitmask, DataLayerBitmask::GeometryBlendShapesOnly)) { + copyBlendShapeTargets(source, this, memRes); + } +} + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/WriterImpl.h b/dnacalib/DNACalib/src/dna/WriterImpl.h new file mode 100644 index 0000000..61891a0 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/WriterImpl.h @@ -0,0 +1,758 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/BaseImpl.h" +#include "dna/TypeDefs.h" +#include "dna/utils/Extd.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dna { + +template +void ensureHasSize(TContainer& target, + std::size_t size, + Args&& ... args) { + target.reserve(size); + while (target.size() < size) { + target.push_back(typename TContainer::value_type(std::forward(args)...)); + } +} + +template +class WriterImpl : public TWriterBase, public virtual BaseImpl { + public: + explicit WriterImpl(MemoryResource* memRes_); + + // DescriptorWriter methods + void setName(const char* name) override; + void setArchetype(Archetype archetype) override; + void setGender(Gender gender) override; + void setAge(std::uint16_t age) override; + void clearMetaData() override; + void setMetaData(const char* key, const char* value) override; + void setTranslationUnit(TranslationUnit unit) override; + void setRotationUnit(RotationUnit unit) override; + void setCoordinateSystem(CoordinateSystem system) override; + void setLODCount(std::uint16_t lodCount) override; + void setDBMaxLOD(std::uint16_t lod) override; + void setDBComplexity(const char* name) override; + void setDBName(const char* name) override; + + // DefinitionWriter methods + void clearGUIControlNames() override; + void setGUIControlName(std::uint16_t index, const char* name) override; + void clearRawControlNames() override; + void setRawControlName(std::uint16_t index, const char* name) override; + void clearJointNames() override; + void setJointName(std::uint16_t index, const char* name) override; + void clearJointIndices() override; + void setJointIndices(std::uint16_t index, const std::uint16_t* jointIndices, std::uint16_t count) override; + void clearLODJointMappings() override; + void setLODJointMapping(std::uint16_t lod, std::uint16_t index) override; + void clearBlendShapeChannelNames() override; + void setJointHierarchy(const std::uint16_t* jointIndices, std::uint16_t count) override; + void setBlendShapeChannelName(std::uint16_t index, const char* name) override; + void clearBlendShapeChannelIndices() override; + void setBlendShapeChannelIndices(std::uint16_t index, const std::uint16_t* blendShapeChannelIndices, + std::uint16_t count) override; + void clearLODBlendShapeChannelMappings() override; + void setLODBlendShapeChannelMapping(std::uint16_t lod, std::uint16_t index) override; + void clearAnimatedMapNames() override; + void setAnimatedMapName(std::uint16_t index, const char* name) override; + void clearAnimatedMapIndices() override; + void setAnimatedMapIndices(std::uint16_t index, const std::uint16_t* animatedMapIndices, std::uint16_t count) override; + void clearLODAnimatedMapMappings() override; + void setLODAnimatedMapMapping(std::uint16_t lod, std::uint16_t index) override; + void clearMeshNames() override; + void setMeshName(std::uint16_t index, const char* name) override; + void clearMeshIndices() override; + void setMeshIndices(std::uint16_t index, const std::uint16_t* meshIndices, std::uint16_t count) override; + void clearLODMeshMappings() override; + void setLODMeshMapping(std::uint16_t lod, std::uint16_t index) override; + void clearMeshBlendShapeChannelMappings() override; + void setMeshBlendShapeChannelMapping(std::uint32_t index, std::uint16_t meshIndex, + std::uint16_t blendShapeChannelIndex) override; + void setNeutralJointTranslations(const Vector3* translations, std::uint16_t count) override; + void setNeutralJointRotations(const Vector3* rotations, std::uint16_t count) override; + + // BehaviorWriter methods + void setGUIToRawInputIndices(const std::uint16_t* inputIndices, std::uint16_t count) override; + void setGUIToRawOutputIndices(const std::uint16_t* outputIndices, std::uint16_t count) override; + void setGUIToRawFromValues(const float* fromValues, std::uint16_t count) override; + void setGUIToRawToValues(const float* toValues, std::uint16_t count) override; + void setGUIToRawSlopeValues(const float* slopeValues, std::uint16_t count) override; + void setGUIToRawCutValues(const float* cutValues, std::uint16_t count) override; + void setPSDCount(std::uint16_t count) override; + void setPSDRowIndices(const std::uint16_t* rowIndices, std::uint16_t count) override; + void setPSDColumnIndices(const std::uint16_t* columnIndices, std::uint16_t count) override; + void setPSDValues(const float* weights, std::uint16_t count) override; + void setJointRowCount(std::uint16_t rowCount) override; + void setJointColumnCount(std::uint16_t columnCount) override; + void clearJointGroups() override; + void deleteJointGroup(std::uint16_t jointGroupIndex) override; + void setJointGroupLODs(std::uint16_t jointGroupIndex, const std::uint16_t* lods, std::uint16_t count) override; + void setJointGroupInputIndices(std::uint16_t jointGroupIndex, const std::uint16_t* inputIndices, + std::uint16_t count) override; + void setJointGroupOutputIndices(std::uint16_t jointGroupIndex, const std::uint16_t* outputIndices, + std::uint16_t count) override; + void setJointGroupValues(std::uint16_t jointGroupIndex, const float* values, std::uint32_t count) override; + void setJointGroupJointIndices(std::uint16_t jointGroupIndex, const std::uint16_t* jointIndices, + std::uint16_t count) override; + void setBlendShapeChannelLODs(const std::uint16_t* lods, std::uint16_t count) override; + void setBlendShapeChannelInputIndices(const std::uint16_t* inputIndices, std::uint16_t count) override; + void setBlendShapeChannelOutputIndices(const std::uint16_t* outputIndices, std::uint16_t count) override; + void setAnimatedMapLODs(const std::uint16_t* lods, std::uint16_t count) override; + void setAnimatedMapInputIndices(const std::uint16_t* inputIndices, std::uint16_t count) override; + void setAnimatedMapOutputIndices(const std::uint16_t* outputIndices, std::uint16_t count) override; + void setAnimatedMapFromValues(const float* fromValues, std::uint16_t count) override; + void setAnimatedMapToValues(const float* toValues, std::uint16_t count) override; + void setAnimatedMapSlopeValues(const float* slopeValues, std::uint16_t count) override; + void setAnimatedMapCutValues(const float* cutValues, std::uint16_t count) override; + + // GeometryWriter methods + void clearMeshes() override; + void deleteMesh(std::uint16_t meshIndex) override; + void setVertexPositions(std::uint16_t meshIndex, const Position* positions, std::uint32_t count) override; + void setVertexTextureCoordinates(std::uint16_t meshIndex, const TextureCoordinate* textureCoordinates, + std::uint32_t count) override; + void setVertexNormals(std::uint16_t meshIndex, const Normal* normals, std::uint32_t count) override; + void setVertexLayouts(std::uint16_t meshIndex, const VertexLayout* layouts, std::uint32_t count) override; + void clearFaceVertexLayoutIndices(std::uint16_t meshIndex) override; + void setFaceVertexLayoutIndices(std::uint16_t meshIndex, + std::uint32_t faceIndex, + const std::uint32_t* layoutIndices, + std::uint32_t count) override; + void setMaximumInfluencePerVertex(std::uint16_t meshIndex, std::uint16_t maxInfluenceCount) override; + void clearSkinWeights(std::uint16_t meshIndex) override; + void setSkinWeightsValues(std::uint16_t meshIndex, std::uint32_t vertexIndex, const float* weights, + std::uint16_t count) override; + void setSkinWeightsJointIndices(std::uint16_t meshIndex, + std::uint32_t vertexIndex, + const std::uint16_t* jointIndices, + std::uint16_t count) override; + void clearBlendShapeTargets(std::uint16_t meshIndex) override; + void setBlendShapeChannelIndex(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + std::uint16_t blendShapeChannelIndex) override; + void setBlendShapeTargetDeltas(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + const Delta* deltas, + std::uint32_t count) override; + void setBlendShapeTargetVertexIndices(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + const std::uint32_t* vertexIndices, + std::uint32_t count) override; + +}; + + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4589) +#endif +template +WriterImpl::WriterImpl(MemoryResource* memRes_) : BaseImpl{memRes_} { +} + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4505) +#endif +template +inline void WriterImpl::setName(const char* name) { + dna.descriptor.name = name; +} + +template +inline void WriterImpl::setArchetype(Archetype archetype) { + dna.descriptor.archetype = static_cast(archetype); +} + +template +inline void WriterImpl::setGender(Gender gender) { + dna.descriptor.gender = static_cast(gender); +} + +template +inline void WriterImpl::setAge(std::uint16_t age) { + dna.descriptor.age = age; +} + +template +inline void WriterImpl::clearMetaData() { + dna.descriptor.metadata.clear(); +} + +template +inline void WriterImpl::setMetaData(const char* key, const char* value) { + using CharStringPair = std::tuple, String >; + auto it = std::find_if(dna.descriptor.metadata.begin(), dna.descriptor.metadata.end(), [&key](const CharStringPair& kv) { + auto& k = std::get<0>(kv); + return (std::strlen(key) == k.size() && std::strncmp(k.data(), key, k.size()) == 0); + }); + if (it == dna.descriptor.metadata.end()) { + if (value != nullptr) { + dna.descriptor.metadata.emplace_back(String{key, memRes}, String{value, memRes}); + } + } else { + if (value == nullptr) { + dna.descriptor.metadata.erase(it); + } else { + std::get<1>(*it) = value; + } + } +} + +template +inline void WriterImpl::setTranslationUnit(TranslationUnit unit) { + dna.descriptor.translationUnit = static_cast(unit); +} + +template +inline void WriterImpl::setRotationUnit(RotationUnit unit) { + dna.descriptor.rotationUnit = static_cast(unit); +} + +template +inline void WriterImpl::setCoordinateSystem(CoordinateSystem system) { + dna.descriptor.coordinateSystem.xAxis = static_cast(system.xAxis); + dna.descriptor.coordinateSystem.yAxis = static_cast(system.yAxis); + dna.descriptor.coordinateSystem.zAxis = static_cast(system.zAxis); +} + +template +inline void WriterImpl::setLODCount(std::uint16_t lodCount) { + dna.descriptor.lodCount = lodCount; +} + +template +inline void WriterImpl::setDBMaxLOD(std::uint16_t lod) { + dna.descriptor.maxLOD = lod; +} + +template +inline void WriterImpl::setDBComplexity(const char* name) { + dna.descriptor.complexity = name; +} + +template +inline void WriterImpl::setDBName(const char* name) { + dna.descriptor.dbName = name; +} + +template +inline void WriterImpl::clearGUIControlNames() { + dna.definition.guiControlNames.clear(); +} + +template +inline void WriterImpl::setGUIControlName(std::uint16_t index, const char* name) { + ensureHasSize(dna.definition.guiControlNames, index + 1ul, memRes); + dna.definition.guiControlNames[index] = name; +} + +template +inline void WriterImpl::clearRawControlNames() { + dna.definition.rawControlNames.clear(); +} + +template +inline void WriterImpl::setRawControlName(std::uint16_t index, const char* name) { + ensureHasSize(dna.definition.rawControlNames, index + 1ul, memRes); + dna.definition.rawControlNames[index] = name; +} + +template +inline void WriterImpl::clearJointNames() { + dna.definition.jointNames.clear(); +} + +template +inline void WriterImpl::setJointName(std::uint16_t index, const char* name) { + ensureHasSize(dna.definition.jointNames, index + 1ul, memRes); + dna.definition.jointNames[index] = name; +} + +template +inline void WriterImpl::clearJointIndices() { + dna.definition.lodJointMapping.resetIndices(); +} + +template +inline void WriterImpl::setJointIndices(std::uint16_t index, const std::uint16_t* jointIndices, + std::uint16_t count) { + dna.definition.lodJointMapping.clearIndices(index); + dna.definition.lodJointMapping.addIndices(index, jointIndices, count); +} + +template +inline void WriterImpl::clearLODJointMappings() { + dna.definition.lodJointMapping.resetLODs(); +} + +template +inline void WriterImpl::setLODJointMapping(std::uint16_t lod, std::uint16_t index) { + dna.definition.lodJointMapping.associateLODWithIndices(lod, index); +} + +template +inline void WriterImpl::setJointHierarchy(const std::uint16_t* jointIndices, std::uint16_t count) { + dna.definition.jointHierarchy.assign(jointIndices, jointIndices + count); +} + +template +inline void WriterImpl::clearBlendShapeChannelNames() { + dna.definition.blendShapeChannelNames.clear(); +} + +template +inline void WriterImpl::setBlendShapeChannelName(std::uint16_t index, const char* name) { + ensureHasSize(dna.definition.blendShapeChannelNames, index + 1ul, memRes); + dna.definition.blendShapeChannelNames[index] = name; +} + +template +inline void WriterImpl::clearBlendShapeChannelIndices() { + dna.definition.lodBlendShapeMapping.resetIndices(); +} + +template +inline void WriterImpl::setBlendShapeChannelIndices(std::uint16_t index, + const std::uint16_t* blendShapeChannelIndices, + std::uint16_t count) { + dna.definition.lodBlendShapeMapping.clearIndices(index); + dna.definition.lodBlendShapeMapping.addIndices(index, blendShapeChannelIndices, count); +} + +template +inline void WriterImpl::clearLODBlendShapeChannelMappings() { + dna.definition.lodBlendShapeMapping.resetLODs(); +} + +template +inline void WriterImpl::setLODBlendShapeChannelMapping(std::uint16_t lod, std::uint16_t index) { + dna.definition.lodBlendShapeMapping.associateLODWithIndices(lod, index); +} + +template +inline void WriterImpl::clearAnimatedMapNames() { + dna.definition.animatedMapNames.clear(); +} + +template +inline void WriterImpl::setAnimatedMapName(std::uint16_t index, const char* name) { + ensureHasSize(dna.definition.animatedMapNames, index + 1ul, memRes); + dna.definition.animatedMapNames[index] = name; +} + +template +inline void WriterImpl::clearAnimatedMapIndices() { + dna.definition.lodAnimatedMapMapping.resetIndices(); +} + +template +inline void WriterImpl::setAnimatedMapIndices(std::uint16_t index, + const std::uint16_t* animatedMapIndices, + std::uint16_t count) { + dna.definition.lodAnimatedMapMapping.clearIndices(index); + dna.definition.lodAnimatedMapMapping.addIndices(index, animatedMapIndices, count); +} + +template +inline void WriterImpl::clearLODAnimatedMapMappings() { + dna.definition.lodAnimatedMapMapping.resetLODs(); +} + +template +inline void WriterImpl::setLODAnimatedMapMapping(std::uint16_t lod, std::uint16_t index) { + dna.definition.lodAnimatedMapMapping.associateLODWithIndices(lod, index); +} + +template +inline void WriterImpl::clearMeshNames() { + dna.definition.meshNames.clear(); +} + +template +inline void WriterImpl::setMeshName(std::uint16_t index, const char* name) { + ensureHasSize(dna.definition.meshNames, index + 1ul, memRes); + dna.definition.meshNames[index] = name; +} + +template +inline void WriterImpl::clearMeshIndices() { + dna.definition.lodMeshMapping.resetIndices(); +} + +template +inline void WriterImpl::setMeshIndices(std::uint16_t index, const std::uint16_t* meshIndices, std::uint16_t count) { + dna.definition.lodMeshMapping.clearIndices(index); + dna.definition.lodMeshMapping.addIndices(index, meshIndices, count); +} + +template +inline void WriterImpl::clearLODMeshMappings() { + dna.definition.lodMeshMapping.resetLODs(); +} + +template +inline void WriterImpl::setLODMeshMapping(std::uint16_t lod, std::uint16_t index) { + dna.definition.lodMeshMapping.associateLODWithIndices(lod, index); +} + +template +inline void WriterImpl::clearMeshBlendShapeChannelMappings() { + dna.definition.meshBlendShapeChannelMapping.clear(); +} + +template +inline void WriterImpl::setMeshBlendShapeChannelMapping(std::uint32_t index, + std::uint16_t meshIndex, + std::uint16_t blendShapeChannelIndex) { + dna.definition.meshBlendShapeChannelMapping.set(index, meshIndex, blendShapeChannelIndex); +} + +template +inline void WriterImpl::setNeutralJointTranslations(const Vector3* translations, std::uint16_t count) { + dna.definition.neutralJointTranslations.assign(translations, translations + count); +} + +template +inline void WriterImpl::setNeutralJointRotations(const Vector3* rotations, std::uint16_t count) { + dna.definition.neutralJointRotations.assign(rotations, rotations + count); +} + +template +inline void WriterImpl::setGUIToRawInputIndices(const std::uint16_t* inputIndices, std::uint16_t count) { + dna.behavior.controls.conditionals.inputIndices.assign(inputIndices, inputIndices + count); +} + +template +inline void WriterImpl::setGUIToRawOutputIndices(const std::uint16_t* outputIndices, std::uint16_t count) { + dna.behavior.controls.conditionals.outputIndices.assign(outputIndices, outputIndices + count); +} + +template +inline void WriterImpl::setGUIToRawFromValues(const float* fromValues, std::uint16_t count) { + dna.behavior.controls.conditionals.fromValues.assign(fromValues, fromValues + count); +} + +template +inline void WriterImpl::setGUIToRawToValues(const float* toValues, std::uint16_t count) { + dna.behavior.controls.conditionals.toValues.assign(toValues, toValues + count); +} + +template +inline void WriterImpl::setGUIToRawSlopeValues(const float* slopeValues, std::uint16_t count) { + dna.behavior.controls.conditionals.slopeValues.assign(slopeValues, slopeValues + count); +} + +template +inline void WriterImpl::setGUIToRawCutValues(const float* cutValues, std::uint16_t count) { + dna.behavior.controls.conditionals.cutValues.assign(cutValues, cutValues + count); +} + +template +inline void WriterImpl::setPSDCount(std::uint16_t count) { + dna.behavior.controls.psdCount = count; +} + +template +inline void WriterImpl::setPSDRowIndices(const std::uint16_t* rowIndices, std::uint16_t count) { + dna.behavior.controls.psds.rows.assign(rowIndices, rowIndices + count); +} + +template +inline void WriterImpl::setPSDColumnIndices(const std::uint16_t* columnIndices, std::uint16_t count) { + dna.behavior.controls.psds.columns.assign(columnIndices, columnIndices + count); +} + +template +inline void WriterImpl::setPSDValues(const float* weights, std::uint16_t count) { + dna.behavior.controls.psds.values.assign(weights, weights + count); +} + +template +inline void WriterImpl::setJointRowCount(std::uint16_t rowCount) { + dna.behavior.joints.rowCount = rowCount; +} + +template +inline void WriterImpl::setJointColumnCount(std::uint16_t columnCount) { + dna.behavior.joints.colCount = columnCount; +} + +template +inline void WriterImpl::clearJointGroups() { + dna.behavior.joints.jointGroups.clear(); +} + +template +inline void WriterImpl::deleteJointGroup(std::uint16_t jointGroupIndex) { + if (jointGroupIndex < dna.behavior.joints.jointGroups.size()) { + auto it = extd::advanced(dna.behavior.joints.jointGroups.begin(), jointGroupIndex); + dna.behavior.joints.jointGroups.erase(it); + } +} + +template +inline void WriterImpl::setJointGroupLODs(std::uint16_t jointGroupIndex, + const std::uint16_t* lods, + std::uint16_t count) { + auto& jointGroups = dna.behavior.joints.jointGroups; + ensureHasSize(jointGroups, jointGroupIndex + 1ul, memRes); + jointGroups[jointGroupIndex].lods.assign(lods, lods + count); +} + +template +inline void WriterImpl::setJointGroupInputIndices(std::uint16_t jointGroupIndex, + const std::uint16_t* inputIndices, + std::uint16_t count) { + auto& jointGroups = dna.behavior.joints.jointGroups; + ensureHasSize(jointGroups, jointGroupIndex + 1ul, memRes); + jointGroups[jointGroupIndex].inputIndices.assign(inputIndices, inputIndices + count); +} + +template +inline void WriterImpl::setJointGroupOutputIndices(std::uint16_t jointGroupIndex, + const std::uint16_t* outputIndices, + std::uint16_t count) { + auto& jointGroups = dna.behavior.joints.jointGroups; + ensureHasSize(jointGroups, jointGroupIndex + 1ul, memRes); + jointGroups[jointGroupIndex].outputIndices.assign(outputIndices, outputIndices + count); +} + +template +inline void WriterImpl::setJointGroupValues(std::uint16_t jointGroupIndex, const float* values, + std::uint32_t count) { + auto& jointGroups = dna.behavior.joints.jointGroups; + ensureHasSize(jointGroups, jointGroupIndex + 1ul, memRes); + jointGroups[jointGroupIndex].values.assign(values, values + count); +} + +template +inline void WriterImpl::setJointGroupJointIndices(std::uint16_t jointGroupIndex, + const std::uint16_t* jointIndices, + std::uint16_t count) { + auto& jointGroups = dna.behavior.joints.jointGroups; + ensureHasSize(jointGroups, jointGroupIndex + 1ul, memRes); + jointGroups[jointGroupIndex].jointIndices.assign(jointIndices, jointIndices + count); +} + +template +inline void WriterImpl::setBlendShapeChannelLODs(const std::uint16_t* lods, std::uint16_t count) { + dna.behavior.blendShapeChannels.lods.assign(lods, lods + count); +} + +template +inline void WriterImpl::setBlendShapeChannelInputIndices(const std::uint16_t* inputIndices, std::uint16_t count) { + dna.behavior.blendShapeChannels.inputIndices.assign(inputIndices, inputIndices + count); +} + +template +inline void WriterImpl::setBlendShapeChannelOutputIndices(const std::uint16_t* outputIndices, std::uint16_t count) { + dna.behavior.blendShapeChannels.outputIndices.assign(outputIndices, outputIndices + count); +} + +template +inline void WriterImpl::setAnimatedMapLODs(const std::uint16_t* lods, std::uint16_t count) { + dna.behavior.animatedMaps.lods.assign(lods, lods + count); +} + +template +inline void WriterImpl::setAnimatedMapInputIndices(const std::uint16_t* inputIndices, std::uint16_t count) { + dna.behavior.animatedMaps.conditionals.inputIndices.assign(inputIndices, inputIndices + count); +} + +template +inline void WriterImpl::setAnimatedMapOutputIndices(const std::uint16_t* outputIndices, std::uint16_t count) { + dna.behavior.animatedMaps.conditionals.outputIndices.assign(outputIndices, outputIndices + count); +} + +template +inline void WriterImpl::setAnimatedMapFromValues(const float* fromValues, std::uint16_t count) { + dna.behavior.animatedMaps.conditionals.fromValues.assign(fromValues, fromValues + count); +} + +template +inline void WriterImpl::setAnimatedMapToValues(const float* toValues, std::uint16_t count) { + dna.behavior.animatedMaps.conditionals.toValues.assign(toValues, toValues + count); +} + +template +inline void WriterImpl::setAnimatedMapSlopeValues(const float* slopeValues, std::uint16_t count) { + dna.behavior.animatedMaps.conditionals.slopeValues.assign(slopeValues, slopeValues + count); +} + +template +inline void WriterImpl::setAnimatedMapCutValues(const float* cutValues, std::uint16_t count) { + dna.behavior.animatedMaps.conditionals.cutValues.assign(cutValues, cutValues + count); +} + +template +inline void WriterImpl::clearMeshes() { + dna.geometry.meshes.clear(); +} + +template +inline void WriterImpl::deleteMesh(std::uint16_t meshIndex) { + if (meshIndex < dna.geometry.meshes.size()) { + auto it = extd::advanced(dna.geometry.meshes.begin(), meshIndex); + dna.geometry.meshes.erase(it); + } +} + +template +inline void WriterImpl::setVertexPositions(std::uint16_t meshIndex, const Position* positions, std::uint32_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + dna.geometry.meshes[meshIndex].positions.assign(positions, positions + count); +} + +template +inline void WriterImpl::setVertexTextureCoordinates(std::uint16_t meshIndex, + const TextureCoordinate* textureCoordinates, + std::uint32_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + auto& destination = dna.geometry.meshes[meshIndex].textureCoordinates; + destination.clear(); + destination.us.resize_uninitialized(count); + destination.vs.resize_uninitialized(count); + for (std::size_t i = 0ul; i < count; ++i) { + destination.us[i] = textureCoordinates[i].u; + destination.vs[i] = textureCoordinates[i].v; + } +} + +template +inline void WriterImpl::setVertexNormals(std::uint16_t meshIndex, const Normal* normals, std::uint32_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + dna.geometry.meshes[meshIndex].normals.assign(normals, normals + count); +} + +template +inline void WriterImpl::setVertexLayouts(std::uint16_t meshIndex, const VertexLayout* layouts, std::uint32_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + auto& destination = dna.geometry.meshes[meshIndex].layouts; + destination.clear(); + destination.positions.resize_uninitialized(count); + destination.textureCoordinates.resize_uninitialized(count); + destination.normals.resize_uninitialized(count); + for (std::size_t i = 0ul; i < count; ++i) { + destination.positions[i] = layouts[i].position; + destination.textureCoordinates[i] = layouts[i].textureCoordinate; + destination.normals[i] = layouts[i].normal; + } +} + +template +inline void WriterImpl::clearFaceVertexLayoutIndices(std::uint16_t meshIndex) { + if (meshIndex < dna.geometry.meshes.size()) { + dna.geometry.meshes[meshIndex].faces.clear(); + } +} + +template +inline void WriterImpl::setFaceVertexLayoutIndices(std::uint16_t meshIndex, + std::uint32_t faceIndex, + const std::uint32_t* layoutIndices, + std::uint32_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + auto& faces = dna.geometry.meshes[meshIndex].faces; + ensureHasSize(faces, faceIndex + 1ul, memRes); + faces[faceIndex].layoutIndices.assign(layoutIndices, layoutIndices + count); +} + +template +inline void WriterImpl::setMaximumInfluencePerVertex(std::uint16_t meshIndex, std::uint16_t maxInfluenceCount) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + dna.geometry.meshes[meshIndex].maximumInfluencePerVertex = maxInfluenceCount; +} + +template +inline void WriterImpl::clearSkinWeights(std::uint16_t meshIndex) { + if (meshIndex < dna.geometry.meshes.size()) { + dna.geometry.meshes[meshIndex].skinWeights.clear(); + } +} + +template +inline void WriterImpl::setSkinWeightsValues(std::uint16_t meshIndex, + std::uint32_t vertexIndex, + const float* weights, + std::uint16_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + auto& skinWeights = dna.geometry.meshes[meshIndex].skinWeights; + ensureHasSize(skinWeights, vertexIndex + 1ul, memRes); + skinWeights[vertexIndex].weights.assign(weights, weights + count); +} + +template +inline void WriterImpl::setSkinWeightsJointIndices(std::uint16_t meshIndex, + std::uint32_t vertexIndex, + const std::uint16_t* jointIndices, + std::uint16_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + auto& skinWeights = dna.geometry.meshes[meshIndex].skinWeights; + ensureHasSize(skinWeights, vertexIndex + 1ul, memRes); + skinWeights[vertexIndex].jointIndices.assign(jointIndices, jointIndices + count); +} + +template +inline void WriterImpl::clearBlendShapeTargets(std::uint16_t meshIndex) { + if (meshIndex < dna.geometry.meshes.size()) { + dna.geometry.meshes[meshIndex].blendShapeTargets.clear(); + } +} + +template +inline void WriterImpl::setBlendShapeChannelIndex(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + std::uint16_t blendShapeChannelIndex) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + ensureHasSize(dna.geometry.meshes[meshIndex].blendShapeTargets, blendShapeTargetIndex + 1ul, memRes); + dna.geometry.meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].blendShapeChannelIndex = blendShapeChannelIndex; +} + +template +inline void WriterImpl::setBlendShapeTargetDeltas(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + const Delta* deltas, + std::uint32_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + ensureHasSize(dna.geometry.meshes[meshIndex].blendShapeTargets, blendShapeTargetIndex + 1ul, memRes); + dna.geometry.meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas.assign(deltas, deltas + count); +} + +template +inline void WriterImpl::setBlendShapeTargetVertexIndices(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + const std::uint32_t* vertexIndices, + std::uint32_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + auto& blendShapeTargets = dna.geometry.meshes[meshIndex].blendShapeTargets; + ensureHasSize(blendShapeTargets, blendShapeTargetIndex + 1ul, memRes); + blendShapeTargets[blendShapeTargetIndex].vertexIndices.assign(vertexIndices, vertexIndices + count); +} + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/filters/AnimatedMapFilter.cpp b/dnacalib/DNACalib/src/dna/filters/AnimatedMapFilter.cpp new file mode 100644 index 0000000..54c914d --- /dev/null +++ b/dnacalib/DNACalib/src/dna/filters/AnimatedMapFilter.cpp @@ -0,0 +1,38 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dna/filters/AnimatedMapFilter.h" + +#include "dna/DNA.h" +#include "dna/TypeDefs.h" +#include "dna/filters/Remap.h" +#include "dna/utils/Extd.h" + +namespace dna { + +AnimatedMapFilter::AnimatedMapFilter(MemoryResource* memRes_) : + memRes{memRes_}, + passingIndices{memRes}, + remappedIndices{memRes} { +} + +void AnimatedMapFilter::configure(std::uint16_t animatedMapCount, UnorderedSet allowedAnimatedMapIndices) { + passingIndices = std::move(allowedAnimatedMapIndices); + // Fill the structure that maps indices prior to deletion to indices after deletion + remap(animatedMapCount, passingIndices, remappedIndices); +} + +void AnimatedMapFilter::apply(RawDefinition& dest) { + // Fix indices so they match the same elements as earlier (but their + // actual position changed with the deletion of the unneeded entries) + dest.lodAnimatedMapMapping.mapIndices([this](std::uint16_t value) { + return remappedIndices.at(value); + }); + // Delete elements that are not referenced by the new subset of LODs + extd::filter(dest.animatedMapNames, extd::byPosition(passingIndices)); +} + +bool AnimatedMapFilter::passes(std::uint16_t index) const { + return extd::contains(passingIndices, index); +} + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/filters/AnimatedMapFilter.h b/dnacalib/DNACalib/src/dna/filters/AnimatedMapFilter.h new file mode 100644 index 0000000..19430e4 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/filters/AnimatedMapFilter.h @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/TypeDefs.h" + +#include + +namespace dna { + +struct RawDefinition; + +class AnimatedMapFilter { + public: + explicit AnimatedMapFilter(MemoryResource* memRes_); + void configure(std::uint16_t animatedMapCount, UnorderedSet allowedAnimatedMapIndices); + void apply(RawDefinition& dest); + bool passes(std::uint16_t index) const; + + private: + MemoryResource* memRes; + UnorderedSet passingIndices; + UnorderedMap remappedIndices; + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/filters/BlendShapeFilter.cpp b/dnacalib/DNACalib/src/dna/filters/BlendShapeFilter.cpp new file mode 100644 index 0000000..e2729fc --- /dev/null +++ b/dnacalib/DNACalib/src/dna/filters/BlendShapeFilter.cpp @@ -0,0 +1,44 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dna/filters/BlendShapeFilter.h" + +#include "dna/DNA.h" +#include "dna/TypeDefs.h" +#include "dna/filters/Remap.h" +#include "dna/utils/Extd.h" + +namespace dna { + +BlendShapeFilter::BlendShapeFilter(MemoryResource* memRes_) : + memRes{memRes_}, + passingIndices{memRes}, + remappedIndices{memRes} { +} + +void BlendShapeFilter::configure(std::uint16_t blendShapeCount, UnorderedSet allowedBlendShapeIndices) { + passingIndices = std::move(allowedBlendShapeIndices); + // Fill the structure that maps indices prior to deletion to indices after deletion + remap(blendShapeCount, passingIndices, remappedIndices); +} + +void BlendShapeFilter::apply(RawDefinition& dest) { + // Fix indices so they match the same elements as earlier (but their + // actual position changed with the deletion of the unneeded entries) + dest.lodBlendShapeMapping.mapIndices([this](std::uint16_t value) { + return remappedIndices.at(value); + }); + // Delete elements that are not referenced by the new subset of LODs + extd::filter(dest.blendShapeChannelNames, extd::byPosition(passingIndices)); + // Delete entries from other mappings that reference any of the deleted elements + auto ignoredByLODConstraint = [this](std::uint16_t /*unused*/, std::uint16_t blendShapeIndex) { + return !extd::contains(passingIndices, blendShapeIndex); + }; + dest.meshBlendShapeChannelMapping.removeIf(ignoredByLODConstraint); + dest.meshBlendShapeChannelMapping.updateTo(remappedIndices); +} + +bool BlendShapeFilter::passes(std::uint16_t index) const { + return extd::contains(passingIndices, index); +} + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/filters/BlendShapeFilter.h b/dnacalib/DNACalib/src/dna/filters/BlendShapeFilter.h new file mode 100644 index 0000000..defa749 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/filters/BlendShapeFilter.h @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/TypeDefs.h" + +#include + +namespace dna { + +struct RawDefinition; + +class BlendShapeFilter { + public: + explicit BlendShapeFilter(MemoryResource* memRes_); + void configure(std::uint16_t blendShapeCount, UnorderedSet allowedBlendShapeIndices); + void apply(RawDefinition& dest); + bool passes(std::uint16_t index) const; + + private: + MemoryResource* memRes; + UnorderedSet passingIndices; + UnorderedMap remappedIndices; + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/filters/JointFilter.cpp b/dnacalib/DNACalib/src/dna/filters/JointFilter.cpp new file mode 100644 index 0000000..f2a2bcc --- /dev/null +++ b/dnacalib/DNACalib/src/dna/filters/JointFilter.cpp @@ -0,0 +1,181 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dna/filters/JointFilter.h" + +#include "dna/DNA.h" +#include "dna/TypeDefs.h" +#include "dna/filters/Remap.h" +#include "dna/utils/Extd.h" + +namespace dna { + +JointFilter::JointFilter(MemoryResource* memRes_) : + memRes{memRes_}, + passingIndices{memRes_}, + remappedIndices{memRes_}, + option{Option::All}, + rootJointIndex{} { +} + +void JointFilter::configure(std::uint16_t jointCount, UnorderedSet allowedJointIndices, Option option_) { + option = option_; + passingIndices = std::move(allowedJointIndices); + // Fill the structure that maps indices prior to deletion to indices after deletion + remap(jointCount, passingIndices, remappedIndices); +} + +void JointFilter::apply(RawDefinition& dest) { + if (option != Option::All) { + return; + } + // Fix indices so they match the same elements as earlier (but their + // actual position changed with the deletion of the unneeded entries) + dest.lodJointMapping.mapIndices([this](std::uint16_t value) { + return remappedIndices.at(value); + }); + // Delete elements that are not referenced by the new subset of LODs + extd::filter(dest.jointNames, extd::byPosition(passingIndices)); + extd::filter(dest.jointHierarchy, extd::byPosition(passingIndices)); + // Fix joint hierarchy indices + for (auto& jntIdx : dest.jointHierarchy) { + jntIdx = remappedIndices[jntIdx]; + } + // Find root joint index + for (std::uint16_t jointIdx = 0u; jointIdx < dest.jointHierarchy.size(); ++jointIdx) { + if (dest.jointHierarchy[jointIdx] == jointIdx) { + rootJointIndex = jointIdx; + break; + } + } + // Delete entries from other mappings that reference any of the deleted elements + extd::filter(dest.neutralJointTranslations.xs, extd::byPosition(passingIndices)); + extd::filter(dest.neutralJointTranslations.ys, extd::byPosition(passingIndices)); + extd::filter(dest.neutralJointTranslations.zs, extd::byPosition(passingIndices)); + extd::filter(dest.neutralJointRotations.xs, extd::byPosition(passingIndices)); + extd::filter(dest.neutralJointRotations.ys, extd::byPosition(passingIndices)); + extd::filter(dest.neutralJointRotations.zs, extd::byPosition(passingIndices)); +} + +void JointFilter::apply(RawBehavior& dest) { + static constexpr std::uint16_t jointAttributeCount = 9u; + + for (auto& jointGroup : dest.joints.jointGroups) { + if (option == Option::All) { + // Remove joint index from joint group and remap joint indices + extd::filter(jointGroup.jointIndices, [this](std::uint16_t jntIdx, std::size_t /*unused*/) { + return passes(jntIdx); + }); + for (auto& jntIdx : jointGroup.jointIndices) { + jntIdx = remapped(jntIdx); + } + } + // Collect row indices of removed output indices to be used for joint delta removal + UnorderedSet rowsToDelete{memRes}; + // Remove output indices belonging to the deletable joint + extd::filter(jointGroup.outputIndices, [this, &rowsToDelete](std::uint16_t outputIndex, std::size_t rowIndex) { + const auto jointIndex = static_cast(outputIndex / jointAttributeCount); + if (!passes(jointIndex)) { + rowsToDelete.insert(rowIndex); + return false; + } + return true; + }); + + if (option == Option::All) { + // Remap the rest of output indices + for (auto& attrIdx : jointGroup.outputIndices) { + const auto jntIdx = static_cast(attrIdx / jointAttributeCount); + const auto relAttrIdx = attrIdx - (jntIdx * jointAttributeCount); + attrIdx = static_cast(remapped(jntIdx) * jointAttributeCount + relAttrIdx); + } + } + + // If no animation data remains, there's no point in keeping input indices + const auto jointGroupColumnCount = static_cast(jointGroup.inputIndices.size()); + if (jointGroup.outputIndices.empty()) { + jointGroup.inputIndices.clear(); + } + + // Remove joint deltas associated with the removed output indices + extd::filter(jointGroup.values, [&rowsToDelete, jointGroupColumnCount](float /*unused*/, std::size_t index) { + const std::uint16_t rowIndex = static_cast(index / jointGroupColumnCount); + return (rowsToDelete.find(rowIndex) == rowsToDelete.end()); + }); + // Recompute LODs + for (auto& lod : jointGroup.lods) { + std::uint16_t decrementBy = 0u; + for (const auto rowIndex : rowsToDelete) { + if (rowIndex < lod) { + ++decrementBy; + } + } + lod = static_cast(lod - decrementBy); + } + } +} + +void JointFilter::apply(RawVertexSkinWeights& dest) { + if (option != Option::All) { + return; + } + + auto itWeightSrc = dest.weights.begin(); + auto itWeightDst = itWeightSrc; + auto itJointSrc = dest.jointIndices.begin(); + auto itJointDst = itJointSrc; + float discardedWeights = 0.0f; + while (itJointSrc != dest.jointIndices.end()) { + if (passes(*itJointSrc)) { + *itJointDst = *itJointSrc; + ++itJointSrc; + ++itJointDst; + + *itWeightDst = *itWeightSrc; + ++itWeightSrc; + ++itWeightDst; + } else { + discardedWeights += *itWeightSrc; + ++itJointSrc; + ++itWeightSrc; + } + } + dest.jointIndices.resize(static_cast(std::distance(dest.jointIndices.begin(), itJointDst))); + dest.weights.resize(static_cast(std::distance(dest.weights.begin(), itWeightDst))); + assert(dest.jointIndices.size() == dest.weights.size()); + + if (passingIndices.empty()) { + return; + } + + if (dest.jointIndices.empty()) { + // Reassign complete influence to root joint + dest.jointIndices.resize_uninitialized(1ul); + dest.jointIndices[0ul] = rootJointIndex; + dest.weights.resize_uninitialized(1ul); + dest.weights[0ul] = 1.0f; + } else { + // Normalize weights + for (auto& jntIdx : dest.jointIndices) { + jntIdx = remapped(jntIdx); + } + + const float normalizationRatio = 1.0f / (1.0f - discardedWeights); + for (auto& weight : dest.weights) { + weight *= normalizationRatio; + } + } +} + +bool JointFilter::passes(std::uint16_t index) const { + return extd::contains(passingIndices, index); +} + +std::uint16_t JointFilter::remapped(std::uint16_t oldIndex) const { + return remappedIndices.at(oldIndex); +} + +std::uint16_t JointFilter::maxRemappedIndex() const { + return (remappedIndices.empty() ? static_cast(0) : extd::maxOf(remappedIndices).second); +} + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/filters/JointFilter.h b/dnacalib/DNACalib/src/dna/filters/JointFilter.h new file mode 100644 index 0000000..7ab820c --- /dev/null +++ b/dnacalib/DNACalib/src/dna/filters/JointFilter.h @@ -0,0 +1,42 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/TypeDefs.h" + +#include + +namespace dna { + +struct RawBehavior; +struct RawDefinition; +struct RawLODMapping; +struct RawVertexSkinWeights; + +class JointFilter { + public: + enum class Option { + All, + AnimationOnly + }; + + public: + explicit JointFilter(MemoryResource* memRes_); + void configure(std::uint16_t jointCount, UnorderedSet allowedJointIndices, Option option_ = Option::All); + void apply(RawDefinition& dest); + void apply(RawBehavior& dest); + void apply(RawVertexSkinWeights& dest); + bool passes(std::uint16_t index) const; + std::uint16_t remapped(std::uint16_t oldIndex) const; + std::uint16_t maxRemappedIndex() const; + + private: + MemoryResource* memRes; + UnorderedSet passingIndices; + UnorderedMap remappedIndices; + Option option; + std::uint16_t rootJointIndex; + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/filters/MeshFilter.cpp b/dnacalib/DNACalib/src/dna/filters/MeshFilter.cpp new file mode 100644 index 0000000..4830057 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/filters/MeshFilter.cpp @@ -0,0 +1,44 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dna/filters/MeshFilter.h" + +#include "dna/DNA.h" +#include "dna/TypeDefs.h" +#include "dna/filters/Remap.h" +#include "dna/utils/Extd.h" + +namespace dna { + +MeshFilter::MeshFilter(MemoryResource* memRes_) : + memRes{memRes_}, + passingIndices{memRes}, + remappedIndices{memRes} { +} + +void MeshFilter::configure(std::uint16_t meshCount, UnorderedSet allowedMeshIndices) { + passingIndices = std::move(allowedMeshIndices); + // Fill the structure that maps indices prior to deletion to indices after deletion + remap(meshCount, passingIndices, remappedIndices); +} + +void MeshFilter::apply(RawDefinition& dest) { + // Fix indices so they match the same elements as earlier (but their + // actual position changed with the deletion of the unneeded entries) + dest.lodMeshMapping.mapIndices([this](std::uint16_t value) { + return remappedIndices.at(value); + }); + // Delete elements that are not referenced by the new subset of LODs + extd::filter(dest.meshNames, extd::byPosition(passingIndices)); + // Delete entries from other mappings that reference any of the deleted elements + auto ignoredByLODConstraint = [this](std::uint16_t meshIndex, std::uint16_t /*unused*/) { + return !extd::contains(passingIndices, meshIndex); + }; + dest.meshBlendShapeChannelMapping.removeIf(ignoredByLODConstraint); + dest.meshBlendShapeChannelMapping.updateFrom(remappedIndices); +} + +bool MeshFilter::passes(std::uint16_t index) const { + return extd::contains(passingIndices, index); +} + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/filters/MeshFilter.h b/dnacalib/DNACalib/src/dna/filters/MeshFilter.h new file mode 100644 index 0000000..24dc1ea --- /dev/null +++ b/dnacalib/DNACalib/src/dna/filters/MeshFilter.h @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/TypeDefs.h" + +#include + +namespace dna { + +struct RawDefinition; + +class MeshFilter { + public: + explicit MeshFilter(MemoryResource* memRes_); + void configure(std::uint16_t meshCount, UnorderedSet allowedMeshIndices); + void apply(RawDefinition& dest); + bool passes(std::uint16_t index) const; + + private: + MemoryResource* memRes; + UnorderedSet passingIndices; + UnorderedMap remappedIndices; + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/filters/Remap.h b/dnacalib/DNACalib/src/dna/filters/Remap.h new file mode 100644 index 0000000..2a048d3 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/filters/Remap.h @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/TypeDefs.h" +#include "dna/utils/Extd.h" + +namespace dna { + +template +inline void remap(T originalCount, const UnorderedSet& keptIndices, UnorderedMap& mapping) { + for (T oldIndex{}, newIndex{}; oldIndex < originalCount; ++oldIndex) { + if (extd::contains(keptIndices, oldIndex)) { + mapping.insert({oldIndex, newIndex}); + ++newIndex; + } + } +} + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/stream/BinaryStreamReaderImpl.cpp b/dnacalib/DNACalib/src/dna/stream/BinaryStreamReaderImpl.cpp new file mode 100644 index 0000000..37dc969 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/stream/BinaryStreamReaderImpl.cpp @@ -0,0 +1,140 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dna/stream/BinaryStreamReaderImpl.h" + +#include "dna/TypeDefs.h" +#include "dna/types/Limits.h" + +#include +#include + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dna { + +#ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wglobal-constructors" +#endif +sc::StatusProvider BinaryStreamReaderImpl::status{SignatureMismatchError, VersionMismatchError, InvalidDataError}; +#ifdef __clang__ + #pragma clang diagnostic pop +#endif + +BinaryStreamReader::~BinaryStreamReader() = default; + +BinaryStreamReader* BinaryStreamReader::create(BoundedIOStream* stream, + DataLayer layer, + std::uint16_t maxLOD, + MemoryResource* memRes) { + PolyAllocator alloc{memRes}; + return alloc.newObject(stream, layer, maxLOD, LODLimits::min(), memRes); +} + +BinaryStreamReader* BinaryStreamReader::create(BoundedIOStream* stream, + DataLayer layer, + std::uint16_t maxLOD, + std::uint16_t minLOD, + MemoryResource* memRes) { + PolyAllocator alloc{memRes}; + return alloc.newObject(stream, layer, maxLOD, minLOD, memRes); +} + +BinaryStreamReader* BinaryStreamReader::create(BoundedIOStream* stream, + DataLayer layer, + std::uint16_t* lods, + std::uint16_t lodCount, + MemoryResource* memRes) { + PolyAllocator alloc{memRes}; + return alloc.newObject(stream, layer, ConstArrayView{lods, lodCount}, memRes); +} + +void BinaryStreamReader::destroy(BinaryStreamReader* instance) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + auto reader = static_cast(instance); + PolyAllocator alloc{reader->getMemoryResource()}; + alloc.deleteObject(reader); +} + +BinaryStreamReaderImpl::BinaryStreamReaderImpl(BoundedIOStream* stream_, + DataLayer layer_, + std::uint16_t maxLOD_, + std::uint16_t minLOD_, + MemoryResource* memRes_) : + BaseImpl{memRes_}, + ReaderImpl{memRes_}, + stream{stream_}, + archive{stream_, layer_, maxLOD_, minLOD_, memRes_}, + lodConstrained{(maxLOD_ != LODLimits::max()) || (minLOD_ != LODLimits::min())} { +} + +BinaryStreamReaderImpl::BinaryStreamReaderImpl(BoundedIOStream* stream_, + DataLayer layer_, + ConstArrayView lods_, + MemoryResource* memRes_) : + BaseImpl{memRes_}, + ReaderImpl{memRes_}, + stream{stream_}, + archive{stream_, layer_, lods_, memRes_}, + lodConstrained{true} { +} + +bool BinaryStreamReaderImpl::isLODConstrained() const { + return lodConstrained; +} + +void BinaryStreamReaderImpl::unload(DataLayer layer) { + if ((layer == DataLayer::All) || + (layer == DataLayer::AllWithoutBlendShapes) || + (layer == DataLayer::Descriptor)) { + dna = DNA{memRes}; + } else if ((layer == DataLayer::Geometry) || (layer == DataLayer::GeometryWithoutBlendShapes)) { + dna.unloadGeometry(); + } else if (layer == DataLayer::Behavior) { + dna.unloadBehavior(); + } else if (layer == DataLayer::Definition) { + dna.unloadGeometry(); + dna.unloadBehavior(); + dna.unloadDefinition(); + } +} + +void BinaryStreamReaderImpl::read() { + // Due to possible usage of custom stream implementations, the status actually must be cleared at this point + // as external streams do not have access to the status reset API + status.reset(); + + trio::StreamScope scope{stream}; + if (!sc::Status::isOk()) { + return; + } + + archive >> dna; + if (!sc::Status::isOk()) { + return; + } + + if (!dna.signature.matches()) { + status.set(SignatureMismatchError, dna.signature.value.expected.data(), dna.signature.value.got.data()); + return; + } + if (!dna.version.matches()) { + status.set(VersionMismatchError, + dna.version.generation.expected, + dna.version.version.expected, + dna.version.generation.got, + dna.version.version.got); + return; + } +} + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/stream/BinaryStreamReaderImpl.h b/dnacalib/DNACalib/src/dna/stream/BinaryStreamReaderImpl.h new file mode 100644 index 0000000..be3382c --- /dev/null +++ b/dnacalib/DNACalib/src/dna/stream/BinaryStreamReaderImpl.h @@ -0,0 +1,38 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/BinaryStreamReader.h" +#include "dna/ReaderImpl.h" +#include "dna/TypeDefs.h" +#include "dna/stream/FilteredInputArchive.h" + +#include + +namespace dna { + +class BinaryStreamReaderImpl : public ReaderImpl { + public: + BinaryStreamReaderImpl(BoundedIOStream* stream_, + DataLayer layer_, + std::uint16_t maxLOD_, + std::uint16_t minLOD_, + MemoryResource* memRes_); + BinaryStreamReaderImpl(BoundedIOStream* stream_, + DataLayer layer_, + ConstArrayView lods, + MemoryResource* memRes_); + + void unload(DataLayer layer) override; + void read() override; + bool isLODConstrained() const; + + private: + static sc::StatusProvider status; + + BoundedIOStream* stream; + FilteredInputArchive archive; + bool lodConstrained; +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/stream/BinaryStreamWriterImpl.cpp b/dnacalib/DNACalib/src/dna/stream/BinaryStreamWriterImpl.cpp new file mode 100644 index 0000000..0d2e41c --- /dev/null +++ b/dnacalib/DNACalib/src/dna/stream/BinaryStreamWriterImpl.cpp @@ -0,0 +1,43 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dna/stream/BinaryStreamWriterImpl.h" + +#include "dna/TypeDefs.h" + +#include +#include +#include +#include +#include + +namespace dna { + +BinaryStreamWriter::~BinaryStreamWriter() = default; + +BinaryStreamWriter* BinaryStreamWriter::create(BoundedIOStream* stream, MemoryResource* memRes) { + PolyAllocator alloc{memRes}; + return alloc.newObject(stream, memRes); +} + +void BinaryStreamWriter::destroy(BinaryStreamWriter* instance) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + auto writer = static_cast(instance); + PolyAllocator alloc{writer->getMemoryResource()}; + alloc.deleteObject(writer); +} + +BinaryStreamWriterImpl::BinaryStreamWriterImpl(BoundedIOStream* stream_, MemoryResource* memRes_) : + BaseImpl{memRes_}, + WriterImpl{memRes_}, + stream{stream_}, + archive{stream_} { +} + +void BinaryStreamWriterImpl::write() { + stream->open(); + archive << dna; + archive.sync(); + stream->close(); +} + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/stream/BinaryStreamWriterImpl.h b/dnacalib/DNACalib/src/dna/stream/BinaryStreamWriterImpl.h new file mode 100644 index 0000000..527d03a --- /dev/null +++ b/dnacalib/DNACalib/src/dna/stream/BinaryStreamWriterImpl.h @@ -0,0 +1,25 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/DNA.h" +#include "dna/BinaryStreamWriter.h" +#include "dna/WriterImpl.h" + +#include + +namespace dna { + +class BinaryStreamWriterImpl : public WriterImpl { + public: + BinaryStreamWriterImpl(BoundedIOStream* stream_, MemoryResource* memRes_); + + void write() override; + + private: + BoundedIOStream* stream; + terse::BinaryOutputArchive archive; + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/stream/FilteredInputArchive.cpp b/dnacalib/DNACalib/src/dna/stream/FilteredInputArchive.cpp new file mode 100644 index 0000000..39128ca --- /dev/null +++ b/dnacalib/DNACalib/src/dna/stream/FilteredInputArchive.cpp @@ -0,0 +1,298 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dna/stream/FilteredInputArchive.h" + +#include "dna/DNA.h" +#include "dna/TypeDefs.h" +#include "dna/utils/Extd.h" +#include "dna/utils/ScopedEnumEx.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dna { + +static constexpr std::uint16_t jointAttributeCount = 9u; + +template +static UnorderedMap remappedPositions(const Vector& target, const UnorderedSet& indices, MemoryResource* memRes) { + UnorderedMap mapping{memRes}; + for (U oldIndex{}, newIndex{}; oldIndex < static_cast(target.size()); ++oldIndex) { + if (indices.find(oldIndex) != indices.end()) { + mapping.insert({oldIndex, newIndex}); + ++newIndex; + } + } + return mapping; +} + +FilteredInputArchive::FilteredInputArchive(BoundedIOStream* stream_, + DataLayer layer_, + std::uint16_t maxLOD_, + std::uint16_t minLOD_, + MemoryResource* memRes_) : + AnimatedMapFilter{memRes_}, + BlendShapeFilter{memRes_}, + JointFilter{memRes_}, + MeshFilter{memRes_}, + BaseArchive{this, stream_}, + stream{stream_}, + memRes{memRes_}, + layerBitmask{computeDataLayerBitmask(layer_)}, + lodConstraint{maxLOD_, minLOD_, memRes}, + unconstrainedLODCount{} { +} + +FilteredInputArchive::FilteredInputArchive(BoundedIOStream* stream_, + DataLayer layer_, + ConstArrayView lods_, + MemoryResource* memRes_) : + AnimatedMapFilter{memRes_}, + BlendShapeFilter{memRes_}, + JointFilter{memRes_}, + MeshFilter{memRes_}, + BaseArchive{this, stream_}, + stream{stream_}, + memRes{memRes_}, + layerBitmask{computeDataLayerBitmask(layer_)}, + lodConstraint{lods_, memRes}, + unconstrainedLODCount{} { +} + +template +void FilteredInputArchive::processSubset(TContainer& dest, std::size_t offset, std::size_t size) { + using ElementType = typename TContainer::value_type; + const auto availableSize = processSize(); + assert(offset + size <= availableSize); + const auto startPosition = stream->tell(); + // Skip over first N elements + stream->seek(startPosition + offset * sizeof(ElementType)); + // Read requested number of elements + BaseArchive::processElements(dest, size); + // Even if not all elements were read, seek to the end of the list + stream->seek(startPosition + availableSize * sizeof(ElementType)); +} + +void FilteredInputArchive::process(RawDescriptor& dest) { + BaseArchive::process(dest); + assert(dest.lodCount > 0u); + lodConstraint.clampTo(dest.lodCount); + unconstrainedLODCount = dest.lodCount; + dest.maxLOD = static_cast(dest.maxLOD + lodConstraint.getMaxLOD()); + dest.lodCount = lodConstraint.getLODCount(); +} + +void FilteredInputArchive::process(RawDefinition& dest) { + if (!contains(layerBitmask, DataLayerBitmask::Definition)) { + return; + } + // Load all data + BaseArchive::process(dest); + // No filtering is done, unless LOD constraint may have some effect + if (!lodConstraint.hasImpactOn(unconstrainedLODCount)) { + return; + } + + // To find joints that are not in any LOD, find the joints that are not in LOD 0 (the current max LOD, at index 0), as it + // contains joints from all lower LODs. + Vector jointsNotInLOD0{memRes}; + const auto jointIndicesForLOD0 = dest.lodJointMapping.getIndices(0); + for (std::uint16_t idx = 0; idx < dest.jointNames.size(); ++idx) { + if (std::find(jointIndicesForLOD0.begin(), jointIndicesForLOD0.end(), idx) == jointIndicesForLOD0.end()) { + jointsNotInLOD0.push_back(idx); + } + } + + // Discard LOD data that is not relevant for the selected MaxLOD and MinLOD constraints + dest.lodMeshMapping.discardLODs(lodConstraint); + dest.lodJointMapping.discardLODs(lodConstraint); + dest.lodBlendShapeMapping.discardLODs(lodConstraint); + dest.lodAnimatedMapMapping.discardLODs(lodConstraint); + MeshFilter::configure(static_cast(dest.meshNames.size()), + dest.lodMeshMapping.getCombinedDistinctIndices(memRes)); + MeshFilter::apply(dest); + auto allowedJointIndices = dest.lodJointMapping.getCombinedDistinctIndices(memRes); + // In order to keep joints that are not in any LOD, add them all to the list of joints to keep when filtering. + allowedJointIndices.insert(jointsNotInLOD0.begin(), jointsNotInLOD0.end()); + JointFilter::configure(static_cast(dest.jointNames.size()), allowedJointIndices); + JointFilter::apply(dest); + BlendShapeFilter::configure(static_cast(dest.blendShapeChannelNames.size()), + dest.lodBlendShapeMapping.getCombinedDistinctIndices(memRes)); + BlendShapeFilter::apply(dest); + AnimatedMapFilter::configure(static_cast(dest.animatedMapNames.size()), + dest.lodAnimatedMapMapping.getCombinedDistinctIndices(memRes)); + AnimatedMapFilter::apply(dest); +} + +void FilteredInputArchive::process(RawBehavior& dest) { + if (contains(layerBitmask, DataLayerBitmask::Behavior)) { + process(dest.marker); + process(dest.controlsMarker); + process(dest.controls); + process(dest.jointsMarker); + process(dest.joints); + process(dest.blendShapeChannelsMarker); + process(dest.blendShapeChannels); + process(dest.animatedMapsMarker); + process(dest.animatedMaps); + } +} + +void FilteredInputArchive::process(RawJoints& dest) { + process(dest.rowCount); + process(dest.colCount); + if (!lodConstraint.hasImpactOn(unconstrainedLODCount)) { + process(dest.jointGroups); + return; + } + // Perform filtered load only if LOD constraints have been set + const auto jointGroupCount = processSize(); + dest.jointGroups.reserve(jointGroupCount); + for (std::size_t i = 0ul; i < jointGroupCount; ++i) { + RawJointGroup jointGroup{memRes}; + process(jointGroup.lods); + // Discard everything that falls outside the region bounded by LOD constraints + lodConstraint.applyTo(jointGroup.lods); + // Input indices are all loaded always (unless the whole joint group is empty) + const auto jointGroupRowCount = (jointGroup.lods.empty() ? static_cast(0) : jointGroup.lods[0]); + if (jointGroupRowCount != 0u) { + process(jointGroup.inputIndices); + } else { + processSubset(jointGroup.inputIndices, 0ul, 0ul); + } + const auto jointGroupColumnCount = jointGroup.inputIndices.size(); + + processSubset(jointGroup.outputIndices, 0ul, jointGroupRowCount); + // Remap joint attribute indices + for (auto& attrIdx : jointGroup.outputIndices) { + const auto jntIdx = static_cast(attrIdx / jointAttributeCount); + const auto relAttrIdx = attrIdx - (jntIdx * jointAttributeCount); + attrIdx = static_cast(JointFilter::remapped(jntIdx) * jointAttributeCount + relAttrIdx); + } + + processSubset(jointGroup.values, 0ul, jointGroupRowCount * jointGroupColumnCount); + // Load and remap joint indices (according to the remapping created while loading the Definition layer) + process(jointGroup.jointIndices); + extd::filter(jointGroup.jointIndices, [this](std::uint16_t jntIdx, std::size_t /*unused*/) { + return JointFilter::passes(jntIdx); + }); + for (auto& jntIdx : jointGroup.jointIndices) { + jntIdx = JointFilter::remapped(jntIdx); + } + + dest.jointGroups.push_back(std::move(jointGroup)); + } + const auto uncompressedJointCount = static_cast(JointFilter::maxRemappedIndex() + 1u); + dest.rowCount = static_cast(uncompressedJointCount * jointAttributeCount); +} + +void FilteredInputArchive::process(RawBlendShapeChannels& dest) { + process(dest.lods); + if (!lodConstraint.hasImpactOn(unconstrainedLODCount)) { + process(dest.inputIndices); + process(dest.outputIndices); + return; + } + // Discard everything that falls outside the region bounded by LOD constraints + lodConstraint.applyTo(dest.lods); + const auto count = (dest.lods.empty() ? static_cast(0) : dest.lods[0]); + processSubset(dest.inputIndices, 0ul, count); + processSubset(dest.outputIndices, 0ul, count); +} + +void FilteredInputArchive::process(RawAnimatedMaps& dest) { + process(dest.lods); + if (!lodConstraint.hasImpactOn(unconstrainedLODCount)) { + process(dest.conditionals); + return; + } + // Discard everything that falls outside the region bounded by LOD constraints + lodConstraint.applyTo(dest.lods); + const auto rowCount = (dest.lods.empty() ? static_cast(0) : dest.lods[0]); + processSubset(dest.conditionals.inputIndices, 0ul, rowCount); + processSubset(dest.conditionals.outputIndices, 0ul, rowCount); + processSubset(dest.conditionals.fromValues, 0ul, rowCount); + processSubset(dest.conditionals.toValues, 0ul, rowCount); + processSubset(dest.conditionals.slopeValues, 0ul, rowCount); + processSubset(dest.conditionals.cutValues, 0ul, rowCount); +} + +void FilteredInputArchive::process(RawGeometry& dest) { + process(dest.marker); + + if (!contains(layerBitmask, DataLayerBitmask::GeometryRest)) { + // As mesh sizes are variable, iterate over each of them, reading only the mesh + // offsets and jumping over the actual data of the meshes. + // This will correctly position the underlying stream (end of geometry layer), + // while still not reading the data. + const auto meshCount = processSize(); + decltype(RawMesh::offset) meshOffset{}; + decltype(RawMesh::marker) meshMarker{meshOffset}; + for (std::uint16_t i = {}; i < meshCount; ++i) { + process(meshOffset); + process(meshMarker); + } + return; + } + + if (!lodConstraint.hasImpactOn(unconstrainedLODCount)) { + process(dest.meshes); + return; + } + // Perform filtered load only if a different maxLOD is set + const auto meshCount = processSize(); + dest.meshes.reserve(meshCount); + for (std::uint16_t i = {}; i < meshCount; ++i) { + RawMesh mesh{memRes}; + // Check if the mesh indices filtered for the current maxLOD permit loading this mesh + if (MeshFilter::passes(i)) { + process(mesh); + dest.meshes.push_back(std::move(mesh)); + } else { + // Jump over the whole section of data related to this mesh + process(mesh.offset); + process(mesh.marker); + } + } +} + +void FilteredInputArchive::process(RawMesh& dest) { + process(dest.offset); + process(dest.positions); + process(dest.textureCoordinates); + process(dest.normals); + process(dest.layouts); + process(dest.faces); + process(dest.maximumInfluencePerVertex); + process(dest.skinWeights); + if (contains(layerBitmask, DataLayerBitmask::GeometryBlendShapesOnly)) { + process(dest.blendShapeTargets); + if (lodConstraint.hasImpactOn(unconstrainedLODCount)) { + extd::filter(dest.blendShapeTargets, [this](const RawBlendShapeTarget& bst, std::size_t /*unused*/) { + return BlendShapeFilter::passes(bst.blendShapeChannelIndex); + }); + } + } + process(dest.marker); +} + +void FilteredInputArchive::process(RawVertexSkinWeights& dest) { + process(dest.weights); + process(dest.jointIndices); + + if (lodConstraint.hasImpactOn(unconstrainedLODCount)) { + assert(dest.weights.size() == dest.jointIndices.size()); + JointFilter::apply(dest); + } +} + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/stream/FilteredInputArchive.h b/dnacalib/DNACalib/src/dna/stream/FilteredInputArchive.h new file mode 100644 index 0000000..448cae9 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/stream/FilteredInputArchive.h @@ -0,0 +1,84 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/DataLayer.h" +#include "dna/DataLayerBitmask.h" +#include "dna/LODConstraint.h" +#include "dna/TypeDefs.h" +#include "dna/filters/AnimatedMapFilter.h" +#include "dna/filters/BlendShapeFilter.h" +#include "dna/filters/JointFilter.h" +#include "dna/filters/MeshFilter.h" + +#include + +#include +#include + +namespace dna { + +struct RawAnimatedMaps; +struct RawBehavior; +struct RawBlendShapeChannels; +struct RawDefinition; +struct RawDescriptor; +struct RawGeometry; +struct RawJoints; +struct RawMesh; +struct RawVertexSkinWeights; + +class FilteredInputArchive final : public AnimatedMapFilter, public BlendShapeFilter, public JointFilter, public MeshFilter, + public terse::ExtendableBinaryInputArchive { + private: + using BaseArchive = terse::ExtendableBinaryInputArchive; + friend Archive; + + public: + FilteredInputArchive(BoundedIOStream* stream_, + DataLayer layer_, + std::uint16_t maxLOD_, + std::uint16_t minLOD_, + MemoryResource* memRes_); + FilteredInputArchive(BoundedIOStream* stream_, + DataLayer layer_, + ConstArrayView lods_, + MemoryResource* memRes_); + + private: + void process(RawDescriptor& dest); + void process(RawDefinition& dest); + void process(RawBehavior& dest); + void process(RawJoints& dest); + void process(RawBlendShapeChannels& dest); + void process(RawAnimatedMaps& dest); + void process(RawGeometry& dest); + void process(RawMesh& dest); + void process(RawVertexSkinWeights& dest); + + template + void process(Args&& ... args) { + BaseArchive::process(std::forward(args)...); + } + + template + void processSubset(TContainer& dest, std::size_t offset, std::size_t size); + + private: + BoundedIOStream* stream; + MemoryResource* memRes; + DataLayerBitmask layerBitmask; + LODConstraint lodConstraint; + std::uint16_t unconstrainedLODCount; + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/stream/JSONStreamReaderImpl.cpp b/dnacalib/DNACalib/src/dna/stream/JSONStreamReaderImpl.cpp new file mode 100644 index 0000000..cde7c7d --- /dev/null +++ b/dnacalib/DNACalib/src/dna/stream/JSONStreamReaderImpl.cpp @@ -0,0 +1,94 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dna/stream/JSONStreamReaderImpl.h" + +#include "dna/TypeDefs.h" +#include "dna/types/Limits.h" + +#include +#include + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dna { + +// Note: JSONStreamReader::status hasn't been initialized deliberately, as it uses the same error codes that were +// already registered in BinaryStreamReader, and since they are all registered in a single, global error code registry, +// this would trigger an assert there. + +JSONStreamReader::~JSONStreamReader() = default; + +JSONStreamReader* JSONStreamReader::create(BoundedIOStream* stream, MemoryResource* memRes) { + PolyAllocator alloc{memRes}; + return alloc.newObject(stream, memRes); +} + +void JSONStreamReader::destroy(JSONStreamReader* instance) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + auto reader = static_cast(instance); + PolyAllocator alloc{reader->getMemoryResource()}; + alloc.deleteObject(reader); +} + +JSONStreamReaderImpl::JSONStreamReaderImpl(BoundedIOStream* stream_, MemoryResource* memRes_) : + BaseImpl{memRes_}, + ReaderImpl{memRes_}, + stream{stream_}, + archive{stream_} { +} + +void JSONStreamReaderImpl::unload(DataLayer layer) { + if ((layer == DataLayer::All) || + (layer == DataLayer::AllWithoutBlendShapes) || + (layer == DataLayer::Descriptor)) { + dna = DNA{memRes}; + } else if ((layer == DataLayer::Geometry) || (layer == DataLayer::GeometryWithoutBlendShapes)) { + dna.unloadGeometry(); + } else if (layer == DataLayer::Behavior) { + dna.unloadBehavior(); + } else if (layer == DataLayer::Definition) { + dna.unloadGeometry(); + dna.unloadBehavior(); + dna.unloadDefinition(); + } +} + +void JSONStreamReaderImpl::read() { + // Due to possible usage of custom stream implementations, the status actually must be cleared at this point + // as external streams do not have access to the status reset API + status.reset(); + + trio::StreamScope scope{stream}; + if (!sc::Status::isOk()) { + return; + } + + archive >> dna; + if (!sc::Status::isOk()) { + return; + } + + if (!dna.signature.matches()) { + status.set(SignatureMismatchError, dna.signature.value.expected.data(), dna.signature.value.got.data()); + return; + } + if (!dna.version.matches()) { + status.set(VersionMismatchError, + dna.version.generation.expected, + dna.version.version.expected, + dna.version.generation.got, + dna.version.version.got); + return; + } +} + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/stream/JSONStreamReaderImpl.h b/dnacalib/DNACalib/src/dna/stream/JSONStreamReaderImpl.h new file mode 100644 index 0000000..53ce911 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/stream/JSONStreamReaderImpl.h @@ -0,0 +1,28 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/JSONStreamReader.h" +#include "dna/ReaderImpl.h" +#include "dna/TypeDefs.h" + +#include +#include + +namespace dna { + +class JSONStreamReaderImpl : public ReaderImpl { + public: + JSONStreamReaderImpl(BoundedIOStream* stream_, MemoryResource* memRes_); + + void unload(DataLayer layer) override; + void read() override; + + private: + static sc::StatusProvider status; + + BoundedIOStream* stream; + terse::JSONInputArchive archive; +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/stream/JSONStreamWriterImpl.cpp b/dnacalib/DNACalib/src/dna/stream/JSONStreamWriterImpl.cpp new file mode 100644 index 0000000..2dd8f1f --- /dev/null +++ b/dnacalib/DNACalib/src/dna/stream/JSONStreamWriterImpl.cpp @@ -0,0 +1,43 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dna/stream/JSONStreamWriterImpl.h" + +#include "dna/TypeDefs.h" + +#include +#include +#include +#include +#include + +namespace dna { + +JSONStreamWriter::~JSONStreamWriter() = default; + +JSONStreamWriter* JSONStreamWriter::create(BoundedIOStream* stream, std::uint32_t indentWidth, MemoryResource* memRes) { + PolyAllocator alloc{memRes}; + return alloc.newObject(stream, indentWidth, memRes); +} + +void JSONStreamWriter::destroy(JSONStreamWriter* instance) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + auto writer = static_cast(instance); + PolyAllocator alloc{writer->getMemoryResource()}; + alloc.deleteObject(writer); +} + +JSONStreamWriterImpl::JSONStreamWriterImpl(BoundedIOStream* stream_, std::uint32_t indentWidth, MemoryResource* memRes_) : + BaseImpl{memRes_}, + WriterImpl{memRes_}, + stream{stream_}, + archive{stream_, indentWidth} { +} + +void JSONStreamWriterImpl::write() { + stream->open(); + archive << dna; + archive.sync(); + stream->close(); +} + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/stream/JSONStreamWriterImpl.h b/dnacalib/DNACalib/src/dna/stream/JSONStreamWriterImpl.h new file mode 100644 index 0000000..9a3862d --- /dev/null +++ b/dnacalib/DNACalib/src/dna/stream/JSONStreamWriterImpl.h @@ -0,0 +1,25 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dna/DNA.h" +#include "dna/JSONStreamWriter.h" +#include "dna/WriterImpl.h" + +#include + +namespace dna { + +class JSONStreamWriterImpl : public WriterImpl { + public: + JSONStreamWriterImpl(BoundedIOStream* stream_, std::uint32_t indentWidth, MemoryResource* memRes_); + + void write() override; + + private: + BoundedIOStream* stream; + terse::JSONOutputArchive archive; + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/stream/StreamReader.cpp b/dnacalib/DNACalib/src/dna/stream/StreamReader.cpp new file mode 100644 index 0000000..930edb1 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/stream/StreamReader.cpp @@ -0,0 +1,13 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dna/StreamReader.h" + +namespace dna { + +StreamReader::~StreamReader() = default; + +const sc::StatusCode StreamReader::SignatureMismatchError{200, "DNA signature mismatched, expected %.3s, got %.3s"}; +const sc::StatusCode StreamReader::VersionMismatchError{201, "DNA version mismatched, expected %hu.%hu, got %hu.%hu"}; +const sc::StatusCode StreamReader::InvalidDataError{202, "Invalid data in DNA"}; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/stream/StreamWriter.cpp b/dnacalib/DNACalib/src/dna/stream/StreamWriter.cpp new file mode 100644 index 0000000..289f8a7 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/stream/StreamWriter.cpp @@ -0,0 +1,9 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dna/StreamWriter.h" + +namespace dna { + +StreamWriter::~StreamWriter() = default; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/types/Limits.h b/dnacalib/DNACalib/src/dna/types/Limits.h new file mode 100644 index 0000000..5bc8955 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/types/Limits.h @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include + +namespace dna { + +struct LODLimits { + static constexpr std::uint16_t max() { + return 0u; + } + + static constexpr std::uint16_t min() { + return 32u; + } + +}; + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dna/utils/Extd.h b/dnacalib/DNACalib/src/dna/utils/Extd.h new file mode 100644 index 0000000..01804b6 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/utils/Extd.h @@ -0,0 +1,145 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +// *INDENT-OFF* +#ifndef EXTD_GUARD +#define EXTD_GUARD +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace extd { + +template +inline T clamp(T value, T low, T high) { + return std::min(std::max(value, low), high); +} + +template +inline T roundUp(T number, T multiple) { + return ((number + multiple - 1) / multiple) * multiple; +} + +template +inline T interpolate(T a, T b, T weight) { + return a * (static_cast(1) - weight) + b * weight; +} + +template +inline bool contains(TInputIterator first, TInputIterator last, const T& value) { + return std::find(first, last, value) != last; +} + +template +inline bool contains(const TContainer& container, const T& value) { + return contains(std::begin(container), std::end(container), value); +} + +template +inline bool contains(const std::set& container, const T& value) { + return container.find(value) != container.end(); +} + +template +inline void filter(std::vector& source, Predicate pred) { + source.erase(std::remove_if(std::begin(source), std::end(source), [&source, &pred](const T& value) { + const auto index = static_cast(&value - &(source.front())); + return !pred(value, index); + }), source.end()); +} + +template +inline void filter(TContainer& source, Predicate pred) { + using value_type = typename TContainer::value_type; + auto newEnd = std::remove_if(std::begin(source), std::end(source), [&source, &pred](const value_type& value) { + const auto index = static_cast(&value - source.data()); + return !pred(value, index); + }); + const auto newSize = static_cast(std::distance(source.begin(), newEnd)); + source.resize(newSize); +} + +namespace impl { + +enum class LUTStrategy { + ByValue, + ByPosition +}; + +template +class LUTFilter { +public: + explicit LUTFilter(const TLUT& lut_) : lut{lut_} {} + + template + typename std::enable_if::type operator()(const T& value, std::size_t /*unused*/) { + return contains(lut, value); + } + + template + typename std::enable_if::type operator()(const T& /*unused*/, std::size_t index) { + return contains(lut, index); + } + +private: + const TLUT& lut; +}; + +} // namespace impl + +template +inline impl::LUTFilter byValue(const TLookUpTable& lookUpTable) { + return impl::LUTFilter{lookUpTable}; +} + +template +inline impl::LUTFilter byPosition(const TLookUpTable& lookUpTable) { + return impl::LUTFilter{lookUpTable}; +} + +template +inline typename TContainer::value_type maxOf(const TContainer& container) { + assert(!container.empty()); + using ValueType = typename TContainer::value_type; + const auto compare = [](const ValueType& lhs, const ValueType& rhs) { + return lhs.second < rhs.second; + }; + const auto it = std::max_element(container.begin(), container.end(), compare); + return (it == container.end() ? ValueType{} : *it); +} + +template +inline void copy(const TSource& source, TDestination& destination) { + std::copy(std::begin(source), std::end(source), std::back_inserter(destination)); +} + +template +TIterator advanced(TIterator source, TDistance distance) { + std::advance(source, static_cast::difference_type>(distance)); + return source; +} + +template +typename std::iterator_traits::difference_type advanceWhile(TIterator& it, const TIterator& end, Predicate pred) { + const auto start = it; + while (it != end && pred(*it)) { + ++it; + } + return std::distance(start, it); +} + +} // namespace extd + +#endif // EXTD_GUARD +// *INDENT-ON* diff --git a/dnacalib/DNACalib/src/dna/utils/ScopedEnumEx.h b/dnacalib/DNACalib/src/dna/utils/ScopedEnumEx.h new file mode 100644 index 0000000..8bc66a0 --- /dev/null +++ b/dnacalib/DNACalib/src/dna/utils/ScopedEnumEx.h @@ -0,0 +1,61 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include + +namespace dna { + +template +typename std::enable_if::value, TEnum>::type +operator&(TEnum lhs, TEnum rhs) { + using Underlying = typename std::underlying_type::type; + return static_cast(static_cast(lhs) & static_cast(rhs)); +} + +template +typename std::enable_if::value, TEnum>::type +operator|(TEnum lhs, TEnum rhs) { + using Underlying = typename std::underlying_type::type; + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +template +typename std::enable_if::value, TEnum>::type +operator^(TEnum lhs, TEnum rhs) { + using Underlying = typename std::underlying_type::type; + return static_cast(static_cast(lhs) ^ static_cast(rhs)); +} + +template +typename std::enable_if::value, TEnum>::type +operator~(TEnum value) { + using Underlying = typename std::underlying_type::type; + return static_cast(~static_cast(value)); +} + +template +typename std::enable_if::value, TEnum>::type +operator&=(TEnum& lhs, TEnum rhs) { + return lhs = (lhs & rhs); +} + +template +typename std::enable_if::value, TEnum>::type +operator|=(TEnum& lhs, TEnum rhs) { + return lhs = (lhs | rhs); +} + +template +typename std::enable_if::value, TEnum>::type +operator^=(TEnum& lhs, TEnum rhs) { + return lhs = (lhs ^ rhs); +} + +template +typename std::enable_if::value, bool>::type +contains(TEnum lhs, TEnum rhs) { + return (lhs & rhs) == rhs; +} + +} // namespace dna diff --git a/dnacalib/DNACalib/src/dnacalib/Command.cpp b/dnacalib/DNACalib/src/dnacalib/Command.cpp new file mode 100644 index 0000000..b075fb4 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/Command.cpp @@ -0,0 +1,9 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/Command.h" + +namespace dnac { + +Command::~Command() = default; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/CommandImplBase.h b/dnacalib/DNACalib/src/dnacalib/CommandImplBase.h new file mode 100644 index 0000000..438a6d9 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/CommandImplBase.h @@ -0,0 +1,36 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/TypeDefs.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +template +class CommandImplBase { + protected: + explicit CommandImplBase(MemoryResource* memRes_) : memRes{memRes_} { + } + + public: + static TCommand* create(MemoryResource* memRes) { + PolyAllocator alloc{memRes}; + return alloc.newObject(memRes); + } + + static void destroy(TCommand* instance) { + PolyAllocator alloc{instance->getMemoryResource()}; + alloc.deleteObject(instance); + } + + MemoryResource* getMemoryResource() { + return memRes; + } + + private: + MemoryResource* memRes; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/TypeDefs.h b/dnacalib/DNACalib/src/dnacalib/TypeDefs.h new file mode 100644 index 0000000..5c116d8 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/TypeDefs.h @@ -0,0 +1,30 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/types/Aliases.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dnac { + +using namespace tdm; +using namespace pma; + +template +using AlignedAllocator = PolyAllocator; + +template +using DynArray = terse::DynArray >; + +template +using AlignedDynArray = terse::DynArray >; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/CalculateMeshLowerLODsCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/CalculateMeshLowerLODsCommand.cpp new file mode 100644 index 0000000..52b7ef4 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/CalculateMeshLowerLODsCommand.cpp @@ -0,0 +1,214 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/CalculateMeshLowerLODsCommand.h" + +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/commands/CalculateMeshLowerLODsCommandImpl.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" +#include "dnacalib/types/UVBarycentricMapping.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + + +namespace dnac { + +class CalculateMeshLowerLODsCommand::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_}, + meshIndex{} { + } + + void setMeshIndex(std::uint16_t meshIndex_) { + meshIndex = meshIndex_; + } + + void run(DNACalibDNAReaderImpl* output) { + auto faceGetter = std::bind(&dna::Reader::getFaceVertexLayoutIndices, output, meshIndex, std::placeholders::_1); + const auto layoutPositions = output->getVertexLayoutPositionIndices(meshIndex); + const auto layoutTexCoords = output->getVertexLayoutTextureCoordinateIndices(meshIndex); + const auto origMappingUs = output->getVertexTextureCoordinateUs(meshIndex); + const auto mappingVs = output->getVertexTextureCoordinateVs(meshIndex); + const auto mappingUs = deduplicateTextureCoordinates(origMappingUs, mappingVs); + const auto faceCount = output->getFaceCount(meshIndex); + + UVBarycentricMapping mapping{faceGetter, layoutPositions, layoutTexCoords, mappingUs, mappingVs, faceCount, + getMemoryResource()}; + + auto srcMeshXs = output->getVertexPositionXs(meshIndex); + auto srcMeshYs = output->getVertexPositionYs(meshIndex); + auto srcMeshZs = output->getVertexPositionZs(meshIndex); + + const auto getSrcVertex = [srcMeshXs, srcMeshYs, srcMeshZs](std::uint32_t positionIndex) { + return fvec3{srcMeshXs[positionIndex], srcMeshYs[positionIndex], srcMeshZs[positionIndex]}; + }; + + for (std::uint16_t mi : findIndicesOfMeshLowerLODs(output)) { + const auto vertexLayoutPositionIndices = output->getVertexLayoutPositionIndices(mi); + const auto vertexLayoutTextureCoordinateIndices = output->getVertexLayoutTextureCoordinateIndices(mi); + const auto vs = output->getVertexTextureCoordinateVs(mi); + const auto us = deduplicateTextureCoordinates(output->getVertexTextureCoordinateUs(mi), vs); + const std::uint32_t positionCount = output->getVertexPositionCount(mi); + RawVector3Vector destVertexPositions {positionCount, {}, getMemoryResource()}; + // As there can be multiple VertexLayout per each VertexPosition we will use arithmetic mean value. + Vector vertexLayoutsPerPosition{positionCount, {}, getMemoryResource()}; + + for (std::uint32_t vli = 0u; vli < vertexLayoutPositionIndices.size(); ++vli) { + std::uint32_t uvIndex = vertexLayoutTextureCoordinateIndices[vli]; + const fvec2 uvs = {us[uvIndex], vs[uvIndex]}; + const auto weightsIndicesPair = mapping.getBarycentric(uvs); + fvec3 barycentric = std::get<0>(weightsIndicesPair); + auto srcVtxIndices = std::get<1>(weightsIndicesPair); + + if (srcVtxIndices.size() == 0) { + // We didn't hit any triangle. We aim to identify the nearest face to this UV, ensuring + // that the selected face has an intersection with at least one of the adjacent faces of the vertex we are + // projecting. + float minDistance = std::numeric_limits::max(); + std::uint32_t sourceTriangleIndex = std::numeric_limits::max(); + // First we find all of the faces that are adjacent to this vertex + for (std::uint32_t fi = 0u; fi < output->getFaceCount(mi); fi++) { + const auto face = output->getFaceVertexLayoutIndices(mi, fi); + if (std::find(face.begin(), face.end(), vli) == face.end()) { + continue; + } + + // Gather all vertex UVs from this face and create a bounding box from it + Vector UVs{getMemoryResource()}; + for (const auto vertexLayoutIndex : face) { + uvIndex = vertexLayoutTextureCoordinateIndices[vertexLayoutIndex]; + UVs.emplace_back(us[uvIndex], vs[uvIndex]); + } + const BoundingBox faceBoundingBox{UVs}; + + // Find the closest triangle that has intersection with this face + auto bBoxes = mapping.getBoundingBoxes(); + for (std::uint32_t bi = 0u; bi < bBoxes.size(); bi++) { + const auto& bBox = bBoxes[bi]; + if (bBox.overlaps(faceBoundingBox)) { + const float distance = bBox.distance(uvs); + if (distance < minDistance) { + minDistance = distance; + sourceTriangleIndex = bi; + } + } + } + } + + if (sourceTriangleIndex != std::numeric_limits::max()) { + barycentric = mapping.getTriangle(sourceTriangleIndex).getBarycentricCoords(uvs); + srcVtxIndices = mapping.getTrianglePositionIndices(sourceTriangleIndex); + } else { + assert(false && "Could not map a vertex. It is not within a face of higher lod."); + continue; + } + } + const fvec3 src = + getSrcVertex(srcVtxIndices[0]) * barycentric[0] + + getSrcVertex(srcVtxIndices[1]) * barycentric[1] + + getSrcVertex(srcVtxIndices[2]) * barycentric[2]; + + const uint32_t positionIndex = vertexLayoutPositionIndices[vli]; + float& destX = destVertexPositions.xs[positionIndex]; + float& destY = destVertexPositions.ys[positionIndex]; + float& destZ = destVertexPositions.zs[positionIndex]; + + const auto vtxLayoutCount = ++vertexLayoutsPerPosition[positionIndex]; + // We require mean average, more than one vertexLayout for this vertex position + const auto lastDenominator = static_cast(vtxLayoutCount - 1u); + const auto newDenominator = static_cast(vtxLayoutCount); + destX = (destX * lastDenominator + src[0]) / newDenominator; + destY = (destY * lastDenominator + src[1]) / newDenominator; + destZ = (destZ * lastDenominator + src[2]) / newDenominator; + + } + output->setVertexPositions(mi, std::move(destVertexPositions)); + } + } + + private: + /** + * @brief Get the Mesh Name without postfix " _lodX_mesh" + */ + static StringView getMeshName(DNACalibDNAReaderImpl* output, std::uint16_t mi) { + const auto meshName = output->getMeshName(mi); + const auto underscoreIter = std::find(meshName.begin(), meshName.end(), '_'); + assert(underscoreIter != meshName.end() && "Mesh naming does not follow convention."); + + auto length = static_cast(std::distance(meshName.begin(), underscoreIter)); + return {meshName.data(), length}; + } + + Vector findIndicesOfMeshLowerLODs(DNACalibDNAReaderImpl* output) { + Vector lowerLODIndices{getMemoryResource()}; + bool isLowerLOD = false; + auto meshName = getMeshName(output, meshIndex); + for (std::uint16_t lodIndex = 0u; lodIndex < output->getLODCount(); ++lodIndex) { + auto lodMeshIndices = output->getMeshIndicesForLOD(lodIndex); + if (isLowerLOD) { + for (std::uint16_t mi : lodMeshIndices) { + if (meshName == getMeshName(output, mi)) { + lowerLODIndices.push_back(mi); + break; + } + } + } else { + isLowerLOD = + std::find(lodMeshIndices.begin(), lodMeshIndices.end(), meshIndex) != lodMeshIndices.end(); + } + } + return lowerLODIndices; + } + + Vector deduplicateTextureCoordinates(ConstArrayView us, ConstArrayView vs) { + Vector usCopy{us.begin(), us.end(), getMemoryResource()}; + if (isUVMapOverlapping(us, vs)) { + // The offset function will not modify those given arrays for which the specified offset is 0.0 + // So const_cast-ing here is just to satisfy the compiler, not for modifying the data sneakily. + offsetOverlappingUVMapRegion(usCopy, {const_cast(vs.data()), vs.size()}, 1.0f, 0.0f); + } + return usCopy; + } + + private: + std::uint16_t meshIndex; +}; + +CalculateMeshLowerLODsCommand::CalculateMeshLowerLODsCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +CalculateMeshLowerLODsCommand::CalculateMeshLowerLODsCommand(std::uint16_t meshIndex, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setMeshIndex(meshIndex); +} + +CalculateMeshLowerLODsCommand::~CalculateMeshLowerLODsCommand() = default; +CalculateMeshLowerLODsCommand::CalculateMeshLowerLODsCommand(CalculateMeshLowerLODsCommand&&) = default; +CalculateMeshLowerLODsCommand& CalculateMeshLowerLODsCommand::operator=(CalculateMeshLowerLODsCommand&&) = default; + +void CalculateMeshLowerLODsCommand::setMeshIndex(std::uint16_t meshIndex) { + pImpl->setMeshIndex(meshIndex); +} + +void CalculateMeshLowerLODsCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/CalculateMeshLowerLODsCommandImpl.cpp b/dnacalib/DNACalib/src/dnacalib/commands/CalculateMeshLowerLODsCommandImpl.cpp new file mode 100644 index 0000000..8b3c359 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/CalculateMeshLowerLODsCommandImpl.cpp @@ -0,0 +1,62 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/CalculateMeshLowerLODsCommandImpl.h" + +#include +#include +#include + +namespace dnac { + +inline bool near(float a, float b, float threshold) { + return std::fabs(a - b) < threshold; +} + +bool isUVMapOverlapping(ConstArrayView us, + ConstArrayView vs, + std::size_t overlapCountThreshold, + float uvCompareThreshold) { + // Quick heuristic to check if the UV is really mirrored into the upper half of the array, + // if first N matches, it will be considered a total match and deduplication should proceed + assert(us.size() == vs.size()); + + if ((us.size() % 2ul) != 0ul) { + return false; + } + + const std::size_t half = (us.size() / 2ul); + for (std::size_t i = {}; i < std::min(half, overlapCountThreshold); ++i) { + bool found = false; + for (std::size_t j = half; j < us.size(); ++j) { + if (near(us[i], us[j], uvCompareThreshold) && near(vs[i], vs[j], uvCompareThreshold)) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; +} + +void offsetOverlappingUVMapRegion(ArrayView us, ArrayView vs, float uOffset, float vOffset, + float uvCompareThreshold) { + assert(us.size() == vs.size()); + const std::size_t half = (us.size() / 2ul); + for (std::size_t i = {}; i < half; ++i) { + for (std::size_t j = half; j < us.size(); ++j) { + if (near(us[i], us[j], uvCompareThreshold) && near(vs[i], vs[j], uvCompareThreshold)) { + if (uOffset != 0.0f) { + us[i] += uOffset; + } + if (vOffset != 0.0f) { + vs[i] += vOffset; + } + break; + } + } + } +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/CalculateMeshLowerLODsCommandImpl.h b/dnacalib/DNACalib/src/dnacalib/commands/CalculateMeshLowerLODsCommandImpl.h new file mode 100644 index 0000000..a0ecec0 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/CalculateMeshLowerLODsCommandImpl.h @@ -0,0 +1,19 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +bool isUVMapOverlapping(ConstArrayView us, + ConstArrayView vs, + std::size_t overlapCountThreshold = 10ul, + float uvCompareThreshold = 0.0002f); +void offsetOverlappingUVMapRegion(ArrayView us, + ArrayView vs, + float uOffset = 1.0f, + float vOffset = 0.0f, + float uvCompareThreshold = 0.0002f); + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/ClearBlendShapesCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/ClearBlendShapesCommand.cpp new file mode 100644 index 0000000..91b7109 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/ClearBlendShapesCommand.cpp @@ -0,0 +1,48 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/ClearBlendShapesCommand.h" + +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class ClearBlendShapesCommand::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_} { + } + + void run(DNACalibDNAReaderImpl* output) { + output->clearBlendShapeChannelNames(); + output->clearBlendShapeChannelIndices(); + output->clearLODBlendShapeChannelMappings(); + output->clearMeshBlendShapeChannelMappings(); + for (std::uint16_t i = 0; i < output->getMeshCount(); ++i) { + output->clearBlendShapeTargets(i); + } + Vector lods{output->getLODCount(), 0u, getMemoryResource()}; + output->setBlendShapeChannelLODs(lods.data(), static_cast(lods.size())); + output->setBlendShapeChannelInputIndices(nullptr, 0); + output->setBlendShapeChannelOutputIndices(nullptr, 0); + } + +}; + +ClearBlendShapesCommand::ClearBlendShapesCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +ClearBlendShapesCommand::~ClearBlendShapesCommand() = default; +ClearBlendShapesCommand::ClearBlendShapesCommand(ClearBlendShapesCommand&&) = default; +ClearBlendShapesCommand& ClearBlendShapesCommand::operator=(ClearBlendShapesCommand&&) = default; + +void ClearBlendShapesCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/CommandSequence.cpp b/dnacalib/DNACalib/src/dnacalib/commands/CommandSequence.cpp new file mode 100644 index 0000000..7312a88 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/CommandSequence.cpp @@ -0,0 +1,89 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/CommandSequence.h" + +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/TypeDefs.h" + +namespace dnac { + +class CommandSequence::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_}, + commands{memRes_} { + } + + void run(DNACalibDNAReader* output) { + for (auto& cmd : commands) { + cmd->run(output); + } + } + + void add(Command* command) { + commands.push_back(command); + } + + void remove(Command* command) { + auto it = std::find(commands.begin(), commands.end(), command); + if (it != commands.end()) { + commands.erase(it); + } + } + + bool contains(Command* command) const { + return (std::find(commands.begin(), commands.end(), command) != commands.end()); + } + + std::size_t size() const { + return commands.size(); + } + + private: + Vector commands; +}; + +CommandSequence::CommandSequence(MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { +} + +CommandSequence::~CommandSequence() = default; +CommandSequence::CommandSequence(CommandSequence&&) = default; +CommandSequence& CommandSequence::operator=(CommandSequence&&) = default; + +void CommandSequence::run(DNACalibDNAReader* output) { + pImpl->run(output); +} + +void CommandSequence::add(Command* command) { + pImpl->add(command); +} + +void CommandSequence::add(ArrayView commands) { + for (auto& cmd : commands) { + pImpl->add(&cmd); + } +} + +void CommandSequence::remove(Command* command) { + pImpl->remove(command); +} + +void CommandSequence::remove(ArrayView commands) { + for (auto& cmd : commands) { + pImpl->remove(&cmd); + } +} + +bool CommandSequence::contains(Command* command) const { + return pImpl->contains(command); +} + +std::size_t CommandSequence::size() const { + return pImpl->size(); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/PruneBlendShapeTargetsCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/PruneBlendShapeTargetsCommand.cpp new file mode 100644 index 0000000..0ab576b --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/PruneBlendShapeTargetsCommand.cpp @@ -0,0 +1,56 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/PruneBlendShapeTargetsCommand.h" + +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class PruneBlendShapeTargetsCommand::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_}, + threshold{} { + } + + void setThreshold(float threshold_) { + threshold = threshold_; + } + + void run(DNACalibDNAReaderImpl* output) { + output->pruneBlendShapeTargets(threshold); + } + + private: + float threshold; + +}; + +PruneBlendShapeTargetsCommand::PruneBlendShapeTargetsCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +PruneBlendShapeTargetsCommand::PruneBlendShapeTargetsCommand(float threshold, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setThreshold(threshold); +} + +PruneBlendShapeTargetsCommand::~PruneBlendShapeTargetsCommand() = default; +PruneBlendShapeTargetsCommand::PruneBlendShapeTargetsCommand(PruneBlendShapeTargetsCommand&&) = default; +PruneBlendShapeTargetsCommand& PruneBlendShapeTargetsCommand::operator=(PruneBlendShapeTargetsCommand&&) = default; + +void PruneBlendShapeTargetsCommand::setThreshold(float threshold) { + pImpl->setThreshold(threshold); +} + +void PruneBlendShapeTargetsCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/RemoveAnimatedMapCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/RemoveAnimatedMapCommand.cpp new file mode 100644 index 0000000..86249e2 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/RemoveAnimatedMapCommand.cpp @@ -0,0 +1,71 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/RemoveAnimatedMapCommand.h" + +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class RemoveAnimatedMapCommand::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_}, + animatedMapIndices{memRes_} { + } + + void setAnimatedMapIndex(std::uint16_t animatedMapIndex_) { + animatedMapIndices.resize(1); + animatedMapIndices[0] = animatedMapIndex_; + } + + void setAnimatedMapIndices(ConstArrayView animatedMapIndices_) { + animatedMapIndices.assign(animatedMapIndices_.begin(), animatedMapIndices_.end()); + } + + void run(DNACalibDNAReaderImpl* output) { + output->removeAnimatedMaps(ConstArrayView{animatedMapIndices}); + } + + private: + Vector animatedMapIndices; + +}; + +RemoveAnimatedMapCommand::RemoveAnimatedMapCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +RemoveAnimatedMapCommand::RemoveAnimatedMapCommand(std::uint16_t animatedMapIndex, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setAnimatedMapIndex(animatedMapIndex); +} + +RemoveAnimatedMapCommand::RemoveAnimatedMapCommand(ConstArrayView animatedMapIndices, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setAnimatedMapIndices(animatedMapIndices); +} + +RemoveAnimatedMapCommand::~RemoveAnimatedMapCommand() = default; +RemoveAnimatedMapCommand::RemoveAnimatedMapCommand(RemoveAnimatedMapCommand&&) = default; +RemoveAnimatedMapCommand& RemoveAnimatedMapCommand::operator=(RemoveAnimatedMapCommand&&) = default; + +void RemoveAnimatedMapCommand::setAnimatedMapIndex(std::uint16_t animatedMapIndex) { + pImpl->setAnimatedMapIndex(animatedMapIndex); +} + +void RemoveAnimatedMapCommand::setAnimatedMapIndices(ConstArrayView animatedMapIndices) { + pImpl->setAnimatedMapIndices(animatedMapIndices); +} + +void RemoveAnimatedMapCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/RemoveBlendShapeCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/RemoveBlendShapeCommand.cpp new file mode 100644 index 0000000..3fb456e --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/RemoveBlendShapeCommand.cpp @@ -0,0 +1,71 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/RemoveBlendShapeCommand.h" + +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class RemoveBlendShapeCommand::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_}, + blendShapeIndices{memRes_} { + } + + void setBlendShapeIndex(std::uint16_t blendShapeIndex_) { + blendShapeIndices.resize(1); + blendShapeIndices[0] = blendShapeIndex_; + } + + void setBlendShapeIndices(ConstArrayView blendShapeIndices_) { + blendShapeIndices.assign(blendShapeIndices_.begin(), blendShapeIndices_.end()); + } + + void run(DNACalibDNAReaderImpl* output) { + output->removeBlendShapes(ConstArrayView{blendShapeIndices}); + } + + private: + Vector blendShapeIndices; + +}; + +RemoveBlendShapeCommand::RemoveBlendShapeCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +RemoveBlendShapeCommand::RemoveBlendShapeCommand(std::uint16_t blendShapeIndex, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setBlendShapeIndex(blendShapeIndex); +} + +RemoveBlendShapeCommand::RemoveBlendShapeCommand(ConstArrayView blendShapeIndices, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setBlendShapeIndices(blendShapeIndices); +} + +RemoveBlendShapeCommand::~RemoveBlendShapeCommand() = default; +RemoveBlendShapeCommand::RemoveBlendShapeCommand(RemoveBlendShapeCommand&&) = default; +RemoveBlendShapeCommand& RemoveBlendShapeCommand::operator=(RemoveBlendShapeCommand&&) = default; + +void RemoveBlendShapeCommand::setBlendShapeIndex(std::uint16_t blendShapeIndex) { + pImpl->setBlendShapeIndex(blendShapeIndex); +} + +void RemoveBlendShapeCommand::setBlendShapeIndices(ConstArrayView blendShapeIndices) { + pImpl->setBlendShapeIndices(blendShapeIndices); +} + +void RemoveBlendShapeCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/RemoveJointAnimationCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/RemoveJointAnimationCommand.cpp new file mode 100644 index 0000000..46e471b --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/RemoveJointAnimationCommand.cpp @@ -0,0 +1,71 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/RemoveJointAnimationCommand.h" + +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class RemoveJointAnimationCommand::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_}, + jointIndices{memRes_} { + } + + void setJointIndex(std::uint16_t jointIndex_) { + jointIndices.resize(1); + jointIndices[0] = jointIndex_; + } + + void setJointIndices(ConstArrayView jointIndices_) { + jointIndices.assign(jointIndices_.begin(), jointIndices_.end()); + } + + void run(DNACalibDNAReaderImpl* output) { + output->removeJointAnimations(ConstArrayView{jointIndices}); + } + + private: + Vector jointIndices; + +}; + +RemoveJointAnimationCommand::RemoveJointAnimationCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +RemoveJointAnimationCommand::RemoveJointAnimationCommand(std::uint16_t jointIndex, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setJointIndex(jointIndex); +} + +RemoveJointAnimationCommand::RemoveJointAnimationCommand(ConstArrayView jointIndices, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setJointIndices(jointIndices); +} + +RemoveJointAnimationCommand::~RemoveJointAnimationCommand() = default; +RemoveJointAnimationCommand::RemoveJointAnimationCommand(RemoveJointAnimationCommand&&) = default; +RemoveJointAnimationCommand& RemoveJointAnimationCommand::operator=(RemoveJointAnimationCommand&&) = default; + +void RemoveJointAnimationCommand::setJointIndex(std::uint16_t jointIndex) { + pImpl->setJointIndex(jointIndex); +} + +void RemoveJointAnimationCommand::setJointIndices(ConstArrayView jointIndices) { + pImpl->setJointIndices(jointIndices); +} + +void RemoveJointAnimationCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/RemoveJointCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/RemoveJointCommand.cpp new file mode 100644 index 0000000..3c1b5e4 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/RemoveJointCommand.cpp @@ -0,0 +1,71 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/RemoveJointCommand.h" + +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class RemoveJointCommand::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_}, + jointIndices{memRes_} { + } + + void setJointIndex(std::uint16_t jointIndex_) { + jointIndices.resize(1); + jointIndices[0] = jointIndex_; + } + + void setJointIndices(ConstArrayView jointIndices_) { + jointIndices.assign(jointIndices_.begin(), jointIndices_.end()); + } + + void run(DNACalibDNAReaderImpl* output) { + output->removeJoints(ConstArrayView{jointIndices}); + } + + private: + Vector jointIndices; + +}; + +RemoveJointCommand::RemoveJointCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +RemoveJointCommand::RemoveJointCommand(std::uint16_t jointIndex, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setJointIndex(jointIndex); +} + +RemoveJointCommand::RemoveJointCommand(ConstArrayView jointIndices, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setJointIndices(jointIndices); +} + +RemoveJointCommand::~RemoveJointCommand() = default; +RemoveJointCommand::RemoveJointCommand(RemoveJointCommand&&) = default; +RemoveJointCommand& RemoveJointCommand::operator=(RemoveJointCommand&&) = default; + +void RemoveJointCommand::setJointIndex(std::uint16_t jointIndex) { + pImpl->setJointIndex(jointIndex); +} + +void RemoveJointCommand::setJointIndices(ConstArrayView jointIndices) { + pImpl->setJointIndices(jointIndices); +} + +void RemoveJointCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/RemoveMeshCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/RemoveMeshCommand.cpp new file mode 100644 index 0000000..79b5b0e --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/RemoveMeshCommand.cpp @@ -0,0 +1,71 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/RemoveMeshCommand.h" + +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class RemoveMeshCommand::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_}, + meshIndices{memRes_} { + } + + void setMeshIndex(std::uint16_t meshIndex_) { + meshIndices.resize(1); + meshIndices[0] = meshIndex_; + } + + void setMeshIndices(ConstArrayView meshIndices_) { + meshIndices.assign(meshIndices_.begin(), meshIndices_.end()); + } + + void run(DNACalibDNAReaderImpl* output) { + output->removeMeshes(ConstArrayView{meshIndices}); + } + + private: + Vector meshIndices; + +}; + +RemoveMeshCommand::RemoveMeshCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +RemoveMeshCommand::RemoveMeshCommand(std::uint16_t meshIndex, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setMeshIndex(meshIndex); +} + +RemoveMeshCommand::RemoveMeshCommand(ConstArrayView meshIndices, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setMeshIndices(meshIndices); +} + +RemoveMeshCommand::~RemoveMeshCommand() = default; +RemoveMeshCommand::RemoveMeshCommand(RemoveMeshCommand&&) = default; +RemoveMeshCommand& RemoveMeshCommand::operator=(RemoveMeshCommand&&) = default; + +void RemoveMeshCommand::setMeshIndex(std::uint16_t meshIndex) { + pImpl->setMeshIndex(meshIndex); +} + +void RemoveMeshCommand::setMeshIndices(ConstArrayView meshIndices) { + pImpl->setMeshIndices(meshIndices); +} + +void RemoveMeshCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/RenameAnimatedMapCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/RenameAnimatedMapCommand.cpp new file mode 100644 index 0000000..8773386 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/RenameAnimatedMapCommand.cpp @@ -0,0 +1,74 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/RenameAnimatedMapCommand.h" + +#include "dnacalib/commands/RenameResourceCommand.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +#ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wweak-vtables" +#endif +class RenameAnimatedMapCommand::Impl : public RenameResourceCommand { + private: + using Super = RenameResourceCommand; + + public: + explicit Impl(MemoryResource* memRes_) : Super{memRes_} { + } + + private: + std::uint16_t getNameCount(const dna::Reader* input) const override { + return input->getAnimatedMapCount(); + } + + StringView getNameByIndex(const dna::Reader* input, std::uint16_t index_) const override { + return input->getAnimatedMapName(index_); + } + + void setNameByIndex(dna::Writer* output, std::uint16_t index_, const char* name) override { + output->setAnimatedMapName(index_, name); + } + +}; +#ifdef __clang__ + #pragma clang diagnostic pop +#endif + +RenameAnimatedMapCommand::RenameAnimatedMapCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +RenameAnimatedMapCommand::RenameAnimatedMapCommand(std::uint16_t animatedMapIndex, const char* newName, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setName(animatedMapIndex, newName); +} + +RenameAnimatedMapCommand::RenameAnimatedMapCommand(const char* oldName, const char* newName, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setName(oldName, newName); +} + +RenameAnimatedMapCommand::~RenameAnimatedMapCommand() = default; +RenameAnimatedMapCommand::RenameAnimatedMapCommand(RenameAnimatedMapCommand&&) = default; +RenameAnimatedMapCommand& RenameAnimatedMapCommand::operator=(RenameAnimatedMapCommand&&) = default; + +void RenameAnimatedMapCommand::setName(std::uint16_t animatedMapIndex, const char* newName) { + pImpl->setName(animatedMapIndex, newName); +} + +void RenameAnimatedMapCommand::setName(const char* oldName, const char* newName) { + pImpl->setName(oldName, newName); +} + +void RenameAnimatedMapCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/RenameBlendShapeCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/RenameBlendShapeCommand.cpp new file mode 100644 index 0000000..db14c3f --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/RenameBlendShapeCommand.cpp @@ -0,0 +1,74 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/RenameBlendShapeCommand.h" + +#include "dnacalib/commands/RenameResourceCommand.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +#ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wweak-vtables" +#endif +class RenameBlendShapeCommand::Impl : public RenameResourceCommand { + private: + using Super = RenameResourceCommand; + + public: + explicit Impl(MemoryResource* memRes_) : Super{memRes_} { + } + + private: + std::uint16_t getNameCount(const dna::Reader* input) const override { + return input->getBlendShapeChannelCount(); + } + + StringView getNameByIndex(const dna::Reader* input, std::uint16_t index_) const override { + return input->getBlendShapeChannelName(index_); + } + + void setNameByIndex(dna::Writer* output, std::uint16_t index_, const char* name) override { + output->setBlendShapeChannelName(index_, name); + } + +}; +#ifdef __clang__ + #pragma clang diagnostic pop +#endif + +RenameBlendShapeCommand::RenameBlendShapeCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +RenameBlendShapeCommand::RenameBlendShapeCommand(std::uint16_t blendShapeIndex, const char* newName, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setName(blendShapeIndex, newName); +} + +RenameBlendShapeCommand::RenameBlendShapeCommand(const char* oldName, const char* newName, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setName(oldName, newName); +} + +RenameBlendShapeCommand::~RenameBlendShapeCommand() = default; +RenameBlendShapeCommand::RenameBlendShapeCommand(RenameBlendShapeCommand&&) = default; +RenameBlendShapeCommand& RenameBlendShapeCommand::operator=(RenameBlendShapeCommand&&) = default; + +void RenameBlendShapeCommand::setName(std::uint16_t blendShapeIndex, const char* newName) { + pImpl->setName(blendShapeIndex, newName); +} + +void RenameBlendShapeCommand::setName(const char* oldName, const char* newName) { + pImpl->setName(oldName, newName); +} + +void RenameBlendShapeCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/RenameJointCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/RenameJointCommand.cpp new file mode 100644 index 0000000..3e6510f --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/RenameJointCommand.cpp @@ -0,0 +1,74 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/RenameJointCommand.h" + +#include "dnacalib/commands/RenameResourceCommand.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +#ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wweak-vtables" +#endif +class RenameJointCommand::Impl : public RenameResourceCommand { + private: + using Super = RenameResourceCommand; + + public: + explicit Impl(MemoryResource* memRes_) : Super{memRes_} { + } + + private: + std::uint16_t getNameCount(const dna::Reader* input) const override { + return input->getJointCount(); + } + + StringView getNameByIndex(const dna::Reader* input, std::uint16_t index_) const override { + return input->getJointName(index_); + } + + void setNameByIndex(dna::Writer* output, std::uint16_t index_, const char* name) override { + output->setJointName(index_, name); + } + +}; +#ifdef __clang__ + #pragma clang diagnostic pop +#endif + +RenameJointCommand::RenameJointCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +RenameJointCommand::RenameJointCommand(std::uint16_t jointIndex, const char* newName, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setName(jointIndex, newName); +} + +RenameJointCommand::RenameJointCommand(const char* oldName, const char* newName, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setName(oldName, newName); +} + +RenameJointCommand::~RenameJointCommand() = default; +RenameJointCommand::RenameJointCommand(RenameJointCommand&&) = default; +RenameJointCommand& RenameJointCommand::operator=(RenameJointCommand&&) = default; + +void RenameJointCommand::setName(std::uint16_t jointIndex, const char* newName) { + pImpl->setName(jointIndex, newName); +} + +void RenameJointCommand::setName(const char* oldName, const char* newName) { + pImpl->setName(oldName, newName); +} + +void RenameJointCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/RenameMeshCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/RenameMeshCommand.cpp new file mode 100644 index 0000000..428d45f --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/RenameMeshCommand.cpp @@ -0,0 +1,74 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/RenameMeshCommand.h" + +#include "dnacalib/commands/RenameResourceCommand.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +#ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wweak-vtables" +#endif +class RenameMeshCommand::Impl : public RenameResourceCommand { + private: + using Super = RenameResourceCommand; + + public: + explicit Impl(MemoryResource* memRes_) : Super{memRes_} { + } + + private: + std::uint16_t getNameCount(const dna::Reader* input) const override { + return input->getMeshCount(); + } + + StringView getNameByIndex(const dna::Reader* input, std::uint16_t index_) const override { + return input->getMeshName(index_); + } + + void setNameByIndex(dna::Writer* output, std::uint16_t index_, const char* name) override { + output->setMeshName(index_, name); + } + +}; +#ifdef __clang__ + #pragma clang diagnostic pop +#endif + +RenameMeshCommand::RenameMeshCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +RenameMeshCommand::RenameMeshCommand(std::uint16_t meshIndex, const char* newName, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setName(meshIndex, newName); +} + +RenameMeshCommand::RenameMeshCommand(const char* oldName, const char* newName, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setName(oldName, newName); +} + +RenameMeshCommand::~RenameMeshCommand() = default; +RenameMeshCommand::RenameMeshCommand(RenameMeshCommand&&) = default; +RenameMeshCommand& RenameMeshCommand::operator=(RenameMeshCommand&&) = default; + +void RenameMeshCommand::setName(std::uint16_t meshIndex, const char* newName) { + pImpl->setName(meshIndex, newName); +} + +void RenameMeshCommand::setName(const char* oldName, const char* newName) { + pImpl->setName(oldName, newName); +} + +void RenameMeshCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/RenameResourceCommand.h b/dnacalib/DNACalib/src/dnacalib/commands/RenameResourceCommand.h new file mode 100644 index 0000000..4ff3b08 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/RenameResourceCommand.h @@ -0,0 +1,89 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/TypeDefs.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" + +#include +#include + +namespace dnac { + +template +class RenameResourceCommand : public CommandImplBase { + private: + using Super = CommandImplBase; + + enum class Configuration { + Unconfigured, + SearchAndRename, + RenameByIndex + }; + + public: + explicit RenameResourceCommand(MemoryResource* memRes_) : + Super{memRes_}, + oldName{memRes_}, + newName{memRes_}, + index{}, + config{Configuration::Unconfigured} { + } + + virtual ~RenameResourceCommand() = default; + + RenameResourceCommand(const RenameResourceCommand&) = default; + RenameResourceCommand& operator=(const RenameResourceCommand&) = default; + + RenameResourceCommand(RenameResourceCommand&&) = default; + RenameResourceCommand& operator=(RenameResourceCommand&&) = default; + + void setName(std::uint16_t index_, const char* newName_) { + index = index_; + newName = newName_; + config = Configuration::RenameByIndex; + } + + void setName(const char* oldName_, const char* newName_) { + oldName = oldName_; + newName = newName_; + config = Configuration::SearchAndRename; + } + + void run(DNACalibDNAReaderImpl* output) { + if (config == Configuration::RenameByIndex) { + rename(output); + } else if (config == Configuration::SearchAndRename) { + searchAndRename(output); + } + } + + private: + void searchAndRename(DNACalibDNAReaderImpl* output) { + for (std::uint16_t i = 0u; i < getNameCount(output); ++i) { + const auto name = getNameByIndex(output, i); + if (name == oldName) { + setNameByIndex(output, i, newName.c_str()); + return; + } + } + } + + void rename(DNACalibDNAReaderImpl* output) { + setNameByIndex(output, index, newName.c_str()); + } + + virtual std::uint16_t getNameCount(const dna::Reader* input) const = 0; + virtual StringView getNameByIndex(const dna::Reader* input, std::uint16_t index_) const = 0; + virtual void setNameByIndex(dna::Writer* output, std::uint16_t index_, const char* name) = 0; + + private: + String oldName; + String newName; + std::uint16_t index; + Configuration config; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/RotateCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/RotateCommand.cpp new file mode 100644 index 0000000..d6fc791 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/RotateCommand.cpp @@ -0,0 +1,151 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/RotateCommand.h" + +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" +#include "dnacalib/utils/Algorithm.h" + +#include + +namespace dnac { + +class RotateCommand::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_}, + degrees{}, + origin{} { + } + + void setRotation(Vector3 degrees_) { + degrees = degrees_; + } + + void setOrigin(Vector3 origin_) { + origin = origin_; + } + + void run(DNACalibDNAReaderImpl* output) { + if (degrees != Vector3{}) { + rotateNeutralJoints(output); + rotateVertexPositions(output); + rotateBlendShapeTargetDeltas(output); + } + } + + private: + tdm::mat4 getRotationTransformationMatrix() const { + const auto inverseTranslationMatrix = tdm::translate(tdm::fvec3{-origin.x, -origin.y, -origin.z}); + const auto rotationMatrix = tdm::rotate(tdm::radians(degrees.x), tdm::radians(degrees.y), tdm::radians(degrees.z)); + const auto translationMatrix = tdm::translate(tdm::fvec3{origin.x, origin.y, origin.z}); + return inverseTranslationMatrix * rotationMatrix * translationMatrix; + } + + void rotateNeutralJoints(DNACalibDNAReaderImpl* output) { + const auto rotationMatrix = getRotationTransformationMatrix(); + for (std::uint16_t jointIndex = 0u; jointIndex < output->getJointCount(); ++jointIndex) { + const auto parentIndex = output->getJointParentIndex(jointIndex); + // Only root joints are rotated + if (jointIndex == parentIndex) { + const auto jointNeutralRotation = output->getNeutralJointRotation(jointIndex); + const auto jointNeutralTranslation = output->getNeutralJointTranslation(jointIndex); + + const auto jointRotationMatrix = tdm::rotate(tdm::radians(jointNeutralRotation.x), + tdm::radians(jointNeutralRotation.y), + tdm::radians(jointNeutralRotation.z)); + const auto jointTranslationMatrix = tdm::translate(fvec3{jointNeutralTranslation.x, + jointNeutralTranslation.y, + jointNeutralTranslation.z}); + + const auto transformMatrix = jointRotationMatrix * jointTranslationMatrix * rotationMatrix; + const auto t = extractTranslationVector(transformMatrix); + const auto r = extractRotationVector(transformMatrix); + + output->setNeutralJointRotation(jointIndex, + Vector3{tdm::degrees(r[0]), tdm::degrees(r[1]), tdm::degrees(r[2])}); + output->setNeutralJointTranslation(jointIndex, Vector3{t[0], t[1], t[2]}); + } + } + } + + void rotateVertexPositions(DNACalibDNAReaderImpl* output) { + const auto rotationMatrix = getRotationTransformationMatrix(); + for (std::uint16_t meshIndex = 0u; meshIndex < output->getMeshCount(); ++meshIndex) { + const auto xs = output->getVertexPositionXs(meshIndex); + const auto ys = output->getVertexPositionYs(meshIndex); + const auto zs = output->getVertexPositionZs(meshIndex); + assert((xs.size() == ys.size()) && (ys.size() == zs.size())); + RawVector3Vector mesh{xs, ys, zs, output->getMemoryResource()}; + for (std::size_t i = 0ul; i < mesh.size(); ++i) { + const tdm::fvec4 vertex{mesh.xs[i], mesh.ys[i], mesh.zs[i], 1.0f}; + const tdm::fvec4 rotatedVertex = vertex * rotationMatrix; + mesh.xs[i] = rotatedVertex[0]; + mesh.ys[i] = rotatedVertex[1]; + mesh.zs[i] = rotatedVertex[2]; + } + output->setVertexPositions(meshIndex, std::move(mesh)); + } + } + + void rotateBlendShapeTargetDeltas(DNACalibDNAReaderImpl* output) { + const auto rotationMatrix = getRotationTransformationMatrix(); + for (std::uint16_t meshIndex = 0u; meshIndex < output->getMeshCount(); ++meshIndex) { + for (std::uint16_t blendShapeTargetIndex = 0u; + blendShapeTargetIndex < output->getBlendShapeTargetCount(meshIndex); + ++blendShapeTargetIndex) { + const auto xs = output->getBlendShapeTargetDeltaXs(meshIndex, blendShapeTargetIndex); + const auto ys = output->getBlendShapeTargetDeltaYs(meshIndex, blendShapeTargetIndex); + const auto zs = output->getBlendShapeTargetDeltaZs(meshIndex, blendShapeTargetIndex); + assert((xs.size() == ys.size()) && (ys.size() == zs.size())); + RawVector3Vector deltas{xs, ys, zs, output->getMemoryResource()}; + for (std::size_t i = 0ul; i < deltas.size(); ++i) { + const tdm::fvec4 delta{deltas.xs[i], deltas.ys[i], deltas.zs[i], 1.0f}; + const tdm::fvec4 rotatedDelta = delta * rotationMatrix; + deltas.xs[i] = rotatedDelta[0]; + deltas.ys[i] = rotatedDelta[1]; + deltas.zs[i] = rotatedDelta[2]; + } + output->setBlendShapeTargetDeltas(meshIndex, blendShapeTargetIndex, std::move(deltas)); + } + } + } + + private: + Vector3 degrees; + Vector3 origin; + +}; + +RotateCommand::RotateCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +RotateCommand::RotateCommand(Vector3 degrees, Vector3 origin, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setRotation(degrees); + pImpl->setOrigin(origin); +} + +RotateCommand::~RotateCommand() = default; +RotateCommand::RotateCommand(RotateCommand&&) = default; +RotateCommand& RotateCommand::operator=(RotateCommand&&) = default; + +void RotateCommand::setRotation(Vector3 degrees) { + pImpl->setRotation(degrees); +} + +void RotateCommand::setOrigin(Vector3 origin) { + pImpl->setOrigin(origin); +} + +void RotateCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/ScaleCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/ScaleCommand.cpp new file mode 100644 index 0000000..d5d6e04 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/ScaleCommand.cpp @@ -0,0 +1,173 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/ScaleCommand.h" + +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" + +#include + +namespace dnac { + +class ScaleCommand::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_}, + origin{}, + scale{1.0f} { + } + + void setScale(float scale_) { + scale = scale_; + } + + void setOrigin(Vector3 origin_) { + origin = origin_; + } + + void run(DNACalibDNAReaderImpl* output) { + if (scale != 1.0f) { + scaleNeutralJoints(output); + scaleJointBehavior(output); + scaleGeometry(output); + } + } + + private: + void scaleNeutralJoints(DNACalibDNAReaderImpl* output) { + const auto xs = output->getNeutralJointTranslationXs(); + const auto ys = output->getNeutralJointTranslationYs(); + const auto zs = output->getNeutralJointTranslationZs(); + assert((xs.size() == ys.size()) && (ys.size() == zs.size())); + RawVector3Vector translations{xs, ys, zs, output->getMemoryResource()}; + for (std::uint16_t i = 0ul; i < translations.size(); ++i) { + const bool isRootJoint = (output->getJointParentIndex(i) == i); + if (isRootJoint) { + translations.xs[i] -= origin.x; + translations.ys[i] -= origin.y; + translations.zs[i] -= origin.z; + + translations.xs[i] *= scale; + translations.ys[i] *= scale; + translations.zs[i] *= scale; + + translations.xs[i] += origin.x; + translations.ys[i] += origin.y; + translations.zs[i] += origin.z; + } else { + translations.xs[i] *= scale; + translations.ys[i] *= scale; + translations.zs[i] *= scale; + } + } + output->setNeutralJointTranslations(std::move(translations)); + } + + void scaleJointBehavior(DNACalibDNAReaderImpl* output) { + constexpr std::uint16_t jointAttributeCount = 9u; + constexpr std::uint16_t rotationOffset = 3u; + + for (std::uint16_t jointGroupIndex = 0u; jointGroupIndex < output->getJointGroupCount(); ++jointGroupIndex) { + const auto values = output->getJointGroupValues(jointGroupIndex); + const auto outputIndices = output->getJointGroupOutputIndices(jointGroupIndex); + const auto inputIndices = output->getJointGroupInputIndices(jointGroupIndex); + const auto columnCount = inputIndices.size(); + AlignedDynArray newValues{values.begin(), values.end(), output->getMemoryResource()}; + for (std::size_t row = 0ul; row < outputIndices.size(); ++row) { + // Only the translation attributes need to be scaled + const auto relAttributeIndex = (outputIndices[row] % jointAttributeCount); + if (relAttributeIndex < rotationOffset) { + for (std::size_t column = 0ul; column < columnCount; ++column) { + newValues[row * columnCount + column] *= scale; + } + } + } + output->setJointGroupValues(jointGroupIndex, std::move(newValues)); + } + } + + void scaleGeometry(DNACalibDNAReaderImpl* output) { + for (std::uint16_t meshIndex = 0u; meshIndex < output->getMeshCount(); ++meshIndex) { + scaleVertexPositions(output, meshIndex); + scaleBlendShapeTargetDeltas(output, meshIndex); + } + } + + void scaleVertexPositions(DNACalibDNAReaderImpl* output, std::uint16_t meshIndex) { + const auto xs = output->getVertexPositionXs(meshIndex); + const auto ys = output->getVertexPositionYs(meshIndex); + const auto zs = output->getVertexPositionZs(meshIndex); + assert((xs.size() == ys.size()) && (ys.size() == zs.size())); + RawVector3Vector vertices{xs, ys, zs, output->getMemoryResource()}; + for (std::uint32_t i = 0u; i < vertices.size(); ++i) { + vertices.xs[i] -= origin.x; + vertices.ys[i] -= origin.y; + vertices.zs[i] -= origin.z; + + vertices.xs[i] *= scale; + vertices.ys[i] *= scale; + vertices.zs[i] *= scale; + + vertices.xs[i] += origin.x; + vertices.ys[i] += origin.y; + vertices.zs[i] += origin.z; + } + output->setVertexPositions(meshIndex, std::move(vertices)); + } + + void scaleBlendShapeTargetDeltas(DNACalibDNAReaderImpl* output, std::uint16_t meshIndex) { + for (std::uint16_t blendShapeTargetIndex = 0u; + blendShapeTargetIndex < output->getBlendShapeTargetCount(meshIndex); + ++blendShapeTargetIndex) { + const auto xs = output->getBlendShapeTargetDeltaXs(meshIndex, blendShapeTargetIndex); + const auto ys = output->getBlendShapeTargetDeltaYs(meshIndex, blendShapeTargetIndex); + const auto zs = output->getBlendShapeTargetDeltaZs(meshIndex, blendShapeTargetIndex); + assert((xs.size() == ys.size()) && (ys.size() == zs.size())); + RawVector3Vector deltas{xs, ys, zs, output->getMemoryResource()}; + for (std::uint32_t i = 0u; i < deltas.size(); ++i) { + deltas.xs[i] *= scale; + deltas.ys[i] *= scale; + deltas.zs[i] *= scale; + } + output->setBlendShapeTargetDeltas(meshIndex, blendShapeTargetIndex, std::move(deltas)); + } + } + + private: + Vector3 origin; + float scale; + +}; + +ScaleCommand::ScaleCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +ScaleCommand::ScaleCommand(float scale, Vector3 origin, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setScale(scale); + pImpl->setOrigin(origin); +} + +ScaleCommand::~ScaleCommand() = default; +ScaleCommand::ScaleCommand(ScaleCommand&&) = default; +ScaleCommand& ScaleCommand::operator=(ScaleCommand&&) = default; + +void ScaleCommand::setScale(float scale) { + pImpl->setScale(scale); +} + +void ScaleCommand::setOrigin(Vector3 origin) { + pImpl->setOrigin(origin); +} + +void ScaleCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/SetBlendShapeTargetDeltasCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/SetBlendShapeTargetDeltasCommand.cpp new file mode 100644 index 0000000..521c631 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/SetBlendShapeTargetDeltasCommand.cpp @@ -0,0 +1,317 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/SetBlendShapeTargetDeltasCommand.h" + +#include "dnacalib/TypeDefs.h" +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/commands/SupportFactories.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" +#include "dnacalib/utils/FormatString.h" + +#include +#include + +namespace dnac { + +class SetBlendShapeTargetDeltasCommand::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_}, + deltas{memRes_}, + vertexIndices{memRes_}, + masks{memRes_}, + operation{VectorOperation::Interpolate}, + meshIndex{}, + blendShapeTargetIndex{} { + } + + void setMeshIndex(std::uint16_t meshIndex_) { + meshIndex = meshIndex_; + } + + void setBlendShapeTargetIndex(std::uint16_t blendShapeTargetIndex_) { + blendShapeTargetIndex = blendShapeTargetIndex_; + } + + void setDeltas(ConstArrayView deltas_) { + deltas.assign(deltas_.begin(), deltas_.end()); + } + + void setDeltas(ConstArrayView xs, ConstArrayView ys, ConstArrayView zs) { + deltas.xs.assign(xs.begin(), xs.end()); + deltas.ys.assign(ys.begin(), ys.end()); + deltas.zs.assign(zs.begin(), zs.end()); + } + + void setVertexIndices(ConstArrayView vertexIndices_) { + vertexIndices.assign(vertexIndices_.begin(), vertexIndices_.end()); + } + + void setMasks(ConstArrayView masks_) { + masks.assign(masks_.begin(), masks_.end()); + } + + void setOperation(VectorOperation operation_) { + operation = operation_; + } + + void run(DNACalibDNAReaderImpl* output) { + status.reset(); + auto getWeight = WeightGetterFactory::create(masks); + auto op = OperationFactory::create(operation); + computeBlendShapeTargetDeltas(op, getWeight, output); + } + + private: + static void densify(RawVector3Vector& bsDeltas, Vector& bsVertexIndices, std::uint32_t vertexCount) { + const auto deltaCount = static_cast(bsDeltas.size()); + bsDeltas.resize(vertexCount); + bsVertexIndices.resize(vertexCount); + for (std::uint32_t j = deltaCount; j > 0; --j) { + const auto i = j - 1; + const auto srcDelta = Vector3{bsDeltas.xs[i], bsDeltas.ys[i], bsDeltas.zs[i]}; + bsDeltas.xs[i] = {}; + bsDeltas.ys[i] = {}; + bsDeltas.zs[i] = {}; + bsDeltas.xs[bsVertexIndices[i]] = srcDelta.x; + bsDeltas.ys[bsVertexIndices[i]] = srcDelta.y; + bsDeltas.zs[bsVertexIndices[i]] = srcDelta.z; + } + std::iota(bsVertexIndices.begin(), bsVertexIndices.end(), 0u); + } + + static void sparsify(RawVector3Vector& bsDeltas, Vector& bsVertexIndices, float threshold) { + const float threshold2 = threshold * threshold; + std::uint32_t di = 0u; + for (std::uint32_t si = 0u; si < bsVertexIndices.size(); si++) { + const auto sourceDelta = tdm::fvec3{bsDeltas.xs[si], bsDeltas.ys[si], bsDeltas.zs[si]}; + const float magnitude2 = tdm::dot(sourceDelta, sourceDelta); + if (magnitude2 > threshold2) { + bsVertexIndices[di] = bsVertexIndices[si]; + bsDeltas.xs[di] = sourceDelta[0]; + bsDeltas.ys[di] = sourceDelta[1]; + bsDeltas.zs[di] = sourceDelta[2]; + ++di; + } + } + bsDeltas.resize(di); + bsVertexIndices.resize(di); + } + + template + void computeBlendShapeTargetDeltas(FOperation op, FWeightGetter getWeight, DNACalibDNAReaderImpl* output) { + const auto xs = output->getBlendShapeTargetDeltaXs(meshIndex, blendShapeTargetIndex); + const auto ys = output->getBlendShapeTargetDeltaYs(meshIndex, blendShapeTargetIndex); + const auto zs = output->getBlendShapeTargetDeltaZs(meshIndex, blendShapeTargetIndex); + const auto vtxIndices = output->getBlendShapeTargetVertexIndices(meshIndex, blendShapeTargetIndex); + assert((xs.size() == ys.size()) && (ys.size() == zs.size()) && (xs.size() == vtxIndices.size())); + RawVector3Vector bsDeltas{xs, ys, zs, output->getMemoryResource()}; + Vector bsVertexIndices{output->getMemoryResource()}; + bsVertexIndices.assign(vtxIndices.begin(), vtxIndices.end()); + + // If no vertex indices were set, try using existing ones. The condition that must be met in that case is that number + // of set deltas equals the number of existing vertex indices. + if (vertexIndices.empty()) { + if (deltas.size() != bsVertexIndices.size()) { + const auto message = formatString( + output->getMemoryResource(), + "No vertex indices set. Current vertex indices in DNA will not be used, as their number (%hu) differs from the number of set deltas (%hu).", + bsVertexIndices.size(), + deltas.size()); + status.set(NoVertexIndicesSetError, message.c_str()); + return; + } + vertexIndices.assign(bsVertexIndices.begin(), bsVertexIndices.end()); + } + + const auto vertexCount = output->getVertexPositionCount(meshIndex); + for (const auto vi : vertexIndices) { + if (vi >= vertexCount) { + const auto message = formatString(output->getMemoryResource(), + "Vertex index (%hu) is out of bounds. Vertex count is (%hu).", + vi, + vertexCount); + status.set(VertexIndicesOutOfBoundsError, message.c_str()); + return; + } + } + + // Densify current blend shapes from DNA + densify(bsDeltas, bsVertexIndices, vertexCount); + + if (deltas.size() != vertexIndices.size()) { + const auto message = formatString(output->getMemoryResource(), + "Number of set deltas (%hu) differs from number of set vertex indices (%hu).", + deltas.size(), + vertexIndices.size()); + status.set(DeltasVertexIndicesCountMismatch, message.c_str()); + return; + } + if (!masks.empty() && (deltas.size() != masks.size())) { + const auto message = formatString(output->getMemoryResource(), + "Number of set deltas (%hu) differs from number of set masks (%hu).", + deltas.size(), + masks.size()); + status.set(DeltasMasksCountMismatch, message.c_str()); + return; + } + + // Compute operation + assert(bsDeltas.size() == vertexCount); + for (std::uint32_t i = 0u; i < vertexIndices.size(); ++i) { + const auto index = vertexIndices[i]; + const float weight = getWeight(masks.data(), i); + bsDeltas.xs[index] = op(bsDeltas.xs[index], deltas.xs[i], weight); + bsDeltas.ys[index] = op(bsDeltas.ys[index], deltas.ys[i], weight); + bsDeltas.zs[index] = op(bsDeltas.zs[index], deltas.zs[i], weight); + } + + // Sparsify result + sparsify(bsDeltas, bsVertexIndices, 0.0f); + + // Set new deltas and vertex indices to output DNA + output->setBlendShapeTargetDeltas(meshIndex, blendShapeTargetIndex, std::move(bsDeltas)); + output->setBlendShapeTargetVertexIndices(meshIndex, blendShapeTargetIndex, + ConstArrayView{bsVertexIndices}); + } + + private: + static sc::StatusProvider status; + + RawVector3Vector deltas; + Vector vertexIndices; + Vector masks; + VectorOperation operation; + std::uint16_t meshIndex; + std::uint16_t blendShapeTargetIndex; + +}; + +const sc::StatusCode SetBlendShapeTargetDeltasCommand::VertexIndicesOutOfBoundsError{3101, "%s"}; +const sc::StatusCode SetBlendShapeTargetDeltasCommand::NoVertexIndicesSetError{3102, "%s"}; +const sc::StatusCode SetBlendShapeTargetDeltasCommand::DeltasVertexIndicesCountMismatch{3103, "%s"}; +const sc::StatusCode SetBlendShapeTargetDeltasCommand::DeltasMasksCountMismatch{3104, "%s"}; + +#ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wglobal-constructors" +#endif +sc::StatusProvider SetBlendShapeTargetDeltasCommand::Impl::status{VertexIndicesOutOfBoundsError, NoVertexIndicesSetError, + DeltasVertexIndicesCountMismatch, DeltasMasksCountMismatch}; +#ifdef __clang__ + #pragma clang diagnostic pop +#endif + +SetBlendShapeTargetDeltasCommand::SetBlendShapeTargetDeltasCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +SetBlendShapeTargetDeltasCommand::SetBlendShapeTargetDeltasCommand(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + ConstArrayView deltas, + ConstArrayView vertexIndices, + VectorOperation operation, + MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setMeshIndex(meshIndex); + pImpl->setBlendShapeTargetIndex(blendShapeTargetIndex); + pImpl->setDeltas(deltas); + pImpl->setVertexIndices(vertexIndices); + pImpl->setOperation(operation); +} + +SetBlendShapeTargetDeltasCommand::SetBlendShapeTargetDeltasCommand(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs, + ConstArrayView vertexIndices, + VectorOperation operation, + MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setMeshIndex(meshIndex); + pImpl->setBlendShapeTargetIndex(blendShapeTargetIndex); + pImpl->setDeltas(xs, ys, zs); + pImpl->setVertexIndices(vertexIndices); + pImpl->setOperation(operation); +} + +SetBlendShapeTargetDeltasCommand::SetBlendShapeTargetDeltasCommand(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + ConstArrayView deltas, + ConstArrayView vertexIndices, + ConstArrayView masks, + VectorOperation operation, + MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setMeshIndex(meshIndex); + pImpl->setBlendShapeTargetIndex(blendShapeTargetIndex); + pImpl->setDeltas(deltas); + pImpl->setVertexIndices(vertexIndices); + pImpl->setMasks(masks); + pImpl->setOperation(operation); +} + +SetBlendShapeTargetDeltasCommand::SetBlendShapeTargetDeltasCommand(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs, + ConstArrayView vertexIndices, + ConstArrayView masks, + VectorOperation operation, + MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setMeshIndex(meshIndex); + pImpl->setBlendShapeTargetIndex(blendShapeTargetIndex); + pImpl->setDeltas(xs, ys, zs); + pImpl->setVertexIndices(vertexIndices); + pImpl->setMasks(masks); + pImpl->setOperation(operation); +} + +SetBlendShapeTargetDeltasCommand::~SetBlendShapeTargetDeltasCommand() = default; +SetBlendShapeTargetDeltasCommand::SetBlendShapeTargetDeltasCommand(SetBlendShapeTargetDeltasCommand&&) = default; +SetBlendShapeTargetDeltasCommand& SetBlendShapeTargetDeltasCommand::operator=(SetBlendShapeTargetDeltasCommand&&) = default; + +void SetBlendShapeTargetDeltasCommand::setMeshIndex(std::uint16_t meshIndex) { + pImpl->setMeshIndex(meshIndex); +} + +void SetBlendShapeTargetDeltasCommand::setBlendShapeTargetIndex(std::uint16_t blendShapeTargetIndex) { + pImpl->setBlendShapeTargetIndex(blendShapeTargetIndex); +} + +void SetBlendShapeTargetDeltasCommand::setDeltas(ConstArrayView deltas) { + pImpl->setDeltas(deltas); +} + +void SetBlendShapeTargetDeltasCommand::setDeltas(ConstArrayView xs, ConstArrayView ys, ConstArrayView zs) { + pImpl->setDeltas(xs, ys, zs); +} + +void SetBlendShapeTargetDeltasCommand::setVertexIndices(ConstArrayView vertexIndices) { + pImpl->setVertexIndices(vertexIndices); +} + +void SetBlendShapeTargetDeltasCommand::setMasks(ConstArrayView masks) { + pImpl->setMasks(masks); +} + +void SetBlendShapeTargetDeltasCommand::setOperation(VectorOperation operation) { + pImpl->setOperation(operation); +} + +void SetBlendShapeTargetDeltasCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/SetLODsCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/SetLODsCommand.cpp new file mode 100644 index 0000000..ce8507d --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/SetLODsCommand.cpp @@ -0,0 +1,67 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/SetLODsCommand.h" + +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class SetLODsCommand::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_}, + lods{memRes_} { + } + + void setLODs(ConstArrayView lods_) { + lods.assign(lods_.begin(), lods_.end()); + } + + void run(DNACalibDNAReaderImpl* output) { + auto buffer = makeScoped(getMemoryResource()); + auto writer = makeScoped(buffer.get(), getMemoryResource()); + writer->setFrom(output); + writer->write(); + buffer->seek(0ul); + auto reader = makeScoped(buffer.get(), + DataLayer::All, + lods.data(), + static_cast(lods.size()), + getMemoryResource()); + reader->read(); + output->setFrom(reader.get()); + } + + private: + Vector lods; + +}; + +SetLODsCommand::SetLODsCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +SetLODsCommand::SetLODsCommand(ConstArrayView lods, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setLODs(lods); +} + +SetLODsCommand::~SetLODsCommand() = default; +SetLODsCommand::SetLODsCommand(SetLODsCommand&&) = default; +SetLODsCommand& SetLODsCommand::operator=(SetLODsCommand&&) = default; + +void SetLODsCommand::setLODs(ConstArrayView lods) { + pImpl->setLODs(lods); +} + +void SetLODsCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/SetNeutralJointRotationsCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/SetNeutralJointRotationsCommand.cpp new file mode 100644 index 0000000..b293314 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/SetNeutralJointRotationsCommand.cpp @@ -0,0 +1,77 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/SetNeutralJointRotationsCommand.h" + +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class SetNeutralJointRotationsCommand::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_}, + rotations{memRes_} { + } + + void setRotations(ConstArrayView rotations_) { + rotations.assign(rotations_.begin(), rotations_.end()); + } + + void setRotations(ConstArrayView xs, ConstArrayView ys, ConstArrayView zs) { + rotations.xs.assign(xs.begin(), xs.end()); + rotations.ys.assign(ys.begin(), ys.end()); + rotations.zs.assign(zs.begin(), zs.end()); + } + + void run(DNACalibDNAReaderImpl* output) { + output->setNeutralJointRotations(ConstArrayView{rotations.xs}, + ConstArrayView{rotations.ys}, + ConstArrayView{rotations.zs}); + } + + private: + RawVector3Vector rotations; + +}; + +SetNeutralJointRotationsCommand::SetNeutralJointRotationsCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +SetNeutralJointRotationsCommand::SetNeutralJointRotationsCommand(ConstArrayView rotations, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setRotations(rotations); +} + +SetNeutralJointRotationsCommand::SetNeutralJointRotationsCommand(ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs, + MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setRotations(xs, ys, zs); +} + +SetNeutralJointRotationsCommand::~SetNeutralJointRotationsCommand() = default; +SetNeutralJointRotationsCommand::SetNeutralJointRotationsCommand(SetNeutralJointRotationsCommand&&) = default; +SetNeutralJointRotationsCommand& SetNeutralJointRotationsCommand::operator=(SetNeutralJointRotationsCommand&&) = default; + +void SetNeutralJointRotationsCommand::setRotations(ConstArrayView rotations) { + pImpl->setRotations(rotations); +} + +void SetNeutralJointRotationsCommand::setRotations(ConstArrayView xs, ConstArrayView ys, ConstArrayView zs) { + pImpl->setRotations(xs, ys, zs); +} + +void SetNeutralJointRotationsCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/SetNeutralJointTranslationsCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/SetNeutralJointTranslationsCommand.cpp new file mode 100644 index 0000000..0625923 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/SetNeutralJointTranslationsCommand.cpp @@ -0,0 +1,80 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/SetNeutralJointTranslationsCommand.h" + +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class SetNeutralJointTranslationsCommand::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_}, + translations{memRes_} { + } + + void setTranslations(ConstArrayView translations_) { + translations.assign(translations_.begin(), translations_.end()); + } + + void setTranslations(ConstArrayView xs, ConstArrayView ys, ConstArrayView zs) { + translations.xs.assign(xs.begin(), xs.end()); + translations.ys.assign(ys.begin(), ys.end()); + translations.zs.assign(zs.begin(), zs.end()); + } + + void run(DNACalibDNAReaderImpl* output) { + output->setNeutralJointTranslations(ConstArrayView{translations.xs}, + ConstArrayView{translations.ys}, + ConstArrayView{translations.zs}); + } + + private: + RawVector3Vector translations; + +}; + +SetNeutralJointTranslationsCommand::SetNeutralJointTranslationsCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +SetNeutralJointTranslationsCommand::SetNeutralJointTranslationsCommand(ConstArrayView translations, + MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setTranslations(translations); +} + +SetNeutralJointTranslationsCommand::SetNeutralJointTranslationsCommand(ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs, + MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setTranslations(xs, ys, zs); +} + +SetNeutralJointTranslationsCommand::~SetNeutralJointTranslationsCommand() = default; +SetNeutralJointTranslationsCommand::SetNeutralJointTranslationsCommand(SetNeutralJointTranslationsCommand&&) = default; +SetNeutralJointTranslationsCommand& SetNeutralJointTranslationsCommand::operator=(SetNeutralJointTranslationsCommand&&) = default; + +void SetNeutralJointTranslationsCommand::setTranslations(ConstArrayView translations) { + pImpl->setTranslations(translations); +} + +void SetNeutralJointTranslationsCommand::setTranslations(ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs) { + pImpl->setTranslations(xs, ys, zs); +} + +void SetNeutralJointTranslationsCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/SetSkinWeightsCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/SetSkinWeightsCommand.cpp new file mode 100644 index 0000000..42a4c38 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/SetSkinWeightsCommand.cpp @@ -0,0 +1,95 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/SetSkinWeightsCommand.h" + +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class SetSkinWeightsCommand::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_}, + weights{memRes_}, + jointIndices{memRes_}, + meshIndex{}, + vertexIndex{} { + } + + void setMeshIndex(std::uint16_t meshIndex_) { + meshIndex = meshIndex_; + } + + void setVertexIndex(std::uint32_t vertexIndex_) { + vertexIndex = vertexIndex_; + } + + void setWeights(ConstArrayView weights_) { + weights.assign(weights_.begin(), weights_.end()); + } + + void setJointIndices(ConstArrayView jointIndices_) { + jointIndices.assign(jointIndices_.begin(), jointIndices_.end()); + } + + void run(DNACalibDNAReaderImpl* output) { + output->setSkinWeightsValues(meshIndex, vertexIndex, weights.data(), static_cast(weights.size())); + output->setSkinWeightsJointIndices(meshIndex, vertexIndex, jointIndices.data(), + static_cast(jointIndices.size())); + } + + private: + Vector weights; + Vector jointIndices; + std::uint16_t meshIndex; + std::uint32_t vertexIndex; + +}; + +SetSkinWeightsCommand::SetSkinWeightsCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +SetSkinWeightsCommand::SetSkinWeightsCommand(std::uint16_t meshIndex, + std::uint32_t vertexIndex, + ConstArrayView weights, + ConstArrayView jointIndices, + MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setMeshIndex(meshIndex); + pImpl->setVertexIndex(vertexIndex); + pImpl->setWeights(weights); + pImpl->setJointIndices(jointIndices); +} + +SetSkinWeightsCommand::~SetSkinWeightsCommand() = default; +SetSkinWeightsCommand::SetSkinWeightsCommand(SetSkinWeightsCommand&&) = default; +SetSkinWeightsCommand& SetSkinWeightsCommand::operator=(SetSkinWeightsCommand&&) = default; + +void SetSkinWeightsCommand::setMeshIndex(std::uint16_t meshIndex) { + pImpl->setMeshIndex(meshIndex); +} + +void SetSkinWeightsCommand::setVertexIndex(std::uint32_t vertexIndex) { + pImpl->setVertexIndex(vertexIndex); +} + +void SetSkinWeightsCommand::setWeights(ConstArrayView weights) { + pImpl->setWeights(weights); +} + +void SetSkinWeightsCommand::setJointIndices(ConstArrayView jointIndices) { + pImpl->setJointIndices(jointIndices); +} + +void SetSkinWeightsCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/SetVertexPositionsCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/SetVertexPositionsCommand.cpp new file mode 100644 index 0000000..19f8bf3 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/SetVertexPositionsCommand.cpp @@ -0,0 +1,188 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/SetVertexPositionsCommand.h" + +#include "dnacalib/TypeDefs.h" +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/commands/SupportFactories.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" +#include "dnacalib/utils/FormatString.h" + +namespace dnac { + +class SetVertexPositionsCommand::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_}, + positions{memRes_}, + masks{memRes_}, + operation{VectorOperation::Interpolate}, + meshIndex{} { + } + + void setMeshIndex(std::uint16_t meshIndex_) { + meshIndex = meshIndex_; + } + + void setPositions(ConstArrayView positions_) { + positions.assign(positions_.begin(), positions_.end()); + } + + void setPositions(ConstArrayView xs, ConstArrayView ys, ConstArrayView zs) { + positions.xs.assign(xs.begin(), xs.end()); + positions.ys.assign(ys.begin(), ys.end()); + positions.zs.assign(zs.begin(), zs.end()); + } + + void setMasks(ConstArrayView masks_) { + masks.assign(masks_.begin(), masks_.end()); + } + + void setOperation(VectorOperation operation_) { + operation = operation_; + } + + void run(DNACalibDNAReaderImpl* output) { + status.reset(); + auto getWeight = WeightGetterFactory::create(masks); + auto op = OperationFactory::create(operation); + computeVertexPositions(op, getWeight, output); + } + + private: + template + void computeVertexPositions(FOperation op, FWeightGetter getWeight, DNACalibDNAReaderImpl* output) { + const auto xs = output->getVertexPositionXs(meshIndex); + const auto ys = output->getVertexPositionYs(meshIndex); + const auto zs = output->getVertexPositionZs(meshIndex); + assert((xs.size() == ys.size()) && (ys.size() == zs.size())); + RawVector3Vector result{xs, ys, zs, output->getMemoryResource()}; + // This accounts for the case when output is empty + result.resize(positions.size(), 0.0f); + if (!masks.empty() && (positions.size() != masks.size())) { + const auto message = formatString(output->getMemoryResource(), + "Number of set positions (%hu) differs from number of set masks (%hu).", + positions.size(), + masks.size()); + status.set(PositionsMasksCountMismatch, message.c_str()); + return; + } + for (std::uint32_t i = 0u; i < positions.size(); ++i) { + const float weight = getWeight(masks.data(), i); + result.xs[i] = op(result.xs[i], positions.xs[i], weight); + result.ys[i] = op(result.ys[i], positions.ys[i], weight); + result.zs[i] = op(result.zs[i], positions.zs[i], weight); + } + output->setVertexPositions(meshIndex, std::move(result)); + } + + private: + static sc::StatusProvider status; + + RawVector3Vector positions; + Vector masks; + VectorOperation operation; + std::uint16_t meshIndex; + +}; + +const sc::StatusCode SetVertexPositionsCommand::PositionsMasksCountMismatch{3201, "%s"}; + +#ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wglobal-constructors" +#endif +sc::StatusProvider SetVertexPositionsCommand::Impl::status{PositionsMasksCountMismatch}; +#ifdef __clang__ + #pragma clang diagnostic pop +#endif + +SetVertexPositionsCommand::SetVertexPositionsCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +SetVertexPositionsCommand::SetVertexPositionsCommand(std::uint16_t meshIndex, + ConstArrayView positions, + VectorOperation operation, + MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setMeshIndex(meshIndex); + pImpl->setPositions(positions); + pImpl->setOperation(operation); +} + +SetVertexPositionsCommand::SetVertexPositionsCommand(std::uint16_t meshIndex, + ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs, + VectorOperation operation, + MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setMeshIndex(meshIndex); + pImpl->setPositions(xs, ys, zs); + pImpl->setOperation(operation); +} + +SetVertexPositionsCommand::SetVertexPositionsCommand(std::uint16_t meshIndex, + ConstArrayView positions, + ConstArrayView masks, + VectorOperation operation, + MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setMeshIndex(meshIndex); + pImpl->setPositions(positions); + pImpl->setMasks(masks); + pImpl->setOperation(operation); +} + +SetVertexPositionsCommand::SetVertexPositionsCommand(std::uint16_t meshIndex, + ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs, + ConstArrayView masks, + VectorOperation operation, + MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setMeshIndex(meshIndex); + pImpl->setPositions(xs, ys, zs); + pImpl->setMasks(masks); + pImpl->setOperation(operation); +} + +SetVertexPositionsCommand::~SetVertexPositionsCommand() = default; +SetVertexPositionsCommand::SetVertexPositionsCommand(SetVertexPositionsCommand&&) = default; +SetVertexPositionsCommand& SetVertexPositionsCommand::operator=(SetVertexPositionsCommand&&) = default; + +void SetVertexPositionsCommand::setMeshIndex(std::uint16_t meshIndex) { + pImpl->setMeshIndex(meshIndex); +} + +void SetVertexPositionsCommand::setPositions(ConstArrayView positions) { + pImpl->setPositions(positions); +} + +void SetVertexPositionsCommand::setPositions(ConstArrayView xs, ConstArrayView ys, ConstArrayView zs) { + pImpl->setPositions(xs, ys, zs); +} + +void SetVertexPositionsCommand::setMasks(ConstArrayView masks) { + pImpl->setMasks(masks); +} + +void SetVertexPositionsCommand::setOperation(VectorOperation operation) { + pImpl->setOperation(operation); +} + +void SetVertexPositionsCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/SupportFactories.h b/dnacalib/DNACalib/src/dnacalib/commands/SupportFactories.h new file mode 100644 index 0000000..3452ac1 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/SupportFactories.h @@ -0,0 +1,73 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/TypeDefs.h" +#include "dnacalib/commands/VectorOperations.h" +#include "dnacalib/utils/Extd.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dnac { + +struct WeightGetterFactory { + using GetterFunc = std::function; + + static GetterFunc create(const Vector& masks) { + if (masks.empty()) { + return [](float* /*unused*/, std::size_t /*unused*/) { + return 1.0f; + }; + } + return [](float* pMasks, std::size_t index) { + return pMasks[index]; + }; + } + +}; + +struct OperationFactory { + using OpFunc = std::function; + + static OpFunc create(VectorOperation operation) { + switch (operation) { + case VectorOperation::Interpolate: { + return [](float a, float b, float weight) { + return extd::interpolate(a, b, weight); + }; + } + case VectorOperation::Add: { + return [](float a, float b, float weight) { + return a + (b * weight); + }; + } + case VectorOperation::Subtract: { + return [](float a, float b, float weight) { + return a - (b * weight); + }; + } + case VectorOperation::Multiply: { + return [](float a, float b, float weight) { + return a * (b * weight); + }; + } + default: { + // Unreachable, but needed to silence gcc warnings + assert(false); + return {}; + } + } + } + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/commands/TranslateCommand.cpp b/dnacalib/DNACalib/src/dnacalib/commands/TranslateCommand.cpp new file mode 100644 index 0000000..5adbc18 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/commands/TranslateCommand.cpp @@ -0,0 +1,87 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/commands/TranslateCommand.h" + +#include "dnacalib/CommandImplBase.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class TranslateCommand::Impl : public CommandImplBase { + private: + using Super = CommandImplBase; + + public: + explicit Impl(MemoryResource* memRes_) : + Super{memRes_}, + translation{} { + } + + void setTranslation(Vector3 translation_) { + translation = translation_; + } + + void run(DNACalibDNAReaderImpl* output) { + if (translation != Vector3{}) { + translateNeutralJoints(output); + translateVertexPositions(output); + } + } + + private: + void translateNeutralJoints(DNACalibDNAReaderImpl* output) { + for (std::uint16_t jointIndex = 0u; jointIndex < output->getJointCount(); ++jointIndex) { + const auto parentIndex = output->getJointParentIndex(jointIndex); + // Only root joints are translated + if (jointIndex == parentIndex) { + const auto joint = output->getNeutralJointTranslation(jointIndex); + output->setNeutralJointTranslation(jointIndex, joint + translation); + } + } + } + + void translateVertexPositions(DNACalibDNAReaderImpl* output) { + for (std::uint16_t meshIndex = 0u; meshIndex < output->getMeshCount(); ++meshIndex) { + const auto xs = output->getVertexPositionXs(meshIndex); + const auto ys = output->getVertexPositionYs(meshIndex); + const auto zs = output->getVertexPositionZs(meshIndex); + assert((xs.size() == ys.size()) && (ys.size() == zs.size())); + RawVector3Vector mesh{xs, ys, zs, output->getMemoryResource()}; + for (std::size_t i = 0ul; i < mesh.size(); ++i) { + mesh.xs[i] += translation.x; + mesh.ys[i] += translation.y; + mesh.zs[i] += translation.z; + } + output->setVertexPositions(meshIndex, std::move(mesh)); + } + } + + private: + Vector3 translation; + +}; + +TranslateCommand::TranslateCommand(MemoryResource* memRes) : pImpl{makeScoped(memRes)} { +} + +TranslateCommand::TranslateCommand(Vector3 translation, MemoryResource* memRes) : + pImpl{makeScoped(memRes)} { + + pImpl->setTranslation(translation); +} + +TranslateCommand::~TranslateCommand() = default; +TranslateCommand::TranslateCommand(TranslateCommand&&) = default; +TranslateCommand& TranslateCommand::operator=(TranslateCommand&&) = default; + +void TranslateCommand::setTranslation(Vector3 translation) { + pImpl->setTranslation(translation); +} + +void TranslateCommand::run(DNACalibDNAReader* output) { + pImpl->run(static_cast(output)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/BaseImpl.h b/dnacalib/DNACalib/src/dnacalib/dna/BaseImpl.h new file mode 100644 index 0000000..5114f10 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/BaseImpl.h @@ -0,0 +1,36 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/dna/DNA.h" +#include "dnacalib/types/Aliases.h" + +namespace dnac { + +class BaseImpl { + protected: + explicit BaseImpl(MemoryResource* memRes_) : + memRes{memRes_}, + dna{memRes} { + } + + ~BaseImpl() = default; + + BaseImpl(const BaseImpl&) = delete; + BaseImpl& operator=(const BaseImpl&) = delete; + + BaseImpl(BaseImpl&& rhs) = delete; + BaseImpl& operator=(BaseImpl&&) = delete; + + public: + MemoryResource* getMemoryResource() { + return memRes; + } + + protected: + MemoryResource* memRes; + DNA dna; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/DNA.h b/dnacalib/DNACalib/src/dnacalib/dna/DNA.h new file mode 100644 index 0000000..f63493b --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/DNA.h @@ -0,0 +1,892 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/TypeDefs.h" +#include "dnacalib/dna/LODMapping.h" +#include "dnacalib/dna/SurjectiveMapping.h" + +#include + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dnac { + +template +struct RawSurjectiveMapping : public SurjectiveMapping { + using SurjectiveMapping::SurjectiveMapping; + + template + void serialize(Archive& archive) { + archive.label("from"); + archive(this->from); + archive.label("to"); + archive(this->to); + } + +}; + +template +struct ExpectedValue { + T expected; + T got; + + explicit ExpectedValue(const T& value) : expected{value}, got{} { + } + + template + void load(Archive& archive) { + archive.label("value"); + archive(got); + } + + template + void save(Archive& archive) { + archive.label("value"); + archive(expected); + } + + bool matches() const { + return (expected == got); + } + +}; + +template +struct Signature { + using SignatureValueType = std::array; + + ExpectedValue value; + + explicit Signature(SignatureValueType bytes) : value{bytes} { + } + + template + void serialize(Archive& archive) { + archive.label("data"); + archive(value); + } + + bool matches() const { + return value.matches(); + } + +}; + +struct Version { + ExpectedValue generation; + ExpectedValue version; + + Version(std::uint16_t generation_, std::uint16_t version_) : + generation{generation_}, + version{version_} { + } + + template + void serialize(Archive& archive) { + archive.label("generation"); + archive(generation); + archive.label("version"); + archive(version); + } + + bool matches() const { + return (generation.matches() && version.matches()); + } + +}; + +struct SectionLookupTable { + terse::ArchiveOffset descriptor; + terse::ArchiveOffset definition; + terse::ArchiveOffset behavior; + terse::ArchiveOffset controls; + terse::ArchiveOffset joints; + terse::ArchiveOffset blendShapeChannels; + terse::ArchiveOffset animatedMaps; + terse::ArchiveOffset geometry; + + template + void serialize(Archive& archive) { + archive.label("descriptor"); + archive(descriptor); + archive.label("definition"); + archive(definition); + archive.label("behavior"); + archive(behavior); + archive.label("controls"); + archive(controls); + archive.label("joints"); + archive(joints); + archive.label("blendShapeChannels"); + archive(blendShapeChannels); + archive.label("animatedMaps"); + archive(animatedMaps); + archive.label("geometry"); + archive(geometry); + } + +}; + +struct RawCoordinateSystem { + std::uint16_t xAxis; + std::uint16_t yAxis; + std::uint16_t zAxis; + + template + void serialize(Archive& archive) { + archive.label("xAxis"); + archive(xAxis); + archive.label("yAxis"); + archive(yAxis); + archive.label("zAxis"); + archive(zAxis); + } + +}; + +struct RawLODMapping : public LODMapping { + using LODMapping::LODMapping; + + template + void serialize(Archive& archive) { + archive.label("lods"); + archive(lods); + archive.label("indices"); + archive(indices); + } + +}; + +struct RawDescriptor { + using StringPair = std::tuple, String >; + + terse::ArchiveOffset::Proxy marker; + String name; + std::uint16_t archetype; + std::uint16_t gender; + std::uint16_t age; + Vector metadata; + std::uint16_t translationUnit; + std::uint16_t rotationUnit; + RawCoordinateSystem coordinateSystem; + std::uint16_t lodCount; + std::uint16_t maxLOD; + String complexity; + String dbName; + + RawDescriptor(terse::ArchiveOffset& markerTarget, MemoryResource* memRes) : + marker{markerTarget}, + name{memRes}, + archetype{}, + gender{}, + age{}, + metadata{memRes}, + translationUnit{}, + rotationUnit{}, + coordinateSystem{}, + lodCount{}, + maxLOD{}, + complexity{memRes}, + dbName{memRes} { + } + + template + void serialize(Archive& archive) { + archive(marker); + archive.label("name"); + archive(name); + archive.label("archetype"); + archive(archetype); + archive.label("gender"); + archive(gender); + archive.label("age"); + archive(age); + archive.label("metadata"); + archive(metadata); + archive.label("translationUnit"); + archive(translationUnit); + archive.label("rotationUnit"); + archive(rotationUnit); + archive.label("coordinateSystem"); + archive(coordinateSystem); + archive.label("lodCount"); + archive(lodCount); + archive.label("maxLOD"); + archive(maxLOD); + archive.label("complexity"); + archive(complexity); + archive.label("dbName"); + archive(dbName); + } + +}; + +struct RawVector3Vector { + AlignedDynArray xs; + AlignedDynArray ys; + AlignedDynArray zs; + + explicit RawVector3Vector(MemoryResource* memRes) : + xs{memRes}, + ys{memRes}, + zs{memRes} { + } + + RawVector3Vector(std::size_t size_, float initial, MemoryResource* memRes) : + xs{size_, initial, memRes}, + ys{size_, initial, memRes}, + zs{size_, initial, memRes} { + } + + RawVector3Vector(ConstArrayView xs_, ConstArrayView ys_, ConstArrayView zs_, MemoryResource* memRes) : + xs{xs_.begin(), xs_.end(), memRes}, + ys{ys_.begin(), ys_.end(), memRes}, + zs{zs_.begin(), zs_.end(), memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("xs"); + archive(xs); + archive.label("ys"); + archive(ys); + archive.label("zs"); + archive(zs); + } + + std::size_t size() const { + assert(xs.size() == ys.size() && ys.size() == zs.size()); + return xs.size(); + } + + void reserve(std::size_t count) { + xs.resize_uninitialized(count); + ys.resize_uninitialized(count); + zs.resize_uninitialized(count); + } + + void resize(std::size_t count) { + xs.resize(count); + ys.resize(count); + zs.resize(count); + } + + void resize(std::size_t count, float value) { + xs.resize(count, value); + ys.resize(count, value); + zs.resize(count, value); + } + + void clear() { + xs.clear(); + ys.clear(); + zs.clear(); + } + + template + void assign(Iterator start, Iterator end) { + reserve(static_cast(std::distance(start, end))); + std::size_t i{}; + for (auto it = start; it != end; ++it, ++i) { + xs[i] = it->x; + ys[i] = it->y; + zs[i] = it->z; + } + } + +}; + +struct RawDefinition { + terse::ArchiveOffset::Proxy marker; + RawLODMapping lodJointMapping; + RawLODMapping lodBlendShapeMapping; + RawLODMapping lodAnimatedMapMapping; + RawLODMapping lodMeshMapping; + Vector > guiControlNames; + Vector > rawControlNames; + Vector > jointNames; + Vector > blendShapeChannelNames; + Vector > animatedMapNames; + Vector > meshNames; + RawSurjectiveMapping meshBlendShapeChannelMapping; + DynArray jointHierarchy; + RawVector3Vector neutralJointTranslations; + RawVector3Vector neutralJointRotations; + + RawDefinition(terse::ArchiveOffset& markerTarget, MemoryResource* memRes) : + marker{markerTarget}, + lodJointMapping{memRes}, + lodBlendShapeMapping{memRes}, + lodAnimatedMapMapping{memRes}, + lodMeshMapping{memRes}, + guiControlNames{memRes}, + rawControlNames{memRes}, + jointNames{memRes}, + blendShapeChannelNames{memRes}, + animatedMapNames{memRes}, + meshNames{memRes}, + meshBlendShapeChannelMapping{memRes}, + jointHierarchy{memRes}, + neutralJointTranslations{memRes}, + neutralJointRotations{memRes} { + } + + template + void serialize(Archive& archive) { + archive(marker); + archive.label("lodJointMapping"); + archive(lodJointMapping); + archive.label("lodBlendShapeMapping"); + archive(lodBlendShapeMapping); + archive.label("lodAnimatedMapMapping"); + archive(lodAnimatedMapMapping); + archive.label("lodMeshMapping"); + archive(lodMeshMapping); + archive.label("guiControlNames"); + archive(guiControlNames); + archive.label("rawControlNames"); + archive(rawControlNames); + archive.label("jointNames"); + archive(jointNames); + archive.label("blendShapeChannelNames"); + archive(blendShapeChannelNames); + archive.label("animatedMapNames"); + archive(animatedMapNames); + archive.label("meshNames"); + archive(meshNames); + archive.label("meshBlendShapeChannelMapping"); + archive(meshBlendShapeChannelMapping); + archive.label("jointHierarchy"); + archive(jointHierarchy); + archive.label("neutralJointTranslations"); + archive(neutralJointTranslations); + archive.label("neutralJointRotations"); + archive(neutralJointRotations); + } + +}; + +struct RawConditionalTable { + DynArray inputIndices; + DynArray outputIndices; + DynArray fromValues; + DynArray toValues; + DynArray slopeValues; + DynArray cutValues; + + explicit RawConditionalTable(MemoryResource* memRes) : + inputIndices{memRes}, + outputIndices{memRes}, + fromValues{memRes}, + toValues{memRes}, + slopeValues{memRes}, + cutValues{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("inputIndices"); + archive(inputIndices); + archive.label("outputIndices"); + archive(outputIndices); + archive.label("fromValues"); + archive(fromValues); + archive.label("toValues"); + archive(toValues); + archive.label("slopeValues"); + archive(slopeValues); + archive.label("cutValues"); + archive(cutValues); + } + +}; + +struct RawPSDMatrix { + DynArray rows; + DynArray columns; + DynArray values; + + explicit RawPSDMatrix(MemoryResource* memRes) : + rows{memRes}, + columns{memRes}, + values{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("rows"); + archive(rows); + archive.label("columns"); + archive(columns); + archive.label("values"); + archive(values); + } + +}; + +struct RawControls { + std::uint16_t psdCount; + RawConditionalTable conditionals; + RawPSDMatrix psds; + + explicit RawControls(MemoryResource* memRes) : + psdCount{}, + conditionals{memRes}, + psds{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("psdCount"); + archive(psdCount); + archive.label("conditionals"); + archive(conditionals); + archive.label("psds"); + archive(psds); + } + +}; + +struct RawJointGroup { + // Row count of each LOD + // 12, 9, 3, + // | | + LOD-2 contains first 3 rows + // | + LOD-1 contains first 9 rows + // + LOD-0 contains first 12 rows + DynArray lods; + // Sub-matrix col -> input vector + DynArray inputIndices; + // Sub-matrix row -> output vector + DynArray outputIndices; + // Non-zero values of all sub-matrices + AlignedDynArray values; + + DynArray jointIndices; + + explicit RawJointGroup(MemoryResource* memRes) : + lods{memRes}, + inputIndices{memRes}, + outputIndices{memRes}, + values{memRes}, + jointIndices{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("lods"); + archive(lods); + archive.label("inputIndices"); + archive(inputIndices); + archive.label("outputIndices"); + archive(outputIndices); + archive.label("values"); + archive(values); + archive.label("jointIndices"); + archive(jointIndices); + } + +}; + +struct RawJoints { + std::uint16_t rowCount; + std::uint16_t colCount; + Vector jointGroups; + + explicit RawJoints(MemoryResource* memRes) : + rowCount{}, + colCount{}, + jointGroups{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("rowCount"); + archive(rowCount); + archive.label("colCount"); + archive(colCount); + archive.label("jointGroups"); + archive(jointGroups); + } + +}; + +struct RawBlendShapeChannels { + DynArray lods; + DynArray inputIndices; + DynArray outputIndices; + + explicit RawBlendShapeChannels(MemoryResource* memRes) : + lods{memRes}, + inputIndices{memRes}, + outputIndices{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("lods"); + archive(lods); + archive.label("inputIndices"); + archive(inputIndices); + archive.label("outputIndices"); + archive(outputIndices); + } + +}; + +struct RawAnimatedMaps { + DynArray lods; + RawConditionalTable conditionals; + + explicit RawAnimatedMaps(MemoryResource* memRes) : + lods{memRes}, + conditionals{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("lods"); + archive(lods); + archive.label("conditionals"); + archive(conditionals); + } + +}; + +struct RawBehavior { + terse::ArchiveOffset::Proxy marker; + + terse::ArchiveOffset::Proxy controlsMarker; + RawControls controls; + + terse::ArchiveOffset::Proxy jointsMarker; + RawJoints joints; + + terse::ArchiveOffset::Proxy blendShapeChannelsMarker; + RawBlendShapeChannels blendShapeChannels; + + terse::ArchiveOffset::Proxy animatedMapsMarker; + RawAnimatedMaps animatedMaps; + + RawBehavior(terse::ArchiveOffset& markerTarget, + terse::ArchiveOffset& controlsMarkerTarget, + terse::ArchiveOffset& jointsMarkerTarget, + terse::ArchiveOffset& blendShapeChannelsMarkerTarget, + terse::ArchiveOffset& animatedMapsMarkerTarget, + MemoryResource* memRes) : + marker{markerTarget}, + controlsMarker{controlsMarkerTarget}, + controls{memRes}, + jointsMarker{jointsMarkerTarget}, + joints{memRes}, + blendShapeChannelsMarker{blendShapeChannelsMarkerTarget}, + blendShapeChannels{memRes}, + animatedMapsMarker{animatedMapsMarkerTarget}, + animatedMaps{memRes} { + } + + template + void serialize(Archive& archive) { + archive(marker, controlsMarker); + archive.label("controls"); + archive(controls); + archive(jointsMarker); + archive.label("joints"); + archive(joints); + archive(blendShapeChannelsMarker); + archive.label("blendShapeChannels"); + archive(blendShapeChannels); + archive(animatedMapsMarker); + archive.label("animatedMaps"); + archive(animatedMaps); + } + +}; + +struct RawTextureCoordinateVector { + DynArray us; + DynArray vs; + + explicit RawTextureCoordinateVector(MemoryResource* memRes) : + us{memRes}, + vs{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("us"); + archive(us); + archive.label("vs"); + archive(vs); + } + + std::size_t size() const { + assert(us.size() == vs.size()); + return us.size(); + } + + void clear() { + us.clear(); + vs.clear(); + } + +}; + +struct RawVertexLayoutVector { + DynArray positions; + DynArray textureCoordinates; + DynArray normals; + + explicit RawVertexLayoutVector(MemoryResource* memRes) : + positions{memRes}, + textureCoordinates{memRes}, + normals{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("positions"); + archive(positions); + archive.label("textureCoordinates"); + archive(textureCoordinates); + archive.label("normals"); + archive(normals); + } + + std::size_t size() const { + assert(positions.size() == textureCoordinates.size() && textureCoordinates.size() == normals.size()); + return positions.size(); + } + + void clear() { + positions.clear(); + textureCoordinates.clear(); + normals.clear(); + } + +}; + +struct RawFace { + DynArray layoutIndices; + + explicit RawFace(MemoryResource* memRes) : + layoutIndices{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("layoutIndices"); + archive(layoutIndices); + } + +}; + +struct RawVertexSkinWeights { + AlignedDynArray weights; + DynArray jointIndices; + + explicit RawVertexSkinWeights(MemoryResource* memRes) : + weights{memRes}, + jointIndices{memRes} { + } + + template + void serialize(Archive& archive) { + archive.label("weights"); + archive(weights); + archive.label("jointIndices"); + archive(jointIndices); + } + +}; + +struct RawBlendShapeTarget { + RawVector3Vector deltas; + DynArray vertexIndices; + std::uint16_t blendShapeChannelIndex; + + explicit RawBlendShapeTarget(MemoryResource* memRes) : + deltas{memRes}, + vertexIndices{memRes}, + blendShapeChannelIndex{} { + } + + template + void serialize(Archive& archive) { + archive.label("deltas"); + archive(deltas); + archive.label("vertexIndices"); + archive(vertexIndices); + archive.label("blendShapeChannelIndex"); + archive(blendShapeChannelIndex); + } + +}; + +struct RawMesh { + terse::ArchiveOffset offset; + RawVector3Vector positions; + RawTextureCoordinateVector textureCoordinates; + RawVector3Vector normals; + RawVertexLayoutVector layouts; + Vector faces; + std::uint16_t maximumInfluencePerVertex; + Vector skinWeights; + Vector blendShapeTargets; + terse::ArchiveOffset::Proxy marker; + + explicit RawMesh(MemoryResource* memRes) : + offset{}, + positions{memRes}, + textureCoordinates{memRes}, + normals{memRes}, + layouts{memRes}, + faces{memRes}, + maximumInfluencePerVertex{}, + skinWeights{memRes}, + blendShapeTargets{memRes}, + marker{offset} { + } + + template + void serialize(Archive& archive) { + archive.label("offset"); + archive(offset); + archive.label("positions"); + archive(positions); + archive.label("textureCoordinates"); + archive(textureCoordinates); + archive.label("normals"); + archive(normals); + archive.label("layouts"); + archive(layouts); + archive.label("faces"); + archive(faces); + archive.label("maximumInfluencePerVertex"); + archive(maximumInfluencePerVertex); + archive.label("skinWeights"); + archive(skinWeights); + archive.label("blendShapeTargets"); + archive(blendShapeTargets); + archive(marker); + } + +}; + +struct RawGeometry { + terse::ArchiveOffset::Proxy marker; + Vector meshes; + + RawGeometry(terse::ArchiveOffset& markerTarget, MemoryResource* memRes) : + marker{markerTarget}, + meshes{memRes} { + } + + template + void serialize(Archive& archive) { + archive(marker); + archive.label("meshes"); + archive(meshes); + } + +}; + +struct DNA { + MemoryResource* memRes; + Signature<3> signature{{'D', 'N', 'A'}}; + Version version{2, 1}; + SectionLookupTable sections; + RawDescriptor descriptor; + RawDefinition definition; + RawBehavior behavior; + RawGeometry geometry; + Signature<3> eof{{'A', 'N', 'D'}}; + + explicit DNA(MemoryResource* memRes_) : + memRes{memRes_}, + sections{}, + descriptor{sections.descriptor, memRes}, + definition{sections.definition, memRes}, + behavior{sections.behavior, + sections.controls, + sections.joints, + sections.blendShapeChannels, + sections.animatedMaps, + memRes}, + geometry{sections.geometry, memRes} { + } + + template + void load(Archive& archive) { + archive.label("signature"); + archive(signature); + archive.label("version"); + archive(version); + if (signature.matches() && version.matches()) { + archive.label("sections"); + archive(sections); + archive.label("descriptor"); + archive(descriptor); + archive.label("definition"); + archive(definition); + archive.label("behavior"); + archive(behavior); + archive.label("geometry"); + archive(geometry); + archive.label("eof"); + archive(eof); + assert(eof.matches()); + } + } + + template + void save(Archive& archive) { + archive.label("signature"); + archive(signature); + archive.label("version"); + archive(version); + archive.label("sections"); + archive(sections); + archive.label("descriptor"); + archive(descriptor); + archive.label("definition"); + archive(definition); + archive.label("behavior"); + archive(behavior); + archive.label("geometry"); + archive(geometry); + archive.label("eof"); + archive(eof); + } + + void unloadDefinition() { + definition = RawDefinition{sections.definition, memRes}; + } + + void unloadBehavior() { + behavior = + RawBehavior{sections.behavior, sections.controls, sections.joints, sections.blendShapeChannels, sections.animatedMaps, + memRes}; + } + + void unloadGeometry() { + geometry = RawGeometry{sections.geometry, memRes}; + } + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/DNACalibDNAReaderImpl.cpp b/dnacalib/DNACalib/src/dnacalib/dna/DNACalibDNAReaderImpl.cpp new file mode 100644 index 0000000..661c38b --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/DNACalibDNAReaderImpl.cpp @@ -0,0 +1,285 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/dna/DNACalibDNAReaderImpl.h" + +#include "dnacalib/TypeDefs.h" +#include "dnacalib/dna/filters/AnimatedMapFilter.h" +#include "dnacalib/dna/filters/BlendShapeFilter.h" +#include "dnacalib/dna/filters/JointFilter.h" +#include "dnacalib/dna/filters/MeshFilter.h" +#include "dnacalib/utils/Extd.h" + +namespace dnac { + +DNACalibDNAReader::~DNACalibDNAReader() = default; + +DNACalibDNAReaderImpl::~DNACalibDNAReaderImpl() = default; + +DNACalibDNAReader* DNACalibDNAReader::create(MemoryResource* memRes) { + PolyAllocator alloc{memRes}; + return alloc.newObject(memRes); +} + +DNACalibDNAReader* DNACalibDNAReader::create(const dna::Reader* reader, MemoryResource* memRes) { + auto instance = static_cast(create(memRes)); + instance->setFrom(reader, dna::DataLayer::All, memRes); + return instance; +} + +void DNACalibDNAReader::destroy(DNACalibDNAReader* instance) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + auto ptr = static_cast(instance); + PolyAllocator alloc{ptr->getMemoryResource()}; + alloc.deleteObject(ptr); +} + +DNACalibDNAReaderImpl::DNACalibDNAReaderImpl(MemoryResource* memRes_) : + BaseImpl{memRes_}, + ReaderImpl{memRes_}, + WriterImpl{memRes_} { +} + +void DNACalibDNAReaderImpl::setLODCount(std::uint16_t lodCount) { + dna.descriptor.lodCount = lodCount; +} + +void DNACalibDNAReaderImpl::setNeutralJointTranslations(ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs) { + dna.definition.neutralJointTranslations.xs.assign(xs.begin(), xs.end()); + dna.definition.neutralJointTranslations.ys.assign(ys.begin(), ys.end()); + dna.definition.neutralJointTranslations.zs.assign(zs.begin(), zs.end()); +} + +void DNACalibDNAReaderImpl::setNeutralJointTranslations(RawVector3Vector&& translations) { + dna.definition.neutralJointTranslations = std::move(translations); +} + +void DNACalibDNAReaderImpl::setNeutralJointTranslation(std::uint16_t index, const Vector3& translation) { + if (index >= dna.definition.neutralJointTranslations.size()) { + dna.definition.neutralJointTranslations.xs.resize(index + 1ul, 0.0f); + dna.definition.neutralJointTranslations.ys.resize(index + 1ul, 0.0f); + dna.definition.neutralJointTranslations.zs.resize(index + 1ul, 0.0f); + } + dna.definition.neutralJointTranslations.xs[index] = translation.x; + dna.definition.neutralJointTranslations.ys[index] = translation.y; + dna.definition.neutralJointTranslations.zs[index] = translation.z; +} + +void DNACalibDNAReaderImpl::setNeutralJointRotations(ConstArrayView xs, ConstArrayView ys, + ConstArrayView zs) { + dna.definition.neutralJointRotations.xs.assign(xs.begin(), xs.end()); + dna.definition.neutralJointRotations.ys.assign(ys.begin(), ys.end()); + dna.definition.neutralJointRotations.zs.assign(zs.begin(), zs.end()); +} + +void DNACalibDNAReaderImpl::setNeutralJointRotations(RawVector3Vector&& rotations) { + dna.definition.neutralJointRotations = std::move(rotations); +} + +void DNACalibDNAReaderImpl::setNeutralJointRotation(std::uint16_t index, const Vector3& rotation) { + if (index >= dna.definition.neutralJointTranslations.size()) { + dna.definition.neutralJointRotations.xs.resize(index + 1ul, 0.0f); + dna.definition.neutralJointRotations.ys.resize(index + 1ul, 0.0f); + dna.definition.neutralJointRotations.zs.resize(index + 1ul, 0.0f); + } + dna.definition.neutralJointRotations.xs[index] = rotation.x; + dna.definition.neutralJointRotations.ys[index] = rotation.y; + dna.definition.neutralJointRotations.zs[index] = rotation.z; +} + +void DNACalibDNAReaderImpl::setJointGroupValues(std::uint16_t jointGroupIndex, AlignedDynArray&& values) { + ensureHasSize(dna.behavior.joints.jointGroups, jointGroupIndex + 1ul, memRes); + dna.behavior.joints.jointGroups[jointGroupIndex].values = std::move(values); +} + +void DNACalibDNAReaderImpl::setVertexPositions(std::uint16_t meshIndex, + ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + dna.geometry.meshes[meshIndex].positions.xs.assign(xs.begin(), xs.end()); + dna.geometry.meshes[meshIndex].positions.ys.assign(ys.begin(), ys.end()); + dna.geometry.meshes[meshIndex].positions.zs.assign(zs.begin(), zs.end()); +} + +void DNACalibDNAReaderImpl::setVertexPositions(std::uint16_t meshIndex, RawVector3Vector&& positions) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + dna.geometry.meshes[meshIndex].positions = std::move(positions); +} + +void DNACalibDNAReaderImpl::setBlendShapeTargetDeltas(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + ensureHasSize(dna.geometry.meshes[meshIndex].blendShapeTargets, blendShapeTargetIndex + 1ul, memRes); + dna.geometry.meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas.xs.assign(xs.begin(), xs.end()); + dna.geometry.meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas.ys.assign(ys.begin(), ys.end()); + dna.geometry.meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas.zs.assign(zs.begin(), zs.end()); +} + +void DNACalibDNAReaderImpl::setBlendShapeTargetDeltas(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + RawVector3Vector&& deltas) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + ensureHasSize(dna.geometry.meshes[meshIndex].blendShapeTargets, blendShapeTargetIndex + 1ul, memRes); + dna.geometry.meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas = std::move(deltas); +} + +void DNACalibDNAReaderImpl::setBlendShapeTargetVertexIndices(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + ConstArrayView vertexIndices) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + ensureHasSize(dna.geometry.meshes[meshIndex].blendShapeTargets, blendShapeTargetIndex + 1ul, memRes); + dna.geometry.meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].vertexIndices.assign(vertexIndices.begin(), + vertexIndices.end()); +} + +void DNACalibDNAReaderImpl::pruneBlendShapeTargets(float threshold) { + const float threshold2 = threshold * threshold; + for (auto& mesh : dna.geometry.meshes) { + for (auto& bst : mesh.blendShapeTargets) { + std::size_t di{}; + for (std::size_t si{}; si < bst.deltas.size(); ++si) { + const float magnitude2 = (bst.deltas.xs[si] * bst.deltas.xs[si]) + + (bst.deltas.ys[si] * bst.deltas.ys[si]) + + (bst.deltas.zs[si] * bst.deltas.zs[si]); + if (magnitude2 > threshold2) { + bst.deltas.xs[di] = bst.deltas.xs[si]; + bst.deltas.ys[di] = bst.deltas.ys[si]; + bst.deltas.zs[di] = bst.deltas.zs[si]; + bst.vertexIndices[di] = bst.vertexIndices[si]; + ++di; + } + } + bst.deltas.resize(di); + bst.vertexIndices.resize(di); + } + } +} + +void DNACalibDNAReaderImpl::removeMeshes(ConstArrayView meshIndices) { + // Filter and remap mesh names and indices + dna.definition.lodMeshMapping.filterIndices([meshIndices](std::uint16_t value) { + return (!extd::contains(meshIndices, value)); + }); + + // Collect all distinct element position indices that are referenced by the present LODs + UnorderedSet allowedMeshIndices = dna.definition.lodMeshMapping.getCombinedDistinctIndices(memRes); + + MeshFilter meshFilter{memRes}; + meshFilter.configure(static_cast(dna.definition.meshNames.size()), std::move(allowedMeshIndices)); + meshFilter.apply(dna.definition); + // Remove mesh geometry + extd::filter(dna.geometry.meshes, [&meshFilter](const RawMesh& /*unused*/, std::size_t index) { + return meshFilter.passes(static_cast(index)); + }); + // Repopulate cache of (mesh, blend shape) mapping per LOD + cache.meshBlendShapeMappingIndices.reset(); + cache.populate(this); +} + +void DNACalibDNAReaderImpl::removeJoints(ConstArrayView jointIndices) { + // To find joints that are not in any LOD, find the joints that are not in LOD 0 (the current max LOD, at index 0), as it + // contains joints from all lower LODs. + Vector jointsNotInLOD0{memRes}; + const auto jointIndicesForLOD0 = dna.definition.lodJointMapping.getIndices(0); + for (std::uint16_t idx = 0; idx < dna.definition.jointNames.size(); ++idx) { + if (extd::contains(jointIndices, idx)) { + // Do not add the joint to remove. + continue; + } + if (std::find(jointIndicesForLOD0.begin(), jointIndicesForLOD0.end(), idx) == jointIndicesForLOD0.end()) { + jointsNotInLOD0.push_back(idx); + } + } + // Filter and remap joint names and indices + dna.definition.lodJointMapping.filterIndices([jointIndices](std::uint16_t value) { + return (!extd::contains(jointIndices, value)); + }); + // Collect all distinct element position indices that are referenced by the present LODs + UnorderedSet allowedJointIndices = dna.definition.lodJointMapping.getCombinedDistinctIndices(memRes); + + // In order to keep joints that are not in any LOD, add them to the list of joints to keep when filtering. + allowedJointIndices.insert(jointsNotInLOD0.begin(), jointsNotInLOD0.end()); + + JointFilter jointFilter{memRes}; + jointFilter.configure(static_cast(dna.definition.jointNames.size()), std::move(allowedJointIndices)); + jointFilter.apply(dna.definition); + // Filter and remap related joint behavior data + jointFilter.apply(dna.behavior); + // Remove skin weights related to this joint and normalize them + for (auto& mesh : dna.geometry.meshes) { + for (auto& skinWeights : mesh.skinWeights) { + jointFilter.apply(skinWeights); + } + } +} + +void DNACalibDNAReaderImpl::removeJointAnimations(ConstArrayView jointIndices) { + UnorderedSet allowedJointIndices = dna.definition.lodJointMapping.getCombinedDistinctIndices(memRes); + for (const auto jointIndex : jointIndices) { + allowedJointIndices.erase(jointIndex); + } + + JointFilter jointFilter{memRes}; + jointFilter.configure(static_cast(dna.definition.jointNames.size()), + std::move(allowedJointIndices), + JointFilter::Option::AnimationOnly); + jointFilter.apply(dna.behavior); +} + +void DNACalibDNAReaderImpl::removeBlendShapes(ConstArrayView blendShapeIndices) { + // Filter blend shapes from LOD blend shape mapping + dna.definition.lodBlendShapeMapping.filterIndices([blendShapeIndices](std::uint16_t value) { + return (!extd::contains(blendShapeIndices, value)); + }); + + Vector blendShapeLODs{dna.definition.lodBlendShapeMapping.getLODCount(), 0u, memRes}; + for (std::uint16_t lodIndex = 0; lodIndex < blendShapeLODs.size(); ++lodIndex) { + blendShapeLODs[lodIndex] = static_cast(dna.definition.lodBlendShapeMapping.getIndices(lodIndex).size()); + } + + UnorderedSet allowedBlendShapeIndices = dna.definition.lodBlendShapeMapping.getCombinedDistinctIndices(memRes); + BlendShapeFilter blendShapeFilter{memRes}; + blendShapeFilter.configure(static_cast(dna.definition.blendShapeChannelNames.size()), + std::move(allowedBlendShapeIndices), std::move(blendShapeLODs)); + + // Remove blend shape from definition + blendShapeFilter.apply(dna.definition); + + // Remove blend shape from behavior + blendShapeFilter.apply(dna.behavior); + + // Remove blend shape from geometry + for (auto& mesh : dna.geometry.meshes) { + blendShapeFilter.apply(mesh); + } +} + +void DNACalibDNAReaderImpl::removeAnimatedMaps(ConstArrayView animatedMapIndices) { + // Keep track of animated map indices per LOD before filtering and remapping + Matrix lodIndices{dna.definition.lodAnimatedMapMapping.getLODCount(), Vector{memRes}, memRes}; + for (std::uint16_t lodIndex = 0; lodIndex < static_cast(lodIndices.size()); ++lodIndex) { + const auto& indices = dna.definition.lodAnimatedMapMapping.getIndices(lodIndex); + lodIndices[lodIndex].assign(indices.begin(), indices.end()); + } + // Filter and remap animated map names and indices + dna.definition.lodAnimatedMapMapping.filterIndices([animatedMapIndices](std::uint16_t value) { + return (!extd::contains(animatedMapIndices, value)); + }); + + // Collect all distinct element position indices that are referenced by the present LODs + UnorderedSet allowedAnimatedMapIndices = + dna.definition.lodAnimatedMapMapping.getCombinedDistinctIndices(memRes); + + AnimatedMapFilter animatedMapFilter{memRes}; + animatedMapFilter.configure(static_cast(dna.definition.animatedMapNames.size()), + std::move(allowedAnimatedMapIndices), std::move(lodIndices)); + animatedMapFilter.apply(dna.definition); + animatedMapFilter.apply(dna.behavior); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/DNACalibDNAReaderImpl.h b/dnacalib/DNACalib/src/dnacalib/dna/DNACalibDNAReaderImpl.h new file mode 100644 index 0000000..01e3613 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/DNACalibDNAReaderImpl.h @@ -0,0 +1,71 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/dna/DNACalibDNAReader.h" +#include "dnacalib/dna/ReaderImpl.h" +#include "dnacalib/dna/WriterImpl.h" + +#include +#include + +namespace dnac { + +class DNACalibDNAReaderImpl : public ReaderImpl, public WriterImpl { + public: + explicit DNACalibDNAReaderImpl(MemoryResource* memRes_); + + virtual ~DNACalibDNAReaderImpl(); + + DNACalibDNAReaderImpl(const DNACalibDNAReaderImpl&) = delete; + DNACalibDNAReaderImpl& operator=(const DNACalibDNAReaderImpl&) = delete; + + DNACalibDNAReaderImpl(DNACalibDNAReaderImpl&&) = delete; + DNACalibDNAReaderImpl& operator=(DNACalibDNAReaderImpl&&) = delete; + + using WriterImpl::setLODCount; + void setLODCount(std::uint16_t lodCount); + + using WriterImpl::setNeutralJointTranslations; + void setNeutralJointTranslations(ConstArrayView xs, ConstArrayView ys, ConstArrayView zs); + void setNeutralJointTranslations(RawVector3Vector&& translations); + void setNeutralJointTranslation(std::uint16_t index, const Vector3& translation); + + using WriterImpl::setNeutralJointRotations; + void setNeutralJointRotations(ConstArrayView xs, ConstArrayView ys, ConstArrayView zs); + void setNeutralJointRotations(RawVector3Vector&& rotations); + void setNeutralJointRotation(std::uint16_t index, const Vector3& rotation); + + using WriterImpl::setJointGroupValues; + void setJointGroupValues(std::uint16_t jointGroupIndex, AlignedDynArray&& values); + + using WriterImpl::setVertexPositions; + void setVertexPositions(std::uint16_t meshIndex, + ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs); + void setVertexPositions(std::uint16_t meshIndex, RawVector3Vector&& positions); + + using WriterImpl::setBlendShapeTargetDeltas; + void setBlendShapeTargetDeltas(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + ConstArrayView xs, + ConstArrayView ys, + ConstArrayView zs); + void setBlendShapeTargetDeltas(std::uint16_t meshIndex, std::uint16_t blendShapeTargetIndex, RawVector3Vector&& deltas); + + using WriterImpl::setBlendShapeTargetVertexIndices; + void setBlendShapeTargetVertexIndices(std::uint16_t meshIndex, std::uint16_t blendShapeTargetIndex, + ConstArrayView vertexIndices); + + void pruneBlendShapeTargets(float threshold); + + void removeMeshes(ConstArrayView meshIndices); + void removeJoints(ConstArrayView jointIndices); + void removeJointAnimations(ConstArrayView jointIndex); + void removeBlendShapes(ConstArrayView blendShapeIndices); + void removeAnimatedMaps(ConstArrayView animatedMapIndices); + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/DenormalizedData.h b/dnacalib/DNACalib/src/dnacalib/dna/DenormalizedData.h new file mode 100644 index 0000000..0486019 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/DenormalizedData.h @@ -0,0 +1,79 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/dna/LODMapping.h" +#include "dnacalib/types/Aliases.h" +#include "dnacalib/utils/Extd.h" + +#include +#include + +namespace dnac { + +template +struct DenormalizedData { + LODMapping jointVariableAttributeIndices; + LODMapping meshBlendShapeMappingIndices; + + explicit DenormalizedData(MemoryResource* memRes) : + jointVariableAttributeIndices{memRes}, + meshBlendShapeMappingIndices{memRes} { + } + + void populate(const Reader* source) { + populateJointVariableAttributeIndices(source, jointVariableAttributeIndices); + populateMeshBlendShapeMappingIndices(source, meshBlendShapeMappingIndices); + } + + private: + void populateJointVariableAttributeIndices(const dna::Reader* source, LODMapping& destination) { + // Prepare storage for all available LODs + const auto lodCount = source->getLODCount(); + destination.setLODCount(lodCount); + // Concatenate all output indices for each LOD + for (std::uint16_t i = 0u; i < source->getJointGroupCount(); ++i) { + const auto outputIndices = source->getJointGroupOutputIndices(i); + const auto lodSizes = source->getJointGroupLODs(i); + assert(lodSizes.size() == lodCount); + for (std::uint16_t lod = 0u; lod < lodCount; ++lod) { + // In this case, each LOD has a distinct set of indices, so the LOD and Index parameters + // are the same for all LODs + destination.addIndices(lod, outputIndices.data(), lodSizes[lod]); + destination.associateLODWithIndices(lod, lod); + } + } + } + + void populateMeshBlendShapeMappingIndices(const dna::Reader* source, LODMapping& destination) { + // Prepare storage for all available LODs + const auto lodCount = source->getLODCount(); + destination.setLODCount(lodCount); + // Include only those mapping indices which are present in the already filtered + // mesh and blendshape LOD mapping + for (std::uint16_t lod = 0u; lod < lodCount; ++lod) { + const auto meshIndices = source->getMeshIndicesForLOD(lod); + const auto blendShapeIndices = source->getBlendShapeChannelIndicesForLOD(lod); + + auto isMappingNeeded = [&meshIndices, &blendShapeIndices](const dna::MeshBlendShapeChannelMapping& mapping) { + const bool meshNeeded = extd::contains(meshIndices, mapping.meshIndex); + const bool blendShapeNeeded = extd::contains(blendShapeIndices, mapping.blendShapeChannelIndex); + return (meshNeeded && blendShapeNeeded); + }; + + for (std::uint16_t i = 0u; i < source->getMeshBlendShapeChannelMappingCount(); ++i) { + const auto mapping = source->getMeshBlendShapeChannelMapping(i); + if (isMappingNeeded(mapping)) { + // In this case, each LOD has a distinct set of indices, so the LOD and Index parameters + // are the same for all LODs + destination.addIndices(lod, &i, static_cast(1)); + } + } + + destination.associateLODWithIndices(lod, lod); + } + } + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/LODConstraint.cpp b/dnacalib/DNACalib/src/dnacalib/dna/LODConstraint.cpp new file mode 100644 index 0000000..c2c647b --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/LODConstraint.cpp @@ -0,0 +1,65 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/dna/LODConstraint.h" + +#include "dnacalib/utils/Extd.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dnac { + +LODConstraint::LODConstraint(std::uint16_t maxLOD, std::uint16_t minLOD, MemoryResource* memRes) : lods{memRes} { + assert(maxLOD <= minLOD); + lods.resize(static_cast(minLOD - maxLOD) + 1ul); + std::iota(lods.begin(), lods.end(), maxLOD); +} + +LODConstraint::LODConstraint(ConstArrayView lods_, MemoryResource* memRes) : lods{lods_.begin(), lods_.end(), + memRes} { + std::sort(lods.begin(), lods.end()); +} + +bool LODConstraint::hasImpactOn(std::uint16_t lodCount) const { + std::uint16_t lod = {}; + for (auto it = lods.begin(); (it != lods.end()) && (lod < lodCount); ++it) { + lod = static_cast(lod + static_cast(lod == *it)); + } + return (lod != lodCount); +} + +std::uint16_t LODConstraint::getMaxLOD() const { + return (lods.empty() ? std::uint16_t{} : lods.front()); +} + +std::uint16_t LODConstraint::getMinLOD() const { + return (lods.empty() ? std::uint16_t{} : lods.back()); +} + +std::uint16_t LODConstraint::getLODCount() const { + return static_cast(lods.size()); +} + +void LODConstraint::clampTo(std::uint16_t lodCount) { + extd::filter(lods, [lodCount](std::uint16_t lod, std::size_t /*unused*/) { + return lod < lodCount; + }); +} + +void LODConstraint::applyTo(Vector& unconstrainedLODs) const { + extd::filter(unconstrainedLODs, extd::byPosition(lods)); +} + +void LODConstraint::applyTo(DynArray& unconstrainedLODs) const { + extd::filter(unconstrainedLODs, extd::byPosition(lods)); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/LODConstraint.h b/dnacalib/DNACalib/src/dnacalib/dna/LODConstraint.h new file mode 100644 index 0000000..47db20f --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/LODConstraint.h @@ -0,0 +1,29 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/TypeDefs.h" + +#include + +namespace dnac { + +class LODConstraint { + public: + LODConstraint(std::uint16_t maxLOD, std::uint16_t minLOD, MemoryResource* memRes); + LODConstraint(ConstArrayView lods, MemoryResource* memRes); + + bool hasImpactOn(std::uint16_t lodCount) const; + std::uint16_t getMaxLOD() const; + std::uint16_t getMinLOD() const; + std::uint16_t getLODCount() const; + void clampTo(std::uint16_t lodCount); + void applyTo(Vector& unconstrainedLODs) const; + void applyTo(DynArray& unconstrainedLODs) const; + + private: + Vector lods; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/LODMapping.cpp b/dnacalib/DNACalib/src/dnacalib/dna/LODMapping.cpp new file mode 100644 index 0000000..66d3031 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/LODMapping.cpp @@ -0,0 +1,137 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/dna/LODMapping.h" + +#include "dnacalib/TypeDefs.h" +#include "dnacalib/dna/LODConstraint.h" +#include "dnacalib/utils/Extd.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dnac { + +LODMapping::LODMapping(MemoryResource* memRes_) : + lods{memRes_}, + indices{memRes_} { +} + +std::uint16_t LODMapping::getLODCount() const { + return static_cast(lods.size()); +} + +void LODMapping::resetIndices() { + indices.clear(); +} + +void LODMapping::resetLODs() { + lods.clear(); +} + +void LODMapping::reset() { + lods.clear(); + indices.clear(); +} + +void LODMapping::setLODCount(std::uint16_t lodCount) { + reset(); + lods.resize(lodCount); + indices.resize(lodCount); +} + +void LODMapping::discardLODs(const LODConstraint& lodConstraint) { + lodConstraint.applyTo(lods); + cleanupIndices(); +} + +void LODMapping::cleanupIndices() { + for (std::size_t i = indices.size(); i > 0ul; --i) { + const auto idx = (i - 1ul); + if (std::find(lods.begin(), lods.end(), idx) == lods.end()) { + indices.erase(extd::advanced(indices.begin(), idx)); + for (auto& l2i : lods) { + if (l2i > idx) { + --l2i; + } + } + } + } +} + +ConstArrayView LODMapping::getIndices(std::uint16_t lod) const { + if (lod >= lods.size()) { + return {}; + } + assert(lods[lod] < indices.size()); + const auto it = extd::advanced(indices.cbegin(), lods[lod]); + return (it == indices.cend() ? ConstArrayView{} : ConstArrayView{it->data(), it->size()}); +} + +std::uint16_t LODMapping::getIndexListCount() const { + return static_cast(indices.size()); +} + +void LODMapping::clearIndices(std::uint16_t index) { + if (index < indices.size()) { + indices[index].clear(); + } else { + indices.resize(index + 1ul); + } +} + +void LODMapping::addIndices(std::uint16_t index, const std::uint16_t* source, std::uint16_t count) { + if (index >= indices.size()) { + indices.resize(index + 1ul); + } + indices[index].reserve(count); + indices[index].insert(indices[index].end(), source, source + count); +} + +void LODMapping::mapIndices(std::function mapper) { + for (auto& row : indices) { + for (auto& value : row) { + value = mapper(value); + } + } +} + +void LODMapping::filterIndices(std::function filterer) { + for (auto& row : indices) { + for (auto it = row.begin(); it != row.end();) { + if (filterer(*it)) { + ++it; + } else { + it = row.erase(it); + } + } + } +} + +void LODMapping::associateLODWithIndices(std::uint16_t lod, std::uint16_t index) { + if (lod >= lods.size()) { + lods.resize(lod + 1ul); + } + if (index >= indices.size()) { + indices.resize(index + 1ul); + } + lods[lod] = index; +} + +UnorderedSet LODMapping::getCombinedDistinctIndices(MemoryResource* memRes) const { + UnorderedSet distinctIndices{memRes}; + for (const auto& row : indices) { + distinctIndices.insert(row.begin(), row.end()); + } + return distinctIndices; +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/LODMapping.h b/dnacalib/DNACalib/src/dnacalib/dna/LODMapping.h new file mode 100644 index 0000000..8e4cb2c --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/LODMapping.h @@ -0,0 +1,57 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/TypeDefs.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dnac { + +class LODConstraint; + +class LODMapping { + public: + explicit LODMapping(MemoryResource* memRes_); + + std::uint16_t getLODCount() const; + void resetIndices(); + void resetLODs(); + void reset(); + void setLODCount(std::uint16_t lodCount); + void discardLODs(const LODConstraint& lodConstraint); + ConstArrayView getIndices(std::uint16_t lod) const; + std::uint16_t getIndexListCount() const; + void clearIndices(std::uint16_t index); + void addIndices(std::uint16_t index, const std::uint16_t* source, std::uint16_t count); + void associateLODWithIndices(std::uint16_t lod, std::uint16_t index); + void mapIndices(std::function mapper); + void filterIndices(std::function filterer); + UnorderedSet getCombinedDistinctIndices(MemoryResource* memRes) const; + + private: + void cleanupIndices(); + + protected: + // Map indices to rows of the below defined matrix, e.g.: + // lods: [0, 0, 1, 1] + // indices: {[10, 15, 12], [9, 7, 43, 67]} + // expands into: + // 0: [10, 15, 12] + // 1: [10, 15, 12] + // 2: [9, 7, 43, 67] + // 3: [9, 7, 43, 67] + Vector lods; + Matrix indices; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/ReaderImpl.h b/dnacalib/DNACalib/src/dnacalib/dna/ReaderImpl.h new file mode 100644 index 0000000..e5daffd --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/ReaderImpl.h @@ -0,0 +1,1000 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/TypeDefs.h" +#include "dnacalib/dna/BaseImpl.h" +#include "dnacalib/dna/DenormalizedData.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dnac { + +using dna::Archetype; +using dna::Gender; +using dna::TranslationUnit; +using dna::RotationUnit; +using dna::Direction; +using dna::CoordinateSystem; +using dna::MeshBlendShapeChannelMapping; +using dna::Position; +using dna::TextureCoordinate; +using dna::Normal; +using dna::VertexLayout; +using dna::Delta; + +template +class ReaderImpl : public TReaderBase, public virtual BaseImpl { + public: + explicit ReaderImpl(MemoryResource* memRes_); + + // DescriptorReader methods start + StringView getName() const override; + Archetype getArchetype() const override; + Gender getGender() const override; + std::uint16_t getAge() const override; + std::uint32_t getMetaDataCount() const override; + StringView getMetaDataKey(std::uint32_t index) const override; + StringView getMetaDataValue(const char* key) const override; + TranslationUnit getTranslationUnit() const override; + RotationUnit getRotationUnit() const override; + CoordinateSystem getCoordinateSystem() const override; + std::uint16_t getLODCount() const override; + std::uint16_t getDBMaxLOD() const override; + StringView getDBComplexity() const override; + StringView getDBName() const override; + + // DefinitionReader methods start + std::uint16_t getGUIControlCount() const override; + StringView getGUIControlName(std::uint16_t index) const override; + std::uint16_t getRawControlCount() const override; + StringView getRawControlName(std::uint16_t index) const override; + std::uint16_t getJointCount() const override; + StringView getJointName(std::uint16_t index) const override; + std::uint16_t getJointIndexListCount() const override; + ConstArrayView getJointIndicesForLOD(std::uint16_t lod) const override; + std::uint16_t getJointParentIndex(std::uint16_t index) const override; + std::uint16_t getBlendShapeChannelCount() const override; + StringView getBlendShapeChannelName(std::uint16_t index) const override; + std::uint16_t getBlendShapeChannelIndexListCount() const override; + ConstArrayView getBlendShapeChannelIndicesForLOD(std::uint16_t lod) const override; + std::uint16_t getAnimatedMapCount() const override; + StringView getAnimatedMapName(std::uint16_t index) const override; + std::uint16_t getAnimatedMapIndexListCount() const override; + ConstArrayView getAnimatedMapIndicesForLOD(std::uint16_t lod) const override; + std::uint16_t getMeshCount() const override; + StringView getMeshName(std::uint16_t index) const override; + std::uint16_t getMeshIndexListCount() const override; + ConstArrayView getMeshIndicesForLOD(std::uint16_t lod) const override; + std::uint16_t getMeshBlendShapeChannelMappingCount() const override; + MeshBlendShapeChannelMapping getMeshBlendShapeChannelMapping(std::uint16_t index) const override; + ConstArrayView getMeshBlendShapeChannelMappingIndicesForLOD(std::uint16_t lod) const override; + Vector3 getNeutralJointTranslation(std::uint16_t index) const override; + ConstArrayView getNeutralJointTranslationXs() const override; + ConstArrayView getNeutralJointTranslationYs() const override; + ConstArrayView getNeutralJointTranslationZs() const override; + Vector3 getNeutralJointRotation(std::uint16_t index) const override; + ConstArrayView getNeutralJointRotationXs() const override; + ConstArrayView getNeutralJointRotationYs() const override; + ConstArrayView getNeutralJointRotationZs() const override; + + // BehaviorReader methods start + ConstArrayView getGUIToRawInputIndices() const override; + ConstArrayView getGUIToRawOutputIndices() const override; + ConstArrayView getGUIToRawFromValues() const override; + ConstArrayView getGUIToRawToValues() const override; + ConstArrayView getGUIToRawSlopeValues() const override; + ConstArrayView getGUIToRawCutValues() const override; + std::uint16_t getPSDCount() const override; + ConstArrayView getPSDRowIndices() const override; + ConstArrayView getPSDColumnIndices() const override; + ConstArrayView getPSDValues() const override; + std::uint16_t getJointRowCount() const override; + std::uint16_t getJointColumnCount() const override; + ConstArrayView getJointVariableAttributeIndices(std::uint16_t lod) const override; + std::uint16_t getJointGroupCount() const override; + ConstArrayView getJointGroupLODs(std::uint16_t jointGroupIndex) const override; + ConstArrayView getJointGroupInputIndices(std::uint16_t jointGroupIndex) const override; + ConstArrayView getJointGroupOutputIndices(std::uint16_t jointGroupIndex) const override; + ConstArrayView getJointGroupValues(std::uint16_t jointGroupIndex) const override; + ConstArrayView getJointGroupJointIndices(std::uint16_t jointGroupIndex) const override; + ConstArrayView getBlendShapeChannelLODs() const override; + ConstArrayView getBlendShapeChannelOutputIndices() const override; + ConstArrayView getBlendShapeChannelInputIndices() const override; + ConstArrayView getAnimatedMapLODs() const override; + ConstArrayView getAnimatedMapInputIndices() const override; + ConstArrayView getAnimatedMapOutputIndices() const override; + ConstArrayView getAnimatedMapFromValues() const override; + ConstArrayView getAnimatedMapToValues() const override; + ConstArrayView getAnimatedMapSlopeValues() const override; + ConstArrayView getAnimatedMapCutValues() const override; + + // GeometryReader methods start + std::uint32_t getVertexPositionCount(std::uint16_t meshIndex) const override; + Position getVertexPosition(std::uint16_t meshIndex, std::uint32_t vertexIndex) const override; + ConstArrayView getVertexPositionXs(std::uint16_t meshIndex) const override; + ConstArrayView getVertexPositionYs(std::uint16_t meshIndex) const override; + ConstArrayView getVertexPositionZs(std::uint16_t meshIndex) const override; + std::uint32_t getVertexTextureCoordinateCount(std::uint16_t meshIndex) const override; + TextureCoordinate getVertexTextureCoordinate(std::uint16_t meshIndex, + std::uint32_t textureCoordinateIndex) const override; + ConstArrayView getVertexTextureCoordinateUs(std::uint16_t meshIndex) const override; + ConstArrayView getVertexTextureCoordinateVs(std::uint16_t meshIndex) const override; + std::uint32_t getVertexNormalCount(std::uint16_t meshIndex) const override; + Normal getVertexNormal(std::uint16_t meshIndex, std::uint32_t normalIndex) const override; + ConstArrayView getVertexNormalXs(std::uint16_t meshIndex) const override; + ConstArrayView getVertexNormalYs(std::uint16_t meshIndex) const override; + ConstArrayView getVertexNormalZs(std::uint16_t meshIndex) const override; + std::uint32_t getFaceCount(std::uint16_t meshIndex) const override; + ConstArrayView getFaceVertexLayoutIndices(std::uint16_t meshIndex, std::uint32_t faceIndex) const override; + std::uint32_t getVertexLayoutCount(std::uint16_t meshIndex) const override; + VertexLayout getVertexLayout(std::uint16_t meshIndex, std::uint32_t layoutIndex) const override; + ConstArrayView getVertexLayoutPositionIndices(std::uint16_t meshIndex) const override; + ConstArrayView getVertexLayoutTextureCoordinateIndices(std::uint16_t meshIndex) const override; + ConstArrayView getVertexLayoutNormalIndices(std::uint16_t meshIndex) const override; + std::uint16_t getMaximumInfluencePerVertex(std::uint16_t meshIndex) const override; + std::uint32_t getSkinWeightsCount(std::uint16_t meshIndex) const override; + ConstArrayView getSkinWeightsValues(std::uint16_t meshIndex, std::uint32_t vertexIndex) const override; + ConstArrayView getSkinWeightsJointIndices(std::uint16_t meshIndex, + std::uint32_t vertexIndex) const override; + std::uint16_t getBlendShapeTargetCount(std::uint16_t meshIndex) const override; + std::uint16_t getBlendShapeChannelIndex(std::uint16_t meshIndex, std::uint16_t blendShapeTargetIndex) const override; + std::uint32_t getBlendShapeTargetDeltaCount(std::uint16_t meshIndex, std::uint16_t blendShapeTargetIndex) const override; + Delta getBlendShapeTargetDelta(std::uint16_t meshIndex, std::uint16_t blendShapeTargetIndex, + std::uint32_t deltaIndex) const override; + ConstArrayView getBlendShapeTargetDeltaXs(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const override; + ConstArrayView getBlendShapeTargetDeltaYs(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const override; + ConstArrayView getBlendShapeTargetDeltaZs(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const override; + ConstArrayView getBlendShapeTargetVertexIndices(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const override; + + void unload(DataLayer layer) override; + + protected: + mutable DenormalizedData cache; + +}; + + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4589) +#endif +template +inline ReaderImpl::ReaderImpl(MemoryResource* memRes_) : BaseImpl{memRes_}, cache{memRes_} { +} + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4505) +#endif +template +inline StringView ReaderImpl::getName() const { + return {dna.descriptor.name.data(), dna.descriptor.name.size()}; +} + +template +inline Archetype ReaderImpl::getArchetype() const { + return static_cast(dna.descriptor.archetype); +} + +template +inline Gender ReaderImpl::getGender() const { + return static_cast(dna.descriptor.gender); +} + +template +inline std::uint16_t ReaderImpl::getAge() const { + return dna.descriptor.age; +} + +template +inline std::uint32_t ReaderImpl::getMetaDataCount() const { + return static_cast(dna.descriptor.metadata.size()); +} + +template +inline StringView ReaderImpl::getMetaDataKey(std::uint32_t index) const { + if (index < dna.descriptor.metadata.size()) { + const auto& key = std::get<0>(dna.descriptor.metadata[index]); + return {key.data(), key.size()}; + } + return {}; +} + +template +inline StringView ReaderImpl::getMetaDataValue(const char* key) const { + for (const auto& data: dna.descriptor.metadata) { + if (std::get<0>(data) == key) { + const auto& value = std::get<1>(data); + return {value.data(), value.size()}; + } + } + return {}; +} + +template +inline TranslationUnit ReaderImpl::getTranslationUnit() const { + return static_cast(dna.descriptor.translationUnit); +} + +template +inline RotationUnit ReaderImpl::getRotationUnit() const { + return static_cast(dna.descriptor.rotationUnit); +} + +template +inline CoordinateSystem ReaderImpl::getCoordinateSystem() const { + return { + static_cast(dna.descriptor.coordinateSystem.xAxis), + static_cast(dna.descriptor.coordinateSystem.yAxis), + static_cast(dna.descriptor.coordinateSystem.zAxis) + }; +} + +template +inline std::uint16_t ReaderImpl::getLODCount() const { + return dna.descriptor.lodCount; +} + +template +inline std::uint16_t ReaderImpl::getDBMaxLOD() const { + return dna.descriptor.maxLOD; +} + +template +inline StringView ReaderImpl::getDBComplexity() const { + return {dna.descriptor.complexity.data(), dna.descriptor.complexity.size()}; +} + +template +inline StringView ReaderImpl::getDBName() const { + return {dna.descriptor.dbName.data(), dna.descriptor.dbName.size()}; +} + +template +inline std::uint16_t ReaderImpl::getGUIControlCount() const { + return static_cast(dna.definition.guiControlNames.size()); +} + +template +inline StringView ReaderImpl::getGUIControlName(std::uint16_t index) const { + if (index < dna.definition.guiControlNames.size()) { + const auto& guiControlName = dna.definition.guiControlNames[index]; + return {guiControlName.data(), guiControlName.size()}; + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getRawControlCount() const { + return static_cast(dna.definition.rawControlNames.size()); +} + +template +inline StringView ReaderImpl::getRawControlName(std::uint16_t index) const { + if (index < dna.definition.rawControlNames.size()) { + const auto& rawControlName = dna.definition.rawControlNames[index]; + return {rawControlName.data(), rawControlName.size()}; + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getJointCount() const { + return static_cast(dna.definition.jointNames.size()); +} + +template +inline StringView ReaderImpl::getJointName(std::uint16_t index) const { + if (index < dna.definition.jointNames.size()) { + const auto& jointName = dna.definition.jointNames[index]; + return {jointName.data(), jointName.size()}; + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getJointIndexListCount() const { + return dna.definition.lodJointMapping.getIndexListCount(); +} + +template +inline ConstArrayView ReaderImpl::getJointIndicesForLOD(std::uint16_t lod) const { + return dna.definition.lodJointMapping.getIndices(lod); +} + +template +inline std::uint16_t ReaderImpl::getJointParentIndex(std::uint16_t index) const { + if (index < dna.definition.jointHierarchy.size()) { + return dna.definition.jointHierarchy[index]; + } + return std::numeric_limits::max(); +} + +template +inline std::uint16_t ReaderImpl::getBlendShapeChannelCount() const { + return static_cast(dna.definition.blendShapeChannelNames.size()); +} + +template +inline StringView ReaderImpl::getBlendShapeChannelName(std::uint16_t index) const { + if (index < dna.definition.blendShapeChannelNames.size()) { + const auto& blendShapeName = dna.definition.blendShapeChannelNames[index]; + return {blendShapeName.data(), blendShapeName.size()}; + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getBlendShapeChannelIndexListCount() const { + return dna.definition.lodBlendShapeMapping.getIndexListCount(); +} + +template +inline ConstArrayView ReaderImpl::getBlendShapeChannelIndicesForLOD(std::uint16_t lod) const { + return dna.definition.lodBlendShapeMapping.getIndices(lod); +} + +template +inline std::uint16_t ReaderImpl::getAnimatedMapCount() const { + return static_cast(dna.definition.animatedMapNames.size()); +} + +template +inline StringView ReaderImpl::getAnimatedMapName(std::uint16_t index) const { + if (index < dna.definition.animatedMapNames.size()) { + const auto& animatedMapName = dna.definition.animatedMapNames[index]; + return {animatedMapName.data(), animatedMapName.size()}; + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getAnimatedMapIndexListCount() const { + return dna.definition.lodAnimatedMapMapping.getIndexListCount(); +} + +template +inline ConstArrayView ReaderImpl::getAnimatedMapIndicesForLOD(std::uint16_t lod) const { + return dna.definition.lodAnimatedMapMapping.getIndices(lod); +} + +template +inline std::uint16_t ReaderImpl::getMeshCount() const { + return static_cast(dna.definition.meshNames.size()); +} + +template +inline StringView ReaderImpl::getMeshName(std::uint16_t index) const { + if (index < dna.definition.meshNames.size()) { + const auto& meshName = dna.definition.meshNames[index]; + return {meshName.data(), meshName.size()}; + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getMeshIndexListCount() const { + return dna.definition.lodMeshMapping.getIndexListCount(); +} + +template +inline ConstArrayView ReaderImpl::getMeshIndicesForLOD(std::uint16_t lod) const { + return dna.definition.lodMeshMapping.getIndices(lod); +} + +template +inline std::uint16_t ReaderImpl::getMeshBlendShapeChannelMappingCount() const { + return static_cast(dna.definition.meshBlendShapeChannelMapping.size()); +} + +template +inline MeshBlendShapeChannelMapping ReaderImpl::getMeshBlendShapeChannelMapping(std::uint16_t index) const { + const auto mapping = dna.definition.meshBlendShapeChannelMapping.get(index); + return {mapping.from, mapping.to}; +} + +template +inline ConstArrayView ReaderImpl::getMeshBlendShapeChannelMappingIndicesForLOD(std::uint16_t lod) +const { + if (cache.meshBlendShapeMappingIndices.getLODCount() == static_cast(0)) { + cache.populate(this); + } + return cache.meshBlendShapeMappingIndices.getIndices(lod); +} + +template +inline Vector3 ReaderImpl::getNeutralJointTranslation(std::uint16_t index) const { + const auto& translations = dna.definition.neutralJointTranslations; + if (index < translations.xs.size()) { + return {translations.xs[index], translations.ys[index], translations.zs[index]}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getNeutralJointTranslationXs() const { + const auto& xs = dna.definition.neutralJointTranslations.xs; + return {xs.data(), xs.size()}; +} + +template +inline ConstArrayView ReaderImpl::getNeutralJointTranslationYs() const { + const auto& ys = dna.definition.neutralJointTranslations.ys; + return {ys.data(), ys.size()}; +} + +template +inline ConstArrayView ReaderImpl::getNeutralJointTranslationZs() const { + const auto& zs = dna.definition.neutralJointTranslations.zs; + return {zs.data(), zs.size()}; +} + +template +inline Vector3 ReaderImpl::getNeutralJointRotation(std::uint16_t index) const { + const auto& rotations = dna.definition.neutralJointRotations; + if (index < rotations.size()) { + return {rotations.xs[index], rotations.ys[index], rotations.zs[index]}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getNeutralJointRotationXs() const { + const auto& xs = dna.definition.neutralJointRotations.xs; + return {xs.data(), xs.size()}; +} + +template +inline ConstArrayView ReaderImpl::getNeutralJointRotationYs() const { + const auto& ys = dna.definition.neutralJointRotations.ys; + return {ys.data(), ys.size()}; +} + +template +inline ConstArrayView ReaderImpl::getNeutralJointRotationZs() const { + const auto& zs = dna.definition.neutralJointRotations.zs; + return {zs.data(), zs.size()}; +} + +template +inline ConstArrayView ReaderImpl::getGUIToRawInputIndices() const { + const auto& inputIndices = dna.behavior.controls.conditionals.inputIndices; + return {inputIndices.data(), inputIndices.size()}; +} + +template +inline ConstArrayView ReaderImpl::getGUIToRawOutputIndices() const { + const auto& outputIndices = dna.behavior.controls.conditionals.outputIndices; + return {outputIndices.data(), outputIndices.size()}; +} + +template +inline ConstArrayView ReaderImpl::getGUIToRawFromValues() const { + const auto& fromValues = dna.behavior.controls.conditionals.fromValues; + return {fromValues.data(), fromValues.size()}; +} + +template +inline ConstArrayView ReaderImpl::getGUIToRawToValues() const { + const auto& toValues = dna.behavior.controls.conditionals.toValues; + return {toValues.data(), toValues.size()}; +} + +template +inline ConstArrayView ReaderImpl::getGUIToRawSlopeValues() const { + const auto& slopeValues = dna.behavior.controls.conditionals.slopeValues; + return {slopeValues.data(), slopeValues.size()}; +} + +template +inline ConstArrayView ReaderImpl::getGUIToRawCutValues() const { + const auto& cutValues = dna.behavior.controls.conditionals.cutValues; + return {cutValues.data(), cutValues.size()}; +} + +template +inline std::uint16_t ReaderImpl::getPSDCount() const { + return dna.behavior.controls.psdCount; +} + +template +inline ConstArrayView ReaderImpl::getPSDRowIndices() const { + const auto& rows = dna.behavior.controls.psds.rows; + return {rows.data(), rows.size()}; +} + +template +inline ConstArrayView ReaderImpl::getPSDColumnIndices() const { + const auto& columns = dna.behavior.controls.psds.columns; + return {columns.data(), columns.size()}; +} + +template +inline ConstArrayView ReaderImpl::getPSDValues() const { + const auto& values = dna.behavior.controls.psds.values; + return {values.data(), values.size()}; +} + +template +inline std::uint16_t ReaderImpl::getJointRowCount() const { + return dna.behavior.joints.rowCount; +} + +template +inline std::uint16_t ReaderImpl::getJointColumnCount() const { + return dna.behavior.joints.colCount; +} + +template +inline ConstArrayView ReaderImpl::getJointVariableAttributeIndices(std::uint16_t lod) const { + if (cache.jointVariableAttributeIndices.getLODCount() == static_cast(0)) { + cache.populate(this); + } + return cache.jointVariableAttributeIndices.getIndices(lod); +} + +template +inline std::uint16_t ReaderImpl::getJointGroupCount() const { + return static_cast(dna.behavior.joints.jointGroups.size()); +} + +template +inline ConstArrayView ReaderImpl::getJointGroupLODs(std::uint16_t jointGroupIndex) const { + if (jointGroupIndex < dna.behavior.joints.jointGroups.size()) { + const auto& lods = dna.behavior.joints.jointGroups[jointGroupIndex].lods; + return {lods.data(), lods.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getJointGroupInputIndices(std::uint16_t jointGroupIndex) const { + if (jointGroupIndex < dna.behavior.joints.jointGroups.size()) { + const auto& inputIndices = dna.behavior.joints.jointGroups[jointGroupIndex].inputIndices; + return {inputIndices.data(), inputIndices.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getJointGroupOutputIndices(std::uint16_t jointGroupIndex) const { + if (jointGroupIndex < dna.behavior.joints.jointGroups.size()) { + const auto& outputIndices = dna.behavior.joints.jointGroups[jointGroupIndex].outputIndices; + return {outputIndices.data(), outputIndices.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getJointGroupValues(std::uint16_t jointGroupIndex) const { + if (jointGroupIndex < dna.behavior.joints.jointGroups.size()) { + const auto& values = dna.behavior.joints.jointGroups[jointGroupIndex].values; + return {values.data(), values.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getJointGroupJointIndices(std::uint16_t jointGroupIndex) const { + if (jointGroupIndex < dna.behavior.joints.jointGroups.size()) { + const auto& jointIndices = dna.behavior.joints.jointGroups[jointGroupIndex].jointIndices; + return {jointIndices.data(), jointIndices.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getBlendShapeChannelLODs() const { + const auto& lods = dna.behavior.blendShapeChannels.lods; + return {lods.data(), lods.size()}; +} + +template +inline ConstArrayView ReaderImpl::getBlendShapeChannelInputIndices() const { + const auto& inputIndices = dna.behavior.blendShapeChannels.inputIndices; + return {inputIndices.data(), inputIndices.size()}; +} + +template +inline ConstArrayView ReaderImpl::getBlendShapeChannelOutputIndices() const { + const auto& outputIndices = dna.behavior.blendShapeChannels.outputIndices; + return {outputIndices.data(), outputIndices.size()}; +} + +template +inline ConstArrayView ReaderImpl::getAnimatedMapLODs() const { + const auto& lods = dna.behavior.animatedMaps.lods; + return {lods.data(), lods.size()}; +} + +template +inline ConstArrayView ReaderImpl::getAnimatedMapInputIndices() const { + const auto& inputIndices = dna.behavior.animatedMaps.conditionals.inputIndices; + return {inputIndices.data(), inputIndices.size()}; +} + +template +inline ConstArrayView ReaderImpl::getAnimatedMapOutputIndices() const { + const auto& outputIndices = dna.behavior.animatedMaps.conditionals.outputIndices; + return {outputIndices.data(), outputIndices.size()}; +} + +template +inline ConstArrayView ReaderImpl::getAnimatedMapFromValues() const { + const auto& fromValues = dna.behavior.animatedMaps.conditionals.fromValues; + return {fromValues.data(), fromValues.size()}; +} + +template +inline ConstArrayView ReaderImpl::getAnimatedMapToValues() const { + const auto& toValues = dna.behavior.animatedMaps.conditionals.toValues; + return {toValues.data(), toValues.size()}; +} + +template +inline ConstArrayView ReaderImpl::getAnimatedMapSlopeValues() const { + const auto& slopeValues = dna.behavior.animatedMaps.conditionals.slopeValues; + return {slopeValues.data(), slopeValues.size()}; +} + +template +inline ConstArrayView ReaderImpl::getAnimatedMapCutValues() const { + const auto& cutValues = dna.behavior.animatedMaps.conditionals.cutValues; + return {cutValues.data(), cutValues.size()}; +} + +template +inline std::uint32_t ReaderImpl::getVertexPositionCount(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + return static_cast(dna.geometry.meshes[meshIndex].positions.xs.size()); + } + return 0u; +} + +template +inline Position ReaderImpl::getVertexPosition(std::uint16_t meshIndex, std::uint32_t vertexIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& positions = dna.geometry.meshes[meshIndex].positions; + if (vertexIndex < positions.size()) { + return {positions.xs[vertexIndex], positions.ys[vertexIndex], positions.zs[vertexIndex]}; + } + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexPositionXs(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& xPositions = dna.geometry.meshes[meshIndex].positions.xs; + return {xPositions.data(), xPositions.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexPositionYs(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& yPositions = dna.geometry.meshes[meshIndex].positions.ys; + return {yPositions.data(), yPositions.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexPositionZs(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& zPositions = dna.geometry.meshes[meshIndex].positions.zs; + return {zPositions.data(), zPositions.size()}; + } + return {}; +} + +template +inline std::uint32_t ReaderImpl::getVertexTextureCoordinateCount(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + return static_cast(dna.geometry.meshes[meshIndex].textureCoordinates.us.size()); + } + return 0u; +} + +template +inline TextureCoordinate ReaderImpl::getVertexTextureCoordinate(std::uint16_t meshIndex, + std::uint32_t textureCoordinateIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& textureCoordinates = dna.geometry.meshes[meshIndex].textureCoordinates; + if (textureCoordinateIndex < textureCoordinates.size()) { + return {textureCoordinates.us[textureCoordinateIndex], textureCoordinates.vs[textureCoordinateIndex]}; + } + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexTextureCoordinateUs(std::uint16_t meshIndex) const { + const auto& uTextureCoordinates = dna.geometry.meshes[meshIndex].textureCoordinates.us; + return {uTextureCoordinates.data(), uTextureCoordinates.size()}; +} + +template +inline ConstArrayView ReaderImpl::getVertexTextureCoordinateVs(std::uint16_t meshIndex) const { + const auto& vTextureCoordinates = dna.geometry.meshes[meshIndex].textureCoordinates.vs; + return {vTextureCoordinates.data(), vTextureCoordinates.size()}; +} + +template +inline std::uint32_t ReaderImpl::getVertexNormalCount(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + return static_cast(dna.geometry.meshes[meshIndex].normals.xs.size()); + } + return 0u; +} + +template +inline Normal ReaderImpl::getVertexNormal(std::uint16_t meshIndex, std::uint32_t normalIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& normals = dna.geometry.meshes[meshIndex].normals; + if (normalIndex < normals.size()) { + return {normals.xs[normalIndex], normals.ys[normalIndex], normals.zs[normalIndex]}; + } + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexNormalXs(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& xNormals = dna.geometry.meshes[meshIndex].normals.xs; + return {xNormals.data(), xNormals.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexNormalYs(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& yNormals = dna.geometry.meshes[meshIndex].normals.ys; + return {yNormals.data(), yNormals.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexNormalZs(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& zNormals = dna.geometry.meshes[meshIndex].normals.zs; + return {zNormals.data(), zNormals.size()}; + } + return {}; +} + +template +inline std::uint32_t ReaderImpl::getFaceCount(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + return static_cast(dna.geometry.meshes[meshIndex].faces.size()); + } + return 0u; +} + +template +inline ConstArrayView ReaderImpl::getFaceVertexLayoutIndices(std::uint16_t meshIndex, + std::uint32_t faceIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (faceIndex < meshes[meshIndex].faces.size())) { + const auto& layoutIndices = meshes[meshIndex].faces[faceIndex].layoutIndices; + return {layoutIndices.data(), layoutIndices.size()}; + } + return {}; +} + +template +inline std::uint32_t ReaderImpl::getVertexLayoutCount(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + return static_cast(dna.geometry.meshes[meshIndex].layouts.positions.size()); + } + return 0u; +} + +template +inline VertexLayout ReaderImpl::getVertexLayout(std::uint16_t meshIndex, std::uint32_t layoutIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& layouts = dna.geometry.meshes[meshIndex].layouts; + if (layoutIndex < layouts.size()) { + return {layouts.positions[layoutIndex], layouts.textureCoordinates[layoutIndex], layouts.normals[layoutIndex]}; + } + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexLayoutPositionIndices(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& positions = dna.geometry.meshes[meshIndex].layouts.positions; + return {positions.data(), positions.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexLayoutTextureCoordinateIndices(std::uint16_t meshIndex) +const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& textureCoordinated = dna.geometry.meshes[meshIndex].layouts.textureCoordinates; + return {textureCoordinated.data(), textureCoordinated.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getVertexLayoutNormalIndices(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + const auto& normals = dna.geometry.meshes[meshIndex].layouts.normals; + return {normals.data(), normals.size()}; + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getMaximumInfluencePerVertex(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + return dna.geometry.meshes[meshIndex].maximumInfluencePerVertex; + } + return {}; +} + +template +inline std::uint32_t ReaderImpl::getSkinWeightsCount(std::uint16_t meshIndex) const { + const auto& meshes = dna.geometry.meshes; + if (meshIndex < meshes.size()) { + return static_cast(meshes[meshIndex].skinWeights.size()); + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getSkinWeightsValues(std::uint16_t meshIndex, + std::uint32_t vertexIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (vertexIndex < meshes[meshIndex].skinWeights.size())) { + const auto& weights = meshes[meshIndex].skinWeights[vertexIndex].weights; + return {weights.data(), weights.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getSkinWeightsJointIndices(std::uint16_t meshIndex, + std::uint32_t vertexIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (vertexIndex < meshes[meshIndex].skinWeights.size())) { + const auto& jointIndices = meshes[meshIndex].skinWeights[vertexIndex].jointIndices; + return {jointIndices.data(), jointIndices.size()}; + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getBlendShapeTargetCount(std::uint16_t meshIndex) const { + if (meshIndex < dna.geometry.meshes.size()) { + return static_cast(dna.geometry.meshes[meshIndex].blendShapeTargets.size()); + } + return {}; +} + +template +inline std::uint16_t ReaderImpl::getBlendShapeChannelIndex(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (blendShapeTargetIndex < meshes[meshIndex].blendShapeTargets.size())) { + return meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].blendShapeChannelIndex; + } + return {}; +} + +template +inline std::uint32_t ReaderImpl::getBlendShapeTargetDeltaCount(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (blendShapeTargetIndex < meshes[meshIndex].blendShapeTargets.size())) { + return static_cast(meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas.xs.size()); + } + return {}; +} + +template +inline Delta ReaderImpl::getBlendShapeTargetDelta(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + std::uint32_t deltaIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (blendShapeTargetIndex < meshes[meshIndex].blendShapeTargets.size()) && + (deltaIndex < meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas.size())) { + const auto& deltas = meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas; + return {deltas.xs[deltaIndex], deltas.ys[deltaIndex], deltas.zs[deltaIndex]}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getBlendShapeTargetDeltaXs(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (blendShapeTargetIndex < meshes[meshIndex].blendShapeTargets.size())) { + const auto& xDeltas = meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas.xs; + return {xDeltas.data(), xDeltas.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getBlendShapeTargetDeltaYs(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (blendShapeTargetIndex < meshes[meshIndex].blendShapeTargets.size())) { + const auto& yDeltas = meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas.ys; + return {yDeltas.data(), yDeltas.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getBlendShapeTargetDeltaZs(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (blendShapeTargetIndex < meshes[meshIndex].blendShapeTargets.size())) { + const auto& zDeltas = meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas.zs; + return {zDeltas.data(), zDeltas.size()}; + } + return {}; +} + +template +inline ConstArrayView ReaderImpl::getBlendShapeTargetVertexIndices(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex) +const { + const auto& meshes = dna.geometry.meshes; + if ((meshIndex < meshes.size()) && (blendShapeTargetIndex < meshes[meshIndex].blendShapeTargets.size())) { + const auto& vertexIndices = meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].vertexIndices; + return {vertexIndices.data(), vertexIndices.size()}; + } + return {}; +} + +template +void ReaderImpl::unload(DataLayer layer) { + if ((layer == DataLayer::All) || + (layer == DataLayer::AllWithoutBlendShapes) || + (layer == DataLayer::Descriptor)) { + dna = DNA{memRes}; + } else if ((layer == DataLayer::Geometry) || (layer == DataLayer::GeometryWithoutBlendShapes)) { + dna.unloadGeometry(); + } else if (layer == DataLayer::Behavior) { + dna.unloadBehavior(); + } else if (layer == DataLayer::Definition) { + dna.unloadGeometry(); + dna.unloadBehavior(); + dna.unloadDefinition(); + } +} + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/SurjectiveMapping.h b/dnacalib/DNACalib/src/dnacalib/dna/SurjectiveMapping.h new file mode 100644 index 0000000..2eb747a --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/SurjectiveMapping.h @@ -0,0 +1,101 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/TypeDefs.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dnac { + +template +struct SurjectiveMapping { + public: + struct Pair { + TFrom from; + TTo to; + }; + + public: + explicit SurjectiveMapping(MemoryResource* memRes) : + from{memRes}, + to{memRes} { + } + + Pair get(std::size_t index) const { + assert(index < size()); + return {from[index], to[index]}; + } + + void add(TFrom from_, TTo to_) { + from.push_back(from_); + to.push_back(to_); + } + + void set(std::size_t index, TFrom from_, TTo to_) { + if (index >= size()) { + from.resize(index + 1ul); + to.resize(index + 1ul); + } + from[index] = from_; + to[index] = to_; + } + + void removeIf(std::function predicate) { + assert(from.size() == to.size()); + + auto itFrom = from.begin(); + auto itTo = to.begin(); + + while (itFrom != from.end()) { + if (predicate(*itFrom, *itTo)) { + itFrom = from.erase(itFrom); + itTo = to.erase(itTo); + } else { + ++itFrom; + ++itTo; + } + } + } + + void updateFrom(const UnorderedMap& mapping) { + update(from, mapping); + } + + void updateTo(const UnorderedMap& mapping) { + update(to, mapping); + } + + std::size_t size() const { + assert(from.size() == to.size()); + return from.size(); + } + + void clear() { + from.clear(); + to.clear(); + } + + private: + template + void update(Vector& target, const UnorderedMap& mapping) { + std::transform(target.begin(), target.end(), target.begin(), [&mapping](U oldValue) { + return mapping.at(oldValue); + }); + } + + protected: + Vector from; + Vector to; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/WriterImpl.h b/dnacalib/DNACalib/src/dnacalib/dna/WriterImpl.h new file mode 100644 index 0000000..8262a9f --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/WriterImpl.h @@ -0,0 +1,757 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/TypeDefs.h" +#include "dnacalib/dna/BaseImpl.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace dnac { + +template +void ensureHasSize(TContainer& target, + std::size_t size, + Args&& ... args) { + target.reserve(size); + while (target.size() < size) { + target.push_back(typename TContainer::value_type(std::forward(args)...)); + } +} + +template +class WriterImpl : public TWriterBase, public virtual BaseImpl { + public: + explicit WriterImpl(MemoryResource* memRes_); + + // DescriptorWriter methods + void setName(const char* name) override; + void setArchetype(Archetype archetype) override; + void setGender(Gender gender) override; + void setAge(std::uint16_t age) override; + void clearMetaData() override; + void setMetaData(const char* key, const char* value) override; + void setTranslationUnit(TranslationUnit unit) override; + void setRotationUnit(RotationUnit unit) override; + void setCoordinateSystem(CoordinateSystem system) override; + void setLODCount(std::uint16_t lodCount) override; + void setDBMaxLOD(std::uint16_t lod) override; + void setDBComplexity(const char* name) override; + void setDBName(const char* name) override; + + // DefinitionWriter methods + void clearGUIControlNames() override; + void setGUIControlName(std::uint16_t index, const char* name) override; + void clearRawControlNames() override; + void setRawControlName(std::uint16_t index, const char* name) override; + void clearJointNames() override; + void setJointName(std::uint16_t index, const char* name) override; + void clearJointIndices() override; + void setJointIndices(std::uint16_t index, const std::uint16_t* jointIndices, std::uint16_t count) override; + void clearLODJointMappings() override; + void setLODJointMapping(std::uint16_t lod, std::uint16_t index) override; + void clearBlendShapeChannelNames() override; + void setJointHierarchy(const std::uint16_t* jointIndices, std::uint16_t count) override; + void setBlendShapeChannelName(std::uint16_t index, const char* name) override; + void clearBlendShapeChannelIndices() override; + void setBlendShapeChannelIndices(std::uint16_t index, const std::uint16_t* blendShapeChannelIndices, + std::uint16_t count) override; + void clearLODBlendShapeChannelMappings() override; + void setLODBlendShapeChannelMapping(std::uint16_t lod, std::uint16_t index) override; + void clearAnimatedMapNames() override; + void setAnimatedMapName(std::uint16_t index, const char* name) override; + void clearAnimatedMapIndices() override; + void setAnimatedMapIndices(std::uint16_t index, const std::uint16_t* animatedMapIndices, std::uint16_t count) override; + void clearLODAnimatedMapMappings() override; + void setLODAnimatedMapMapping(std::uint16_t lod, std::uint16_t index) override; + void clearMeshNames() override; + void setMeshName(std::uint16_t index, const char* name) override; + void clearMeshIndices() override; + void setMeshIndices(std::uint16_t index, const std::uint16_t* meshIndices, std::uint16_t count) override; + void clearLODMeshMappings() override; + void setLODMeshMapping(std::uint16_t lod, std::uint16_t index) override; + void clearMeshBlendShapeChannelMappings() override; + void setMeshBlendShapeChannelMapping(std::uint32_t index, std::uint16_t meshIndex, + std::uint16_t blendShapeChannelIndex) override; + void setNeutralJointTranslations(const Vector3* translations, std::uint16_t count) override; + void setNeutralJointRotations(const Vector3* rotations, std::uint16_t count) override; + + // BehaviorWriter methods + void setGUIToRawInputIndices(const std::uint16_t* inputIndices, std::uint16_t count) override; + void setGUIToRawOutputIndices(const std::uint16_t* outputIndices, std::uint16_t count) override; + void setGUIToRawFromValues(const float* fromValues, std::uint16_t count) override; + void setGUIToRawToValues(const float* toValues, std::uint16_t count) override; + void setGUIToRawSlopeValues(const float* slopeValues, std::uint16_t count) override; + void setGUIToRawCutValues(const float* cutValues, std::uint16_t count) override; + void setPSDCount(std::uint16_t count) override; + void setPSDRowIndices(const std::uint16_t* rowIndices, std::uint16_t count) override; + void setPSDColumnIndices(const std::uint16_t* columnIndices, std::uint16_t count) override; + void setPSDValues(const float* weights, std::uint16_t count) override; + void setJointRowCount(std::uint16_t rowCount) override; + void setJointColumnCount(std::uint16_t columnCount) override; + void clearJointGroups() override; + void deleteJointGroup(std::uint16_t jointGroupIndex) override; + void setJointGroupLODs(std::uint16_t jointGroupIndex, const std::uint16_t* lods, std::uint16_t count) override; + void setJointGroupInputIndices(std::uint16_t jointGroupIndex, const std::uint16_t* inputIndices, + std::uint16_t count) override; + void setJointGroupOutputIndices(std::uint16_t jointGroupIndex, const std::uint16_t* outputIndices, + std::uint16_t count) override; + void setJointGroupValues(std::uint16_t jointGroupIndex, const float* values, std::uint32_t count) override; + void setJointGroupJointIndices(std::uint16_t jointGroupIndex, const std::uint16_t* jointIndices, + std::uint16_t count) override; + void setBlendShapeChannelLODs(const std::uint16_t* lods, std::uint16_t count) override; + void setBlendShapeChannelInputIndices(const std::uint16_t* inputIndices, std::uint16_t count) override; + void setBlendShapeChannelOutputIndices(const std::uint16_t* outputIndices, std::uint16_t count) override; + void setAnimatedMapLODs(const std::uint16_t* lods, std::uint16_t count) override; + void setAnimatedMapInputIndices(const std::uint16_t* inputIndices, std::uint16_t count) override; + void setAnimatedMapOutputIndices(const std::uint16_t* outputIndices, std::uint16_t count) override; + void setAnimatedMapFromValues(const float* fromValues, std::uint16_t count) override; + void setAnimatedMapToValues(const float* toValues, std::uint16_t count) override; + void setAnimatedMapSlopeValues(const float* slopeValues, std::uint16_t count) override; + void setAnimatedMapCutValues(const float* cutValues, std::uint16_t count) override; + + // GeometryWriter methods + void clearMeshes() override; + void deleteMesh(std::uint16_t meshIndex) override; + void setVertexPositions(std::uint16_t meshIndex, const Position* positions, std::uint32_t count) override; + void setVertexTextureCoordinates(std::uint16_t meshIndex, const TextureCoordinate* textureCoordinates, + std::uint32_t count) override; + void setVertexNormals(std::uint16_t meshIndex, const Normal* normals, std::uint32_t count) override; + void setVertexLayouts(std::uint16_t meshIndex, const VertexLayout* layouts, std::uint32_t count) override; + void clearFaceVertexLayoutIndices(std::uint16_t meshIndex) override; + void setFaceVertexLayoutIndices(std::uint16_t meshIndex, + std::uint32_t faceIndex, + const std::uint32_t* layoutIndices, + std::uint32_t count) override; + void setMaximumInfluencePerVertex(std::uint16_t meshIndex, std::uint16_t maxInfluenceCount) override; + void clearSkinWeights(std::uint16_t meshIndex) override; + void setSkinWeightsValues(std::uint16_t meshIndex, std::uint32_t vertexIndex, const float* weights, + std::uint16_t count) override; + void setSkinWeightsJointIndices(std::uint16_t meshIndex, + std::uint32_t vertexIndex, + const std::uint16_t* jointIndices, + std::uint16_t count) override; + void clearBlendShapeTargets(std::uint16_t meshIndex) override; + void setBlendShapeChannelIndex(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + std::uint16_t blendShapeChannelIndex) override; + void setBlendShapeTargetDeltas(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + const Delta* deltas, + std::uint32_t count) override; + void setBlendShapeTargetVertexIndices(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + const std::uint32_t* vertexIndices, + std::uint32_t count) override; + +}; + + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4589) +#endif +template +WriterImpl::WriterImpl(MemoryResource* memRes_) : BaseImpl{memRes_} { +} + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4505) +#endif +template +inline void WriterImpl::setName(const char* name) { + dna.descriptor.name = name; +} + +template +inline void WriterImpl::setArchetype(Archetype archetype) { + dna.descriptor.archetype = static_cast(archetype); +} + +template +inline void WriterImpl::setGender(Gender gender) { + dna.descriptor.gender = static_cast(gender); +} + +template +inline void WriterImpl::setAge(std::uint16_t age) { + dna.descriptor.age = age; +} + +template +inline void WriterImpl::clearMetaData() { + dna.descriptor.metadata.clear(); +} + +template +inline void WriterImpl::setMetaData(const char* key, const char* value) { + using CharStringPair = std::tuple, String >; + auto it = std::find_if(dna.descriptor.metadata.begin(), dna.descriptor.metadata.end(), [&key](const CharStringPair& kv) { + auto& k = std::get<0>(kv); + return (std::strlen(key) == k.size() && std::strncmp(k.data(), key, k.size()) == 0); + }); + if (it == dna.descriptor.metadata.end()) { + if (value != nullptr) { + dna.descriptor.metadata.emplace_back(String{key, memRes}, String{value, memRes}); + } + } else { + if (value == nullptr) { + dna.descriptor.metadata.erase(it); + } else { + std::get<1>(*it) = value; + } + } +} + +template +inline void WriterImpl::setTranslationUnit(TranslationUnit unit) { + dna.descriptor.translationUnit = static_cast(unit); +} + +template +inline void WriterImpl::setRotationUnit(RotationUnit unit) { + dna.descriptor.rotationUnit = static_cast(unit); +} + +template +inline void WriterImpl::setCoordinateSystem(CoordinateSystem system) { + dna.descriptor.coordinateSystem.xAxis = static_cast(system.xAxis); + dna.descriptor.coordinateSystem.yAxis = static_cast(system.yAxis); + dna.descriptor.coordinateSystem.zAxis = static_cast(system.zAxis); +} + +template +inline void WriterImpl::setLODCount(std::uint16_t lodCount) { + dna.descriptor.lodCount = lodCount; +} + +template +inline void WriterImpl::setDBMaxLOD(std::uint16_t lod) { + dna.descriptor.maxLOD = lod; +} + +template +inline void WriterImpl::setDBComplexity(const char* name) { + dna.descriptor.complexity = name; +} + +template +inline void WriterImpl::setDBName(const char* name) { + dna.descriptor.dbName = name; +} + +template +inline void WriterImpl::clearGUIControlNames() { + dna.definition.guiControlNames.clear(); +} + +template +inline void WriterImpl::setGUIControlName(std::uint16_t index, const char* name) { + ensureHasSize(dna.definition.guiControlNames, index + 1ul, memRes); + dna.definition.guiControlNames[index] = name; +} + +template +inline void WriterImpl::clearRawControlNames() { + dna.definition.rawControlNames.clear(); +} + +template +inline void WriterImpl::setRawControlName(std::uint16_t index, const char* name) { + ensureHasSize(dna.definition.rawControlNames, index + 1ul, memRes); + dna.definition.rawControlNames[index] = name; +} + +template +inline void WriterImpl::clearJointNames() { + dna.definition.jointNames.clear(); +} + +template +inline void WriterImpl::setJointName(std::uint16_t index, const char* name) { + ensureHasSize(dna.definition.jointNames, index + 1ul, memRes); + dna.definition.jointNames[index] = name; +} + +template +inline void WriterImpl::clearJointIndices() { + dna.definition.lodJointMapping.resetIndices(); +} + +template +inline void WriterImpl::setJointIndices(std::uint16_t index, const std::uint16_t* jointIndices, + std::uint16_t count) { + dna.definition.lodJointMapping.clearIndices(index); + dna.definition.lodJointMapping.addIndices(index, jointIndices, count); +} + +template +inline void WriterImpl::clearLODJointMappings() { + dna.definition.lodJointMapping.resetLODs(); +} + +template +inline void WriterImpl::setLODJointMapping(std::uint16_t lod, std::uint16_t index) { + dna.definition.lodJointMapping.associateLODWithIndices(lod, index); +} + +template +inline void WriterImpl::setJointHierarchy(const std::uint16_t* jointIndices, std::uint16_t count) { + dna.definition.jointHierarchy.assign(jointIndices, jointIndices + count); +} + +template +inline void WriterImpl::clearBlendShapeChannelNames() { + dna.definition.blendShapeChannelNames.clear(); +} + +template +inline void WriterImpl::setBlendShapeChannelName(std::uint16_t index, const char* name) { + ensureHasSize(dna.definition.blendShapeChannelNames, index + 1ul, memRes); + dna.definition.blendShapeChannelNames[index] = name; +} + +template +inline void WriterImpl::clearBlendShapeChannelIndices() { + dna.definition.lodBlendShapeMapping.resetIndices(); +} + +template +inline void WriterImpl::setBlendShapeChannelIndices(std::uint16_t index, + const std::uint16_t* blendShapeChannelIndices, + std::uint16_t count) { + dna.definition.lodBlendShapeMapping.clearIndices(index); + dna.definition.lodBlendShapeMapping.addIndices(index, blendShapeChannelIndices, count); +} + +template +inline void WriterImpl::clearLODBlendShapeChannelMappings() { + dna.definition.lodBlendShapeMapping.resetLODs(); +} + +template +inline void WriterImpl::setLODBlendShapeChannelMapping(std::uint16_t lod, std::uint16_t index) { + dna.definition.lodBlendShapeMapping.associateLODWithIndices(lod, index); +} + +template +inline void WriterImpl::clearAnimatedMapNames() { + dna.definition.animatedMapNames.clear(); +} + +template +inline void WriterImpl::setAnimatedMapName(std::uint16_t index, const char* name) { + ensureHasSize(dna.definition.animatedMapNames, index + 1ul, memRes); + dna.definition.animatedMapNames[index] = name; +} + +template +inline void WriterImpl::clearAnimatedMapIndices() { + dna.definition.lodAnimatedMapMapping.resetIndices(); +} + +template +inline void WriterImpl::setAnimatedMapIndices(std::uint16_t index, + const std::uint16_t* animatedMapIndices, + std::uint16_t count) { + dna.definition.lodAnimatedMapMapping.clearIndices(index); + dna.definition.lodAnimatedMapMapping.addIndices(index, animatedMapIndices, count); +} + +template +inline void WriterImpl::clearLODAnimatedMapMappings() { + dna.definition.lodAnimatedMapMapping.resetLODs(); +} + +template +inline void WriterImpl::setLODAnimatedMapMapping(std::uint16_t lod, std::uint16_t index) { + dna.definition.lodAnimatedMapMapping.associateLODWithIndices(lod, index); +} + +template +inline void WriterImpl::clearMeshNames() { + dna.definition.meshNames.clear(); +} + +template +inline void WriterImpl::setMeshName(std::uint16_t index, const char* name) { + ensureHasSize(dna.definition.meshNames, index + 1ul, memRes); + dna.definition.meshNames[index] = name; +} + +template +inline void WriterImpl::clearMeshIndices() { + dna.definition.lodMeshMapping.resetIndices(); +} + +template +inline void WriterImpl::setMeshIndices(std::uint16_t index, const std::uint16_t* meshIndices, std::uint16_t count) { + dna.definition.lodMeshMapping.clearIndices(index); + dna.definition.lodMeshMapping.addIndices(index, meshIndices, count); +} + +template +inline void WriterImpl::clearLODMeshMappings() { + dna.definition.lodMeshMapping.resetLODs(); +} + +template +inline void WriterImpl::setLODMeshMapping(std::uint16_t lod, std::uint16_t index) { + dna.definition.lodMeshMapping.associateLODWithIndices(lod, index); +} + +template +inline void WriterImpl::clearMeshBlendShapeChannelMappings() { + dna.definition.meshBlendShapeChannelMapping.clear(); +} + +template +inline void WriterImpl::setMeshBlendShapeChannelMapping(std::uint32_t index, + std::uint16_t meshIndex, + std::uint16_t blendShapeChannelIndex) { + dna.definition.meshBlendShapeChannelMapping.set(index, meshIndex, blendShapeChannelIndex); +} + +template +inline void WriterImpl::setNeutralJointTranslations(const Vector3* translations, std::uint16_t count) { + dna.definition.neutralJointTranslations.assign(translations, translations + count); +} + +template +inline void WriterImpl::setNeutralJointRotations(const Vector3* rotations, std::uint16_t count) { + dna.definition.neutralJointRotations.assign(rotations, rotations + count); +} + +template +inline void WriterImpl::setGUIToRawInputIndices(const std::uint16_t* inputIndices, std::uint16_t count) { + dna.behavior.controls.conditionals.inputIndices.assign(inputIndices, inputIndices + count); +} + +template +inline void WriterImpl::setGUIToRawOutputIndices(const std::uint16_t* outputIndices, std::uint16_t count) { + dna.behavior.controls.conditionals.outputIndices.assign(outputIndices, outputIndices + count); +} + +template +inline void WriterImpl::setGUIToRawFromValues(const float* fromValues, std::uint16_t count) { + dna.behavior.controls.conditionals.fromValues.assign(fromValues, fromValues + count); +} + +template +inline void WriterImpl::setGUIToRawToValues(const float* toValues, std::uint16_t count) { + dna.behavior.controls.conditionals.toValues.assign(toValues, toValues + count); +} + +template +inline void WriterImpl::setGUIToRawSlopeValues(const float* slopeValues, std::uint16_t count) { + dna.behavior.controls.conditionals.slopeValues.assign(slopeValues, slopeValues + count); +} + +template +inline void WriterImpl::setGUIToRawCutValues(const float* cutValues, std::uint16_t count) { + dna.behavior.controls.conditionals.cutValues.assign(cutValues, cutValues + count); +} + +template +inline void WriterImpl::setPSDCount(std::uint16_t count) { + dna.behavior.controls.psdCount = count; +} + +template +inline void WriterImpl::setPSDRowIndices(const std::uint16_t* rowIndices, std::uint16_t count) { + dna.behavior.controls.psds.rows.assign(rowIndices, rowIndices + count); +} + +template +inline void WriterImpl::setPSDColumnIndices(const std::uint16_t* columnIndices, std::uint16_t count) { + dna.behavior.controls.psds.columns.assign(columnIndices, columnIndices + count); +} + +template +inline void WriterImpl::setPSDValues(const float* weights, std::uint16_t count) { + dna.behavior.controls.psds.values.assign(weights, weights + count); +} + +template +inline void WriterImpl::setJointRowCount(std::uint16_t rowCount) { + dna.behavior.joints.rowCount = rowCount; +} + +template +inline void WriterImpl::setJointColumnCount(std::uint16_t columnCount) { + dna.behavior.joints.colCount = columnCount; +} + +template +inline void WriterImpl::clearJointGroups() { + dna.behavior.joints.jointGroups.clear(); +} + +template +inline void WriterImpl::deleteJointGroup(std::uint16_t jointGroupIndex) { + if (jointGroupIndex < dna.behavior.joints.jointGroups.size()) { + auto it = extd::advanced(dna.behavior.joints.jointGroups.begin(), jointGroupIndex); + dna.behavior.joints.jointGroups.erase(it); + } +} + +template +inline void WriterImpl::setJointGroupLODs(std::uint16_t jointGroupIndex, + const std::uint16_t* lods, + std::uint16_t count) { + auto& jointGroups = dna.behavior.joints.jointGroups; + ensureHasSize(jointGroups, jointGroupIndex + 1ul, memRes); + jointGroups[jointGroupIndex].lods.assign(lods, lods + count); +} + +template +inline void WriterImpl::setJointGroupInputIndices(std::uint16_t jointGroupIndex, + const std::uint16_t* inputIndices, + std::uint16_t count) { + auto& jointGroups = dna.behavior.joints.jointGroups; + ensureHasSize(jointGroups, jointGroupIndex + 1ul, memRes); + jointGroups[jointGroupIndex].inputIndices.assign(inputIndices, inputIndices + count); +} + +template +inline void WriterImpl::setJointGroupOutputIndices(std::uint16_t jointGroupIndex, + const std::uint16_t* outputIndices, + std::uint16_t count) { + auto& jointGroups = dna.behavior.joints.jointGroups; + ensureHasSize(jointGroups, jointGroupIndex + 1ul, memRes); + jointGroups[jointGroupIndex].outputIndices.assign(outputIndices, outputIndices + count); +} + +template +inline void WriterImpl::setJointGroupValues(std::uint16_t jointGroupIndex, const float* values, + std::uint32_t count) { + auto& jointGroups = dna.behavior.joints.jointGroups; + ensureHasSize(jointGroups, jointGroupIndex + 1ul, memRes); + jointGroups[jointGroupIndex].values.assign(values, values + count); +} + +template +inline void WriterImpl::setJointGroupJointIndices(std::uint16_t jointGroupIndex, + const std::uint16_t* jointIndices, + std::uint16_t count) { + auto& jointGroups = dna.behavior.joints.jointGroups; + ensureHasSize(jointGroups, jointGroupIndex + 1ul, memRes); + jointGroups[jointGroupIndex].jointIndices.assign(jointIndices, jointIndices + count); +} + +template +inline void WriterImpl::setBlendShapeChannelLODs(const std::uint16_t* lods, std::uint16_t count) { + dna.behavior.blendShapeChannels.lods.assign(lods, lods + count); +} + +template +inline void WriterImpl::setBlendShapeChannelInputIndices(const std::uint16_t* inputIndices, std::uint16_t count) { + dna.behavior.blendShapeChannels.inputIndices.assign(inputIndices, inputIndices + count); +} + +template +inline void WriterImpl::setBlendShapeChannelOutputIndices(const std::uint16_t* outputIndices, std::uint16_t count) { + dna.behavior.blendShapeChannels.outputIndices.assign(outputIndices, outputIndices + count); +} + +template +inline void WriterImpl::setAnimatedMapLODs(const std::uint16_t* lods, std::uint16_t count) { + dna.behavior.animatedMaps.lods.assign(lods, lods + count); +} + +template +inline void WriterImpl::setAnimatedMapInputIndices(const std::uint16_t* inputIndices, std::uint16_t count) { + dna.behavior.animatedMaps.conditionals.inputIndices.assign(inputIndices, inputIndices + count); +} + +template +inline void WriterImpl::setAnimatedMapOutputIndices(const std::uint16_t* outputIndices, std::uint16_t count) { + dna.behavior.animatedMaps.conditionals.outputIndices.assign(outputIndices, outputIndices + count); +} + +template +inline void WriterImpl::setAnimatedMapFromValues(const float* fromValues, std::uint16_t count) { + dna.behavior.animatedMaps.conditionals.fromValues.assign(fromValues, fromValues + count); +} + +template +inline void WriterImpl::setAnimatedMapToValues(const float* toValues, std::uint16_t count) { + dna.behavior.animatedMaps.conditionals.toValues.assign(toValues, toValues + count); +} + +template +inline void WriterImpl::setAnimatedMapSlopeValues(const float* slopeValues, std::uint16_t count) { + dna.behavior.animatedMaps.conditionals.slopeValues.assign(slopeValues, slopeValues + count); +} + +template +inline void WriterImpl::setAnimatedMapCutValues(const float* cutValues, std::uint16_t count) { + dna.behavior.animatedMaps.conditionals.cutValues.assign(cutValues, cutValues + count); +} + +template +inline void WriterImpl::clearMeshes() { + dna.geometry.meshes.clear(); +} + +template +inline void WriterImpl::deleteMesh(std::uint16_t meshIndex) { + if (meshIndex < dna.geometry.meshes.size()) { + auto it = extd::advanced(dna.geometry.meshes.begin(), meshIndex); + dna.geometry.meshes.erase(it); + } +} + +template +inline void WriterImpl::setVertexPositions(std::uint16_t meshIndex, const Position* positions, std::uint32_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + dna.geometry.meshes[meshIndex].positions.assign(positions, positions + count); +} + +template +inline void WriterImpl::setVertexTextureCoordinates(std::uint16_t meshIndex, + const TextureCoordinate* textureCoordinates, + std::uint32_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + auto& destination = dna.geometry.meshes[meshIndex].textureCoordinates; + destination.clear(); + destination.us.resize_uninitialized(count); + destination.vs.resize_uninitialized(count); + for (std::size_t i = 0ul; i < count; ++i) { + destination.us[i] = textureCoordinates[i].u; + destination.vs[i] = textureCoordinates[i].v; + } +} + +template +inline void WriterImpl::setVertexNormals(std::uint16_t meshIndex, const Normal* normals, std::uint32_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + dna.geometry.meshes[meshIndex].normals.assign(normals, normals + count); +} + +template +inline void WriterImpl::setVertexLayouts(std::uint16_t meshIndex, const VertexLayout* layouts, std::uint32_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + auto& destination = dna.geometry.meshes[meshIndex].layouts; + destination.clear(); + destination.positions.resize_uninitialized(count); + destination.textureCoordinates.resize_uninitialized(count); + destination.normals.resize_uninitialized(count); + for (std::size_t i = 0ul; i < count; ++i) { + destination.positions[i] = layouts[i].position; + destination.textureCoordinates[i] = layouts[i].textureCoordinate; + destination.normals[i] = layouts[i].normal; + } +} + +template +inline void WriterImpl::clearFaceVertexLayoutIndices(std::uint16_t meshIndex) { + if (meshIndex < dna.geometry.meshes.size()) { + dna.geometry.meshes[meshIndex].faces.clear(); + } +} + +template +inline void WriterImpl::setFaceVertexLayoutIndices(std::uint16_t meshIndex, + std::uint32_t faceIndex, + const std::uint32_t* layoutIndices, + std::uint32_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + auto& faces = dna.geometry.meshes[meshIndex].faces; + ensureHasSize(faces, faceIndex + 1ul, memRes); + faces[faceIndex].layoutIndices.assign(layoutIndices, layoutIndices + count); +} + +template +inline void WriterImpl::setMaximumInfluencePerVertex(std::uint16_t meshIndex, std::uint16_t maxInfluenceCount) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + dna.geometry.meshes[meshIndex].maximumInfluencePerVertex = maxInfluenceCount; +} + +template +inline void WriterImpl::clearSkinWeights(std::uint16_t meshIndex) { + if (meshIndex < dna.geometry.meshes.size()) { + dna.geometry.meshes[meshIndex].skinWeights.clear(); + } +} + +template +inline void WriterImpl::setSkinWeightsValues(std::uint16_t meshIndex, + std::uint32_t vertexIndex, + const float* weights, + std::uint16_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + auto& skinWeights = dna.geometry.meshes[meshIndex].skinWeights; + ensureHasSize(skinWeights, vertexIndex + 1ul, memRes); + skinWeights[vertexIndex].weights.assign(weights, weights + count); +} + +template +inline void WriterImpl::setSkinWeightsJointIndices(std::uint16_t meshIndex, + std::uint32_t vertexIndex, + const std::uint16_t* jointIndices, + std::uint16_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + auto& skinWeights = dna.geometry.meshes[meshIndex].skinWeights; + ensureHasSize(skinWeights, vertexIndex + 1ul, memRes); + skinWeights[vertexIndex].jointIndices.assign(jointIndices, jointIndices + count); +} + +template +inline void WriterImpl::clearBlendShapeTargets(std::uint16_t meshIndex) { + if (meshIndex < dna.geometry.meshes.size()) { + dna.geometry.meshes[meshIndex].blendShapeTargets.clear(); + } +} + +template +inline void WriterImpl::setBlendShapeChannelIndex(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + std::uint16_t blendShapeChannelIndex) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + ensureHasSize(dna.geometry.meshes[meshIndex].blendShapeTargets, blendShapeTargetIndex + 1ul, memRes); + dna.geometry.meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].blendShapeChannelIndex = blendShapeChannelIndex; +} + +template +inline void WriterImpl::setBlendShapeTargetDeltas(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + const Delta* deltas, + std::uint32_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + ensureHasSize(dna.geometry.meshes[meshIndex].blendShapeTargets, blendShapeTargetIndex + 1ul, memRes); + dna.geometry.meshes[meshIndex].blendShapeTargets[blendShapeTargetIndex].deltas.assign(deltas, deltas + count); +} + +template +inline void WriterImpl::setBlendShapeTargetVertexIndices(std::uint16_t meshIndex, + std::uint16_t blendShapeTargetIndex, + const std::uint32_t* vertexIndices, + std::uint32_t count) { + ensureHasSize(dna.geometry.meshes, meshIndex + 1ul, memRes); + auto& blendShapeTargets = dna.geometry.meshes[meshIndex].blendShapeTargets; + ensureHasSize(blendShapeTargets, blendShapeTargetIndex + 1ul, memRes); + blendShapeTargets[blendShapeTargetIndex].vertexIndices.assign(vertexIndices, vertexIndices + count); +} + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/filters/AnimatedMapFilter.cpp b/dnacalib/DNACalib/src/dnacalib/dna/filters/AnimatedMapFilter.cpp new file mode 100644 index 0000000..17f41ee --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/filters/AnimatedMapFilter.cpp @@ -0,0 +1,90 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/dna/filters/AnimatedMapFilter.h" + +#include "dnacalib/TypeDefs.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/filters/Remap.h" +#include "dnacalib/utils/Extd.h" + +namespace dnac { + +AnimatedMapFilter::AnimatedMapFilter(MemoryResource* memRes_) : + memRes{memRes_}, + passingIndices{memRes}, + remappedIndices{memRes} { +} + +void AnimatedMapFilter::configure(std::uint16_t animatedMapCount, UnorderedSet allowedAnimatedMapIndices, + Matrix lodIndices) { + passingIndices = std::move(allowedAnimatedMapIndices); + // Fill the structure that maps indices prior to deletion to indices after deletion + remap(animatedMapCount, passingIndices, remappedIndices); + animatedMapLODIndices = std::move(lodIndices); +} + +void AnimatedMapFilter::apply(RawDefinition& dest) { + // Fix indices so they match the same elements as earlier (but their + // actual position changed with the deletion of the unneeded entries) + dest.lodAnimatedMapMapping.mapIndices([this](std::uint16_t value) { + return remappedIndices.at(value); + }); + // Delete elements that are not referenced by the new subset of LODs + extd::filter(dest.animatedMapNames, extd::byPosition(passingIndices)); +} + +void AnimatedMapFilter::apply(RawBehavior& dest) { + UnorderedSet indicesToDelete{memRes}; + + // Remove output indices of animated maps to remove and update LODs + extd::filter(dest.animatedMaps.conditionals.outputIndices, + [this, &indicesToDelete, &dest](std::uint16_t outputIndex, std::size_t index) { + if (!passes(outputIndex)) { + indicesToDelete.insert(index); + for (std::uint16_t lodIndex = 0; lodIndex < static_cast(animatedMapLODIndices.size()); ++lodIndex) { + const auto& lodIndices = animatedMapLODIndices[lodIndex]; + if (extd::contains(lodIndices, outputIndex)) { + dest.animatedMaps.lods[lodIndex]--; + } + } + return false; + } + return true; + }); + + // Remap remaining output indices + for (auto& outputIdx : dest.animatedMaps.conditionals.outputIndices) { + outputIdx = remappedIndices[outputIdx]; + } + + // Remove input indices associated with the removed output indices + extd::filter(dest.animatedMaps.conditionals.inputIndices, [&indicesToDelete](std::uint16_t /*unused*/, std::size_t index) { + return (indicesToDelete.find(index) == indicesToDelete.end()); + }); + + // Remove from values associated with the removed output indices + extd::filter(dest.animatedMaps.conditionals.fromValues, [&indicesToDelete](float /*unused*/, std::size_t index) { + return (indicesToDelete.find(index) == indicesToDelete.end()); + }); + + // Remove to values associated with the removed output indices + extd::filter(dest.animatedMaps.conditionals.toValues, [&indicesToDelete](float /*unused*/, std::size_t index) { + return (indicesToDelete.find(index) == indicesToDelete.end()); + }); + + // Remove slope values associated with the removed output indices + extd::filter(dest.animatedMaps.conditionals.slopeValues, [&indicesToDelete](float /*unused*/, std::size_t index) { + return (indicesToDelete.find(index) == indicesToDelete.end()); + }); + + // Remove cut values associated with the removed output indices + extd::filter(dest.animatedMaps.conditionals.cutValues, [&indicesToDelete](float /*unused*/, std::size_t index) { + return (indicesToDelete.find(index) == indicesToDelete.end()); + }); +} + +bool AnimatedMapFilter::passes(std::uint16_t index) const { + return extd::contains(passingIndices, index); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/filters/AnimatedMapFilter.h b/dnacalib/DNACalib/src/dnacalib/dna/filters/AnimatedMapFilter.h new file mode 100644 index 0000000..709633b --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/filters/AnimatedMapFilter.h @@ -0,0 +1,32 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/TypeDefs.h" + +#include + +namespace dnac { + +struct RawBehavior; +struct RawDefinition; + +class AnimatedMapFilter { + public: + explicit AnimatedMapFilter(MemoryResource* memRes_); + void configure(std::uint16_t animatedMapCount, + UnorderedSet allowedAnimatedMapIndices, + Matrix lodIndices); + void apply(RawDefinition& dest); + void apply(RawBehavior& dest); + bool passes(std::uint16_t index) const; + + private: + MemoryResource* memRes; + UnorderedSet passingIndices; + UnorderedMap remappedIndices; + Matrix animatedMapLODIndices; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/filters/BlendShapeFilter.cpp b/dnacalib/DNACalib/src/dnacalib/dna/filters/BlendShapeFilter.cpp new file mode 100644 index 0000000..5df64f8 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/filters/BlendShapeFilter.cpp @@ -0,0 +1,86 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/dna/filters/BlendShapeFilter.h" + +#include "dnacalib/TypeDefs.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/filters/Remap.h" +#include "dnacalib/utils/Extd.h" + +namespace dnac { + +BlendShapeFilter::BlendShapeFilter(MemoryResource* memRes_) : + memRes{memRes_}, + passingIndices{memRes}, + remappedIndices{memRes}, + newBlendShapeLODs{memRes} { +} + +void BlendShapeFilter::configure(std::uint16_t blendShapeCount, UnorderedSet allowedBlendShapeIndices, + Vector blendShapeLODs) { + passingIndices = std::move(allowedBlendShapeIndices); + // Fill the structure that maps indices prior to deletion to indices after deletion + remap(blendShapeCount, passingIndices, remappedIndices); + newBlendShapeLODs = std::move(blendShapeLODs); +} + +void BlendShapeFilter::apply(RawDefinition& dest) { + // Fix indices so they match the same elements as earlier (but their + // actual position changed with the deletion of the unneeded entries) + dest.lodBlendShapeMapping.mapIndices([this](std::uint16_t value) { + return remappedIndices.at(value); + }); + // Delete elements that are not referenced by the new subset of LODs + extd::filter(dest.blendShapeChannelNames, extd::byPosition(passingIndices)); + // Delete entries from other mappings that reference any of the deleted elements + auto ignoredByLODConstraint = [this](std::uint16_t /*unused*/, std::uint16_t blendShapeIndex) { + return !extd::contains(passingIndices, blendShapeIndex); + }; + dest.meshBlendShapeChannelMapping.removeIf(ignoredByLODConstraint); + dest.meshBlendShapeChannelMapping.updateTo(remappedIndices); +} + +void BlendShapeFilter::apply(RawBehavior& dest) { + UnorderedSet indicesToDelete{memRes}; + + // Remove output indices of blend shapes to remove + extd::filter(dest.blendShapeChannels.outputIndices, [this, &indicesToDelete](std::uint16_t outputIndex, std::size_t index) { + if (!passes(outputIndex)) { + indicesToDelete.insert(index); + return false; + } + return true; + }); + + // Remap remaining output indices + for (auto& outputIdx : dest.blendShapeChannels.outputIndices) { + outputIdx = remappedIndices[outputIdx]; + } + + // Remove input indices associated with the removed output indices + extd::filter(dest.blendShapeChannels.inputIndices, [&indicesToDelete](std::uint16_t /*unused*/, std::size_t index) { + return (indicesToDelete.find(index) == indicesToDelete.end()); + }); + + // Set new LODs + assert(newBlendShapeLODs.size() == dest.blendShapeChannels.lods.size()); + dest.blendShapeChannels.lods.assign(newBlendShapeLODs.begin(), newBlendShapeLODs.end()); +} + +void BlendShapeFilter::apply(RawMesh& dest) { + // Remove blend shape targets of blend shapes to remove + extd::filter(dest.blendShapeTargets, [this](const RawBlendShapeTarget& bsTarget, std::size_t /*unused*/) { + return passes(bsTarget.blendShapeChannelIndex); + }); + + // Remap blend shape targets + for (auto& bsTarget : dest.blendShapeTargets) { + bsTarget.blendShapeChannelIndex = remappedIndices[bsTarget.blendShapeChannelIndex]; + } +} + +bool BlendShapeFilter::passes(std::uint16_t index) const { + return extd::contains(passingIndices, index); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/filters/BlendShapeFilter.h b/dnacalib/DNACalib/src/dnacalib/dna/filters/BlendShapeFilter.h new file mode 100644 index 0000000..7a75e0e --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/filters/BlendShapeFilter.h @@ -0,0 +1,34 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/TypeDefs.h" + +#include + +namespace dnac { + +struct RawBehavior; +struct RawDefinition; +struct RawMesh; + +class BlendShapeFilter { + public: + explicit BlendShapeFilter(MemoryResource* memRes_); + void configure(std::uint16_t blendShapeCount, + UnorderedSet allowedBlendShapeIndices, + Vector blendShapeLODs); + void apply(RawDefinition& dest); + void apply(RawBehavior& dest); + void apply(RawMesh& dest); + bool passes(std::uint16_t index) const; + + private: + MemoryResource* memRes; + UnorderedSet passingIndices; + UnorderedMap remappedIndices; + Vector newBlendShapeLODs; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/filters/JointFilter.cpp b/dnacalib/DNACalib/src/dnacalib/dna/filters/JointFilter.cpp new file mode 100644 index 0000000..e9f7cd8 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/filters/JointFilter.cpp @@ -0,0 +1,181 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/dna/filters/JointFilter.h" + +#include "dnacalib/TypeDefs.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/filters/Remap.h" +#include "dnacalib/utils/Extd.h" + +namespace dnac { + +JointFilter::JointFilter(MemoryResource* memRes_) : + memRes{memRes_}, + passingIndices{memRes_}, + remappedIndices{memRes_}, + option{Option::All}, + rootJointIndex{} { +} + +void JointFilter::configure(std::uint16_t jointCount, UnorderedSet allowedJointIndices, Option option_) { + option = option_; + passingIndices = std::move(allowedJointIndices); + // Fill the structure that maps indices prior to deletion to indices after deletion + remap(jointCount, passingIndices, remappedIndices); +} + +void JointFilter::apply(RawDefinition& dest) { + if (option != Option::All) { + return; + } + // Fix indices so they match the same elements as earlier (but their + // actual position changed with the deletion of the unneeded entries) + dest.lodJointMapping.mapIndices([this](std::uint16_t value) { + return remappedIndices.at(value); + }); + // Delete elements that are not referenced by the new subset of LODs + extd::filter(dest.jointNames, extd::byPosition(passingIndices)); + extd::filter(dest.jointHierarchy, extd::byPosition(passingIndices)); + // Fix joint hierarchy indices + for (auto& jntIdx : dest.jointHierarchy) { + jntIdx = remappedIndices[jntIdx]; + } + // Find root joint index + for (std::uint16_t jointIdx = 0u; jointIdx < dest.jointHierarchy.size(); ++jointIdx) { + if (dest.jointHierarchy[jointIdx] == jointIdx) { + rootJointIndex = jointIdx; + break; + } + } + // Delete entries from other mappings that reference any of the deleted elements + extd::filter(dest.neutralJointTranslations.xs, extd::byPosition(passingIndices)); + extd::filter(dest.neutralJointTranslations.ys, extd::byPosition(passingIndices)); + extd::filter(dest.neutralJointTranslations.zs, extd::byPosition(passingIndices)); + extd::filter(dest.neutralJointRotations.xs, extd::byPosition(passingIndices)); + extd::filter(dest.neutralJointRotations.ys, extd::byPosition(passingIndices)); + extd::filter(dest.neutralJointRotations.zs, extd::byPosition(passingIndices)); +} + +void JointFilter::apply(RawBehavior& dest) { + static constexpr std::uint16_t jointAttributeCount = 9u; + + for (auto& jointGroup : dest.joints.jointGroups) { + if (option == Option::All) { + // Remove joint index from joint group and remap joint indices + extd::filter(jointGroup.jointIndices, [this](std::uint16_t jntIdx, std::size_t /*unused*/) { + return passes(jntIdx); + }); + for (auto& jntIdx : jointGroup.jointIndices) { + jntIdx = remapped(jntIdx); + } + } + // Collect row indices of removed output indices to be used for joint delta removal + UnorderedSet rowsToDelete{memRes}; + // Remove output indices belonging to the deletable joint + extd::filter(jointGroup.outputIndices, [this, &rowsToDelete](std::uint16_t outputIndex, std::size_t rowIndex) { + const auto jointIndex = static_cast(outputIndex / jointAttributeCount); + if (!passes(jointIndex)) { + rowsToDelete.insert(rowIndex); + return false; + } + return true; + }); + + if (option == Option::All) { + // Remap the rest of output indices + for (auto& attrIdx : jointGroup.outputIndices) { + const auto jntIdx = static_cast(attrIdx / jointAttributeCount); + const auto relAttrIdx = attrIdx - (jntIdx * jointAttributeCount); + attrIdx = static_cast(remapped(jntIdx) * jointAttributeCount + relAttrIdx); + } + } + + // If no animation data remains, there's no point in keeping input indices + const auto jointGroupColumnCount = static_cast(jointGroup.inputIndices.size()); + if (jointGroup.outputIndices.empty()) { + jointGroup.inputIndices.clear(); + } + + // Remove joint deltas associated with the removed output indices + extd::filter(jointGroup.values, [&rowsToDelete, jointGroupColumnCount](float /*unused*/, std::size_t index) { + const std::uint16_t rowIndex = static_cast(index / jointGroupColumnCount); + return (rowsToDelete.find(rowIndex) == rowsToDelete.end()); + }); + // Recompute LODs + for (auto& lod : jointGroup.lods) { + std::uint16_t decrementBy = 0u; + for (const auto rowIndex : rowsToDelete) { + if (rowIndex < lod) { + ++decrementBy; + } + } + lod = static_cast(lod - decrementBy); + } + } +} + +void JointFilter::apply(RawVertexSkinWeights& dest) { + if (option != Option::All) { + return; + } + + auto itWeightSrc = dest.weights.begin(); + auto itWeightDst = itWeightSrc; + auto itJointSrc = dest.jointIndices.begin(); + auto itJointDst = itJointSrc; + float discardedWeights = 0.0f; + while (itJointSrc != dest.jointIndices.end()) { + if (passes(*itJointSrc)) { + *itJointDst = *itJointSrc; + ++itJointSrc; + ++itJointDst; + + *itWeightDst = *itWeightSrc; + ++itWeightSrc; + ++itWeightDst; + } else { + discardedWeights += *itWeightSrc; + ++itJointSrc; + ++itWeightSrc; + } + } + dest.jointIndices.resize(static_cast(std::distance(dest.jointIndices.begin(), itJointDst))); + dest.weights.resize(static_cast(std::distance(dest.weights.begin(), itWeightDst))); + assert(dest.jointIndices.size() == dest.weights.size()); + + if (passingIndices.empty()) { + return; + } + + if (dest.jointIndices.empty()) { + // Reassign complete influence to root joint + dest.jointIndices.resize_uninitialized(1ul); + dest.jointIndices[0ul] = rootJointIndex; + dest.weights.resize_uninitialized(1ul); + dest.weights[0ul] = 1.0f; + } else { + // Normalize weights + for (auto& jntIdx : dest.jointIndices) { + jntIdx = remapped(jntIdx); + } + + const float normalizationRatio = 1.0f / (1.0f - discardedWeights); + for (auto& weight : dest.weights) { + weight *= normalizationRatio; + } + } +} + +bool JointFilter::passes(std::uint16_t index) const { + return extd::contains(passingIndices, index); +} + +std::uint16_t JointFilter::remapped(std::uint16_t oldIndex) const { + return remappedIndices.at(oldIndex); +} + +std::uint16_t JointFilter::maxRemappedIndex() const { + return (remappedIndices.empty() ? static_cast(0) : extd::maxOf(remappedIndices).second); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/filters/JointFilter.h b/dnacalib/DNACalib/src/dnacalib/dna/filters/JointFilter.h new file mode 100644 index 0000000..26d2ea2 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/filters/JointFilter.h @@ -0,0 +1,42 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/TypeDefs.h" + +#include + +namespace dnac { + +struct RawBehavior; +struct RawDefinition; +struct RawLODMapping; +struct RawVertexSkinWeights; + +class JointFilter { + public: + enum class Option { + All, + AnimationOnly + }; + + public: + explicit JointFilter(MemoryResource* memRes_); + void configure(std::uint16_t jointCount, UnorderedSet allowedJointIndices, Option option_ = Option::All); + void apply(RawDefinition& dest); + void apply(RawBehavior& dest); + void apply(RawVertexSkinWeights& dest); + bool passes(std::uint16_t index) const; + std::uint16_t remapped(std::uint16_t oldIndex) const; + std::uint16_t maxRemappedIndex() const; + + private: + MemoryResource* memRes; + UnorderedSet passingIndices; + UnorderedMap remappedIndices; + Option option; + std::uint16_t rootJointIndex; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/filters/MeshFilter.cpp b/dnacalib/DNACalib/src/dnacalib/dna/filters/MeshFilter.cpp new file mode 100644 index 0000000..f6c8a9b --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/filters/MeshFilter.cpp @@ -0,0 +1,44 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/dna/filters/MeshFilter.h" + +#include "dnacalib/TypeDefs.h" +#include "dnacalib/dna/DNA.h" +#include "dnacalib/dna/filters/Remap.h" +#include "dnacalib/utils/Extd.h" + +namespace dnac { + +MeshFilter::MeshFilter(MemoryResource* memRes_) : + memRes{memRes_}, + passingIndices{memRes}, + remappedIndices{memRes} { +} + +void MeshFilter::configure(std::uint16_t meshCount, UnorderedSet allowedMeshIndices) { + passingIndices = std::move(allowedMeshIndices); + // Fill the structure that maps indices prior to deletion to indices after deletion + remap(meshCount, passingIndices, remappedIndices); +} + +void MeshFilter::apply(RawDefinition& dest) { + // Fix indices so they match the same elements as earlier (but their + // actual position changed with the deletion of the unneeded entries) + dest.lodMeshMapping.mapIndices([this](std::uint16_t value) { + return remappedIndices.at(value); + }); + // Delete elements that are not referenced by the new subset of LODs + extd::filter(dest.meshNames, extd::byPosition(passingIndices)); + // Delete entries from other mappings that reference any of the deleted elements + auto ignoredByLODConstraint = [this](std::uint16_t meshIndex, std::uint16_t /*unused*/) { + return !extd::contains(passingIndices, meshIndex); + }; + dest.meshBlendShapeChannelMapping.removeIf(ignoredByLODConstraint); + dest.meshBlendShapeChannelMapping.updateFrom(remappedIndices); +} + +bool MeshFilter::passes(std::uint16_t index) const { + return extd::contains(passingIndices, index); +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/filters/MeshFilter.h b/dnacalib/DNACalib/src/dnacalib/dna/filters/MeshFilter.h new file mode 100644 index 0000000..2c11cc3 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/filters/MeshFilter.h @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/TypeDefs.h" + +#include + +namespace dnac { + +struct RawDefinition; + +class MeshFilter { + public: + explicit MeshFilter(MemoryResource* memRes_); + void configure(std::uint16_t meshCount, UnorderedSet allowedMeshIndices); + void apply(RawDefinition& dest); + bool passes(std::uint16_t index) const; + + private: + MemoryResource* memRes; + UnorderedSet passingIndices; + UnorderedMap remappedIndices; + +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/dna/filters/Remap.h b/dnacalib/DNACalib/src/dnacalib/dna/filters/Remap.h new file mode 100644 index 0000000..25aa9dc --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/dna/filters/Remap.h @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/TypeDefs.h" +#include "dnacalib/utils/Extd.h" + +namespace dnac { + +template +inline void remap(T originalCount, const UnorderedSet& keptIndices, UnorderedMap& mapping) { + for (T oldIndex{}, newIndex{}; oldIndex < originalCount; ++oldIndex) { + if (extd::contains(keptIndices, oldIndex)) { + mapping.insert({oldIndex, newIndex}); + ++newIndex; + } + } +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/types/BoundingBox.h b/dnacalib/DNACalib/src/dnacalib/types/BoundingBox.h new file mode 100644 index 0000000..051524a --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/types/BoundingBox.h @@ -0,0 +1,74 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/types/Aliases.h" +#include "dnacalib/TypeDefs.h" + +#include +#include + +namespace dnac { + +class BoundingBox { + public: + static constexpr float defaultAlpha = 0.0003f; + + private: + template + using unqualified_iter_value_type = typename std::remove_cv::value_type>::type; + + public: + template, fvec2>::value, void>::type* = nullptr> + BoundingBox(TIter begin, TIter end, float alpha) : + min{std::numeric_limits::max(), std::numeric_limits::max()}, + max{std::numeric_limits::min(), std::numeric_limits::min()} { + + for (auto it = begin; it < end; it++) { + const auto& vertex = *it; + min[0] = std::min(vertex[0], min[0]); + min[1] = std::min(vertex[1], min[1]); + max[0] = std::max(vertex[0], max[0]); + max[1] = std::max(vertex[1], max[1]); + } + min -= alpha; + max += alpha; + } + + template + explicit BoundingBox(TContainer container, float alpha = defaultAlpha) : + BoundingBox(std::begin(container), std::end(container), alpha) { + } + + bool contains(fvec2 point) const { + return point[0] >= min[0] && point[0] <= max[0] && + point[1] >= min[1] && point[1] <= max[1]; + } + + float distance(fvec2 point) const { + const float dx = std::max({min[0] - point[0], 0.0f, point[0] - max[0]}); + const float dy = std::max({min[1] - point[1], 0.0f, point[1] - max[1]}); + return std::sqrt(dx * dx + dy * dy); + } + + bool overlaps(const BoundingBox& other) const { + return (max[0] >= other.min[0] && other.max[0] >= min[0]) && + (max[1] >= other.min[1] && other.max[1] >= min[1]); + } + + fvec2 getMin() const { + return min; + } + + fvec2 getMax() const { + return max; + } + + private: + fvec2 min; + fvec2 max; +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/types/Triangle.cpp b/dnacalib/DNACalib/src/dnacalib/types/Triangle.cpp new file mode 100644 index 0000000..4247abd --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/types/Triangle.cpp @@ -0,0 +1,46 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/types/Triangle.h" + +namespace dnac { + +Triangle::Triangle(const fvec2& a_, const fvec2& b, const fvec2& c) : + a{a_}, + v0{b - a}, + v1{c - a}, + d00{tdm::dot(v0, v0)}, + d01{tdm::dot(v0, v1)}, + d11{tdm::dot(v1, v1)}, + denom{d00 * d11 - d01 * d01} { +} + +Triangle::Triangle(const std::array& vertices) : + Triangle(vertices[0], vertices[1], vertices[2]) { +} + +fvec2 Triangle::A() const { + return a; +} + +fvec2 Triangle::B() const { + return v0 + a; +} + +fvec2 Triangle::C() const { + return v1 + a; +} + +fvec3 Triangle::getBarycentricCoords(const fvec2& point) const { + // Real-Time Collision Detection The Morgan Kaufmann Series, chapter 3.4 Barycentric Coordinates + const fvec2 v2 = point - a; + + const float d20 = tdm::dot(v2, v0); + const float d21 = tdm::dot(v2, v1); + + const float v = (d11 * d20 - d01 * d21) / denom; + const float w = (d00 * d21 - d01 * d20) / denom; + const float u = 1.0f - v - w; + return {u, v, w}; +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/types/Triangle.h b/dnacalib/DNACalib/src/dnacalib/types/Triangle.h new file mode 100644 index 0000000..4b72116 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/types/Triangle.h @@ -0,0 +1,33 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/TypeDefs.h" + +#include + +namespace dnac { + +class Triangle { + + public: + Triangle(const fvec2& a, const fvec2& b, const fvec2& c); + explicit Triangle(const std::array& vertices); + + fvec3 getBarycentricCoords(const fvec2& point) const; + + fvec2 A() const; + fvec2 B() const; + fvec2 C() const; + + private: + fvec2 a; + fvec2 v0; // b-a + fvec2 v1; // c-a; + float d00; // v0 dot v0 + float d01; // v0 dot v1 + float d11; // v1 dot v1 + float denom; // d00 * d11 - d01 * d01; +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/types/UVBarycentricMapping.cpp b/dnacalib/DNACalib/src/dnacalib/types/UVBarycentricMapping.cpp new file mode 100644 index 0000000..ceb2518 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/types/UVBarycentricMapping.cpp @@ -0,0 +1,85 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/types/UVBarycentricMapping.h" + +namespace dnac { + +UVBarycentricMapping::UVBarycentricMapping(const std::function(std::uint32_t)>& faceGetter, + ConstArrayView vertexPositionIndices, + ConstArrayView textureCoordinateUVIndices, + ConstArrayView Us, + ConstArrayView Vs, + std::uint32_t faceCount, + MemoryResource* memRes) : + triangles{memRes}, + trianglePositionIndices{memRes} { + + auto estimatedTriangleCount = static_cast(static_cast(faceCount) * 2.5f); + triangles.reserve(estimatedTriangleCount); + boundingBoxes.reserve(estimatedTriangleCount); + trianglePositionIndices.reserve(estimatedTriangleCount); + + for (std::uint32_t fi = 0u; fi < faceCount; fi++) { + auto face = faceGetter(fi); + if (face.size() > 2) { + const auto n = face.size(); + for (std::uint32_t i = 0; i < n - 2; ++i) { + for (std::uint32_t j = i + 1; j < n - 1; ++j) { + for (std::uint32_t k = j + 1; k < n; ++k) { + const auto vli0 = face[i]; + const auto vli1 = face[j]; + const auto vli2 = face[k]; + const std::array positionIndices {vertexPositionIndices[vli0], + vertexPositionIndices[vli1], + vertexPositionIndices[vli2]}; + const auto uvIndex0 = textureCoordinateUVIndices[vli0]; + const auto uvIndex1 = textureCoordinateUVIndices[vli1]; + const auto uvIndex2 = textureCoordinateUVIndices[vli2]; + + const std::array UVs = {fvec2{Us[uvIndex0], Vs[uvIndex0]}, + fvec2{Us[uvIndex1], Vs[uvIndex1]}, + fvec2{Us[uvIndex2], Vs[uvIndex2]}}; + triangles.emplace_back(UVs); + boundingBoxes.emplace_back(UVs); + trianglePositionIndices.emplace_back(positionIndices); + } + } + } + } + } + triangles.shrink_to_fit(); + boundingBoxes.shrink_to_fit(); + trianglePositionIndices.shrink_to_fit(); +} + +UVBarycentricMapping::BarycentricPositionIndicesPair UVBarycentricMapping::getBarycentric(fvec2 uv) const { + const auto isPointInsideTriangle = [](const fvec3& barycentricPoint) { + return barycentricPoint[0] > 0.0f && barycentricPoint[1] > 0.0f && barycentricPoint[2] > 0.0f; + }; + for (std::uint32_t i = 0; i < triangles.size(); i++) { + const auto& triangle = triangles[i]; + // we check if point is inside triangle (all barycentric coordinates are positive) + if (boundingBoxes[i].contains(uv)) { + const auto barycentricPoint = triangle.getBarycentricCoords(uv); + if (isPointInsideTriangle(barycentricPoint)) { + return BarycentricPositionIndicesPair{barycentricPoint, + ConstArrayView{trianglePositionIndices[i]}}; + } + } + } + return {}; +} + +const Triangle& UVBarycentricMapping::getTriangle(std::uint32_t index) const { + return triangles[index]; +} + +ConstArrayView UVBarycentricMapping::getTrianglePositionIndices(std::uint32_t index) const { + return trianglePositionIndices[index]; +} + +ConstArrayView UVBarycentricMapping::getBoundingBoxes() const { + return boundingBoxes; +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/types/UVBarycentricMapping.h b/dnacalib/DNACalib/src/dnacalib/types/UVBarycentricMapping.h new file mode 100644 index 0000000..71b3fda --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/types/UVBarycentricMapping.h @@ -0,0 +1,36 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/types/Aliases.h" +#include "dnacalib/types/BoundingBox.h" +#include "dnacalib/types/Triangle.h" + +namespace dnac { + +class UVBarycentricMapping { + public: + using TrianglePositionIndicesPair = std::tuple >; + using BarycentricPositionIndicesPair = std::tuple >; + + public: + UVBarycentricMapping(const std::function(std::uint32_t)>& faceGetter, + ConstArrayView vertexPositionIndices, + ConstArrayView textureCoordinateUVIndices, + ConstArrayView Us, + ConstArrayView Vs, + std::uint32_t faceCount, + MemoryResource* memRes); + + BarycentricPositionIndicesPair getBarycentric(fvec2 uv) const; + const Triangle& getTriangle(std::uint32_t index) const; + ConstArrayView getTrianglePositionIndices(std::uint32_t index) const; + ConstArrayView getBoundingBoxes() const; + + private: + Vector triangles; + Vector boundingBoxes; + Vector > trianglePositionIndices; +}; + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/utils/Algorithm.h b/dnacalib/DNACalib/src/dnacalib/utils/Algorithm.h new file mode 100644 index 0000000..65bacb0 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/utils/Algorithm.h @@ -0,0 +1,65 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/TypeDefs.h" + +namespace dnac { + +inline fmat4 getTransformationMatrix(const fvec3& translation, const fvec3& rotation, const fvec3& scale = {1.0f, 1.0f, 1.0f}) { + return tdm::scale(scale) * tdm::rotate(rotation) * tdm::translate(translation); +} + +inline fmat4 extractTranslationMatrix(const fmat4& transformationMatrix) { + auto t = fmat4::identity(); + t(3, 0) = transformationMatrix(3, 0); + t(3, 1) = transformationMatrix(3, 1); + t(3, 2) = transformationMatrix(3, 2); + return t; +} + +inline fvec3 extractScaleVector(const fmat4& transformationMatrix) { + const auto& m = transformationMatrix; + const float sx = fvec3{m(0, 0), m(0, 1), m(0, 2)}.length(); + const float sy = fvec3{m(1, 0), m(1, 1), m(1, 2)}.length(); + const float sz = fvec3{m(2, 0), m(2, 1), m(2, 2)}.length(); + return {sx, sy, sz}; +} + +inline fmat4 extractRotationMatrix(const fmat4& transformationMatrix) { + auto r = transformationMatrix; + r(3, 0) = 0.0f; + r(3, 1) = 0.0f; + r(3, 2) = 0.0f; + const auto scale = tdm::scale(extractScaleVector(transformationMatrix)); + const auto inverseScale = tdm::inverse(scale); + return inverseScale * r; +} + +inline fvec3 extractTranslationVector(const fmat4& transformationMatrix) { + return {transformationMatrix(3, 0), transformationMatrix(3, 1), transformationMatrix(3, 2)}; +} + +inline fvec3 extractRotationVector(const fmat4& transformationMatrix) { + fvec3 angle{}; + const auto r = extractRotationMatrix(transformationMatrix); + const auto r02 = r(0, 2); + if (r02 < 1.0f) { + if (r02 > -1.0f) { + angle[0] = std::atan2(r(1, 2), r(2, 2)); + angle[1] = std::asin(-r02); + angle[2] = std::atan2(r(0, 1), r(0, 0)); + } else { + angle[0] = std::atan2(-r(2, 1), r(1, 1)); + angle[1] = static_cast(tdm::pi() / 2.0f); + angle[2] = 0; + } + } else { + angle[0] = -std::atan2(-r(2, 1), r(1, 1)); + angle[1] = static_cast(-tdm::pi() / 2.0f); + angle[2] = 0; + } + return angle; +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/utils/Extd.h b/dnacalib/DNACalib/src/dnacalib/utils/Extd.h new file mode 100644 index 0000000..ab37928 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/utils/Extd.h @@ -0,0 +1,145 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +// *INDENT-OFF* +#ifndef EXTD_GUARD +#define EXTD_GUARD +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace extd { + +template +inline T clamp(T value, T low, T high) { + return std::min(std::max(value, low), high); +} + +template +inline T roundUp(T number, T multiple) { + return ((number + multiple - 1) / multiple) * multiple; +} + +template +inline T interpolate(T a, T b, T weight) { + return a * (static_cast(1) - weight) + b * weight; +} + +template +inline bool contains(TInputIterator first, TInputIterator last, const T& value) { + return std::find(first, last, value) != last; +} + +template +inline bool contains(const TContainer& container, const T& value) { + return contains(std::begin(container), std::end(container), value); +} + +template +inline bool contains(const std::set& container, const T& value) { + return container.find(value) != container.end(); +} + +template +inline void filter(std::vector& source, Predicate pred) { + source.erase(std::remove_if(std::begin(source), std::end(source), [&source, &pred](const T& value) { + const auto index = static_cast(&value - &(source.front())); + return !pred(value, index); + }), source.end()); +} + +template +inline void filter(TContainer& source, Predicate pred) { + using value_type = typename TContainer::value_type; + auto newEnd = std::remove_if(std::begin(source), std::end(source), [&source, &pred](const value_type& value) { + const auto index = static_cast(&value - source.data()); + return !pred(value, index); + }); + const auto newSize = static_cast(std::distance(source.begin(), newEnd)); + source.resize(newSize); +} + +namespace impl { + +enum class LUTStrategy { + ByValue, + ByPosition +}; + +template +class LUTFilter { +public: + explicit LUTFilter(const TLUT& lut_) : lut{lut_} {} + + template + typename std::enable_if::type operator()(const T& value, std::size_t /*unused*/) { + return contains(lut, value); + } + + template + typename std::enable_if::type operator()(const T& /*unused*/, std::size_t index) { + return contains(lut, index); + } + +private: + const TLUT& lut; +}; + +} // namespace impl + +template +inline impl::LUTFilter byValue(const TLookUpTable& lookUpTable) { + return impl::LUTFilter{lookUpTable}; +} + +template +inline impl::LUTFilter byPosition(const TLookUpTable& lookUpTable) { + return impl::LUTFilter{lookUpTable}; +} + +template +inline typename TContainer::value_type maxOf(const TContainer& container) { + assert(!container.empty()); + using ValueType = typename TContainer::value_type; + const auto compare = [](const ValueType& lhs, const ValueType& rhs) { + return lhs.second < rhs.second; + }; + const auto it = std::max_element(container.begin(), container.end(), compare); + return (it == container.end() ? ValueType{} : *it); +} + +template +inline void copy(const TSource& source, TDestination& destination) { + std::copy(std::begin(source), std::end(source), std::back_inserter(destination)); +} + +template +TIterator advanced(TIterator source, TDistance distance) { + std::advance(source, static_cast::difference_type>(distance)); + return source; +} + +template +typename TIterator::difference_type advanceWhile(TIterator& it, const TIterator& end, Predicate pred) { + const auto start = it; + while (it != end && pred(*it)) { + ++it; + } + return std::distance(start, it); +} + +} // namespace extd + +#endif // EXTD_GUARD +// *INDENT-ON* diff --git a/dnacalib/DNACalib/src/dnacalib/utils/FormatString.h b/dnacalib/DNACalib/src/dnacalib/utils/FormatString.h new file mode 100644 index 0000000..8759361 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/utils/FormatString.h @@ -0,0 +1,28 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "dnacalib/TypeDefs.h" + +namespace dnac { + +template +String formatString(MemoryResource* memRes, const char* format, Args&& ... args) { + String result{memRes}; + result.resize(1024); + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wformat-security" + #endif + // The returned number of bytes to be written does not include the null terminator + const auto neededSize = snprintf(nullptr, 0ul, format, args ...) + 1; + const auto size = std::min(result.size(), static_cast(neededSize)); + snprintf(&result[0], size, format, args ...); + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + result.resize(size); + return result; +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/utils/ScopedEnumEx.h b/dnacalib/DNACalib/src/dnacalib/utils/ScopedEnumEx.h new file mode 100644 index 0000000..0250c38 --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/utils/ScopedEnumEx.h @@ -0,0 +1,61 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include + +namespace dnac { + +template +typename std::enable_if::value, TEnum>::type +operator&(TEnum lhs, TEnum rhs) { + using Underlying = typename std::underlying_type::type; + return static_cast(static_cast(lhs) & static_cast(rhs)); +} + +template +typename std::enable_if::value, TEnum>::type +operator|(TEnum lhs, TEnum rhs) { + using Underlying = typename std::underlying_type::type; + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +template +typename std::enable_if::value, TEnum>::type +operator^(TEnum lhs, TEnum rhs) { + using Underlying = typename std::underlying_type::type; + return static_cast(static_cast(lhs) ^ static_cast(rhs)); +} + +template +typename std::enable_if::value, TEnum>::type +operator~(TEnum value) { + using Underlying = typename std::underlying_type::type; + return static_cast(~static_cast(value)); +} + +template +typename std::enable_if::value, TEnum>::type +operator&=(TEnum& lhs, TEnum rhs) { + return lhs = (lhs & rhs); +} + +template +typename std::enable_if::value, TEnum>::type +operator|=(TEnum& lhs, TEnum rhs) { + return lhs = (lhs | rhs); +} + +template +typename std::enable_if::value, TEnum>::type +operator^=(TEnum& lhs, TEnum rhs) { + return lhs = (lhs ^ rhs); +} + +template +typename std::enable_if::value, bool>::type +contains(TEnum lhs, TEnum rhs) { + return (lhs & rhs) == rhs; +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/dnacalib/version/VersionInfo.cpp b/dnacalib/DNACalib/src/dnacalib/version/VersionInfo.cpp new file mode 100644 index 0000000..77617bd --- /dev/null +++ b/dnacalib/DNACalib/src/dnacalib/version/VersionInfo.cpp @@ -0,0 +1,36 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "dnacalib/version/VersionInfo.h" + +#include "dnacalib/version/Version.h" + +#include + +namespace dnac { + +namespace { + +constexpr int majorVersion = DNAC_MAJOR_VERSION; +constexpr int minorVersion = DNAC_MINOR_VERSION; +constexpr int patchVersion = DNAC_PATCH_VERSION; +constexpr const char* versionString = DNAC_VERSION_STRING; + +} // namespace + +int VersionInfo::getMajorVersion() { + return majorVersion; +} + +int VersionInfo::getMinorVersion() { + return minorVersion; +} + +int VersionInfo::getPatchVersion() { + return patchVersion; +} + +StringView VersionInfo::getVersionString() { + return {versionString, std::strlen(versionString)}; +} + +} // namespace dnac diff --git a/dnacalib/DNACalib/src/pma/MemoryResource.cpp b/dnacalib/DNACalib/src/pma/MemoryResource.cpp new file mode 100644 index 0000000..ebd6e04 --- /dev/null +++ b/dnacalib/DNACalib/src/pma/MemoryResource.cpp @@ -0,0 +1,9 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "pma/MemoryResource.h" + +namespace pma { + +MemoryResource::~MemoryResource() = default; + +} // namespace pma diff --git a/dnacalib/DNACalib/src/pma/resources/AlignedMemoryResource.cpp b/dnacalib/DNACalib/src/pma/resources/AlignedMemoryResource.cpp new file mode 100644 index 0000000..db699f0 --- /dev/null +++ b/dnacalib/DNACalib/src/pma/resources/AlignedMemoryResource.cpp @@ -0,0 +1,94 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "pma/resources/AlignedMemoryResource.h" + +#include +#include +#include +#include + +#if defined(__linux__) || defined(__APPLE__) + #include +#elif defined(_MSC_VER) || defined(__ANDROID__) + #include +#endif + +#if defined(__cplusplus) && (__cplusplus >= 201703L) && (defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) || \ + defined(_LIBCPP_HAS_ALIGNED_ALLOC) || defined(_LIBCPP_HAS_C11_FEATURES)) + #define ALIGNED_ALLOC(ptr, alignment, size) ptr = std::aligned_alloc(alignment, size) + #define ALIGNED_FREE(ptr) std::free(ptr) +#elif ((defined(_POSIX_VERSION) && (_POSIX_VERSION >= 200112L)) || defined(__linux__) || defined(__APPLE__)) + #define ALIGNED_ALLOC(ptr, alignment, size) if (::posix_memalign(&(ptr), alignment, size)) \ + (ptr) = nullptr + #define ALIGNED_FREE(ptr) ::free(ptr) +#elif defined(_MSC_VER) + #define ALIGNED_ALLOC(ptr, alignment, size) ptr = _aligned_malloc(size, alignment) + #define ALIGNED_FREE(ptr) _aligned_free(ptr) +#elif defined(__ANDROID__) + #define ALIGNED_ALLOC(ptr, alignment, size) ptr = ::memalign(alignment, size) + #define ALIGNED_FREE(ptr) ::free(ptr) +#else + inline std::uintptr_t alignAddress(std::uintptr_t address, std::size_t alignment) { + const std::size_t mask = alignment - 1ul; + assert((alignment & mask) == 0ul); + return (address + mask) & ~mask; + } + + template + inline T* alignPointer(T* ptr, std::size_t alignment) { + const std::uintptr_t address = reinterpret_cast(ptr); + const std::uintptr_t aligned = alignAddress(address, alignment); + return reinterpret_cast(aligned); + } + + void* alignedAlloc(std::size_t size, std::size_t alignment) { + const std::size_t actualSize = size + alignment; + char* pUnalignedChunk = new char[actualSize]; + char* pAlignedChunk = alignPointer(pUnalignedChunk, alignment); + if (pAlignedChunk == pUnalignedChunk) { + // There was no need for additional alignment, so an artifical gap + // must be added to make room for the shift value + pAlignedChunk += alignment; + } + // Compute and write the shift value into the buffer (it is needed during + // deallocation to reconstruct by which amount was the given pointer + // offset to meet the alignment requirement) + std::ptrdiff_t shift = pAlignedChunk - pUnalignedChunk; + // The guaranteed storage size for the shift value is only one byte + assert(shift > 0 && shift <= 256); + pAlignedChunk[-1] = static_cast(shift & 0xFF); + return static_cast(pAlignedChunk); + } + + void alignedFree(void* ptr) { + if (ptr) { + auto pAlignedChunk = reinterpret_cast(ptr); + std::ptrdiff_t shift = pAlignedChunk[-1]; + // There is no 0 alignment, so the value is reused to actually denote alignment by 256 + if (shift == 0) { + shift = 256; + } + // Compute the actual start address of the chunk, prior to aligning it's address + auto pUnalignedChunk = pAlignedChunk - shift; + delete[] pUnalignedChunk; + } + } + + #define ALIGNED_ALLOC(ptr, alignment, size) ptr = alignedAlloc(size, alignment) + #define ALIGNED_FREE(ptr) alignedFree(ptr) +#endif + +namespace pma { + +void* AlignedMemoryResource::allocate(std::size_t size, std::size_t alignment) { + void* ptr; + ALIGNED_ALLOC(ptr, alignment, size); + return ptr; +} + +void AlignedMemoryResource::deallocate(void* ptr, std::size_t /*unused*/, std::size_t /*unused*/) { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc,hicpp-no-malloc) + ALIGNED_FREE(ptr); +} + +} // namespace pma diff --git a/dnacalib/DNACalib/src/pma/resources/ArenaMemoryResource.cpp b/dnacalib/DNACalib/src/pma/resources/ArenaMemoryResource.cpp new file mode 100644 index 0000000..5f2d1fc --- /dev/null +++ b/dnacalib/DNACalib/src/pma/resources/ArenaMemoryResource.cpp @@ -0,0 +1,153 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "pma/resources/ArenaMemoryResource.h" + +#include "pma/ScopedPtr.h" +#include "pma/TypeDefs.h" + +#include +#include +#include +#include + +namespace pma { + +inline std::uintptr_t alignAddress(std::uintptr_t address, std::size_t alignment) { + const std::size_t mask = alignment - 1ul; + assert((alignment & mask) == 0ul); + return (address + mask) & ~mask; +} + +template +inline T* alignPointer(T* ptr, std::size_t alignment) { + const std::uintptr_t address = reinterpret_cast(ptr); + const std::uintptr_t aligned = alignAddress(address, alignment); + return reinterpret_cast(aligned); +} + +class ArenaMemoryResource::Impl { + public: + static Impl* create(std::size_t initialSize_, std::size_t regionSize_, float growthFactor_, MemoryResource* upstream_) { + PolyAllocator alloc{upstream_}; + return alloc.newObject(initialSize_, regionSize_, growthFactor_, upstream_); + } + + static void destroy(Impl* instance) { + PolyAllocator alloc{instance->upstream}; + alloc.deleteObject(instance); + } + + Impl(std::size_t initialSize_, std::size_t regionSize_, float growthFactor_, MemoryResource* upstream_) : + arenas{upstream_}, + regionSize{regionSize_}, + growthFactor{growthFactor_}, + upstream{upstream_}, + ptr{nullptr} { + + assert(upstream != nullptr); + allocateArena(initialSize_); + } + + ~Impl() { + for (auto it = arenas.rbegin(); it != arenas.rend(); ++it) { + upstream->deallocate(it->memory, it->size, alignof(std::max_align_t)); + } + arenas.clear(); + } + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + + Impl(Impl&&) = delete; + Impl& operator=(Impl&&) = delete; + + void* allocate(std::size_t size, std::size_t alignment) { + auto pAligned = alignPointer(ptr, alignment); + // If the old(current) pointer equals the aligned pointer, it was already + // adhering to the given alignment requirement. + const auto alignmentCorrection = static_cast(static_cast(pAligned) - static_cast(ptr)); + const auto neededSize = size + alignmentCorrection; + // Check if current arena has enough free space + assert(!arenas.empty()); + const auto& currentArena = arenas.back(); + if ((static_cast(ptr) + neededSize) > (static_cast(currentArena.memory) + currentArena.size)) { + // Not enough space in current arena, allocate an additional one (honoring growth factor), + // unless this is the first additional region that's needed, in which case it's size will + // be exactly `regionSize` + const bool isFirstAdditional = (arenas.size() == 1ul); + const std::size_t newArenaSize = + (isFirstAdditional ? regionSize : static_cast(std::lroundf(static_cast(currentArena.size) + * growthFactor))); + allocateArena(newArenaSize); + // Retry allocation relying on newly allocated arena + return allocate(size, alignment); + } + + ptr = static_cast(static_cast(ptr) + neededSize); + return pAligned; + } + + MemoryResource* getUpstreamMemoryResource() const { + return upstream; + } + + private: + void allocateArena(std::size_t size) { + if (!arenas.empty() && (arenas.back().memory == ptr)) { + // No allocation happened in the arena whatsoever, and the first allocation was + // immediately larger than the arena's size, thus it's an unused arena, and should + // be discarded immediately. + auto unusedArena = arenas.back(); + arenas.pop_back(); + upstream->deallocate(unusedArena.memory, unusedArena.size, alignof(std::max_align_t)); + } + arenas.push_back({upstream->allocate(size, alignof(std::max_align_t)), size}); + ptr = arenas.back().memory; + } + + private: + struct Arena { + void* memory; + std::size_t size; + }; + + pma::List arenas; + std::size_t regionSize; + float growthFactor; + MemoryResource* upstream; + void* ptr; + +}; + +ArenaMemoryResource::ArenaMemoryResource(std::size_t initialSize, + std::size_t regionSize, + float growthFactor, + MemoryResource* upstream) : + pImpl{makeScoped(initialSize, regionSize, growthFactor, upstream)} { +} + +ArenaMemoryResource::ArenaMemoryResource(std::size_t regionSize, float growthFactor, MemoryResource* upstream) : + ArenaMemoryResource{regionSize, regionSize, growthFactor, upstream} { +} + +ArenaMemoryResource::ArenaMemoryResource(std::size_t regionSize, MemoryResource* upstream) : + ArenaMemoryResource{regionSize, regionSize, 1.0f, upstream} { +} + +ArenaMemoryResource::~ArenaMemoryResource() = default; +ArenaMemoryResource::ArenaMemoryResource(ArenaMemoryResource&&) = default; +ArenaMemoryResource& ArenaMemoryResource::operator=(ArenaMemoryResource&&) = default; + +void* ArenaMemoryResource::allocate(std::size_t size, std::size_t alignment) { + return pImpl->allocate(size, alignment); +} + +void ArenaMemoryResource::deallocate(void* /*unused*/, std::size_t /*unused*/, std::size_t /*unused*/) { + // No-op +} + +MemoryResource* ArenaMemoryResource::getUpstreamMemoryResource() const { + return pImpl->getUpstreamMemoryResource(); +} + +} // namespace pma diff --git a/dnacalib/DNACalib/src/pma/resources/DefaultMemoryResource.cpp b/dnacalib/DNACalib/src/pma/resources/DefaultMemoryResource.cpp new file mode 100644 index 0000000..c608308 --- /dev/null +++ b/dnacalib/DNACalib/src/pma/resources/DefaultMemoryResource.cpp @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "pma/resources/DefaultMemoryResource.h" + +#include +#include + +namespace pma { + +void* DefaultMemoryResource::allocate(std::size_t size, std::size_t /*unused*/) { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc,hicpp-no-malloc) + return std::malloc(size); +} + +void DefaultMemoryResource::deallocate(void* ptr, std::size_t /*unused*/, std::size_t /*unused*/) { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc,hicpp-no-malloc) + std::free(ptr); +} + +} // namespace pma diff --git a/dnacalib/DNACalib/src/status/PredefinedCodes.h b/dnacalib/DNACalib/src/status/PredefinedCodes.h new file mode 100644 index 0000000..a8fb6fd --- /dev/null +++ b/dnacalib/DNACalib/src/status/PredefinedCodes.h @@ -0,0 +1,11 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "status/StatusCode.h" + +namespace sc { + +static const StatusCode OK{0, "Ok"}; + +} // namespace sc diff --git a/dnacalib/DNACalib/src/status/Provider.cpp b/dnacalib/DNACalib/src/status/Provider.cpp new file mode 100644 index 0000000..e3da469 --- /dev/null +++ b/dnacalib/DNACalib/src/status/Provider.cpp @@ -0,0 +1,37 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "status/Provider.h" + +#include "status/StatusCode.h" +#include "status/Storage.h" +#include "status/Registry.h" + +#include + +namespace sc { + +StatusProvider::StatusProvider(std::initializer_list statuses) { + // The Release build will eliminate this call, as it's really just a sanity check + // to avoid defining duplicate error codes + assert(StatusCodeRegistry::insert(statuses)); + // Avoid warning in Release builds + static_cast(statuses); +} + +void StatusProvider::reset() { + StatusStorage::reset(); +} + +StatusCode StatusProvider::get() { + return StatusStorage::get(); +} + +bool StatusProvider::isOk() { + return StatusStorage::isOk(); +} + +void StatusProvider::set(StatusCode status) { + StatusStorage::set(status); +} + +} // namespace sc diff --git a/dnacalib/DNACalib/src/status/Registry.cpp b/dnacalib/DNACalib/src/status/Registry.cpp new file mode 100644 index 0000000..01baeaa --- /dev/null +++ b/dnacalib/DNACalib/src/status/Registry.cpp @@ -0,0 +1,57 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "status/Registry.h" + +#include "status/PredefinedCodes.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace sc { + +static std::unordered_set& getRegistry() { + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wexit-time-destructors" + #endif + static std::unordered_set registry; + #ifdef __clang__ + #pragma clang diagnostic pop + #endif + return registry; +} + +bool StatusCodeRegistry::insert(std::initializer_list statuses) { + auto& registry = getRegistry(); + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wexit-time-destructors" + #endif + static std::mutex mut; + #ifdef __clang__ + #pragma clang diagnostic pop + #endif + std::lock_guard lock{mut}; + // Zero is a reserved code + registry.insert(OK.code); + bool allUnique = true; + for (auto s : statuses) { + allUnique = allUnique && registry.insert(s.code).second; + } + return allUnique; +} + +bool StatusCodeRegistry::contains(StatusCode status) { + auto& registry = getRegistry(); + return (registry.find(status.code) != registry.end()); +} + +} // namespace sc diff --git a/dnacalib/DNACalib/src/status/Registry.h b/dnacalib/DNACalib/src/status/Registry.h new file mode 100644 index 0000000..6ae6a21 --- /dev/null +++ b/dnacalib/DNACalib/src/status/Registry.h @@ -0,0 +1,18 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "status/StatusCode.h" + +#include + +namespace sc { + +class StatusCodeRegistry { + public: + static bool insert(std::initializer_list statuses); + static bool contains(StatusCode status); + +}; + +} // namespace sc diff --git a/dnacalib/DNACalib/src/status/Status.cpp b/dnacalib/DNACalib/src/status/Status.cpp new file mode 100644 index 0000000..f0278aa --- /dev/null +++ b/dnacalib/DNACalib/src/status/Status.cpp @@ -0,0 +1,17 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "status/Status.h" + +#include "status/Storage.h" + +namespace sc { + +StatusCode Status::get() { + return StatusStorage::get(); +} + +bool Status::isOk() { + return StatusStorage::isOk(); +} + +} // namespace sc diff --git a/dnacalib/DNACalib/src/status/Storage.cpp b/dnacalib/DNACalib/src/status/Storage.cpp new file mode 100644 index 0000000..6c8b894 --- /dev/null +++ b/dnacalib/DNACalib/src/status/Storage.cpp @@ -0,0 +1,59 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "status/Storage.h" + +#include "status/PredefinedCodes.h" +#include "status/Registry.h" +#include "status/StatusCode.h" + +#include +#include +#include + +namespace sc { + +namespace { + +struct StatusCodeStorage { + int code; + char message[512]; +}; + +void strcopy(char* destination, const char* source, std::size_t bufferSize) { + #ifdef _MSC_VER + strncpy_s(destination, bufferSize, source, bufferSize - 1ul); + #else + std::strncpy(destination, source, bufferSize - 1ul); + #endif +} + +} // namespace + +thread_local static StatusCodeStorage currentStatus{0, "Ok"}; + +void StatusStorage::set(StatusCode status) { + // The Release build will eliminate this call, as it's really just a sanity check + // to avoid using unregistered status codes + assert(StatusCodeRegistry::contains(status)); + currentStatus.code = status.code; + strcopy(currentStatus.message, status.message, bufferSize()); +} + +void StatusStorage::reset() { + currentStatus.code = OK.code; + strcopy(currentStatus.message, OK.message, bufferSize()); +} + +StatusCode StatusStorage::get() { + return {currentStatus.code, currentStatus.message}; +} + +bool StatusStorage::isOk() { + return (currentStatus.code == OK.code); +} + +constexpr std::size_t StatusStorage::bufferSize() { + return sizeof(currentStatus.message); +} + +} // namespace sc diff --git a/dnacalib/DNACalib/src/status/Storage.h b/dnacalib/DNACalib/src/status/Storage.h new file mode 100644 index 0000000..55308bc --- /dev/null +++ b/dnacalib/DNACalib/src/status/Storage.h @@ -0,0 +1,21 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "status/StatusCode.h" + +#include + +namespace sc { + +class StatusStorage { + public: + static void set(StatusCode status); + static void reset(); + static StatusCode get(); + static bool isOk(); + static constexpr std::size_t bufferSize(); + +}; + +} // namespace sc diff --git a/dnacalib/DNACalib/src/tdm/Computations.h b/dnacalib/DNACalib/src/tdm/Computations.h new file mode 100644 index 0000000..33ae912 --- /dev/null +++ b/dnacalib/DNACalib/src/tdm/Computations.h @@ -0,0 +1,159 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "tdm/Types.h" +#include "tdm/Mat.h" +#include "tdm/Vec.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace tdm { + +template +inline vec3 cross(const vec3& lhs, const vec3& rhs) { + return vec3{ + lhs[1] * rhs[2] - lhs[2] * rhs[1], + lhs[2] * rhs[0] - lhs[0] * rhs[2], + lhs[0] * rhs[1] - lhs[1] * rhs[0] + }; +} + +template +inline T dot(const vec& lhs, const vec& rhs) { + return (lhs * rhs).sum(); +} + +template +inline vec negate(vec v) { + return v.negate(); +} + +template +inline mat negate(const mat& m) { + return m.negate(); +} + +template +inline typename std::enable_if::value, T>::type length(const vec& v) { + return v.length(); +} + +template +inline typename std::enable_if::value, vec >::type normalize(vec v) { + v.normalize(); + return v; +} + +template +inline mat transpose(const mat& m) { + using row_type = typename mat::row_type; + mat ret; + ret.apply([&m](row_type& row, dim_t i) { + row = m.column(i); + }); + return ret; +} + +namespace impl { + +#pragma push_macro("minor") +#undef minor + +template +inline void minor(const mat& input, dim_t dimensions, dim_t i, dim_t j, mat& output) { + for (dim_t outRow{}, inRow{}; inRow < dimensions; ++inRow) { + for (dim_t outCol{}, inCol{}; inCol < dimensions; ++inCol) { + if ((inRow != i) && (inCol != j)) { + output(outRow, outCol) = input(inRow, inCol); + ++outCol; + if (outCol == (dimensions - static_cast(1))) { + outCol = {}; + ++outRow; + } + } + } + } +} + +template +inline T determinant(const mat& m, dim_t dimensions) { + if (dimensions == static_cast(1)) { + return m(0, 0); + } + + T result{}; + mat temp; + auto sign = static_cast(1); + const dim_t i{}; + for (dim_t j{}; j < dimensions; ++j) { + minor(m, dimensions, i, j, temp); + result += (sign * m(i, j) * determinant(temp, dimensions - 1)); + sign = -sign; + } + + return result; +} + +template +inline mat adjoint(const mat& m) { + if (m.rows() == static_cast(1)) { + return mat{static_cast(1)}; + } + + mat result; + mat temp; + for (dim_t row{}; row < m.rows(); ++row) { + for (dim_t col{}; col < m.columns(); ++col) { + minor(m, N, row, col, temp); + const T sign = static_cast((row + col) % 2u == 0u ? 1 : -1); + result(col, row) = (sign * determinant(temp, N - 1)); + } + } + return result; +} + +#pragma pop_macro("minor") + +} // namespace impl + +template +inline T determinant(const mat& m) { + return impl::determinant(m, N); +} + +template +inline mat inverse(const mat& m) { + T det = determinant(m); + if (det == T{}) { + return {}; + } + + mat adj = impl::adjoint(m); + mat inv; + for (dim_t row{}; row < m.rows(); ++row) { + for (dim_t col{}; col < m.columns(); ++col) { + inv(row, col) = adj(row, col) / det; + } + } + return inv; +} + +template +inline T trace(const mat& m) { + T trace{0}; + for (dim_t row{}; row < m.rows(); ++row) { + trace += m(row, row); + } + return trace; +} + +} // namespace tdm diff --git a/dnacalib/DNACalib/src/tdm/Mat.h b/dnacalib/DNACalib/src/tdm/Mat.h new file mode 100644 index 0000000..91a5387 --- /dev/null +++ b/dnacalib/DNACalib/src/tdm/Mat.h @@ -0,0 +1,402 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "tdm/Types.h" +#include "tdm/Vec.h" + +namespace tdm { + +template +inline mat transpose(const mat& m); + +template +inline mat inverse(const mat& m); + +template +struct mat { + using value_type = T; + using row_type = vec; + using column_type = vec; + + static constexpr dim_t rows() { + return column_type::dimensions(); + } + + static constexpr dim_t columns() { + return row_type::dimensions(); + } + + private: + row_type values[R]; + + public: + mat() : values{} { + } + + ~mat() = default; + + mat(const mat&) = default; + mat& operator=(const mat&) = default; + + mat(mat&&) = default; + mat& operator=(mat&&) = default; + + template + explicit mat(U scalar) { + apply([scalar](row_type& row, dim_t /*unused*/) { + row = row_type{scalar}; + }); + } + + template::type* = nullptr> + mat(Us... scalars) { + T tmp[sizeof...(Us)] = {static_cast(scalars)...}; + apply([&tmp](row_type& row, dim_t ri) { + row.apply([&tmp, ri](value_type& value, dim_t ci) { + value = tmp[ri * columns() + ci]; + }); + }); + } + + template + mat(const mat& rhs) { + apply([&rhs](row_type& row, dim_t ri) { + row = rhs[ri]; + }); + } + + template + mat& operator=(const mat& rhs) { + return operator=(mat{rhs}); + } + + template::type* = nullptr> + mat(const vec& ... vs) : values{row_type{vs} ...} { + } + + template::type * = nullptr> + static mat fromRows(const vec& ... vs) { + return mat{vs ...}; + } + + template::type* = nullptr> + static mat fromColumns(const vec& ... vs) { + mat tmp{vs ...}; + return tdm::transpose(tmp); + } + + template + static typename std::enable_if::type diagonal(U scalar) { + mat ret; + ret.apply([scalar](row_type& row, dim_t ri) { + row[ri] = scalar; + }); + return ret; + } + + template + static typename std::enable_if::type diagonal(const vec& scalars) { + mat ret; + ret.apply([&scalars](row_type& row, dim_t ri) { + row[ri] = scalars[ri]; + }); + return ret; + } + + template + static typename std::enable_if<(H == W) && (sizeof...(Us) == H), mat>::type diagonal(Us... scalars) { + return diagonal(vec{scalars ...}); + } + + template + static typename std::enable_if::type identity() { + return diagonal(static_cast(1)); + } + + row_type& operator[](dim_t index) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + assert(index < rows()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) + return values[index]; + } + + const row_type& operator[](dim_t index) const { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + assert(index < rows()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) + return values[index]; + } + + T& operator()(dim_t rowIndex, dim_t colIndex) { + return operator[](rowIndex)[colIndex]; + } + + const T& operator()(dim_t rowIndex, dim_t colIndex) const { + return operator[](rowIndex)[colIndex]; + } + + template + mat& apply(F func) { + for (dim_t ri{}; ri != rows(); ++ri) { + func(values[ri], ri); + } + return *this; + } + + template + const mat& apply(F func) const { + for (dim_t ri{}; ri != rows(); ++ri) { + func(values[ri], ri); + } + return *this; + } + + mat& operator++() { + return apply([](row_type& row, dim_t /*unused*/) { + ++row; + }); + } + + mat& operator--() { + return apply([](row_type& row, dim_t /*unused*/) { + --row; + }); + } + + template + mat& operator+=(U rhs) { + return apply([rhs](row_type& row, dim_t /*unused*/) { + row += rhs; + }); + } + + template + mat& operator+=(const mat& rhs) { + return apply([&rhs](row_type& row, dim_t ri) { + row += rhs[ri]; + }); + } + + mat& operator+=(const mat& rhs) { + return operator+=(rhs); + } + + template + mat& operator-=(U rhs) { + return apply([rhs](row_type& row, dim_t /*unused*/) { + row -= rhs; + }); + } + + template + mat& operator-=(const mat& rhs) { + return apply([&rhs](row_type& row, dim_t ri) { + row -= rhs[ri]; + }); + } + + mat& operator-=(const mat& rhs) { + return operator-=(rhs); + } + + template + mat& operator*=(U rhs) { + return apply([rhs](row_type& row, dim_t /*unused*/) { + row *= rhs; + }); + } + + template + mat& operator*=(const mat& rhs) { + return (*this = *this * rhs); + } + + mat& operator*=(const mat& rhs) { + return operator*=(rhs); + } + + template + mat& operator/=(U rhs) { + return apply([rhs](row_type& row, dim_t /*unused*/) { + row /= rhs; + }); + } + + template + mat& operator/=(const mat& rhs) { + return operator*=(inverse(rhs)); + } + + mat& operator/=(const mat& rhs) { + return operator/=(rhs); + } + + template + typename std::enable_if::type transpose() { + return (*this = tdm::transpose(*this)); + } + + mat& negate() { + apply([](row_type& row, dim_t /*unused*/) { + row.negate(); + }); + return *this; + } + + row_type row(dim_t index) const { + return operator[](index); + } + + column_type column(dim_t index) const { + column_type col; + apply([&col, index](const row_type& row, dim_t ri) { + col[ri] = row[index]; + }); + return col; + } + + template + typename std::enable_if<(H > 1 && W > 1 && H <= R && W <= C), mat >::type submat(dim_t y, dim_t x) const { + assert(H + y <= rows()); + assert(W + x <= columns()); + mat ret; + ret.apply([this, y, x](typename mat::row_type& row, dim_t ri) { + row.apply([this, y, x, ri](value_type& value, dim_t ci) { + value = values[y + ri][x + ci]; + }); + }); + return ret; + } + +}; + +template +inline bool operator==(const mat& lhs, const mat& rhs) { + using row_type = typename mat::row_type; + bool retval = true; + lhs.apply([&rhs, &retval](const row_type& row, dim_t ri) { + retval = retval && (row == rhs[ri]); + }); + return retval; +} + +template +inline bool operator!=(const mat& lhs, const mat& rhs) { + return !(lhs == rhs); +} + +template +inline mat operator+(const mat& m) { + return m; +} + +template +inline mat operator-(const mat& m) { + mat ret{m}; + ret.negate(); + return ret; +} + +template +inline mat operator+(const mat& lhs, T rhs) { + return mat(lhs) += rhs; +} + +template +inline mat operator+(T lhs, const mat& rhs) { + return mat(lhs) += rhs; +} + +template +inline mat operator+(const mat& lhs, const mat& rhs) { + return mat(lhs) += rhs; +} + +template +inline mat operator-(const mat& lhs, T rhs) { + return mat(lhs) -= rhs; +} + +template +inline mat operator-(T lhs, const mat& rhs) { + return mat(lhs) -= rhs; +} + +template +inline mat operator-(const mat& lhs, const mat& rhs) { + return mat(lhs) -= rhs; +} + +template +inline mat operator*(const mat& lhs, T rhs) { + return mat(lhs) *= rhs; +} + +template +inline mat operator*(T lhs, const mat& rhs) { + return mat(rhs) *= lhs; +} + +template +inline typename mat::row_type operator*(const typename mat::column_type& lhs, const mat& rhs) { + using row_type = typename mat::row_type; + row_type ret; + rhs.apply([&ret, &lhs](const row_type& row, dim_t ri) { + ret += (row * row_type{lhs[ri]}); + }); + return ret; +} + +template +inline typename mat::column_type operator*(const mat& lhs, const typename mat::row_type& rhs) { + using column_type = typename mat::column_type; + using value_type = typename column_type::value_type; + column_type ret; + rhs.apply([&ret, &lhs](value_type value, dim_t ci) { + ret += (lhs.column(ci) * column_type{value}); + }); + return ret; +} + +template +inline mat operator*(const mat& lhs, const mat& rhs) { + using row_type = typename mat::row_type; + mat ret; + ret.apply([&lhs, &rhs](row_type& row, dim_t ri) { + row = (lhs[ri] * rhs); + }); + return ret; +} + +template +inline mat operator/(const mat& lhs, T rhs) { + return mat(lhs) /= rhs; +} + +template +inline mat operator/(T lhs, const mat& rhs) { + using row_type = typename mat::row_type; + mat tmp{rhs}; + tmp.apply([lhs](row_type& row, dim_t /*unused*/) { + row = lhs / row; + }); + return tmp; +} + +template +inline typename mat::row_type operator/(const typename mat::column_type& lhs, const mat& rhs) { + return (lhs * inverse(rhs)); +} + +template +inline typename mat::column_type operator/(const mat& lhs, const typename mat::row_type& rhs) { + return (inverse(lhs) * rhs); +} + +template +inline mat operator/(const mat& lhs, const mat& rhs) { + return mat(lhs) /= rhs; +} + +} // namespace tdm diff --git a/dnacalib/DNACalib/src/tdm/TDM.h b/dnacalib/DNACalib/src/tdm/TDM.h new file mode 100644 index 0000000..7017548 --- /dev/null +++ b/dnacalib/DNACalib/src/tdm/TDM.h @@ -0,0 +1,9 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "tdm/Types.h" +#include "tdm/Vec.h" +#include "tdm/Mat.h" +#include "tdm/Computations.h" +#include "tdm/Transforms.h" diff --git a/dnacalib/DNACalib/src/tdm/Transforms.h b/dnacalib/DNACalib/src/tdm/Transforms.h new file mode 100644 index 0000000..b231976 --- /dev/null +++ b/dnacalib/DNACalib/src/tdm/Transforms.h @@ -0,0 +1,184 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "tdm/Computations.h" +#include "tdm/Types.h" + +namespace tdm { + +inline constexpr double pi() { + return 3.14159265358979323846; +} + +template +inline typename std::enable_if::value, T>::type degrees(T radians) { + static constexpr const auto c = static_cast(180.0 / pi()); + return c * radians; +} + +template +inline typename std::enable_if::value, T>::type radians(T degrees) { + static constexpr const auto c = static_cast(pi() / 180.0); + return c * degrees; +} + +namespace affine { + +template +inline mat scale(const vec& factors) { + return mat::diagonal(factors); +} + +template +inline mat scale(const mat& m, const vec& factors) { + return m * scale(factors); +} + +template +inline mat scale(T factor) { + return scale(vec{factor}); +} + +template +inline mat scale(const mat& m, T factor) { + return scale(m, vec{factor}); +} + +} // namespace affine + +inline namespace projective { + +template +inline mat4 rotate(const vec3& axis, T radians, handedness h = handedness::right) { + const T c = std::cos(radians); + const T s = std::sin(radians) * static_cast(h); + const T one_minus_c = static_cast(1) - c; + const vec3 n = normalize(axis); + return mat4{n[0] * n[0] * one_minus_c + c, + n[1] * n[0] * one_minus_c - n[2] * s, + n[2] * n[0] * one_minus_c + n[1] * s, + static_cast(0), + n[0] * n[1] * one_minus_c + n[2] * s, + n[1] * n[1] * one_minus_c + c, + n[2] * n[1] * one_minus_c - n[0] * s, + static_cast(0), + n[0] * n[2] * one_minus_c - n[1] * s, + n[1] * n[2] * one_minus_c + n[0] * s, + n[2] * n[2] * one_minus_c + c, + static_cast(0), + static_cast(0), + static_cast(0), + static_cast(0), + static_cast(1)}; +} + +template +inline mat4 rotate(const mat4& m, const vec3& axis, T radians, handedness h = handedness::right) { + return m * rotate(axis, radians, h); +} + +template +inline mat4 rotate(T xRadians, T yRadians, T zRadians, handedness h = handedness::right) { + const T sx = std::sin(xRadians) * static_cast(h); + const T sy = std::sin(yRadians) * static_cast(h); + const T sz = std::sin(zRadians) * static_cast(h); + const T cx = std::cos(xRadians); + const T cy = std::cos(yRadians); + const T cz = std::cos(zRadians); + auto rx = mat4::identity(); + rx(1, 1) = cx; + rx(1, 2) = sx; + rx(2, 1) = -sx; + rx(2, 2) = cx; + auto ry = mat4::identity(); + ry(0, 0) = cy; + ry(0, 2) = -sy; + ry(2, 0) = sy; + ry(2, 2) = cy; + auto rz = mat4::identity(); + rz(0, 0) = cz; + rz(0, 1) = sz; + rz(1, 0) = -sz; + rz(1, 1) = cz; + return rx * ry * rz; +} + +template +inline mat4 rotate(const mat4& m, T xRadians, T yRadians, T zRadians, handedness h = handedness::right) { + return m * rotate(xRadians, yRadians, zRadians, h); +} + +template +inline mat4 rotate(const vec3& radians, handedness h = handedness::right) { + return rotate(radians[0], radians[1], radians[2], h); +} + +template +inline mat4 rotate(const mat4& m, const vec3& radians, handedness h = handedness::right) { + return m * rotate(radians[0], radians[1], radians[2], h); +} + +template +inline mat scale(const vec& factors) { + vec diagonal{static_cast(1)}; + factors.apply([&diagonal](const T& value, dim_t i) { + diagonal[i] = value; + }); + return mat::diagonal(diagonal); +} + +template +inline mat scale(const mat& m, const vec& factors) { + return m * scale(factors); +} + +template +inline mat scale(T factor) { + return scale(vec{factor}); +} + +template +inline mat scale(const mat& m, T factor) { + return scale(m, vec{factor}); +} + +template +inline mat translate(const vec& position) { + auto m = mat::identity(); + position.apply([&m](const T& value, dim_t i) { + m(L, i) = value; + }); + return m; +} + +template +inline mat translate(const mat& m, const vec& position) { + return m * translate(position); +} + +template +struct axis_angle { + tdm::vec3 axis; + T radians; +}; + +template +axis_angle xyz2AxisAngle(T xRadians, T yRadians, T zRadians, handedness h = handedness::right) { + const mat4 m = rotate(xRadians, yRadians, zRadians, h); + const T radians = std::acos((trace(m.template submat<3, 3>(0, 0)) - static_cast(1)) / static_cast(2)); + const T factor = static_cast(1) / (static_cast(2) * std::sin(radians)); + const T x = factor * (m(2, 1) - m(1, 2)); + const T y = factor * (m(0, 2) - m(2, 0)); + const T z = factor * (m(1, 0) - m(0, 1)); + return {{x, y, z}, radians}; +} + +template +axis_angle xyz2AxisAngle(const vec3& radians, handedness h = handedness::right) { + return xyz2AxisAngle(radians[0], radians[1], radians[2], h); +} + +} // namespace projective + +} // namespace tdm diff --git a/dnacalib/DNACalib/src/tdm/Types.h b/dnacalib/DNACalib/src/tdm/Types.h new file mode 100644 index 0000000..a6ac60b --- /dev/null +++ b/dnacalib/DNACalib/src/tdm/Types.h @@ -0,0 +1,89 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace tdm { + +// Scalar types + +using dim_t = std::size_t; + +// Vector types + +template +struct vec; + +template +using vec2 = vec<2, T>; + +template +using vec3 = vec<3, T>; + +template +using vec4 = vec<4, T>; + +// Common vector types + +template +using ivec = vec; + +using ivec2 = ivec<2>; +using ivec3 = ivec<3>; +using ivec4 = ivec<4>; + +template +using fvec = vec; + +using fvec2 = fvec<2>; +using fvec3 = fvec<3>; +using fvec4 = fvec<4>; + +// Matrix types + +template +struct mat; + +template +using mat2 = mat<2, 2, T>; + +template +using mat3 = mat<3, 3, T>; + +template +using mat4 = mat<4, 4, T>; + +// Common matrix types + +template +using imat = mat; + +using imat2 = imat<2, 2>; +using imat3 = imat<3, 3>; +using imat4 = imat<4, 4>; + +template +using fmat = mat; + +using fmat2 = fmat<2, 2>; +using fmat3 = fmat<3, 3>; +using fmat4 = fmat<4, 4>; + +enum class handedness { + left = -1, + right = 1 +}; + +} // namespace tdm diff --git a/dnacalib/DNACalib/src/tdm/Vec.h b/dnacalib/DNACalib/src/tdm/Vec.h new file mode 100644 index 0000000..fd77954 --- /dev/null +++ b/dnacalib/DNACalib/src/tdm/Vec.h @@ -0,0 +1,278 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "tdm/Types.h" + +namespace tdm { + +template +struct vec { + using value_type = T; + + static constexpr dim_t dimensions() { + return L; + } + + value_type values[L]; + + vec() : values{} { + } + + ~vec() = default; + + vec(const vec& rhs) = default; + vec& operator=(const vec& rhs) = default; + + vec(vec&& rhs) = default; + vec& operator=(vec&& rhs) = default; + + template + vec(const vec& rhs) { + std::copy(std::begin(rhs.values), std::end(rhs.values), std::begin(values)); + } + + template + vec& operator=(const vec& rhs) { + std::copy(std::begin(rhs.values), std::end(rhs.values), std::begin(values)); + return *this; + } + + template::type* = nullptr> + vec(Vs... vs) : values{vs ...} { + } + + template::value && (L > 1)>::type * = nullptr> + explicit vec(U v) { + std::fill_n(values, dimensions(), v); + } + + template::value>::type* = nullptr> + explicit vec(U* pv) { + std::copy_n(pv, dimensions(), std::begin(values)); + } + + T& operator[](dim_t index) { + assert(index < dimensions()); + return values[index]; + } + + const T& operator[](dim_t index) const { + assert(index < dimensions()); + return values[index]; + } + + struct Vectorized; + + template + vec& apply(F func) { + for (dim_t i{}; i < dimensions(); ++i) { + func(values[i], i); + } + return *this; + } + + template + const vec& apply(F func) const { + for (dim_t i{}; i < dimensions(); ++i) { + func(values[i], i); + } + return *this; + } + + vec& operator++() { + return apply([](T& v, dim_t /*unused*/) { + ++v; + }); + } + + vec& operator--() { + return apply([](T& v, dim_t /*unused*/) { + --v; + }); + } + + template + vec& operator+=(U rhs) { + return apply([rhs](T& v, dim_t /*unused*/) { + v += rhs; + }); + } + + template + vec& operator+=(const vec& rhs) { + return apply([&rhs](T& v, dim_t i) { + v += rhs[i]; + }); + } + + vec& operator+=(const vec& rhs) { + return operator+=(rhs); + } + + template + vec& operator-=(U rhs) { + return apply([rhs](T& v, dim_t /*unused*/) { + v -= rhs; + }); + } + + template + vec& operator-=(const vec& rhs) { + return apply([&rhs](T& v, dim_t i) { + v -= rhs[i]; + }); + } + + vec& operator-=(const vec& rhs) { + return operator-=(rhs); + } + + template + vec& operator*=(U rhs) { + return apply([rhs](T& v, dim_t /*unused*/) { + v *= rhs; + }); + } + + template + vec& operator*=(const vec& rhs) { + return apply([&rhs](T& v, dim_t i) { + v *= rhs[i]; + }); + } + + vec& operator*=(const vec& rhs) { + return operator*=(rhs); + } + + template + vec& operator/=(U rhs) { + return apply([rhs](T& v, dim_t /*unused*/) { + v /= rhs; + }); + } + + template + vec& operator/=(const vec& rhs) { + return apply([&rhs](T& v, dim_t i) { + v /= rhs[i]; + }); + } + + vec& operator/=(const vec& rhs) { + return operator/=(rhs); + } + + template + typename std::enable_if::value, V>::type length() const { + const auto& v = *this; + return std::sqrt((v * v).sum()); + } + + template + typename std::enable_if::value, vec&>::type normalize() { + return operator/=(length()); + } + + vec& negate() { + return apply([](T& v, dim_t /*unused*/) { + v = -v; + }); + } + + T sum() const { + T retval{}; + apply([&retval](const T& v, dim_t /*unused*/) { + retval += v; + }); + return retval; + } + +}; + +template +inline bool operator==(const vec& lhs, const vec& rhs) { + bool equal = true; + lhs.apply([&equal, &rhs](const T& v, dim_t i) { + equal = equal && (v == rhs[i]); + }); + return equal; +} + +template +inline bool operator!=(const vec& lhs, const vec& rhs) { + return !(lhs == rhs); +} + +template +inline vec operator+(const vec& v) { + return v; +} + +template +inline vec operator-(vec v) { + return v.negate(); +} + +template +inline vec operator+(const vec& lhs, const vec& rhs) { + return vec(lhs) += rhs; +} + +template +inline vec operator+(const vec& lhs, U rhs) { + return vec(lhs) += rhs; +} + +template +inline vec operator+(T lhs, const vec& rhs) { + return vec(lhs) += rhs; +} + +template +inline vec operator-(const vec& lhs, const vec& rhs) { + return vec(lhs) -= rhs; +} + +template +inline vec operator-(const vec& lhs, U rhs) { + return vec(lhs) -= rhs; +} + +template +inline vec operator-(T lhs, const vec& rhs) { + return vec(lhs) -= rhs; +} + +template +inline vec operator*(const vec& lhs, const vec& rhs) { + return vec(lhs) *= rhs; +} + +template +inline typename std::enable_if::value, vec >::type operator*(const vec& lhs, U rhs) { + return vec(lhs) *= rhs; +} + +template +inline typename std::enable_if::value, vec >::type operator*(T lhs, const vec& rhs) { + return vec(lhs) *= rhs; +} + +template +inline vec operator/(const vec& lhs, const vec& rhs) { + return vec(lhs) /= rhs; +} + +template +inline typename std::enable_if::value, vec >::type operator/(const vec& lhs, U rhs) { + return vec(lhs) /= rhs; +} + +template +inline typename std::enable_if::value, vec >::type operator/(T lhs, const vec& rhs) { + return vec(lhs) /= rhs; +} + +} // namespace tdm diff --git a/dnacalib/DNACalib/src/tdm/version/Version.h b/dnacalib/DNACalib/src/tdm/version/Version.h new file mode 100644 index 0000000..84adc5f --- /dev/null +++ b/dnacalib/DNACalib/src/tdm/version/Version.h @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#define TDM_MAJOR_VERSION 2 +#define TDM_MINOR_VERSION 0 +#define TDM_PATCH_VERSION 0 +#define TDM_VERSION_STRING "2.0.0" diff --git a/dnacalib/DNACalib/src/terse/Archive.h b/dnacalib/DNACalib/src/terse/Archive.h new file mode 100644 index 0000000..2cb00be --- /dev/null +++ b/dnacalib/DNACalib/src/terse/Archive.h @@ -0,0 +1,76 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace terse { + +template +class Archive { + public: + explicit Archive(ArchiveImpl* impl_) : impl{impl_}, userData{nullptr} { + } + + bool isOk() { + return impl->isOk(); + } + + void sync() { + impl->sync(); + } + + void label(const char* value) { + impl->label(value); + } + + template + void operator()(Args&& ... args) { + dispatch(std::forward(args)...); + } + + template + ArchiveImpl& operator<<(TSerializable& source) { + dispatch(source); + return *impl; + } + + template + ArchiveImpl& operator>>(TSerializable& dest) { + dispatch(dest); + return *impl; + } + + void* getUserData() const { + return userData; + } + + void setUserData(void* data) { + userData = data; + } + + protected: + template + void dispatch(Head&& head) { + impl->process(std::forward(head)); + } + + template + void dispatch(Head&& head, Tail&& ... tail) { + dispatch(std::forward(head)); + dispatch(std::forward(tail)...); + } + + private: + ArchiveImpl* impl; + void* userData; +}; + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/archives/Common.h b/dnacalib/DNACalib/src/terse/archives/Common.h new file mode 100644 index 0000000..842e710 --- /dev/null +++ b/dnacalib/DNACalib/src/terse/archives/Common.h @@ -0,0 +1,72 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "terse/archives/Traits.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace pma { + +class MemoryResource; + +} // namespace pma + +namespace terse { + +namespace impl { + +template +struct ValueFactory { + static constexpr bool NeedsAllocator = traits::needs_allocator::value; + static constexpr bool NeedsMemoryResource = std::is_constructible::value; + static constexpr bool IsPair = traits::is_pair::value; + static constexpr bool IsTuple = traits::is_tuple::value; + static constexpr bool IsPrimitive = (!NeedsAllocator && !NeedsMemoryResource & !IsPair & !IsTuple); + + template + static typename std::enable_if::type create(const ParentAllocator& /*unused*/) { + return T{}; + } + + template + static typename std::enable_if::type create(const ParentAllocator& alloc) { + return T{alloc.getMemoryResource()}; + } + + template + static typename std::enable_if::type create( + const ParentAllocator& alloc) { + using TAllocator = typename std::allocator_traits::template rebind_alloc; + return T{TAllocator{alloc}}; + } + + template + static typename std::enable_if::type create( + const ParentAllocator& alloc) { + using K = typename T::first_type; + using V = typename T::second_type; + return T{ValueFactory::create(alloc), ValueFactory::create(alloc)}; + } + + template + static typename std::enable_if::type create( + const ParentAllocator& alloc) { + using K = typename std::tuple_element<0, T>::type; + using V = typename std::tuple_element<0, T>::type; + return T{ValueFactory::create(alloc), ValueFactory::create(alloc)}; + } + +}; + +} // namespace impl + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/archives/Traits.h b/dnacalib/DNACalib/src/terse/archives/Traits.h new file mode 100644 index 0000000..dfa1232 --- /dev/null +++ b/dnacalib/DNACalib/src/terse/archives/Traits.h @@ -0,0 +1,135 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +static_assert(sizeof(char) == 1ul, "Unsupported platform, char is not 8-bits wide."); + +namespace terse { + +namespace traits { + +template +struct sink { + using type = void; +}; + +template +struct needs_allocator : std::false_type {}; + +template +struct needs_allocator().get_allocator())>::type> : std::true_type {}; + +template +struct true_sink : std::true_type {}; + +// Serializer member functions +template +static auto test_serialize_member(std::int32_t)->true_sink().serialize(std::declval()))>; + +template +static auto test_serialize_member(std::uint32_t)->std::false_type; + +template +struct has_serialize_member : decltype(test_serialize_member(0)) {}; + +template +static auto test_load_member(std::int32_t)->true_sink().load(std::declval()))>; + +template +static auto test_load_member(std::uint32_t)->std::false_type; + +template +struct has_load_member : decltype(test_load_member(0)) {}; + +template +static auto test_save_member(std::int32_t)->true_sink().save(std::declval()))>; + +template +static auto test_save_member(std::uint32_t)->std::false_type; + +template +struct has_save_member : decltype(test_save_member(0)) {}; + +template +static auto test_reserve_member(std::int32_t)->true_sink().reserve(0u))>; + +template +static auto test_reserve_member(std::uint32_t)->std::false_type; + +template +struct has_reserve_member : decltype(test_reserve_member(0)) {}; + +template +static auto test_push_back_member(std::int32_t)->true_sink().push_back( + std::declval()))>; + +template +static auto test_push_back_member(std::uint32_t)->std::false_type; + +template +struct has_push_back_member : decltype(test_push_back_member(0)) {}; + +// Serializer free functions + +template +static auto test_serialize_function(std::int32_t)->true_sink(), std::declval()))>; + +template +static auto test_serialize_function(std::uint32_t)->std::false_type; + +template +struct has_serialize_function : decltype(test_serialize_function(0)) {}; + +template +static auto test_load_function(std::int32_t)->true_sink(), std::declval()))>; + +template +static auto test_load_function(std::uint32_t)->std::false_type; + +template +struct has_load_function : decltype(test_load_function(0)) {}; + +template +static auto test_save_function(std::int32_t)->true_sink(), std::declval()))>; + +template +static auto test_save_function(std::uint32_t)->std::false_type; + +template +struct has_save_function : decltype(test_save_function(0)) {}; + +template +using is_batchable = std::is_scalar; + +template +struct has_wide_elements { + static constexpr bool value = (sizeof(typename TContainer::value_type) > 1ul); +}; + +template +struct is_pair : public std::false_type {}; + +template +struct is_pair > : public std::true_type {}; + +template +struct is_tuple : public std::false_type {}; + +template +struct is_tuple > : public std::true_type {}; + +} // namespace traits + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/archives/binary/InputArchive.h b/dnacalib/DNACalib/src/terse/archives/binary/InputArchive.h new file mode 100644 index 0000000..3ce842b --- /dev/null +++ b/dnacalib/DNACalib/src/terse/archives/binary/InputArchive.h @@ -0,0 +1,285 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "terse/Archive.h" +#include "terse/archives/Common.h" +#include "terse/archives/Traits.h" +#include "terse/types/Anchor.h" +#include "terse/types/ArchiveOffset.h" +#include "terse/types/ArchiveSize.h" +#include "terse/types/Blob.h" +#include "terse/types/DynArray.h" +#include "terse/types/Transparent.h" +#include "terse/utils/ByteSwap.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace terse { + +template +class ExtendableBinaryInputArchive : public Archive { + public: + // Given the possibility of both 32 and 64bit platforms, use a fixed width type during serialization + using SizeType = TSize; + using OffsetType = TOffset; + + static constexpr Endianness endianness() { + return EByteOrder; + } + + private: + using BaseArchive = Archive; + + public: + ExtendableBinaryInputArchive(TExtender* extender, TStream* stream_) : BaseArchive{extender}, stream{stream_} { + } + + bool isOk() { + return true; + } + + void sync() { + } + + void label(const char* /*unused*/) { + } + + protected: + template + void process(Transparent&& dest) { + process(dest.data); + } + + void process(Anchor& dest) { + dest.value = static_cast(stream->tell()); + } + + void process(ArchiveOffset& dest) { + // Load the offset value itself (this points forward within the stream to the position of + // the data with which the offset is associated) + process(dest.value); + } + + void process(typename ArchiveOffset::Proxy& dest) { + // Rely on the offset value stored in the associated `ArchiveOffset` and seek to it + stream->seek(dest.target->value); + } + + void process(typename ArchiveOffset::Proxy&& dest) { + process(dest); + } + + void process(ArchiveSize& dest) { + // Load the size value itself (this value can be combined with an offset to get the end of a data region) + process(dest.value); + } + + void process(typename ArchiveSize::Proxy& dest) { + // Calculate the offset + size value stored in the associated `ArchiveSize` and `Anchor` and seek to it + assert(dest.base != nullptr); + stream->seek(dest.base->value + dest.target->value); + } + + void process(typename ArchiveSize::Proxy&& dest) { + process(dest); + } + + template + void process(Blob& dest) { + using ValueType = typename Blob::value_type; + // Blob relies on a predetermined (user specified) size of how much data it should consume + if (dest.size() != 0ul) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + stream->read(reinterpret_cast(dest.data()), dest.size() * sizeof(ValueType)); + } + } + + template + typename std::enable_if::value, + void>::type process(T& dest) { + dest.load(*static_cast(this)); + } + + template + typename std::enable_if::value, + void>::type process(T& dest) { + dest.serialize(*static_cast(this)); + } + + template + typename std::enable_if::value, + void>::type process(T& dest) { + load(*static_cast(this), dest); + } + + template + typename std::enable_if::value, + void>::type process(T& dest) { + serialize(*static_cast(this), dest); + } + + template + typename std::enable_if::value && !traits::has_serialize_member::value && + !traits::has_load_function::value && !traits::has_serialize_function::value, + void>::type process(T& dest) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + stream->read(reinterpret_cast(&dest), sizeof(T)); + SwapFrom::swap(dest); + } + + template + void process(std::array& dest) { + for (auto& element : dest) { + BaseArchive::dispatch(element); + } + } + + template + void process(std::vector& dest) { + const auto size = processSize(); + processElements(dest, size); + } + + template + void process(DynArray& dest) { + const auto size = processSize(); + processElements(dest, size); + } + + template + void process(std::basic_string& dest) { + const auto size = processSize(); + processElements(dest, size); + } + + template + void process(std::pair& dest) { + BaseArchive::dispatch(dest.first); + BaseArchive::dispatch(dest.second); + } + + template + void process(std::tuple& dest) { + BaseArchive::dispatch(std::get<0>(dest)); + BaseArchive::dispatch(std::get<1>(dest)); + } + + std::size_t processSize() { + SizeType size{}; + process(size); + return static_cast(size); + } + + template + typename std::enable_if::value + && traits::has_reserve_member::value + && traits::has_push_back_member::value + >::type + processElements(TContainer& dest, std::size_t size) { + using ValueType = typename TContainer::value_type; + dest.clear(); + dest.reserve(size); + for (std::size_t i = 0ul; i < size; ++i) { + dest.push_back(impl::ValueFactory::create(dest.get_allocator())); + BaseArchive::dispatch(dest.back()); + } + } + + template + typename std::enable_if::value + && !traits::has_reserve_member::value + && !traits::has_push_back_member::value>::type + processElements(TContainer& dest, std::size_t size) { + resize(dest, size); + for (auto& element : dest) { + BaseArchive::dispatch(element); + } + } + + template + typename std::enable_if::value && traits::has_wide_elements::value>::type + processElements(TContainer& dest, std::size_t size) { + using ValueType = typename TContainer::value_type; + if (size != 0ul) { + resize(dest, size); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + stream->read(reinterpret_cast(&dest[0]), size * sizeof(ValueType)); + const std::size_t blockWidth = 16ul / sizeof(ValueType); + const std::size_t alignedSize = size - (size % blockWidth); + for (std::size_t i = 0ul; i < alignedSize; i += blockWidth) { + SwapFrom::swap(&dest[i]); + } + + for (std::size_t i = alignedSize; i < size; ++i) { + SwapFrom::swap(dest[i]); + } + } + } + + template + typename std::enable_if::value && !traits::has_wide_elements::value>::type + processElements(TContainer& dest, std::size_t size) { + using ValueType = typename TContainer::value_type; + if (size != 0ul) { + resize(dest, size); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + stream->read(reinterpret_cast(&dest[0]), size * sizeof(ValueType)); + } + } + + private: + template + void resize(TContainer& dest, std::size_t size) { + dest.resize(size); + } + + template + void resize(DynArray& dest, std::size_t size) { + dest.resize_uninitialized(size); + } + + private: + TStream* stream; +}; + +template +class BinaryInputArchive : public ExtendableBinaryInputArchive, + TStream, + TSize, + TOffset, + EByteOrder> { + private: + using BaseArchive = ExtendableBinaryInputArchive; + friend Archive; + + public: + explicit BinaryInputArchive(TStream* stream_) : BaseArchive{this, stream_} { + } + + private: + template + void process(T&& dest) { + BaseArchive::process(std::forward(dest)); + } + +}; + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/archives/binary/OutputArchive.h b/dnacalib/DNACalib/src/terse/archives/binary/OutputArchive.h new file mode 100644 index 0000000..401533b --- /dev/null +++ b/dnacalib/DNACalib/src/terse/archives/binary/OutputArchive.h @@ -0,0 +1,283 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "terse/Archive.h" +#include "terse/archives/Traits.h" +#include "terse/types/Anchor.h" +#include "terse/types/ArchiveOffset.h" +#include "terse/types/ArchiveSize.h" +#include "terse/types/Blob.h" +#include "terse/types/DynArray.h" +#include "terse/types/Transparent.h" +#include "terse/utils/ByteSwap.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace terse { + +template +class ExtendableBinaryOutputArchive : public Archive { + private: + using BaseArchive = Archive; + + public: + // Given the possibility of both 32 and 64bit platforms, use a fixed width type during serialization + using SizeType = TSize; + using OffsetType = TOffset; + + static constexpr Endianness endianness() { + return EByteOrder; + } + + public: + ExtendableBinaryOutputArchive(TExtender* extender, TStream* stream_) : BaseArchive{extender}, stream{stream_} { + } + + bool isOk() { + return true; + } + + void sync() { + } + + void label(const char* /*unused*/) { + } + + protected: + template + void process(Transparent&& source) { + process(source.data); + } + + void process(Anchor& source) { + source.value = static_cast(stream->tell()); + } + + void process(ArchiveOffset& source) { + // Record the position where the offset is going to be written so it can be seeked to later when + // its proxy is found + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wuseless-cast" + #endif + source.position = static_cast(stream->tell()); + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + // Since the actual offset value is not yet known at the time when its declaration is encountered, + // fill its place with zeros as a placeholder, and it will be later populated when its associated + // proxy is found + process(typename ArchiveOffset::ValueType{}); + } + + void process(typename ArchiveOffset::Proxy& source) { + // The current position of the stream needs to be written to the position where the associated + // `ArchiveOffset` is found. + auto current = stream->tell(); + assert(current <= std::numeric_limits::max()); + source.target->value = static_cast(current); + // Seek to the actual position of the offset marker and write the stream's above captured position there + stream->seek(source.target->position); + process(static_cast(current)); + // Return to the earlier captured stream position so processing can safely resume from there + stream->seek(current); + } + + void process(typename ArchiveOffset::Proxy&& source) { + process(source); + } + + void process(ArchiveSize& source) { + // Record the position where the size is going to be written so it can be seeked to later when + // its proxy is found + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wuseless-cast" + #endif + source.position = static_cast(stream->tell()); + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + // Since the actual size value is not yet known at the time when it's declaration is encountered, + // fill it's place with zeros as a placeholder, and it will be later populated when it's associated + // proxy is found + process(typename ArchiveSize::ValueType{}); + } + + void process(typename ArchiveSize::Proxy& source) { + // The current position of the stream minus the base offset needs to be written to the position + // where the associated `ArchiveSize` is found. + const auto current = stream->tell(); + assert(source.base != nullptr); + assert(source.base->value <= current); + auto size = current - source.base->value; + assert(size <= std::numeric_limits::max()); + source.target->value = static_cast(size); + // Seek to the actual position of the size marker and write the above calculated size + stream->seek(source.target->position); + process(static_cast(size)); + // Return to the earlier captured stream position so processing can safely resume from there + stream->seek(current); + } + + void process(typename ArchiveSize::Proxy&& source) { + process(source); + } + + template + void process(const Blob& source) { + using ValueType = typename Blob::value_type; + if (source.size() != 0ul) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + stream->write(reinterpret_cast(source.data()), source.size() * sizeof(ValueType)); + } + } + + template + typename std::enable_if::value, + void>::type process(const T& source) { + const_cast(source).save(*static_cast(this)); + } + + template + typename std::enable_if::value, + void>::type process(const T& source) { + const_cast(source).serialize(*static_cast(this)); + } + + template + typename std::enable_if::value, + void>::type process(const T& source) { + save(*static_cast(this), const_cast(source)); + } + + template + typename std::enable_if::value, + void>::type process(const T& source) { + serialize(*static_cast(this), const_cast(source)); + } + + template + typename std::enable_if::value && !traits::has_serialize_member::value && + !traits::has_save_function::value && !traits::has_serialize_function::value, + void>::type process(const T& source) { + T swapped; + std::memcpy(&swapped, &source, sizeof(T)); + SwapTo::swap(swapped); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + stream->write(reinterpret_cast(&swapped), sizeof(T)); + } + + template + void process(const std::array& source) { + processElements(source); + } + + template + void process(const std::vector& source) { + processSize(source.size()); + processElements(source); + } + + template + void process(const DynArray& source) { + processSize(source.size()); + processElements(source); + } + + template + void process(const std::basic_string& source) { + processSize(source.size()); + processElements(source); + } + + template + void process(const std::pair& source) { + BaseArchive::dispatch(source.first); + BaseArchive::dispatch(source.second); + } + + template + void process(const std::tuple& source) { + BaseArchive::dispatch(std::get<0>(source)); + BaseArchive::dispatch(std::get<1>(source)); + } + + void processSize(std::size_t size) { + assert(size <= std::numeric_limits::max()); + process(static_cast(size)); + } + + template + typename std::enable_if::value>::type + processElements(const TContainer& source) { + for (const auto& element : source) { + BaseArchive::dispatch(element); + } + } + + template + typename std::enable_if::value && traits::has_wide_elements::value>::type + processElements(const TContainer& source) { + for (const auto& element : source) { + BaseArchive::dispatch(element); + } + } + + template + typename std::enable_if::value && !traits::has_wide_elements::value>::type + processElements(const TContainer& source) { + using ValueType = typename TContainer::value_type; + const auto size = source.size(); + if (size != 0ul) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + stream->write(reinterpret_cast(&source[0]), size * sizeof(ValueType)); + } + } + + private: + TStream* stream; +}; + +template +class BinaryOutputArchive : public ExtendableBinaryOutputArchive, + TStream, + TSize, + TOffset, + EByteOrder> { + public: + using BaseArchive = ExtendableBinaryOutputArchive; + friend Archive; + + public: + explicit BinaryOutputArchive(TStream* stream_) : BaseArchive{this, stream_} { + } + + private: + template + void process(T&& dest) { + BaseArchive::process(std::forward(dest)); + } + +}; + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/archives/json/InputArchive.h b/dnacalib/DNACalib/src/terse/archives/json/InputArchive.h new file mode 100644 index 0000000..4ab8bed --- /dev/null +++ b/dnacalib/DNACalib/src/terse/archives/json/InputArchive.h @@ -0,0 +1,552 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "terse/Archive.h" +#include "terse/archives/Common.h" +#include "terse/archives/Traits.h" +#include "terse/types/Anchor.h" +#include "terse/types/ArchiveOffset.h" +#include "terse/types/ArchiveSize.h" +#include "terse/types/Blob.h" +#include "terse/types/CharInputStreamBuf.h" +#include "terse/types/DynArray.h" +#include "terse/types/Transparent.h" +#include "terse/utils/Base64.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace terse { + +template +class ExtendableJSONInputArchive : public Archive { + public: + // Given the possibility of both 32 and 64bit platforms, use a fixed width type during serialization + using SizeType = TSize; + using OffsetType = TOffset; + + private: + using BaseArchive = Archive; + + public: + ExtendableJSONInputArchive(TExtender* extender, TStream* stream_) : BaseArchive{extender}, streamBuf{stream_}, + stream{&streamBuf}, state{false, false, false} { + } + + bool isOk() const { + return !state.malformed; + } + + void sync() { + } + + void label(const char* value) { + if (state.malformed) { + return; + } + + skipWhiteSpace(); + + if (state.firstMember) { + state.firstMember = false; + } else { + if (!expectChar(',')) { + return; + } + skipWhiteSpace(); + } + + if (!expectChar('"')) { + return; + } + + for (std::size_t i = {}; i < std::strlen(value); ++i) { + if (!expectChar(value[i])) { + return; + } + } + + if (!expectChar('"')) { + return; + } + + skipWhiteSpace(); + + if (!expectChar(':')) { + return; + } + + skipWhiteSpace(); + } + + protected: + template + void process(Transparent&& dest) { + pushTransparency(); + process(dest.data); + } + + void process(Anchor& /*unused*/) { + // Anchor has no meaning in a non-binary format, so it's just silently ignored + } + + void process(ArchiveOffset& dest) { + process(dest.value); + } + + void process(typename ArchiveOffset::Proxy& /*unused*/) { + // ArchiveOffset::Proxy has no meaning in a non-binary format, so it's just silently ignored + } + + void process(typename ArchiveOffset::Proxy&& /*unused*/) { + // ArchiveOffset::Proxy has no meaning in a non-binary format, so it's just silently ignored + } + + void process(ArchiveSize& dest) { + process(dest.value); + } + + void process(typename ArchiveSize::Proxy& /*unused*/) { + // ArchiveSize::Proxy has no meaning in a non-binary format, so it's just silently ignored + } + + void process(typename ArchiveSize::Proxy&& /*unused*/) { + // ArchiveSize::Proxy has no meaning in a non-binary format, so it's just silently ignored + } + + template + void process(Blob& dest) { + std::basic_string, + typename Blob::allocator_type> buffer{dest.get_allocator()}; + // dest.size() is the size of the decoded data already (set by user), from which the length of the + // encoded data is calculated and used to reserve storage for the temporary buffer where the encoded + // data is first loaded + buffer.reserve(base64encode(dest.size())); + // Read base64-encoded data into temporary buffer + process(buffer); + // Decode from temp buffer into dest + base64decode(dest.data(), buffer.data(), buffer.size()); + } + + template + typename std::enable_if::value, + void>::type process(T& dest) { + if (state.malformed) { + return; + } + + const bool transparent = popTransparency(); + if (!transparent) { + preStructInput(); + } + + dest.load(*static_cast(this)); + + if (!transparent) { + postStructInput(); + } + } + + template + typename std::enable_if::value, + void>::type process(T& dest) { + if (state.malformed) { + return; + } + + const bool transparent = popTransparency(); + if (!transparent) { + preStructInput(); + } + + dest.serialize(*static_cast(this)); + + if (!transparent) { + postStructInput(); + } + } + + template + typename std::enable_if::value, + void>::type process(T& dest) { + if (state.malformed) { + return; + } + + const bool transparent = popTransparency(); + if (!transparent) { + preStructInput(); + } + + load(*static_cast(this), dest); + + if (!transparent) { + postStructInput(); + } + } + + template + typename std::enable_if::value, + void>::type process(T& dest) { + if (state.malformed) { + return; + } + + const bool transparent = popTransparency(); + if (!transparent) { + preStructInput(); + } + + serialize(*static_cast(this), dest); + + if (!transparent) { + postStructInput(); + } + } + + template + typename std::enable_if::value && !traits::has_serialize_member::value && + !traits::has_load_function::value && !traits::has_serialize_function::value, + void>::type process(T& dest) { + if (state.malformed) { + return; + } + stream >> dest; + } + + void process(char& dest) { + if (!expectChar('"')) { + return; + } + + if (!readChar(&dest)) { + return; + } + + if (!expectChar('"')) { + return; + } + } + + void process(std::uint8_t& dest) { + std::uint16_t temp = {}; + stream >> temp; + dest = static_cast(temp); + } + + void process(std::int8_t& dest) { + std::int16_t temp = {}; + stream >> temp; + dest = static_cast(temp); + } + + template + void process(std::array& dest) { + if (state.malformed) { + return; + } + + skipWhiteSpace(); + if (!expectChar('[')) { + return; + } + skipWhiteSpace(); + + if (stream.peek() == ']') { + expectChar(']'); + return; + } + + for (auto& element : dest) { + BaseArchive::dispatch(element); + if (state.malformed) { + return; + } + + skipWhiteSpace(); + char ch = {}; + if (!readChar(&ch)) { + return; + } + if (ch == ',') { + skipWhiteSpace(); + } else if (ch == ']') { + break; + } + } + + skipWhiteSpace(); + } + + template + void process(std::vector& dest) { + if (state.malformed) { + return; + } + + skipWhiteSpace(); + if (!expectChar('[')) { + return; + } + skipWhiteSpace(); + + if (stream.peek() == ']') { + expectChar(']'); + return; + } + + dest.clear(); + while (true) { + dest.push_back(impl::ValueFactory::create(dest.get_allocator())); + BaseArchive::dispatch(dest.back()); + if (state.malformed) { + return; + } + + skipWhiteSpace(); + char ch = {}; + if (!readChar(&ch)) { + return; + } + if (ch == ',') { + skipWhiteSpace(); + } else if (ch == ']') { + break; + } + } + + skipWhiteSpace(); + } + + template + void process(DynArray& dest) { + if (state.malformed) { + return; + } + + skipWhiteSpace(); + if (!expectChar('[')) { + return; + } + skipWhiteSpace(); + + if (stream.peek() == ']') { + expectChar(']'); + return; + } + + dest.resize_uninitialized(1ul); + std::size_t validElementCount = {}; + + while (true) { + BaseArchive::dispatch(dest[validElementCount]); + if (state.malformed) { + break; + } + + ++validElementCount; + if (validElementCount == dest.size()) { + dest.resize_uninitialized(dest.size() * 2ul); + } + + skipWhiteSpace(); + char ch = {}; + if (!readChar(&ch)) { + break; + } + if (ch == ',') { + skipWhiteSpace(); + } else if (ch == ']') { + break; + } + } + + dest.resize(validElementCount); + skipWhiteSpace(); + } + + template + void process(std::basic_string& dest) { + if (state.malformed) { + return; + } + + if (!expectChar('"')) { + return; + } + + dest.clear(); + char ch = {}; + while (readChar(&ch)) { + if (ch == '"') { + return; + } else { + dest.push_back(ch); + } + } + + // This should not be reached if the string was properly quoted + state.malformed = true; + } + + template + void process(std::pair& dest) { + if (state.malformed) { + return; + } + + skipWhiteSpace(); + if (!expectChar('[')) { + return; + } + skipWhiteSpace(); + + BaseArchive::dispatch(dest.first); + + skipWhiteSpace(); + if (!expectChar(',')) { + return; + } + skipWhiteSpace(); + + BaseArchive::dispatch(dest.second); + + skipWhiteSpace(); + if (!expectChar(']')) { + return; + } + skipWhiteSpace(); + } + + template + void process(std::tuple& dest) { + if (state.malformed) { + return; + } + + skipWhiteSpace(); + if (!expectChar('[')) { + return; + } + skipWhiteSpace(); + + BaseArchive::dispatch(std::get<0>(dest)); + + skipWhiteSpace(); + if (!expectChar(',')) { + return; + } + skipWhiteSpace(); + + BaseArchive::dispatch(std::get<1>(dest)); + + skipWhiteSpace(); + if (!expectChar(']')) { + return; + } + skipWhiteSpace(); + } + + private: + bool readChar(char* dest) { + if (!stream.read(dest, 1)) { + state.malformed = true; + return false; + } + return true; + } + + bool expectChar(char expected) { + char ch = {}; + if (!readChar(&ch)) { + return false; + } + if (ch != expected) { + state.malformed = true; + return false; + } + return true; + } + + void skipWhiteSpace() { + std::ws(stream); + } + + void preStructInput() { + state.firstMember = true; + + skipWhiteSpace(); + if (!expectChar('{')) { + return; + } + skipWhiteSpace(); + } + + void postStructInput() { + skipWhiteSpace(); + if (!expectChar('}')) { + return; + } + skipWhiteSpace(); + } + + void pushTransparency() { + state.transparent = true; + } + + bool popTransparency() { + const bool transparent = state.transparent; + state.transparent = false; + return transparent; + } + + private: + struct SerializationState { + bool malformed; + bool firstMember; + bool transparent; + }; + + private: + CharInputStreamBuf streamBuf; + std::istream stream; + SerializationState state; +}; + +template +class JSONInputArchive : public ExtendableJSONInputArchive, TStream, TSize, TOffset> { + private: + using BaseArchive = ExtendableJSONInputArchive; + friend Archive; + + public: + explicit JSONInputArchive(TStream* stream_) : BaseArchive{this, stream_} { + } + + private: + template + void process(T&& dest) { + BaseArchive::process(std::forward(dest)); + } + +}; + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/archives/json/OutputArchive.h b/dnacalib/DNACalib/src/terse/archives/json/OutputArchive.h new file mode 100644 index 0000000..f7a8e4d --- /dev/null +++ b/dnacalib/DNACalib/src/terse/archives/json/OutputArchive.h @@ -0,0 +1,310 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "terse/Archive.h" +#include "terse/archives/Traits.h" +#include "terse/types/Anchor.h" +#include "terse/types/ArchiveOffset.h" +#include "terse/types/ArchiveSize.h" +#include "terse/types/Blob.h" +#include "terse/types/CharOutputStreamBuf.h" +#include "terse/types/DynArray.h" +#include "terse/types/Transparent.h" +#include "terse/utils/Base64.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace terse { + +template +class ExtendableJSONOutputArchive : public Archive { + private: + using BaseArchive = Archive; + + public: + // Given the possibility of both 32 and 64bit platforms, use a fixed width type during serialization + using SizeType = TSize; + using OffsetType = TOffset; + + public: + ExtendableJSONOutputArchive(TExtender* extender, TStream* stream_, std::uint32_t indentWidth) : BaseArchive{extender}, + streamBuf{stream_}, + stream{&streamBuf}, + state{indentWidth, 0u, false, false} { + } + + bool isOk() { + return true; + } + + void sync() { + stream << std::flush; + } + + void label(const char* value) { + if (state.firstMember) { + state.firstMember = false; + } else { + stream << ",\n"; + } + indent(); + stream << "\"" << value << "\"" << ": "; + } + + protected: + template + void process(Transparent&& source) { + pushTransparency(); + process(source.data); + } + + void process(Anchor& /*unused*/) { + // Anchor has no meaning in a non-binary format, so it's just silently ignored + } + + void process(ArchiveOffset& source) { + process(source.value); + } + + void process(typename ArchiveOffset::Proxy& /*unused*/) { + // ArchiveOffset::Proxy has no meaning in a non-binary format, so it's just silently ignored + } + + void process(typename ArchiveOffset::Proxy&& /*unused*/) { + // ArchiveOffset::Proxy has no meaning in a non-binary format, so it's just silently ignored + } + + void process(ArchiveSize& source) { + process(source.value); + } + + void process(typename ArchiveSize::Proxy& /*unused*/) { + // ArchiveSize::Proxy has no meaning in a non-binary format, so it's just silently ignored + } + + void process(typename ArchiveSize::Proxy&& /*unused*/) { + // ArchiveSize::Proxy has no meaning in a non-binary format, so it's just silently ignored + } + + template + void process(const Blob& source) { + const auto encodedSize = base64encode(source.size()); + std::basic_string, typename Blob::allocator_type> buffer{encodedSize, '\0', + source.get_allocator()}; + base64encode(&buffer[0], source.data(), source.size()); + // Write base64-encoded data from string temporary buffer + process(buffer); + } + + template + typename std::enable_if::value, + void>::type process(const T& source) { + const bool transparent = popTransparency(); + if (!transparent) { + preStructOutput(); + } + const_cast(source).save(*static_cast(this)); + if (!transparent) { + postStructOutput(); + } + } + + template + typename std::enable_if::value, + void>::type process(const T& source) { + const bool transparent = popTransparency(); + if (!transparent) { + preStructOutput(); + } + const_cast(source).serialize(*static_cast(this)); + if (!transparent) { + postStructOutput(); + } + } + + template + typename std::enable_if::value, + void>::type process(const T& source) { + const bool transparent = popTransparency(); + if (!transparent) { + preStructOutput(); + } + save(*static_cast(this), const_cast(source)); + if (!transparent) { + postStructOutput(); + } + } + + template + typename std::enable_if::value, + void>::type process(const T& source) { + const bool transparent = popTransparency(); + if (!transparent) { + preStructOutput(); + } + serialize(*static_cast(this), const_cast(source)); + if (!transparent) { + postStructOutput(); + } + } + + template + typename std::enable_if::value && !traits::has_serialize_member::value && + !traits::has_save_function::value && !traits::has_serialize_function::value, + void>::type process(const T& source) { + stream << source; + } + + void process(char source) { + stream << "\"" << source << "\""; + } + + void process(std::uint8_t source) { + stream << static_cast(source); + } + + void process(std::int8_t source) { + stream << static_cast(source); + } + + template + void process(const std::array& source) { + processElements(source); + } + + template + void process(const std::vector& source) { + processElements(source); + } + + template + void process(const DynArray& source) { + processElements(source); + } + + template + void process(const std::basic_string& source) { + stream << "\"" << source << "\""; + } + + template + void process(const std::pair& source) { + stream << "["; + BaseArchive::dispatch(source.first); + stream << ", "; + BaseArchive::dispatch(source.second); + stream << "]"; + } + + template + void process(const std::tuple& source) { + stream << "["; + BaseArchive::dispatch(std::get<0>(source)); + stream << ", "; + BaseArchive::dispatch(std::get<1>(source)); + stream << "]"; + } + + template + void processElements(const TContainer& source) { + stream << "["; + if (!source.empty()) { + for (auto it = source.begin();;) { + BaseArchive::dispatch(*it); + ++it; + if (it == source.end()) { + break; + } else { + stream << ", "; + } + } + } + stream << "]"; + } + + private: + void indent() { + std::fill_n(std::ostream_iterator(stream), state.indentLevel * state.indentWidth, ' '); + } + + void preStructOutput() { + state.firstMember = true; + + stream << "{"; + stream << "\n"; + + ++state.indentLevel; + } + + void postStructOutput() { + --state.indentLevel; + + stream << "\n"; + indent(); + stream << "}"; + } + + void pushTransparency() { + state.transparent = true; + } + + bool popTransparency() { + const bool transparent = state.transparent; + state.transparent = false; + return transparent; + } + + private: + struct SerializationState { + std::uint32_t indentWidth; + std::uint32_t indentLevel; + bool firstMember; + bool transparent; + }; + + private: + CharOutputStreamBuf streamBuf; + std::ostream stream; + SerializationState state; +}; + +template +class JSONOutputArchive : public ExtendableJSONOutputArchive, TStream, TSize, + TOffset> { + public: + using BaseArchive = ExtendableJSONOutputArchive; + friend Archive; + + public: + JSONOutputArchive(TStream* stream_, std::uint32_t indentWidth_) : BaseArchive{this, stream_, indentWidth_} { + } + + private: + template + void process(T&& dest) { + BaseArchive::process(std::forward(dest)); + } + +}; + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/types/Anchor.h b/dnacalib/DNACalib/src/terse/types/Anchor.h new file mode 100644 index 0000000..e5671a7 --- /dev/null +++ b/dnacalib/DNACalib/src/terse/types/Anchor.h @@ -0,0 +1,56 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +namespace terse { + +// Anchor is a virtual, utility type only (meaning it does not write or read any data from the stream) +// It only captures the current position in the stream (into an in-memory variable only), at the time +// when the anchor is encountered in the serialization process. +// An Anchor can be associated with an ArchiveSize type, and ArchiveSize will use it as the base offset +// from which to calculate its own size. +template +struct Anchor { + using ValueType = TOffset; + using OnMoveCallback = void (*)(Anchor*, void*); + + mutable ValueType value; + + OnMoveCallback onMoveCallback; + void* onMoveCallbackData; + + Anchor() : value{}, onMoveCallback{nullptr}, onMoveCallbackData{nullptr} { + } + + ~Anchor() = default; + + Anchor(const Anchor&) = delete; + Anchor& operator=(const Anchor&) = delete; + + Anchor(Anchor&& rhs) : value{}, onMoveCallback{nullptr}, onMoveCallbackData{nullptr} { + std::swap(value, rhs.value); + std::swap(onMoveCallback, rhs.onMoveCallback); + std::swap(onMoveCallbackData, rhs.onMoveCallbackData); + if (onMoveCallback) { + onMoveCallback(this, onMoveCallbackData); + } + } + + Anchor& operator=(Anchor&& rhs) { + std::swap(value, rhs.value); + std::swap(onMoveCallback, rhs.onMoveCallback); + std::swap(onMoveCallbackData, rhs.onMoveCallbackData); + if (onMoveCallback) { + onMoveCallback(this, onMoveCallbackData); + } + return *this; + } + + void onMove(OnMoveCallback callback, void* data) { + onMoveCallback = callback; + onMoveCallbackData = data; + } + +}; + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/types/ArchiveOffset.h b/dnacalib/DNACalib/src/terse/types/ArchiveOffset.h new file mode 100644 index 0000000..9f91d31 --- /dev/null +++ b/dnacalib/DNACalib/src/terse/types/ArchiveOffset.h @@ -0,0 +1,106 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace terse { + +// ArchiveOffset is a type that stores an absolute stream offset (both in-memory and writes that value into the stream as well). +// It has an accompanying Proxy type, which is a virtual, utility type, and they work together to achieve the function +// of ArchiveOffset. +// When ArchiveOffset is encountered during the serialization process, at first an empty offset (zero) value will be written +// into the stream and the position of that offset value will be captured in an in-memory variable. Later, when its associated +// ArchiveOffset::Proxy is encountered, the current stream position will be captured, and it will update the in-memory value +// of its ArchiveOffset with it. Then, it will seek the stream to the earlier captured position of the offset value (where zeros +// were written initially), and it will write the offset value captured by the proxy into the stream. Lastly, it will seek back +// to the position of the stream before the proxy was encountered, and resume serialization. +template +struct ArchiveOffset { + using ValueType = TOffset; + + struct Proxy { + ArchiveOffset* target; + + explicit Proxy(ArchiveOffset& ptr) : target{std::addressof(ptr)} { + target->proxy = this; + } + + ~Proxy() { + if (target != nullptr) { + target->proxy = nullptr; + } + } + + Proxy(const Proxy&) = delete; + Proxy& operator=(const Proxy&) = delete; + + Proxy(Proxy&& rhs) : target{nullptr} { + std::swap(target, rhs.target); + target->proxy = this; + } + + Proxy& operator=(Proxy&& rhs) { + std::swap(target, rhs.target); + target->proxy = this; + return *this; + } + + }; + + // The position of the marker itself in the stream (this is a runtime-only value + // which is not written to the file, needed only for the serializer to know where + // to seek within the stream when the marker's actual value needs to be written) + mutable std::size_t position; + // The position in the stream where the marker wants to point (this is the actual + // value that is written to the file) + mutable ValueType value; + // When offset is moved, it's associated proxy must be updated about the new address + Proxy* proxy; + + ArchiveOffset() : position{}, value{}, proxy{nullptr} { + } + + ~ArchiveOffset() = default; + + ArchiveOffset(const ArchiveOffset&) = delete; + ArchiveOffset& operator=(const ArchiveOffset&) = delete; + + ArchiveOffset(ArchiveOffset&& rhs) : position{}, value{}, proxy{nullptr} { + std::swap(position, rhs.position); + std::swap(value, rhs.value); + std::swap(proxy, rhs.proxy); + // Update proxy with new address + if (proxy != nullptr) { + proxy->target = this; + } + } + + ArchiveOffset& operator=(ArchiveOffset&& rhs) { + std::swap(position, rhs.position); + std::swap(value, rhs.value); + std::swap(proxy, rhs.proxy); + // Update proxy with new address + if (proxy != nullptr) { + proxy->target = this; + } + return *this; + } + +}; + +template +typename ArchiveOffset::Proxy proxy(ArchiveOffset& offset) { + return typename ArchiveOffset::Proxy{offset}; +} + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/types/ArchiveSize.h b/dnacalib/DNACalib/src/terse/types/ArchiveSize.h new file mode 100644 index 0000000..20e6b33 --- /dev/null +++ b/dnacalib/DNACalib/src/terse/types/ArchiveSize.h @@ -0,0 +1,128 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "terse/types/Anchor.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace terse { + +// ArchiveSize is a type that stores an arbitrary size value (both in-memory and writes that value into the stream as well). +// It has two accompanying types: Proxy and Anchor, which are both virtual, utility types, and they work together to achieve +// the function of ArchiveSize. +// When ArchiveSize is encountered during the serialization process, at first an empty size (zero) value will be written +// into the stream and the position of that size value will be captured in an in-memory variable. +// Later, when its associated Anchor is encountered, it will capture the current stream position as the base (start) offset, +// from which the size is going to be computed (size is relative to some base offset). +// Lastly, when its ArchiveSize::Proxy is encountered, the current stream position will be captured, and the earlier captured +// anchor base offset will be subtracted from it (resulting in the size value - the size between Anchor offset and Proxy offset). +// With the size value computed, it will update the in-memory value stored in ArchiveSize with it. Then, it will seek the +// stream to the earlier captured position of the size value (where zeros were written initially), and it will write the size +// value into the stream at that position. With all that done, it will seek back to the position of the stream before the +// proxy was encountered, and resume serialization. +template +struct ArchiveSize { + using ValueType = TSize; + + struct Proxy { + ArchiveSize* target; + Anchor* base; + + Proxy(ArchiveSize& size, Anchor& offset) : target{std::addressof(size)}, base{std::addressof(offset)} { + target->proxy = this; + base->onMove(onBaseMoved, this); + } + + ~Proxy() { + if (target != nullptr) { + target->proxy = nullptr; + } + if (base != nullptr) { + base->onMove(onBaseMoved, nullptr); + } + } + + Proxy(const Proxy&) = delete; + Proxy& operator=(const Proxy&) = delete; + + Proxy(Proxy&& rhs) : target{nullptr}, base{nullptr} { + std::swap(target, rhs.target); + std::swap(base, rhs.base); + target->proxy = this; + base->onMove(onBaseMoved, this); + } + + Proxy& operator=(Proxy&& rhs) { + std::swap(target, rhs.target); + std::swap(base, rhs.base); + target->proxy = this; + base->onMove(onBaseMoved, this); + return *this; + } + + private: + static void onBaseMoved(Anchor* anchor, void* data) { + if (data != nullptr) { + auto proxy = static_cast(data); + proxy->base = anchor; + } + } + + }; + + // The position of the marker itself in the stream (this is a runtime-only value + // which is not written to the file, needed only for the serializer to know where + // to seek within the stream when the marker's actual value needs to be written) + mutable std::size_t position; + // The size value itself (what is written to the file) + mutable ValueType value; + // When size is moved, it's associated proxy must be updated about the new address + Proxy* proxy; + + ArchiveSize() : position{}, value{}, proxy{nullptr} { + } + + ~ArchiveSize() = default; + + ArchiveSize(const ArchiveSize&) = delete; + ArchiveSize& operator=(const ArchiveSize&) = delete; + + ArchiveSize(ArchiveSize&& rhs) : position{}, value{}, proxy{nullptr} { + std::swap(position, rhs.position); + std::swap(value, rhs.value); + std::swap(proxy, rhs.proxy); + // Update proxy with new address + if (proxy != nullptr) { + proxy->target = this; + } + } + + ArchiveSize& operator=(ArchiveSize&& rhs) { + std::swap(position, rhs.position); + std::swap(value, rhs.value); + std::swap(proxy, rhs.proxy); + // Update proxy with new address + if (proxy != nullptr) { + proxy->target = this; + } + return *this; + } + +}; + +template +typename ArchiveSize::Proxy proxy(ArchiveSize& size, Anchor& base) { + return typename ArchiveSize::Proxy{size, base}; +} + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/types/Blob.h b/dnacalib/DNACalib/src/terse/types/Blob.h new file mode 100644 index 0000000..cd6a3ce --- /dev/null +++ b/dnacalib/DNACalib/src/terse/types/Blob.h @@ -0,0 +1,55 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "terse/types/DynArray.h" + +namespace terse { + +// Blob is a wrapper type around a byte-array essentially, and is used to serialize binary blobs, +// whose size is predefined by another entity (e.g. an ArchiveSize object). +// During serialization, the amount of bytes either written to or read from the stream will be +// controlled by the user (by setting the size on the blob type through the setSize member function). +// For text-based serializers, binary blobs are Base64 encoded/decoded. +template +class Blob { + public: + static_assert(sizeof(T) == sizeof(char), "Blob supports only native byte-sized types."); + + using value_type = T; + using allocator_type = TAllocator; + + public: + Blob() = default; + + explicit Blob(const allocator_type& alloc) : bytes{alloc} { + } + + Blob(std::size_t size, const allocator_type& alloc) : bytes{size, alloc} { + } + + allocator_type get_allocator() const noexcept { + return bytes.get_allocator(); + } + + value_type* data() { + return bytes.data(); + } + + const value_type* data() const { + return bytes.data(); + } + + std::size_t size() const { + return bytes.size(); + } + + void setSize(std::size_t newSize) { + bytes.resize_uninitialized(newSize); + } + + private: + DynArray bytes; +}; + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/types/CharInputStreamBuf.h b/dnacalib/DNACalib/src/terse/types/CharInputStreamBuf.h new file mode 100644 index 0000000..40bccdb --- /dev/null +++ b/dnacalib/DNACalib/src/terse/types/CharInputStreamBuf.h @@ -0,0 +1,98 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace terse { + +template +class CharInputStreamBuf : public std::streambuf { + public: + using PosType = std::streambuf::pos_type; + using OffType = std::streambuf::off_type; + using CharType = std::streambuf::char_type; + using IntType = std::streambuf::int_type; + using TraitsType = std::streambuf::traits_type; + + public: + explicit CharInputStreamBuf(TStream* stream_) : stream{stream_}, buffer{} { + char* bufferEnd = buffer.data() + buffer.size(); + setg(bufferEnd, bufferEnd, bufferEnd); + } + + std::streamsize xsgetn(CharType* destination, std::streamsize size) override { + if (size <= 0) { + return 0; + } + + std::streamsize bytesNeeded = size; + + // Exhaust buffer contents first + if (gptr() < egptr()) { + const std::size_t bytesInBuffer = static_cast(egptr() - gptr()); + const std::size_t bytesToCopy = std::min(static_cast(bytesNeeded), bytesInBuffer); + std::memcpy(destination, gptr(), bytesToCopy); + destination += bytesToCopy; + bytesNeeded -= static_cast(bytesToCopy); + gbump(static_cast(bytesToCopy)); + } + + // If there are still bytes needed (more data than buffer contained), read the rest directly from the stream + if (bytesNeeded > 0) { + const auto bytesRead = + static_cast(stream->read(destination, static_cast(bytesNeeded))); + bytesNeeded -= bytesRead; + } + + // Refill buffer for subsequent accesses (if possible) + underflow(); + + return size - bytesNeeded; + } + + IntType underflow() override { + if (gptr() < egptr()) { + return TraitsType::to_int_type(*gptr()); + } + + char* base = buffer.data(); + char* start = base; + // Initially eback() == buffer.end() , but if that's not the case, buffer has already been + // filled before, in which case first copy the data from the put-back area (located at the end of + // the buffer) into the beginning of the buffer, making them the next characters to be read from it. + // Put-back area is just one byte in this implementation currently. + if (eback() == base) { + std::memcpy(base, egptr() - 1ul, 1ul); + ++start; + } + + // Now fill the rest of the buffer after the put-back area is moved to the beginning of the buffer + const std::size_t bytesRead = stream->read(start, buffer.size() - static_cast(start - base)); + if (bytesRead == 0ul) { + return TraitsType::eof(); + } + + setg(base, start, start + bytesRead); + return TraitsType::to_int_type(*gptr()); + } + + std::streamsize showmanyc() override { + return static_cast(stream->size() - stream->tell()); + } + + private: + TStream* stream; + std::array buffer; + +}; + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/types/CharOutputStreamBuf.h b/dnacalib/DNACalib/src/terse/types/CharOutputStreamBuf.h new file mode 100644 index 0000000..bf1d6d5 --- /dev/null +++ b/dnacalib/DNACalib/src/terse/types/CharOutputStreamBuf.h @@ -0,0 +1,85 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace terse { + +template +class CharOutputStreamBuf : public std::streambuf { + public: + using PosType = std::streambuf::pos_type; + using OffType = std::streambuf::off_type; + using CharType = std::streambuf::char_type; + using IntType = std::streambuf::int_type; + using TraitsType = std::streambuf::traits_type; + + public: + explicit CharOutputStreamBuf(TStream* stream_) : stream{stream_}, buffer{} { + setp(buffer.data(), buffer.data() + buffer.size() - 1ul); + } + + ~CharOutputStreamBuf() { + sync(); + } + + std::streamsize xsputn(const CharType* source, std::streamsize size) override { + // Write a sequence of characters + if (size <= 0) { + return 0; + } + + // If there's space, write data into buffer + const std::ptrdiff_t spaceLeft = epptr() - pptr(); + if (size < spaceLeft) { + std::memcpy(pptr(), source, static_cast(size)); + pbump(static_cast(size)); + return size; + } + + // No space in buffer, flush it first, and write directly into stream + if (sync()) { + return 0; + } + return static_cast(stream->write(source, static_cast(size))); + } + + IntType overflow(IntType value) override { + // Write a single character + if (value == TraitsType::eof()) { + return value; + } + + const CharType data = static_cast(value); + *pptr() = data; + pbump(1); + return (sync() ? TraitsType::eof() : value); + } + + IntType sync() override { + const std::ptrdiff_t diff = pptr() - pbase(); + if (diff > 0) { + const std::size_t bytesToWrite = static_cast(diff); + const std::size_t bytesWritten = stream->write(buffer.data(), bytesToWrite); + pbump(static_cast(-diff)); + return (bytesToWrite == bytesWritten ? 0 : -1); + } + return 0; + } + + private: + TStream* stream; + std::array buffer; + +}; + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/types/DynArray.h b/dnacalib/DNACalib/src/terse/types/DynArray.h new file mode 100644 index 0000000..10b1c96 --- /dev/null +++ b/dnacalib/DNACalib/src/terse/types/DynArray.h @@ -0,0 +1,231 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace terse { + +/** + * @brief Resizable array-like abstraction for trivial-types only. + */ +template +class DynArray { + public: + static_assert(std::is_trivial::value, "Uninitialized vector doesn't support non-trivial types."); + + using value_type = T; + using allocator_type = TAllocator; + + private: + using pointer_type = std::unique_ptr >; + + public: + explicit DynArray(const allocator_type& allocator) : + alloc{allocator}, + sz{}, + ptr{nullptr} { + } + + DynArray() : DynArray{allocator_type{}} { + } + + DynArray(std::size_t size, const allocator_type& allocator = allocator_type{}) : + alloc{allocator}, + sz{size}, + ptr{create(sz, alloc)} { + } + + DynArray(std::size_t size, const value_type& value, const allocator_type& allocator = allocator_type{}) : + DynArray{size, allocator} { + + std::fill_n(data(), size, value); + } + + DynArray(const value_type* source, std::size_t size, const allocator_type& allocator = allocator_type{}) : + DynArray{size, allocator} { + + if ((data() != nullptr) && (source != nullptr)) { + std::memcpy(data(), source, size * sizeof(value_type)); + } + } + + template + DynArray(TIterator start, TIterator end, const allocator_type& allocator = allocator_type{}) : + DynArray{static_cast(std::distance(start, end)), allocator} { + + #if defined(_MSC_VER) && !defined(__clang__) + if (size() != 0ul) { + std::copy(start, end, stdext::checked_array_iterator(data(), size())); + } + #else + std::copy(start, end, data()); + #endif + } + + ~DynArray() = default; + + DynArray(const DynArray& rhs) : DynArray{rhs.size(), rhs.get_allocator()} { + if ((data() != nullptr) && (rhs.data() != nullptr)) { + std::memcpy(data(), rhs.data(), rhs.size() * sizeof(value_type)); + } + } + + DynArray& operator=(const DynArray& rhs) { + DynArray tmp{rhs}; + std::swap(alloc, tmp.alloc); + std::swap(ptr, tmp.ptr); + std::swap(sz, tmp.sz); + return *this; + } + + DynArray(DynArray&& rhs) noexcept : + alloc{}, + sz{}, + ptr{} { + + std::swap(alloc, rhs.alloc); + std::swap(ptr, rhs.ptr); + std::swap(sz, rhs.sz); + } + + DynArray& operator=(DynArray&& rhs) noexcept { + std::swap(alloc, rhs.alloc); + std::swap(ptr, rhs.ptr); + std::swap(sz, rhs.sz); + return *this; + } + + allocator_type get_allocator() const noexcept { + return alloc; + } + + void clear() { + ptr.reset(); + sz = 0ul; + } + + value_type* data() { + return ptr.get(); + } + + const value_type* data() const { + return ptr.get(); + } + + std::size_t size() const { + return sz; + } + + bool empty() const { + return (sz == 0ul); + } + + value_type& operator[](std::size_t index) { + assert(index < size()); + return data()[index]; + } + + const value_type& operator[](std::size_t index) const { + assert(index < size()); + return data()[index]; + } + + value_type* begin() { + return data(); + } + + value_type* end() { + return data() + sz; + } + + const value_type* cbegin() const { + return data(); + } + + const value_type* cend() const { + return data() + sz; + } + + const value_type* begin() const { + return cbegin(); + } + + const value_type* end() const { + return cend(); + } + + void resize(std::size_t size, const value_type& value) { + if (size > sz) { + pointer_type old{ptr.release(), [this](value_type* p) { + alloc.deallocate(p, sz); + }}; + ptr = create(size, alloc); + assert(ptr != nullptr); + if (old != nullptr) { + std::memcpy(ptr.get(), old.get(), sz * sizeof(value_type)); + } + std::fill_n(begin() + sz, size - sz, value); + } + sz = size; + } + + void resize(std::size_t size) { + resize(size, value_type{}); + } + + void resize_uninitialized(std::size_t size) { + if (size > sz) { + pointer_type old{ptr.release(), [this](value_type* p) { + alloc.deallocate(p, sz); + }}; + ptr = create(size, alloc); + assert(ptr != nullptr); + if (old != nullptr) { + std::memcpy(ptr.get(), old.get(), sz * sizeof(value_type)); + } + } + sz = size; + } + + template + void assign(TIterator start, TIterator end) { + resize_uninitialized(static_cast(std::distance(start, end))); + #if defined(_MSC_VER) && !defined(__clang__) + if (size() != 0ul) { + std::copy(start, end, stdext::checked_array_iterator(data(), size())); + } + #else + std::copy(start, end, data()); + #endif + } + + private: + static pointer_type create(std::size_t size, allocator_type alloc) { + return pointer_type{alloc.allocate(size), [alloc, size](value_type* p) mutable { + alloc.deallocate(p, size); + }}; + } + + private: + allocator_type alloc; + std::size_t sz; + pointer_type ptr; + +}; + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/types/Transparent.h b/dnacalib/DNACalib/src/terse/types/Transparent.h new file mode 100644 index 0000000..469737a --- /dev/null +++ b/dnacalib/DNACalib/src/terse/types/Transparent.h @@ -0,0 +1,24 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +namespace terse { + +// Textual serialization (currently JSON only) generates a pair of braces (`{}`) around every type +// that has a load, save or serialize member or free function, capable of serializing the type in question. +// In some cases, with lots of nested structures, for a simpler textual representation, some of these +// layers of abstractions might be preferable to be ignored. The Transparent wrapper type is used for +// exactly that purpose. By wrapping any member from within a serializer function, that member will be +// treated as transparent, thus avoiding the generation of a pair of braces for it. +template +struct Transparent { + using WrappedType = T; + WrappedType& data; +}; + +template +Transparent transparent(T& data) { + return Transparent{data}; +} + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/utils/Base64.h b/dnacalib/DNACalib/src/terse/utils/Base64.h new file mode 100644 index 0000000..2c7815c --- /dev/null +++ b/dnacalib/DNACalib/src/terse/utils/Base64.h @@ -0,0 +1,86 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace terse { + +static const char* alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +constexpr std::size_t base64encode(std::size_t size) { + return ((4ul * size / 3ul) + 3ul) & ~3ul; +} + +// destination should be of length ((4 * size / 3) + 3) & ~3 +inline std::size_t base64encode(char* destination, const char* source, std::size_t size) { + char* out = destination; + + int val = 0; + int valb = -6; + + for (std::size_t pos = {}; pos < size; ++pos) { + const auto c = static_cast(source[pos]); + val = (val << 8) + c; + valb += 8; + while (valb >= 0) { + *out++ = (alphabet[(val >> valb) & 0x3F]); + valb -= 6; + } + } + + if (valb > -6) { + *out++ = alphabet[((val << 8) >> (valb + 8)) & 0x3F]; + } + + while (static_cast(out - destination) % 4) { + *out++ = '='; + } + + // Length of base64encoded data + return static_cast(out - destination); +} + +constexpr std::size_t base64decode(std::size_t size) { + return (size * 3ul) / 4ul; +} + +// destination should be of length (size / 4) * 3 +inline std::size_t base64decode(char* destination, const char* source, std::size_t size) { + char* out = destination; + + int buffer[256]; + std::fill_n(buffer, 256, -1); + for (int i = 0; i < 64; i++) { + buffer[static_cast(alphabet[i])] = i; + } + + int val = 0; + int valb = -8; + + for (std::size_t pos = {}; pos < size; ++pos) { + const auto c = static_cast(source[pos]); + if (buffer[c] == -1) { + break; + } + val = (val << 6) + buffer[c]; + valb += 6; + if (valb >= 0) { + *out++ = static_cast((val >> valb) & 0xFF); + valb -= 8; + } + } + + // Length of base64decoded data + return static_cast(out - destination); +} + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/utils/ByteSwap.h b/dnacalib/DNACalib/src/terse/utils/ByteSwap.h new file mode 100644 index 0000000..5e75434 --- /dev/null +++ b/dnacalib/DNACalib/src/terse/utils/ByteSwap.h @@ -0,0 +1,166 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#ifdef TERSE_ENABLE_SSE + #define ENABLE_SSE_BSWAP +#endif // TERSE_ENABLE_SSE + +#include "terse/utils/Endianness.h" + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace terse { + +namespace traits { + +template +struct uint_of_size { + using type = typename std::conditional<(size == 1ul), std::uint8_t, + typename std::conditional<(size == 2ul), std::uint16_t, + typename std::conditional<(size <= 4ul), std::uint32_t, + std::uint64_t>::type>::type>::type; +}; + +} // namespace traits + +namespace impl { + +struct block128 { + + static constexpr std::size_t alignment() { + #if defined(__arm__) || defined(__aarch64__) || defined(_M_ARM) || defined(_M_ARM64) + return std::alignment_of::value; + #else + return 16ul; + #endif + } + +}; + +} // namespace impl + +enum class Endianness { + Little, + Big, + Network = Big +}; + +template +struct ByteSwapper; + +template<> +struct ByteSwapper { + + template + static void swap(T& value) { + #ifdef TARGET_LITTLE_ENDIAN + static_cast(value); + #else + using UIntType = typename traits::uint_of_size::type; + static_assert(sizeof(T) == sizeof(UIntType), "No matching unsigned integral type found for the given type."); + // Using memcpy is the only well-defined way of reconstructing arbitrary types from raw bytes. + // The seemingly unnecessary copies and memcpy calls are all optimized away, + // compiler knows what's up. + UIntType swapped; + std::memcpy(&swapped, &value, sizeof(T)); + swapped = bswap(swapped); + std::memcpy(&value, &swapped, sizeof(T)); + #endif // TARGET_LITTLE_ENDIAN + } + + template + static void swap(T* values) { + #ifdef TARGET_LITTLE_ENDIAN + static_cast(values); + #else + using UIntType = typename traits::uint_of_size::type; + static_assert(sizeof(T) == sizeof(UIntType), "No matching unsigned integral type found for the given type."); + // Using memcpy is the only well-defined way of reconstructing arbitrary types from raw bytes. + // The seemingly unnecessary copies and memcpy calls are all optimized away, + // compiler knows what's up. + alignas(impl::block128::alignment()) UIntType swapped[16ul / sizeof(T)]; + std::memcpy(static_cast(swapped), values, 16ul); + bswap(static_cast(swapped)); + std::memcpy(values, static_cast(swapped), 16ul); + #endif // TARGET_LITTLE_ENDIAN + } + +}; + +template<> +struct ByteSwapper { + + template + static void swap(T& value) { + #ifdef TARGET_LITTLE_ENDIAN + using UIntType = typename traits::uint_of_size::type; + static_assert(sizeof(T) == sizeof(UIntType), "No matching unsigned integral type found for the given type."); + // Using memcpy is the only well-defined way of reconstructing arbitrary types from raw bytes. + // The seemingly unnecessary copies and memcpy calls are all optimized away, + // compiler knows what's up. + UIntType swapped; + std::memcpy(&swapped, &value, sizeof(T)); + swapped = bswap(swapped); + std::memcpy(&value, &swapped, sizeof(T)); + #else + static_cast(value); + #endif // TARGET_LITTLE_ENDIAN + } + + template + static void swap(T* values) { + #ifdef TARGET_LITTLE_ENDIAN + using UIntType = typename traits::uint_of_size::type; + static_assert(sizeof(T) == sizeof(UIntType), "No matching unsigned integral type found for the given type."); + // Using memcpy is the only well-defined way of reconstructing arbitrary types from raw bytes. + // The seemingly unnecessary copies and memcpy calls are all optimized away, + // compiler knows what's up. + alignas(impl::block128::alignment()) UIntType swapped[16ul / sizeof(T)]; + std::memcpy(static_cast(swapped), values, 16ul); + bswap(static_cast(swapped)); + std::memcpy(values, static_cast(swapped), 16ul); + #else + static_cast(values); + #endif // TARGET_LITTLE_ENDIAN + } + +}; + +template +using SwapTo = ByteSwapper; + +template +using SwapFrom = ByteSwapper; + +template +inline void networkToHost(T& value) { + SwapFrom::swap(value); +} + +template +inline void networkToHost128(T* values) { + SwapFrom::swap(values); +} + +template +inline void hostToNetwork(T& value) { + SwapTo::swap(value); +} + +template +inline void hostToNetwork128(T* values) { + SwapTo::swap(values); +} + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/utils/Endianness.h b/dnacalib/DNACalib/src/terse/utils/Endianness.h new file mode 100644 index 0000000..ac617c6 --- /dev/null +++ b/dnacalib/DNACalib/src/terse/utils/Endianness.h @@ -0,0 +1,346 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include + +#if defined(ENABLE_SSE_BSWAP) + #include +#endif + +#if !defined(NO_ENDIAN_H) + #if defined(USE_ENDIAN_H) || defined(USE_MACHINE_ENDIAN_H) || defined(USE_SYS_ENDIAN_H) || defined(USE_SYS_ISA_DEFS_H) || \ + defined(USE_SYS_PARAM_H) + #define OVERRIDDEN_ENDIAN_H + #endif + + #if !defined(OVERRIDDEN_ENDIAN_H) + #if defined(__linux__) || defined(__GLIBC__) || defined(__CYGWIN__) || defined(__ANDROID__) + #define USE_ENDIAN_H + #elif defined(__APPLE__) + #define USE_MACHINE_ENDIAN_H + #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) + #define USE_SYS_ENDIAN_H + #elif defined(__sun) + #define USE_SYS_ISA_DEFS_H + #elif defined(__MINGW32__) || defined(__MINGW64__) || !(defined(_WIN64) || defined(_WIN32)) + #define USE_SYS_PARAM_H + #endif + #endif + + #if defined(USE_ENDIAN_H) + #include + #elif defined(USE_MACHINE_ENDIAN_H) + #include + #elif defined(USE_SYS_ENDIAN_H) + #include + #elif defined(USE_SYS_ISA_DEFS_H) + #include + #elif defined(USE_SYS_PARAM_H) + #include + #endif +#endif + +#if !defined(TARGET_LITTLE_ENDIAN) && !defined(TARGET_BIG_ENDIAN) + #if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) /*GCC*/ || \ + (defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN) /*Linux*/ || \ + (defined(_BYTE_ORDER) && _BYTE_ORDER == _LITTLE_ENDIAN) /*xBSD,Sun*/ || \ + (defined(BYTE_ORDER) && BYTE_ORDER == LITTLE_ENDIAN) /*Apple,MingW*/ || \ + defined(__LITTLE_ENDIAN__) /*GCC Mac*/ || defined(__ARMEL__) /*GCC,Clang*/ || \ + defined(__THUMBEL__) /*GCC,Clang*/ || defined(__AARCH64EL__) /*GCC,Clang*/ || \ + defined(_MIPSEL) /*GCC,Clang*/ || defined(__MIPSEL) /*GCC,Clang*/ || \ + defined(__MIPSEL__) /*GCC,Clang*/ || defined(_M_IX86) /*MSVC*/ || \ + defined(_M_X64) /*MSVC*/ || defined(_M_IA64) /*MSVC*/ || \ + defined(_M_AMD64) /*MSVC*/ || defined(_M_ARM) /*MSVC*/ || \ + defined(_M_ARM64) /*MSVC*/ + #define TARGET_LITTLE_ENDIAN + #elif (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) /*GCC*/ || \ + (defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN) /*Linux*/ || \ + (defined(_BYTE_ORDER) && _BYTE_ORDER == _BIG_ENDIAN) /*xBSD,Sun*/ || \ + (defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN) /*Apple,MingW*/ || \ + defined(_M_PPC) /*MSVC for XBox-360*/ || defined(__BIG_ENDIAN__) /*GCC Mac*/ || \ + defined(__ARMEB__) /*GCC,Clang*/ || defined(__THUMBEB__) /*GCC,Clang*/ || \ + defined(__AARCH64EB__) /*GCC,Clang*/ || defined(_MIPSEB) /*GCC,Clang*/ || \ + defined(__MIPSEB) /*GCC,Clang*/ || defined(__MIPSEB__) /*GCC,Clang*/ + #define TARGET_BIG_ENDIAN + #elif defined(_WIN32) + #define TARGET_LITTLE_ENDIAN + #endif // End of byte order checks +#endif // End of guard for explicitly defined endianness + +/* + * Swap intrinsics + */ +#if defined(__clang__) || (defined(__GNUC__) && \ + ((__GNUC__ == 4 && __GNUC_MINOR__ >= 8) || __GNUC__ >= 5)) + #define bswap16(x) __builtin_bswap16((x)) + #define bswap32(x) __builtin_bswap32((x)) + #define bswap64(x) __builtin_bswap64((x)) +#elif defined(__linux__) || defined(__GLIBC__) + #include + #define bswap16(x) bswap_16((x)) + #define bswap32(x) bswap_32((x)) + #define bswap64(x) bswap_64((x)) +#elif defined(_MSC_VER) + #include + #define bswap16(x) _byteswap_ushort((x)) + #define bswap32(x) _byteswap_ulong((x)) + #define bswap64(x) _byteswap_uint64((x)) +#elif defined(__APPLE__) + #include + #define bswap16(x) OSSwapInt16((x)) + #define bswap32(x) OSSwapInt32((x)) + #define bswap64(x) OSSwapInt64((x)) +#elif defined(__FreeBSD__) || defined(__NetBSD__) + #include // This defines the intrinsics as per the chosen naming convention +#elif defined(__OpenBSD__) + #include + #define bswap16(x) swap16((x)) + #define bswap32(x) swap32((x)) + #define bswap64(x) swap64((x)) +#elif defined(__sun) || defined(sun) + #include + #define bswap16(x) BSWAP_16((x)) + #define bswap32(x) BSWAP_32((x)) + #define bswap64(x) BSWAP_64((x)) +#else + static inline std::uint16_t bswap16(std::uint16_t x) { + return (((x& std::uint16_t{0x00FF}) << 8) | + ((x& std::uint16_t{0xFF00}) >> 8)); + } + + static inline std::uint32_t bswap32(std::uint32_t x) { + return (((x& std::uint32_t{0x000000FF}) << 24) | + ((x& std::uint32_t{0x0000FF00}) << 8) | + ((x& std::uint32_t{0x00FF0000}) >> 8) | + ((x& std::uint32_t{0xFF000000}) >> 24)); + } + + static inline std::uint64_t bswap64(std::uint64_t x) { + return (((x& std::uint64_t{0x00000000000000FF}) << 56) | + ((x& std::uint64_t{0x000000000000FF00}) << 40) | + ((x& std::uint64_t{0x0000000000FF0000}) << 24) | + ((x& std::uint64_t{0x00000000FF000000}) << 8) | + ((x& std::uint64_t{0x000000FF00000000}) >> 8) | + ((x& std::uint64_t{0x0000FF0000000000}) >> 24) | + ((x& std::uint64_t{0x00FF000000000000}) >> 40) | + ((x& std::uint64_t{0xFF00000000000000}) >> 56)); + } + +#endif + +#if defined(ENABLE_SSE_BSWAP) + + static inline void bswap16x8(std::uint16_t* source) { + const __m128i v = _mm_load_si128(reinterpret_cast<__m128i*>(source)); + const __m128i swapped = _mm_or_si128(_mm_slli_epi16(v, 8), _mm_srli_epi16(v, 8)); + _mm_store_si128(reinterpret_cast<__m128i*>(source), swapped); + } + + static inline void bswap32x4(std::uint32_t* source) { + const __m128i v = _mm_load_si128(reinterpret_cast<__m128i*>(source)); + const __m128i swapped = _mm_shuffle_epi8(v, + _mm_set_epi8( + 12, 13, 14, 15, + 8, 9, 10, 11, + 4, 5, 6, 7, + 0, 1, 2, 3 + ) + ); + _mm_store_si128(reinterpret_cast<__m128i*>(source), swapped); + } + + static inline void bswap64x2(std::uint64_t* source) { + const __m128i v = _mm_load_si128(reinterpret_cast<__m128i*>(source)); + const __m128i swapped = _mm_shuffle_epi8(v, + _mm_set_epi8( + 8, 9, 10, 11, + 12, 13, 14, 15, + 0, 1, 2, 3, + 4, 5, 6, 7 + ) + ); + _mm_store_si128(reinterpret_cast<__m128i*>(source), swapped); + } + +#else + + static inline void bswap16x8(std::uint16_t* source) { + source[0] = bswap16(source[0]); + source[1] = bswap16(source[1]); + source[2] = bswap16(source[2]); + source[3] = bswap16(source[3]); + source[4] = bswap16(source[4]); + source[5] = bswap16(source[5]); + source[6] = bswap16(source[6]); + source[7] = bswap16(source[7]); + } + + static inline void bswap32x4(std::uint32_t* source) { + source[0] = bswap32(source[0]); + source[1] = bswap32(source[1]); + source[2] = bswap32(source[2]); + source[3] = bswap32(source[3]); + } + + static inline void bswap64x2(std::uint64_t* source) { + source[0] = bswap64(source[0]); + source[1] = bswap64(source[1]); + } + +#endif // ENABLE_SSE_BSWAP + +// Target architecture specific ntoh and hton for all relevant sizes +// In case of big endian architectures this is a noop +#if defined(TARGET_LITTLE_ENDIAN) + #define ntoh8(x) (x) + #define hton8(x) (x) + #define ntoh16(x) bswap16((x)) + #define hton16(x) bswap16((x)) + #define ntoh32(x) bswap32((x)) + #define hton32(x) bswap32((x)) + #define ntoh64(x) bswap64((x)) + #define hton64(x) bswap64((x)) + // Vectorized swap + #define ntoh8x16(x) (x) + #define hton8x16(x) (x) + #define ntoh16x8(x) bswap16x8((x)) + #define hton16x8(x) bswap16x8((x)) + #define ntoh32x4(x) bswap32x4((x)) + #define hton32x4(x) bswap32x4((x)) + #define ntoh64x2(x) bswap64x2((x)) + #define hton64x2(x) bswap64x2((x)) +#elif defined(TARGET_BIG_ENDIAN) + #define ntoh8(x) (x) + #define hton8(x) (x) + #define ntoh16(x) (x) + #define hton16(x) (x) + #define ntoh32(x) (x) + #define hton32(x) (x) + #define ntoh64(x) (x) + #define hton64(x) (x) + // Vectorized swap + #define ntoh8x16(x) (x) + #define hton8x16(x) (x) + #define ntoh16x8(x) (x) + #define hton16x8(x) (x) + #define ntoh32x4(x) (x) + #define hton32x4(x) (x) + #define ntoh64x2(x) (x) + #define hton64x2(x) (x) +#else + #error "Platform not supported, no byte swap functions defined." +#endif + +#if defined(__clang__) || defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-value" +#endif + +// Process single values + +inline std::uint8_t ntoh(std::uint8_t x) { + return ntoh8(x); +} + +inline std::uint16_t ntoh(std::uint16_t x) { + return ntoh16(x); +} + +inline std::uint32_t ntoh(std::uint32_t x) { + return ntoh32(x); +} + +inline std::uint64_t ntoh(std::uint64_t x) { + return ntoh64(x); +} + +inline std::uint8_t hton(std::uint8_t x) { + return hton8(x); +} + +inline std::uint16_t hton(std::uint16_t x) { + return hton16(x); +} + +inline std::uint32_t hton(std::uint32_t x) { + return hton32(x); +} + +inline std::uint64_t hton(std::uint64_t x) { + return hton64(x); +} + +// Process multiple blocks simultaneously + +inline void ntoh(std::uint8_t* x) { + ntoh8x16(x); +} + +inline void ntoh(std::uint16_t* x) { + ntoh16x8(x); +} + +inline void ntoh(std::uint32_t* x) { + ntoh32x4(x); +} + +inline void ntoh(std::uint64_t* x) { + ntoh64x2(x); +} + +inline void hton(std::uint8_t* x) { + hton8x16(x); +} + +inline void hton(std::uint16_t* x) { + hton16x8(x); +} + +inline void hton(std::uint32_t* x) { + hton32x4(x); +} + +inline void hton(std::uint64_t* x) { + hton64x2(x); +} + +// Byte swap overloads + +inline std::uint8_t bswap(std::uint8_t x) { + // No operation + return x; +} + +inline std::uint16_t bswap(std::uint16_t x) { + return bswap16(x); +} + +inline std::uint32_t bswap(std::uint32_t x) { + return bswap32(x); +} + +inline std::uint64_t bswap(std::uint64_t x) { + return bswap64(x); +} + +inline void bswap(std::uint8_t* x) { + // No operation + static_cast(x); +} + +inline void bswap(std::uint16_t* x) { + bswap16x8(x); +} + +inline void bswap(std::uint32_t* x) { + bswap32x4(x); +} + +inline void bswap(std::uint64_t* x) { + bswap64x2(x); +} + +#if defined(__clang__) || defined(__GNUC__) + #pragma GCC diagnostic pop +#endif diff --git a/dnacalib/DNACalib/src/terse/utils/VirtualSerializerProxy.h b/dnacalib/DNACalib/src/terse/utils/VirtualSerializerProxy.h new file mode 100644 index 0000000..e6c5f6e --- /dev/null +++ b/dnacalib/DNACalib/src/terse/utils/VirtualSerializerProxy.h @@ -0,0 +1,28 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +namespace terse { + +template +class VirtualSerializerProxy { + public: + explicit VirtualSerializerProxy(Serializable* instance_) : instance{instance_} { + } + + template + void load(TArchive& archive) { + instance->load(archive); + } + + template + void save(TArchive& archive) { + instance->save(archive); + } + + private: + Serializable* instance; + +}; + +} // namespace terse diff --git a/dnacalib/DNACalib/src/terse/version/Version.h b/dnacalib/DNACalib/src/terse/version/Version.h new file mode 100644 index 0000000..f3414e4 --- /dev/null +++ b/dnacalib/DNACalib/src/terse/version/Version.h @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#define TERSE_MAJOR_VERSION 3 +#define TERSE_MINOR_VERSION 0 +#define TERSE_PATCH_VERSION 1 +#define TERSE_VERSION_STRING "3.0.1" diff --git a/dnacalib/DNACalib/src/trio/Concepts.cpp b/dnacalib/DNACalib/src/trio/Concepts.cpp new file mode 100644 index 0000000..3b63075 --- /dev/null +++ b/dnacalib/DNACalib/src/trio/Concepts.cpp @@ -0,0 +1,17 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "trio/Concepts.h" + +namespace trio { + +Readable::~Readable() = default; +Writable::~Writable() = default; +Seekable::~Seekable() = default; +Openable::~Openable() = default; +Closeable::~Closeable() = default; +Controllable::~Controllable() = default; +Bounded::~Bounded() = default; +Buffered::~Buffered() = default; +Resizable::~Resizable() = default; + +} // namespace trio diff --git a/dnacalib/DNACalib/src/trio/Stream.cpp b/dnacalib/DNACalib/src/trio/Stream.cpp new file mode 100644 index 0000000..ebcfdab --- /dev/null +++ b/dnacalib/DNACalib/src/trio/Stream.cpp @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "trio/Stream.h" + +namespace trio { + +const sc::StatusCode BoundedIOStream::OpenError{100, "Error opening file"}; +const sc::StatusCode BoundedIOStream::ReadError{101, "Error reading file"}; +const sc::StatusCode BoundedIOStream::WriteError{102, "Error writing file"}; +const sc::StatusCode BoundedIOStream::AlreadyOpenError{103, "File already open"}; +const sc::StatusCode BoundedIOStream::SeekError{104, "Error seeking file"}; + +BoundedIOStream::~BoundedIOStream() = default; + +} // namespace trio diff --git a/dnacalib/DNACalib/src/trio/streams/FileStreamImpl.cpp b/dnacalib/DNACalib/src/trio/streams/FileStreamImpl.cpp new file mode 100644 index 0000000..fcc9d4a --- /dev/null +++ b/dnacalib/DNACalib/src/trio/streams/FileStreamImpl.cpp @@ -0,0 +1,208 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "trio/streams/FileStreamImpl.h" + +#include "trio/utils/NativeString.h" +#include "trio/utils/ScopedEnumEx.h" + +#include + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace trio { + +namespace { + +constexpr std::size_t bufferSize = 4096ul; + +inline std::uint64_t getFileSizeStd(const NativeCharacter* path) { + std::streamoff fileSize = std::ifstream(path, std::ios_base::ate | std::ios_base::binary).tellg(); + return (fileSize > 0 ? static_cast(fileSize) : 0ul); +} + +inline void ensureFileExistsStd(const NativeCharacter* path) { + std::fstream file; + file.open(path); + if (file.fail()) { + // File does not exist, create it + file.open(path, std::ios_base::out); + } +} + +} // namespace + +FileStream::~FileStream() = default; + +FileStream* FileStream::create(const char* path, AccessMode accessMode, OpenMode openMode, MemoryResource* memRes) { + pma::PolyAllocator alloc{memRes}; + return alloc.newObject(path, accessMode, openMode, memRes); +} + +void FileStream::destroy(FileStream* instance) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + auto stream = static_cast(instance); + pma::PolyAllocator alloc{stream->getMemoryResource()}; + alloc.deleteObject(stream); +} + +FileStreamImpl::FileStreamImpl(const char* path_, AccessMode accessMode_, OpenMode openMode_, MemoryResource* memRes_) : + filePath{NativeStringConverter::from(path_, memRes_)}, + fileAccessMode{accessMode_}, + fileOpenMode{openMode_}, + fileSize{getFileSizeStd(filePath.c_str())}, + memRes{memRes_} { +} + +void FileStreamImpl::open() { + status->reset(); + if (file.is_open()) { + status->set(AlreadyOpenError, filePath.c_str()); + return; + } + + std::ios_base::openmode flags{}; + flags |= (contains(fileAccessMode, AccessMode::Read) ? std::ios_base::in : flags); + flags |= (contains(fileAccessMode, AccessMode::Write) ? std::ios_base::out : flags); + flags |= (contains(fileOpenMode, OpenMode::Binary) ? std::ios_base::binary : flags); + flags |= std::ios_base::ate; + + if (fileAccessMode == AccessMode::ReadWrite) { + ensureFileExistsStd(filePath.c_str()); + } + + file.open(filePath.c_str(), flags); + if (!file.good()) { + status->set(OpenError, filePath.c_str()); + return; + } + fileSize = static_cast(file.tellg()); + seek(0ul); +} + +void FileStreamImpl::close() { + file.close(); +} + +std::uint64_t FileStreamImpl::tell() { + return static_cast(file.tellp()); +} + +void FileStreamImpl::seek(std::uint64_t position) { + const bool seekable = (position == 0ul || position <= size()) && file.good(); + if (!seekable) { + status->set(SeekError, filePath.c_str()); + return; + } + + file.seekp(static_cast(position)); + if (!file.good()) { + status->set(SeekError, filePath.c_str()); + } +} + +std::size_t FileStreamImpl::read(char* destination, std::size_t size) { + if ((destination == nullptr) || file.fail() || !file.is_open() || !contains(fileAccessMode, AccessMode::Read)) { + status->set(ReadError, filePath.c_str()); + return 0ul; + } + + file.read(destination, static_cast(size)); + if (!file.good() && !file.eof()) { + status->set(ReadError, filePath.c_str()); + } + + const auto bytesRead = file.gcount(); + return (bytesRead > 0 ? static_cast(bytesRead) : 0ul); +} + +std::size_t FileStreamImpl::read(Writable* destination, std::size_t size) { + if ((destination == nullptr) || file.fail() || !file.is_open() || !contains(fileAccessMode, AccessMode::Read)) { + status->set(ReadError, filePath.c_str()); + return 0ul; + } + + char buffer[bufferSize]; + while (size > bufferSize) { + file.read(buffer, static_cast(bufferSize)); + destination->write(buffer, bufferSize); + size -= bufferSize; + } + file.read(buffer, static_cast(size)); + destination->write(buffer, size); + + if (!file.good() && !file.eof()) { + status->set(ReadError, filePath.c_str()); + } + const auto bytesRead = file.gcount(); + return (bytesRead > 0 ? static_cast(bytesRead) : 0ul); +} + +std::size_t FileStreamImpl::write(const char* source, std::size_t size) { + if ((source == nullptr) || file.fail() || !file.is_open() || !contains(fileAccessMode, AccessMode::Write)) { + status->set(WriteError, filePath.c_str()); + return 0ul; + } + + const auto preWritePos = file.tellp(); + file.write(source, static_cast(size)); + const auto postWritePos = file.tellp(); + + if (!file.good()) { + status->set(WriteError, filePath.c_str()); + } else { + fileSize = std::max(static_cast(postWritePos), fileSize); + } + + return (postWritePos > preWritePos ? static_cast(postWritePos - preWritePos) : 0ul); +} + +std::size_t FileStreamImpl::write(Readable* source, std::size_t size) { + if ((source == nullptr) || file.fail() || !file.is_open() || !contains(fileAccessMode, AccessMode::Write)) { + status->set(WriteError, filePath.c_str()); + return 0ul; + } + + const auto preWritePos = file.tellp(); + + char buffer[bufferSize]; + while (size > bufferSize) { + source->read(buffer, bufferSize); + file.write(buffer, static_cast(bufferSize)); + size -= bufferSize; + } + source->read(buffer, size); + file.write(buffer, static_cast(size)); + + const auto postWritePos = file.tellp(); + + if (!file.good()) { + status->set(WriteError, filePath.c_str()); + } else { + fileSize = std::max(static_cast(postWritePos), fileSize); + } + + return (postWritePos > preWritePos ? static_cast(postWritePos - preWritePos) : 0ul); +} + +std::uint64_t FileStreamImpl::size() { + return fileSize; +} + +MemoryResource* FileStreamImpl::getMemoryResource() { + return memRes; +} + +} // namespace trio diff --git a/dnacalib/DNACalib/src/trio/streams/FileStreamImpl.h b/dnacalib/DNACalib/src/trio/streams/FileStreamImpl.h new file mode 100644 index 0000000..cebebd5 --- /dev/null +++ b/dnacalib/DNACalib/src/trio/streams/FileStreamImpl.h @@ -0,0 +1,52 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "trio/streams/FileStream.h" +#include "trio/streams/StreamStatus.h" +#include "trio/types/Aliases.h" +#include "trio/utils/NativeString.h" + +#include + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace trio { + +class FileStreamImpl : public FileStream { + public: + FileStreamImpl(const char* path_, AccessMode accessMode_, OpenMode openMode_, MemoryResource* memRes_); + + void open() override; + void close() override; + std::uint64_t tell() override; + void seek(std::uint64_t position) override; + std::uint64_t size() override; + std::size_t read(char* destination, std::size_t size) override; + std::size_t read(Writable* destination, std::size_t size) override; + std::size_t write(const char* source, std::size_t size) override; + std::size_t write(Readable* source, std::size_t size) override; + + MemoryResource* getMemoryResource(); + + private: + std::fstream file; + NativeString filePath; + AccessMode fileAccessMode; + OpenMode fileOpenMode; + std::uint64_t fileSize; + MemoryResource* memRes; + StreamStatus status; +}; + +} // namespace trio diff --git a/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStream.cpp b/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStream.cpp new file mode 100644 index 0000000..5bee719 --- /dev/null +++ b/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStream.cpp @@ -0,0 +1,35 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "trio/streams/MemoryMappedFileStream.h" + +#include "trio/streams/MemoryMappedFileStreamFallback.h" +#include "trio/streams/MemoryMappedFileStreamUnix.h" +#include "trio/streams/MemoryMappedFileStreamWindows.h" + +#include + +namespace trio { + +#if defined(TRIO_WINDOWS_FILE_MAPPING_AVAILABLE) + using MemoryMappedFileStreamImpl = MemoryMappedFileStreamWindows; +#elif defined(TRIO_MMAP_AVAILABLE) + using MemoryMappedFileStreamImpl = MemoryMappedFileStreamUnix; +#else + using MemoryMappedFileStreamImpl = MemoryMappedFileStreamFallback; +#endif + +MemoryMappedFileStream* MemoryMappedFileStream::create(const char* path, AccessMode accessMode, MemoryResource* memRes) { + pma::PolyAllocator alloc{memRes}; + return alloc.newObject(path, accessMode, memRes); +} + +void MemoryMappedFileStream::destroy(MemoryMappedFileStream* instance) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + auto stream = static_cast(instance); + pma::PolyAllocator alloc{stream->getMemoryResource()}; + alloc.deleteObject(stream); +} + +MemoryMappedFileStream::~MemoryMappedFileStream() = default; + +} // namespace trio diff --git a/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamFallback.cpp b/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamFallback.cpp new file mode 100644 index 0000000..89cebb3 --- /dev/null +++ b/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamFallback.cpp @@ -0,0 +1,78 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +// *INDENT-OFF* +#if !defined(TRIO_WINDOWS_FILE_MAPPING_AVAILABLE) && !defined(TRIO_MMAP_AVAILABLE) + +#include "trio/streams/MemoryMappedFileStreamFallback.h" + +#include "trio/utils/ScopedEnumEx.h" + +#include + +#include +#include + +namespace trio { + +MemoryMappedFileStreamFallback::MemoryMappedFileStreamFallback(const char* path_, AccessMode accessMode_, + MemoryResource* memRes_) : + stream{pma::makeScoped(path_, accessMode_, OpenMode::Binary, memRes_)}, + memRes{memRes_} { +} + +MemoryMappedFileStreamFallback::~MemoryMappedFileStreamFallback() { + MemoryMappedFileStreamFallback::close(); +} + +void MemoryMappedFileStreamFallback::open() { + stream->open(); +} + +void MemoryMappedFileStreamFallback::close() { + stream->close(); +} + +std::uint64_t MemoryMappedFileStreamFallback::tell() { + return stream->tell(); +} + +void MemoryMappedFileStreamFallback::seek(std::uint64_t position) { + stream->seek(position); +} + +std::size_t MemoryMappedFileStreamFallback::read(char* destination, std::size_t size) { + return stream->read(destination, size); +} + +std::size_t MemoryMappedFileStreamFallback::read(Writable* destination, std::size_t size) { + return stream->read(destination, size); +} + +std::size_t MemoryMappedFileStreamFallback::write(const char* source, std::size_t size) { + return stream->write(source, size); +} + +std::size_t MemoryMappedFileStreamFallback::write(Readable* source, std::size_t size) { + return stream->write(source, size); +} + +void MemoryMappedFileStreamFallback::flush() { + // Unbuffered, so noop +} + +void MemoryMappedFileStreamFallback::resize(std::uint64_t /*unused*/) { + // No-op, as it's written to disk directly +} + +std::uint64_t MemoryMappedFileStreamFallback::size() { + return stream->size(); +} + +MemoryResource* MemoryMappedFileStreamFallback::getMemoryResource() { + return memRes; +} + +} // namespace trio + +#endif +// *INDENT-OFF* diff --git a/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamFallback.h b/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamFallback.h new file mode 100644 index 0000000..dcae85c --- /dev/null +++ b/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamFallback.h @@ -0,0 +1,54 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +// *INDENT-OFF* +#if !defined(TRIO_WINDOWS_FILE_MAPPING_AVAILABLE) && !defined(TRIO_MMAP_AVAILABLE) + +#include "trio/streams/FileStream.h" +#include "trio/streams/MemoryMappedFileStream.h" +#include "trio/streams/StreamStatus.h" +#include "trio/types/Aliases.h" + +#include +#include + +#include +#include + +namespace trio { + +class MemoryMappedFileStreamFallback : public MemoryMappedFileStream { + public: + MemoryMappedFileStreamFallback(const char* path_, AccessMode accessMode_, MemoryResource* memRes_); + ~MemoryMappedFileStreamFallback(); + + MemoryMappedFileStreamFallback(const MemoryMappedFileStreamFallback&) = delete; + MemoryMappedFileStreamFallback& operator=(const MemoryMappedFileStreamFallback&) = delete; + + MemoryMappedFileStreamFallback(MemoryMappedFileStreamFallback&&) = delete; + MemoryMappedFileStreamFallback& operator=(MemoryMappedFileStreamFallback&&) = delete; + + void open() override; + void close() override; + std::uint64_t tell() override; + void seek(std::uint64_t position) override; + std::uint64_t size() override; + std::size_t read(char* destination, std::size_t size) override; + std::size_t read(Writable* destination, std::size_t size) override; + std::size_t write(const char* source, std::size_t size) override; + std::size_t write(Readable* source, std::size_t size) override; + void flush() override; + void resize(std::uint64_t size) override; + + MemoryResource* getMemoryResource(); + + private: + pma::ScopedPtr stream; + MemoryResource* memRes; +}; + +} // namespace trio + +#endif +// *INDENT-OFF* diff --git a/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamUnix.cpp b/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamUnix.cpp new file mode 100644 index 0000000..61dd385 --- /dev/null +++ b/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamUnix.cpp @@ -0,0 +1,486 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +// *INDENT-OFF* +#ifdef TRIO_MMAP_AVAILABLE + +#ifdef TRIO_LARGE_FILE_SUPPORT_AVAILABLE + #define _FILE_OFFSET_BITS 64 +#endif // TRIO_LARGE_FILE_SUPPORT + +#include "trio/streams/MemoryMappedFileStreamUnix.h" +#include "trio/utils/NativeString.h" +#include "trio/utils/ScopedEnumEx.h" + +#include + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace trio { + +namespace { + +constexpr std::size_t minViewSizeUnix = 65536ul; + +inline std::uint64_t getFileSizeUnix(const NativeCharacter* path) { + struct stat st{}; + if (::stat(path, &st) != 0) { + return 0ul; + } + return static_cast(st.st_size); +} + +inline std::uint64_t getPageSizeUnix() { + #ifdef TRIO_PAGE_SIZE_UNIX + return static_cast(TRIO_PAGE_SIZE_UNIX); + #else + return static_cast(sysconf(_SC_PAGE_SIZE)); + #endif // TRIO_PAGE_SIZE_UNIX +} + +inline std::uint64_t alignOffsetUnix(std::uint64_t offset) { + const std::uint64_t pageSize = getPageSizeUnix(); + return offset / pageSize * pageSize; +} + +class MemoryReaderUnix : public Readable { +public: + explicit MemoryReaderUnix(const char* source_) : source{source_} {} + + std::size_t read(char* destination, std::size_t size) override { + std::memcpy(destination, source, size); + source += size; + return size; + } + + std::size_t read(Writable* destination, std::size_t size) override { + destination->write(source, size); + source += size; + return size; + } + +private: + const char* source; +}; + +class MemoryWriterUnix : public Writable { +public: + explicit MemoryWriterUnix(char* destination_) : destination{destination_} {} + + std::size_t write(const char* source, std::size_t size) override { + std::memcpy(destination, source, size); + destination += size; + return size; + } + + std::size_t write(Readable* source, std::size_t size) override { + source->read(destination, size); + destination += size; + return size; + } + +private: + char* destination; +}; + +} // namespace + +MemoryMappedFileStreamUnix::MemoryMappedFileStreamUnix(const char* path_, AccessMode accessMode_, MemoryResource* memRes_) : + filePath{NativeStringConverter::from(path_, memRes_)}, + fileAccessMode{accessMode_}, + memRes{memRes_}, + file{-1}, + data{nullptr}, + position{}, + fileSize{getFileSizeUnix(filePath.c_str())}, + viewOffset{}, + viewSize{}, + delayedMapping{false}, + dirty{false} { +} + +MemoryMappedFileStreamUnix::~MemoryMappedFileStreamUnix() { + MemoryMappedFileStreamUnix::close(); +} + +MemoryResource* MemoryMappedFileStreamUnix::getMemoryResource() { + return memRes; +} + +std::uint64_t MemoryMappedFileStreamUnix::size() { + return fileSize; +} + +void MemoryMappedFileStreamUnix::open() { + status->reset(); + if (file != -1) { + status->set(AlreadyOpenError, filePath.c_str()); + return; + } + + delayedMapping = false; + + openFile(); + if (file == -1) { + status->set(OpenError, filePath.c_str()); + return; + } + + struct stat st{}; + if (::fstat(file, &st) != 0) { + fileSize = 0ul; + closeFile(); + status->set(OpenError, filePath.c_str()); + return; + } + + fileSize = static_cast(st.st_size); + // Mapping of 0-length files is delayed until the file is resized to a non-zero size. + delayedMapping = (fileSize == 0ul); + if (delayedMapping) { + return; + } + + mapFile(0ul, fileSize); + if (data == reinterpret_cast(-1)) { + status->set(OpenError, filePath.c_str()); + delayedMapping = false; + unmapFile(); + closeFile(); + return; + } + + MemoryMappedFileStreamUnix::seek(0ul); + dirty = false; +} + +void MemoryMappedFileStreamUnix::close() { + delayedMapping = false; + + flush(); + unmapFile(); + closeFile(); +} + +std::uint64_t MemoryMappedFileStreamUnix::tell() { + return position; +} + +void MemoryMappedFileStreamUnix::seek(std::uint64_t position_) { + const bool seekable = ((position_ == 0ul) || (position_ <= size())) && (data != nullptr); + if (!seekable) { + status->set(SeekError, filePath.c_str()); + return; + } + + position = position_; + if ((position < viewOffset) || (position >= (viewOffset + viewSize))) { + flush(); + if (dirty) { + return; + } + unmapFile(); + mapFile(position, fileSize - position); + } +} + +std::size_t MemoryMappedFileStreamUnix::read(char* destination, std::size_t size) { + if (destination == nullptr) { + status->set(ReadError, filePath.c_str()); + return 0ul; + } + + MemoryWriterUnix writer{destination}; + return read(&writer, size); +} + +std::size_t MemoryMappedFileStreamUnix::read(Writable* destination, std::size_t size) { + if ((destination == nullptr) || !contains(fileAccessMode, AccessMode::Read)) { + status->set(ReadError, filePath.c_str()); + return 0ul; + } + + if (data == nullptr) { + if (!delayedMapping) { + status->set(ReadError, filePath.c_str()); + } + return 0ul; + } + + const std::uint64_t bytesAvailable = fileSize - position; + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wuseless-cast" + #endif + const std::size_t bytesToRead = static_cast(std::min(static_cast(size), bytesAvailable)); + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + std::size_t bytesRead = 0ul; + + while (bytesRead != bytesToRead) { + const std::size_t bytesRemaining = bytesToRead - bytesRead; + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wuseless-cast" + #endif + std::size_t viewPosition = static_cast(position - viewOffset); + std::size_t bytesReadable = static_cast(viewSize - viewPosition); + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + // If the view is exhausted during reading, remap a new view till the end of file if possible, + // starting at the current position + if (bytesReadable == 0ul) { + unmapFile(); + mapFile(position, fileSize - position); + if (data == nullptr) { + // Failed to map new view + status->set(ReadError, filePath.c_str()); + break; + } + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wuseless-cast" + #endif + viewPosition = static_cast(position - viewOffset); + bytesReadable = static_cast(viewSize - viewPosition); + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + } + + const std::size_t chunkSize = std::min(bytesRemaining, bytesReadable); + const std::size_t chunkCopied = destination->write(static_cast(data) + viewPosition, chunkSize); + bytesRead += chunkCopied; + position += chunkCopied; + } + + return bytesRead; +} + +std::size_t MemoryMappedFileStreamUnix::write(const char* source, std::size_t size) { + if (source == nullptr) { + status->set(WriteError, filePath.c_str()); + return 0ul; + } + + MemoryReaderUnix reader{source}; + return write(&reader, size); +} + +std::size_t MemoryMappedFileStreamUnix::write(Readable* source, std::size_t size) { + if ((source == nullptr) || !contains(fileAccessMode, AccessMode::Write)) { + status->set(WriteError, filePath.c_str()); + return 0ul; + } + + if ((data == nullptr) && !delayedMapping) { + status->set(WriteError, filePath.c_str()); + return 0ul; + } + + if (size == 0ul) { + return 0ul; + } + + if (position + size > fileSize) { + resize(position + size); + if (fileSize != (position + size)) { + // Resize not successful (resize sets status in such cases) + return 0ul; + } + } + + std::size_t bytesWritten = 0ul; + + while (bytesWritten != size) { + const std::size_t bytesRemaining = size - bytesWritten; + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wuseless-cast" + #endif + std::size_t viewPosition = static_cast(position - viewOffset); + std::size_t bytesWritable = static_cast(viewSize - viewPosition); + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + // If the view is exhausted during writing, remap a new view till the end of file if possible, + // starting at the current position + if (bytesWritable == 0ul) { + flush(); + if (dirty) { + break; + } + unmapFile(); + mapFile(position, fileSize - position); + if (data == nullptr) { + // Failed to map new view + status->set(WriteError, filePath.c_str()); + break; + } + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wuseless-cast" + #endif + viewPosition = static_cast(position - viewOffset); + bytesWritable = static_cast(viewSize - viewPosition); + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + } + + const std::size_t chunkSize = std::min(bytesRemaining, bytesWritable); + const std::size_t chunkCopied = source->read(static_cast(data) + viewPosition, chunkSize); + bytesWritten += chunkCopied; + position += chunkCopied; + } + + dirty = (bytesWritten > 0ul); + + return bytesWritten; +} + +void MemoryMappedFileStreamUnix::flush() { + if (data != nullptr) { + if (::msync(data, viewSize, MS_SYNC) != 0) { + status->set(WriteError, filePath.c_str()); + return; + } + } + dirty = false; +} + +void MemoryMappedFileStreamUnix::resize(std::uint64_t size) { + if (fileAccessMode == AccessMode::Read) { + status->set(WriteError, filePath.c_str()); + return; + } + + flush(); + if (dirty) { + return; + } + + unmapFile(); + resizeFile(size); + if (fileSize != size) { + status->set(WriteError, filePath.c_str()); + return; + } + + // Either mremap is not available, or there was no data pointer to be remapped in the + // first place. In both cases, fallback to mmap + mapFile(position, fileSize - position); + if (data == nullptr) { + status->set(WriteError, filePath.c_str()); + return; + } +} + +void MemoryMappedFileStreamUnix::openFile() { + int openFlags{}; + if (fileAccessMode == AccessMode::ReadWrite) { + openFlags = O_RDWR | O_CREAT; + } else if (fileAccessMode == AccessMode::Read) { + openFlags = O_RDONLY; + } else if (fileAccessMode == AccessMode::Write) { + // mmap needs also read permission to the underlying file descriptor + openFlags = O_RDWR | O_CREAT; + } + + const int mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + file = ::open(filePath.c_str(), openFlags, mode); +} + +void MemoryMappedFileStreamUnix::closeFile() { + if (file != -1) { + ::close(file); + file = -1; + } +} + +void MemoryMappedFileStreamUnix::mapFile(std::uint64_t offset, std::uint64_t size) { + int prot{}; + prot |= (contains(fileAccessMode, AccessMode::Write) ? PROT_WRITE : prot); + prot |= (contains(fileAccessMode, AccessMode::Read) ? PROT_READ : prot); + + const int flags = (fileAccessMode == AccessMode::Read ? MAP_PRIVATE : MAP_SHARED); + + const std::uint64_t alignedOffset = alignOffsetUnix(offset); + + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wuseless-cast" + #endif + // Make sure size does not exceed system limits + std::size_t safeSize = static_cast(std::min(static_cast(std::numeric_limits::max()), size)); + // Increase to-be-mapped size by the difference caused by alignment (guard against wrap-around on overflow) + safeSize = std::max(static_cast(safeSize + (offset - alignedOffset)), safeSize); + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + + // Try mapping requested size, but if it fails keep repeating by halving the view size each time (e.g. if not enough VA space) + std::size_t nextSize = safeSize; + do { + safeSize = nextSize; + data = ::mmap(nullptr, safeSize, prot, flags, file, static_cast(alignedOffset)); + if (data != reinterpret_cast(-1)) { + break; + } + nextSize = safeSize / 2ul; + } + while (nextSize > minViewSizeUnix); + + if (data != reinterpret_cast(-1)) { + viewOffset = alignedOffset; + viewSize = safeSize; + } +} + +void MemoryMappedFileStreamUnix::unmapFile() { + if (data != nullptr) { + ::munmap(data, viewSize); + data = nullptr; + } + + viewOffset = 0ul; + viewSize = 0ul; +} + +void MemoryMappedFileStreamUnix::resizeFile(std::uint64_t size) { + if (file == -1) { + return; + } + + if (::ftruncate(file, static_cast(size)) != 0) { + return; + } + + fileSize = size; +} + +} // namespace trio + +#endif // TRIO_MMAP_AVAILABLE +// *INDENT-ON* diff --git a/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamUnix.h b/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamUnix.h new file mode 100644 index 0000000..5abc091 --- /dev/null +++ b/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamUnix.h @@ -0,0 +1,69 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +// *INDENT-OFF* +#ifdef TRIO_MMAP_AVAILABLE + +#include "trio/streams/MemoryMappedFileStream.h" +#include "trio/streams/StreamStatus.h" +#include "trio/types/Aliases.h" +#include "trio/utils/NativeString.h" + +#include + +#include + +namespace trio { + +class MemoryMappedFileStreamUnix : public MemoryMappedFileStream { + public: + MemoryMappedFileStreamUnix(const char* path_, AccessMode accessMode_, MemoryResource* memRes_); + ~MemoryMappedFileStreamUnix(); + + MemoryMappedFileStreamUnix(const MemoryMappedFileStreamUnix&) = delete; + MemoryMappedFileStreamUnix& operator=(const MemoryMappedFileStreamUnix&) = delete; + + MemoryMappedFileStreamUnix(MemoryMappedFileStreamUnix&&) = delete; + MemoryMappedFileStreamUnix& operator=(MemoryMappedFileStreamUnix&&) = delete; + + void open() override; + void close() override; + std::uint64_t tell() override; + void seek(std::uint64_t position) override; + std::uint64_t size() override; + std::size_t read(char* destination, std::size_t size) override; + std::size_t read(Writable* destination, std::size_t size) override; + std::size_t write(const char* source, std::size_t size) override; + std::size_t write(Readable* source, std::size_t size) override; + void flush() override; + void resize(std::uint64_t size) override; + + MemoryResource* getMemoryResource(); + + private: + void openFile(); + void closeFile(); + void mapFile(std::uint64_t offset, std::uint64_t size); + void unmapFile(); + void resizeFile(std::uint64_t size); + + private: + StreamStatus status; + NativeString filePath; + AccessMode fileAccessMode; + MemoryResource* memRes; + int file; + void* data; + std::uint64_t position; + std::uint64_t fileSize; + std::uint64_t viewOffset; + std::size_t viewSize; + bool delayedMapping; + bool dirty; +}; + +} // namespace trio + +#endif // TRIO_MMAP_AVAILABLE +// *INDENT-ON* diff --git a/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamWindows.cpp b/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamWindows.cpp new file mode 100644 index 0000000..01f7695 --- /dev/null +++ b/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamWindows.cpp @@ -0,0 +1,465 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +// *INDENT-OFF* +#ifdef TRIO_WINDOWS_FILE_MAPPING_AVAILABLE + +#include "trio/streams/MemoryMappedFileStreamWindows.h" + +#include "trio/utils/NativeString.h" +#include "trio/utils/ScopedEnumEx.h" + +#include + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace trio { + +namespace { + +constexpr std::size_t minViewSizeWindows = 65536ul; + +inline std::uint64_t getFileSizeWindows(const NativeCharacter* path) { + WIN32_FILE_ATTRIBUTE_DATA w32fad; + if (GetFileAttributesEx(path, GetFileExInfoStandard, &w32fad) == 0) { + return 0ul; + } + ULARGE_INTEGER size; + size.HighPart = w32fad.nFileSizeHigh; + size.LowPart = w32fad.nFileSizeLow; + return static_cast(size.QuadPart); +} + +inline std::uint64_t getPageSizeWindows() { + SYSTEM_INFO SystemInfo; + GetSystemInfo(&SystemInfo); + return SystemInfo.dwAllocationGranularity; +} + +inline std::uint64_t alignOffsetWindows(std::uint64_t offset) { + const std::uint64_t pageSize = getPageSizeWindows(); + return offset / pageSize * pageSize; +} + +class MemoryReaderWindows : public Readable { +public: + explicit MemoryReaderWindows(const char* source_) : source{source_} {} + + std::size_t read(char* destination, std::size_t size) override { + CopyMemory(destination, source, size); + source += size; + return size; + } + + std::size_t read(Writable* destination, std::size_t size) override { + destination->write(source, size); + source += size; + return size; + } + +private: + const char* source; +}; + +class MemoryWriterWindows : public Writable { +public: + explicit MemoryWriterWindows(char* destination_) : destination{destination_} {} + + std::size_t write(const char* source, std::size_t size) override { + CopyMemory(destination, source, size); + destination += size; + return size; + } + + std::size_t write(Readable* source, std::size_t size) override { + source->read(destination, size); + destination += size; + return size; + } + +private: + char* destination; +}; + +} // namespace + +MemoryMappedFileStreamWindows::MemoryMappedFileStreamWindows(const char* path_, AccessMode accessMode_, MemoryResource* memRes_) : + filePath{NativeStringConverter::from(path_, memRes_)}, + fileAccessMode{accessMode_}, + memRes{memRes_}, + file{INVALID_HANDLE_VALUE}, + mapping{nullptr}, + data{nullptr}, + position{}, + fileSize{getFileSizeWindows(filePath.c_str())}, + viewOffset{}, + viewSize{}, + delayedMapping{false}, + dirty{false} { +} + +MemoryMappedFileStreamWindows::~MemoryMappedFileStreamWindows() { + delayedMapping = false; + flush(); + unmapFile(); + closeFile(); +} + +MemoryResource* MemoryMappedFileStreamWindows::getMemoryResource() { + return memRes; +} + +std::uint64_t MemoryMappedFileStreamWindows::size() { + return fileSize; +} + +void MemoryMappedFileStreamWindows::open() { + status->reset(); + if (file != INVALID_HANDLE_VALUE) { + status->set(AlreadyOpenError, filePath.c_str()); + return; + } + + delayedMapping = false; + + openFile(); + if (file == INVALID_HANDLE_VALUE) { + status->set(OpenError, filePath.c_str()); + return; + } + + // Retrieve file size + LARGE_INTEGER size{}; + if (GetFileSizeEx(file, &size) == 0) { + fileSize = 0ul; + closeFile(); + status->set(OpenError, filePath.c_str()); + return; + } + + fileSize = static_cast(size.QuadPart); + // Mapping of 0-length files is delayed until the file is resized to a non-zero size. + delayedMapping = (fileSize == 0ul); + if (delayedMapping) { + return; + } + + // Create file mapping + mapFile(0ul, fileSize); + if (data == nullptr) { + status->set(OpenError, filePath.c_str()); + delayedMapping = false; + unmapFile(); + closeFile(); + return; + } + + seek(0ul); + dirty = false; +} + +void MemoryMappedFileStreamWindows::close() { + delayedMapping = false; + + flush(); + unmapFile(); + closeFile(); +} + +std::uint64_t MemoryMappedFileStreamWindows::tell() { + return position; +} + +void MemoryMappedFileStreamWindows::seek(std::uint64_t position_) { + const bool seekable = ((position_ == 0ul) || (position_ <= size())) && (data != nullptr); + if (!seekable) { + status->set(SeekError, filePath.c_str()); + return; + } + + position = position_; + if ((position < viewOffset) || (position >= (viewOffset + viewSize))) { + flush(); + if (dirty) { + return; + } + unmapFile(); + mapFile(position, fileSize - position); + } +} + +std::size_t MemoryMappedFileStreamWindows::read(char* destination, std::size_t size) { + if (destination == nullptr) { + status->set(ReadError, filePath.c_str()); + return 0ul; + } + + MemoryWriterWindows writer{destination}; + return read(&writer, size); +} + +std::size_t MemoryMappedFileStreamWindows::read(Writable* destination, std::size_t size) { + if ((destination == nullptr) || !contains(fileAccessMode, AccessMode::Read)) { + status->set(ReadError, filePath.c_str()); + return 0ul; + } + + if (data == nullptr) { + if (!delayedMapping) { + status->set(ReadError, filePath.c_str()); + } + return 0ul; + } + + const std::uint64_t bytesAvailable = fileSize - position; + const std::size_t bytesToRead = static_cast(std::min(static_cast(size), bytesAvailable)); + std::size_t bytesRead = 0ul; + + while (bytesRead != bytesToRead) { + const std::size_t bytesRemaining = bytesToRead - bytesRead; + std::size_t viewPosition = static_cast(position - viewOffset); + std::size_t bytesReadable = static_cast(viewSize - viewPosition); + // If the view is exhausted during reading, remap a new view till the end of file if possible, + // starting at the current position + if (bytesReadable == 0ul) { + unmapFile(); + mapFile(position, fileSize - position); + if (data == nullptr) { + // Failed to map new view + status->set(ReadError, filePath.c_str()); + break; + } + viewPosition = static_cast(position - viewOffset); + bytesReadable = static_cast(viewSize - viewPosition); + } + + const std::size_t chunkSize = std::min(bytesRemaining, bytesReadable); + const std::size_t chunkCopied = destination->write(static_cast(data) + viewPosition, chunkSize); + bytesRead += chunkCopied; + position += chunkCopied; + } + + return bytesRead; +} + +std::size_t MemoryMappedFileStreamWindows::write(const char* source, std::size_t size) { + if (source == nullptr) { + status->set(WriteError, filePath.c_str()); + return 0ul; + } + + MemoryReaderWindows reader{source}; + return write(&reader, size); +} + +std::size_t MemoryMappedFileStreamWindows::write(Readable* source, std::size_t size) { + if ((source == nullptr) || !contains(fileAccessMode, AccessMode::Write)) { + status->set(WriteError, filePath.c_str()); + return 0ul; + } + + if ((data == nullptr) && !delayedMapping) { + status->set(WriteError, filePath.c_str()); + return 0ul; + } + + if (size == 0ul) { + return 0ul; + } + + if (position + size > fileSize) { + resize(position + size); + if (fileSize != (position + size)) { + // Resize not successful (resize sets status in such cases) + return 0ul; + } + } + + std::size_t bytesWritten = 0ul; + + while (bytesWritten != size) { + const std::size_t bytesRemaining = size - bytesWritten; + std::size_t viewPosition = static_cast(position - viewOffset); + std::size_t bytesWritable = static_cast(viewSize - viewPosition); + // If the view is exhausted during writing, remap a new view till the end of file if possible, + // starting at the current position + if (bytesWritable == 0ul) { + flush(); + if (dirty) { + break; + } + unmapFile(); + mapFile(position, fileSize - position); + if (data == nullptr) { + // Failed to map new view + status->set(WriteError, filePath.c_str()); + break; + } + viewPosition = static_cast(position - viewOffset); + bytesWritable = static_cast(viewSize - viewPosition); + } + + const std::size_t chunkSize = std::min(bytesRemaining, bytesWritable); + const std::size_t chunkCopied = source->read(static_cast(data) + viewPosition, chunkSize); + bytesWritten += chunkCopied; + position += chunkCopied; + } + + dirty = (bytesWritten > 0ul); + + return bytesWritten; +} + +void MemoryMappedFileStreamWindows::flush() { + if ((data != nullptr) && (fileAccessMode != AccessMode::Read)) { + if (!FlushViewOfFile(data, 0ul)) { + status->set(WriteError, filePath.c_str()); + return; + } + } + dirty = false; +} + +void MemoryMappedFileStreamWindows::resize(std::uint64_t size) { + if (fileAccessMode == AccessMode::Read) { + status->set(WriteError, filePath.c_str()); + return; + } + + flush(); + if (dirty) { + return; + } + + unmapFile(); + + resizeFile(size); + if (fileSize != size) { + status->set(WriteError, filePath.c_str()); + return; + } + + // Remap file from current position, till the end of file + mapFile(position, fileSize - position); + if (data == nullptr) { + status->set(WriteError, filePath.c_str()); + return; + } +} + +void MemoryMappedFileStreamWindows::openFile() { + DWORD access{GENERIC_READ}; + access |= (contains(fileAccessMode, AccessMode::Write) ? GENERIC_WRITE : access); + + // 0 == no sharing in any way + DWORD sharing{}; + + // Non-existing files are created unless in read-only mode + DWORD creationDisposition{}; + if (fileAccessMode == AccessMode::Read) { + creationDisposition = static_cast(OPEN_EXISTING); + } else { + creationDisposition = static_cast(OPEN_ALWAYS); + } + + file = CreateFile(filePath.c_str(), access, sharing, nullptr, creationDisposition, FILE_ATTRIBUTE_NORMAL, nullptr); +} + +void MemoryMappedFileStreamWindows::closeFile() { + if (file != INVALID_HANDLE_VALUE) { + CloseHandle(file); + file = INVALID_HANDLE_VALUE; + } +} + +void MemoryMappedFileStreamWindows::mapFile(std::uint64_t offset, std::uint64_t size) { + // Create file mapping + const auto protect = static_cast(contains(fileAccessMode, AccessMode::Write) ? PAGE_READWRITE : PAGE_READONLY); + mapping = CreateFileMapping(file, nullptr, protect, 0u, 0u, nullptr); + if (mapping == nullptr) { + return; + } + + // Map a view of the file mapping into the address space + DWORD desiredAccess{}; + desiredAccess |= (contains(fileAccessMode, AccessMode::Write) ? FILE_MAP_WRITE : desiredAccess); + desiredAccess |= (contains(fileAccessMode, AccessMode::Read) ? FILE_MAP_READ : desiredAccess); + + ULARGE_INTEGER alignedOffset{}; + alignedOffset.QuadPart = static_cast(alignOffsetWindows(offset)); + + // Make sure size does not exceed system limits + std::size_t safeSize = static_cast(std::min(static_cast(std::numeric_limits::max()), size)); + // Increase to-be-mapped size by the difference caused by alignment + safeSize = std::max(static_cast(safeSize + (offset - alignedOffset.QuadPart)), safeSize); + + // Try mapping requested size, but if it fails keep repeating by halving the view size each time (e.g. if not enough VA space) + std::size_t nextSize = safeSize; + do { + safeSize = nextSize; + data = MapViewOfFile(mapping, desiredAccess, alignedOffset.HighPart, alignedOffset.LowPart, safeSize); + if (data != nullptr) { + break; + } + nextSize = safeSize / 2ul; + } + while (nextSize > minViewSizeWindows); + + if (data != nullptr) { + viewOffset = alignedOffset.QuadPart; + viewSize = safeSize; + } +} + +void MemoryMappedFileStreamWindows::unmapFile() { + if (data != nullptr) { + UnmapViewOfFile(data); + data = nullptr; + } + + if (mapping != nullptr) { + CloseHandle(mapping); + mapping = nullptr; + } + + viewOffset = 0ul; + viewSize = 0ul; +} + +void MemoryMappedFileStreamWindows::resizeFile(std::uint64_t size) { + if (file == INVALID_HANDLE_VALUE) { + return; + } + + // Seek to the new size + LARGE_INTEGER moveBy{}; + moveBy.QuadPart = static_cast(size); + if (SetFilePointerEx(file, moveBy, nullptr, FILE_BEGIN) == 0) { + return; + } + + // Resize the file to it's current position + if (SetEndOfFile(file) == 0) { + return; + } + + fileSize = size; +} + +} // namespace trio + +#endif // TRIO_WINDOWS_FILE_MAPPING_AVAILABLE +// *INDENT-ON* diff --git a/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamWindows.h b/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamWindows.h new file mode 100644 index 0000000..f164175 --- /dev/null +++ b/dnacalib/DNACalib/src/trio/streams/MemoryMappedFileStreamWindows.h @@ -0,0 +1,72 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +// *INDENT-OFF* +#ifdef TRIO_WINDOWS_FILE_MAPPING_AVAILABLE + +#include "trio/streams/MemoryMappedFileStream.h" +#include "trio/streams/StreamStatus.h" +#include "trio/types/Aliases.h" +#include "trio/utils/NativeString.h" +#include "trio/utils/PlatformWindows.h" + +#include + +#include +#include + +namespace trio { + +class MemoryMappedFileStreamWindows : public MemoryMappedFileStream { + public: + MemoryMappedFileStreamWindows(const char* path_, AccessMode accessMode_, MemoryResource* memRes_); + ~MemoryMappedFileStreamWindows(); + + MemoryMappedFileStreamWindows(const MemoryMappedFileStreamWindows&) = delete; + MemoryMappedFileStreamWindows& operator=(const MemoryMappedFileStreamWindows&) = delete; + + MemoryMappedFileStreamWindows(MemoryMappedFileStreamWindows&&) = delete; + MemoryMappedFileStreamWindows& operator=(MemoryMappedFileStreamWindows&&) = delete; + + void open() override; + void close() override; + std::uint64_t tell() override; + void seek(std::uint64_t position) override; + std::uint64_t size() override; + std::size_t read(char* destination, std::size_t size) override; + std::size_t read(Writable* destination, std::size_t size) override; + std::size_t write(const char* source, std::size_t size) override; + std::size_t write(Readable* source, std::size_t size) override; + void flush() override; + void resize(std::uint64_t size) override; + + MemoryResource* getMemoryResource(); + + private: + void openFile(); + void closeFile(); + void mapFile(std::uint64_t offset, std::uint64_t size); + void unmapFile(); + void resizeFile(std::uint64_t size); + + private: + StreamStatus status; + NativeString filePath; + AccessMode fileAccessMode; + MemoryResource* memRes; + HANDLE file; + HANDLE mapping; + LPVOID data; + std::uint64_t position; + std::uint64_t fileSize; + std::uint64_t viewOffset; + std::size_t viewSize; + bool delayedMapping; + bool dirty; +}; + +} // namespace trio + +#endif // TRIO_WINDOWS_FILE_MAPPING_AVAILABLE +// *INDENT-ON* diff --git a/dnacalib/DNACalib/src/trio/streams/MemoryStreamImpl.cpp b/dnacalib/DNACalib/src/trio/streams/MemoryStreamImpl.cpp new file mode 100644 index 0000000..d942578 --- /dev/null +++ b/dnacalib/DNACalib/src/trio/streams/MemoryStreamImpl.cpp @@ -0,0 +1,179 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "trio/streams/MemoryStreamImpl.h" + +#include + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace trio { + +namespace { + + +class MemoryReader : public Readable { + public: + explicit MemoryReader(const char* source_) : source{source_} { + } + + std::size_t read(char* destination, std::size_t size) override { + std::memcpy(destination, source, size); + source += size; + return size; + } + + std::size_t read(Writable* destination, std::size_t size) override { + destination->write(source, size); + source += size; + return size; + } + + private: + const char* source; +}; + +class MemoryWriter : public Writable { + public: + explicit MemoryWriter(char* destination_) : destination{destination_} { + } + + std::size_t write(const char* source, std::size_t size) override { + std::memcpy(destination, source, size); + destination += size; + return size; + } + + std::size_t write(Readable* source, std::size_t size) override { + source->read(destination, size); + destination += size; + return size; + } + + private: + char* destination; +}; + +} // namespace + +MemoryStream::~MemoryStream() = default; + +MemoryStream* MemoryStream::create(MemoryResource* memRes) { + return create(0ul, memRes); +} + +MemoryStream* MemoryStream::create(std::size_t initialSize, MemoryResource* memRes) { + pma::PolyAllocator alloc{memRes}; + return alloc.newObject(initialSize, memRes); +} + +void MemoryStream::destroy(MemoryStream* instance) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + auto stream = static_cast(instance); + pma::PolyAllocator alloc{stream->getMemoryResource()}; + alloc.deleteObject(stream); +} + +MemoryStreamImpl::MemoryStreamImpl(std::size_t initialSize, MemoryResource* memRes_) : + data{initialSize, static_cast(0), memRes_}, + position{}, + memRes{memRes_} { +} + +void MemoryStreamImpl::open() { + position = 0ul; +} + +void MemoryStreamImpl::close() { + position = 0ul; +} + +std::uint64_t MemoryStreamImpl::tell() { + return position; +} + +void MemoryStreamImpl::seek(std::uint64_t position_) { + if ((position_ == 0ul) || (position_ <= size())) { + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wuseless-cast" + #endif + position = static_cast(position_); + #if !defined(__clang__) && defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + } else { + status->set(SeekError); + } +} + +std::size_t MemoryStreamImpl::read(char* destination, std::size_t size) { + if (destination == nullptr) { + status->set(ReadError); + return 0ul; + } + + MemoryWriter writer{destination}; + return read(&writer, size); +} + +std::size_t MemoryStreamImpl::read(Writable* destination, std::size_t size) { + if (destination == nullptr) { + status->set(ReadError); + return 0ul; + } + + const std::size_t available = data.size() - position; + const std::size_t bytesToRead = std::min(size, available); + const std::size_t bytesCopied = (bytesToRead > 0ul ? destination->write(&data[position], bytesToRead) : 0ul); + position += bytesCopied; + return bytesCopied; +} + +std::size_t MemoryStreamImpl::write(const char* source, std::size_t size) { + if (source == nullptr) { + status->set(WriteError); + return 0ul; + } + + MemoryReader reader{source}; + return write(&reader, size); +} + +std::size_t MemoryStreamImpl::write(Readable* source, std::size_t size) { + if (source == nullptr) { + status->set(WriteError); + return 0ul; + } + const std::size_t available = data.size() - position; + if (available < size) { + const std::size_t newSize = data.size() + (size - available); + // Check for overflow / wrap-around + if (newSize < data.size()) { + status->set(WriteError); + return 0ul; + } + data.resize(newSize); + } + const std::size_t bytesCopied = source->read(&data[position], size); + position += bytesCopied; + return bytesCopied; +} + +std::uint64_t MemoryStreamImpl::size() { + return data.size(); +} + +MemoryResource* MemoryStreamImpl::getMemoryResource() { + return memRes; +} + +} // namespace trio diff --git a/dnacalib/DNACalib/src/trio/streams/MemoryStreamImpl.h b/dnacalib/DNACalib/src/trio/streams/MemoryStreamImpl.h new file mode 100644 index 0000000..6653cbf --- /dev/null +++ b/dnacalib/DNACalib/src/trio/streams/MemoryStreamImpl.h @@ -0,0 +1,47 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "trio/streams/MemoryStream.h" +#include "trio/streams/StreamStatus.h" +#include "trio/types/Aliases.h" + +#include + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace trio { + +class MemoryStreamImpl : public MemoryStream { + public: + MemoryStreamImpl(std::size_t initialSize, MemoryResource* memRes_); + + void open() override; + void close() override; + std::uint64_t tell() override; + void seek(std::uint64_t position_) override; + std::uint64_t size() override; + std::size_t read(char* destination, std::size_t size) override; + std::size_t read(Writable* destination, std::size_t size) override; + std::size_t write(const char* source, std::size_t size) override; + std::size_t write(Readable* source, std::size_t size) override; + + MemoryResource* getMemoryResource(); + + private: + StreamStatus status; + Vector data; + std::size_t position; + MemoryResource* memRes; +}; + +} // namespace trio diff --git a/dnacalib/DNACalib/src/trio/streams/StreamStatus.cpp b/dnacalib/DNACalib/src/trio/streams/StreamStatus.cpp new file mode 100644 index 0000000..f4cb549 --- /dev/null +++ b/dnacalib/DNACalib/src/trio/streams/StreamStatus.cpp @@ -0,0 +1,22 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "trio/streams/StreamStatus.h" + +#include "trio/Stream.h" + +namespace trio { + +#ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wglobal-constructors" +#endif +sc::StatusProvider StreamStatus::status{BoundedIOStream::OpenError, + BoundedIOStream::ReadError, + BoundedIOStream::WriteError, + BoundedIOStream::AlreadyOpenError, + BoundedIOStream::SeekError}; +#ifdef __clang__ + #pragma clang diagnostic pop +#endif + +} // namespace trio diff --git a/dnacalib/DNACalib/src/trio/streams/StreamStatus.h b/dnacalib/DNACalib/src/trio/streams/StreamStatus.h new file mode 100644 index 0000000..464b9ad --- /dev/null +++ b/dnacalib/DNACalib/src/trio/streams/StreamStatus.h @@ -0,0 +1,22 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "trio/types/Aliases.h" + +#include + +namespace trio { + +class StreamStatus { + public: + sc::StatusProvider* operator->() { + return &status; + } + + private: + static sc::StatusProvider status; + +}; + +} // namespace trio diff --git a/dnacalib/DNACalib/src/trio/utils/NativeString.h b/dnacalib/DNACalib/src/trio/utils/NativeString.h new file mode 100644 index 0000000..17bb7fa --- /dev/null +++ b/dnacalib/DNACalib/src/trio/utils/NativeString.h @@ -0,0 +1,79 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#ifdef _WIN32 + #include "trio/utils/PlatformWindows.h" +#endif + +#include + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4365 4987) +#endif +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +namespace trio { + +template +struct StringConverter; + +template<> +struct StringConverter { + using CharacterType = char; + using StringType = pma::String; + + static StringType from(const char* source, pma::MemoryResource* memRes) { + return StringType{source, memRes}; + } + +}; + +template<> +struct StringConverter { + using CharacterType = wchar_t; + using StringType = pma::String; + + static StringType from(const char* source, pma::MemoryResource* memRes) { + #ifdef _WIN32 + const std::size_t length = std::strlen(source); + const int neededSize = MultiByteToWideChar(CP_UTF8, 0, source, static_cast(length), nullptr, 0); + StringType result{static_cast(neededSize), 0, memRes}; + MultiByteToWideChar(CP_UTF8, 0, source, static_cast(length), &result[0], neededSize); + return result; + #else + const char* current = std::setlocale(LC_ALL, nullptr); + std::setlocale(LC_ALL, "en_US.utf8"); // Any UTF-8 locale will work + + std::mbstate_t state{}; + const std::size_t neededSize = std::mbsrtowcs(nullptr, &source, 0, &state) + 1ul; + StringType result{neededSize, 0, memRes}; + std::mbsrtowcs(&result[0], &source, result.size(), &state); + + if (current != nullptr) { + std::setlocale(LC_ALL, current); + } + + return result; + #endif + } + +}; + +#if defined(_WIN32) && defined(UNICODE) + using NativeCharacter = wchar_t; +#else + using NativeCharacter = char; +#endif + +using NativeString = pma::String; +using NativeStringConverter = StringConverter; + +} // namespace trio diff --git a/dnacalib/DNACalib/src/trio/utils/PlatformWindows.h b/dnacalib/DNACalib/src/trio/utils/PlatformWindows.h new file mode 100644 index 0000000..814d3e5 --- /dev/null +++ b/dnacalib/DNACalib/src/trio/utils/PlatformWindows.h @@ -0,0 +1,55 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4005 4365 4987) +#endif + +#ifdef TRIO_CUSTOM_WINDOWS_H + #include TRIO_CUSTOM_WINDOWS_H +#else + #define WIN32_LEAN_AND_MEAN + #define NOGDICAPMASKS + #define NOVIRTUALKEYCODES + #define NOWINMESSAGES + #define NOWINSTYLES + #define NOSYSMETRICS + #define NOMENUS + #define NOICONS + #define NOKEYSTATES + #define NOSYSCOMMANDS + #define NORASTEROPS + #define NOSHOWWINDOW + #define NOATOM + #define NOCLIPBOARD + #define NOCOLOR + #define NOCTLMGR + #define NODRAWTEXT + #define NOGDI + #define NOKERNEL + #define NOUSER + #define NOMB + #define NOMEMMGR + #define NOMETAFILE + #define NOMINMAX + #define NOMSG + #define NOOPENFILE + #define NOSCROLL + #define NOSERVICE + #define NOSOUND + #define NOTEXTMETRIC + #define NOWH + #define NOWINOFFSETS + #define NOCOMM + #define NOKANJI + #define NOHELP + #define NOPROFILER + #define NODEFERWINDOWPOS + #define NOMCX + #include +#endif // TRIO_CUSTOM_WINDOWS_H +#ifdef _MSC_VER + #pragma warning(pop) +#endif diff --git a/dnacalib/DNACalib/src/trio/utils/ScopedEnumEx.h b/dnacalib/DNACalib/src/trio/utils/ScopedEnumEx.h new file mode 100644 index 0000000..456e023 --- /dev/null +++ b/dnacalib/DNACalib/src/trio/utils/ScopedEnumEx.h @@ -0,0 +1,61 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include + +namespace trio { + +template +typename std::enable_if::value, TEnum>::type +operator&(TEnum lhs, TEnum rhs) { + using Underlying = typename std::underlying_type::type; + return static_cast(static_cast(lhs) & static_cast(rhs)); +} + +template +typename std::enable_if::value, TEnum>::type +operator|(TEnum lhs, TEnum rhs) { + using Underlying = typename std::underlying_type::type; + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +template +typename std::enable_if::value, TEnum>::type +operator^(TEnum lhs, TEnum rhs) { + using Underlying = typename std::underlying_type::type; + return static_cast(static_cast(lhs) ^ static_cast(rhs)); +} + +template +typename std::enable_if::value, TEnum>::type +operator~(TEnum value) { + using Underlying = typename std::underlying_type::type; + return static_cast(~static_cast(value)); +} + +template +typename std::enable_if::value, TEnum>::type +operator&=(TEnum& lhs, TEnum rhs) { + return lhs = (lhs & rhs); +} + +template +typename std::enable_if::value, TEnum>::type +operator|=(TEnum& lhs, TEnum rhs) { + return lhs = (lhs | rhs); +} + +template +typename std::enable_if::value, TEnum>::type +operator^=(TEnum& lhs, TEnum rhs) { + return lhs = (lhs ^ rhs); +} + +template +typename std::enable_if::value, bool>::type +contains(TEnum lhs, TEnum rhs) { + return (lhs & rhs) == rhs; +} + +} // namespace trio diff --git a/dnacalib/PyDNA/CMakeLists.txt b/dnacalib/PyDNA/CMakeLists.txt new file mode 100644 index 0000000..8e4e79c --- /dev/null +++ b/dnacalib/PyDNA/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.14) + +project(PyDNA) + +set(CMAKE_CXX_STANDARD 11) + +find_package(SWIG 4.0.0) +include(UseSWIG) + +add_subdirectory(python3) diff --git a/dnacalib/PyDNA/README.md b/dnacalib/PyDNA/README.md new file mode 100644 index 0000000..f2a69a9 --- /dev/null +++ b/dnacalib/PyDNA/README.md @@ -0,0 +1,7 @@ +# PyDNA + +Python wrapper for the DNA API. + +## Usage + +For now, check out the examples under `python3/examples/`. diff --git a/dnacalib/PyDNA/python3/CMakeLists.txt b/dnacalib/PyDNA/python3/CMakeLists.txt new file mode 100644 index 0000000..3736e0f --- /dev/null +++ b/dnacalib/PyDNA/python3/CMakeLists.txt @@ -0,0 +1,70 @@ +set(PYTHON3_EXACT_VERSION "" CACHE STRING "Specify exact python3 version against which the extension should be built") +if(PYTHON3_EXACT_VERSION) + set(find_python3_extra_args ${PYTHON3_EXACT_VERSION} EXACT) +endif() +find_package(Python3 ${find_python3_extra_args} COMPONENTS Development Interpreter) + +set(py_version "py${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}") +set(output_dir "${py_version}") +set_property(SOURCE DNA.i PROPERTY CPLUSPLUS ON) +set_property(SOURCE DNA.i PROPERTY SWIG_MODULE_NAME dna) +set_property(SOURCE DNA.i PROPERTY SWIG_FLAGS "-doxygen") + +option(TYPEMAP_DEBUG "Debug deducing of typemaps" OFF) +if(TYPEMAP_DEBUG) + set_property(SOURCE DNA.i PROPERTY SWIG_FLAGS "-debug-tmsearch") +endif() + +swig_add_library(py3dna + TYPE + SHARED + LANGUAGE + python + OUTPUT_DIR + ${CMAKE_BINARY_DIR}/${output_dir} + OUTFILE_DIR + ${CMAKE_BINARY_DIR}/python3 + SOURCES + DNA.i) +add_library(PyDNA::py3dna ALIAS py3dna) + +set_target_properties(py3dna PROPERTIES + SWIG_USE_TARGET_INCLUDE_DIRECTORIES ON + SWIG_COMPILE_DEFINITIONS DNA_BUILD_WITH_JSON_SUPPORT=ON) +target_compile_definitions(py3dna PRIVATE DNA_BUILD_WITH_JSON_SUPPORT=ON) +target_include_directories(py3dna PUBLIC ${CMAKE_CURRENT_LIST_DIR}) +target_link_libraries(py3dna + PUBLIC + DNACalib::dnacalib + Spyus::spyus + PRIVATE + Python3::Python) + +set(component_name "${PROJECT_NAME}-${py_version}") +get_property(wrapper_files TARGET py3dna PROPERTY SWIG_SUPPORT_FILES) +install(FILES ${wrapper_files} DESTINATION ${output_dir} COMPONENT ${component_name}) +install(TARGETS py3dna + RUNTIME + DESTINATION ${output_dir} + COMPONENT ${component_name} + LIBRARY + DESTINATION ${output_dir} + COMPONENT ${component_name} + NAMELINK_COMPONENT ${component_name} + ARCHIVE + DESTINATION ${output_dir} + COMPONENT ${component_name}) +install(FILES ${CMAKE_CURRENT_LIST_DIR}/examples/demo.py DESTINATION ${output_dir} RENAME dna_demo.py COMPONENT ${component_name}) + +set(CPACK_COMPONENTS_ALL "${CPACK_COMPONENTS_ALL};${component_name}" PARENT_SCOPE) + +if(WIN32) + set(extra_env "PATH=${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") +endif() +set(DNA_TEST_NAMES dna_binary_to_json_demo dna_demo) +foreach(test_name ${DNA_TEST_NAMES}) + add_test(NAME ${test_name} + COMMAND ${CMAKE_COMMAND} -E env ${extra_env} LD_LIBRARY_PATH=${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR} PYTHONPATH=. ${Python3_EXECUTABLE} "${CMAKE_CURRENT_LIST_DIR}/../../../examples/${test_name}.py" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${output_dir}) + set_property(TEST ${test_name} PROPERTY PASS_REGULAR_EXPRESSION "Done\.") +endforeach() diff --git a/dnacalib/PyDNA/python3/DNA.i b/dnacalib/PyDNA/python3/DNA.i new file mode 100644 index 0000000..8842ae1 --- /dev/null +++ b/dnacalib/PyDNA/python3/DNA.i @@ -0,0 +1,185 @@ +%module dna + +%pythonbegin +%{ +import os +if hasattr(os, 'add_dll_directory'): + for path in os.environ.get('PATH', '').split(';'): + try: + if path: + os.add_dll_directory(path) + except Exception: + pass +%} + +%{ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dna/Defs.h" +#include "dna/DataLayer.h" +#include "dna/types/ArrayView.h" +#include "dna/types/StringView.h" +#include "dna/types/Aliases.h" +#include "dna/layers/Descriptor.h" +#include "dna/layers/Geometry.h" + +#include "dna/layers/DescriptorReader.h" +#include "dna/layers/DefinitionReader.h" +#include "dna/layers/BehaviorReader.h" +#include "dna/layers/GeometryReader.h" +#include "dna/Reader.h" +#include "dna/StreamReader.h" +#include "dna/BinaryStreamReader.h" +#include "dna/JSONStreamReader.h" + +#include "dna/layers/DescriptorWriter.h" +#include "dna/layers/DefinitionWriter.h" +#include "dna/layers/BehaviorWriter.h" +#include "dna/layers/GeometryWriter.h" +#include "dna/Writer.h" +#include "dna/StreamWriter.h" +#include "dna/BinaryStreamWriter.h" +#include "dna/JSONStreamWriter.h" +%} + +%include + +%include "stdint.i" +%include + +%ignore sc::operator==; +%ignore sc::operator!=; + +%include +%include +%include +%include +%include +%include +%include +%include +%include +%include +%include +%include +%include +%include +%include +%include +pythonize_unmanaged_type(FileStream, create, destroy) +pythonize_unmanaged_type(MemoryMappedFileStream, create, destroy) +pythonize_unmanaged_type(MemoryStream, create, destroy) + +%pythoncode %{ +FileStream.AccessMode_Read = AccessMode_Read +FileStream.AccessMode_Write = AccessMode_Write +FileStream.AccessMode_ReadWrite = AccessMode_ReadWrite + +FileStream.OpenMode_Binary = OpenMode_Binary +FileStream.OpenMode_Text = OpenMode_Text + +MemoryMappedFileStream.AccessMode_Read = AccessMode_Read +MemoryMappedFileStream.AccessMode_Write = AccessMode_Write +MemoryMappedFileStream.AccessMode_ReadWrite = AccessMode_ReadWrite +%} + +%include +%include + +%include "Geometry.i" + +array_view_to_py_list(trust::ArrayView); +%apply trust::ArrayView { + trust::ConstArrayView, + dna::ArrayView, + dna::ConstArrayView +}; + +string_view_to_py_string(trust::StringView); +%apply trust::StringView { + dna::StringView +}; + +vector3_typemap(dna::Vector3); +texture_coordinate_typemap(dna::TextureCoordinate); +vertex_layout_typemap(dna::VertexLayout); + +py_list_to_c_array(const std::uint16_t* jointIndices, std::uint16_t count); +%apply (const std::uint16_t* jointIndices, std::uint16_t count) { + (const std::uint16_t* animatedMapIndices, std::uint16_t count), + (const std::uint16_t* meshIndices, std::uint16_t count), + (const std::uint16_t* rowIndices, std::uint16_t count), + (const std::uint16_t* columnIndices, std::uint16_t count), + (const std::uint16_t* outputIndices, std::uint16_t count), + (const std::uint16_t* inputIndices, std::uint16_t count), + (const std::uint16_t* lods, std::uint16_t count), + (const std::uint16_t* blendShapeChannelIndices, std::uint16_t count), + (const std::uint32_t* vertexIndices, std::uint32_t count), + (const std::uint32_t* layoutIndices, std::uint32_t count) +}; + +py_list_to_c_array(const float* fromValues, std::uint16_t count); +%apply (const float* fromValues, std::uint16_t count) { + (const float* toValues, std::uint16_t count), + (const float* slopeValues, std::uint16_t count), + (const float* cutValues, std::uint16_t count), + (const float* weights, std::uint16_t count) +}; + +py_list_to_c_array(const float* values, std::uint32_t count); + +py_list_to_c_array(const dna::VertexLayout* layouts, std::uint32_t count); + +py_list_to_c_array(const dna::TextureCoordinate* textureCoordinates, std::uint32_t count); + +py_list_to_c_array(const dna::Normal* normals, std::uint32_t count); +%apply (const dna::Normal* normals, std::uint32_t count) { + (const dna::Vector3* translations, std::uint16_t count), + (const dna::Vector3* rotations, std::uint16_t count), + (const dna::Position* positions, std::uint32_t count), + (const dna::Delta* deltas, std::uint32_t count) +}; + +%include "dna/Defs.h" +%include "dna/types/ArrayView.h" +%include "dna/types/StringView.h" +%include "dna/types/Aliases.h" +%include "dna/types/Vector3.h" +%include "dna/DataLayer.h" +%include "dna/layers/Descriptor.h" +%include "dna/layers/Geometry.h" +%include "dna/layers/DescriptorReader.h" +%include "dna/layers/DefinitionReader.h" +%include "dna/layers/BehaviorReader.h" +%include "dna/layers/GeometryReader.h" +%include "dna/Reader.h" +%include "dna/StreamReader.h" +%include "dna/BinaryStreamReader.h" +%include "dna/JSONStreamReader.h" +pythonize_unmanaged_type(BinaryStreamReader, create, destroy) +pythonize_unmanaged_type(JSONStreamReader, create, destroy) +%include "dna/layers/DescriptorWriter.h" +%include "dna/layers/DefinitionWriter.h" +%include "dna/layers/BehaviorWriter.h" +%include "dna/layers/GeometryWriter.h" +%include "dna/Writer.h" +%include "dna/StreamWriter.h" +%include "dna/BinaryStreamWriter.h" +%include "dna/JSONStreamWriter.h" +pythonize_unmanaged_type(BinaryStreamWriter, create, destroy) +pythonize_unmanaged_type(JSONStreamWriter, create, destroy) diff --git a/dnacalib/PyDNA/python3/Geometry.i b/dnacalib/PyDNA/python3/Geometry.i new file mode 100644 index 0000000..0a67719 --- /dev/null +++ b/dnacalib/PyDNA/python3/Geometry.i @@ -0,0 +1,87 @@ +%include + +%include + +%define texture_coordinate_typemap(type_name) +%ignore type_name; + +%inline{ + template<> + struct Caster { + + static type_name fromPy(PyObject* listObject) { + return type_name{ + Caster::fromPy(PyList_GetItem(listObject, 0)), + Caster::fromPy(PyList_GetItem(listObject, 1)) + }; + } + + static PyObject* toPy(type_name textureCoordinate) { + PyObject* pyTextureCoordinate = PyList_New(static_cast(2)); + PyList_SetItem(pyTextureCoordinate, static_cast(0), Caster::toPy(textureCoordinate.u)); + PyList_SetItem(pyTextureCoordinate, static_cast(1), Caster::toPy(textureCoordinate.v)); + return pyTextureCoordinate; + } + }; +} + +%typemap(in) (type_name) { + if (PyList_Check($input)) { + $1 = Caster::fromPy($input); + } else { + SWIG_exception(SWIG_TypeError, "list expected"); + } +}; + +%typemap(out) type_name { + $result = Caster<$1_basetype>::toPy($1); +} + +%typemap(typecheck, precedence=SWIG_TYPECHECK_FLOAT_ARRAY) type_name { + $1 = PyList_Check($input) ? 1 : 0; +} + +%enddef + +%define vertex_layout_typemap(type_name) +%ignore type_name; + +%inline { + template<> + struct Caster { + + static type_name fromPy(PyObject* listObject) { + return type_name{ + Caster::fromPy(PyList_GetItem(listObject, 0)), + Caster::fromPy(PyList_GetItem(listObject, 1)), + Caster::fromPy(PyList_GetItem(listObject, 2)) + }; + } + + static PyObject* toPy(type_name vertexLayout) { + PyObject* pyVertexLayout = PyList_New(static_cast(3)); + PyList_SetItem(pyVertexLayout, static_cast(0), Caster::toPy(vertexLayout.position)); + PyList_SetItem(pyVertexLayout, static_cast(1), Caster::toPy(vertexLayout.textureCoordinate)); + PyList_SetItem(pyVertexLayout, static_cast(2), Caster::toPy(vertexLayout.normal)); + return pyVertexLayout; + } + }; +} + +%typemap(in) (type_name) { + if (PyList_Check($input)) { + $1 = Caster::fromPy($input); + } else { + SWIG_exception(SWIG_TypeError, "list expected"); + } +}; + +%typemap(out) type_name { + $result = Caster<$1_basetype>::toPy($1); +} + +%typemap(typecheck, precedence=SWIG_TYPECHECK_FLOAT_ARRAY) type_name { + $1 = PyList_Check($input) ? 1 : 0; +} + +%enddef diff --git a/dnacalib/PyDNA/python3/examples/demo.py b/dnacalib/PyDNA/python3/examples/demo.py new file mode 100644 index 0000000..81133e8 --- /dev/null +++ b/dnacalib/PyDNA/python3/examples/demo.py @@ -0,0 +1,71 @@ +#-*- coding: utf-8 -*- +import argparse + +import dna + + +def createDNA(path): + stream = dna.FileStream(path, dna.FileStream.AccessMode_Write, dna.FileStream.OpenMode_Binary) + writer = dna.BinaryStreamWriter(stream) + + writer.setName("rig name") + writer.setLODCount(4) + writer.setJointName(0, "spine") + writer.setJointName(1, "neck") + + writer.setMeshName(0, "head") + writer.setVertexPositions(0, [[0.0, 0.5, 0.3], [1.0, 3.0, -8.0]]) + writer.setVertexTextureCoordinates(0, [[0.25, 0.55], [1.5, 3.6]]) + + writer.write() + if not dna.Status.isOk(): + status = dna.Status.get() + raise RuntimeError("Error saving DNA: {}".format(status.message)) + + +def loadDNA(path): + stream = dna.FileStream(path, dna.FileStream.AccessMode_Read, dna.FileStream.OpenMode_Binary) + reader = dna.BinaryStreamReader(stream, dna.DataLayer_All) + reader.read() + if not dna.Status.isOk(): + status = dna.Status.get() + raise RuntimeError("Error loading DNA: {}".format(status.message)) + return reader + + +def printDNASummary(dnaReader): + print("Name: {}".format(dnaReader.getName())) + print("Joint count: {}".format(dnaReader.getJointCount())) + jointNames = ', '.join(dnaReader.getJointName(i) for i in range(dnaReader.getJointCount())) + print("Joint names: " + jointNames) + + for meshIdx in range(dnaReader.getMeshCount()): + # Get vertices one by one + for vtxId in range(dnaReader.getVertexPositionCount(meshIdx)): + vtx = dnaReader.getVertexPosition(meshIdx, vtxId) + print("Mesh {} - Vertex {} : {}".format(meshIdx, vtxId, vtx)) + # Get all X / Y / Z coordinates + print(dnaReader.getVertexPositionXs(meshIdx)) + print(dnaReader.getVertexPositionYs(meshIdx)) + print(dnaReader.getVertexPositionZs(meshIdx)) + + for tcIdx in range(dnaReader.getVertexTextureCoordinateCount(meshIdx)): + texCoord = dnaReader.getVertexTextureCoordinate(meshIdx, tcIdx) + print("Mesh {} - Texture coordinate {} : {}".format(meshIdx, tcIdx, texCoord)) + + +def main(): + parser = argparse.ArgumentParser(description="DNA demo") + parser.add_argument('dna_path', + metavar='dna_path', + help='Path where to save the DNA file') + + args = parser.parse_args() + + createDNA(args.dna_path) + dnaReader = loadDNA(args.dna_path) + printDNASummary(dnaReader) + + +if __name__ == '__main__': + main() diff --git a/dnacalib/PyDNACalib/CMakeLists.txt b/dnacalib/PyDNACalib/CMakeLists.txt new file mode 100644 index 0000000..87e5461 --- /dev/null +++ b/dnacalib/PyDNACalib/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.14) +project(PyDNACalib) + +set(CMAKE_CXX_STANDARD 11) + +find_package(SWIG 4.0.0) +include(UseSWIG) + +add_subdirectory(python3) diff --git a/dnacalib/PyDNACalib/README.md b/dnacalib/PyDNACalib/README.md new file mode 100644 index 0000000..c511283 --- /dev/null +++ b/dnacalib/PyDNACalib/README.md @@ -0,0 +1,7 @@ +# PyDNACalib + +Python wrapper for the DNACalib API. + +## Usage + +For now, check out the examples under `python3/examples/`. diff --git a/dnacalib/PyDNACalib/python3/CMakeLists.txt b/dnacalib/PyDNACalib/python3/CMakeLists.txt new file mode 100644 index 0000000..da41b3b --- /dev/null +++ b/dnacalib/PyDNACalib/python3/CMakeLists.txt @@ -0,0 +1,73 @@ +set(PYTHON3_EXACT_VERSION "" CACHE STRING "Specify exact python3 version against which the extension should be built") +if(PYTHON3_EXACT_VERSION) + set(find_python3_extra_args ${PYTHON3_EXACT_VERSION} EXACT) +endif() +find_package(Python3 ${find_python3_extra_args} COMPONENTS Development Interpreter) + +set(py_version "py${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}") +set(output_dir "${py_version}") +set_property(SOURCE DNACalib.i PROPERTY CPLUSPLUS ON) +set_property(SOURCE DNACalib.i PROPERTY SWIG_MODULE_NAME dnacalib) +set_property(SOURCE DNACalib.i PROPERTY SWIG_FLAGS "-doxygen") + +option(TYPEMAP_DEBUG "Debug deducing of typemaps" OFF) +if(TYPEMAP_DEBUG) + set_property(SOURCE DNACalib.i PROPERTY SWIG_FLAGS "-debug-tmsearch") +endif() + +swig_add_library(py3dnacalib + TYPE + SHARED + LANGUAGE + python + OUTPUT_DIR + ${CMAKE_BINARY_DIR}/${output_dir} + OUTFILE_DIR + ${CMAKE_BINARY_DIR}/python3 + SOURCES + DNACalib.i) +add_library(PyDNACalib::py3dnacalib ALIAS py3dnacalib) + +set_target_properties(py3dnacalib PROPERTIES SWIG_USE_TARGET_INCLUDE_DIRECTORIES ON) +target_link_libraries(py3dnacalib + PUBLIC + PyDNA::py3dna + PRIVATE + Spyus::spyus + DNACalib::dnacalib + Python3::Python) + +set(component_name "${PROJECT_NAME}-${py_version}") +get_property(wrapper_files TARGET py3dnacalib PROPERTY SWIG_SUPPORT_FILES) +install(FILES ${wrapper_files} DESTINATION ${output_dir} COMPONENT ${component_name}) +install(TARGETS py3dnacalib + RUNTIME + DESTINATION ${output_dir} + COMPONENT ${component_name} + LIBRARY + DESTINATION ${output_dir} + COMPONENT ${component_name} + NAMELINK_COMPONENT ${component_name} + ARCHIVE + DESTINATION ${output_dir} + COMPONENT ${component_name}) +install(FILES ${CMAKE_CURRENT_LIST_DIR}/examples/clear_blend_shapes.py DESTINATION ${output_dir}/examples RENAME dnacalib_clear_blend_shapes.py COMPONENT ${component_name}) +install(FILES ${CMAKE_CURRENT_LIST_DIR}/examples/demo.py DESTINATION ${output_dir}/examples RENAME dnacalib_demo.py COMPONENT ${component_name}) +install(FILES ${CMAKE_CURRENT_LIST_DIR}/examples/remove_joint.py DESTINATION ${output_dir}/examples RENAME dnacalib_remove_joint.py COMPONENT ${component_name}) +set(CPACK_COMPONENTS_ALL "${CPACK_COMPONENTS_ALL};${component_name}" PARENT_SCOPE) + +if(WIN32) + set(extra_env "PATH=${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") +endif() +set(DNACALIB_TEST_NAMES dnacalib_clear_blend_shapes + dnacalib_demo + dnacalib_lod_demo + dnacalib_neutral_mesh_subtract + dnacalib_remove_joint + dnacalib_rename_joint_demo) +foreach(test_name ${DNACALIB_TEST_NAMES}) + add_test(NAME ${test_name} + COMMAND ${CMAKE_COMMAND} -E env ${extra_env} LD_LIBRARY_PATH=${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR} PYTHONPATH=. ${Python3_EXECUTABLE} "${CMAKE_CURRENT_LIST_DIR}/../../../examples/${test_name}.py" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${output_dir}) + set_property(TEST ${test_name} PROPERTY PASS_REGULAR_EXPRESSION "Done\.") +endforeach() diff --git a/dnacalib/PyDNACalib/python3/DNACalib.i b/dnacalib/PyDNACalib/python3/DNACalib.i new file mode 100644 index 0000000..9151989 --- /dev/null +++ b/dnacalib/PyDNACalib/python3/DNACalib.i @@ -0,0 +1,137 @@ +%module dnacalib + +%pythonbegin +%{ +import os +if hasattr(os, 'add_dll_directory'): + for path in os.environ.get('PATH', '').split(';'): + try: + if path: + os.add_dll_directory(path) + except Exception: + pass +%} + +%include +%include + +%include +%include +%include + +%import "DNA.i" + +%{ +#include "dnacalib/Command.h" +#include "dnacalib/Defs.h" +#include "dnacalib/commands/CalculateMeshLowerLODsCommand.h" +#include "dnacalib/commands/ClearBlendShapesCommand.h" +#include "dnacalib/commands/CommandSequence.h" +#include "dnacalib/commands/PruneBlendShapeTargetsCommand.h" +#include "dnacalib/commands/RemoveAnimatedMapCommand.h" +#include "dnacalib/commands/RemoveBlendShapeCommand.h" +#include "dnacalib/commands/RemoveJointAnimationCommand.h" +#include "dnacalib/commands/RemoveJointCommand.h" +#include "dnacalib/commands/RemoveMeshCommand.h" +#include "dnacalib/commands/RenameAnimatedMapCommand.h" +#include "dnacalib/commands/RenameBlendShapeCommand.h" +#include "dnacalib/commands/RenameJointCommand.h" +#include "dnacalib/commands/RenameMeshCommand.h" +#include "dnacalib/commands/RotateCommand.h" +#include "dnacalib/commands/ScaleCommand.h" +#include "dnacalib/commands/SetBlendShapeTargetDeltasCommand.h" +#include "dnacalib/commands/SetLODsCommand.h" +#include "dnacalib/commands/SetNeutralJointRotationsCommand.h" +#include "dnacalib/commands/SetNeutralJointTranslationsCommand.h" +#include "dnacalib/commands/SetSkinWeightsCommand.h" +#include "dnacalib/commands/SetVertexPositionsCommand.h" +#include "dnacalib/commands/TranslateCommand.h" +#include "dnacalib/commands/VectorOperations.h" +#include "dnacalib/dna/DNACalibDNAReader.h" +#include "dnacalib/types/Aliases.h" +#include "dnacalib/version/VersionInfo.h" +%} + +py_list_to_array_view(dnac::ConstArrayView, SWIG_TYPECHECK_FLOAT_ARRAY) +py_list_to_array_view(dnac::ConstArrayView, SWIG_TYPECHECK_FLOAT_ARRAY) +py_list_to_array_view(dnac::ConstArrayView, SWIG_TYPECHECK_INT16_ARRAY) +py_list_to_array_view(dnac::ConstArrayView, SWIG_TYPECHECK_INT32_ARRAY) + +array_view_to_py_list(trust::ArrayView); +%apply trust::ArrayView { + trust::ConstArrayView, + dnac::ArrayView, + dnac::ConstArrayView +}; + +string_view_to_py_string(trust::StringView); +%apply trust::StringView { + dnac::StringView +}; + +vector3_typemap(dnac::Vector3); + +// Ignore all `add` and `remove` overloads +%rename("$ignore") dnac::CommandSequence::add; +%rename("$ignore") dnac::CommandSequence::remove; +// Selectively enable only one of the overloads +%rename("%s") dnac::CommandSequence::add(Command* command); +%rename("%s") dnac::CommandSequence::remove(Command* command); + +%include "dnacalib/Defs.h" +%include "dnacalib/types/Aliases.h" +%include "dnacalib/version/VersionInfo.h" +%include "dnacalib/dna/DNACalibDNAReader.h" +pythonize_unmanaged_type(DNACalibDNAReader, create, destroy) +%include "dnacalib/Command.h" +%include "dnacalib/commands/VectorOperations.h" + +%include "dnacalib/commands/CommandSequence.h" +// CommandSequence doesn't take ownership over the provided commands. +// To avoid use-after-free issues, references to the command instances must be kept +// on the Python side as well. +%pythoncode %{ +def command_sequence_init(_init): + def wrapper(self, *args, **kwargs): + self._commands = [] + _init(self, *args, **kwargs) + return wrapper + +def command_sequence_add(_add): + def wrapper(self, command): + self._commands.append(command) + _add(self, command) + return wrapper + +def command_sequence_remove(_remove): + def wrapper(self, command): + self._commands.remove(command) + _remove(self, command) + return wrapper + +CommandSequence.__init__ = command_sequence_init(CommandSequence.__init__) +CommandSequence.add = command_sequence_add(CommandSequence.add) +CommandSequence.remove = command_sequence_remove(CommandSequence.remove) +%} + +%include "dnacalib/commands/CalculateMeshLowerLODsCommand.h" +%include "dnacalib/commands/ClearBlendShapesCommand.h" +%include "dnacalib/commands/PruneBlendShapeTargetsCommand.h" +%include "dnacalib/commands/RemoveAnimatedMapCommand.h" +%include "dnacalib/commands/RemoveBlendShapeCommand.h" +%include "dnacalib/commands/RemoveJointAnimationCommand.h" +%include "dnacalib/commands/RemoveJointCommand.h" +%include "dnacalib/commands/RemoveMeshCommand.h" +%include "dnacalib/commands/RenameAnimatedMapCommand.h" +%include "dnacalib/commands/RenameBlendShapeCommand.h" +%include "dnacalib/commands/RenameJointCommand.h" +%include "dnacalib/commands/RenameMeshCommand.h" +%include "dnacalib/commands/RotateCommand.h" +%include "dnacalib/commands/ScaleCommand.h" +%include "dnacalib/commands/SetBlendShapeTargetDeltasCommand.h" +%include "dnacalib/commands/SetLODsCommand.h" +%include "dnacalib/commands/SetNeutralJointRotationsCommand.h" +%include "dnacalib/commands/SetNeutralJointTranslationsCommand.h" +%include "dnacalib/commands/SetSkinWeightsCommand.h" +%include "dnacalib/commands/SetVertexPositionsCommand.h" +%include "dnacalib/commands/TranslateCommand.h" diff --git a/dnacalib/PyDNACalib/python3/examples/clear_blend_shapes.py b/dnacalib/PyDNACalib/python3/examples/clear_blend_shapes.py new file mode 100644 index 0000000..65bdb0a --- /dev/null +++ b/dnacalib/PyDNACalib/python3/examples/clear_blend_shapes.py @@ -0,0 +1,120 @@ +#-*- coding: utf-8 -*- +import argparse + +import dnacalib as dnac +import dna + + +def loadDNA(path): + stream = dna.FileStream(path, dna.FileStream.AccessMode_Read, dna.FileStream.OpenMode_Binary) + reader = dna.BinaryStreamReader(stream, dna.DataLayer_All) + reader.read() + if not dna.Status.isOk(): + status = dna.Status.get() + raise RuntimeError("Error loading DNA: {}".format(status.message)) + return reader + + +def saveDNA(reader, path): + stream = dna.FileStream(path, dna.FileStream.AccessMode_Write, dna.FileStream.OpenMode_Binary) + writer = dna.BinaryStreamWriter(stream) + writer.setFrom(reader) + writer.write() + + if not dna.Status.isOk(): + status = dna.Status.get() + raise RuntimeError("Error saving DNA: {}".format(status.message)) + +def validate_geometry(dna): + mesh_count = dna.getMeshCount() + for mesh_index in range(mesh_count): + bs_tgt_count = dna.getBlendShapeTargetCount(mesh_index) + for bs_tgt_index in range(bs_tgt_count): + bs_tgt_delta_count = dna.getBlendShapeTargetDeltaCount(mesh_index, bs_tgt_index) + if bs_tgt_delta_count != 0: + raise RuntimeError("Blend shape target deltas not removed properly!") + +def validate_animation_data(dna): + bs_channel_lods = dna.getBlendShapeChannelLODs() + bs_channel_input_indices = dna.getBlendShapeChannelInputIndices() + bs_channel_output_indices = dna.getBlendShapeChannelOutputIndices() + + if len(bs_channel_lods) != dna.getLODCount(): + raise RuntimeError("Blend shape animation data not removed properly! Number of blend shape LODs does not match LOD count!") + + for lod in bs_channel_lods: + if lod != 0: + raise RuntimeError("Blend shape animation data not removed properly!") + + if (len(bs_channel_input_indices) != 0) or (len(bs_channel_output_indices) != 0): + raise RuntimeError("Blend shape animation data not removed properly!") + +def calibrateDNA(inputPath, outputPath): + dna = loadDNA(inputPath) + + # Copies DNA contents and will serve as input/output parameter to command + calibrated = dnac.DNACalibDNAReader(dna) + + mesh_count = calibrated.getMeshCount() + print("Number of meshes: {}".format(mesh_count)) + + for mesh_index in range(mesh_count): + bs_tgt_count = calibrated.getBlendShapeTargetCount(mesh_index) + print("Number of blendshape targets for mesh {}({}): {}".format(calibrated.getMeshName(mesh_index), mesh_index, bs_tgt_count)) + for bs_tgt_index in range(bs_tgt_count): + bs_tgt_delta_count = calibrated.getBlendShapeTargetDeltaCount(mesh_index, bs_tgt_index) + print("Number of blendshape target deltas for mesh {}({}), blend shape target {}: {}".format(calibrated.getMeshName(mesh_index), mesh_index, bs_tgt_index, bs_tgt_delta_count)) + + print("Blend shape channel LODs: {}".format(calibrated.getBlendShapeChannelLODs())) + print("Blend shape channel input indices: {}".format(calibrated.getBlendShapeChannelInputIndices())) + print("Blend shape channel output indices: {}".format(calibrated.getBlendShapeChannelOutputIndices())) + + # Clears all blend shapes + command = dnac.ClearBlendShapesCommand() + + print("\n\nClearing blend shape data...\n\n") + # Modifies calibrated DNA in-place + command.run(calibrated) + + validate_geometry(calibrated) + validate_animation_data(calibrated) + + print("Number of meshes: {}".format(mesh_count)) + + for mesh_index in range(mesh_count): + bs_tgt_count = calibrated.getBlendShapeTargetCount(mesh_index) + print("Number of blendshape targets for mesh {}({}): {}".format(calibrated.getMeshName(mesh_index), mesh_index, bs_tgt_count)) + for bs_tgt_index in range(bs_tgt_count): + bs_tgt_delta_count = calibrated.getBlendShapeTargetDeltaCount(mesh_index, bs_tgt_index) + print("Number of blendshape target deltas for mesh {}({}), blend shape target {}: {}".format(calibrated.getMeshName(mesh_index), mesh_index, bs_tgt_index, bs_tgt_delta_count)) + + bs_channel_lods = dna.getBlendShapeChannelLODs() + bs_channel_input_indices = dna.getBlendShapeChannelInputIndices() + bs_channel_output_indices = dna.getBlendShapeChannelOutputIndices() + + print("Blend shape channel LODs: {}".format(bs_channel_lods)) + print("Blend shape channel input indices: {}".format(bs_channel_input_indices)) + print("Blend shape channel output indices: {}".format(bs_channel_output_indices)) + + print("\n\nSuccessfully cleared blend shape data.") + + print("Saving DNA...") + saveDNA(calibrated, outputPath) + print("Done.") + +def main(): + parser = argparse.ArgumentParser(description="DNACalib clear blend shapes example") + parser.add_argument('input_dna', + metavar='input-dna', + help='Path to DNA file to load') + parser.add_argument('output_dna', + metavar='output-dna', + help='Path where to save modified DNA file') + + args = parser.parse_args() + + calibrateDNA(args.input_dna, args.output_dna) + + +if __name__ == '__main__': + main() diff --git a/dnacalib/PyDNACalib/python3/examples/demo.py b/dnacalib/PyDNACalib/python3/examples/demo.py new file mode 100644 index 0000000..611ae60 --- /dev/null +++ b/dnacalib/PyDNACalib/python3/examples/demo.py @@ -0,0 +1,122 @@ +#-*- coding: utf-8 -*- +import argparse + +import dnacalib as dnac +import dna + + +def loadDNA(path): + stream = dna.FileStream(path, dna.FileStream.AccessMode_Read, dna.FileStream.OpenMode_Binary) + reader = dna.BinaryStreamReader(stream, dna.DataLayer_All) + reader.read() + if not dna.Status.isOk(): + status = dna.Status.get() + raise RuntimeError("Error loading DNA: {}".format(status.message)) + return reader + + +def saveDNA(reader, path): + stream = dna.FileStream(path, dna.FileStream.AccessMode_Write, dna.FileStream.OpenMode_Binary) + writer = dna.BinaryStreamWriter(stream) + writer.setFrom(reader) + writer.write() + + if not dna.Status.isOk(): + status = dna.Status.get() + raise RuntimeError("Error saving DNA: {}".format(status.message)) + + +def buildCommandList(): + # Abstraction to collect all commands into a sequence, and run them with only one invocation + commands = dnac.CommandSequence() + + print("Creating a sequence of commands...") + # Instantiate command with parameters: scale-factor = 2 , origin-xyz = (0, 120, 0) + scaleByTwo = dnac.ScaleCommand(2.0, [0.0, 120.0, 0.0]) + # Alternatively a command can be instantiated empty, and populated with parameters later, e.g.: + # scaleByTwo = dnac.ScaleCommand() + # scaleByTwo.setScale(2.0) + # scaleByTwo.setOrigin([0.0, 120.0, 0.0]) + commands.add(scaleByTwo) + + print("Added command to scale dna") + # Rename by joint index (faster) + commands.add(dnac.RenameJointCommand(10, "NewJointA")) + + # Rename by matching joint name (slower) + commands.add(dnac.RenameJointCommand("OldJointB", "NewJointB")) + + print("Added command to rename joint") + # Interpolate blend shape target deltas between original DNA and below specified deltas + # ¯\_(ツ)_/¯ + # Deltas in [[x, y, z], [x, y, z], [x, y, z]] format + blendShapeTargetDeltas = [[0.0, 0.0, 2.0], [0.0, -1.0, 4.0], [3.0, -3.0, 8.0]] + vertexIndices = [0, 1, 2] + # Weights for interpolation between original deltas and above defined deltas + # 1.0 == take the new value completely, 0.0 means keep the old value + # Format: [Delta-0-Mask, Delta-1-Mask, Delta-2-Mask] + masks = [1.0, 0.0, 0.5] + setBlendShapesM0B0 = dnac.SetBlendShapeTargetDeltasCommand(0, # mesh index + 0, # blend shape target index + blendShapeTargetDeltas, + vertexIndices, + masks, + dnac.VectorOperation_Interpolate) + commands.add(setBlendShapesM0B0) + print("Added command to change blend shape target deltas") + + # Add vertex position deltas onto existing vertex positions + # Note the alternative data format, instead of using nested lists, separate all X, Y, Z + # components into distinct lists (might also be faster) + positionXs = [1.0, -4.5, 7.2] + positionYs = [2.0, -5.5, -8.3] + positionZs = [3.0, -6.5, 9.7] + # Weights to be multiplied with the above specified delta positions, before adding + # them onto the original data + # Format: [Delta-0-Weight, Delta-1-Weight, Delta-2-Weight] + masks = [1.0, 0.2, 0.4] + setVerticesM0 = dnac.SetVertexPositionsCommand(0, # mesh index + positionXs, + positionYs, + positionZs, + masks, + dnac.VectorOperation_Add) + commands.add(setVerticesM0) + print("Added command to change vertex positions") + + return commands + + +def calibrateDNA(inputPath, outputPath): + dna = loadDNA(inputPath) + + # Copies DNA contents and will serve as input/output parameter to commands + calibrated = dnac.DNACalibDNAReader(dna) + + commands = buildCommandList() + + print("Running command sequence...") + # Modifies calibrated DNA in-place + commands.run(calibrated) + + print("Saving DNA...") + saveDNA(calibrated, outputPath) + + print("Done.") + +def main(): + parser = argparse.ArgumentParser(description="DNACalib demo") + parser.add_argument('input_dna', + metavar='input-dna', + help='Path to DNA file to load') + parser.add_argument('output_dna', + metavar='output-dna', + help='Path where to save modified DNA file') + + args = parser.parse_args() + + calibrateDNA(args.input_dna, args.output_dna) + + +if __name__ == '__main__': + main() diff --git a/dnacalib/PyDNACalib/python3/examples/remove_joint.py b/dnacalib/PyDNACalib/python3/examples/remove_joint.py new file mode 100644 index 0000000..c71b746 --- /dev/null +++ b/dnacalib/PyDNACalib/python3/examples/remove_joint.py @@ -0,0 +1,83 @@ +#-*- coding: utf-8 -*- +import argparse + +import dnacalib as dnac +import dna + + +def loadDNA(path): + stream = dna.FileStream(path, dna.FileStream.AccessMode_Read, dna.FileStream.OpenMode_Binary) + reader = dna.BinaryStreamReader(stream, dna.DataLayer_All) + reader.read() + if not dna.Status.isOk(): + status = dna.Status.get() + raise RuntimeError("Error loading DNA: {}".format(status.message)) + return reader + + +def saveDNA(reader, path): + stream = dna.FileStream(path, dna.FileStream.AccessMode_Write, dna.FileStream.OpenMode_Binary) + writer = dna.BinaryStreamWriter(stream) + writer.setFrom(reader) + writer.write() + + if not dna.Status.isOk(): + status = dna.Status.get() + raise RuntimeError("Error saving DNA: {}".format(status.message)) + +def getJoints(dna): + joints = [] + for jointIndex in range(dna.getJointCount()): + joints.append(dna.getJointName(jointIndex)) + return joints + +def printJoints(dna): + for jointIndex in range(dna.getJointCount()): + print(dna.getJointName(jointIndex)) + +def calibrateDNA(inputPath, outputPath): + dna = loadDNA(inputPath) + + # Copies DNA contents and will serve as input/output parameter to command + calibrated = dnac.DNACalibDNAReader(dna) + + original_joints = getJoints(calibrated) + + # An example joint to remove + joint_index = 314 + joint_name = calibrated.getJointName(joint_index) + + # Removes joint with specified index + command = dnac.RemoveJointCommand(joint_index) + + # Modifies calibrated DNA in-place + command.run(calibrated) + + modified_joints = getJoints(calibrated) + + if ((len(modified_joints) != (len(original_joints) - 1)) or (joint_name in modified_joints)): + raise RuntimeError("Joint not removed properly!") + + print("Successfully removed joint `{}`.".format(joint_name)) + + print("Saving DNA...") + saveDNA(calibrated, outputPath) + + print("Done.") + +def main(): + parser = argparse.ArgumentParser(description="DNACalib remove joint example") + parser.add_argument('input_dna', + metavar='input-dna', + help='Path to DNA file to load') + parser.add_argument('output_dna', + metavar='output-dna', + help='Path where to save modified DNA file') + + args = parser.parse_args() + + calibrateDNA(args.input_dna, args.output_dna) + + +if __name__ == '__main__': + main() diff --git a/dnacalib/SPyUS/CMakeLists.txt b/dnacalib/SPyUS/CMakeLists.txt new file mode 100644 index 0000000..8975ac6 --- /dev/null +++ b/dnacalib/SPyUS/CMakeLists.txt @@ -0,0 +1,62 @@ +cmake_minimum_required(VERSION 3.14) + +################################################ +# Project setup +set(SPYUS spyus) +set(SPYUS_VERSION 1.2.1) + +# Prevent in-source-tree builds +set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) +# Create compilation database +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "" FORCE) + +project(Spyus VERSION ${SPYUS_VERSION} LANGUAGES CXX) + +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +set(CMAKE_MODULES_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +list(APPEND CMAKE_MODULE_PATH ${CMAKE_MODULES_ROOT_DIR}) + +include(FetchContent) +function(module_exists module_name) + include(${module_name} OPTIONAL RESULT_VARIABLE found) + set("${module_name}_FOUND" ${found} PARENT_SCOPE) +endfunction() + +# Make custom cmake modules available +module_exists(CMakeModulesExtra) +if(NOT CMakeModulesExtra_FOUND) + include(CMakeModulesExtraLoader) +endif() + +include(CMakeModulesExtra) +list(APPEND CMAKE_MODULE_PATH ${CMakeModulesExtra_DIRS}) + +################################################ +# Source code +set(HEADERS + include/spyus/ArrayView.i + include/spyus/Caster.i + include/spyus/ExceptionHandling.i + include/spyus/TDM.i + include/spyus/Vector3.i) + +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${HEADERS}) + +################################################ +# Add target and build options +add_library(${SPYUS} INTERFACE) +add_library(Spyus::spyus ALIAS ${SPYUS}) + +include(GNUInstallDirs) + +set(SPYUS_PUBLIC_INCLUDE_DIRS + $ + $) + +target_include_directories(${SPYUS} INTERFACE ${SPYUS_PUBLIC_INCLUDE_DIRS}) + +################################################ +# Export and install target +include(InstallLibrary) +install_library(${SPYUS}) diff --git a/dnacalib/SPyUS/README.md b/dnacalib/SPyUS/README.md new file mode 100644 index 0000000..de32ab5 --- /dev/null +++ b/dnacalib/SPyUS/README.md @@ -0,0 +1,2 @@ +# SPyUs +Swig python utilities diff --git a/dnacalib/SPyUS/include/spyus/ArrayView.i b/dnacalib/SPyUS/include/spyus/ArrayView.i new file mode 100644 index 0000000..4fd72a8 --- /dev/null +++ b/dnacalib/SPyUS/include/spyus/ArrayView.i @@ -0,0 +1,85 @@ +%include + +%include "spyus/Caster.i" + +%define array_view_to_py_list(type_name) +%typemap(out) type_name { + const std::size_t count = $1.size(); + $result = PyList_New(static_cast(count)); + using ValueType = std::remove_cv<$1_basetype::value_type>::type; + + $1_basetype arrayView = $1; + for (std::size_t i = 0ul; i < count; ++i) { + PyList_SetItem($result, static_cast(i), Caster::toPy(arrayView[i])); + } +} +%enddef + +%define string_view_to_py_string(type_name) +%typemap(out) type_name { + %#if PY_VERSION_HEX >= 0x03000000 + $result = PyUnicode_FromStringAndSize($1.c_str(), $1.size()); + %#else + $result = PyString_FromStringAndSize($1.c_str(), $1.size()); + %#endif +} +%enddef + +%define py_list_to_array_view(type_name, typecheck_precedence) +%typemap(in) (type_name) { + if (PyList_Check($input)) { + bool isCorrect = true; + auto item = PyList_GetItem($input, 0); + if (item != NULL) { + if (std::is_floating_point<$1_basetype::value_type>::value) { + isCorrect = PyFloat_Check(item) ? true : false; + } else if (std::is_integral<$1_basetype::value_type>::value) { + isCorrect = PyLong_Check(item) || PyInt_Check(item) ? true : false; + } else { + // For structs, classes, and other object types, just assume true until something breaks + isCorrect = true; + } + } else { + isCorrect = true; + } + if (!isCorrect) { + SWIG_Python_RaiseOrModifyTypeError("wrong element type"); + return 0; + } + using value_type = std::remove_cv<$1_basetype::value_type>::type; + const std::size_t count = static_cast(PyList_Size($input)); + const auto ptr = reinterpret_cast(malloc(count * sizeof(value_type))); + for (std::size_t i{}; i < count; ++i) { + ptr[i] = Caster::fromPy(PyList_GetItem($input, i)); + } + $1 = $1_basetype{ptr, count}; + } else { + SWIG_exception(SWIG_TypeError, "list expected"); + } +} + +%typemap(freearg) (type_name) { + using value_type = std::remove_cv<$1_basetype::value_type>::type; + free(const_cast($1.data())); +} + +%typemap(typecheck, precedence=typecheck_precedence) type_name { + $1 = 0; + if (PyList_Check($input)) { + auto item = PyList_GetItem($input, 0); + if (item != NULL) { + if (std::is_floating_point<$1_basetype::value_type>::value) { + $1 = PyFloat_Check(item) ? 1 : 0; + } else if (std::is_integral<$1_basetype::value_type>::value) { + $1 = PyLong_Check(item) || PyInt_Check(item) ? 1 : 0; + } else { + // For structs, classes, and other object types, just assume true until something breaks + $1 = 1; + } + } else { + $1 = 1; + } + } +} + +%enddef diff --git a/dnacalib/SPyUS/include/spyus/Caster.i b/dnacalib/SPyUS/include/spyus/Caster.i new file mode 100644 index 0000000..6c80ad2 --- /dev/null +++ b/dnacalib/SPyUS/include/spyus/Caster.i @@ -0,0 +1,229 @@ +%{ +#include +%} + +%inline{ + template + struct Caster; + + template<> + struct Caster { + static std::uint16_t fromPy(PyObject* pyObject) { + return static_cast(PyLong_AsLong(pyObject)); + } + + static PyObject* toPy(std::uint16_t value) { + return PyLong_FromLong(static_cast(value)); + } + }; + + template<> + struct Caster { + static std::uint32_t fromPy(PyObject* pyObject) { + return static_cast(PyLong_AsLong(pyObject)); + } + + static PyObject* toPy(std::uint32_t value) { + return PyLong_FromLong(static_cast(value)); + } + }; + + template<> + struct Caster { + static float fromPy(PyObject* pyObject) { + return static_cast(PyFloat_AsDouble(pyObject)); + } + + static PyObject* toPy(float value) { + return PyFloat_FromDouble(static_cast(value)); + } + }; +} + +%include "stdint.i" + +%define py_list_to_c_pointer(arg1) +%typemap(in) (arg1) { + if (PyList_Check($input)) { + const std::size_t count = static_cast(PyList_Size($input)); + $1 = reinterpret_cast<$1_basetype*>(malloc(count * sizeof($1_basetype))); + for (std::size_t i{}; i < count; ++i) { + $1[i] = Caster<$1_basetype>::fromPy(PyList_GetItem($input, i)); + } + } else { + SWIG_fail; + } +} + +%typemap(freearg) (arg1) { + free($1); +} +%enddef + +%define py_list_to_c_array(arg1, arg2) +%typemap(in) (arg1, arg2) { + if (PyList_Check($input)) { + $2 = static_cast<$2_basetype>(PyList_Size($input)); + $1 = reinterpret_cast<$1_basetype*>(malloc($2 * sizeof($1_basetype))); + for ($2_basetype i{}; i < $2; ++i) { + $1[i] = Caster<$1_basetype>::fromPy(PyList_GetItem($input, i)); + } + } else { + SWIG_fail; + } +} + +%typemap(freearg) (arg1, arg2) { + free($1); +} +%enddef + +%define py_list_to_c_matrix(arg1, arg2, arg3) +%typemap(in) (arg1, arg2, arg3) { + if(PyList_Check($input)) { + $3 = static_cast<$3_basetype>(PyList_Size($input)); + $2 = reinterpret_cast<$2_basetype*>(malloc($3 * sizeof($2_basetype))); + + std::size_t totalSize{}; + for ($3_basetype i{}; i < $3; ++i) { + auto pyRow = PyList_GetItem($input, i); + if (PyList_Check(pyRow)) { + $2[i] = static_cast<$2_basetype>(PyObject_Length(pyRow)); + totalSize += static_cast($2[i]); + } else { + SWIG_fail; + } + } + $1 = reinterpret_cast<$1_basetype*>(malloc(totalSize * sizeof($1_basetype))); + std::size_t matrixIndex{}; + for ($3_basetype i{}; i < $3; ++i) { + auto pyRow = PyList_GetItem($input, i); + for ($2_basetype j{}; j < $2[i]; ++j) { + $1[matrixIndex] = Caster<$1_basetype>::fromPy(PyList_GetItem(pyRow, j)); + ++matrixIndex; + } + } + } +} + +%typemap(freearg) (arg1, arg2, arg3) { + free($1); + free($2); +} +%enddef + +%define py_list_to_c_array_complex_type(arg1, arg2) +%typemap(in) (arg1, arg2) { + if (PyList_Check($input)) { + $2 = static_cast<$2_basetype>(PyList_Size($input)); + $1 = reinterpret_cast<$1_ltype>(malloc($2 * sizeof($*1_ltype))); + for ($2_basetype i{}; i < $2; ++i) { + int res = 0; + void *convert_ptr = 0 ; + res = SWIG_ConvertPtr(PyList_GetItem($input, i), &convert_ptr, $*1_descriptor , 0 | 0 ); + if (!SWIG_IsOK(res)) { + SWIG_fail; + } + $1[i] = reinterpret_cast<$*1_ltype>(convert_ptr); + } + } else { + SWIG_fail; + } +} + +%typemap(typecheck /*, precedence=SWIG_TYPECHECK_INTEGER*/) (arg1, arg2) { + $1 = PyList_Check($input) ? 1 : 0; +} + +%typemap(freearg) (arg1, arg2) { + free($1); +} +%enddef + +%include "carrays.i" +%define wrap_array_class(type, name) +%array_functions(type, name); +%pythoncode %{ +class name(object): + def __init__(self, arr=[], nelements=0): + self.nelements = max(len(arr), nelements) + assert self.nelements > -1, "Nelements has to be greater then or equal to zero." + self.this = new_ ## name ## (self.nelements) + for (i, el) in enumerate(arr): + self[i] = el + + def __del__(self): + delete_ ## name ## (self.this) + + def __setitem__(self, index, value): + name ## _setitem(self.this, index, value) + + def __getitem__(self, index): + return name ## _getitem(self.this, index) + + def __iter__(self): + for i in range(self.nelements): + yield self[i] + + def __len__(self): + return self.nelements +%} +%enddef + +%pythoncode %{ +def with_metaclass(meta, *bases): + class metaclass(type): + + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + + @classmethod + def __prepare__(cls, name, this_bases): + return meta.__prepare__(name, bases) + return type.__new__(metaclass, 'temporary_class', (), {}) +%} + +%define pythonize_unmanaged_type(typename, creator, destroyer) +%pythoncode %{ +typename ## Impl = typename + +class typename ## ImplReflectionMixin(type): + + def __getattr__(cls, name): + return getattr(typename ## Impl, name) + + def __dir__(cls): + return [name for name in dir(typename ## Impl) if name not in (#creator, #destroyer)] + +class typename(with_metaclass(typename ## ImplReflectionMixin, object)): + __slots__ = ('_args', '_kwargs', '_instance') + + def __init__(self, *args, **kwargs): + self._args = args + self._kwargs = kwargs + self._instance = typename ## Impl. ## creator(*args, **kwargs) + + def __del__(self): + typename ## Impl. ## destroyer(self._instance) + + def _in_slots(self, attr): + for cls in type(self).__mro__: + if attr in getattr(cls, '__slots__', []): + return True + return False + + def __getattr__(self, attr): + if self._in_slots(attr): + return object.__getattr__(self, attr) + return getattr(self._instance, attr) + + def __setattr__(self, attr, value): + if self._in_slots(attr): + object.__setattr__(self, attr, value) + else: + setattr(self._instance, attr, value) + + def __dir__(self): + return [name for name in self._instance.__dir__() if name not in (#creator, #destroyer)] +%} +%enddef diff --git a/dnacalib/SPyUS/include/spyus/ExceptionHandling.i b/dnacalib/SPyUS/include/spyus/ExceptionHandling.i new file mode 100644 index 0000000..19ace07 --- /dev/null +++ b/dnacalib/SPyUS/include/spyus/ExceptionHandling.i @@ -0,0 +1,10 @@ +%include exception.i +%exception { + try { + $action + } catch (std::exception& e) { + SWIG_exception_fail(SWIG_RuntimeError, e.what()); + } catch (...) { + SWIG_exception_fail(SWIG_RuntimeError, "Unhandled exception occurred"); + } +} diff --git a/dnacalib/SPyUS/include/spyus/TDM.i b/dnacalib/SPyUS/include/spyus/TDM.i new file mode 100644 index 0000000..9ba83ff --- /dev/null +++ b/dnacalib/SPyUS/include/spyus/TDM.i @@ -0,0 +1,54 @@ +/** + * Copyright Epic Games, Inc. All Rights Reserved. + */ + +%include + +%include "spyus/Caster.i" + + +%define tdm_vec_typemap(type_name) +%ignore type_name; + +%inline { + template<> + struct Caster { + + static type_name fromPy(PyObject* listObject) { + type_name cVec; + for (std::size_t i = 0; i < type_name::dimensions(); i++) { + cVec[i] = Caster::fromPy(PyList_GetItem(listObject, i)); + } + return cVec; + } + + static PyObject* toPy(type_name cVec) { + std::size_t length = type_name::dimensions(); + PyObject* pyVec = PyList_New(static_cast(length)); + for (std::size_t i = 0; i < length; i++) { + PyList_SetItem(pyVec, static_cast(i), Caster::toPy(cVec[i])); + } + return pyVec; + } + + }; +} + +%typemap(in) (type_name) { + if (PyList_Check($input)) { + $1 = Caster::fromPy($input); + } else { + SWIG_exception(SWIG_TypeError, "list expected"); + } +}; + +%typemap(out) type_name { + $result = Caster<$1_basetype>::toPy($1); +} + +%typemap(typecheck, precedence=SWIG_TYPECHECK_BOOL_ARRAY) type_name { + $1 = PyList_Check($input) ? 1 : 0; +} + +%enddef + diff --git a/dnacalib/SPyUS/include/spyus/Vector3.i b/dnacalib/SPyUS/include/spyus/Vector3.i new file mode 100644 index 0000000..729ad14 --- /dev/null +++ b/dnacalib/SPyUS/include/spyus/Vector3.i @@ -0,0 +1,47 @@ +%include + +%include "spyus/Caster.i" + +%define vector3_typemap(type_name) +%ignore type_name; + +%inline { + template<> + struct Caster { + + static type_name fromPy(PyObject* listObject) { + return type_name{ + Caster::fromPy(PyList_GetItem(listObject, 0)), + Caster::fromPy(PyList_GetItem(listObject, 1)), + Caster::fromPy(PyList_GetItem(listObject, 2)) + }; + } + + static PyObject* toPy(type_name vec3) { + PyObject* pyVec3 = PyList_New(static_cast(3)); + PyList_SetItem(pyVec3, static_cast(0), Caster::toPy(vec3.x)); + PyList_SetItem(pyVec3, static_cast(1), Caster::toPy(vec3.y)); + PyList_SetItem(pyVec3, static_cast(2), Caster::toPy(vec3.z)); + return pyVec3; + } + + }; +} + +%typemap(in) (type_name) { + if (PyList_Check($input)) { + $1 = Caster::fromPy($input); + } else { + SWIG_exception(SWIG_TypeError, "list expected"); + } +}; + +%typemap(out) type_name { + $result = Caster<$1_basetype>::toPy($1); +} + +%typemap(typecheck, precedence=SWIG_TYPECHECK_FLOAT_ARRAY) type_name { + $1 = PyList_Check($input) ? 1 : 0; +} + +%enddef diff --git a/resources/styles/style.qss b/resources/styles/style.qss index a09204f..ff9cc48 100644 --- a/resources/styles/style.qss +++ b/resources/styles/style.qss @@ -1,6 +1,6 @@ /* 全局 QPushButton 样式 */ QPushButton { - background-color: #333333; + background-color: #2A2A2A; color: #CCCCCC; border-radius: 3px; padding: 5px; @@ -10,17 +10,17 @@ QPushButton { } QPushButton:hover { - background-color: #444444; + background-color: #3A3A3A; border-color: #555555; } QPushButton:pressed { - background-color: #222222; + background-color: #1A1A1A; border-color: #333333; } QPushButton:disabled { - background-color: #222222; + background-color: #1A1A1A; border-color: #333333; color: #666666; } @@ -79,7 +79,7 @@ QToolBar { } QToolButton { - background-color: #333333; + background-color: #2A2A2A; border: 1px solid #444444; color: #CCCCCC; padding: 5px; @@ -88,17 +88,17 @@ QToolButton { } QToolButton:hover { - background-color: #444444; + background-color: #3A3A3A; border-color: #555555; } QToolButton:pressed { - background-color: #222222; + background-color: #1A1A1A; border-color: #333333; } QToolButton:disabled { - background-color: #222222; + background-color: #1A1A1A; border-color: #333333; color: #666666; } @@ -175,31 +175,6 @@ QComboBox::down-arrow { height: 12px; } -/* 按钮样式 */ -QPushButton { - background-color: #333333; - border: 1px solid #444444; - color: #CCCCCC; - padding: 5px; - border-radius: 3px; -} - -QPushButton:hover { - background-color: #444444; - border-color: #555555; -} - -QPushButton:pressed { - background-color: #222222; - border-color: #333333; -} - -QPushButton:disabled { - background-color: #222222; - border-color: #333333; - color: #666666; -} - /* 滚动条样式 */ QScrollBar:vertical { background: #2A2A2A; diff --git a/scripts/MetaFusion.py b/scripts/MetaFusion.py index 992ab21..95d394a 100644 --- a/scripts/MetaFusion.py +++ b/scripts/MetaFusion.py @@ -3,7 +3,10 @@ import os import sys -from PySide2 import QtCore, QtGui, QtWidgets +import maya.cmds as cmds +import maya.OpenMayaUI as omui +from shiboken2 import wrapInstance +import traceback # 添加项目根目录到 Python 路径 ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) @@ -28,20 +31,34 @@ TOOL_ICON = data.TOOL_ICON TOOL_COMMAND_ICON = data.TOOL_COMMAND_ICON DNA_PATH = data.DNA_PATH DNA_IMG_PATH = data.DNA_IMG_PATH +DNALIB_PATH = data.DNALIB_PATH +BUILDER_PATH = data.BUILDER_PATH main_window = None class MetaFusionWindow(QtWidgets.QMainWindow): def __init__(self, parent=None): - super(MetaFusionWindow, self).__init__(parent) - self.setWindowTitle("MetaFusion") - self.resize(800, 600) - - # 加载样式表 - self.load_stylesheet() - - # 创建UI - self.setup_ui() + try: + super(MetaFusionWindow, self).__init__(parent) + self.setWindowTitle("MetaFusion") + self.resize(800, 600) + + # 设置窗口标志 + if parent is None: + # 独立窗口模式 + self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) + else: + # 嵌入模式 + self.setWindowFlags(QtCore.Qt.Widget) + + # 加载样式表 + self.load_stylesheet() + + # 创建UI + self.setup_ui() + except Exception as e: + cmds.warning(f"窗口初始化失败: {str(e)}") + self.safe_shutdown() def load_stylesheet(self): """加载QSS样式表""" @@ -74,55 +91,62 @@ class MetaFusionWindow(QtWidgets.QMainWindow): # 文件菜单 file_menu = menubar.addMenu("文件") - file_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "open.png")), "打开DNA") - file_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "save.png")), "保存DNA") - file_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "open.png")), "加载当前项目的DNA") - file_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "rename.png")), "修改混合目标名称") - file_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "resetname.png")), "重置混合目标名称") - file_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "export.png")), "导出FBX") + self.add_menu_action(file_menu, "打开DNA", "open.png", self.on_open_dna) + self.add_menu_action(file_menu, "保存DNA", "save.png", self.on_save_dna) + self.add_menu_action(file_menu, "加载当前项目的DNA", "open.png", self.on_load_project_dna) + self.add_menu_action(file_menu, "修改混合目标名称", "rename.png", self.on_rename_blend_target) + self.add_menu_action(file_menu, "重置混合目标名称", "resetname.png", self.on_reset_blend_target) + self.add_menu_action(file_menu, "导出FBX", "export.png", self.on_export_fbx) file_menu.addSeparator() - file_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "exit.png")), "退出") + self.add_menu_action(file_menu, "退出", "exit.png", self.close) # 编辑菜单 edit_menu = menubar.addMenu("编辑") - edit_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "connect.png")), "创建RL4节点") - edit_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "disconnect.png")), "删除RL4节点") - edit_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "mirrorL.png")), "镜像左至右") - edit_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "mirrorR.png")), "镜像右至左") - edit_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "pose_A_To_T.png")), "姿势由A型转T型") - edit_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "pose_T_To_A.png")), "姿势由T型转A型") - edit_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "locator.png")), "传输LOD贴图") - edit_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "color.png")), "设置关节颜色") - edit_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "unmark_all.png")), "取消全部标记") - edit_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "rebuildTargets.png")), "重建所有目标") - edit_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "bakeAnimation.png")), "为所有表情设置关键帧") - edit_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "centerCurrentTime.png")), "烘焙所有表情的关键帧") + self.add_menu_action(edit_menu, "创建RL4节点", "connect.png", self.create_rl4_node) + self.add_menu_action(edit_menu, "删除RL4节点", "disconnect.png", self.delete_rl4_node) + self.add_menu_action(edit_menu, "镜像左至右", "mirrorL.png", self.mirror_left_to_right) + self.add_menu_action(edit_menu, "镜像右至左", "mirrorR.png", self.mirror_right_to_left) + self.add_menu_action(edit_menu, "姿势由A型转T型", "pose_A_To_T.png", self.pose_A_to_T) + self.add_menu_action(edit_menu, "姿势由T型转A型", "pose_T_To_A.png", self.pose_T_to_A) + self.add_menu_action(edit_menu, "传输LOD贴图", "locator.png", self.transfer_lod_texture) + self.add_menu_action(edit_menu, "设置关节颜色", "color.png", self.set_joint_color) + self.add_menu_action(edit_menu, "取消全部标记", "unmark_all.png", self.unmark_all) + self.add_menu_action(edit_menu, "重建所有目标", "rebuildTargets.png", self.rebuild_targets) + self.add_menu_action(edit_menu, "为所有表情设置关键帧", "bakeAnimation.png", self.bake_all_animations) + self.add_menu_action(edit_menu, "烘焙所有表情的关键帧", "centerCurrentTime.png", self.bake_all_keyframes) # 工具菜单 tool_menu = menubar.addMenu("工具") - tool_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "export_skin.png")), "导出蒙皮") - tool_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "import_skin.png")), "导入蒙皮") - tool_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "copy_skin.png")), "拷贝装皮") - tool_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "blendShape.png")), "RBF变形器") - tool_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "clothing_weight.png")), "快速绑定服装") - tool_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "blendShape.png")), "克隆混合变形") - tool_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "repair_vertex_order.png")), "UV传递点序") - tool_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "controller.png")), "面部生成控制器") - tool_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "ARKit52.png")), "提取52BS") - tool_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "joint.png")), "关节轴向修复") - tool_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "create_body_ctrl.png")), "生成身体控制器") - tool_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "import_face_anim.png")), "导入面部动画") - tool_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "import_body_anim.png")), "导入身体动画") + self.add_menu_action(tool_menu, "导出蒙皮", "export_skin.png", self.export_skin) + self.add_menu_action(tool_menu, "导入蒙皮", "import_skin.png", self.import_skin) + self.add_menu_action(tool_menu, "拷贝装皮", "copy_skin.png", self.copy_skin) + self.add_menu_action(tool_menu, "RBF变形器", "blendShape.png", self.create_blend_shape) + self.add_menu_action(tool_menu, "快速绑定服装", "clothing_weight.png", self.quick_bind_clothing) + self.add_menu_action(tool_menu, "克隆混合变形", "blendShape.png", self.clone_blend_shape) + self.add_menu_action(tool_menu, "UV传递点序", "repair_vertex_order.png", self.repair_vertex_order) + self.add_menu_action(tool_menu, "面部生成控制器", "controller.png", self.create_face_controller) + self.add_menu_action(tool_menu, "提取52BS", "ARKit52.png", self.extract_52BS) + self.add_menu_action(tool_menu, "关节轴向修复", "joint.png", self.repair_joint_axis) + self.add_menu_action(tool_menu, "生成身体控制器", "create_body_ctrl.png", self.create_body_controller) + self.add_menu_action(tool_menu, "导入面部动画", "import_face_anim.png", self.import_face_animation) + self.add_menu_action(tool_menu, "导入身体动画", "import_body_anim.png", self.import_body_animation) # 语言菜单 lang_menu = menubar.addMenu("语言") - lang_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "chinese.png")), "中文") - lang_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "english.png")), "English") + self.add_menu_action(lang_menu, "中文", "chinese.png", self.set_chinese) + self.add_menu_action(lang_menu, "English", "english.png", self.set_english) # 帮助菜单 help_menu = menubar.addMenu("帮助") - help_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "help.png")), "帮助文档") - help_menu.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "warning.png")), "关于") + self.add_menu_action(help_menu, "帮助文档", "help.png", self.show_help_document) + self.add_menu_action(help_menu, "关于", "warning.png", self.show_about_dialog) + + def add_menu_action(self, menu, text, icon, callback): + """通用菜单项添加方法""" + action = QtWidgets.QAction(QtGui.QIcon(os.path.join(ICONS_PATH, icon)), text, self) + action.triggered.connect(callback) + menu.addAction(action) + return action def create_tool_bar(self): """创建工具栏""" @@ -256,8 +280,6 @@ class MetaFusionWindow(QtWidgets.QMainWindow): self.dna_browser.setIconSize(QtCore.QSize(size, size)) def on_import_settings(self): - """导入设置""" - # TODO: 实现导入设置功能 pass def on_export_settings(self): @@ -316,56 +338,139 @@ class MetaFusionWindow(QtWidgets.QMainWindow): self.dna_manager = DNAManager() def on_load_dna(self): - """处理载入DNA事件""" - file_path, _ = QtWidgets.QFileDialog.getOpenFileName( - self, "选择DNA文件", "", "DNA Files (*.dna)") - - if file_path: - if self.dna_manager.load_dna(file_path): - self.dna_edit.setText(f"已加载DNA文件: {file_path}") - else: - QtWidgets.QMessageBox.warning( - self, "错误", "DNA文件加载失败") + pass def on_save_dna(self): - """处理保存DNA事件""" - file_path, _ = QtWidgets.QFileDialog.getSaveFileName( - self, "保存DNA文件", "", "DNA Files (*.dna)") - - if file_path: - if self.dna_manager.save_dna(file_path): - QtWidgets.QMessageBox.information( - self, "成功", "DNA文件保存成功") - else: - QtWidgets.QMessageBox.warning( - self, "错误", "DNA文件保存失败") + pass def on_export_fbx(self): - """处理导出FBX事件""" - file_path, _ = QtWidgets.QFileDialog.getSaveFileName( - self, "导出FBX", "", "FBX Files (*.fbx)") + pass + + def on_open_dna(self): + pass + + def on_load_project_dna(self): + pass + + def on_rename_blend_target(self): + pass + + def on_reset_blend_target(self): + pass + + def create_rl4_node(self): + pass + + def delete_rl4_node(self): + pass + + def mirror_left_to_right(self): + pass + + def mirror_right_to_left(self): + pass + + def pose_A_to_T(self): + pass + + def pose_T_to_A(self): + pass + + def transfer_lod_texture(self): + pass + + def set_joint_color(self): + pass + + def unmark_all(self): + pass + + def rebuild_targets(self): + pass + + def bake_all_animations(self): + pass + + def bake_all_keyframes(self): + pass + + def safe_shutdown(self): + pass + + +# ===================================== 显示主窗口 ===================================== +def get_maya_window(): + """获取 Maya 主窗口""" + import maya.OpenMayaUI as omui + from shiboken2 import wrapInstance + + maya_main_window_ptr = omui.MQtUtil.mainWindow() + return wrapInstance(int(maya_main_window_ptr), QtWidgets.QWidget) + +def dock_to_maya(): + """将窗口嵌入到 Maya 的 Dock 面板""" + try: + # 先清理可能存在的旧控件 + if cmds.workspaceControl("MetaFusionDock", exists=True): + cmds.deleteUI("MetaFusionDock") - if file_path: - if self.dna_manager.export_fbx(file_path): - QtWidgets.QMessageBox.information( - self, "成功", "FBX导出成功") - else: - QtWidgets.QMessageBox.warning( - self, "错误", "FBX导出失败") + # 创建新的Dock控件 + dock_control = cmds.workspaceControl( + "MetaFusionDock", + label=TOOL_NAME, + tabToControl=["AttributeEditor", -1], + initialWidth=1400, + minimumWidth=1000, + minimumHeight=800, + widthProperty="free", + heightProperty="free" + ) + + # 获取Dock控件指针 + maya_dock_ptr = omui.MQtUtil.findControl(dock_control) + if not maya_dock_ptr: + raise RuntimeError("无法获取Dock控件指针") + + # 获取Maya主窗口并创建实例 + maya_main_window = get_maya_window() + main_window = MetaFusionWindow(parent=maya_main_window) + + # 嵌入到Dock + maya_dock_widget = wrapInstance(int(maya_dock_ptr), QtWidgets.QWidget) + maya_dock_widget.layout().addWidget(main_window) + + return main_window + + except Exception as e: + error_msg = f"Dock嵌入失败: {str(e)}\n{''.join(traceback.format_exc())}" + cmds.warning(error_msg) + return None def show(): """显示主窗口""" global main_window try: - main_window.close() + # 清理旧实例 + if main_window: + main_window.close() + cmds.deleteUI("MetaFusionDock") except: pass - - main_window = MetaFusionWindow() - main_window.show() + + # 尝试嵌入 Dock + main_window = dock_to_maya() + + # 备用方案:独立窗口模式 + if not main_window: + cmds.warning("Dock 模式失败,使用独立窗口模式") + main_window = MetaFusionWindow() + main_window.show() + return main_window +# ===================================== 主函数 ===================================== + if __name__ == "__main__": app = QtWidgets.QApplication([]) window = show() diff --git a/scripts/builder/__init__.py b/scripts/builder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/builder/builder.py b/scripts/builder/builder.py new file mode 100644 index 0000000..8fd5be6 --- /dev/null +++ b/scripts/builder/builder.py @@ -0,0 +1,433 @@ +import logging +import traceback +from dataclasses import dataclass, field +from pathlib import Path +from typing import Dict, List, Optional + +from maya import cmds, mel + +from ..builder.maya.util import Maya +from ..common import DNAViewerError +from ..dnalib.dnalib import DNA +from ..model import Joint as JointModel +from .config import AngleUnit, Config, LinearUnit +from .joint import Joint as JointBuilder +from .mesh import Mesh + + +@dataclass +class BuildResult: + """ + A class used for returning data after finishing the build process + + Attributes + ---------- + @type meshes_per_lod: Dict[int, List[str]] + @param meshes_per_lod: The list of mesh names created group by LOD number + """ + + meshes_per_lod: Dict[int, List[str]] = field(default_factory=dict) + + def get_all_meshes(self) -> List[str]: + """ + Flatten meshes to single list. + + @rtype: List[str] + @returns: The list of all mesh names. + """ + + all_meshes = [] + for meshes_per_lod in self.meshes_per_lod.values(): + all_meshes.extend(meshes_per_lod) + return all_meshes + + +class Builder: + """ + A builder class used for building the character + + Attributes + ---------- + @type config: Config + @param config: The configuration options used for building the character + + @type dna: DNA + @param dna: The DNA object read from the DNA file + + @type meshes: Dict[int, List[str]] + @param meshes: A list of meshes created grouped by lod + + """ + + def __init__(self, dna: DNA, config: Optional[Config] = None) -> None: + self.config = config or Config() + self.dna = dna + self.meshes: Dict[int, List[str]] = {} + self.all_loaded_meshes: List[int] = [] + + def _build(self) -> bool: + self.new_scene() + self.set_filtered_meshes() + if not self.all_loaded_meshes: + logging.error("No mashes has been loaded.") + return False + + self.create_groups() + + self.set_units() + self.add_joints() + self.build_meshes() + self.add_ctrl_attributes_on_root_joint() + self.add_animated_map_attributes_on_root_joint() + self.add_key_frames() + return True + + def build(self) -> BuildResult: + """Builds the character""" + self.meshes = {} + try: + filename = Path(self.dna.path).stem + logging.info("******************************") + logging.info(f"{filename} started building") + logging.info("******************************") + + self._build() + + logging.info(f"{filename} built successfully!") + + except DNAViewerError as e: + traceback.print_exc() + raise e + except Exception as e: + traceback.print_exc() + logging.error(f"Unhandled exception, {e}") + raise DNAViewerError(f"Scene creation failed! Reason: {e}") from e + return BuildResult(meshes_per_lod=self.meshes) + + def new_scene(self) -> None: + cmds.file(new=True, force=True) + + def add_mesh_to_display_layer(self, mesh_name: str, lod: int) -> None: + """ + Add the mesh with the given name to an already created display layer. + + @type mesh_name: str + @param mesh_name: The name of the mesh that should be added to a display layer. + + @type lod: int + @param lod: The lod value, this is needed for determining the name of the display layer that the mesh should be added to. + """ + if self.config.create_display_layers: + cmds.editDisplayLayerMembers( + f"{self.config.top_level_group}_lod{lod}_layer", mesh_name + ) + + def _add_joints(self) -> List[JointModel]: + """ + Reads and adds the joints to the scene, also returns a list model objects of joints that were added. + + @rtype: List[JointModel] + @returns: The list containing model objects representing the joints that were added to the scene. + """ + + joints: List[JointModel] = self.dna.read_all_neutral_joints() + builder = JointBuilder( + joints, + ) + builder.process() + return joints + + def add_joints(self) -> None: + """ + Starts adding the joints the character, if the character configuration options have add_joints set to False, + this step will be skipped. + """ + + if self.config.add_joints: + logging.info("adding joints to character...") + joints = self._add_joints() + + if self.config.group_by_lod and joints: + cmds.parent(joints[0].name, self.config.get_top_level_group()) + + def create_groups(self) -> None: + """ + Creates a Maya transform which will hold the character, if the character configuration options have + create_character_node set to False, this step will be skipped. + """ + + if self.config.group_by_lod: + logging.info("building character node...") + cmds.group(world=True, empty=True, name=self.config.get_top_level_group()) + cmds.group( + parent=self.config.get_top_level_group(), + empty=True, + name=self.config.get_geometry_group(), + ) + cmds.group( + parent=self.config.get_top_level_group(), + empty=True, + name=self.config.get_rig_group(), + ) + for lod in self.get_display_layers(): + name = f"{self.config.top_level_group}_lod{lod}_layer" + if not cmds.objExists(name): + if self.config.group_by_lod: + cmds.group( + parent=self.config.get_geometry_group(), + empty=True, + name=f"{self.config.top_level_group}_lod{lod}_grp", + ) + cmds.select( + f"{self.config.top_level_group}_lod{lod}_grp", + replace=True, + ) + if self.config.create_display_layers: + cmds.createDisplayLayer(name=name, noRecurse=True) + + def attach_mesh_to_lod(self, mesh_name: str, lod: int) -> None: + """ + Attaches the mesh called mesh_name to a given lod. + + @type mesh_name: str + @param mesh_name: The mesh that needs to be attached to a lod holder object. + + @type lod: str + @param lod: The name of the mesh that should be added to a display layer. + """ + if self.config.group_by_lod: + parent_node = f"{self.config.get_top_level_group()}|{self.config.get_geometry_group()}|{self.config.top_level_group}_lod{lod}_grp" + cmds.parent( + self.get_mesh_node_fullpath_on_root(mesh_name=mesh_name), parent_node + ) + + def get_mesh_node_fullpath_on_root(self, mesh_name: str) -> str: + """ + Gets the full path in the scene of a mesh. + + @type mesh_name: str + @param mesh_name: The mesh thats path is needed. + + @rtype: str + @returns: The full path of the mesh object in the scene + """ + + return str(Maya.get_element(f"|{mesh_name}").fullPathName()) + + def add_ctrl_attributes_on_root_joint(self) -> None: + """ + Adds and sets the raw gui control attributes on root joint. + """ + + if self.config.add_ctrl_attributes_on_root_joint and self.config.add_joints: + gui_control_names = self.dna.get_raw_control_names() + for name in gui_control_names: + ctrl_and_attr_names = name.split(".") + self.add_attribute( + control_name=self.config.facial_root_joint_name, + long_name=ctrl_and_attr_names[1], + ) + + def add_animated_map_attributes_on_root_joint(self) -> None: + """ + Adds and sets the animated map attributes on root joint. + """ + + if ( + self.config.add_animated_map_attributes_on_root_joint + and self.config.add_joints + ): + names = self.dna.get_animated_map_names() + for name in names: + long_name = name.replace(".", "_") + self.add_attribute( + control_name=self.config.facial_root_joint_name, long_name=long_name + ) + + def add_attribute(self, control_name: str, long_name: str) -> None: + """ + Adds attributes wrapper for internal usage. + """ + cmds.addAttr( + control_name, + longName=long_name, + keyable=True, + attributeType="float", + minValue=0.0, + maxValue=1.0, + ) + + def add_key_frames(self) -> None: + """ + Adds a starting key frame to the facial root joint if joints are added and the add_key_frames option is set + to True. + """ + + if self.config.add_key_frames and self.config.add_joints: + logging.info("setting keyframe on the root joint...") + cmds.currentTime(0) + if cmds.objExists(self.config.facial_root_joint_name): + cmds.select(self.config.facial_root_joint_name, replace=True) + cmds.setKeyframe(inTangentType="linear", outTangentType="linear") + + def set_filtered_meshes(self) -> None: + self.all_loaded_meshes = self.get_filtered_meshes() + + def get_mesh_indices_filter(self) -> List[int]: + indices = [] + for index in range(self.dna.get_mesh_count()): + mesh_name = self.dna.get_mesh_name(index) + for cur_filter in self.config.mesh_filter: + if cur_filter in mesh_name: + indices.append(index) + return indices + + def get_filtered_meshes(self) -> List[int]: + if not self.config.mesh_filter and not self.config.lod_filter: + if self.config.meshes: + return self.config.meshes + return list(range(self.dna.get_mesh_count())) + + meshes: List[int] = [] + meshes_by_lod = self.dna.get_all_meshes_grouped_by_lod() + all_meshes = [mesh_index for meshes in meshes_by_lod for mesh_index in meshes] + mesh_indices_filter = self.get_mesh_indices_filter() + + if self.config.lod_filter: + for lod in self.config.lod_filter: + if 0 <= lod < len(meshes_by_lod): + meshes.extend(meshes_by_lod[lod]) + if mesh_indices_filter: + return list(set(meshes) & set(mesh_indices_filter)) + return meshes + if self.config.mesh_filter: + return list(set(all_meshes) & set(mesh_indices_filter)) + return all_meshes + + def build_meshes(self) -> None: + """ + Builds the meshes. If specified in the config they get parented to a created + character node transform, otherwise the meshes get put to the root level of the scene. + """ + + logging.info("adding character meshes...") + self.meshes = {} + for lod, meshes_per_lod in enumerate( + self.dna.get_meshes_by_lods(self.all_loaded_meshes) + ): + self.meshes[lod] = self.build_meshes_by_lod( + lod=lod, meshes_per_lod=meshes_per_lod + ) + + def build_meshes_by_lod(self, lod: int, meshes_per_lod: List[int]) -> List[str]: + """ + Builds the meshes from the provided mesh ids and then attaches them to a given lod if specified in the + character configuration. + + @type lod: int + @param lod: The lod number representing the display layer the meshes to the display layer. + + @type meshes_per_lod: List[int] + @param meshes_per_lod: List of mesh indices that are being built. + + @rtype: List[MObject] + @returns: The list of maya objects that represent the meshes added to the scene. + """ + + meshes: List[str] = [] + for mesh_index in meshes_per_lod: + builder = Mesh( + config=self.config, + dna=self.dna, + mesh_index=mesh_index, + ) + builder.build() + + mesh_name = self.dna.get_mesh_name(index=mesh_index) + meshes.append(mesh_name) + + self.add_mesh_to_display_layer(mesh_name, lod) + self.attach_mesh_to_lod(mesh_name, lod) + self.default_lambert_shader(mesh_name) + return meshes + + def default_lambert_shader(self, mesh_name: str) -> None: + try: + if self.config.group_by_lod: + names = cmds.ls(f"*|{mesh_name}", l=True) + for item in names: + if item.startswith(f"|{self.config.get_top_level_group()}"): + cmds.select(item, r=True) + break + else: + cmds.select(mesh_name, r=True) + + mel.eval("sets -e -forceElement initialShadingGroup") + + except Exception as e: + logging.error( + f"Couldn't set lambert shader for mesh {mesh_name}. Reason: {e}" + ) + raise DNAViewerError(e) from e + + def set_units(self) -> None: + """Sets the translation and rotation units of the scene from @config""" + + linear_unit = self.get_linear_unit() + angle_unit = self.get_angle_unit() + + cmds.currentUnit(linear=linear_unit.name, angle=angle_unit.name) + + def get_linear_unit(self) -> LinearUnit: + return self.get_linear_unit_from_int(self.dna.get_translation_unit()) + + def get_angle_unit(self) -> AngleUnit: + return self.get_angle_unit_from_int(self.dna.get_rotation_unit()) + + def get_linear_unit_from_int(self, value: int) -> LinearUnit: + """ + Returns an enum from an int value. + 0 -> cm + 1 -> m + + @type value: int + @param value: The value that the enum is mapped to. + + @rtype: LinearUnit + @returns: LinearUnit.cm or LinearUnit.m + """ + + if value == 0: + return LinearUnit.cm + if value == 1: + return LinearUnit.m + raise DNAViewerError(f"Unknown linear unit set in DNA file! value {value}") + + def get_angle_unit_from_int(self, value: int) -> AngleUnit: + """ + Returns an enum from an int value. + 0 -> degree + 1 -> radian + + @type value: int + @param value: The value that the enum is mapped to. + + @rtype: AngleUnit + @returns: AngleUnit.degree or AngleUnit.radian + """ + + if value == 0: + return AngleUnit.degree + if value == 1: + return AngleUnit.radian + raise DNAViewerError(f"Unknown angle unit set in DNA file! value {value}") + + def get_display_layers(self) -> List[int]: + """Gets a lod id list that need to be created for the meshes from @config""" + meshes: List[int] = [] + for idx, meshes_per_lod in enumerate( + self.dna.get_meshes_by_lods(self.all_loaded_meshes) + ): + if meshes_per_lod: + meshes.append(idx) + return list(set(meshes)) diff --git a/scripts/builder/config.py b/scripts/builder/config.py new file mode 100644 index 0000000..f142337 --- /dev/null +++ b/scripts/builder/config.py @@ -0,0 +1,257 @@ +from dataclasses import dataclass, field +from enum import Enum +from typing import Any, Dict, List, Optional + + +class LinearUnit(Enum): + """ + An enum used to represent the unit used for linear representation. + + Attributes + ---------- + @cm: using cm as unit + @m: using m as unit + """ + + cm = 0 + m = 1 + + +class AngleUnit(Enum): + """ + An enum used to represent the unit used for angle representation. + + Attributes + ---------- + @degree: using degree as unit + @radian: using radian as unit + """ + + degree = 0 + radian = 1 + + +@dataclass +class Config: + """ + A class used to represent the config for @Builder + + Attributes + ---------- + @type mesh_filter: List[str] + @param mesh_filter: List of mesh names that should be filtered. Mash names can be just substrings. ["head"] will find all meshes that contins string "head" in its mash name. + + + @type lod_filter: List[int] + @param lod_filter: List of lods that should be filtered. + + @type group_by_lod: bool + @param group_by_lod: A flag representing whether the character should be parented to a character transform node in the scene hierarchy + + @type group_by_lod: bool + @param group_by_lod: A flag representing whether the character should be parented to a character transform node in rig hierarchy + + @type top_level_group: str + @param top_level_group: Value that is going to be used when creating root group + + @type geometry_group: str + @param geometry_group: Value that is going to be used when creating group that contains geometry + + @type facial_root_joint_name: str + @param facial_root_joint_name: The name of the facial root joint + + @type blend_shape_group_prefix: str + @param blend_shape_group_prefix: prefix string for blend shape group + + @type blend_shape_name_postfix: str + @param blend_shape_name_postfix: postfix string for blend shape name + + @type skin_cluster_suffix: str + @param skin_cluster_suffix: postfix string for skin cluster name + + @type animated_map_attribute_multipliers_name: str + @param animated_map_attribute_multipliers_name: string for frame animated map attribute name + + @type create_display_layers: bool + @param create_display_layers: A flag representing whether the created meshes should be assigned to a display layer + + @type add_joints: bool + @param add_joints: A flag representing whether joints should be added + + @type add_blend_shapes: bool + @param add_blend_shapes: A flag representing whether blend shapes should be added + + @type add_skin_cluster: bool + @param add_skin_cluster: A flag representing whether skin should be added + + @type add_ctrl_attributes_on_root_joint: bool + @param add_ctrl_attributes_on_root_joint: A flag representing whether control attributes should be added to the root joint + + @type add_animated_map_attributes_on_root_joint: bool + @param add_animated_map_attributes_on_root_joint: A flag representing whether animated map attributes should be added to the root joint + + @type add_key_frames: bool + @param add_key_frames: A flag representing whether key frames should be added + + @type add_mesh_name_to_blend_shape_channel_name: bool + @param add_mesh_name_to_blend_shape_channel_name: A flag representing whether mesh name of blend shape channel is added to name when creating it + """ + + meshes: List[int] = field(default_factory=list) + mesh_filter: List[str] = field(default_factory=list) + lod_filter: List[int] = field(default_factory=list) + + group_by_lod: bool = field(default=True) + top_level_group: str = "head" + geometry_group: str = "geometry" + + facial_root_joint_name: str = "FACIAL_C_FacialRoot" + + blend_shape_group_prefix: str = "BlendshapeGroup_" + blend_shape_name_postfix: str = "_blendShapes" + skin_cluster_suffix: str = "skinCluster" + + animated_map_attribute_multipliers_name = "FRM_WMmultipliers" + + create_display_layers: bool = field(default=True) + + add_joints: bool = field(default=True) + add_blend_shapes: bool = field(default=True) + add_skin_cluster: bool = field(default=True) + add_ctrl_attributes_on_root_joint: bool = field(default=True) + add_animated_map_attributes_on_root_joint: bool = field(default=True) + add_key_frames: bool = field(default=True) + add_mesh_name_to_blend_shape_channel_name: bool = field(default=True) + + def get_top_level_group(self) -> str: + return f"{self.top_level_group}_grp" + + def get_geometry_group(self) -> str: + return f"{self.geometry_group}_grp" + + def get_rig_group(self) -> str: + return f"{self.top_level_group}Rig_grp" + + +@dataclass +class RigConfig(Config): + """ + A class used to represent the config for @RigBuilder + + + @type add_rig_logic: bool + @param add_rig_logic: A flag representing whether normals should be added + + @type rig_logic_command: str + @param rig_logic_command: The command used to start creating the rig logic using the plugin + + @type rig_logic_name: str + @param rig_logic_name: The name of the rig logic node + + @type control_naming: str + @param control_naming: The naming pattern of controls + + @type joint_naming: str + @param joint_naming: The naming pattern of joints + + @type blend_shape_naming: str + @param blend_shape_naming: The naming pattern of blend shapes + + @type animated_map_naming: str + @param animated_map_naming: The naming pattern of animated maps + + @type gui_path: str + @param gui_path: The location of the gui file + + @type left_eye_joint_name: str + @param left_eye_joint_name: The name of the left eye joint + + @type eye_gui_name: str + @param eye_gui_name: The name of the control in the gui + + @type gui_translate_x: float + @param gui_translate_x: Represents the value that the gui should be additionally translated on the X axis + + @type analog_gui_path: str + @param analog_gui_path: The location of the analog gui file + + @type left_eye_joint_name: str + @param left_eye_joint_name: The name of the left eye joint + + @type right_eye_joint_name: str + @param right_eye_joint_name: The name of the right eye joint + + @type central_driver_name: str + @param central_driver_name: The name of the central driver + + @type left_eye_driver_name: str + @param left_eye_driver_name: The name of the left eye driver + + @type right_eye_driver_name: str + @param right_eye_driver_name: The name of the right eye driver + + @type central_aim: str + @param central_aim: The name of the central aim + + @type le_aim: str + @param le_aim: The name of the left eye aim + + @type re_aim: str + @param re_aim: The name of the right eye aim + + @type aas_path: Optional[str] + @param aas_path: The location of the script file + + @type aas_method: str + @param aas_method: The method that should be called + + @type aas_parameter: Dict[Any, Any] + @param aas_parameter: The parameters that will be passed as the method arguments + + """ + + add_rig_logic: bool = field(default=True) + rig_logic_command: str = field(default="createEmbeddedNodeRL4") + rig_logic_name: str = field(default="") + control_naming: str = field(default=".") + joint_naming: str = field(default=".") + blend_shape_naming: str = field(default="") + animated_map_naming: str = field(default="") + gui_path: str = field(default=None) + + eye_gui_name: str = "CTRL_C_eye" + gui_translate_x: float = 10 + + analog_gui_path: str = field(default=None) + + left_eye_joint_name: str = "FACIAL_L_Eye" + right_eye_joint_name: str = "FACIAL_R_Eye" + + central_driver_name: str = "LOC_C_eyeDriver" + left_eye_driver_name: str = "LOC_L_eyeDriver" + right_eye_driver_name: str = "LOC_R_eyeDriver" + + left_eye_aim_up_name: str = "LOC_L_eyeAimUp" + right_eye_aim_up_name: str = "LOC_R_eyeAimUp" + central_aim: str = "GRP_C_eyesAim" + + le_aim: str = "GRP_L_eyeAim" + re_aim: str = "GRP_R_eyeAim" + + aas_path: Optional[str] = field(default=None) + aas_method: str = "run_after_assemble" + aas_parameter: Dict[Any, Any] = field(default_factory=dict) + + def __post_init__(self) -> None: + if self.add_mesh_name_to_blend_shape_channel_name: + self.blend_shape_naming = ( + f"{self.blend_shape_name_postfix}.__" + ) + else: + self.blend_shape_naming = ( + f"{self.blend_shape_name_postfix}." + ) + + self.animated_map_naming = ( + f"{self.animated_map_attribute_multipliers_name}._" + ) diff --git a/scripts/builder/joint.py b/scripts/builder/joint.py new file mode 100644 index 0000000..d0e17ad --- /dev/null +++ b/scripts/builder/joint.py @@ -0,0 +1,78 @@ +from typing import Dict, List + +from maya import cmds + +from ..model import Joint as JointModel + + +class Joint: + """ + A builder class used for adding joints to the scene + + Attributes + ---------- + @type joints: List[JointModel] + @param joints: data representing the joints + + @type joint_flags: Dict[str, bool] + @param joint_flags: A mapping used for setting flags that are used to avoid adding the same joint multiple times + """ + + def __init__(self, joints: List[JointModel]) -> None: + self.joints = joints + self.joint_flags: Dict[str, bool] = {} + + for joint in self.joints: + self.joint_flags[joint.name] = False + + def add_joint_to_scene(self, joint: JointModel) -> None: + """ + Adds the given joint to the scene + + @type joint: JointModel + @param joint: The joint to be added to the scene + """ + + if self.joint_flags[joint.name]: + return + + in_parent_space = True + + if cmds.objExists(joint.parent_name): + cmds.select(joint.parent_name) + else: + if joint.name != joint.parent_name: + parent_joint = next( + j for j in self.joints if j.name == joint.parent_name + ) + self.add_joint_to_scene(parent_joint) + else: + # this is the first node + cmds.select(d=True) + in_parent_space = False + + position = ( + joint.translation.x, + joint.translation.y, + joint.translation.z, + ) + orientation = ( + joint.orientation.x, + joint.orientation.y, + joint.orientation.z, + ) + cmds.joint( + p=position, + o=orientation, + n=joint.name, + r=in_parent_space, + a=not in_parent_space, + scaleCompensate=False, + ) + self.joint_flags[joint.name] = True + + def process(self) -> None: + """Starts adding all the provided joints to the scene""" + + for joint in self.joints: + self.add_joint_to_scene(joint) diff --git a/scripts/builder/maya/__init__.py b/scripts/builder/maya/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/builder/maya/mesh.py b/scripts/builder/maya/mesh.py new file mode 100644 index 0000000..ebda703 --- /dev/null +++ b/scripts/builder/maya/mesh.py @@ -0,0 +1,421 @@ +import logging +from dataclasses import dataclass, field +from typing import List, Tuple + +from maya import cmds +from maya.api.OpenMaya import MDagModifier, MFnDagNode, MFnMesh, MObject, MPoint + +from ...builder.maya.util import Maya +from ...common import SKIN_WEIGHT_PRINT_RANGE +from ...dnalib.dnalib import DNA +from ...model import Point3 + + +@dataclass +class Mesh: + """ + A model class for holding data needed in the mesh building process + + Attributes + ---------- + @type dna_vertex_positions: List[Point3] + @param dna_vertex_positions: Data representing the positions of the vertices + + @type dna_vertex_layout_positions: List[int] + @param dna_vertex_layout_positions: Data representing layout position indices of vertices + + @type polygon_faces: List[int] + @param polygon_faces: List of lengths of vertex layout indices + + @type polygon_connects: List[int] + @param polygon_connects: List of vertex layout position indices + + @type derived_mesh_names: List[str] + @param derived_mesh_names: List of mesh names + """ + + dna_vertex_positions: List[Point3] = field(default_factory=list) + dna_vertex_layout_positions: List[int] = field(default_factory=list) + polygon_faces: List[int] = field(default_factory=list) + polygon_connects: List[int] = field(default_factory=list) + derived_mesh_names: List[str] = field(default_factory=list) + + +class MayaMesh: + """ + A builder class used for adding joints to the scene + + Attributes + ---------- + @type mesh_index: int + @param mesh_index: The index of the mesh + + @type dna: DNA + @param dna: Instance of DNA + + @type blend_shape_group_prefix: str + @param blend_shape_group_prefix: prefix string for blend shape group + + @type blend_shape_name_postfix: str + @param blend_shape_name_postfix: postfix string for blend shape name + + @type skin_cluster_suffix: str + @param skin_cluster_suffix: postfix string for skin cluster name + + @type data: Mesh + @param data: mesh data used in the mesh creation process + + @type fn_mesh: om.MFnMesh + @param fn_mesh: OpenMaya class used for creating the mesh + + @type mesh_object: om.MObject + @param mesh_object: the object representing the mesh + + @type dag_modifier: om.MDagModifier + @param dag_modifier: OpenMaya class used for naming the mesh + """ + + def __init__( + self, + mesh_index: int, + dna: DNA, + blend_shape_group_prefix: str, + blend_shape_name_postfix: str, + skin_cluster_suffix: str, + ) -> None: + self.mesh_index = mesh_index + self.data: Mesh = Mesh() + self.fn_mesh = MFnMesh() + self.mesh_object: MObject = None + self.dag_modifier: MDagModifier = None + self.dna = dna + self.blend_shape_group_prefix = blend_shape_group_prefix + self.blend_shape_name_postfix = blend_shape_name_postfix + self.skin_cluster_suffix = skin_cluster_suffix + + def create_neutral_mesh(self) -> MObject: + """ + Creates the neutral mesh using the config provided for this builder class object + + @rtype: om.MObject + @returns: the instance of the created mesh object + """ + self.prepare_mesh() + self.mesh_object = self.create_mesh_object() + self.dag_modifier = self.rename_mesh() + self.add_texture_coordinates() + return self.mesh_object + + def create_mesh_object(self) -> MObject: + """ + Gets a list of points that represent the vertex positions. + + @rtype: MObject + @returns: Maya objects representing maya mesh functions and the created maya mesh object. + """ + + mesh_object = self.fn_mesh.create( + self.get_vertex_positions_from_dna_vertex_positions(), + self.data.polygon_faces, + self.data.polygon_connects, + ) + + return mesh_object + + def get_vertex_positions_from_dna_vertex_positions(self) -> List[MPoint]: + """ + Gets a list of points that represent the vertex positions. + + @rtype: List[MPoint] + @returns: List of maya point objects. + """ + + vertex_positions = [] + for position in self.data.dna_vertex_positions: + vertex_positions.append( + MPoint( + position.x, + position.y, + position.z, + ) + ) + return vertex_positions + + def rename_mesh(self) -> MDagModifier: + """ + Renames the initial mesh object that was created to the name from the configuration. + + @rtype: Tuple[MDagModifier] + @returns: Maya object representing the dag modifier. + """ + + mesh_name = self.dna.get_mesh_name(self.mesh_index) + dag_modifier = MDagModifier() + dag_modifier.renameNode(self.mesh_object, mesh_name) + dag_modifier.doIt() + return dag_modifier + + def prepare_mesh(self) -> None: + """ + Gets a list of points that represent the vertex positions. + + """ + + logging.info("==============================") + mesh_name = self.dna.get_mesh_name(self.mesh_index) + logging.info(f"adding mesh: {mesh_name}") + self.data.dna_vertex_positions = self.dna.get_vertex_positions_for_mesh_index( + self.mesh_index + ) + self.data.dna_vertex_layout_positions = ( + self.dna.get_vertex_layout_positions_for_mesh_index(self.mesh_index) + ) + + ( + self.data.polygon_faces, + self.data.polygon_connects, + ) = self.dna.get_polygon_faces_and_connects(self.mesh_index) + + def add_texture_coordinates(self) -> None: + """ + Method for adding texture coordinates. + + """ + + logging.info("adding texture coordinates...") + + ( + texture_coordinate_us, + texture_coordinate_vs, + texture_coordinate_indices, + ) = self.get_texture_data() + + self.fn_mesh.setUVs(texture_coordinate_us, texture_coordinate_vs) + self.fn_mesh.assignUVs(self.data.polygon_faces, texture_coordinate_indices) + + mesh_name = self.dna.get_mesh_name(self.mesh_index) + + cmds.select(mesh_name, replace=True) + cmds.polyMergeUV(mesh_name, distance=0.01, constructionHistory=False) + + def get_texture_data(self) -> Tuple[List[float], List[float], List[int]]: + """ + Gets the data needed for the creation of textures. + + @rtype: Tuple[List[float], List[float], List[int]] @returns: The tuple containing the list of texture + coordinate Us, the list of texture coordinate Vs and the list of texture coordinate indices. + """ + + texture_coordinates = self.dna.get_vertex_texture_coordinates_for_mesh( + self.mesh_index + ) + dna_faces = self.dna.get_faces(self.mesh_index) + + coordinate_indices = [] + for layout_id in range( + len(self.dna.get_layouts_for_mesh_index(self.mesh_index)) + ): + coordinate_indices.append( + self.dna.get_texture_coordinate_index(self.mesh_index, layout_id) + ) + + texture_coordinate_us = [] + texture_coordinate_vs = [] + texture_coordinate_indices = [] + + index_counter = 0 + + for vertices_layout_index_array in dna_faces: + for vertex_layout_index_array in vertices_layout_index_array: + texture_coordinate = texture_coordinates[ + coordinate_indices[vertex_layout_index_array] + ] + texture_coordinate_us.append(texture_coordinate.u) + texture_coordinate_vs.append(texture_coordinate.v) + texture_coordinate_indices.append(index_counter) + index_counter += 1 + + return texture_coordinate_us, texture_coordinate_vs, texture_coordinate_indices + + def add_blend_shapes(self, add_mesh_name_to_blend_shape_channel_name: bool) -> None: + """Adds blend shapes to the mesh""" + if self.dna.has_blend_shapes(self.mesh_index): + self.create_blend_shapes(add_mesh_name_to_blend_shape_channel_name) + self.create_blend_shape_node() + + def create_blend_shape_node(self) -> None: + """ + Creates a blend shape node. + """ + mesh_name = self.dna.get_mesh_name(self.mesh_index) + + nodes = [] + for derived_mesh_name in self.data.derived_mesh_names: + nodes.append(derived_mesh_name) + + cmds.select(nodes, replace=True) + + cmds.select(mesh_name, add=True) + cmds.blendShape(name=f"{mesh_name}{self.blend_shape_name_postfix}") + cmds.delete(f"{self.blend_shape_group_prefix}{mesh_name}") + + def create_blend_shapes( + self, add_mesh_name_to_blend_shape_channel_name: bool + ) -> None: + """ + Builds all the derived meshes using the provided mesh and the blend shapes data of the DNA. + + @type add_mesh_name_to_blend_shape_channel_name: bool + @param add_mesh_name_to_blend_shape_channel_name: A flag representing whether mesh name of blend shape channel is added to name when creating it + """ + + logging.info("adding derived meshes...") + + group: str = cmds.group( + empty=True, + name=f"{self.blend_shape_group_prefix}{self.dna.get_mesh_name(self.mesh_index)}", + ) + + self.data.derived_mesh_names = [] + blend_shapes = self.dna.get_blend_shapes(self.mesh_index) + for blend_shape_target_index, blend_shape in enumerate(blend_shapes): + + self.create_blend_shape( + blend_shape_target_index, + blend_shape.channel, + group, + add_mesh_name_to_blend_shape_channel_name, + ) + cmds.setAttr(f"{group}.visibility", 0) + + def create_blend_shape( + self, + blend_shape_target_index: int, + blend_shape_channel: int, + group: str, + add_mesh_name_to_blend_shape_channel_name: bool, + ) -> None: + """ + Builds a single derived mesh using the provided mesh and the blend shape data of the DNA. + + + @type blend_shape_target_index: int + @param blend_shape_target_index: Used for getting a delta value representing the value change concerning the blend shape. + + @type blend_shape_channel: int + @param blend_shape_channel: Used for getting the blend shape name from the DNA. + + @type group: str + @param group: The transform the new meshes will be added to. + + @type add_mesh_name_to_blend_shape_channel_name: bool + @param add_mesh_name_to_blend_shape_channel_name: A flag representing whether mesh name of blend shape channel is added to name when creating it + """ + + new_vert_layout = self.get_vertex_positions_from_dna_vertex_positions() + + zipped_deltas = self.dna.get_blend_shape_target_deltas_with_vertex_id( + self.mesh_index, blend_shape_target_index + ) + for zipped_delta in zipped_deltas: + delta: Point3 = zipped_delta[1] + new_vert_layout[zipped_delta[0]] += MPoint( + delta.x, + delta.y, + delta.z, + ) + + new_mesh = self.fn_mesh.create( + new_vert_layout, self.data.polygon_faces, self.data.polygon_connects + ) + derived_name = self.dna.get_blend_shape_channel_name(blend_shape_channel) + name = ( + f"{self.dna.geometry_meshes[self.mesh_index].name}__{derived_name}" + if add_mesh_name_to_blend_shape_channel_name + else derived_name + ) + self.dag_modifier.renameNode(new_mesh, name) + self.dag_modifier.doIt() + + dag = MFnDagNode(Maya.get_element(group)) + dag.addChild(new_mesh) + + self.data.derived_mesh_names.append(name) + + def add_skin_cluster(self, joint_names: List[str], joint_ids: List[int]) -> None: + """ + Adds skin cluster to the mesh + + @type joint_names: List[str] + @param joint_names: Joint names needed for adding the skin cluster + + @type joint_ids: List[int] + @param joint_ids: Joint indices needed for setting skin weights + """ + + mesh_name = self.dna.get_mesh_name(self.mesh_index) + + self._add_skin_cluster(mesh_name, joint_names) + self.set_skin_weights(mesh_name, joint_ids) + + def _add_skin_cluster(self, mesh_name: str, joint_names: List[str]) -> None: + """ + Creates a skin cluster object. + + @type mesh_name: str + @param mesh_name: The mesh name that is used for skin cluster naming. + + @type joints: List[Joint] + @param joints: List of joints used for adding the skin cluster. + """ + + logging.info("adding skin cluster...") + maximum_influences = self.dna.get_maximum_influence_per_vertex(self.mesh_index) + + cmds.select(joint_names[0], replace=True) + + cmds.select(mesh_name, add=True) + skin_cluster = cmds.skinCluster( + toSelectedBones=True, + name=f"{mesh_name}_{self.skin_cluster_suffix}", + maximumInfluences=maximum_influences, + skinMethod=0, + obeyMaxInfluences=True, + ) + cmds.skinCluster( + skin_cluster, edit=True, addInfluence=joint_names[1:], weight=0 + ) + + def set_skin_weights(self, mesh_name: str, joint_ids: List[int]) -> None: + """ + Sets the skin weights attributes. + + @type mesh_name: str + @param mesh_name: The mesh name that is used for getting the skin cluster name. + + @type joint_ids: List[int] + @param joint_ids: List of joint indices used for setting the skin weight attribute. + """ + + logging.info("adding skin weights...") + skin_weights = self.dna.get_skin_weight_matrix_for_mesh(self.mesh_index) + + # import skin weights + temp_str = f"{mesh_name}_{self.skin_cluster_suffix}.wl[" + for vertex_id, skin_weight in enumerate(skin_weights): + if not (vertex_id + 1) % SKIN_WEIGHT_PRINT_RANGE: + logging.info(f"\t{vertex_id + 1} / {len(skin_weights)}") + vertex_infos = skin_weight + + # set all skin weights to zero + vertex_string = f"{temp_str}{str(vertex_id)}].w[" + cmds.setAttr(f"{vertex_string}0]", 0.0) + + # import skin weights + for vertex_info in vertex_infos: + cmds.setAttr( + f"{vertex_string}{str(joint_ids.index(vertex_info[0]))}]", + float(vertex_info[1]), + ) + if len(skin_weights) % SKIN_WEIGHT_PRINT_RANGE != 0: + logging.info(f"\t{len(skin_weights)} / {len(skin_weights)}") diff --git a/scripts/builder/maya/skin_weights.py b/scripts/builder/maya/skin_weights.py new file mode 100644 index 0000000..64f35f4 --- /dev/null +++ b/scripts/builder/maya/skin_weights.py @@ -0,0 +1,201 @@ +import logging +from typing import List, Tuple, Union + +from maya import cmds, mel +from maya.api.OpenMaya import MFnMesh, MGlobal +from maya.api.OpenMayaAnim import MFnSkinCluster + +from ...builder.maya.util import Maya +from ...common import DNAViewerError + + +class MayaSkinWeights: + """ + A class used for reading and storing skin weight related data needed for adding skin clusters + """ + + no_of_influences: int + skinning_method: int + joints: List[str] + vertices_info: List[List[Union[int, float]]] + + def __init__(self, skin_cluster: MFnSkinCluster, mesh_name: str) -> None: + self.no_of_influences = cmds.skinCluster(skin_cluster.name(), q=True, mi=True) + + self.skinning_method = cmds.skinCluster(skin_cluster.name(), q=True, sm=True) + + self.joints = self.get_skin_cluster_influence(skin_cluster) + + self.vertices_info = self.get_skin_weights_for_mesh_name( + skin_cluster, mesh_name + ) + + def get_skin_cluster_influence(self, skin_cluster: MFnSkinCluster) -> List[str]: + """ + Gets a list of joint names that are influences to the skin cluster. + + @type skin_cluster: MFnSkinCluster + @param skin_cluster: The functionalities of a maya skin cluster object + + @rtype: List[str] + @returns: The list if names of the joints that influence the skin cluster + """ + + influences: List[str] = cmds.skinCluster(skin_cluster.name(), q=True, inf=True) + if influences and not isinstance(influences[0], str): + influences = [obj.name() for obj in influences] + return influences + + def get_skin_weights_for_mesh_name( + self, + skin_cluster: MFnSkinCluster, + mesh_name: str, + ) -> List[List[Union[int, float]]]: + """ + Gets the skin weights concerning the given mesh. + + @type skin_cluster: MFnSkinCluster + @param skin_cluster: The functionalities of a maya skin cluster object + + @type mesh_name: str + @param mesh_name: The name of the mesh + + @rtype: List[List[Union[int, float]]] + @returns: A list of list of weight indices and the weight values + """ + + mesh = Maya.get_element(mesh_name) + components = MGlobal.getSelectionListByName(f"{mesh_name}.vtx[*]").getComponent( + 0 + )[1] + weights_data, chunk = skin_cluster.getWeights(mesh, components) + iterator = [ + weights_data[i : i + chunk] for i in range(0, len(weights_data), chunk) + ] + + vertices_info = [] + for weights in iterator: + vertex_weights: List[float] = [] + vertices_info.append(vertex_weights) + + for i, weight in enumerate(weights): + if weight: + vertex_weights.append(i) + vertex_weights.append(weight) + return vertices_info + + +def get_skin_weights_data(mesh_name: str) -> Tuple[MFnMesh, MFnSkinCluster]: + """ + Gets the maya objects that manipulate the mesh node and the skin cluster for a given mesh name. + + @type mesh_name: str + @param mesh_name: The name of the mesh + + @rtype: Tuple[MFnMesh, MFnSkinCluster] + @returns: The maya object that manipulate the mesh node and the skin cluster for a given mesh name. + """ + + skin_cluster_name = mel.eval(f"findRelatedSkinCluster {mesh_name}") + if skin_cluster_name: + skin_cluster = MFnSkinCluster(Maya.get_element(skin_cluster_name)) + mesh_node = MFnMesh(Maya.get_element(mesh_name)) + return mesh_node, skin_cluster + raise DNAViewerError(f"Unable to find skin for given mesh: {mesh_name}") + + +def get_skin_weights_from_scene(mesh_name: str) -> MayaSkinWeights: + """ + Gets the instance of this class filled with data from the scene for a given mesh name. + + @type mesh_name: str + @param mesh_name: The mesh name + + @rtype: MayaSkinWeights + @returns: An instance of this class with the data from the scene + """ + + _, skin_cluster = get_skin_weights_data(mesh_name) + + return MayaSkinWeights(skin_cluster, mesh_name) + + +def get_file_joint_mappings( + skin_weights: MayaSkinWeights, skin_cluster: MFnSkinCluster +) -> List[int]: + """ + Returns a list of object indices representing the influences concerning the joint names specified in the skin weight model. + + @type skin_weights: MayaSkinWeights + @param skin_weights: The instance of the model storing data about skin weights + + @type skin_cluster: MFnSkinCluster + @param skin_cluster: An object for working with functions concerning a skin cluster in maya + + @rtype: List[int] + @returns: a list of indices representing the influences concerning the given joints + """ + + file_joint_mapping: List[int] = [] + for joint_name in skin_weights.joints: + file_joint_mapping.append( + skin_cluster.indexForInfluenceObject(Maya.get_element(joint_name)) + ) + return file_joint_mapping + + +def set_skin_weights_to_scene(mesh_name: str, skin_weights: MayaSkinWeights) -> None: + """ + Sets the skin weights to the scene. + + @type mesh_name: str + @param mesh_name: The mesh name + + @type skin_weights: MayaSkinWeights + @param skin_weights: The object containing data that need to be set to the scene. + """ + + mesh_node, skin_cluster = get_skin_weights_data(mesh_name) + + file_joint_mapping = get_file_joint_mappings(skin_weights, skin_cluster) + + import_skin_weights(skin_cluster, mesh_node, skin_weights, file_joint_mapping) + + logging.info("Set skin weights ended.") + + +def import_skin_weights( + skin_cluster: MFnSkinCluster, + mesh_node: MFnMesh, + skin_weights: MayaSkinWeights, + file_joint_mapping: List[int], +) -> None: + """ + Imports the skin weights to the scene using the joint mapping and the data provided in the model containing the weights. + + @type skin_cluster: MFnSkinCluster + @param skin_cluster: An object for working with functions concerning a skin cluster in maya + + @type mesh_node: MFnMesh + @param mesh_node: An object for working with functions concerning meshes in maya + + @type skin_weights: MayaSkinWeights + @param skin_weights: The instance of the model storing data about skin weights + + @type file_joint_mapping: List[int] + @param file_joint_mapping: a list of indices representing the influences concerning joints + """ + + temp_str = f"{skin_cluster.name()}.wl[" + for vtx_id in range(cmds.polyEvaluate(mesh_node.name(), vertex=True)): + vtx_info = skin_weights.vertices_info[vtx_id] + + vtx_str = f"{temp_str}{str(vtx_id)}].w[" + + cmds.setAttr(f"{vtx_str}0]", 0.0) + + for i in range(0, len(vtx_info), 2): + cmds.setAttr( + f"{vtx_str}{str(file_joint_mapping[int(vtx_info[i])])}]", + vtx_info[i + 1], + ) diff --git a/scripts/builder/maya/util.py b/scripts/builder/maya/util.py new file mode 100644 index 0000000..69ae04f --- /dev/null +++ b/scripts/builder/maya/util.py @@ -0,0 +1,81 @@ +from typing import Union + +from maya.api.OpenMaya import ( + MDagPath, + MFnDagNode, + MFnTransform, + MGlobal, + MSpace, + MVector, +) + +from ...common import DNAViewerError + + +class Maya: + """A utility class used for interfacing with maya transforms""" + + @staticmethod + def get_element(name: str) -> Union[MDagPath, MFnDagNode]: + """gets the Union[MDagPath, MFnDagNode] object of the element with the given name + + @type name: str + @param name: The name of the element to be retrieved + + @rtype: Union[MDagPath, MFnDagNode] + @returns: A OpenMaya object representing the given element + """ + try: + sellist = MGlobal.getSelectionListByName(name) + except Exception as exception: + raise DNAViewerError(f"Element with name:{name} not found!") from exception + + try: + return sellist.getDagPath(0) + except Exception: + return sellist.getDependNode(0) + + @staticmethod + def get_transform(name: str) -> MFnTransform: + """gets the transform of the element with the given name + + @type element: str + @param element: The element name that we want the transform of + + @rtype: MFnTransform + @returns: A MFnTransform object representing the given elements transform + """ + return MFnTransform(Maya.get_element(name)) + + @staticmethod + def get_translation(element: str, space: int = MSpace.kObject) -> MVector: + """gets the translation of the element with the given name + + @type element: str + @param element: The element name that we want the translation of + + @type space: str + @param space: A string value representing the translation space (default is "world") + + @rtype: MVector + @returns: A MVector object representing the given elements translation + """ + return MFnTransform(Maya.get_element(element)).translation(space) + + @staticmethod + def set_translation( + element: str, translation: MVector, space: int = MSpace.kObject + ) -> None: + """sets the translation of the element with the given name + + @type element: str + @param element: The element name that we want to set the translation of + + @type translation: MVector + @param translation: The new translation value + + @type space: str + @param space: A string value representing the translation space (default is "object") + """ + element_obj = Maya.get_transform(element) + element_obj.setTranslation(translation, space) diff --git a/scripts/builder/mesh.py b/scripts/builder/mesh.py new file mode 100644 index 0000000..5e9c25f --- /dev/null +++ b/scripts/builder/mesh.py @@ -0,0 +1,114 @@ +import logging +from typing import List + +from ..builder.maya.mesh import MayaMesh +from ..dnalib.dnalib import DNA +from .config import Config + + +class Mesh: + """ + A builder class used for adding joints to the scene + + Attributes + ---------- + @type dna: DNA + @param dna: The location of the DNA file + + @type mesh_index: int + @param mesh_index: The mesh index we are working with + + @type joint_ids: List[int] + @param joint_ids: The joint indices used for adding skin + + @type joint_names: List[str] + @param joint_names: The joint names used for adding skin + + @type config: Config + @param config: The build options that will be applied when creating the mesh + + + @type mesh: MayaMesh + @param mesh: The builder class object for creating the meshes + + @type dna: DNA + @param dna: The DNA object that was loaded in + """ + + def __init__( + self, + config: Config, + dna: DNA, + mesh_index: int, + ) -> None: + self.mesh_index: int = mesh_index + self.joint_ids: List[int] = [] + self.joint_names: List[str] = [] + self.config = config + self.dna = dna + self.mesh = MayaMesh( + self.mesh_index, + self.dna, + blend_shape_group_prefix=self.config.blend_shape_group_prefix, + blend_shape_name_postfix=self.config.blend_shape_name_postfix, + skin_cluster_suffix=self.config.skin_cluster_suffix, + ) + + def build(self) -> None: + """Starts the build process, creates the neutral mesh, then adds normals, blends shapes and skin if needed""" + + self.create_neutral_mesh() + self.add_blend_shapes() + self.add_skin_cluster() + + def create_neutral_mesh(self) -> None: + """Creates the neutral mesh""" + + self.mesh.create_neutral_mesh() + + def add_blend_shapes(self) -> None: + """Reads in the blend shapes, then adds them to the mesh if it is set in the build options""" + + if self.config.add_blend_shapes: + logging.info("adding blend shapes...") + self.mesh.add_blend_shapes( + self.config.add_mesh_name_to_blend_shape_channel_name + ) + + def add_skin_cluster(self) -> None: + """Adds skin cluster to the mesh if it is set in the build options""" + + if self.config.add_skin_cluster and self.config.add_joints: + self.prepare_joints() + if self.joint_names: + self.mesh.add_skin_cluster(self.joint_names, self.joint_ids) + + def prepare_joints(self) -> None: + """ + Gets the joint indices and names needed for the given mesh. + """ + + self.prepare_joint_ids() + + joints = self.dna.read_all_neutral_joints() + self.joint_names = [] + for joint_id in self.joint_ids: + self.joint_names.append(joints[joint_id].name) + + def prepare_joint_ids(self) -> None: + joints_temp: List[int] = [] + joint_indices = self.dna.get_all_skin_weights_joint_indices_for_mesh( + self.mesh_index + ) + self.joint_ids = [] + if any(joint_indices): + for row in joint_indices: + for column in row: + joints_temp.append(column) + + self.joint_ids = list(set(joints_temp)) + self.joint_ids.sort() + else: + lod = self.dna.get_lowest_lod_containing_meshes([self.mesh_index]) + if lod: + self.joint_ids = self.dna.get_joint_indices_for_lod(lod) diff --git a/scripts/builder/rig_builder.py b/scripts/builder/rig_builder.py new file mode 100644 index 0000000..45c5692 --- /dev/null +++ b/scripts/builder/rig_builder.py @@ -0,0 +1,290 @@ +import logging +from importlib.machinery import SourceFileLoader +from importlib.util import module_from_spec, spec_from_loader +from pathlib import Path +from types import ModuleType +from typing import Optional + +from maya import cmds, mel +from maya.api.OpenMaya import MSpace, MVector + +from ..builder.maya.util import Maya +from ..common import ANALOG_GUI_HOLDER, GUI_HOLDER, RIG_LOGIC_PREFIX, DNAViewerError +from ..dnalib.dnalib import DNA +from .builder import Builder +from .config import RigConfig + + +class RigBuilder(Builder): + """ + A builder class used for building meshes + """ + + def __init__(self, dna: DNA, config: Optional[RigConfig] = None) -> None: + super().__init__(dna=dna, config=config) + self.config: Optional[RigConfig] + self.eye_l_pos: MVector + self.eye_r_pos: MVector + + def _build(self) -> None: + if super()._build(): + self.add_gui() + self.add_analog_gui() + self.add_rig_logic() + self.run_additional_assemble_script() + + def run_additional_assemble_script(self) -> None: + """ + Runs an additional assemble script if specified in the character configuration. + """ + + if self.config.aas_path: + logging.info("running additional assemble script...") + try: + module_name = Path(self.config.aas_path).stem + script = self.source_py_file(module_name, self.config.aas_path) + script_method = getattr(script, self.config.aas_method) + script_method( + self.config.get_top_level_group(), + self.config.get_rig_group(), + self.config.aas_parameter, + ) + except Exception as e: + raise DNAViewerError(f"Can't run aas script. Reason: {e}") from e + + def add_rig_logic(self) -> None: + """ + Creates and adds a rig logic node specified in the character configuration. + """ + + if ( + self.config.add_rig_logic + and self.config.add_joints + and self.config.add_skin_cluster + and self.config.add_blend_shapes + and self.config.aas_path + and self.config.analog_gui_path + and self.config.gui_path + ): + logging.info("adding rig logic...") + try: + cmds.loadPlugin("embeddedRL4.mll") + self.config.rig_logic_name = f"{RIG_LOGIC_PREFIX}{self.dna.name}" + dna = self.dna.path.replace("\\", "/") + + mel_command = self.config.rig_logic_command + mel_command += f' -n "{self.config.rig_logic_name}"' + mel_command += f' -dfp "{dna}"' + mel_command += f' -cn "{self.config.control_naming}"' + mel_command += f' -jn "{self.config.joint_naming}"' + mel_command += f' -bsn "{self.config.blend_shape_naming}"' + mel_command += f' -amn "{self.config.animated_map_naming}"; ' + + logging.info(f"mel command: {mel_command}") + mel.eval(mel_command) + except Exception as e: + logging.error( + "The procedure needed for assembling the rig logic was not found, the plugin needed for this might not be loaded." + ) + raise DNAViewerError( + f"Something went wrong, skipping adding the rig logic... Reason: {e}" + ) from e + + def add_gui(self) -> None: + """ + Adds a gui according to the specified gui options. If none is specified no gui will be added. + """ + + if self.config.gui_path: + logging.info("adding gui...") + + self.import_gui( + gui_path=self.config.gui_path, + group_name=GUI_HOLDER, + ) + self.position_gui(GUI_HOLDER) + + self.add_ctrl_attributes() + self.add_animated_map_attributes() + + def add_ctrl_attributes(self) -> None: + """ + Adds and sets the raw gui control attributes. + """ + + gui_control_names = self.dna.get_raw_control_names() + for name in gui_control_names: + ctrl_and_attr_names = name.split(".") + self.add_attribute( + control_name=ctrl_and_attr_names[0], + long_name=ctrl_and_attr_names[1], + ) + + def add_animated_map_attributes(self) -> None: + """ + Adds and sets the animated map attributes. + """ + + names = self.dna.get_animated_map_names() + for name in names: + long_name = name.replace(".", "_") + if self.config.gui_path: + self.add_attribute( + control_name=self.config.animated_map_attribute_multipliers_name, + long_name=long_name, + ) + + def position_gui(self, group_name: str) -> None: + """Sets the gui position to align with the character eyes""" + + if not cmds.objExists(self.config.eye_gui_name) or not cmds.objExists( + self.config.left_eye_joint_name + ): + logging.warning( + "could not find joints needed for positioning the gui, leaving it at its default position..." + ) + return + + gui_y = ( + Maya.get_transform(self.config.eye_gui_name).translation(MSpace.kObject).y + ) + eyes_y = ( + Maya.get_transform(self.config.left_eye_joint_name) + .translation(MSpace.kObject) + .y + ) + delta_y = eyes_y - gui_y + + if isinstance(self.config.gui_translate_x, str): + try: + logging.warning( + "gui_translate_x should be a float, trying to cast the value to float..." + ) + self.config.gui_translate_x = float(self.config.gui_translate_x) + except ValueError: + logging.error("could not cast string value to float") + return + + Maya.get_transform(group_name).translateBy( + MVector(self.config.gui_translate_x, delta_y, 0), MSpace.kObject + ) + + def add_analog_gui(self) -> None: + """ + Adds an analog gui according to the specified analog gui options. If none is specified no analog gui will be + added. + """ + + if self.config.analog_gui_path and self.config.add_joints: + logging.info("adding analog gui...") + self.import_gui( + gui_path=self.config.analog_gui_path, + group_name=ANALOG_GUI_HOLDER, + ) + if self.dna.joints.names: + self.add_eyes() + self.add_eye_locators() + + def add_eyes(self) -> None: + """Add eyes to the analog gui""" + + self.eye_l_pos = Maya.get_translation(self.config.left_eye_joint_name) + self.eye_r_pos = Maya.get_translation(self.config.right_eye_joint_name) + + Maya.set_translation( + self.config.central_driver_name, + Maya.get_translation(self.config.facial_root_joint_name), + ) + + delta_l = Maya.get_translation( + self.config.left_eye_aim_up_name + ) - Maya.get_translation(self.config.left_eye_driver_name) + delta_r = Maya.get_translation( + self.config.right_eye_aim_up_name + ) - Maya.get_translation(self.config.right_eye_driver_name) + + Maya.set_translation(self.config.left_eye_driver_name, self.eye_l_pos) + Maya.set_translation( + self.config.right_eye_driver_name, + self.eye_r_pos, + ) + Maya.set_translation( + self.config.left_eye_aim_up_name, + MVector( + self.eye_l_pos[0] + delta_l[0], + self.eye_l_pos[1] + delta_l[1], + self.eye_l_pos[2] + delta_l[2], + ), + ) + Maya.set_translation( + self.config.right_eye_aim_up_name, + MVector( + self.eye_r_pos[0] + delta_r[0], + self.eye_r_pos[1] + delta_r[1], + self.eye_r_pos[2] + delta_r[2], + ), + ) + + def add_eye_locators(self) -> None: + """Add eye locators to the analog gui""" + + eye_l_locator_pos = Maya.get_translation(self.config.le_aim) + eye_r_locator_pos = Maya.get_translation(self.config.re_aim) + central_aim_pos = Maya.get_translation(self.config.central_aim) + + eye_middle_delta = (self.eye_l_pos - self.eye_r_pos) / 2 + + eye_middle = self.eye_r_pos + eye_middle_delta + + Maya.set_translation( + self.config.central_aim, + MVector(eye_middle[0], eye_middle[1], central_aim_pos[2]), + ) + Maya.set_translation( + self.config.le_aim, + MVector(self.eye_l_pos[0], self.eye_l_pos[1], eye_l_locator_pos[2]), + ) + Maya.set_translation( + self.config.re_aim, + MVector(self.eye_r_pos[0], self.eye_r_pos[1], eye_r_locator_pos[2]), + ) + + def source_py_file(self, name: str, path: str) -> Optional[ModuleType]: + """ + Used for loading a python file, used for additional assemble script. + + @type name: str + @param name: The name of the module. + + @type path: str + @param path: The path of the python file. + + @rtype: Optional[ModuleType] + @returns: The loaded module. + """ + + path_obj = Path(path.strip()) + if ( + path + and path_obj.exists() + and path_obj.is_file() + and path_obj.suffix == ".py" + ): + spec = spec_from_loader(name, SourceFileLoader(name, path)) + module = module_from_spec(spec) + spec.loader.exec_module(module) + return module + raise DNAViewerError(f"File {path} is not found!") + + def import_gui(self, gui_path: str, group_name: str) -> None: + """ + Imports a gui using the provided parameters. + + @type gui_path: str + @param gui_path: The path of the gui file that needs to be imported. + + @type group_name: str + @param group_name: The name of the transform that holds the imported asset. + """ + + cmds.file(gui_path, i=True, groupReference=True, groupName=group_name) diff --git a/scripts/dna_utils.py b/scripts/dna_utils.py index e04a68d..32e07f4 100644 --- a/scripts/dna_utils.py +++ b/scripts/dna_utils.py @@ -13,41 +13,53 @@ SYSTEM_OS = "win64" if cmds.about(os=True) == "Windows" else "linux" PYTHON_VERSION = sys.version_info def load_dna_plugin(): - """加载DNA插件""" - try: - # 加载DNA校准插件 - dna_plugin = os.path.join(data.PLUGIN_PATH, "dnacalib.py") - if not cmds.pluginInfo(dna_plugin, query=True, loaded=True): - cmds.loadPlugin(dna_plugin) + """安全加载插件""" + plugins = [ + ("dnacalib.py", "dnacalib"), + ("embeddedRL4.mll", "embeddedRL4"), + ("MayaUERBFPlugin.mll", "MayaUERBFPlugin") + ] + + for file_name, plugin_name in plugins: + try: + plugin_path = os.path.join(data.PLUGIN_PATH, file_name) + if not os.path.exists(plugin_path): + cmds.warning(f"⚠️ 插件文件缺失: {file_name}") + continue + + if cmds.pluginInfo(plugin_name, q=True, loaded=True): + print(f"✅ 已加载: {plugin_name}") + continue + + cmds.loadPlugin(plugin_path) + print(f"✔️ 成功加载: {plugin_name}") - # 加载嵌入式插件 - embedded_plugin = os.path.join(data.PLUGIN_PATH, "embeddedRL4.mll") - if not cmds.pluginInfo(embedded_plugin, query=True, loaded=True): - cmds.loadPlugin(embedded_plugin) - - # 加载RBF插件 - rbf_plugin = os.path.join(data.PLUGIN_PATH, f"MayaUERBFPlugin.mll") - if not cmds.pluginInfo(rbf_plugin, query=True, loaded=True): - cmds.loadPlugin(rbf_plugin) - - return True - except Exception as e: - cmds.warning(f"加载DNA插件失败: {str(e)}") - return False + except Exception as e: + cmds.warning(f"❌ 加载失败 {plugin_name}: {str(e)}") + # 不要退出,继续尝试加载其他插件 def setup_dna_path(): - """设置DNA模块路径""" - # 获取DNA插件路径 - if data.PLUGIN_PATH not in sys.path: - sys.path.append(data.PLUGIN_PATH) - - # 获取PyDNA路径 - if data.PYDNA_PATH not in sys.path: - sys.path.append(data.PYDNA_PATH) - - # 添加DLL搜索路径 - if SYSTEM_OS == "win64": - os.environ["PATH"] = f"{data.PYDNA_PATH};{os.environ['PATH']}" + """增强路径设置""" + try: + # 添加二进制路径到系统PATH + bin_path = os.path.join(data.PYDNA_PATH, "bin") + os.environ['PATH'] = f"{bin_path}{os.pathsep}{os.environ['PATH']}" + print(f"已添加二进制路径: {bin_path}") + + # 确保Python能搜索到必要路径 + required_paths = [ + data.PLUGIN_PATH, + data.PYDNA_PATH, + os.path.join(data.PYDNA_PATH, "bin") + ] + + for path in required_paths: + if path not in sys.path: + sys.path.insert(0, path) + print(f"已添加路径: {path}") + + except Exception as e: + cmds.warning(f"路径设置失败: {str(e)}") # 设置路径并加载插件 setup_dna_path() @@ -58,11 +70,12 @@ dna = None dnacalib = None try: - # 确保 PYDNA_PATH 在 sys.path 中 - if data.PYDNA_PATH not in sys.path: - sys.path.append(data.PYDNA_PATH) + # 在导入前添加二进制模块路径 + PYBIN_PATH = os.path.join(data.PYDNA_PATH, "bin") + if PYBIN_PATH not in sys.path: + sys.path.insert(0, PYBIN_PATH) - # 导入 DNA 模块 + # 然后导入 DNA 模块 from dna import * import dnacalib except ImportError as e: diff --git a/scripts/dnalib/__init__.py b/scripts/dnalib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/dnalib/behavior.py b/scripts/dnalib/behavior.py new file mode 100644 index 0000000..d824890 --- /dev/null +++ b/scripts/dnalib/behavior.py @@ -0,0 +1,371 @@ +from dataclasses import dataclass, field +from typing import List, Optional, cast + +from dna import BinaryStreamReader as DNAReader + +from .definition import Definition +from .layer import Layer + + +class Behavior(Definition): + """ + @type reader: BinaryStreamReader + @param reader: The binary stream reader being used + + @type gui_to_raw: ConditionalTable + @param gui_to_raw: Mapping data about gui to raw values + + @type psd: PSDMatrix + @param psd: The data representing Pose Space Deformation + + @type blend_shapes: BlendShapesData + @param blend_shapes: The data representing blend shapes + + @type animated_maps: AnimatedMapsConditionalTable + @param animated_maps: The data representing animated maps + + @type joints: JointGroups + @param joints: The data representing joints + """ + + def __init__(self, reader: DNAReader, layers: Optional[List[Layer]]) -> None: + super().__init__(reader, layers) + + self.gui_to_raw = ConditionalTable() + self.psd = PSDMatrix() + self.blend_shapes = BlendShapesData() + self.animated_maps_conditional_table = AnimatedMapsConditionalTable() + self.joint_groups = JointGroups() + self.behavior_read = False + + def start_read(self) -> None: + super().start_read() + self.behavior_read = False + + def is_read(self) -> bool: + return super().is_read() and self.behavior_read + + def read(self) -> None: + """ + Starts reading in the behavior part of the DNA + """ + super().read() + + if not self.behavior_read and self.layer_enabled(Layer.behavior): + self.behavior_read = True + self.add_gui_to_raw() + self.add_psd() + self.add_joint_groups() + self.add_blend_shapes() + self.add_animated_maps_conditional_table() + + def get_animated_map_lods(self) -> List[int]: + return cast(List[int], self.reader.getAnimatedMapLODs()) + + def get_animated_map_from_values(self) -> List[float]: + return cast(List[float], self.reader.getAnimatedMapFromValues()) + + def get_animated_map_to_values(self) -> List[float]: + return cast(List[float], self.reader.getAnimatedMapToValues()) + + def get_animated_map_slope_values(self) -> List[float]: + return cast(List[float], self.reader.getAnimatedMapSlopeValues()) + + def get_animated_map_cut_values(self) -> List[float]: + return cast(List[float], self.reader.getAnimatedMapCutValues()) + + def get_animated_map_input_indices(self) -> List[int]: + return cast(List[int], self.reader.getAnimatedMapInputIndices()) + + def get_animated_map_output_indices(self) -> List[int]: + return cast(List[int], self.reader.getAnimatedMapOutputIndices()) + + def get_gui_to_raw_from_values(self) -> List[float]: + return cast(List[float], self.reader.getGUIToRawFromValues()) + + def get_gui_to_raw_to_values(self) -> List[float]: + return cast(List[float], self.reader.getGUIToRawToValues()) + + def gget_gui_to_raw_slope_values(self) -> List[float]: + return cast(List[float], self.reader.getGUIToRawSlopeValues()) + + def get_gui_to_raw_cut_values(self) -> List[float]: + return cast(List[float], self.reader.getGUIToRawCutValues()) + + def get_gui_to_raw_input_indices(self) -> List[int]: + return cast(List[int], self.reader.getGUIToRawInputIndices()) + + def get_gui_to_raw_output_indices(self) -> List[int]: + return cast(List[int], self.reader.getGUIToRawOutputIndices()) + + def get_psd_count(self) -> int: + return cast(int, self.reader.getPSDCount()) + + def get_psd_row_indices(self) -> List[int]: + return cast(List[int], self.reader.getPSDRowIndices()) + + def get_psd_column_indices(self) -> List[int]: + return cast(List[int], self.reader.getPSDColumnIndices()) + + def get_psd_values(self) -> List[float]: + return cast(List[float], self.reader.getPSDValues()) + + def get_blend_shape_channel_lods(self) -> List[int]: + return cast(List[int], self.reader.getBlendShapeChannelLODs()) + + def get_blend_shape_channel_input_indices(self) -> List[int]: + return cast(List[int], self.reader.getBlendShapeChannelInputIndices()) + + def get_blend_shape_channel_output_indices(self) -> List[int]: + return cast(List[int], self.reader.getBlendShapeChannelOutputIndices()) + + def get_joint_row_count(self) -> int: + return cast(int, self.reader.getJointRowCount()) + + def get_joint_column_count(self) -> int: + return cast(int, self.reader.getJointColumnCount()) + + def get_joint_variable_attribute_indices(self) -> int: + return cast(int, self.reader.getJointVariableAttributeIndices()) + + def get_joint_group_count(self) -> int: + return cast(int, self.reader.getJointGroupCount()) + + def get_joint_group_logs(self, joint_group_index: int) -> List[int]: + return cast(List[int], self.reader.getJointGroupLODs(joint_group_index)) + + def get_joint_group_input_indices(self, joint_group_index: int) -> List[int]: + return cast(List[int], self.reader.getJointGroupInputIndices(joint_group_index)) + + def get_joint_group_output_indices(self, joint_group_index: int) -> List[int]: + return cast( + List[int], self.reader.getJointGroupOutputIndices(joint_group_index) + ) + + def get_joint_group_values(self, joint_group_index: int) -> List[float]: + return cast(List[float], self.reader.getJointGroupValues(joint_group_index)) + + def get_joint_group_joint_indices(self, joint_group_index: int) -> List[int]: + return cast(List[int], self.reader.getJointGroupJointIndices(joint_group_index)) + + def add_gui_to_raw(self) -> None: + """Reads in the gui to raw mapping""" + + self.reader.gui_to_raw = ConditionalTable( + inputs=self.get_gui_to_raw_input_indices(), + outputs=self.get_gui_to_raw_output_indices(), + from_values=self.get_gui_to_raw_from_values(), + to_values=self.get_gui_to_raw_to_values(), + slope_values=self.gget_gui_to_raw_slope_values(), + cut_values=self.get_gui_to_raw_cut_values(), + ) + + def add_psd(self) -> None: + """Reads in the PSD part of the behavior""" + + self.psd = PSDMatrix( + count=self.get_psd_count(), + rows=self.get_psd_row_indices(), + columns=self.get_psd_column_indices(), + values=self.get_psd_values(), + ) + + def add_joint_groups(self) -> None: + """Reads in the joints part of the behavior""" + + self.joint_groups.joint_row_count = self.reader.getJointRowCount() + self.joint_groups.joint_column_count = self.reader.getJointColumnCount() + for lod in range(self.get_lod_count()): + self.joint_groups.joint_variable_attribute_indices.append( + self.reader.getJointVariableAttributeIndices(lod) + ) + for joint_group_index in range(self.get_joint_group_count()): + self.joint_groups.joint_groups.append( + JointGroup( + lods=self.get_joint_group_logs(joint_group_index), + inputs=self.get_joint_group_input_indices(joint_group_index), + outputs=self.get_joint_group_output_indices(joint_group_index), + values=self.get_joint_group_values(joint_group_index), + joints=self.get_joint_group_joint_indices(joint_group_index), + ) + ) + + def add_blend_shapes(self) -> None: + """Reads in the blend shapes part of the behavior""" + + self.blend_shapes = BlendShapesData( + lods=self.get_blend_shape_channel_lods(), + inputs=self.get_blend_shape_channel_input_indices(), + outputs=self.get_blend_shape_channel_output_indices(), + ) + + def add_animated_maps_conditional_table(self) -> None: + """Reads in the animated maps part of the behavior""" + + self.reader.animated_maps_conditional_table = AnimatedMapsConditionalTable( + lods=self.get_animated_map_lods(), + conditional_table=ConditionalTable( + from_values=self.get_animated_map_from_values(), + to_values=self.get_animated_map_to_values(), + slope_values=self.get_animated_map_slope_values(), + cut_values=self.get_animated_map_cut_values(), + inputs=self.get_animated_map_input_indices(), + outputs=self.get_animated_map_output_indices(), + ), + ) + + +@dataclass +class ConditionalTable: + """ + A model class for holding various values + + Attributes + ---------- + @type from_values: List[float] + @param from_values: The list of values + + @type to_values: List[float] + @param to_values: The list of values + + @type slope_values: List[float] + @param slope_values: The list of slope values + + @type cut_values: List[float] + @param cut_values: The list of cut values + + @type inputs: List[int] + @param inputs: The indices of inputs + + @type outputs: List[int] + @param outputs: The indices of outputs + """ + + from_values: List[float] = field(default_factory=list) + to_values: List[float] = field(default_factory=list) + slope_values: List[float] = field(default_factory=list) + cut_values: List[float] = field(default_factory=list) + inputs: List[int] = field(default_factory=list) + outputs: List[int] = field(default_factory=list) + + +@dataclass +class PSDMatrix: + """ + A model class for holding data about Pose Space Deformation + + Attributes + ---------- + @type count: int + @param count: The list of values + + @type rows: List[int] + @param rows: List of row indices used for storing values + + @type columns: List[int] + @param columns: List of row indices used for storing values + + @type values: List[float] + @param values: The list of values, that can be accessed from the row and column index + """ + + count: Optional[int] = field(default=None) + rows: List[int] = field(default_factory=list) + columns: List[int] = field(default_factory=list) + values: List[float] = field(default_factory=list) + + +@dataclass +class JointGroup: + """ + A model class for holding data about joint groups + + Attributes + ---------- + @type lods: List[int] + @param lods: A list of lod indices that the joint group is contained within + + @type values: List[float] + @param values: A list of values + + @type joints: List[int] + @param joints: A list of joint indices + + @type inputs: List[int] + @param inputs: The indices of inputs + + @type outputs: List[int] + @param outputs: The indices of outputs + """ + + lods: List[int] = field(default_factory=list) + values: List[float] = field(default_factory=list) + joints: List[int] = field(default_factory=list) + inputs: List[int] = field(default_factory=list) + outputs: List[int] = field(default_factory=list) + + +@dataclass +class BlendShapesData: + """ + A model class for holding data about blend shapes + + Attributes + ---------- + @type lods: List[int] + @param lods: A list of lod indices that the blend shapes are contained within + + @type inputs: List[int] + @param inputs: The indices of inputs + + @type outputs: List[int] + @param outputs: The indices of outputs + """ + + lods: List[int] = field(default_factory=list) + inputs: List[int] = field(default_factory=list) + outputs: List[int] = field(default_factory=list) + + +@dataclass +class AnimatedMapsConditionalTable: + """ + A model class for holding data about animated maps + + Attributes + ---------- + @type lods: List[int] + @param lods: A list of lod indices that the blend shapes are contained within + + @type conditional_table: ConditionalTable + @param conditional_table: Data needed for animated maps + """ + + lods: List[int] = field(default_factory=list) + conditional_table: ConditionalTable = field(default_factory=ConditionalTable) + + +@dataclass +class JointGroups: + """ + A model class for storing data about joints + + Attributes + ---------- + @type joint_row_count: int + @param joint_row_count: The row count of the matrix that stores the joints data + + @type joint_column_count: int + @param joint_column_count: The column count of the matrix that stores the joints data + + @type joint_variable_attribute_indices: List[List[int]] + @param joint_variable_attribute_indices: List of joint variable attribute indices per LOD + + @type joint_groups: List[JointGroup] + @param joint_groups: The list of joint groups + """ + + joint_row_count: Optional[int] = field(default=None) + joint_column_count: Optional[int] = field(default=None) + joint_variable_attribute_indices: List[List[int]] = field(default_factory=list) + joint_groups: List[JointGroup] = field(default_factory=list) diff --git a/scripts/dnalib/definition.py b/scripts/dnalib/definition.py new file mode 100644 index 0000000..b64aa99 --- /dev/null +++ b/scripts/dnalib/definition.py @@ -0,0 +1,333 @@ +from dataclasses import dataclass, field +from typing import Dict, List, Optional, Tuple, cast + +from dna import BinaryStreamReader as DNAReader +from dna import MeshBlendShapeChannelMapping + +from ..model import Point3 +from .descriptor import Descriptor +from .layer import Layer + + +class Definition(Descriptor): + """ + A class used for reading and accessing the definition part of the DNA file + + Attributes + ---------- + @type reader: BinaryStreamReader + @param reader: The binary stream reader being used + + @type definition: DefinitionModel + @param definition: The object that holds the definition data read from the DNA file + + @type joints: Joints + @param joints: The data about joints + + @type blend_shape_channels: GeometryEntity + @param blend_shape_channels: The names and indices of blend shape channels + + @type animated_maps: GeometryEntity + @param animated_maps: The names and indices of animated maps + + @type meshes: GeometryEntity + @param meshes: The names and indices of the meshes + + @type gui_control_names: List[str] + @param gui_control_names: The list of gui control names + + @type raw_control_names: List[str] + @param raw_control_names: The list of raw control names + + @type mesh_blend_shape_channel_mapping: List[Tuple[int, int]] + @param mesh_blend_shape_channel_mapping: Mapping of mesh index to the blend shape channel index + + @type mesh_blend_shape_channel_mapping_indices_for_lod: List[List[int]] + @param mesh_blend_shape_channel_mapping_indices_for_lod: The list of blend shape channel mapping indices by lod + + @type neutral_joint_translations: List[Point3] + @param neutral_joint_translations: The list of neutral joint translations + + @type neutral_joint_rotations: List[Point3] + @param neutral_joint_rotations: The list of neutral joint rotations + """ + + def __init__(self, reader: DNAReader, layers: Optional[List[Layer]]) -> None: + super().__init__(reader, layers) + self.joints = Joints() + self.blend_shape_channels = GeometryEntity() + self.animated_maps = GeometryEntity() + self.meshes = GeometryEntity() + self.meshes_mapping: Dict[str, int] = {} + + self.gui_control_names: List[str] = [] + self.raw_control_names: List[str] = [] + + self.mesh_blend_shape_channel_mapping: List[Tuple[int, int]] = [] + self.mesh_blend_shape_channel_mapping_indices_for_lod: List[List[int]] = [] + + self.neutral_joint_translations: List[Point3] = [] + self.neutral_joint_rotations: List[Point3] = [] + self.definition_read = False + + def start_read(self) -> None: + super().start_read() + self.definition_read = False + + def is_read(self) -> bool: + return super().is_read() and self.definition_read + + def read(self) -> None: + """ + Starts reading in the definition part of the DNA + + @rtype: DefinitionModel + @returns: the instance of the created definition model + """ + super().read() + + if not self.definition_read and self.layer_enabled(Layer.definition): + self.definition_read = True + self.add_controls() + self.add_joints() + self.add_blend_shape_channels() + self.add_animated_maps() + self.add_meshes() + self.add_mesh_blend_shape_channel_mapping() + self.add_neutral_joints() + + def get_lod_count(self) -> int: + return cast(int, self.reader.getLODCount()) + + def get_gui_control_count(self) -> int: + return cast(int, self.reader.getGUIControlCount()) + + def get_gui_control_name(self, index: int) -> str: + return cast(str, self.reader.getGUIControlName(index)) + + def get_raw_control_count(self) -> int: + return cast(int, self.reader.getRawControlCount()) + + def get_raw_control_name(self, index: int) -> str: + return cast(str, self.reader.getRawControlName(index)) + + def get_raw_control_names(self) -> List[str]: + names = [] + for i in range(self.get_raw_control_count()): + names.append(self.get_raw_control_name(i)) + return names + + def get_neutral_joint_translation(self, index: int) -> Point3: + translation = cast(List[float], self.reader.getNeutralJointTranslation(index)) + return Point3(translation[0], translation[1], translation[2]) + + def get_neutral_joint_translation_xs(self) -> List[float]: + return cast(List[float], self.reader.getNeutralJointTranslationXs()) + + def get_neutral_joint_translation_ys(self) -> List[float]: + return cast(List[float], self.reader.getNeutralJointTranslationYs()) + + def get_neutral_joint_translation_zs(self) -> List[float]: + return cast(List[float], self.reader.getNeutralJointTranslationZs()) + + def get_neutral_joint_rotation(self, index: int) -> Point3: + translation = cast(List[float], self.reader.getNeutralJointRotation(index)) + return Point3(translation[0], translation[1], translation[2]) + + def get_neutral_joint_rotation_xs(self) -> List[float]: + return cast(List[float], self.reader.getNeutralJointRotationXs()) + + def get_neutral_joint_rotation_ys(self) -> List[float]: + return cast(List[float], self.reader.getNeutralJointRotationYs()) + + def get_neutral_joint_rotation_zs(self) -> List[float]: + return cast(List[float], self.reader.getNeutralJointRotationZs()) + + def get_mesh_blend_shape_channel_mapping_count(self) -> int: + return cast(int, self.reader.getMeshBlendShapeChannelMappingCount()) + + def get_mesh_blend_shape_channel_mapping( + self, index: int + ) -> MeshBlendShapeChannelMapping: + return cast( + MeshBlendShapeChannelMapping, + self.reader.getMeshBlendShapeChannelMapping(index), + ) + + def get_mesh_blend_shape_channel_mapping_for_lod(self, lod: int) -> List[int]: + return cast( + List[int], self.reader.getMeshBlendShapeChannelMappingIndicesForLOD(lod) + ) + + def get_joint_count(self) -> int: + return cast(int, self.reader.getJointCount()) + + def get_joint_name(self, index: int) -> str: + return cast(str, self.reader.getJointName(index)) + + def get_joint_parent_index(self, index: int) -> int: + return cast(int, self.reader.getJointParentIndex(index)) + + def get_joint_indices_for_lod(self, index: int) -> List[int]: + return cast(List[int], self.reader.getJointIndicesForLOD(index)) + + def get_blend_shape_channel_count(self) -> int: + return cast(int, self.reader.getBlendShapeChannelCount()) + + def get_blend_shape_channel_name(self, index: int) -> str: + return cast(str, self.reader.getBlendShapeChannelName(index)) + + def get_mesh_count(self) -> int: + return cast(int, self.reader.getMeshCount()) + + def get_mesh_name(self, index: int) -> str: + return cast(str, self.reader.getMeshName(index)) + + def get_mesh_indices_for_lod(self, index: int) -> List[int]: + return cast(List[int], self.reader.getMeshIndicesForLOD(index)) + + def get_blend_shape_channel_indices_for_lod(self, index: int) -> List[int]: + return cast(List[int], self.reader.getBlendShapeChannelIndicesForLOD(index)) + + def get_animated_map_count(self) -> int: + return cast(int, self.reader.getAnimatedMapCount()) + + def get_animated_map_name(self, index: int) -> str: + return cast(str, self.reader.getAnimatedMapName(index)) + + def get_animated_map_names(self) -> List[str]: + names = [] + for i in range(self.get_animated_map_count()): + names.append(self.get_animated_map_name(i)) + return names + + def get_animated_map_indices_for_lod(self, index: int) -> List[int]: + return cast(List[int], self.reader.getAnimatedMapIndicesForLOD(index)) + + def get_translation_unit(self) -> int: + return cast(int, self.reader.getTranslationUnit()) + + def get_rotation_unit(self) -> int: + return cast(int, self.reader.getRotationUnit()) + + def add_neutral_joints(self) -> None: + """Reads in the neutral joints part of the definition""" + + neutral_joint_translation_xs = self.get_neutral_joint_translation_xs() + neutral_joint_translation_ys = self.get_neutral_joint_translation_ys() + neutral_joint_translation_zs = self.get_neutral_joint_translation_zs() + neutral_joint_translation_count_x = len(neutral_joint_translation_xs) + for index in range(neutral_joint_translation_count_x): + self.neutral_joint_translations.append( + Point3( + x=neutral_joint_translation_xs[index], + y=neutral_joint_translation_ys[index], + z=neutral_joint_translation_zs[index], + ) + ) + neutral_joint_rotation_xs = self.get_neutral_joint_rotation_xs() + neutral_joint_rotation_ys = self.get_neutral_joint_rotation_ys() + neutral_joint_rotation_zs = self.get_neutral_joint_rotation_zs() + neutral_joint_rotation_count_x = len(neutral_joint_rotation_xs) + for index in range(neutral_joint_rotation_count_x): + self.neutral_joint_rotations.append( + Point3( + x=neutral_joint_rotation_xs[index], + y=neutral_joint_rotation_ys[index], + z=neutral_joint_rotation_zs[index], + ) + ) + + def add_mesh_blend_shape_channel_mapping(self) -> None: + """Reads in the mesh blend shape channel mapping""" + + for index in range(self.get_mesh_blend_shape_channel_mapping_count()): + mapping = self.get_mesh_blend_shape_channel_mapping(index) + self.mesh_blend_shape_channel_mapping.append( + (mapping.meshIndex, mapping.blendShapeChannelIndex) + ) + for lod in range(self.get_lod_count()): + self.mesh_blend_shape_channel_mapping_indices_for_lod.append( + self.get_mesh_blend_shape_channel_mapping_for_lod(lod) + ) + + def add_meshes(self) -> None: + """Reads in the meshes of the definition""" + + for index in range(self.get_mesh_count()): + mesh_name = self.get_mesh_name(index) + self.meshes.names.append(mesh_name) + self.meshes_mapping[mesh_name] = index + for index in range(self.get_lod_count()): + self.meshes.lod_indices.append(self.get_mesh_indices_for_lod(index)) + + def add_animated_maps(self) -> None: + """Reads in the animated maps of the definition""" + + for index in range(self.get_animated_map_count()): + self.animated_maps.names.append(self.get_animated_map_name(index)) + for index in range(self.get_lod_count()): + self.animated_maps.lod_indices.append( + self.get_animated_map_indices_for_lod(index) + ) + + def add_blend_shape_channels(self) -> None: + """Reads in the neutral joints part of the definition""" + + for index in range(self.get_blend_shape_channel_count()): + self.blend_shape_channels.names.append( + self.get_blend_shape_channel_name(index) + ) + for index in range(self.get_lod_count()): + self.blend_shape_channels.lod_indices.append( + self.get_blend_shape_channel_indices_for_lod(index) + ) + + def add_joints(self) -> None: + """Reads in the joints of the definition""" + + for index in range(self.get_joint_count()): + self.joints.names.append(self.get_joint_name(index)) + self.joints.parent_index.append(self.get_joint_parent_index(index)) + for index in range(self.get_lod_count()): + self.joints.lod_indices.append(self.get_joint_indices_for_lod(index)) + + def add_controls(self) -> None: + """Reads in the gui and raw controls of the definition""" + + for index in range(self.get_gui_control_count()): + self.gui_control_names.append(self.get_gui_control_name(index)) + for index in range(self.get_raw_control_count()): + self.raw_control_names.append(self.get_raw_control_name(index)) + + +@dataclass +class GeometryEntity: + """ + A model class for holding names and indices + + Attributes + ---------- + @type names: List[str] + @param names: List of names + + @type lod_indices: List[List[int]] + @param lod_indices: List of indices per lod + """ + + names: List[str] = field(default_factory=list) + lod_indices: List[List[int]] = field(default_factory=list) + + +@dataclass +class Joints(GeometryEntity): + """ + A model class for holding data about the joints + + Attributes + ---------- + @type parent_index: List[int] + @param parent_index: List of parent indices for each joint index + """ + + parent_index: List[int] = field(default_factory=list) diff --git a/scripts/dnalib/descriptor.py b/scripts/dnalib/descriptor.py new file mode 100644 index 0000000..c359702 --- /dev/null +++ b/scripts/dnalib/descriptor.py @@ -0,0 +1,129 @@ +from typing import Dict, List, Optional, Tuple + +from dna import BinaryStreamReader as DNAReader + +from ..dnalib.layer import Layer + + +class Descriptor: + """ + A class used for reading and accessing the descriptor part of the DNA file + + Attributes + ---------- + + @type name: str + @param name: The name of the character + + @type archetype: int + @param archetype: A value that represents the archetype of the character + + @type gender: int + @param gender: A value that represents the gender of the character + + @type age: int + @param age: The age of the character + + @type metadata: Dict[str, str] + @param metadata: Metadata stored for the character + + @type translation_unit: int + @param translation_unit: The translation unit that was used for creating the character + + @type rotation_unit: int + @param rotation_unit: The translation unit that was used for creating the character + + @type coordinate_system: Tuple[int, int, int] + @param coordinate_system: A tuple representing the coordinate system + + @type lod_count: int + @param lod_count: The number of LODs for the characters + + @type db_max_lod:int + @param db_max_lod: A LOD constraint representing the greatest LOD we wish wish to produce (ie. if the value is n, the potential LODs are 0, 1, .. n-1) + + @type db_complexity: str + @param db_complexity: Will be used in future + + @type db_name: str + @param db_name: DB identifier + """ + + def __init__(self, reader: DNAReader, layers: Optional[List[Layer]]) -> None: + self.reader = reader + self.layers = layers + self.name: Optional[str] = None + self.archetype: Optional[int] = None + self.gender: Optional[int] = None + self.age: Optional[int] = None + self.metadata: Dict[str, str] = {} + + self.translation_unit: Optional[int] = None + self.rotation_unit: Optional[int] = None + + self.coordinate_system: Optional[Tuple[int, int, int]] = None + + self.lod_count: Optional[int] = None + self.db_max_lod: Optional[int] = None + self.db_complexity: Optional[str] = None + self.db_name: Optional[str] = None + self.descriptor_read = False + + def start_read(self) -> None: + self.descriptor_read = False + + def is_read(self) -> bool: + return self.descriptor_read + + def layer_enabled(self, layer: Layer) -> bool: + return layer in self.layers or Layer.all in self.layers + + def read(self) -> None: + """ + Starts reading in the descriptor part of the DNA + + @rtype: DescriptorModel + @returns: the instance of the created descriptor model + """ + + if not self.descriptor_read and self.layer_enabled(Layer.descriptor): + self.descriptor_read = True + self.add_basic_data() + self.add_metadata() + self.add_geometry_data() + self.add_db_data() + + def add_basic_data(self) -> None: + """Reads in the character name, archetype, gender and age""" + + self.name = self.reader.getName() + self.archetype = self.reader.getArchetype() + self.gender = self.reader.getGender() + self.age = self.reader.getAge() + + def add_metadata(self) -> None: + """Reads in the metadata provided from the DNA file""" + + for i in range(self.reader.getMetaDataCount()): + key = self.reader.getMetaDataKey(i) + self.metadata[key] = self.reader.getMetaDataValue(key) + + def add_geometry_data(self) -> None: + """Sets the translation unit, rotation unit, and coordinate system from the DNA file""" + + self.translation_unit = self.reader.getTranslationUnit() + self.rotation_unit = self.reader.getRotationUnit() + coordinate_system = self.reader.getCoordinateSystem() + self.coordinate_system = ( + coordinate_system.xAxis, + coordinate_system.yAxis, + coordinate_system.zAxis, + ) + + def add_db_data(self) -> None: + """Reads in the db data from the DNA file""" + + self.lod_count = self.reader.getLODCount() + self.db_max_lod = self.reader.getDBMaxLOD() + self.db_complexity = self.reader.getDBComplexity() + self.db_name = self.reader.getDBName() diff --git a/scripts/dnalib/dnalib.py b/scripts/dnalib/dnalib.py new file mode 100644 index 0000000..27a1d0f --- /dev/null +++ b/scripts/dnalib/dnalib.py @@ -0,0 +1,250 @@ +from typing import List, Optional, Tuple + +from dna import BinaryStreamReader as DNAReader +from dna import DataLayer_All, FileStream, Status + +from ..common import DNAViewerError +from ..model import UV, BlendShape, Joint, Layout, Point3 +from .behavior import Behavior +from .geometry import Geometry +from .layer import Layer + + +class DNA(Behavior, Geometry): + """ + A class used for accessing data in DNA file. + + @type dna_path: str + @param dna_path: The path of the DNA file + + @type layers: Optional[List[Layer]] + @param layers: List of parts of DNA to be loaded. If noting is passed, whole DNA is going to be loaded. Same as + passing Layer.all. + """ + + def __init__(self, dna_path: str, layers: Optional[List[Layer]] = None) -> None: + self.path = dna_path + self.reader = self.create_reader(dna_path) + layers = layers or [Layer.all] + Behavior.__init__(self, self.reader, layers) + Geometry.__init__(self, self.reader, layers) + self.read() + + def create_reader(self, dna_path: str) -> DNAReader: + """ + Creates a stream reader needed for reading values from the DNA file. + + @type dna_path: str + @param dna_path: The path of the DNA file + + @rtype: DNA + @returns: The reader needed for reading values from the DNA file + """ + + stream = FileStream( + dna_path, FileStream.AccessMode_Read, FileStream.OpenMode_Binary + ) + + reader = DNAReader(stream, DataLayer_All) + reader.read() + if not Status.isOk(): + status = Status.get() + raise RuntimeError(f"Error loading DNA: {status.message}") + return reader + + def is_read(self) -> bool: + return Behavior.is_read(self) and Geometry.is_read(self) + + def read(self) -> None: + if not self.is_read(): + self.start_read() + Behavior.read(self) + Geometry.read(self) + + def read_all_neutral_joints(self) -> List[Joint]: + joints = [] + for i in range(self.get_joint_count()): + name = self.get_joint_name(i) + translation = self.get_neutral_joint_translation(i) + orientation = self.get_neutral_joint_rotation(i) + parent_name = self.get_joint_name(self.get_joint_parent_index(i)) + + joint = Joint( + name=name, + translation=translation, + orientation=orientation, + parent_name=parent_name, + ) + + joints.append(joint) + + return joints + + def get_all_skin_weights_joint_indices_for_mesh( + self, mesh_index: int + ) -> List[List[int]]: + return self.geometry_meshes[mesh_index].skin_weights.joint_indices + + def get_blend_shape_target_deltas_with_vertex_id( + self, mesh_index: int, blend_shape_target_index: int + ) -> List[Tuple[int, Point3]]: + blend_shape = self.geometry_meshes[mesh_index].blend_shapes[ + blend_shape_target_index + ] + indices = list(blend_shape.deltas.keys()) + + deltas: List[Point3] = [] + for i in indices: + deltas.append(blend_shape.deltas[i]) + + if not deltas: + return [] + + return list(zip(indices, deltas)) + + def get_all_skin_weights_values_for_mesh( + self, mesh_index: int + ) -> List[List[float]]: + skin_weight_values = [] + mesh = self.geometry_meshes[mesh_index] + for i in range(len(mesh.topology.positions)): + skin_weight_values.append(mesh.skin_weights.values[i]) + + return skin_weight_values + + def get_skin_weight_matrix_for_mesh( + self, mesh_index: int + ) -> List[List[Tuple[int, float]]]: + vertex_position_count = len(self.geometry_meshes[mesh_index].topology.positions) + + joint_indices = self.get_all_skin_weights_joint_indices_for_mesh(mesh_index) + if len(joint_indices) != vertex_position_count: + raise DNAViewerError( + "Number of joint indices and vertex count don't match!" + ) + + skin_weight_values = self.get_all_skin_weights_values_for_mesh(mesh_index) + + if len(skin_weight_values) != vertex_position_count: + raise DNAViewerError( + "Number of skin weight values and vertex count don't match!" + ) + if len(joint_indices) != len(skin_weight_values): + raise DNAViewerError( + "Number of skin weight values and joint indices count don't match for vertex!" + ) + + weight_matrix = [] + for indices, values in zip(joint_indices, skin_weight_values): + if not indices: + raise DNAViewerError( + "JointIndexArray for vertex can't be less than one!" + ) + vertex_weights = [] + for joint_index, skin_weight_value in zip(indices, values): + vertex_weights.append((joint_index, skin_weight_value)) + weight_matrix.append(vertex_weights) + return weight_matrix + + def get_vertex_texture_coordinates_for_mesh(self, mesh_index: int) -> List[UV]: + return self.geometry_meshes[mesh_index].topology.texture_coordinates + + def get_vertex_positions_for_mesh_index(self, mesh_index: int) -> List[Point3]: + return self.geometry_meshes[mesh_index].topology.positions + + def get_vertex_layout_positions_for_mesh_index(self, mesh_index: int) -> List[int]: + return [ + item.position_index + for item in self.geometry_meshes[mesh_index].topology.layouts + ] + + def get_faces(self, mesh_index: int) -> List[List[int]]: + return self.geometry_meshes[mesh_index].topology.face_vertex_layouts + + def get_polygon_faces_and_connects( + self, + mesh_index: int = None, + dna_faces: List[List[int]] = None, + dna_vertex_layout_positions: List[int] = None, + ) -> Tuple[List[int], List[int]]: + if mesh_index is None: + if None in (dna_faces, dna_vertex_layout_positions): + raise DNAViewerError( + "get_polygon_faces_and_connects -> Must provide either mesh_index or dna_faces and dna_vertex_layout_positions" + ) + if dna_faces is None: + dna_faces = self.get_faces(mesh_index) + if dna_vertex_layout_positions is None: + dna_vertex_layout_positions = ( + self.get_vertex_layout_positions_for_mesh_index(mesh_index) + ) + + polygon_faces = [] + polygon_connects = [] + for vertices_layout_index_array in dna_faces: + polygon_faces.append(len(vertices_layout_index_array)) + for vertex_layout_index_array in vertices_layout_index_array: + polygon_connects.append( + dna_vertex_layout_positions[vertex_layout_index_array] + ) + + return polygon_faces, polygon_connects + + def get_layouts_for_mesh_index(self, mesh_index: int) -> List[Layout]: + return self.geometry_meshes[mesh_index].topology.layouts + + def get_texture_coordinate_index(self, mesh_index: int, layout_id: int) -> int: + return ( + self.geometry_meshes[mesh_index] + .topology.layouts[layout_id] + .texture_coordinate_index + ) + + def has_blend_shapes(self, mesh_index: int) -> bool: + return ( + len([bs.channel for bs in self.geometry_meshes[mesh_index].blend_shapes]) + > 0 + ) + + def get_lowest_lod_containing_meshes( + self, mesh_indices: List[int] + ) -> Optional[int]: + unique_mesh_indices = set(mesh_indices) + for lod in range(self.get_lod_count()): + if any(list(unique_mesh_indices & set(self.get_mesh_indices_for_lod(lod)))): + return lod + return None + + def get_meshes_by_lods(self, mesh_indices: List[int]) -> List[List[int]]: + result_list = [] + for lod in range(self.get_lod_count()): + temp = list(set(mesh_indices) & set(self.get_mesh_indices_for_lod(lod))) + result_list.append(temp) + return result_list + + def get_all_meshes_grouped_by_lod(self) -> List[List[int]]: + """ + Gets the list of list of mesh indices grouped by the lod number. + + @type dna: DNA + @param dna: Instance of DNA. + + @rtype: List[List[int]] + @returns: The list of list of mesh indices grouped by the lod number + """ + + result: List[List[int]] = [] + + for lod in range(self.get_lod_count()): + mesh_indices = [] + for mesh_index in self.get_mesh_indices_for_lod(lod): + mesh_indices.append(mesh_index) + result.append(mesh_indices) + + return result + + def get_blend_shapes(self, mesh_index: int) -> List[BlendShape]: + return self.geometry_meshes[mesh_index].blend_shapes + + def get_mesh_id_from_mesh_name(self, mesh_name: str) -> Optional[int]: + return self.meshes_mapping.get(mesh_name, None) diff --git a/scripts/dnalib/geometry.py b/scripts/dnalib/geometry.py new file mode 100644 index 0000000..803d1ed --- /dev/null +++ b/scripts/dnalib/geometry.py @@ -0,0 +1,283 @@ +from typing import Dict, List, Optional, Tuple, cast + +from dna import BinaryStreamReader as DNAReader + +from ..model import UV, BlendShape, Layout, Mesh, Point3, SkinWeightsData, Topology +from .definition import Definition +from .layer import Layer + + +class Geometry(Definition): + def __init__(self, reader: DNAReader, layers: Optional[List[Layer]]) -> None: + super().__init__(reader, layers) + self.geometry_meshes: List[Mesh] = [] + self.geometry_read = False + + def start_read(self) -> None: + super().start_read() + self.geometry_read = False + + def is_read(self) -> bool: + return super().is_read() and self.geometry_read + + def read(self) -> None: + """ + Starts reading in the mesh from the geometry part of the DNA + """ + super().read() + + if not self.geometry_read and self.layer_enabled(Layer.geometry): + self.geometry_read = True + self.geometry_meshes = [] + for lod in range(self.get_lod_count()): + for mesh_index in self.get_mesh_indices_for_lod(lod): + self.geometry_meshes.append(self.add_mesh(mesh_index)) + + def get_maximum_influence_per_vertex(self, mesh_index: int) -> int: + return cast(int, self.reader.getMaximumInfluencePerVertex(meshIndex=mesh_index)) + + def get_vertex_position_count(self, mesh_index: int) -> int: + return cast(int, self.reader.getVertexPositionCount(mesh_index)) + + def get_skin_weights_values( + self, mesh_index: int, vertex_index: int + ) -> List[float]: + return cast( + List[float], + self.reader.getSkinWeightsValues( + meshIndex=mesh_index, vertexIndex=vertex_index + ), + ) + + def get_skin_weights_joint_indices( + self, mesh_index: int, vertex_index: int + ) -> List[int]: + return cast( + List[int], + self.reader.getSkinWeightsJointIndices( + meshIndex=mesh_index, vertexIndex=vertex_index + ), + ) + + def get_vertex_texture_coordinate_count(self, mesh_index: int) -> int: + return cast( + int, self.reader.getVertexTextureCoordinateCount(meshIndex=mesh_index) + ) + + def get_vertex_texture_coordinate( + self, mesh_index: int, texture_coordinate_index: int + ) -> Tuple[float, float]: + return cast( + Tuple[float, float], + self.reader.getVertexTextureCoordinate( + meshIndex=mesh_index, textureCoordinateIndex=texture_coordinate_index + ), + ) + + def get_face_count(self, mesh_index: int) -> int: + return cast(int, self.reader.getFaceCount(meshIndex=mesh_index)) + + def get_face_vertex_layout_indices( + self, mesh_index: int, face_index: int + ) -> List[int]: + return cast( + List[int], + self.reader.getFaceVertexLayoutIndices( + meshIndex=mesh_index, faceIndex=face_index + ), + ) + + def get_vertex_layout( + self, mesh_index: int, layout_index: int + ) -> Tuple[int, int, int]: + return cast( + Tuple[int, int, int], + self.reader.getVertexLayout(meshIndex=mesh_index, layoutIndex=layout_index), + ) + + def get_vertex_layout_count(self, mesh_index: int) -> int: + return cast(int, self.reader.getVertexLayoutCount(meshIndex=mesh_index)) + + def get_vertex_position( + self, mesh_index: int, vertex_index: int + ) -> Tuple[float, float, float]: + return cast( + Tuple[float, float, float], + self.reader.getVertexPosition( + meshIndex=mesh_index, vertexIndex=vertex_index + ), + ) + + def get_blend_shape_target_vertex_indices( + self, mesh_index: int, blend_shape_target_index: int + ) -> List[int]: + return cast( + List[int], + self.reader.getBlendShapeTargetVertexIndices( + meshIndex=mesh_index, blendShapeTargetIndex=blend_shape_target_index + ), + ) + + def get_blend_shape_target_delta_count( + self, mesh_index: int, blend_shape_target_index: int + ) -> int: + return cast( + int, + self.reader.getBlendShapeTargetDeltaCount( + meshIndex=mesh_index, blendShapeTargetIndex=blend_shape_target_index + ), + ) + + def get_blend_shape_target_delta( + self, mesh_index: int, blend_shape_target_index: int, delta_index: int + ) -> Tuple[int, int, int]: + return cast( + Tuple[int, int, int], + self.reader.getBlendShapeTargetDelta( + meshIndex=mesh_index, + blendShapeTargetIndex=blend_shape_target_index, + deltaIndex=delta_index, + ), + ) + + def get_blend_shape_target_count(self, mesh_index: int) -> int: + return cast(int, self.reader.getBlendShapeTargetCount(meshIndex=mesh_index)) + + def get_blend_shape_channel_index( + self, mesh_index: int, blend_shape_target_index: int + ) -> int: + return cast( + int, + self.reader.getBlendShapeChannelIndex( + meshIndex=mesh_index, blendShapeTargetIndex=blend_shape_target_index + ), + ) + + def add_mesh(self, mesh_index: int) -> Mesh: + mesh = Mesh() + mesh.name = self.get_mesh_name(mesh_index) + mesh.topology = self.add_mesh_topology(mesh_index) + mesh.skin_weights = self.add_mesh_skin_weights(mesh_index) + mesh.blend_shapes = self.add_mesh_blend_shapes(mesh_index) + return mesh + + def add_mesh_skin_weights(self, mesh_index: int) -> SkinWeightsData: + """Reads in the skin weights""" + skin_weights = SkinWeightsData() + for vertex_index in range(self.get_vertex_position_count(mesh_index)): + skin_weights.values.append( + self.get_skin_weights_values(mesh_index, vertex_index) + ) + skin_weights.joint_indices.append( + self.get_skin_weights_joint_indices(mesh_index, vertex_index) + ) + + return skin_weights + + def add_mesh_topology(self, mesh_index: int) -> Topology: + """Reads in the positions, texture coordinates, normals, layouts and face vertex layouts""" + topology = Topology() + topology.positions = self.add_positions(mesh_index) + topology.texture_coordinates = self.add_texture_coordinates(mesh_index) + topology.layouts = self.add_layouts(mesh_index) + topology.face_vertex_layouts = self.add_face_vertex_layouts(mesh_index) + return topology + + def add_face_vertex_layouts(self, mesh_index: int) -> List[List[int]]: + """Reads in the face vertex layouts""" + face_vertex_layouts = [] + + for face_index in range(self.get_face_count(mesh_index)): + face_vertex_layouts.append( + self.get_face_vertex_layout_indices(mesh_index, face_index) + ) + + return face_vertex_layouts + + def add_layouts(self, mesh_index: int) -> List[Layout]: + """Reads in the vertex layouts""" + layouts = [] + + for layout_index in range(self.get_vertex_layout_count(mesh_index)): + ( + position_id, + texture_coordinate_id, + _, + ) = self.get_vertex_layout(mesh_index, layout_index) + layouts.append( + Layout( + position_index=position_id, + texture_coordinate_index=texture_coordinate_id, + ) + ) + return layouts + + def add_texture_coordinates(self, mesh_index: int) -> List[UV]: + """Reads in the texture coordinates""" + texture_coordinates = [] + for texture_coordinate_index in range( + self.get_vertex_texture_coordinate_count(mesh_index) + ): + u, v = self.get_vertex_texture_coordinate( + mesh_index, texture_coordinate_index + ) + texture_coordinates.append(UV(u=u, v=v)) + return texture_coordinates + + def add_positions(self, mesh_index: int) -> List[Point3]: + """Reads in the vertex positions""" + + positions = [] + for vertex_index in range(self.get_vertex_position_count(mesh_index)): + x, y, z = self.get_vertex_position(mesh_index, vertex_index) + positions.append(Point3(x=x, y=y, z=z)) + return positions + + def read_target_deltas( + self, mesh_index: int, blend_shape_target_index: int + ) -> Dict[int, Point3]: + """ + Reads in the target deltas + + @rtype: Dict[int, Point3] + @returns: Mapping of vertex indices to positions + """ + + result: Dict[int, Point3] = {} + + vertices = self.get_blend_shape_target_vertex_indices( + mesh_index, blend_shape_target_index + ) + + blend_shape_target_delta_count = self.get_blend_shape_target_delta_count( + mesh_index, blend_shape_target_index + ) + for delta_index in range(blend_shape_target_delta_count): + x, y, z = self.get_blend_shape_target_delta( + mesh_index, blend_shape_target_index, delta_index + ) + result[vertices[delta_index]] = Point3(x=x, y=y, z=z) + return result + + def add_mesh_blend_shapes(self, mesh_index: int) -> List[BlendShape]: + """ + Reads in the blend shapes + + @type mesh_index: int + @param mesh_index: The mesh index + """ + + blend_shape_target_count = self.get_blend_shape_target_count(mesh_index) + blend_shapes = [] + for blend_shape_target_index in range(blend_shape_target_count): + blend_shapes.append( + BlendShape( + channel=self.get_blend_shape_channel_index( + mesh_index, blend_shape_target_index + ), + deltas=self.read_target_deltas( + mesh_index, blend_shape_target_index + ), + ) + ) + return blend_shapes diff --git a/scripts/dnalib/layer.py b/scripts/dnalib/layer.py new file mode 100644 index 0000000..1e9c641 --- /dev/null +++ b/scripts/dnalib/layer.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class Layer(Enum): + descriptor = 1 + definition = 2 + behavior = 3 + geometry = 4 + all = 5