From 4045466f733680e130c73b82642f68f17c9817df Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Fri, 24 Feb 2017 08:35:15 +0800 Subject: [PATCH 01/23] pass through but panic --- CMakeLists.txt | 4 + FindDevIL.cmake | 72 +++ FindICU.cmake | 690 ++++++++++++++++++++ code/CMakeLists.txt | 16 +- code/ImporterRegistry.cpp | 8 +- code/MMDCpp14.h | 41 ++ code/MMDImporter.cpp | 190 ++++++ code/MMDImporter.h | 100 +++ code/MMDPmdParser.h | 630 +++++++++++++++++++ code/MMDPmxParser.cpp | 625 +++++++++++++++++++ code/MMDPmxParser.h | 865 ++++++++++++++++++++++++++ code/MMDVmdParser.h | 367 +++++++++++ test/CMakeLists.txt | 1 + test/unit/utPMXImporter.cpp | 62 ++ tools/assimp_qt_viewer/CMakeLists.txt | 2 +- 15 files changed, 3670 insertions(+), 3 deletions(-) create mode 100644 FindDevIL.cmake create mode 100644 FindICU.cmake create mode 100644 code/MMDCpp14.h create mode 100644 code/MMDImporter.cpp create mode 100644 code/MMDImporter.h create mode 100644 code/MMDPmdParser.h create mode 100644 code/MMDPmxParser.cpp create mode 100644 code/MMDPmxParser.h create mode 100644 code/MMDVmdParser.h create mode 100644 test/unit/utPMXImporter.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8aaaf36de..3079d13d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -344,7 +344,11 @@ IF ( ASSIMP_BUILD_ASSIMP_TOOLS ) # Why assimp_qt_viewer/CMakeLists.txt still contain similar check? # Because viewer can be build independently of Assimp. FIND_PACKAGE(Qt5Widgets QUIET) + set(CMAKE_MODULE_PATH_BACKUP ${CMAKE_MODULE_PATH}) + set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}) FIND_PACKAGE(DevIL QUIET) + set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}) + set(CMAKE_MODULE_PATH_BACKUP ${CMAKE_MODULE_PATH}) FIND_PACKAGE(OpenGL QUIET) IF ( Qt5Widgets_FOUND AND IL_FOUND AND OPENGL_FOUND) ADD_SUBDIRECTORY( tools/assimp_qt_viewer/ ) diff --git a/FindDevIL.cmake b/FindDevIL.cmake new file mode 100644 index 000000000..381a75dd2 --- /dev/null +++ b/FindDevIL.cmake @@ -0,0 +1,72 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#.rst: +# FindDevIL +# --------- +# +# +# +# This module locates the developer's image library. +# http://openil.sourceforge.net/ +# +# This module sets: +# +# :: +# +# IL_LIBRARIES - the name of the IL library. These include the full path to +# the core DevIL library. This one has to be linked into the +# application. +# ILU_LIBRARIES - the name of the ILU library. Again, the full path. This +# library is for filters and effects, not actual loading. It +# doesn't have to be linked if the functionality it provides +# is not used. +# ILUT_LIBRARIES - the name of the ILUT library. Full path. This part of the +# library interfaces with OpenGL. It is not strictly needed +# in applications. +# IL_INCLUDE_DIR - where to find the il.h, ilu.h and ilut.h files. +# IL_FOUND - this is set to TRUE if all the above variables were set. +# This will be set to false if ILU or ILUT are not found, +# even if they are not needed. In most systems, if one +# library is found all the others are as well. That's the +# way the DevIL developers release it. + +# TODO: Add version support. +# Tested under Linux and Windows (MSVC) + +#include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake) +include(FindPackageHandleStandardArgs) + +find_path(IL_INCLUDE_DIR il.h + PATH_SUFFIXES include IL + DOC "The path to the directory that contains il.h" +) + +#message("IL_INCLUDE_DIR is ${IL_INCLUDE_DIR}") + +find_library(IL_LIBRARIES + NAMES IL DEVIL + PATH_SUFFIXES lib64 lib lib32 + DOC "The file that corresponds to the base il library." +) + +#message("IL_LIBRARIES is ${IL_LIBRARIES}") + +find_library(ILUT_LIBRARIES + NAMES ILUT + PATH_SUFFIXES lib64 lib lib32 + DOC "The file that corresponds to the il (system?) utility library." +) + +#message("ILUT_LIBRARIES is ${ILUT_LIBRARIES}") + +find_library(ILU_LIBRARIES + NAMES ILU + PATH_SUFFIXES lib64 lib lib32 + DOC "The file that corresponds to the il utility library." +) + +#message("ILU_LIBRARIES is ${ILU_LIBRARIES}") + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(IL DEFAULT_MSG + IL_LIBRARIES IL_INCLUDE_DIR) diff --git a/FindICU.cmake b/FindICU.cmake new file mode 100644 index 000000000..59dd891af --- /dev/null +++ b/FindICU.cmake @@ -0,0 +1,690 @@ +# This module can find the International Components for Unicode (ICU) libraries +# +# Requirements: +# - CMake >= 2.8.3 (for new version of find_package_handle_standard_args) +# +# The following variables will be defined for your use: +# - ICU_FOUND : were all of your specified components found? +# - ICU_INCLUDE_DIRS : ICU include directory +# - ICU_LIBRARIES : ICU libraries +# - ICU_VERSION : complete version of ICU (x.y.z) +# - ICU_VERSION_MAJOR : major version of ICU +# - ICU_VERSION_MINOR : minor version of ICU +# - ICU_VERSION_PATCH : patch version of ICU +# - ICU__FOUND : were found? (FALSE for non specified component if it is not a dependency) +# +# For windows or non standard installation, define ICU_ROOT_DIR variable to point to the root installation of ICU. Two ways: +# - run cmake with -DICU_ROOT_DIR= +# - define an environment variable with the same name before running cmake +# With cmake-gui, before pressing "Configure": +# 1) Press "Add Entry" button +# 2) Add a new entry defined as: +# - Name: ICU_ROOT_DIR +# - Type: choose PATH in the selection list +# - Press "..." button and select the root installation of ICU +# +# Example Usage: +# +# 1. Copy this file in the root of your project source directory +# 2. Then, tell CMake to search this non-standard module in your project directory by adding to your CMakeLists.txt: +# set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}) +# 3. Finally call find_package() once, here are some examples to pick from +# +# Require ICU 4.4 or later +# find_package(ICU 4.4 REQUIRED) +# +# if(ICU_FOUND) +# add_executable(myapp myapp.c) +# include_directories(${ICU_INCLUDE_DIRS}) +# target_link_libraries(myapp ${ICU_LIBRARIES}) +# # with CMake >= 3.0.0, the last two lines can be replaced by the following +# target_link_libraries(myapp ICU::ICU) +# endif(ICU_FOUND) + +########## ########## + +find_package(PkgConfig QUIET) + +########## Private ########## +if(NOT DEFINED ICU_PUBLIC_VAR_NS) + set(ICU_PUBLIC_VAR_NS "ICU") # Prefix for all ICU relative public variables +endif(NOT DEFINED ICU_PUBLIC_VAR_NS) +if(NOT DEFINED ICU_PRIVATE_VAR_NS) + set(ICU_PRIVATE_VAR_NS "_${ICU_PUBLIC_VAR_NS}") # Prefix for all ICU relative internal variables +endif(NOT DEFINED ICU_PRIVATE_VAR_NS) +if(NOT DEFINED PC_ICU_PRIVATE_VAR_NS) + set(PC_ICU_PRIVATE_VAR_NS "_PC${ICU_PRIVATE_VAR_NS}") # Prefix for all pkg-config relative internal variables +endif(NOT DEFINED PC_ICU_PRIVATE_VAR_NS) + +set(${ICU_PRIVATE_VAR_NS}_HINTS ) +# +# for future removal +if(DEFINED ENV{ICU_ROOT}) + list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS "$ENV{ICU_ROOT}") + message(AUTHOR_WARNING "ENV{ICU_ROOT} is deprecated in favor of ENV{ICU_ROOT_DIR}") +endif(DEFINED ENV{ICU_ROOT}) +if (DEFINED ICU_ROOT) + list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS "${ICU_ROOT}") + message(AUTHOR_WARNING "ICU_ROOT is deprecated in favor of ICU_ROOT_DIR") +endif(DEFINED ICU_ROOT) +# +if(DEFINED ENV{ICU_ROOT_DIR}) + list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS "$ENV{ICU_ROOT_DIR}") +endif(DEFINED ENV{ICU_ROOT_DIR}) +if (DEFINED ICU_ROOT_DIR) + list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS "${ICU_ROOT_DIR}") +endif(DEFINED ICU_ROOT_DIR) + +set(${ICU_PRIVATE_VAR_NS}_COMPONENTS ) +# ... +macro(_icu_declare_component _NAME) + list(APPEND ${ICU_PRIVATE_VAR_NS}_COMPONENTS ${_NAME}) + set("${ICU_PRIVATE_VAR_NS}_COMPONENTS_${_NAME}" ${ARGN}) +endmacro(_icu_declare_component) + +_icu_declare_component(data icudata) +_icu_declare_component(uc icuuc) # Common and Data libraries +_icu_declare_component(i18n icui18n icuin) # Internationalization library +_icu_declare_component(io icuio ustdio) # Stream and I/O Library +_icu_declare_component(le icule) # Layout library +_icu_declare_component(lx iculx) # Paragraph Layout library + +########## Public ########## +set(${ICU_PUBLIC_VAR_NS}_FOUND FALSE) +set(${ICU_PUBLIC_VAR_NS}_LIBRARIES ) +set(${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS ) +set(${ICU_PUBLIC_VAR_NS}_C_FLAGS "") +set(${ICU_PUBLIC_VAR_NS}_CXX_FLAGS "") +set(${ICU_PUBLIC_VAR_NS}_CPP_FLAGS "") +set(${ICU_PUBLIC_VAR_NS}_C_SHARED_FLAGS "") +set(${ICU_PUBLIC_VAR_NS}_CXX_SHARED_FLAGS "") +set(${ICU_PUBLIC_VAR_NS}_CPP_SHARED_FLAGS "") + +foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PRIVATE_VAR_NS}_COMPONENTS}) + string(TOUPPER "${${ICU_PRIVATE_VAR_NS}_COMPONENT}" ${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT) + set("${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_FOUND" FALSE) # may be done in the _icu_declare_component macro +endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT) + +# Check components +if(NOT ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS) # uc required at least + set(${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS uc) +else(NOT ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS) + list(APPEND ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS uc) + list(REMOVE_DUPLICATES ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS) + foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS}) + if(NOT DEFINED ${ICU_PRIVATE_VAR_NS}_COMPONENTS_${${ICU_PRIVATE_VAR_NS}_COMPONENT}) + message(FATAL_ERROR "Unknown ICU component: ${${ICU_PRIVATE_VAR_NS}_COMPONENT}") + endif(NOT DEFINED ${ICU_PRIVATE_VAR_NS}_COMPONENTS_${${ICU_PRIVATE_VAR_NS}_COMPONENT}) + endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT) +endif(NOT ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS) + +# if pkg-config is available check components dependencies and append `pkg-config icu- --variable=prefix` to hints +if(PKG_CONFIG_FOUND) + set(${ICU_PRIVATE_VAR_NS}_COMPONENTS_DUP ${${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS}) + foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PRIVATE_VAR_NS}_COMPONENTS_DUP}) + pkg_check_modules(${PC_ICU_PRIVATE_VAR_NS} "icu-${${ICU_PRIVATE_VAR_NS}_COMPONENT}" QUIET) + + if(${PC_ICU_PRIVATE_VAR_NS}_FOUND) + list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS ${${PC_ICU_PRIVATE_VAR_NS}_PREFIX}) + foreach(${PC_ICU_PRIVATE_VAR_NS}_LIBRARY ${${PC_ICU_PRIVATE_VAR_NS}_LIBRARIES}) + string(REGEX REPLACE "^icu" "" ${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY ${${PC_ICU_PRIVATE_VAR_NS}_LIBRARY}) + if(NOT ${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY STREQUAL "data") + list(FIND ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS ${${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY} ${ICU_PRIVATE_VAR_NS}_COMPONENT_INDEX) + if(${ICU_PRIVATE_VAR_NS}_COMPONENT_INDEX EQUAL -1) + message(WARNING "Missing component dependency: ${${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY}. Add it to your find_package(ICU) line as COMPONENTS to fix this warning.") + list(APPEND ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS ${${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY}) + endif(${ICU_PRIVATE_VAR_NS}_COMPONENT_INDEX EQUAL -1) + endif(NOT ${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY STREQUAL "data") + endforeach(${PC_ICU_PRIVATE_VAR_NS}_LIBRARY) + endif(${PC_ICU_PRIVATE_VAR_NS}_FOUND) + endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT) +endif(PKG_CONFIG_FOUND) +# list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS ENV ICU_ROOT_DIR) +# message("${ICU_PRIVATE_VAR_NS}_HINTS = ${${ICU_PRIVATE_VAR_NS}_HINTS}") + +# Includes +find_path( + ${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR + NAMES unicode/utypes.h utypes.h + HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS} + PATH_SUFFIXES "include" + DOC "Include directories for ICU" +) + +if(${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR) + ########## ########## + if(EXISTS "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/uvernum.h") # ICU >= 4.4 + file(READ "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/uvernum.h" ${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS) + elseif(EXISTS "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/uversion.h") # ICU [2;4.4[ + file(READ "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/uversion.h" ${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS) + elseif(EXISTS "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/utypes.h") # ICU [1.4;2[ + file(READ "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/utypes.h" ${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS) + elseif(EXISTS "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/utypes.h") # ICU 1.3 + file(READ "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/utypes.h" ${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS) + else() + message(FATAL_ERROR "ICU version header not found") + endif() + + if(${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS MATCHES ".*# *define *ICU_VERSION *\"([0-9]+)\".*") # ICU 1.3 + # [1.3;1.4[ as #define ICU_VERSION "3" (no patch version, ie all 1.3.X versions will be detected as 1.3.0) + set(${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR "1") + set(${ICU_PUBLIC_VAR_NS}_VERSION_MINOR "${CMAKE_MATCH_1}") + set(${ICU_PUBLIC_VAR_NS}_VERSION_PATCH "0") + elseif(${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS MATCHES ".*# *define *U_ICU_VERSION_MAJOR_NUM *([0-9]+).*") + # + # Since version 4.9.1, ICU release version numbering was totaly changed, see: + # - http://site.icu-project.org/download/49 + # - http://userguide.icu-project.org/design#TOC-Version-Numbers-in-ICU + # + set(${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR "${CMAKE_MATCH_1}") + string(REGEX REPLACE ".*# *define *U_ICU_VERSION_MINOR_NUM *([0-9]+).*" "\\1" ${ICU_PUBLIC_VAR_NS}_VERSION_MINOR "${${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS}") + string(REGEX REPLACE ".*# *define *U_ICU_VERSION_PATCHLEVEL_NUM *([0-9]+).*" "\\1" ${ICU_PUBLIC_VAR_NS}_VERSION_PATCH "${${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS}") + elseif(${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS MATCHES ".*# *define *U_ICU_VERSION *\"(([0-9]+)(\\.[0-9]+)*)\".*") # ICU [1.4;1.8[ + # [1.4;1.8[ as #define U_ICU_VERSION "1.4.1.2" but it seems that some 1.4.[12](?:\.\d)? have releasing error and appears as 1.4.0 + set(${ICU_PRIVATE_VAR_NS}_FULL_VERSION "${CMAKE_MATCH_1}") # copy CMAKE_MATCH_1, no longer valid on the following if + if(${ICU_PRIVATE_VAR_NS}_FULL_VERSION MATCHES "^([0-9]+)\\.([0-9]+)$") + set(${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR "${CMAKE_MATCH_1}") + set(${ICU_PUBLIC_VAR_NS}_VERSION_MINOR "${CMAKE_MATCH_2}") + set(${ICU_PUBLIC_VAR_NS}_VERSION_PATCH "0") + elseif(${ICU_PRIVATE_VAR_NS}_FULL_VERSION MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)") + set(${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR "${CMAKE_MATCH_1}") + set(${ICU_PUBLIC_VAR_NS}_VERSION_MINOR "${CMAKE_MATCH_2}") + set(${ICU_PUBLIC_VAR_NS}_VERSION_PATCH "${CMAKE_MATCH_3}") + endif() + else() + message(FATAL_ERROR "failed to detect ICU version") + endif() + set(${ICU_PUBLIC_VAR_NS}_VERSION "${${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR}.${${ICU_PUBLIC_VAR_NS}_VERSION_MINOR}.${${ICU_PUBLIC_VAR_NS}_VERSION_PATCH}") + ########## ########## +endif(${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR) + +# Check libraries +if(MSVC) + include(SelectLibraryConfigurations) +endif(MSVC) +foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS}) + string(TOUPPER "${${ICU_PRIVATE_VAR_NS}_COMPONENT}" ${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT) + if(MSVC) + set(${ICU_PRIVATE_VAR_NS}_POSSIBLE_RELEASE_NAMES ) + set(${ICU_PRIVATE_VAR_NS}_POSSIBLE_DEBUG_NAMES ) + foreach(${ICU_PRIVATE_VAR_NS}_BASE_NAME ${${ICU_PRIVATE_VAR_NS}_COMPONENTS_${${ICU_PRIVATE_VAR_NS}_COMPONENT}}) + list(APPEND ${ICU_PRIVATE_VAR_NS}_POSSIBLE_RELEASE_NAMES "${${ICU_PRIVATE_VAR_NS}_BASE_NAME}") + list(APPEND ${ICU_PRIVATE_VAR_NS}_POSSIBLE_DEBUG_NAMES "${${ICU_PRIVATE_VAR_NS}_BASE_NAME}d") + list(APPEND ${ICU_PRIVATE_VAR_NS}_POSSIBLE_RELEASE_NAMES "${${ICU_PRIVATE_VAR_NS}_BASE_NAME}${${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR}${${ICU_PUBLIC_VAR_NS}_VERSION_MINOR}") + list(APPEND ${ICU_PRIVATE_VAR_NS}_POSSIBLE_DEBUG_NAMES "${${ICU_PRIVATE_VAR_NS}_BASE_NAME}${${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR}${${ICU_PUBLIC_VAR_NS}_VERSION_MINOR}d") + endforeach(${ICU_PRIVATE_VAR_NS}_BASE_NAME) + + find_library( + ${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_RELEASE + NAMES ${${ICU_PRIVATE_VAR_NS}_POSSIBLE_RELEASE_NAMES} + HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS} + DOC "Release library for ICU ${${ICU_PRIVATE_VAR_NS}_COMPONENT} component" + ) + find_library( + ${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_DEBUG + NAMES ${${ICU_PRIVATE_VAR_NS}_POSSIBLE_DEBUG_NAMES} + HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS} + DOC "Debug library for ICU ${${ICU_PRIVATE_VAR_NS}_COMPONENT} component" + ) + + select_library_configurations("${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}") + list(APPEND ${ICU_PUBLIC_VAR_NS}_LIBRARY ${${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY}) + else(MSVC) + find_library( + ${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY + NAMES ${${ICU_PRIVATE_VAR_NS}_COMPONENTS_${${ICU_PRIVATE_VAR_NS}_COMPONENT}} + PATHS ${${ICU_PRIVATE_VAR_NS}_HINTS} + DOC "Library for ICU ${${ICU_PRIVATE_VAR_NS}_COMPONENT} component" + ) + + if(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY) + set("${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_FOUND" TRUE) + list(APPEND ${ICU_PUBLIC_VAR_NS}_LIBRARY ${${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY}) + endif(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY) + endif(MSVC) +endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT) + +# Try to find out compiler flags +find_program(${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE icu-config HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS}) +if(${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE) + execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cflags OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_C_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cxxflags OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_CXX_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cppflags OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_CPP_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) + + execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cflags-dynamic OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_C_SHARED_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cxxflags-dynamic OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_CXX_SHARED_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cppflags-dynamic OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_CPP_SHARED_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) +endif(${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE) + +# Check find_package arguments +include(FindPackageHandleStandardArgs) +if(${ICU_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${ICU_PUBLIC_VAR_NS}_FIND_QUIETLY) + find_package_handle_standard_args( + ${ICU_PUBLIC_VAR_NS} + REQUIRED_VARS ${ICU_PUBLIC_VAR_NS}_LIBRARY ${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR + VERSION_VAR ${ICU_PUBLIC_VAR_NS}_VERSION + ) +else(${ICU_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${ICU_PUBLIC_VAR_NS}_FIND_QUIETLY) + find_package_handle_standard_args(${ICU_PUBLIC_VAR_NS} "Could NOT find ICU" ${ICU_PUBLIC_VAR_NS}_LIBRARY ${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR) +endif(${ICU_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${ICU_PUBLIC_VAR_NS}_FIND_QUIETLY) + +if(${ICU_PUBLIC_VAR_NS}_FOUND) + # + # for compatibility with previous versions, alias old ICU_(MAJOR|MINOR|PATCH)_VERSION to ICU_VERSION_$1 + set(${ICU_PUBLIC_VAR_NS}_MAJOR_VERSION ${${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR}) + set(${ICU_PUBLIC_VAR_NS}_MINOR_VERSION ${${ICU_PUBLIC_VAR_NS}_VERSION_MINOR}) + set(${ICU_PUBLIC_VAR_NS}_PATCH_VERSION ${${ICU_PUBLIC_VAR_NS}_VERSION_PATCH}) + # + set(${ICU_PUBLIC_VAR_NS}_LIBRARIES ${${ICU_PUBLIC_VAR_NS}_LIBRARY}) + set(${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS ${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}) + + if(NOT CMAKE_VERSION VERSION_LESS "3.0.0") + if(NOT TARGET ICU::ICU) + add_library(ICU::ICU INTERFACE IMPORTED) + endif(NOT TARGET ICU::ICU) + set_target_properties(ICU::ICU PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}") + foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS}) + string(TOUPPER "${${ICU_PRIVATE_VAR_NS}_COMPONENT}" ${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT) + add_library("ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" UNKNOWN IMPORTED) + if(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_RELEASE) + set_property(TARGET "ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) + set_target_properties("ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" PROPERTIES IMPORTED_LOCATION_RELEASE "${${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_RELEASE}") + endif(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_RELEASE) + if(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_DEBUG) + set_property(TARGET "ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG) + set_target_properties("ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" PROPERTIES IMPORTED_LOCATION_DEBUG "${${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_DEBUG}") + endif(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_DEBUG) + if(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY) + set_target_properties("ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" PROPERTIES IMPORTED_LOCATION "${${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY}") + endif(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY) + set_property(TARGET ICU::ICU APPEND PROPERTY INTERFACE_LINK_LIBRARIES "ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}") +# set_target_properties("ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}") + endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT) + endif(NOT CMAKE_VERSION VERSION_LESS "3.0.0") +endif(${ICU_PUBLIC_VAR_NS}_FOUND) + +mark_as_advanced( + ${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR + ${ICU_PUBLIC_VAR_NS}_LIBRARY +) + +########## ########## + +########## ########## + +########## Private ########## +function(_icu_extract_locale_from_rb _BUNDLE_SOURCE _RETURN_VAR_NAME) + file(READ "${_BUNDLE_SOURCE}" _BUNDLE_CONTENTS) + string(REGEX REPLACE "//[^\n]*\n" "" _BUNDLE_CONTENTS_WITHOUT_COMMENTS ${_BUNDLE_CONTENTS}) + string(REGEX REPLACE "[ \t\n]" "" _BUNDLE_CONTENTS_WITHOUT_COMMENTS_AND_SPACES ${_BUNDLE_CONTENTS_WITHOUT_COMMENTS}) + string(REGEX MATCH "^([a-zA-Z_-]+)(:table)?{" LOCALE_FOUND ${_BUNDLE_CONTENTS_WITHOUT_COMMENTS_AND_SPACES}) + set("${_RETURN_VAR_NAME}" "${CMAKE_MATCH_1}" PARENT_SCOPE) +endfunction(_icu_extract_locale_from_rb) + +########## Public ########## + +# +# Prototype: +# icu_generate_resource_bundle([NAME ] [PACKAGE] [DESTINATION ] [FILES ]) +# +# Common arguments: +# - NAME : name of output package and to create dummy targets +# - FILES ... : list of resource bundles sources +# - DEPENDS ... : required to package as library (shared or static), a list of cmake parent targets to link to +# Note: only (PREVIOUSLY DECLARED) add_executable and add_library as dependencies +# - DESTINATION : optional, directory where to install final binary file(s) +# - FORMAT : optional, one of none (ICU4C binary format, default), java (plain java) or xliff (XML), see below +# +# Arguments depending on FORMAT: +# - none (default): +# * PACKAGE : if present, package all resource bundles together. Default is to stop after building individual *.res files +# * TYPE : one of : +# + common or archive (default) : archive all ressource bundles into a single .dat file +# + library or dll : assemble all ressource bundles into a separate and loadable library (.dll/.so) +# + static : integrate all ressource bundles to targets designed by DEPENDS parameter (as a static library) +# * NO_SHARED_FLAGS : only with TYPE in ['library', 'dll', 'static'], do not append ICU_C(XX)_SHARED_FLAGS to targets given as DEPENDS argument +# - JAVA: +# * BUNDLE : required, prefix for generated classnames +# - XLIFF: +# (none) +# + +# +# For an archive, the idea is to generate the following dependencies: +# +# root.txt => root.res \ +# | +# en.txt => en.res | +# | => pkglist.txt => application.dat +# fr.txt => fr.res | +# | +# and so on / +# +# Lengend: 'A => B' means B depends on A +# +# Steps (correspond to arrows): +# 1) genrb (from .txt to .res) +# 2) generate a file text (pkglist.txt) with all .res files to put together +# 3) build final archive (from *.res/pkglist.txt to .dat) +# + +function(icu_generate_resource_bundle) + + ##### ##### + find_program(${ICU_PUBLIC_VAR_NS}_GENRB_EXECUTABLE genrb HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS}) + find_program(${ICU_PUBLIC_VAR_NS}_PKGDATA_EXECUTABLE pkgdata HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS}) + + if(NOT ${ICU_PUBLIC_VAR_NS}_GENRB_EXECUTABLE) + message(FATAL_ERROR "genrb not found") + endif(NOT ${ICU_PUBLIC_VAR_NS}_GENRB_EXECUTABLE) + if(NOT ${ICU_PUBLIC_VAR_NS}_PKGDATA_EXECUTABLE) + message(FATAL_ERROR "pkgdata not found") + endif(NOT ${ICU_PUBLIC_VAR_NS}_PKGDATA_EXECUTABLE) + ##### ##### + + ##### ##### + set(TARGET_SEPARATOR "+") + set(__FUNCTION__ "icu_generate_resource_bundle") + set(PACKAGE_TARGET_PREFIX "ICU${TARGET_SEPARATOR}PKG") + set(RESOURCE_TARGET_PREFIX "ICU${TARGET_SEPARATOR}RB") + ##### ##### + + ##### ##### + # filename extension of built resource bundle (without dot) + set(BUNDLES__SUFFIX "res") + set(BUNDLES_JAVA_SUFFIX "java") + set(BUNDLES_XLIFF_SUFFIX "xlf") + # alias: none (default) = common = archive ; dll = library ; static + set(PKGDATA__ALIAS "") + set(PKGDATA_COMMON_ALIAS "") + set(PKGDATA_ARCHIVE_ALIAS "") + set(PKGDATA_DLL_ALIAS "LIBRARY") + set(PKGDATA_LIBRARY_ALIAS "LIBRARY") + set(PKGDATA_STATIC_ALIAS "STATIC") + # filename prefix of built package + set(PKGDATA__PREFIX "") + set(PKGDATA_LIBRARY_PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}") + set(PKGDATA_STATIC_PREFIX "${CMAKE_STATIC_LIBRARY_PREFIX}") + # filename extension of built package (with dot) + set(PKGDATA__SUFFIX ".dat") + set(PKGDATA_LIBRARY_SUFFIX "${CMAKE_SHARED_LIBRARY_SUFFIX}") + set(PKGDATA_STATIC_SUFFIX "${CMAKE_STATIC_LIBRARY_SUFFIX}") + # pkgdata option mode specific + set(PKGDATA__OPTIONS "-m" "common") + set(PKGDATA_STATIC_OPTIONS "-m" "static") + set(PKGDATA_LIBRARY_OPTIONS "-m" "library") + # cmake library type for output package + set(PKGDATA_LIBRARY__TYPE "") + set(PKGDATA_LIBRARY_STATIC_TYPE STATIC) + set(PKGDATA_LIBRARY_LIBRARY_TYPE SHARED) + ##### ##### + + include(CMakeParseArguments) + cmake_parse_arguments( + PARSED_ARGS # output variable name + # options (true/false) (default value: false) + "PACKAGE;NO_SHARED_FLAGS" + # univalued parameters (default value: "") + "NAME;DESTINATION;TYPE;FORMAT;BUNDLE" + # multivalued parameters (default value: "") + "FILES;DEPENDS" + ${ARGN} + ) + + # assert(${PARSED_ARGS_NAME} != "") + if(NOT PARSED_ARGS_NAME) + message(FATAL_ERROR "${__FUNCTION__}(): no name given, NAME parameter missing") + endif(NOT PARSED_ARGS_NAME) + + # assert(length(PARSED_ARGS_FILES) > 0) + list(LENGTH PARSED_ARGS_FILES PARSED_ARGS_FILES_LEN) + if(PARSED_ARGS_FILES_LEN LESS 1) + message(FATAL_ERROR "${__FUNCTION__}() expects at least 1 resource bundle as FILES argument, 0 given") + endif(PARSED_ARGS_FILES_LEN LESS 1) + + string(TOUPPER "${PARSED_ARGS_FORMAT}" UPPER_FORMAT) + # assert(${UPPER_FORMAT} in ['', 'java', 'xlif']) + if(NOT DEFINED BUNDLES_${UPPER_FORMAT}_SUFFIX) + message(FATAL_ERROR "${__FUNCTION__}(): unknown FORMAT '${PARSED_ARGS_FORMAT}'") + endif(NOT DEFINED BUNDLES_${UPPER_FORMAT}_SUFFIX) + + if(UPPER_FORMAT STREQUAL "JAVA") + # assert(${PARSED_ARGS_BUNDLE} != "") + if(NOT PARSED_ARGS_BUNDLE) + message(FATAL_ERROR "${__FUNCTION__}(): java bundle name expected, BUNDLE parameter missing") + endif(NOT PARSED_ARGS_BUNDLE) + endif(UPPER_FORMAT STREQUAL "JAVA") + + if(PARSED_ARGS_PACKAGE) + # assert(${PARSED_ARGS_FORMAT} == "") + if(PARSED_ARGS_FORMAT) + message(FATAL_ERROR "${__FUNCTION__}(): packaging is only supported for binary format, not xlif neither java outputs") + endif(PARSED_ARGS_FORMAT) + + string(TOUPPER "${PARSED_ARGS_TYPE}" UPPER_MODE) + # assert(${UPPER_MODE} in ['', 'common', 'archive', 'dll', library']) + if(NOT DEFINED PKGDATA_${UPPER_MODE}_ALIAS) + message(FATAL_ERROR "${__FUNCTION__}(): unknown TYPE '${PARSED_ARGS_TYPE}'") + else(NOT DEFINED PKGDATA_${UPPER_MODE}_ALIAS) + set(TYPE "${PKGDATA_${UPPER_MODE}_ALIAS}") + endif(NOT DEFINED PKGDATA_${UPPER_MODE}_ALIAS) + + # Package name: strip file extension if present + get_filename_component(PACKAGE_NAME_WE ${PARSED_ARGS_NAME} NAME_WE) + # Target name to build package + set(PACKAGE_TARGET_NAME "${PACKAGE_TARGET_PREFIX}${TARGET_SEPARATOR}${PACKAGE_NAME_WE}") + # Target name to build intermediate list file + set(PACKAGE_LIST_TARGET_NAME "${PACKAGE_TARGET_NAME}${TARGET_SEPARATOR}PKGLIST") + # Directory (absolute) to set as "current directory" for genrb (does not include package directory, -p) + # We make our "cook" there to prevent any conflict + if(DEFINED CMAKE_PLATFORM_ROOT_BIN) # CMake < 2.8.10 + set(RESOURCE_GENRB_CHDIR_DIR "${CMAKE_PLATFORM_ROOT_BIN}/${PACKAGE_TARGET_NAME}.dir/") + else(DEFINED CMAKE_PLATFORM_ROOT_BIN) # CMake >= 2.8.10 + set(RESOURCE_GENRB_CHDIR_DIR "${CMAKE_PLATFORM_INFO_DIR}/${PACKAGE_TARGET_NAME}.dir/") + endif(DEFINED CMAKE_PLATFORM_ROOT_BIN) + # Directory (absolute) where resource bundles are built: concatenation of RESOURCE_GENRB_CHDIR_DIR and package name + set(RESOURCE_OUTPUT_DIR "${RESOURCE_GENRB_CHDIR_DIR}/${PACKAGE_NAME_WE}/") + # Output (relative) path for built package + if(MSVC AND TYPE STREQUAL PKGDATA_LIBRARY_ALIAS) + set(PACKAGE_OUTPUT_PATH "${RESOURCE_GENRB_CHDIR_DIR}/${PACKAGE_NAME_WE}/${PKGDATA_${TYPE}_PREFIX}${PACKAGE_NAME_WE}${PKGDATA_${TYPE}_SUFFIX}") + else(MSVC AND TYPE STREQUAL PKGDATA_LIBRARY_ALIAS) + set(PACKAGE_OUTPUT_PATH "${RESOURCE_GENRB_CHDIR_DIR}/${PKGDATA_${TYPE}_PREFIX}${PACKAGE_NAME_WE}${PKGDATA_${TYPE}_SUFFIX}") + endif(MSVC AND TYPE STREQUAL PKGDATA_LIBRARY_ALIAS) + # Output (absolute) path for the list file + set(PACKAGE_LIST_OUTPUT_PATH "${RESOURCE_GENRB_CHDIR_DIR}/pkglist.txt") + + file(MAKE_DIRECTORY "${RESOURCE_OUTPUT_DIR}") + else(PARSED_ARGS_PACKAGE) + set(RESOURCE_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/") +# set(RESOURCE_GENRB_CHDIR_DIR "UNUSED") + endif(PARSED_ARGS_PACKAGE) + + set(TARGET_RESOURCES ) + set(COMPILED_RESOURCES_PATH ) + set(COMPILED_RESOURCES_BASENAME ) + foreach(RESOURCE_SOURCE ${PARSED_ARGS_FILES}) + _icu_extract_locale_from_rb(${RESOURCE_SOURCE} RESOURCE_NAME_WE) + get_filename_component(SOURCE_BASENAME ${RESOURCE_SOURCE} NAME) + get_filename_component(ABSOLUTE_SOURCE ${RESOURCE_SOURCE} ABSOLUTE) + + if(UPPER_FORMAT STREQUAL "XLIFF") + if(RESOURCE_NAME_WE STREQUAL "root") + set(XLIFF_LANGUAGE "en") + else(RESOURCE_NAME_WE STREQUAL "root") + string(REGEX REPLACE "[^a-z].*$" "" XLIFF_LANGUAGE "${RESOURCE_NAME_WE}") + endif(RESOURCE_NAME_WE STREQUAL "root") + endif(UPPER_FORMAT STREQUAL "XLIFF") + + ##### ##### + set(RESOURCE_TARGET_NAME "${RESOURCE_TARGET_PREFIX}${TARGET_SEPARATOR}${PARSED_ARGS_NAME}${TARGET_SEPARATOR}${RESOURCE_NAME_WE}") + + set(RESOURCE_OUTPUT__PATH "${RESOURCE_NAME_WE}.res") + if(RESOURCE_NAME_WE STREQUAL "root") + set(RESOURCE_OUTPUT_JAVA_PATH "${PARSED_ARGS_BUNDLE}.java") + else(RESOURCE_NAME_WE STREQUAL "root") + set(RESOURCE_OUTPUT_JAVA_PATH "${PARSED_ARGS_BUNDLE}_${RESOURCE_NAME_WE}.java") + endif(RESOURCE_NAME_WE STREQUAL "root") + set(RESOURCE_OUTPUT_XLIFF_PATH "${RESOURCE_NAME_WE}.xlf") + + set(GENRB__OPTIONS "") + set(GENRB_JAVA_OPTIONS "-j" "-b" "${PARSED_ARGS_BUNDLE}") + set(GENRB_XLIFF_OPTIONS "-x" "-l" "${XLIFF_LANGUAGE}") + ##### ##### + + # build .txt from .res + if(PARSED_ARGS_PACKAGE) + add_custom_command( + OUTPUT "${RESOURCE_OUTPUT_DIR}${RESOURCE_OUTPUT_${UPPER_FORMAT}_PATH}" + COMMAND ${CMAKE_COMMAND} -E chdir ${RESOURCE_GENRB_CHDIR_DIR} ${${ICU_PUBLIC_VAR_NS}_GENRB_EXECUTABLE} ${GENRB_${UPPER_FORMAT}_OPTIONS} -d ${PACKAGE_NAME_WE} ${ABSOLUTE_SOURCE} + DEPENDS ${RESOURCE_SOURCE} + ) + else(PARSED_ARGS_PACKAGE) + add_custom_command( + OUTPUT "${RESOURCE_OUTPUT_DIR}${RESOURCE_OUTPUT_${UPPER_FORMAT}_PATH}" + COMMAND ${${ICU_PUBLIC_VAR_NS}_GENRB_EXECUTABLE} ${GENRB_${UPPER_FORMAT}_OPTIONS} -d ${RESOURCE_OUTPUT_DIR} ${ABSOLUTE_SOURCE} + DEPENDS ${RESOURCE_SOURCE} + ) + endif(PARSED_ARGS_PACKAGE) + # dummy target (ICU+RB++) for each locale to build the .res file from its .txt by the add_custom_command above + add_custom_target( + "${RESOURCE_TARGET_NAME}" ALL + COMMENT "" + DEPENDS "${RESOURCE_OUTPUT_DIR}${RESOURCE_OUTPUT_${UPPER_FORMAT}_PATH}" + SOURCES ${RESOURCE_SOURCE} + ) + + if(PARSED_ARGS_DESTINATION AND NOT PARSED_ARGS_PACKAGE) + install(FILES "${RESOURCE_OUTPUT_DIR}${RESOURCE_OUTPUT_${UPPER_FORMAT}_PATH}" DESTINATION ${PARSED_ARGS_DESTINATION} PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) + endif(PARSED_ARGS_DESTINATION AND NOT PARSED_ARGS_PACKAGE) + + list(APPEND TARGET_RESOURCES "${RESOURCE_TARGET_NAME}") + list(APPEND COMPILED_RESOURCES_PATH "${RESOURCE_OUTPUT_DIR}${RESOURCE_OUTPUT_${UPPER_FORMAT}_PATH}") + list(APPEND COMPILED_RESOURCES_BASENAME "${RESOURCE_NAME_WE}.${BUNDLES_${UPPER_FORMAT}_SUFFIX}") + endforeach(RESOURCE_SOURCE) + # convert semicolon separated list to a space separated list + # NOTE: if the pkglist.txt file starts (or ends?) with a whitespace, pkgdata add an undefined symbol (named _) for it + string(REPLACE ";" " " COMPILED_RESOURCES_BASENAME "${COMPILED_RESOURCES_BASENAME}") + + if(PARSED_ARGS_PACKAGE) + # create a text file (pkglist.txt) with the list of the *.res to package together + add_custom_command( + OUTPUT "${PACKAGE_LIST_OUTPUT_PATH}" + COMMAND ${CMAKE_COMMAND} -E echo "${COMPILED_RESOURCES_BASENAME}" > "${PACKAGE_LIST_OUTPUT_PATH}" + DEPENDS ${COMPILED_RESOURCES_PATH} + ) + # run pkgdata from pkglist.txt + add_custom_command( + OUTPUT "${PACKAGE_OUTPUT_PATH}" + COMMAND ${CMAKE_COMMAND} -E chdir ${RESOURCE_GENRB_CHDIR_DIR} ${${ICU_PUBLIC_VAR_NS}_PKGDATA_EXECUTABLE} -F ${PKGDATA_${TYPE}_OPTIONS} -s ${PACKAGE_NAME_WE} -p ${PACKAGE_NAME_WE} ${PACKAGE_LIST_OUTPUT_PATH} + DEPENDS "${PACKAGE_LIST_OUTPUT_PATH}" + VERBATIM + ) + if(PKGDATA_LIBRARY_${TYPE}_TYPE) + # assert(${PARSED_ARGS_DEPENDS} != "") + if(NOT PARSED_ARGS_DEPENDS) + message(FATAL_ERROR "${__FUNCTION__}(): static and library mode imply a list of targets to link to, DEPENDS parameter missing") + endif(NOT PARSED_ARGS_DEPENDS) + add_library(${PACKAGE_TARGET_NAME} ${PKGDATA_LIBRARY_${TYPE}_TYPE} IMPORTED) + if(MSVC) + string(REGEX REPLACE "${PKGDATA_LIBRARY_SUFFIX}\$" "${CMAKE_IMPORT_LIBRARY_SUFFIX}" PACKAGE_OUTPUT_LIB "${PACKAGE_OUTPUT_PATH}") + set_target_properties(${PACKAGE_TARGET_NAME} PROPERTIES IMPORTED_LOCATION ${PACKAGE_OUTPUT_PATH} IMPORTED_IMPLIB ${PACKAGE_OUTPUT_LIB}) + else(MSVC) + set_target_properties(${PACKAGE_TARGET_NAME} PROPERTIES IMPORTED_LOCATION ${PACKAGE_OUTPUT_PATH}) + endif(MSVC) + foreach(DEPENDENCY ${PARSED_ARGS_DEPENDS}) + target_link_libraries(${DEPENDENCY} ${PACKAGE_TARGET_NAME}) + if(NOT PARSED_ARGS_NO_SHARED_FLAGS) + get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) + list(LENGTH "${ENABLED_LANGUAGES}" ENABLED_LANGUAGES_LENGTH) + if(ENABLED_LANGUAGES_LENGTH GREATER 1) + message(WARNING "Project has more than one language enabled, skip automatic shared flags appending") + else(ENABLED_LANGUAGES_LENGTH GREATER 1) + set_property(TARGET "${DEPENDENCY}" APPEND PROPERTY COMPILE_FLAGS "${${ICU_PUBLIC_VAR_NS}_${ENABLED_LANGUAGES}_SHARED_FLAGS}") + endif(ENABLED_LANGUAGES_LENGTH GREATER 1) + endif(NOT PARSED_ARGS_NO_SHARED_FLAGS) + endforeach(DEPENDENCY) + # http://www.mail-archive.com/cmake-commits@cmake.org/msg01135.html + set(PACKAGE_INTERMEDIATE_TARGET_NAME "${PACKAGE_TARGET_NAME}${TARGET_SEPARATOR}DUMMY") + # dummy intermediate target (ICU+PKG++DUMMY) to link the package to the produced library by running pkgdata (see add_custom_command above) + add_custom_target( + ${PACKAGE_INTERMEDIATE_TARGET_NAME} + COMMENT "" + DEPENDS "${PACKAGE_OUTPUT_PATH}" + ) + add_dependencies("${PACKAGE_TARGET_NAME}" "${PACKAGE_INTERMEDIATE_TARGET_NAME}") + else(PKGDATA_LIBRARY_${TYPE}_TYPE) + # dummy target (ICU+PKG+) to run pkgdata (see add_custom_command above) + add_custom_target( + "${PACKAGE_TARGET_NAME}" ALL + COMMENT "" + DEPENDS "${PACKAGE_OUTPUT_PATH}" + ) + endif(PKGDATA_LIBRARY_${TYPE}_TYPE) + # dummy target (ICU+PKG++PKGLIST) to build the file pkglist.txt + add_custom_target( + "${PACKAGE_LIST_TARGET_NAME}" ALL + COMMENT "" + DEPENDS "${PACKAGE_LIST_OUTPUT_PATH}" + ) + # package => pkglist.txt + add_dependencies("${PACKAGE_TARGET_NAME}" "${PACKAGE_LIST_TARGET_NAME}") + # pkglist.txt => *.res + add_dependencies("${PACKAGE_LIST_TARGET_NAME}" ${TARGET_RESOURCES}) + + if(PARSED_ARGS_DESTINATION) + install(FILES "${PACKAGE_OUTPUT_PATH}" DESTINATION ${PARSED_ARGS_DESTINATION} PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) + endif(PARSED_ARGS_DESTINATION) + endif(PARSED_ARGS_PACKAGE) + +endfunction(icu_generate_resource_bundle) + +########## ########## + +########## ########## + +if(${ICU_PUBLIC_VAR_NS}_DEBUG) + + function(icudebug _VARNAME) + if(DEFINED ${ICU_PUBLIC_VAR_NS}_${_VARNAME}) + message("${ICU_PUBLIC_VAR_NS}_${_VARNAME} = ${${ICU_PUBLIC_VAR_NS}_${_VARNAME}}") + else(DEFINED ${ICU_PUBLIC_VAR_NS}_${_VARNAME}) + message("${ICU_PUBLIC_VAR_NS}_${_VARNAME} = ") + endif(DEFINED ${ICU_PUBLIC_VAR_NS}_${_VARNAME}) + endfunction(icudebug) + + # IN (args) + icudebug("FIND_COMPONENTS") + icudebug("FIND_REQUIRED") + icudebug("FIND_QUIETLY") + icudebug("FIND_VERSION") + + # OUT + # Found + icudebug("FOUND") + # Flags + icudebug("C_FLAGS") + icudebug("CPP_FLAGS") + icudebug("CXX_FLAGS") + icudebug("C_SHARED_FLAGS") + icudebug("CPP_SHARED_FLAGS") + icudebug("CXX_SHARED_FLAGS") + # Linking + icudebug("INCLUDE_DIRS") + icudebug("LIBRARIES") + # Version + icudebug("VERSION_MAJOR") + icudebug("VERSION_MINOR") + icudebug("VERSION_PATCH") + icudebug("VERSION") + # _(FOUND|LIBRARY) + set(${ICU_PRIVATE_VAR_NS}_COMPONENT_VARIABLES "FOUND" "LIBRARY" "LIBRARY_RELEASE" "LIBRARY_DEBUG") + foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PRIVATE_VAR_NS}_COMPONENTS}) + string(TOUPPER "${${ICU_PRIVATE_VAR_NS}_COMPONENT}" ${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT) + foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT_VARIABLE ${${ICU_PRIVATE_VAR_NS}_COMPONENT_VARIABLES}) + icudebug("${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_${${ICU_PRIVATE_VAR_NS}_COMPONENT_VARIABLE}") + endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT_VARIABLE) + endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT) + +endif(${ICU_PUBLIC_VAR_NS}_DEBUG) + +########## ########## diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 92199c234..2442513cb 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -657,6 +657,18 @@ ADD_ASSIMP_IMPORTER( 3MF D3MFOpcPackage.cpp ) +ADD_ASSIMP_IMPORTER( MMD + MMDCpp14.h + MMDImporter.cpp + MMDImporter.h + MMDPmdParser.h + MMDPmxParser.h + MMDPmxParser.cpp + MMDVmdParser.h +) + +find_package(ICU COMPONENTS uc io REQUIRED) + SET( Step_SRCS StepExporter.h StepExporter.cpp @@ -850,9 +862,11 @@ IF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) INCLUDE_DIRECTORIES(${C4D_INCLUDES}) ENDIF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) +INCLUDE_DIRECTORIES(${ICU_INCLUDE_DIRS}) + ADD_LIBRARY( assimp ${assimp_src} ) -TARGET_LINK_LIBRARIES(assimp ${ZLIB_LIBRARIES} ${OPENDDL_PARSER_LIBRARIES} ) +TARGET_LINK_LIBRARIES(assimp ${ZLIB_LIBRARIES} ${OPENDDL_PARSER_LIBRARIES} ${ICU_LIBRARIES} ) if(ANDROID AND ASSIMP_ANDROID_JNIIOSYSTEM) set(ASSIMP_ANDROID_JNIIOSYSTEM_PATH port/AndroidJNI) diff --git a/code/ImporterRegistry.cpp b/code/ImporterRegistry.cpp index 95a1867e0..7f1751654 100644 --- a/code/ImporterRegistry.cpp +++ b/code/ImporterRegistry.cpp @@ -188,6 +188,9 @@ corresponding preprocessor flag to selectively disable formats. #ifndef ASSIMP_BUILD_NO_X3D_IMPORTER # include "X3DImporter.hpp" #endif +#ifndef ASSIMP_BUILD_NO_MMD_IMPORTER +# include "MMDImporter.h" +#endif namespace Assimp { @@ -332,11 +335,14 @@ void GetImporterInstanceList(std::vector< BaseImporter* >& out) out.push_back( new C4DImporter() ); #endif #if ( !defined ASSIMP_BUILD_NO_3MF_IMPORTER ) - out.push_back(new D3MFImporter() ); + out.push_back( new D3MFImporter() ); #endif #ifndef ASSIMP_BUILD_NO_X3D_IMPORTER out.push_back( new X3DImporter() ); #endif +#ifndef ASSIMP_BUILD_NO_MMD_IMPORTER + out.push_back( new MMDImporter() ); +#endif } /** will delete all registered importers. */ diff --git a/code/MMDCpp14.h b/code/MMDCpp14.h new file mode 100644 index 000000000..212897dc2 --- /dev/null +++ b/code/MMDCpp14.h @@ -0,0 +1,41 @@ +#pragma once + +#ifndef MMD_CPP14_H + +#include +#include +#include +#include + +namespace std { + template struct _Unique_if { + typedef unique_ptr _Single_object; + }; + + template struct _Unique_if { + typedef unique_ptr _Unknown_bound; + }; + + template struct _Unique_if { + typedef void _Known_bound; + }; + + template + typename _Unique_if::_Single_object + make_unique(Args&&... args) { + return unique_ptr(new T(std::forward(args)...)); + } + + template + typename _Unique_if::_Unknown_bound + make_unique(size_t n) { + typedef typename remove_extent::type U; + return unique_ptr(new U[n]()); + } + + template + typename _Unique_if::_Known_bound + make_unique(Args&&...) = delete; +} + +#endif \ No newline at end of file diff --git a/code/MMDImporter.cpp b/code/MMDImporter.cpp new file mode 100644 index 000000000..4179a7cc9 --- /dev/null +++ b/code/MMDImporter.cpp @@ -0,0 +1,190 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2016, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_MMD_IMPORTER + +#include "DefaultIOSystem.h" +#include "MMDImporter.h" +#include "MMDPmxParser.h" +#include "MMDPmdParser.h" +#include "MMDVmdParser.h" +//#include "IOStreamBuffer.h" +#include +#include +#include +#include +#include +#include +#include + +static const aiImporterDesc desc = { + "MMD Importer", + "", + "", + "surfaces supported?", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "pmx" +}; + +namespace Assimp { + +using namespace std; + +// ------------------------------------------------------------------------------------------------ +// Default constructor +MMDImporter::MMDImporter() : + m_Buffer(), + //m_pRootObject( NULL ), + m_strAbsPath( "" ) +{ + DefaultIOSystem io; + m_strAbsPath = io.getOsSeparator(); +} + +// ------------------------------------------------------------------------------------------------ +// Destructor. +MMDImporter::~MMDImporter() +{ + //delete m_pRootObject; + //m_pRootObject = NULL; +} + +// ------------------------------------------------------------------------------------------------ +// Returns true, if file is an pmx file. +bool MMDImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler , bool checkSig ) const +{ + if(!checkSig) //Check File Extension + { + return SimpleExtensionCheck(pFile,"pmx"); + } + else //Check file Header + { + static const char *pTokens[] = { "PMX " }; + return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, pTokens, 1 ); + } +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc* MMDImporter::GetInfo () const +{ + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// MMD import implementation +void MMDImporter::InternReadFile( const std::string &file, aiScene* pScene, IOSystem* pIOHandler) +{ + // Read file by istream + std::filebuf fb; + if( !fb.open(file, std::ios::in | std::ios::binary ) ) { + throw DeadlyImportError( "Failed to open file " + file + "." ); + } + + std::istream fileStream( &fb ); + + // Get the file-size and validate it, throwing an exception when fails + fileStream.seekg(0, fileStream.end); + size_t fileSize = fileStream.tellg(); + fileStream.seekg(0, fileStream.beg); + + if( fileSize < sizeof(pmx::PmxModel) ) { + throw DeadlyImportError( file + " is too small." ); + } + + pmx::PmxModel model; + model.Read(&fileStream); + + CreateDataFromImport(&model, pScene); +} + +// ------------------------------------------------------------------------------------------------ +void MMDImporter::CreateDataFromImport(const pmx::PmxModel* pModel, aiScene* pScene) +{ + if( pModel == NULL ) { + return; + } + + pScene->mRootNode = new aiNode; + if ( !pModel->model_name.empty() ) { + pScene->mRootNode->mName.Set(pModel->model_name); + } + else { + ai_assert(false); + } + + std::cout << pScene->mRootNode->mName.C_Str() << std::endl; + + // workaround, must be deleted + pScene->mNumMeshes = 1; + pScene->mNumMaterials = 1; + pScene->mRootNode->mMeshes = new unsigned int[1]; + aiMesh *pMesh = new aiMesh; + pScene->mRootNode->mMeshes[0] = 100; + // workaround + +/* + // Create nodes for the whole scene + std::vector MeshArray; + for ( size_t index = 0; index < pModel->bone_count; index++ ) { + createNodes( pModel, pModel->bones[i], pScene, MeshArray); + } + + if ( pScene->mNumMeshes > 0 ) { + pScene->mMeshes = new aiMesh*[ MeshArray.size() ]; + for ( size_t index = 0; index < MeshArray.size(); index++ ) { + pScene->mMeshes[ index ] = MeshArray[ index ]; + } + } + + // Create all materials + createMaterials( pModel, pScene ); + */ +} + +// ------------------------------------------------------------------------------------------------ + +} // Namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_MMD_IMPORTER diff --git a/code/MMDImporter.h b/code/MMDImporter.h new file mode 100644 index 000000000..64ae1eb60 --- /dev/null +++ b/code/MMDImporter.h @@ -0,0 +1,100 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2016, assimp team +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#ifndef MMD_FILE_IMPORTER_H_INC +#define MMD_FILE_IMPORTER_H_INC + +#include "BaseImporter.h" +#include "MMDPmxParser.h" +#include +#include + +struct aiMesh; +struct aiNode; + +namespace Assimp { + +/* +namespace MMDFile { + struct Object; + struct Model; +} +*/ + +// ------------------------------------------------------------------------------------------------ +/// \class MMDImporter +/// \brief Imports MMD a pmx/pmd/vmd file +// ------------------------------------------------------------------------------------------------ +class MMDImporter : public BaseImporter { +public: + /// \brief Default constructor + MMDImporter(); + + /// \brief Destructor + ~MMDImporter(); + +public: + /// \brief Returns whether the class can handle the format of the given file. + /// \remark See BaseImporter::CanRead() for details. + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const; + +private: + //! \brief Appends the supported extension. + const aiImporterDesc* GetInfo () const; + + //! \brief File import implementation. + void InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler); + + //! \brief Create the data from imported content. + void CreateDataFromImport(const pmx::PmxModel* pModel, aiScene* pScene); + +private: + //! Data buffer + std::vector m_Buffer; + //! Pointer to root object instance + //MMDFile::Object *m_pRootObject; + //! Absolute pathname of model in file system + std::string m_strAbsPath; +}; + +// ------------------------------------------------------------------------------------------------ + +} // Namespace Assimp + +#endif \ No newline at end of file diff --git a/code/MMDPmdParser.h b/code/MMDPmdParser.h new file mode 100644 index 000000000..c1c7656f5 --- /dev/null +++ b/code/MMDPmdParser.h @@ -0,0 +1,630 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "MMDCpp14.h" + +namespace pmd +{ + /// ヘッダ + class PmdHeader + { + public: + /// モデル名 + std::string name; + /// モデル名(英語) + std::string name_english; + /// コメント + std::string comment; + /// コメント(英語) + std::string comment_english; + + bool Read(std::ifstream* stream) + { + char buffer[256]; + stream->read(buffer, 20); + name = std::string(buffer); + stream->read(buffer, 256); + comment = std::string(buffer); + return true; + } + + bool ReadExtension(std::ifstream* stream) + { + char buffer[256]; + stream->read(buffer, 20); + name_english = std::string(buffer); + stream->read(buffer, 256); + comment_english = std::string(buffer); + return true; + } + }; + + /// 頂点 + class PmdVertex + { + public: + /// 位置 + float position[3]; + + /// 法線 + float normal[3]; + + /// UV座標 + float uv[2]; + + /// 関連ボーンインデックス + uint16_t bone_index[2]; + + /// ボーンウェイト + uint8_t bone_weight; + + /// エッジ不可視 + bool edge_invisible; + + bool Read(std::ifstream* stream) + { + stream->read((char*) position, sizeof(float) * 3); + stream->read((char*) normal, sizeof(float) * 3); + stream->read((char*) uv, sizeof(float) * 2); + stream->read((char*) bone_index, sizeof(uint16_t) * 2); + stream->read((char*) &bone_weight, sizeof(uint8_t)); + stream->read((char*) &edge_invisible, sizeof(uint8_t)); + return true; + } + }; + + /// 材質 + class PmdMaterial + { + public: + /// 減衰色 + float diffuse[4]; + /// 光沢度 + float power; + /// 光沢色 + float specular[3]; + /// 環境色 + float ambient[3]; + /// トーンインデックス + uint8_t toon_index; + /// エッジ + uint8_t edge_flag; + /// インデックス数 + uint32_t index_count; + /// テクスチャファイル名 + std::string texture_filename; + /// スフィアファイル名 + std::string sphere_filename; + + bool Read(std::ifstream* stream) + { + char buffer[20]; + stream->read((char*) &diffuse, sizeof(float) * 4); + stream->read((char*) &power, sizeof(float)); + stream->read((char*) &specular, sizeof(float) * 3); + stream->read((char*) &ambient, sizeof(float) * 3); + stream->read((char*) &toon_index, sizeof(uint8_t)); + stream->read((char*) &edge_flag, sizeof(uint8_t)); + stream->read((char*) &index_count, sizeof(uint32_t)); + stream->read((char*) &buffer, sizeof(char) * 20); + char* pstar = strchr(buffer, '*'); + if (NULL == pstar) + { + texture_filename = std::string(buffer); + sphere_filename.clear(); + } + else { + *pstar = NULL; + texture_filename = std::string(buffer); + sphere_filename = std::string(pstar+1); + } + return true; + } + }; + + enum class BoneType : uint8_t + { + Rotation, + RotationAndMove, + IkEffector, + Unknown, + IkEffectable, + RotationEffectable, + IkTarget, + Invisible, + Twist, + RotationMovement + }; + + /// ボーン + class PmdBone + { + public: + /// ボーン名 + std::string name; + /// ボーン名(英語) + std::string name_english; + /// 親ボーン番号 + uint16_t parent_bone_index; + /// 末端ボーン番号 + uint16_t tail_pos_bone_index; + /// ボーン種類 + BoneType bone_type; + /// IKボーン番号 + uint16_t ik_parent_bone_index; + /// ボーンのヘッドの位置 + float bone_head_pos[3]; + + void Read(std::istream *stream) + { + char buffer[20]; + stream->read(buffer, 20); + name = std::string(buffer); + stream->read((char*) &parent_bone_index, sizeof(uint16_t)); + stream->read((char*) &tail_pos_bone_index, sizeof(uint16_t)); + stream->read((char*) &bone_type, sizeof(uint8_t)); + stream->read((char*) &ik_parent_bone_index, sizeof(uint16_t)); + stream->read((char*) &bone_head_pos, sizeof(float) * 3); + } + + void ReadExpantion(std::istream *stream) + { + char buffer[20]; + stream->read(buffer, 20); + name_english = std::string(buffer); + } + }; + + /// IK + class PmdIk + { + public: + /// IKボーン番号 + uint16_t ik_bone_index; + /// IKターゲットボーン番号 + uint16_t target_bone_index; + /// 再帰回数 + uint16_t interations; + /// 角度制限 + float angle_limit; + /// 影響下ボーン番号 + std::vector ik_child_bone_index; + + void Read(std::istream *stream) + { + stream->read((char *) &ik_bone_index, sizeof(uint16_t)); + stream->read((char *) &target_bone_index, sizeof(uint16_t)); + uint8_t ik_chain_length; + stream->read((char*) &ik_chain_length, sizeof(uint8_t)); + stream->read((char *) &interations, sizeof(uint16_t)); + stream->read((char *) &angle_limit, sizeof(float)); + ik_child_bone_index.resize(ik_chain_length); + for (int i = 0; i < ik_chain_length; i++) + { + stream->read((char *) &ik_child_bone_index[i], sizeof(uint16_t)); + } + } + }; + + class PmdFaceVertex + { + public: + int vertex_index; + float position[3]; + + void Read(std::istream *stream) + { + stream->read((char *) &vertex_index, sizeof(int)); + stream->read((char *) position, sizeof(float) * 3); + } + }; + + enum class FaceCategory : uint8_t + { + Base, + Eyebrow, + Eye, + Mouth, + Other + }; + + class PmdFace + { + public: + std::string name; + FaceCategory type; + std::vector vertices; + std::string name_english; + + void Read(std::istream *stream) + { + char buffer[20]; + stream->read(buffer, 20); + name = std::string(buffer); + int vertex_count; + stream->read((char*) &vertex_count, sizeof(int)); + stream->read((char*) &type, sizeof(uint8_t)); + vertices.resize(vertex_count); + for (int i = 0; i < vertex_count; i++) + { + vertices[i].Read(stream); + } + } + + void ReadExpantion(std::istream *stream) + { + char buffer[20]; + stream->read(buffer, 20); + name_english = std::string(buffer); + } + }; + + /// ボーン枠用の枠名 + class PmdBoneDispName + { + public: + std::string bone_disp_name; + std::string bone_disp_name_english; + + void Read(std::istream *stream) + { + char buffer[50]; + stream->read(buffer, 50); + bone_disp_name = std::string(buffer); + bone_disp_name_english.clear(); + } + void ReadExpantion(std::istream *stream) + { + char buffer[50]; + stream->read(buffer, 50); + bone_disp_name_english = std::string(buffer); + } + }; + + class PmdBoneDisp + { + public: + uint16_t bone_index; + uint8_t bone_disp_index; + + void Read(std::istream *stream) + { + stream->read((char*) &bone_index, sizeof(uint16_t)); + stream->read((char*) &bone_disp_index, sizeof(uint8_t)); + } + }; + + /// 衝突形状 + enum class RigidBodyShape : uint8_t + { + /// 球 + Sphere = 0, + /// 直方体 + Box = 1, + /// カプセル + Cpusel = 2 + }; + + /// 剛体タイプ + enum class RigidBodyType : uint8_t + { + /// ボーン追従 + BoneConnected = 0, + /// 物理演算 + Physics = 1, + /// 物理演算(Bone位置合せ) + ConnectedPhysics = 2 + }; + + /// 剛体 + class PmdRigidBody + { + public: + /// 名前 + std::string name; + /// 関連ボーン番号 + uint16_t related_bone_index; + /// グループ番号 + uint8_t group_index; + /// マスク + uint16_t mask; + /// 形状 + RigidBodyShape shape; + /// 大きさ + float size[3]; + /// 位置 + float position[3]; + /// 回転 + float orientation[3]; + /// 質量 + float weight; + /// 移動ダンピング + float linear_damping; + /// 回転ダンピング + float anglar_damping; + /// 反発係数 + float restitution; + /// 摩擦係数 + float friction; + /// 演算方法 + RigidBodyType rigid_type; + + void Read(std::istream *stream) + { + char buffer[20]; + stream->read(buffer, sizeof(char) * 20); + name = (std::string(buffer)); + stream->read((char*) &related_bone_index, sizeof(uint16_t)); + stream->read((char*) &group_index, sizeof(uint8_t)); + stream->read((char*) &mask, sizeof(uint16_t)); + stream->read((char*) &shape, sizeof(uint8_t)); + stream->read((char*) size, sizeof(float) * 3); + stream->read((char*) position, sizeof(float) * 3); + stream->read((char*) orientation, sizeof(float) * 3); + stream->read((char*) &weight, sizeof(float)); + stream->read((char*) &linear_damping, sizeof(float)); + stream->read((char*) &anglar_damping, sizeof(float)); + stream->read((char*) &restitution, sizeof(float)); + stream->read((char*) &friction, sizeof(float)); + stream->read((char*) &rigid_type, sizeof(char)); + } + }; + + /// 剛体の拘束 + class PmdConstraint + { + public: + /// 名前 + std::string name; + /// 剛体Aのインデックス + uint32_t rigid_body_index_a; + /// 剛体Bのインデックス + uint32_t rigid_body_index_b; + /// 位置 + float position[3]; + /// 回転 + float orientation[3]; + /// 最小移動制限 + float linear_lower_limit[3]; + /// 最大移動制限 + float linear_upper_limit[3]; + /// 最小回転制限 + float angular_lower_limit[3]; + /// 最大回転制限 + float angular_upper_limit[3]; + /// 移動に対する復元力 + float linear_stiffness[3]; + /// 回転に対する復元力 + float angular_stiffness[3]; + + void Read(std::istream *stream) + { + char buffer[20]; + stream->read(buffer, 20); + name = std::string(buffer); + stream->read((char *) &rigid_body_index_a, sizeof(uint32_t)); + stream->read((char *) &rigid_body_index_b, sizeof(uint32_t)); + stream->read((char *) position, sizeof(float) * 3); + stream->read((char *) orientation, sizeof(float) * 3); + stream->read((char *) linear_lower_limit, sizeof(float) * 3); + stream->read((char *) linear_upper_limit, sizeof(float) * 3); + stream->read((char *) angular_lower_limit, sizeof(float) * 3); + stream->read((char *) angular_upper_limit, sizeof(float) * 3); + stream->read((char *) linear_stiffness, sizeof(float) * 3); + stream->read((char *) angular_stiffness, sizeof(float) * 3); + } + }; + + /// PMDモデル + class PmdModel + { + public: + float version; + PmdHeader header; + std::vector vertices; + std::vector indices; + std::vector materials; + std::vector bones; + std::vector iks; + std::vector faces; + std::vector faces_indices; + std::vector bone_disp_name; + std::vector bone_disp; + std::vector toon_filenames; + std::vector rigid_bodies; + std::vector constraints; + + static std::unique_ptr LoadFromFile(const char *filename) + { + std::ifstream stream(filename, std::ios::binary); + if (stream.fail()) + { + std::cerr << "could not open \"" << filename << "\"" << std::endl; + return nullptr; + } + auto result = LoadFromStream(&stream); + stream.close(); + return result; + } + + /// ファイルからPmdModelを生成する + static std::unique_ptr LoadFromStream(std::ifstream *stream) + { + auto result = std::make_unique(); + char buffer[100]; + + // magic + char magic[3]; + stream->read(magic, 3); + if (magic[0] != 'P' || magic[1] != 'm' || magic[2] != 'd') + { + std::cerr << "invalid file" << std::endl; + return nullptr; + } + + // version + stream->read((char*) &(result->version), sizeof(float)); + if (result ->version != 1.0f) + { + std::cerr << "invalid version" << std::endl; + return nullptr; + } + + // header + result->header.Read(stream); + + // vertices + uint32_t vertex_num; + stream->read((char*) &vertex_num, sizeof(uint32_t)); + result->vertices.resize(vertex_num); + for (uint32_t i = 0; i < vertex_num; i++) + { + result->vertices[i].Read(stream); + } + + // indices + uint32_t index_num; + stream->read((char*) &index_num, sizeof(uint32_t)); + result->indices.resize(index_num); + for (uint32_t i = 0; i < index_num; i++) + { + stream->read((char*) &result->indices[i], sizeof(uint16_t)); + } + + // materials + uint32_t material_num; + stream->read((char*) &material_num, sizeof(uint32_t)); + result->materials.resize(material_num); + for (uint32_t i = 0; i < material_num; i++) + { + result->materials[i].Read(stream); + } + + // bones + uint16_t bone_num; + stream->read((char*) &bone_num, sizeof(uint16_t)); + result->bones.resize(bone_num); + for (uint32_t i = 0; i < bone_num; i++) + { + result->bones[i].Read(stream); + } + + // iks + uint16_t ik_num; + stream->read((char*) &ik_num, sizeof(uint16_t)); + result->iks.resize(ik_num); + for (uint32_t i = 0; i < ik_num; i++) + { + result->iks[i].Read(stream); + } + + // faces + uint16_t face_num; + stream->read((char*) &face_num, sizeof(uint16_t)); + result->faces.resize(face_num); + for (uint32_t i = 0; i < face_num; i++) + { + result->faces[i].Read(stream); + } + + // face frames + uint8_t face_frame_num; + stream->read((char*) &face_frame_num, sizeof(uint8_t)); + result->faces_indices.resize(face_frame_num); + for (uint32_t i = 0; i < face_frame_num; i++) + { + stream->read((char*) &result->faces_indices[i], sizeof(uint16_t)); + } + + // bone names + uint8_t bone_disp_num; + stream->read((char*) &bone_disp_num, sizeof(uint8_t)); + result->bone_disp_name.resize(bone_disp_num); + for (uint32_t i = 0; i < bone_disp_num; i++) + { + result->bone_disp_name[i].Read(stream); + } + + // bone frame + uint32_t bone_frame_num; + stream->read((char*) &bone_frame_num, sizeof(uint32_t)); + result->bone_disp.resize(bone_frame_num); + for (uint32_t i = 0; i < bone_frame_num; i++) + { + result->bone_disp[i].Read(stream); + } + + // english name + bool english; + stream->read((char*) &english, sizeof(char)); + if (english) + { + result->header.ReadExtension(stream); + for (uint32_t i = 0; i < bone_num; i++) + { + result->bones[i].ReadExpantion(stream); + } + for (uint32_t i = 0; i < face_num; i++) + { + if (result->faces[i].type == pmd::FaceCategory::Base) + { + continue; + } + result->faces[i].ReadExpantion(stream); + } + for (uint32_t i = 0; i < result->bone_disp_name.size(); i++) + { + result->bone_disp_name[i].ReadExpantion(stream); + } + } + + // toon textures + if (stream->peek() == std::ios::traits_type::eof()) + { + result->toon_filenames.clear(); + } + else { + result->toon_filenames.resize(10); + for (uint32_t i = 0; i < 10; i++) + { + stream->read(buffer, 100); + result->toon_filenames[i] = std::string(buffer); + } + } + + // physics + if (stream->peek() == std::ios::traits_type::eof()) + { + result->rigid_bodies.clear(); + result->constraints.clear(); + } + else { + uint32_t rigid_body_num; + stream->read((char*) &rigid_body_num, sizeof(uint32_t)); + result->rigid_bodies.resize(rigid_body_num); + for (uint32_t i = 0; i < rigid_body_num; i++) + { + result->rigid_bodies[i].Read(stream); + } + uint32_t constraint_num; + stream->read((char*) &constraint_num, sizeof(uint32_t)); + result->constraints.resize(constraint_num); + for (uint32_t i = 0; i < constraint_num; i++) + { + result->constraints[i].Read(stream); + } + } + + if (stream->peek() != std::ios::traits_type::eof()) + { + std::cerr << "there is unknown data" << std::endl; + } + + return result; + } + }; +} \ No newline at end of file diff --git a/code/MMDPmxParser.cpp b/code/MMDPmxParser.cpp new file mode 100644 index 000000000..639574a59 --- /dev/null +++ b/code/MMDPmxParser.cpp @@ -0,0 +1,625 @@ +#include +#include "MMDPmxParser.h" +#ifndef __unix__ +#include "EncodingHelper.h" +#else +#include +#endif + +namespace pmx +{ + /// インデックス値を読み込む + int ReadIndex(std::istream *stream, int size) + { + switch (size) + { + case 1: + uint8_t tmp8; + stream->read((char*) &tmp8, sizeof(uint8_t)); + if (255 == tmp8) + { + return -1; + } + else { + return (int) tmp8; + } + case 2: + uint16_t tmp16; + stream->read((char*) &tmp16, sizeof(uint16_t)); + if (65535 == tmp16) + { + return -1; + } + else { + return (int) tmp16; + } + case 4: + int tmp32; + stream->read((char*) &tmp32, sizeof(int)); + return tmp32; + default: + return -1; + } + } + + /// 文字列を読み込む + utfstring ReadString(std::istream *stream, uint8_t encoding) + { +#ifndef __unix__ + oguna::EncodingConverter converter = oguna::EncodingConverter(); +#endif + int size; + stream->read((char*) &size, sizeof(int)); + std::vector buffer; + if (size == 0) + { +#ifndef __unix__ + return utfstring(L""); +#else + return utfstring(""); +#endif + } + buffer.reserve(size); + stream->read((char*) buffer.data(), size); + if (encoding == 0) + { + // UTF16 +#ifndef __unix__ + return utfstring((wchar_t*) buffer.data(), size / 2); +#else + utfstring result; + std::vector outbuf; + outbuf.reserve(size*2); + + // Always remember to set U_ZERO_ERROR before calling ucnv_convert(), + // otherwise the function will fail. + UErrorCode err = U_ZERO_ERROR; + size = ucnv_convert("UTF-8", "UTF-16LE", (char*)outbuf.data(), outbuf.capacity(), buffer.data(), size, &err); + if(!U_SUCCESS(err)) { + std::cout << "oops, something wrong?" << std::endl; + std::cout << u_errorName(err) << std::endl; + exit(-1); + } + + result.assign((const char*)outbuf.data(), size); + return result; +#endif + } + else + { + // UTF8 +#ifndef __unix__ + utfstring result; + converter.Utf8ToUtf16(buffer.data(), size, &result); + return result; +#else + return utfstring((const char*)buffer.data(), size); +#endif + } + } + + void PmxSetting::Read(std::istream *stream) + { + uint8_t count; + stream->read((char*) &count, sizeof(uint8_t)); + if (count < 8) + { + throw; + } + stream->read((char*) &encoding, sizeof(uint8_t)); + stream->read((char*) &uv, sizeof(uint8_t)); + stream->read((char*) &vertex_index_size, sizeof(uint8_t)); + stream->read((char*) &texture_index_size, sizeof(uint8_t)); + stream->read((char*) &material_index_size, sizeof(uint8_t)); + stream->read((char*) &bone_index_size, sizeof(uint8_t)); + stream->read((char*) &morph_index_size, sizeof(uint8_t)); + stream->read((char*) &rigidbody_index_size, sizeof(uint8_t)); + uint8_t temp; + for (int i = 8; i < count; i++) + { + stream->read((char*)&temp, sizeof(uint8_t)); + } + } + + void PmxVertexSkinningBDEF1::Read(std::istream *stream, PmxSetting *setting) + { + this->bone_index = ReadIndex(stream, setting->bone_index_size); + } + + void PmxVertexSkinningBDEF2::Read(std::istream *stream, PmxSetting *setting) + { + this->bone_index1 = ReadIndex(stream, setting->bone_index_size); + this->bone_index2 = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &this->bone_weight, sizeof(float)); + } + + void PmxVertexSkinningBDEF4::Read(std::istream *stream, PmxSetting *setting) + { + this->bone_index1 = ReadIndex(stream, setting->bone_index_size); + this->bone_index2 = ReadIndex(stream, setting->bone_index_size); + this->bone_index3 = ReadIndex(stream, setting->bone_index_size); + this->bone_index4 = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &this->bone_weight1, sizeof(float)); + stream->read((char*) &this->bone_weight2, sizeof(float)); + stream->read((char*) &this->bone_weight3, sizeof(float)); + stream->read((char*) &this->bone_weight4, sizeof(float)); + } + + void PmxVertexSkinningSDEF::Read(std::istream *stream, PmxSetting *setting) + { + this->bone_index1 = ReadIndex(stream, setting->bone_index_size); + this->bone_index2 = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &this->bone_weight, sizeof(float)); + stream->read((char*) this->sdef_c, sizeof(float) * 3); + stream->read((char*) this->sdef_r0, sizeof(float) * 3); + stream->read((char*) this->sdef_r1, sizeof(float) * 3); + } + + void PmxVertexSkinningQDEF::Read(std::istream *stream, PmxSetting *setting) + { + this->bone_index1 = ReadIndex(stream, setting->bone_index_size); + this->bone_index2 = ReadIndex(stream, setting->bone_index_size); + this->bone_index3 = ReadIndex(stream, setting->bone_index_size); + this->bone_index4 = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &this->bone_weight1, sizeof(float)); + stream->read((char*) &this->bone_weight2, sizeof(float)); + stream->read((char*) &this->bone_weight3, sizeof(float)); + stream->read((char*) &this->bone_weight4, sizeof(float)); + } + + void PmxVertex::Read(std::istream *stream, PmxSetting *setting) + { + stream->read((char*) this->positon, sizeof(float) * 3); + stream->read((char*) this->normal, sizeof(float) * 3); + stream->read((char*) this->uv, sizeof(float) * 2); + for (int i = 0; i < setting->uv; ++i) + { + stream->read((char*) this->uva[i], sizeof(float) * 4); + } + stream->read((char*) &this->skinning_type, sizeof(PmxVertexSkinningType)); + switch (this->skinning_type) + { + case PmxVertexSkinningType::BDEF1: + this->skinning = std::make_unique(); + break; + case PmxVertexSkinningType::BDEF2: + this->skinning = std::make_unique(); + break; + case PmxVertexSkinningType::BDEF4: + this->skinning = std::make_unique(); + break; + case PmxVertexSkinningType::SDEF: + this->skinning = std::make_unique(); + break; + case PmxVertexSkinningType::QDEF: + this->skinning = std::make_unique(); + break; + default: + throw "invalid skinning type"; + } + this->skinning->Read(stream, setting); + stream->read((char*) &this->edge, sizeof(float)); + } + + void PmxMaterial::Read(std::istream *stream, PmxSetting *setting) + { + this->material_name = std::move(ReadString(stream, setting->encoding)); + this->material_english_name = std::move(ReadString(stream, setting->encoding)); + stream->read((char*) this->diffuse, sizeof(float) * 4); + stream->read((char*) this->specular, sizeof(float) * 3); + stream->read((char*) &this->specularlity, sizeof(float)); + stream->read((char*) this->ambient, sizeof(float) * 3); + stream->read((char*) &this->flag, sizeof(uint8_t)); + stream->read((char*) this->edge_color, sizeof(float) * 4); + stream->read((char*) &this->edge_size, sizeof(float)); + this->diffuse_texture_index = ReadIndex(stream, setting->texture_index_size); + this->sphere_texture_index = ReadIndex(stream, setting->texture_index_size); + stream->read((char*) &this->sphere_op_mode, sizeof(uint8_t)); + stream->read((char*) &this->common_toon_flag, sizeof(uint8_t)); + if (this->common_toon_flag) + { + stream->read((char*) &this->toon_texture_index, sizeof(uint8_t)); + } + else { + this->toon_texture_index = ReadIndex(stream, setting->texture_index_size); + } + this->memo = std::move(ReadString(stream, setting->encoding)); + stream->read((char*) &this->index_count, sizeof(int)); + } + + void PmxIkLink::Read(std::istream *stream, PmxSetting *setting) + { + this->link_target = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &this->angle_lock, sizeof(uint8_t)); + if (angle_lock == 1) + { + stream->read((char*) this->max_radian, sizeof(float) * 3); + stream->read((char*) this->min_radian, sizeof(float) * 3); + } + } + + void PmxBone::Read(std::istream *stream, PmxSetting *setting) + { + this->bone_name = std::move(ReadString(stream, setting->encoding)); + this->bone_english_name = std::move(ReadString(stream, setting->encoding)); + stream->read((char*) this->position, sizeof(float) * 3); + this->parent_index = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &this->level, sizeof(int)); + stream->read((char*) &this->bone_flag, sizeof(uint16_t)); + if (this->bone_flag & 0x0001) { + this->target_index = ReadIndex(stream, setting->bone_index_size); + } + else { + stream->read((char*)this->offset, sizeof(float) * 3); + } + if (this->bone_flag & (0x0100 | 0x0200)) { + this->grant_parent_index = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &this->grant_weight, sizeof(float)); + } + if (this->bone_flag & 0x0400) { + stream->read((char*)this->lock_axis_orientation, sizeof(float) * 3); + } + if (this->bone_flag & 0x0800) { + stream->read((char*)this->local_axis_x_orientation, sizeof(float) * 3); + stream->read((char*)this->local_axis_y_orientation, sizeof(float) * 3); + } + if (this->bone_flag & 0x2000) { + stream->read((char*) &this->key, sizeof(int)); + } + if (this->bone_flag & 0x0020) { + this->ik_target_bone_index = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &ik_loop, sizeof(int)); + stream->read((char*) &ik_loop_angle_limit, sizeof(float)); + stream->read((char*) &ik_link_count, sizeof(int)); + this->ik_links = std::make_unique(ik_link_count); + for (int i = 0; i < ik_link_count; i++) { + ik_links[i].Read(stream, setting); + } + } + } + + void PmxMorphVertexOffset::Read(std::istream *stream, PmxSetting *setting) + { + this->vertex_index = ReadIndex(stream, setting->vertex_index_size); + stream->read((char*)this->position_offset, sizeof(float) * 3); + } + + void PmxMorphUVOffset::Read(std::istream *stream, PmxSetting *setting) + { + this->vertex_index = ReadIndex(stream, setting->vertex_index_size); + stream->read((char*)this->uv_offset, sizeof(float) * 4); + } + + void PmxMorphBoneOffset::Read(std::istream *stream, PmxSetting *setting) + { + this->bone_index = ReadIndex(stream, setting->bone_index_size); + stream->read((char*)this->translation, sizeof(float) * 3); + stream->read((char*)this->rotation, sizeof(float) * 4); + } + + void PmxMorphMaterialOffset::Read(std::istream *stream, PmxSetting *setting) + { + this->material_index = ReadIndex(stream, setting->material_index_size); + stream->read((char*) &this->offset_operation, sizeof(uint8_t)); + stream->read((char*)this->diffuse, sizeof(float) * 4); + stream->read((char*)this->specular, sizeof(float) * 3); + stream->read((char*) &this->specularity, sizeof(float)); + stream->read((char*)this->ambient, sizeof(float) * 3); + stream->read((char*)this->edge_color, sizeof(float) * 4); + stream->read((char*) &this->edge_size, sizeof(float)); + stream->read((char*)this->texture_argb, sizeof(float) * 4); + stream->read((char*)this->sphere_texture_argb, sizeof(float) * 4); + stream->read((char*)this->toon_texture_argb, sizeof(float) * 4); + } + + void PmxMorphGroupOffset::Read(std::istream *stream, PmxSetting *setting) + { + this->morph_index = ReadIndex(stream, setting->morph_index_size); + stream->read((char*) &this->morph_weight, sizeof(float)); + } + + void PmxMorphFlipOffset::Read(std::istream *stream, PmxSetting *setting) + { + this->morph_index = ReadIndex(stream, setting->morph_index_size); + stream->read((char*) &this->morph_value, sizeof(float)); + } + + void PmxMorphImplusOffset::Read(std::istream *stream, PmxSetting *setting) + { + this->rigid_body_index = ReadIndex(stream, setting->rigidbody_index_size); + stream->read((char*) &this->is_local, sizeof(uint8_t)); + stream->read((char*)this->velocity, sizeof(float) * 3); + stream->read((char*)this->angular_torque, sizeof(float) * 3); + } + + void PmxMorph::Read(std::istream *stream, PmxSetting *setting) + { + this->morph_name = ReadString(stream, setting->encoding); + this->morph_english_name = ReadString(stream, setting->encoding); + stream->read((char*) &category, sizeof(MorphCategory)); + stream->read((char*) &morph_type, sizeof(MorphType)); + stream->read((char*) &this->offset_count, sizeof(int)); + switch (this->morph_type) + { + case MorphType::Group: + group_offsets = std::make_unique(this->offset_count); + for (int i = 0; i < offset_count; i++) + { + group_offsets[i].Read(stream, setting); + } + break; + case MorphType::Vertex: + vertex_offsets = std::make_unique(this->offset_count); + for (int i = 0; i < offset_count; i++) + { + vertex_offsets[i].Read(stream, setting); + } + break; + case MorphType::Bone: + bone_offsets = std::make_unique(this->offset_count); + for (int i = 0; i < offset_count; i++) + { + bone_offsets[i].Read(stream, setting); + } + break; + case MorphType::Matrial: + material_offsets = std::make_unique(this->offset_count); + for (int i = 0; i < offset_count; i++) + { + material_offsets[i].Read(stream, setting); + } + break; + case MorphType::UV: + case MorphType::AdditionalUV1: + case MorphType::AdditionalUV2: + case MorphType::AdditionalUV3: + case MorphType::AdditionalUV4: + uv_offsets = std::make_unique(this->offset_count); + for (int i = 0; i < offset_count; i++) + { + uv_offsets[i].Read(stream, setting); + } + break; + default: + throw; + } + } + + void PmxFrameElement::Read(std::istream *stream, PmxSetting *setting) + { + stream->read((char*) &this->element_target, sizeof(uint8_t)); + if (this->element_target == 0x00) + { + this->index = ReadIndex(stream, setting->bone_index_size); + } + else { + this->index = ReadIndex(stream, setting->morph_index_size); + } + } + + void PmxFrame::Read(std::istream *stream, PmxSetting *setting) + { + this->frame_name = ReadString(stream, setting->encoding); + this->frame_english_name = ReadString(stream, setting->encoding); + stream->read((char*) &this->frame_flag, sizeof(uint8_t)); + stream->read((char*) &this->element_count, sizeof(int)); + this->elements = std::make_unique(this->element_count); + for (int i = 0; i < this->element_count; i++) + { + this->elements[i].Read(stream, setting); + } + } + + void PmxRigidBody::Read(std::istream *stream, PmxSetting *setting) + { + this->girid_body_name = ReadString(stream, setting->encoding); + this->girid_body_english_name = ReadString(stream, setting->encoding); + this->target_bone = ReadIndex(stream, setting->bone_index_size); + stream->read((char*) &this->group, sizeof(uint8_t)); + stream->read((char*) &this->mask, sizeof(uint16_t)); + stream->read((char*) &this->shape, sizeof(uint8_t)); + stream->read((char*) this->size, sizeof(float) * 3); + stream->read((char*) this->position, sizeof(float) * 3); + stream->read((char*) this->orientation, sizeof(float) * 3); + stream->read((char*) &this->mass, sizeof(float)); + stream->read((char*) &this->move_attenuation, sizeof(float)); + stream->read((char*) &this->rotation_attenuation, sizeof(float)); + stream->read((char*) &this->repulsion, sizeof(float)); + stream->read((char*) &this->friction, sizeof(float)); + stream->read((char*) &this->physics_calc_type, sizeof(uint8_t)); + } + + void PmxJointParam::Read(std::istream *stream, PmxSetting *setting) + { + this->rigid_body1 = ReadIndex(stream, setting->rigidbody_index_size); + this->rigid_body2 = ReadIndex(stream, setting->rigidbody_index_size); + stream->read((char*) this->position, sizeof(float) * 3); + stream->read((char*) this->orientaiton, sizeof(float) * 3); + stream->read((char*) this->move_limitation_min, sizeof(float) * 3); + stream->read((char*) this->move_limitation_max, sizeof(float) * 3); + stream->read((char*) this->rotation_limitation_min, sizeof(float) * 3); + stream->read((char*) this->rotation_limitation_max, sizeof(float) * 3); + stream->read((char*) this->spring_move_coefficient, sizeof(float) * 3); + stream->read((char*) this->spring_rotation_coefficient, sizeof(float) * 3); + } + + void PmxJoint::Read(std::istream *stream, PmxSetting *setting) + { + this->joint_name = ReadString(stream, setting->encoding); + this->joint_english_name = ReadString(stream, setting->encoding); + stream->read((char*) &this->joint_type, sizeof(uint8_t)); + this->param.Read(stream, setting); + } + + void PmxAncherRigidBody::Read(std::istream *stream, PmxSetting *setting) + { + this->related_rigid_body = ReadIndex(stream, setting->rigidbody_index_size); + this->related_vertex = ReadIndex(stream, setting->vertex_index_size); + stream->read((char*) &this->is_near, sizeof(uint8_t)); + } + + void PmxSoftBody::Read(std::istream *stream, PmxSetting *setting) + { + // 未実装 + std::cerr << "Not Implemented Exception" << std::endl; + throw; + } + + void PmxModel::Init() + { + this->version = 0.0f; + this->model_name.clear(); + this->model_english_name.clear(); + this->model_comment.clear(); + this->model_english_comment.clear(); + this->vertex_count = 0; + this->vertices = nullptr; + this->index_count = 0; + this->indices = nullptr; + this->texture_count = 0; + this->textures = nullptr; + this->material_count = 0; + this->materials = nullptr; + this->bone_count = 0; + this->bones = nullptr; + this->morph_count = 0; + this->morphs = nullptr; + this->frame_count = 0; + this->frames = nullptr; + this->rigid_body_count = 0; + this->rigid_bodies = nullptr; + this->joint_count = 0; + this->joints = nullptr; + this->soft_body_count = 0; + this->soft_bodies = nullptr; + } + + void PmxModel::Read(std::istream *stream) + { + // マジック + char magic[4]; + stream->read((char*) magic, sizeof(char) * 4); + if (magic[0] != 0x50 || magic[1] != 0x4d || magic[2] != 0x58 || magic[3] != 0x20) + { + std::cerr << "invalid magic number." << std::endl; + throw; + } + // バージョン + stream->read((char*) &version, sizeof(float)); + if (version != 2.0f && version != 2.1f) + { + std::cerr << "this is not ver2.0 or ver2.1 but " << version << "." << std::endl; + throw; + } + // ファイル設定 + this->setting.Read(stream); + + // モデル情報 + this->model_name = std::move(ReadString(stream, setting.encoding)); + this->model_english_name = std::move(ReadString(stream, setting.encoding)); + this->model_comment = std::move(ReadString(stream, setting.encoding)); + this->model_english_comment = std::move(ReadString(stream, setting.encoding)); + + // 頂点 + stream->read((char*) &vertex_count, sizeof(int)); + this->vertices = std::make_unique(vertex_count); + for (int i = 0; i < vertex_count; i++) + { + vertices[i].Read(stream, &setting); + } + + // 面 + stream->read((char*) &index_count, sizeof(int)); + this->indices = std::make_unique(index_count); + for (int i = 0; i < index_count; i++) + { + this->indices[i] = ReadIndex(stream, setting.vertex_index_size); + } + + // テクスチャ + stream->read((char*) &texture_count, sizeof(int)); + this->textures = std::make_unique(texture_count); + for (int i = 0; i < texture_count; i++) + { + this->textures[i] = ReadString(stream, setting.encoding); + } + + // マテリアル + stream->read((char*) &material_count, sizeof(int)); + this->materials = std::make_unique(material_count); + for (int i = 0; i < material_count; i++) + { + this->materials[i].Read(stream, &setting); + } + + // ボーン + stream->read((char*) &this->bone_count, sizeof(int)); + this->bones = std::make_unique(this->bone_count); + for (int i = 0; i < this->bone_count; i++) + { + this->bones[i].Read(stream, &setting); + } + + // モーフ + stream->read((char*) &this->morph_count, sizeof(int)); + this->morphs = std::make_unique(this->morph_count); + for (int i = 0; i < this->morph_count; i++) + { + this->morphs[i].Read(stream, &setting); + } + + // 表示枠 + stream->read((char*) &this->frame_count, sizeof(int)); + this->frames = std::make_unique(this->frame_count); + for (int i = 0; i < this->frame_count; i++) + { + this->frames[i].Read(stream, &setting); + } + + // 剛体 + stream->read((char*) &this->rigid_body_count, sizeof(int)); + this->rigid_bodies = std::make_unique(this->rigid_body_count); + for (int i = 0; i < this->rigid_body_count; i++) + { + this->rigid_bodies[i].Read(stream, &setting); + } + + // ジョイント + stream->read((char*) &this->joint_count, sizeof(int)); + this->joints = std::make_unique(this->joint_count); + for (int i = 0; i < this->joint_count; i++) + { + this->joints[i].Read(stream, &setting); + } + + //// ソフトボディ + //if (this->version == 2.1f) + //{ + // stream->read((char*) &this->soft_body_count, sizeof(int)); + // this->soft_bodies = std::make_unique(this->soft_body_count); + // for (int i = 0; i < this->soft_body_count; i++) + // { + // this->soft_bodies[i].Read(stream, &setting); + // } + //} + } + + //std::unique_ptr ReadFromFile(const char *filename) + //{ + // auto stream = std::ifstream(filename, std::ios_base::binary); + // auto pmx = PmxModel::ReadFromStream(&stream); + // if (!stream.eof()) + // { + // std::cerr << "don't reach the end of file." << std::endl; + // } + // stream.close(); + // return pmx; + //} + + //std::unique_ptr ReadFromStream(std::istream *stream) + //{ + // auto pmx = std::make_unique(); + // pmx->Read(stream); + // return pmx; + //} +} diff --git a/code/MMDPmxParser.h b/code/MMDPmxParser.h new file mode 100644 index 000000000..1387fef58 --- /dev/null +++ b/code/MMDPmxParser.h @@ -0,0 +1,865 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "MMDCpp14.h" + +namespace pmx +{ +#ifndef __unix__ +#define utfstring std::wstring +#else +#define utfstring std::string +#endif + /// インデックス設定 + class PmxSetting + { + public: + PmxSetting() + : encoding(0) + , uv(0) + , vertex_index_size(0) + , texture_index_size(0) + , material_index_size(0) + , bone_index_size(0) + , morph_index_size(0) + , rigidbody_index_size(0) + {} + + /// エンコード方式 + uint8_t encoding; + /// 追加UV数 + uint8_t uv; + /// 頂点インデックスサイズ + uint8_t vertex_index_size; + /// テクスチャインデックスサイズ + uint8_t texture_index_size; + /// マテリアルインデックスサイズ + uint8_t material_index_size; + /// ボーンインデックスサイズ + uint8_t bone_index_size; + /// モーフインデックスサイズ + uint8_t morph_index_size; + /// 剛体インデックスサイズ + uint8_t rigidbody_index_size; + void Read(std::istream *stream); + }; + + /// 頂点スキニングタイプ + enum class PmxVertexSkinningType : uint8_t + { + BDEF1 = 0, + BDEF2 = 1, + BDEF4 = 2, + SDEF = 3, + QDEF = 4, + }; + + /// 頂点スキニング + class PmxVertexSkinning + { + public: + virtual void Read(std::istream *stream, PmxSetting *setting) = 0; + }; + + class PmxVertexSkinningBDEF1 : public PmxVertexSkinning + { + public: + PmxVertexSkinningBDEF1() + : bone_index(0) + {} + + int bone_index; + void Read(std::istream *stresam, PmxSetting *setting); + }; + + class PmxVertexSkinningBDEF2 : public PmxVertexSkinning + { + public: + PmxVertexSkinningBDEF2() + : bone_index1(0) + , bone_index2(0) + , bone_weight(0.0f) + {} + + int bone_index1; + int bone_index2; + float bone_weight; + void Read(std::istream *stresam, PmxSetting *setting); + }; + + class PmxVertexSkinningBDEF4 : public PmxVertexSkinning + { + public: + PmxVertexSkinningBDEF4() + : bone_index1(0) + , bone_index2(0) + , bone_index3(0) + , bone_index4(0) + , bone_weight1(0.0f) + , bone_weight2(0.0f) + , bone_weight3(0.0f) + , bone_weight4(0.0f) + {} + + int bone_index1; + int bone_index2; + int bone_index3; + int bone_index4; + float bone_weight1; + float bone_weight2; + float bone_weight3; + float bone_weight4; + void Read(std::istream *stresam, PmxSetting *setting); + }; + + class PmxVertexSkinningSDEF : public PmxVertexSkinning + { + public: + PmxVertexSkinningSDEF() + : bone_index1(0) + , bone_index2(0) + , bone_weight(0.0f) + { + for (int i = 0; i < 3; ++i) { + sdef_c[i] = 0.0f; + sdef_r0[i] = 0.0f; + sdef_r1[i] = 0.0f; + } + } + + int bone_index1; + int bone_index2; + float bone_weight; + float sdef_c[3]; + float sdef_r0[3]; + float sdef_r1[3]; + void Read(std::istream *stresam, PmxSetting *setting); + }; + + class PmxVertexSkinningQDEF : public PmxVertexSkinning + { + public: + PmxVertexSkinningQDEF() + : bone_index1(0) + , bone_index2(0) + , bone_index3(0) + , bone_index4(0) + , bone_weight1(0.0f) + , bone_weight2(0.0f) + , bone_weight3(0.0f) + , bone_weight4(0.0f) + {} + + int bone_index1; + int bone_index2; + int bone_index3; + int bone_index4; + float bone_weight1; + float bone_weight2; + float bone_weight3; + float bone_weight4; + void Read(std::istream *stresam, PmxSetting *setting); + }; + + /// 頂点 + class PmxVertex + { + public: + PmxVertex() + : edge(0.0f) + { + uv[0] = uv[1] = 0.0f; + for (int i = 0; i < 3; ++i) { + positon[i] = 0.0f; + normal[i] = 0.0f; + } + for (int i = 0; i < 4; ++i) { + for (int k = 0; k < 4; ++k) { + uva[i][k] = 0.0f; + } + } + } + + /// 位置 + float positon[3]; + /// 法線 + float normal[3]; + /// テクスチャ座標 + float uv[2]; + /// 追加テクスチャ座標 + float uva[4][4]; + /// スキニングタイプ + PmxVertexSkinningType skinning_type; + /// スキニング + std::unique_ptr skinning; + /// エッジ倍率 + float edge; + void Read(std::istream *stream, PmxSetting *setting); + }; + + /// マテリアル + class PmxMaterial + { + public: + PmxMaterial() + : specularlity(0.0f) + , flag(0) + , edge_size(0.0f) + , diffuse_texture_index(0) + , sphere_texture_index(0) + , sphere_op_mode(0) + , common_toon_flag(0) + , toon_texture_index(0) + , index_count(0) + { + for (int i = 0; i < 3; ++i) { + specular[i] = 0.0f; + ambient[i] = 0.0f; + edge_color[i] = 0.0f; + } + for (int i = 0; i < 4; ++i) { + diffuse[i] = 0.0f; + } + } + + /// モデル名 + utfstring material_name; + /// モデル英名 + utfstring material_english_name; + /// 減衰色 + float diffuse[4]; + /// 光沢色 + float specular[3]; + /// 光沢度 + float specularlity; + /// 環境色 + float ambient[3]; + /// 描画フラグ + uint8_t flag; + /// エッジ色 + float edge_color[4]; + /// エッジサイズ + float edge_size; + /// アルベドテクスチャインデックス + int diffuse_texture_index; + /// スフィアテクスチャインデックス + int sphere_texture_index; + /// スフィアテクスチャ演算モード + uint8_t sphere_op_mode; + /// 共有トゥーンフラグ + uint8_t common_toon_flag; + /// トゥーンテクスチャインデックス + int toon_texture_index; + /// メモ + utfstring memo; + /// 頂点インデックス数 + int index_count; + void Read(std::istream *stream, PmxSetting *setting); + }; + + /// リンク + class PmxIkLink + { + public: + PmxIkLink() + : link_target(0) + , angle_lock(0) + { + for (int i = 0; i < 3; ++i) { + max_radian[i] = 0.0f; + min_radian[i] = 0.0f; + } + } + + /// リンクボーンインデックス + int link_target; + /// 角度制限 + uint8_t angle_lock; + /// 最大制限角度 + float max_radian[3]; + /// 最小制限角度 + float min_radian[3]; + void Read(std::istream *stream, PmxSetting *settingn); + }; + + /// ボーン + class PmxBone + { + public: + PmxBone() + : parent_index(0) + , level(0) + , bone_flag(0) + , target_index(0) + , grant_parent_index(0) + , grant_weight(0.0f) + , key(0) + , ik_target_bone_index(0) + , ik_loop(0) + , ik_loop_angle_limit(0.0f) + , ik_link_count(0) + { + for (int i = 0; i < 3; ++i) { + position[i] = 0.0f; + offset[i] = 0.0f; + lock_axis_orientation[i] = 0.0f; + local_axis_x_orientation[i] = 0.0f; + local_axis_y_orientation[i] = 0.0f; + } + } + + /// ボーン名 + utfstring bone_name; + /// ボーン英名 + utfstring bone_english_name; + /// 位置 + float position[3]; + /// 親ボーンインデックス + int parent_index; + /// 階層 + int level; + /// ボーンフラグ + uint16_t bone_flag; + /// 座標オフセット(has Target) + float offset[3]; + /// 接続先ボーンインデックス(not has Target) + int target_index; + /// 付与親ボーンインデックス + int grant_parent_index; + /// 付与率 + float grant_weight; + /// 固定軸の方向 + float lock_axis_orientation[3]; + /// ローカル軸のX軸方向 + float local_axis_x_orientation[3]; + /// ローカル軸のY軸方向 + float local_axis_y_orientation[3]; + /// 外部親変形のkey値 + int key; + /// IKターゲットボーン + int ik_target_bone_index; + /// IKループ回数 + int ik_loop; + /// IKループ計算時の角度制限(ラジアン) + float ik_loop_angle_limit; + /// IKリンク数 + int ik_link_count; + /// IKリンク + std::unique_ptr ik_links; + void Read(std::istream *stream, PmxSetting *setting); + }; + + enum class MorphType : uint8_t + { + Group = 0, + Vertex = 1, + Bone = 2, + UV = 3, + AdditionalUV1 = 4, + AdditionalUV2 = 5, + AdditionalUV3 = 6, + AdditionalUV4 = 7, + Matrial = 8, + Flip = 9, + Implus = 10, + }; + + enum class MorphCategory : uint8_t + { + ReservedCategory = 0, + Eyebrow = 1, + Eye = 2, + Mouth = 3, + Other = 4, + }; + + class PmxMorphOffset + { + public: + void virtual Read(std::istream *stream, PmxSetting *setting) = 0; + }; + + class PmxMorphVertexOffset : public PmxMorphOffset + { + public: + PmxMorphVertexOffset() + : vertex_index(0) + { + for (int i = 0; i < 3; ++i) { + position_offset[i] = 0.0f; + } + } + int vertex_index; + float position_offset[3]; + void Read(std::istream *stream, PmxSetting *setting) override; + }; + + class PmxMorphUVOffset : public PmxMorphOffset + { + public: + PmxMorphUVOffset() + : vertex_index(0) + { + for (int i = 0; i < 4; ++i) { + uv_offset[i] = 0.0f; + } + } + int vertex_index; + float uv_offset[4]; + void Read(std::istream *stream, PmxSetting *setting) override; + }; + + class PmxMorphBoneOffset : public PmxMorphOffset + { + public: + PmxMorphBoneOffset() + : bone_index(0) + { + for (int i = 0; i < 3; ++i) { + translation[i] = 0.0f; + } + for (int i = 0; i < 4; ++i) { + rotation[i] = 0.0f; + } + } + int bone_index; + float translation[3]; + float rotation[4]; + void Read(std::istream *stream, PmxSetting *setting) override; + }; + + class PmxMorphMaterialOffset : public PmxMorphOffset + { + public: + PmxMorphMaterialOffset() + : specularity(0.0f) + , edge_size(0.0f) + { + for (int i = 0; i < 3; ++i) { + specular[i] = 0.0f; + ambient[i] = 0.0f; + } + for (int i = 0; i < 4; ++i) { + diffuse[i] = 0.0f; + edge_color[i] = 0.0f; + texture_argb[i] = 0.0f; + sphere_texture_argb[i] = 0.0f; + toon_texture_argb[i] = 0.0f; + } + } + int material_index; + uint8_t offset_operation; + float diffuse[4]; + float specular[3]; + float specularity; + float ambient[3]; + float edge_color[4]; + float edge_size; + float texture_argb[4]; + float sphere_texture_argb[4]; + float toon_texture_argb[4]; + void Read(std::istream *stream, PmxSetting *setting) override; + }; + + class PmxMorphGroupOffset : public PmxMorphOffset + { + public: + PmxMorphGroupOffset() + : morph_index(0) + , morph_weight(0.0f) + {} + int morph_index; + float morph_weight; + void Read(std::istream *stream, PmxSetting *setting) override; + }; + + class PmxMorphFlipOffset : public PmxMorphOffset + { + public: + PmxMorphFlipOffset() + : morph_index(0) + , morph_value(0.0f) + {} + int morph_index; + float morph_value; + void Read(std::istream *stream, PmxSetting *setting) override; + }; + + class PmxMorphImplusOffset : public PmxMorphOffset + { + public: + PmxMorphImplusOffset() + : rigid_body_index(0) + , is_local(0) + { + for (int i = 0; i < 3; ++i) { + velocity[i] = 0.0f; + angular_torque[i] = 0.0f; + } + } + int rigid_body_index; + uint8_t is_local; + float velocity[3]; + float angular_torque[3]; + void Read(std::istream *stream, PmxSetting *setting) override; + }; + + /// モーフ + class PmxMorph + { + public: + PmxMorph() + : offset_count(0) + { + } + /// モーフ名 + utfstring morph_name; + /// モーフ英名 + utfstring morph_english_name; + /// カテゴリ + MorphCategory category; + /// モーフタイプ + MorphType morph_type; + /// オフセット数 + int offset_count; + /// 頂点モーフ配列 + std::unique_ptr vertex_offsets; + /// UVモーフ配列 + std::unique_ptr uv_offsets; + /// ボーンモーフ配列 + std::unique_ptr bone_offsets; + /// マテリアルモーフ配列 + std::unique_ptr material_offsets; + /// グループモーフ配列 + std::unique_ptr group_offsets; + /// フリップモーフ配列 + std::unique_ptr flip_offsets; + /// インパルスモーフ配列 + std::unique_ptr implus_offsets; + void Read(std::istream *stream, PmxSetting *setting); + }; + + /// 枠内要素 + class PmxFrameElement + { + public: + PmxFrameElement() + : element_target(0) + , index(0) + { + } + /// 要素対象 + uint8_t element_target; + /// 要素対象インデックス + int index; + void Read(std::istream *stream, PmxSetting *setting); + }; + + /// 表示枠 + class PmxFrame + { + public: + PmxFrame() + : frame_flag(0) + , element_count(0) + { + } + /// 枠名 + utfstring frame_name; + /// 枠英名 + utfstring frame_english_name; + /// 特殊枠フラグ + uint8_t frame_flag; + /// 枠内要素数 + int element_count; + /// 枠内要素配列 + std::unique_ptr elements; + void Read(std::istream *stream, PmxSetting *setting); + }; + + class PmxRigidBody + { + public: + PmxRigidBody() + : target_bone(0) + , group(0) + , mask(0) + , shape(0) + , mass(0.0f) + , move_attenuation(0.0f) + , rotation_attenuation(0.0f) + , repulsion(0.0f) + , friction(0.0f) + , physics_calc_type(0) + { + for (int i = 0; i < 3; ++i) { + size[i] = 0.0f; + position[i] = 0.0f; + orientation[i] = 0.0f; + } + } + /// 剛体名 + utfstring girid_body_name; + /// 剛体英名 + utfstring girid_body_english_name; + /// 関連ボーンインデックス + int target_bone; + /// グループ + uint8_t group; + /// マスク + uint16_t mask; + /// 形状 + uint8_t shape; + float size[3]; + float position[3]; + float orientation[3]; + float mass; + float move_attenuation; + float rotation_attenuation; + float repulsion; + float friction; + uint8_t physics_calc_type; + void Read(std::istream *stream, PmxSetting *setting); + }; + + enum class PmxJointType : uint8_t + { + Generic6DofSpring = 0, + Generic6Dof = 1, + Point2Point = 2, + ConeTwist = 3, + Slider = 5, + Hinge = 6 + }; + + class PmxJointParam + { + public: + PmxJointParam() + : rigid_body1(0) + , rigid_body2(0) + { + for (int i = 0; i < 3; ++i) { + position[i] = 0.0f; + orientaiton[i] = 0.0f; + move_limitation_min[i] = 0.0f; + move_limitation_max[i] = 0.0f; + rotation_limitation_min[i] = 0.0f; + rotation_limitation_max[i] = 0.0f; + spring_move_coefficient[i] = 0.0f; + spring_rotation_coefficient[i] = 0.0f; + } + } + int rigid_body1; + int rigid_body2; + float position[3]; + float orientaiton[3]; + float move_limitation_min[3]; + float move_limitation_max[3]; + float rotation_limitation_min[3]; + float rotation_limitation_max[3]; + float spring_move_coefficient[3]; + float spring_rotation_coefficient[3]; + void Read(std::istream *stream, PmxSetting *setting); + }; + + class PmxJoint + { + public: + utfstring joint_name; + utfstring joint_english_name; + PmxJointType joint_type; + PmxJointParam param; + void Read(std::istream *stream, PmxSetting *setting); + }; + + enum PmxSoftBodyFlag : uint8_t + { + BLink = 0x01, + Cluster = 0x02, + Link = 0x04 + }; + + class PmxAncherRigidBody + { + public: + PmxAncherRigidBody() + : related_rigid_body(0) + , related_vertex(0) + , is_near(false) + {} + int related_rigid_body; + int related_vertex; + bool is_near; + void Read(std::istream *stream, PmxSetting *setting); + }; + + class PmxSoftBody + { + public: + PmxSoftBody() + : shape(0) + , target_material(0) + , group(0) + , mask(0) + , blink_distance(0) + , cluster_count(0) + , mass(0.0) + , collisioni_margin(0.0) + , aero_model(0) + , VCF(0.0f) + , DP(0.0f) + , DG(0.0f) + , LF(0.0f) + , PR(0.0f) + , VC(0.0f) + , DF(0.0f) + , MT(0.0f) + , CHR(0.0f) + , KHR(0.0f) + , SHR(0.0f) + , AHR(0.0f) + , SRHR_CL(0.0f) + , SKHR_CL(0.0f) + , SSHR_CL(0.0f) + , SR_SPLT_CL(0.0f) + , SK_SPLT_CL(0.0f) + , SS_SPLT_CL(0.0f) + , V_IT(0) + , P_IT(0) + , D_IT(0) + , C_IT(0) + , LST(0.0f) + , AST(0.0f) + , VST(0.0f) + , anchor_count(0) + , pin_vertex_count(0) + {} + utfstring soft_body_name; + utfstring soft_body_english_name; + uint8_t shape; + int target_material; + uint8_t group; + uint16_t mask; + PmxSoftBodyFlag flag; + int blink_distance; + int cluster_count; + float mass; + float collisioni_margin; + int aero_model; + float VCF; + float DP; + float DG; + float LF; + float PR; + float VC; + float DF; + float MT; + float CHR; + float KHR; + float SHR; + float AHR; + float SRHR_CL; + float SKHR_CL; + float SSHR_CL; + float SR_SPLT_CL; + float SK_SPLT_CL; + float SS_SPLT_CL; + int V_IT; + int P_IT; + int D_IT; + int C_IT; + float LST; + float AST; + float VST; + int anchor_count; + std::unique_ptr anchers; + int pin_vertex_count; + std::unique_ptr pin_vertices; + void Read(std::istream *stream, PmxSetting *setting); + }; + + /// PMXモデル + class PmxModel + { + public: + PmxModel() + : version(0.0f) + , vertex_count(0) + , index_count(0) + , texture_count(0) + , material_count(0) + , bone_count(0) + , morph_count(0) + , frame_count(0) + , rigid_body_count(0) + , joint_count(0) + , soft_body_count(0) + {} + + /// バージョン + float version; + /// 設定 + PmxSetting setting; + /// モデル名 + utfstring model_name; + /// モデル英名 + utfstring model_english_name; + /// コメント + utfstring model_comment; + /// 英語コメント + utfstring model_english_comment; + /// 頂点数 + int vertex_count; + /// 頂点配列 + std::unique_ptr vertices; + /// インデックス数 + int index_count; + /// インデックス配列 + std::unique_ptr indices; + /// テクスチャ数 + int texture_count; + /// テクスチャ配列 + std::unique_ptr< utfstring []> textures; + /// マテリアル数 + int material_count; + /// マテリアル + std::unique_ptr materials; + /// ボーン数 + int bone_count; + /// ボーン配列 + std::unique_ptr bones; + /// モーフ数 + int morph_count; + /// モーフ配列 + std::unique_ptr morphs; + /// 表示枠数 + int frame_count; + /// 表示枠配列 + std::unique_ptr frames; + /// 剛体数 + int rigid_body_count; + /// 剛体配列 + std::unique_ptr rigid_bodies; + /// ジョイント数 + int joint_count; + /// ジョイント配列 + std::unique_ptr joints; + /// ソフトボディ数 + int soft_body_count; + /// ソフトボディ配列 + std::unique_ptr soft_bodies; + /// モデル初期化 + void Init(); + /// モデル読み込み + void Read(std::istream *stream); + ///// ファイルからモデルの読み込み + //static std::unique_ptr ReadFromFile(const char *filename); + ///// 入力ストリームからモデルの読み込み + //static std::unique_ptr ReadFromStream(std::istream *stream); + }; +} diff --git a/code/MMDVmdParser.h b/code/MMDVmdParser.h new file mode 100644 index 000000000..889a579db --- /dev/null +++ b/code/MMDVmdParser.h @@ -0,0 +1,367 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "MMDCpp14.h" + +namespace vmd +{ + /// ボーンフレーム + class VmdBoneFrame + { + public: + /// ボーン名 + std::string name; + /// フレーム番号 + int frame; + /// 位置 + float position[3]; + /// 回転 + float orientation[4]; + /// 補間曲線 + char interpolation[4][4][4]; + + void Read(std::istream* stream) + { + char buffer[15]; + stream->read((char*) buffer, sizeof(char)*15); + name = std::string(buffer); + stream->read((char*) &frame, sizeof(int)); + stream->read((char*) position, sizeof(float)*3); + stream->read((char*) orientation, sizeof(float)*4); + stream->read((char*) interpolation, sizeof(char) * 4 * 4 * 4); + } + + void Write(std::ostream* stream) + { + stream->write((char*)name.c_str(), sizeof(char) * 15); + stream->write((char*)&frame, sizeof(int)); + stream->write((char*)position, sizeof(float) * 3); + stream->write((char*)orientation, sizeof(float) * 4); + stream->write((char*)interpolation, sizeof(char) * 4 * 4 * 4); + } + }; + + /// 表情フレーム + class VmdFaceFrame + { + public: + /// 表情名 + std::string face_name; + /// 表情の重み + float weight; + /// フレーム番号 + uint32_t frame; + + void Read(std::istream* stream) + { + char buffer[15]; + stream->read((char*) &buffer, sizeof(char) * 15); + face_name = std::string(buffer); + stream->read((char*) &frame, sizeof(int)); + stream->read((char*) &weight, sizeof(float)); + } + + void Write(std::ostream* stream) + { + stream->write((char*)face_name.c_str(), sizeof(char) * 15); + stream->write((char*)&frame, sizeof(int)); + stream->write((char*)&weight, sizeof(float)); + } + }; + + /// カメラフレーム + class VmdCameraFrame + { + public: + /// フレーム番号 + int frame; + /// 距離 + float distance; + /// 位置 + float position[3]; + /// 回転 + float orientation[3]; + /// 補間曲線 + char interpolation[6][4]; + /// 視野角 + float angle; + /// 不明データ + char unknown[3]; + + void Read(std::istream *stream) + { + stream->read((char*) &frame, sizeof(int)); + stream->read((char*) &distance, sizeof(float)); + stream->read((char*) position, sizeof(float) * 3); + stream->read((char*) orientation, sizeof(float) * 3); + stream->read((char*) interpolation, sizeof(char) * 24); + stream->read((char*) &angle, sizeof(float)); + stream->read((char*) unknown, sizeof(char) * 3); + } + + void Write(std::ostream *stream) + { + stream->write((char*)&frame, sizeof(int)); + stream->write((char*)&distance, sizeof(float)); + stream->write((char*)position, sizeof(float) * 3); + stream->write((char*)orientation, sizeof(float) * 3); + stream->write((char*)interpolation, sizeof(char) * 24); + stream->write((char*)&angle, sizeof(float)); + stream->write((char*)unknown, sizeof(char) * 3); + } + }; + + /// ライトフレーム + class VmdLightFrame + { + public: + /// フレーム番号 + int frame; + /// 色 + float color[3]; + /// 位置 + float position[3]; + + void Read(std::istream* stream) + { + stream->read((char*) &frame, sizeof(int)); + stream->read((char*) color, sizeof(float) * 3); + stream->read((char*) position, sizeof(float) * 3); + } + + void Write(std::ostream* stream) + { + stream->write((char*)&frame, sizeof(int)); + stream->write((char*)color, sizeof(float) * 3); + stream->write((char*)position, sizeof(float) * 3); + } + }; + + /// IKの有効無効 + class VmdIkEnable + { + public: + std::string ik_name; + bool enable; + }; + + /// IKフレーム + class VmdIkFrame + { + public: + int frame; + bool display; + std::vector ik_enable; + + void Read(std::istream *stream) + { + char buffer[20]; + stream->read((char*) &frame, sizeof(int)); + stream->read((char*) &display, sizeof(uint8_t)); + int ik_count; + stream->read((char*) &ik_count, sizeof(int)); + ik_enable.resize(ik_count); + for (int i = 0; i < ik_count; i++) + { + stream->read(buffer, 20); + ik_enable[i].ik_name = std::string(buffer); + stream->read((char*) &ik_enable[i].enable, sizeof(uint8_t)); + } + } + + void Write(std::ostream *stream) + { + stream->write((char*)&frame, sizeof(int)); + stream->write((char*)&display, sizeof(uint8_t)); + int ik_count = static_cast(ik_enable.size()); + stream->write((char*)&ik_count, sizeof(int)); + for (int i = 0; i < ik_count; i++) + { + const VmdIkEnable& ik_enable = this->ik_enable.at(i); + stream->write(ik_enable.ik_name.c_str(), 20); + stream->write((char*)&ik_enable.enable, sizeof(uint8_t)); + } + } + }; + + /// VMDモーション + class VmdMotion + { + public: + /// モデル名 + std::string model_name; + /// バージョン + int version; + /// ボーンフレーム + std::vector bone_frames; + /// 表情フレーム + std::vector face_frames; + /// カメラフレーム + std::vector camera_frames; + /// ライトフレーム + std::vector light_frames; + /// IKフレーム + std::vector ik_frames; + + static std::unique_ptr LoadFromFile(char const *filename) + { + std::ifstream stream(filename, std::ios::binary); + auto result = LoadFromStream(&stream); + stream.close(); + return result; + } + + static std::unique_ptr LoadFromStream(std::ifstream *stream) + { + + char buffer[30]; + auto result = std::make_unique(); + + // magic and version + stream->read((char*) buffer, 30); + if (strncmp(buffer, "Vocaloid Motion Data", 20)) + { + std::cerr << "invalid vmd file." << std::endl; + return nullptr; + } + result->version = std::atoi(buffer + 20); + + // name + stream->read(buffer, 20); + result->model_name = std::string(buffer); + + // bone frames + int bone_frame_num; + stream->read((char*) &bone_frame_num, sizeof(int)); + result->bone_frames.resize(bone_frame_num); + for (int i = 0; i < bone_frame_num; i++) + { + result->bone_frames[i].Read(stream); + } + + // face frames + int face_frame_num; + stream->read((char*) &face_frame_num, sizeof(int)); + result->face_frames.resize(face_frame_num); + for (int i = 0; i < face_frame_num; i++) + { + result->face_frames[i].Read(stream); + } + + // camera frames + int camera_frame_num; + stream->read((char*) &camera_frame_num, sizeof(int)); + result->camera_frames.resize(camera_frame_num); + for (int i = 0; i < camera_frame_num; i++) + { + result->camera_frames[i].Read(stream); + } + + // light frames + int light_frame_num; + stream->read((char*) &light_frame_num, sizeof(int)); + result->light_frames.resize(light_frame_num); + for (int i = 0; i < light_frame_num; i++) + { + result->light_frames[i].Read(stream); + } + + // unknown2 + stream->read(buffer, 4); + + // ik frames + if (stream->peek() != std::ios::traits_type::eof()) + { + int ik_num; + stream->read((char*) &ik_num, sizeof(int)); + result->ik_frames.resize(ik_num); + for (int i = 0; i < ik_num; i++) + { + result->ik_frames[i].Read(stream); + } + } + + if (stream->peek() != std::ios::traits_type::eof()) + { + std::cerr << "vmd stream has unknown data." << std::endl; + } + + return result; + } + + bool SaveToFile(const std::u16string& filename) + { + // TODO: How to adapt u16string to string? + /* + std::ofstream stream(filename.c_str(), std::ios::binary); + auto result = SaveToStream(&stream); + stream.close(); + return result; + */ + return false; + } + + bool SaveToStream(std::ofstream *stream) + { + std::string magic = "Vocaloid Motion Data 0002\0"; + magic.resize(30); + + // magic and version + stream->write(magic.c_str(), 30); + + // name + stream->write(model_name.c_str(), 20); + + // bone frames + const int bone_frame_num = static_cast(bone_frames.size()); + stream->write(reinterpret_cast(&bone_frame_num), sizeof(int)); + for (int i = 0; i < bone_frame_num; i++) + { + bone_frames[i].Write(stream); + } + + // face frames + const int face_frame_num = static_cast(face_frames.size()); + stream->write(reinterpret_cast(&face_frame_num), sizeof(int)); + for (int i = 0; i < face_frame_num; i++) + { + face_frames[i].Write(stream); + } + + // camera frames + const int camera_frame_num = static_cast(camera_frames.size()); + stream->write(reinterpret_cast(&camera_frame_num), sizeof(int)); + for (int i = 0; i < camera_frame_num; i++) + { + camera_frames[i].Write(stream); + } + + // light frames + const int light_frame_num = static_cast(light_frames.size()); + stream->write(reinterpret_cast(&light_frame_num), sizeof(int)); + for (int i = 0; i < light_frame_num; i++) + { + light_frames[i].Write(stream); + } + + // self shadow datas + const int self_shadow_num = 0; + stream->write(reinterpret_cast(&self_shadow_num), sizeof(int)); + + // ik frames + const int ik_num = static_cast(ik_frames.size()); + stream->write(reinterpret_cast(&ik_num), sizeof(int)); + for (int i = 0; i < ik_num; i++) + { + ik_frames[i].Write(stream); + } + + return true; + } + }; +} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b6035d951..5b7fc6b91 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -105,6 +105,7 @@ SET( TEST_SRCS unit/utObjImportExport.cpp unit/utPretransformVertices.cpp unit/utPLYImportExport.cpp + unit/utPMXImporter.cpp unit/utRemoveComments.cpp unit/utRemoveComponent.cpp unit/utRemoveRedundantMaterials.cpp diff --git a/test/unit/utPMXImporter.cpp b/test/unit/utPMXImporter.cpp new file mode 100644 index 000000000..cf70fd65d --- /dev/null +++ b/test/unit/utPMXImporter.cpp @@ -0,0 +1,62 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2016, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +#include "UnitTestPCH.h" +#include "SceneDiffer.h" +#include "AbstractImportExportBase.h" +#include "MMDImporter.h" + +#include + +using namespace ::Assimp; + +class utPMXImporter : public AbstractImportExportBase { +public: + virtual bool importerTest() { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/../models-nonbsd/MMD/kawakaze.pmx", 0 ); + return nullptr != scene; + } +}; + +TEST_F( utPMXImporter, importTest ) { + EXPECT_TRUE( importerTest() ); +} diff --git a/tools/assimp_qt_viewer/CMakeLists.txt b/tools/assimp_qt_viewer/CMakeLists.txt index 2985d6e30..42ef0fb34 100644 --- a/tools/assimp_qt_viewer/CMakeLists.txt +++ b/tools/assimp_qt_viewer/CMakeLists.txt @@ -1,5 +1,5 @@ -project(assimp_qt_viewer) set(PROJECT_VERSION "") +project(assimp_qt_viewer) cmake_minimum_required(VERSION 2.6) From e80d3aa9d4974800259ea6eb3c3ca5185fc79c28 Mon Sep 17 00:00:00 2001 From: UMW Date: Mon, 27 Feb 2017 21:02:58 +0800 Subject: [PATCH 02/23] finish mesh creation --- code/CMakeLists.txt | 4 ++ code/MMDImporter.cpp | 88 ++++++++++++++++++++++++++++++++++--------- code/MMDImporter.h | 3 ++ code/MMDPmxParser.cpp | 2 +- code/MMDPmxParser.h | 4 +- 5 files changed, 81 insertions(+), 20 deletions(-) diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 2442513cb..7987147f5 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -667,7 +667,11 @@ ADD_ASSIMP_IMPORTER( MMD MMDVmdParser.h ) +# use custom FindICU.cmake +set(CMAKE_MODULE_PATH_BACKUP ${CMAKE_MODULE_PATH}) +set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}) find_package(ICU COMPONENTS uc io REQUIRED) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH_BACKUP}) SET( Step_SRCS StepExporter.h diff --git a/code/MMDImporter.cpp b/code/MMDImporter.cpp index 4179a7cc9..e1482a24d 100644 --- a/code/MMDImporter.cpp +++ b/code/MMDImporter.cpp @@ -47,6 +47,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "MMDPmdParser.h" #include "MMDVmdParser.h" //#include "IOStreamBuffer.h" +#include "ConvertToLHProcess.h" #include #include #include @@ -156,31 +157,84 @@ void MMDImporter::CreateDataFromImport(const pmx::PmxModel* pModel, aiScene* pSc std::cout << pScene->mRootNode->mName.C_Str() << std::endl; - // workaround, must be deleted + pScene->mRootNode->mNumMeshes = 1; + pScene->mRootNode->mMeshes = new unsigned int[1]; + pScene->mRootNode->mMeshes[0] = 0; + + //aiMesh *pMesh = new aiMesh; + aiMaterial* mat = new aiMaterial; pScene->mNumMeshes = 1; pScene->mNumMaterials = 1; - pScene->mRootNode->mMeshes = new unsigned int[1]; + pScene->mMeshes = new aiMesh*[1]; + pScene->mMaterials = new aiMaterial*[1]; + pScene->mMeshes[0] = CreateMesh(pModel, pScene); + pScene->mMaterials[0] = mat; + + /** + pMesh->mNumVertices = 3; + pMesh->mVertices = new aiVector3D[3]; + pMesh->mVertices[0] = aiVector3D(1.0, 0.0, 0.0); + pMesh->mVertices[1] = aiVector3D(0.0, 1.0, 0.0); + pMesh->mVertices[2] = aiVector3D(0.0, 0.0, 1.0); + + pMesh->mNumFaces= 1; + pMesh->mFaces = new aiFace[1]; + pMesh->mFaces[0].mNumIndices = 3; + pMesh->mFaces[0].mIndices = new unsigned int[3]; + pMesh->mFaces[0].mIndices[0] = 0; + pMesh->mFaces[0].mIndices[1] = 1; + pMesh->mFaces[0].mIndices[2] = 2; + **/ + + // Convert everything to OpenGL space... it's the same operation as the conversion back, so we can reuse the step directly + MakeLeftHandedProcess convertProcess; + convertProcess.Execute( pScene); + + FlipWindingOrderProcess flipper; + flipper.Execute(pScene); + +} + +// ------------------------------------------------------------------------------------------------ +aiMesh* MMDImporter::CreateMesh(const pmx::PmxModel* pModel, aiScene* pScene) +{ aiMesh *pMesh = new aiMesh; - pScene->mRootNode->mMeshes[0] = 100; - // workaround -/* - // Create nodes for the whole scene - std::vector MeshArray; - for ( size_t index = 0; index < pModel->bone_count; index++ ) { - createNodes( pModel, pModel->bones[i], pScene, MeshArray); - } + pMesh->mNumVertices = pModel->vertex_count; + std::vector vertices_refID(pMesh->mNumVertices, -1); + vertices_refID.reserve(3*pMesh->mNumVertices); - if ( pScene->mNumMeshes > 0 ) { - pScene->mMeshes = new aiMesh*[ MeshArray.size() ]; - for ( size_t index = 0; index < MeshArray.size(); index++ ) { - pScene->mMeshes[ index ] = MeshArray[ index ]; + pMesh->mNumFaces = pModel->index_count / 3; + pMesh->mFaces = new aiFace[ pMesh->mNumFaces ]; + + for( unsigned int index = 0; index < pMesh->mNumFaces; index++ ) { + pMesh->mFaces[index].mNumIndices = 3; + unsigned int *indices = new unsigned int[3]; + // here we change LH to RH coord + indices[0] = pModel->indices[3*index]; + indices[1] = pModel->indices[3*index+1]; + indices[2] = pModel->indices[3*index+2]; + for( unsigned int j = 0; j < 3; j++ ) { + if(vertices_refID[indices[j]] != -1) { + vertices_refID.push_back(indices[j]); + indices[j] = vertices_refID.size() - 1; + } + else { + vertices_refID[indices[j]] = indices[j]; + } } + pMesh->mFaces[index].mIndices = indices; } - // Create all materials - createMaterials( pModel, pScene ); - */ + pMesh->mNumVertices = vertices_refID.size(); + pMesh->mVertices = new aiVector3D[ pMesh->mNumVertices ]; + + for( unsigned int index = 0; index < pMesh->mNumVertices; index++ ) { + const float* position = pModel->vertices[vertices_refID[index]].position; + pMesh->mVertices[index].Set(position[0], position[1], position[2]); + } + + return pMesh; } // ------------------------------------------------------------------------------------------------ diff --git a/code/MMDImporter.h b/code/MMDImporter.h index 64ae1eb60..bde4fcbf2 100644 --- a/code/MMDImporter.h +++ b/code/MMDImporter.h @@ -84,6 +84,9 @@ private: //! \brief Create the data from imported content. void CreateDataFromImport(const pmx::PmxModel* pModel, aiScene* pScene); + //! \brief Create the mesh + aiMesh* CreateMesh(const pmx::PmxModel* pModel, aiScene* pScene); + private: //! Data buffer std::vector m_Buffer; diff --git a/code/MMDPmxParser.cpp b/code/MMDPmxParser.cpp index 639574a59..b656a4ef4 100644 --- a/code/MMDPmxParser.cpp +++ b/code/MMDPmxParser.cpp @@ -169,7 +169,7 @@ namespace pmx void PmxVertex::Read(std::istream *stream, PmxSetting *setting) { - stream->read((char*) this->positon, sizeof(float) * 3); + stream->read((char*) this->position, sizeof(float) * 3); stream->read((char*) this->normal, sizeof(float) * 3); stream->read((char*) this->uv, sizeof(float) * 2); for (int i = 0; i < setting->uv; ++i) diff --git a/code/MMDPmxParser.h b/code/MMDPmxParser.h index 1387fef58..5b64370b6 100644 --- a/code/MMDPmxParser.h +++ b/code/MMDPmxParser.h @@ -174,7 +174,7 @@ namespace pmx { uv[0] = uv[1] = 0.0f; for (int i = 0; i < 3; ++i) { - positon[i] = 0.0f; + position[i] = 0.0f; normal[i] = 0.0f; } for (int i = 0; i < 4; ++i) { @@ -185,7 +185,7 @@ namespace pmx } /// 位置 - float positon[3]; + float position[3]; /// 法線 float normal[3]; /// テクスチャ座標 From e89c29a9cc40e6b191db27e958857bac9b93c437 Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Wed, 1 Mar 2017 13:52:46 +0800 Subject: [PATCH 03/23] finish vertex --- code/MMDImporter.cpp | 116 ++++++++++++++++++++++++------------------- code/MMDImporter.h | 2 +- code/MMDPmdParser.h | 2 +- 3 files changed, 66 insertions(+), 54 deletions(-) diff --git a/code/MMDImporter.cpp b/code/MMDImporter.cpp index e1482a24d..69e885498 100644 --- a/code/MMDImporter.cpp +++ b/code/MMDImporter.cpp @@ -147,46 +147,51 @@ void MMDImporter::CreateDataFromImport(const pmx::PmxModel* pModel, aiScene* pSc return; } - pScene->mRootNode = new aiNode; + aiNode *pNode = new aiNode; if ( !pModel->model_name.empty() ) { - pScene->mRootNode->mName.Set(pModel->model_name); + pNode->mName.Set(pModel->model_name); } else { ai_assert(false); } + pScene->mRootNode = pNode; std::cout << pScene->mRootNode->mName.C_Str() << std::endl; + std::cout << pModel->index_count << std::endl; - pScene->mRootNode->mNumMeshes = 1; - pScene->mRootNode->mMeshes = new unsigned int[1]; - pScene->mRootNode->mMeshes[0] = 0; + // split mesh by materials + pNode->mNumMeshes = pModel->material_count; + pNode->mMeshes = new unsigned int[pNode->mNumMeshes]; + for( unsigned int index = 0; index < pNode->mNumMeshes; index++ ) { + pNode->mMeshes[index] = index; + } - //aiMesh *pMesh = new aiMesh; - aiMaterial* mat = new aiMaterial; - pScene->mNumMeshes = 1; - pScene->mNumMaterials = 1; - pScene->mMeshes = new aiMesh*[1]; - pScene->mMaterials = new aiMaterial*[1]; - pScene->mMeshes[0] = CreateMesh(pModel, pScene); - pScene->mMaterials[0] = mat; + pScene->mNumMeshes = pNode->mNumMeshes; + pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; + for( unsigned int i = 0, indexStart = 0; i < pScene->mNumMeshes; i++ ) { + const int indexCount = pModel->materials[i].index_count; - /** - pMesh->mNumVertices = 3; - pMesh->mVertices = new aiVector3D[3]; - pMesh->mVertices[0] = aiVector3D(1.0, 0.0, 0.0); - pMesh->mVertices[1] = aiVector3D(0.0, 1.0, 0.0); - pMesh->mVertices[2] = aiVector3D(0.0, 0.0, 1.0); + std::cout << pModel->materials[i].material_name << std::endl; + std::cout << indexStart << " " << indexCount << std::endl; - pMesh->mNumFaces= 1; - pMesh->mFaces = new aiFace[1]; - pMesh->mFaces[0].mNumIndices = 3; - pMesh->mFaces[0].mIndices = new unsigned int[3]; - pMesh->mFaces[0].mIndices[0] = 0; - pMesh->mFaces[0].mIndices[1] = 1; - pMesh->mFaces[0].mIndices[2] = 2; - **/ - - // Convert everything to OpenGL space... it's the same operation as the conversion back, so we can reuse the step directly + pScene->mMeshes[i] = CreateMesh(pModel, indexStart, indexCount); + pScene->mMeshes[i]->mName = pModel->materials[i].material_name; + pScene->mMeshes[i]->mMaterialIndex = i; + indexStart += indexCount; + } + + pScene->mNumMaterials = pModel->material_count; + pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials]; + for( unsigned int i = 0; i < pScene->mNumMaterials; i++ ) { + aiMaterial* mat = new aiMaterial; + pScene->mMaterials[i] = mat; + aiString name(pModel->materials[i].material_name); + mat->AddProperty(&name, AI_MATKEY_NAME); + aiColor3D color(0.01*i, 0.01*i, 0.01*i); + mat->AddProperty(&color, 1, AI_MATKEY_COLOR_DIFFUSE); + } + + // Convert everything to OpenGL space MakeLeftHandedProcess convertProcess; convertProcess.Execute( pScene); @@ -196,42 +201,49 @@ void MMDImporter::CreateDataFromImport(const pmx::PmxModel* pModel, aiScene* pSc } // ------------------------------------------------------------------------------------------------ -aiMesh* MMDImporter::CreateMesh(const pmx::PmxModel* pModel, aiScene* pScene) +aiMesh* MMDImporter::CreateMesh(const pmx::PmxModel* pModel, const int indexStart, const int indexCount) { aiMesh *pMesh = new aiMesh; - pMesh->mNumVertices = pModel->vertex_count; - std::vector vertices_refID(pMesh->mNumVertices, -1); - vertices_refID.reserve(3*pMesh->mNumVertices); + pMesh->mNumVertices = indexCount; - pMesh->mNumFaces = pModel->index_count / 3; + pMesh->mNumFaces = indexCount / 3; pMesh->mFaces = new aiFace[ pMesh->mNumFaces ]; for( unsigned int index = 0; index < pMesh->mNumFaces; index++ ) { - pMesh->mFaces[index].mNumIndices = 3; - unsigned int *indices = new unsigned int[3]; - // here we change LH to RH coord - indices[0] = pModel->indices[3*index]; - indices[1] = pModel->indices[3*index+1]; - indices[2] = pModel->indices[3*index+2]; - for( unsigned int j = 0; j < 3; j++ ) { - if(vertices_refID[indices[j]] != -1) { - vertices_refID.push_back(indices[j]); - indices[j] = vertices_refID.size() - 1; - } - else { - vertices_refID[indices[j]] = indices[j]; - } - } + const int numIndices = 3; // trianglular face + pMesh->mFaces[index].mNumIndices = numIndices; + unsigned int *indices = new unsigned int[numIndices]; + indices[0] = numIndices * index; + indices[1] = numIndices * index + 1; + indices[2] = numIndices * index + 2; pMesh->mFaces[index].mIndices = indices; } - pMesh->mNumVertices = vertices_refID.size(); pMesh->mVertices = new aiVector3D[ pMesh->mNumVertices ]; + pMesh->mNormals = new aiVector3D[ pMesh->mNumVertices ]; + pMesh->mTextureCoords[0] = new aiVector3D[ pMesh->mNumVertices ]; + pMesh->mNumUVComponents[0] = 2; - for( unsigned int index = 0; index < pMesh->mNumVertices; index++ ) { - const float* position = pModel->vertices[vertices_refID[index]].position; + // additional UVs + for( int i = 1; i <= pModel->setting.uv; i++ ) { + pMesh->mTextureCoords[i] = new aiVector3D[ pMesh->mNumVertices ]; + pMesh->mNumUVComponents[i] = 4; + } + + for( int index = 0; index < indexCount; index++ ) { + const pmx::PmxVertex *v = &pModel->vertices[pModel->indices[indexStart + index]]; + const float* position = v->position; pMesh->mVertices[index].Set(position[0], position[1], position[2]); + const float* normal = v->normal; + pMesh->mNormals[index].Set(normal[0], normal[1], normal[2]); + pMesh->mTextureCoords[0][index].x = v->uv[0]; + pMesh->mTextureCoords[0][index].y = v->uv[1]; + for( int i = 1; i <= pModel->setting.uv; i++ ) { + // TODO: wrong here? use quaternion transform? + pMesh->mTextureCoords[i][index].x = v->uva[i][2] - v->uva[i][0]; + pMesh->mTextureCoords[i][index].y = v->uva[i][3] - v->uva[i][1]; + } } return pMesh; diff --git a/code/MMDImporter.h b/code/MMDImporter.h index bde4fcbf2..66e9c1fd4 100644 --- a/code/MMDImporter.h +++ b/code/MMDImporter.h @@ -85,7 +85,7 @@ private: void CreateDataFromImport(const pmx::PmxModel* pModel, aiScene* pScene); //! \brief Create the mesh - aiMesh* CreateMesh(const pmx::PmxModel* pModel, aiScene* pScene); + aiMesh* CreateMesh(const pmx::PmxModel* pModel, const int indexStart, const int indexCount); private: //! Data buffer diff --git a/code/MMDPmdParser.h b/code/MMDPmdParser.h index c1c7656f5..233e5de14 100644 --- a/code/MMDPmdParser.h +++ b/code/MMDPmdParser.h @@ -118,7 +118,7 @@ namespace pmd sphere_filename.clear(); } else { - *pstar = NULL; + *pstar = (char)NULL; texture_filename = std::string(buffer); sphere_filename = std::string(pstar+1); } From 7a25f5ac252d0efb8b9928c18c32d473ba123b07 Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Thu, 2 Mar 2017 23:16:29 +0800 Subject: [PATCH 04/23] finish UV texture --- code/MMDImporter.cpp | 73 ++++++++++++++++++++++++++++++++++++++------ code/MMDImporter.h | 13 ++------ 2 files changed, 67 insertions(+), 19 deletions(-) diff --git a/code/MMDImporter.cpp b/code/MMDImporter.cpp index 69e885498..681608516 100644 --- a/code/MMDImporter.cpp +++ b/code/MMDImporter.cpp @@ -159,6 +159,12 @@ void MMDImporter::CreateDataFromImport(const pmx::PmxModel* pModel, aiScene* pSc std::cout << pScene->mRootNode->mName.C_Str() << std::endl; std::cout << pModel->index_count << std::endl; + pNode = new aiNode; + pScene->mRootNode->addChildren(1, &pNode); + pScene->mRootNode->mNumChildren = 1; + pNode->mParent = pScene->mRootNode; + pNode->mName.Set(string(pModel->model_name) + string("_mesh")); + // split mesh by materials pNode->mNumMeshes = pModel->material_count; pNode->mMeshes = new unsigned int[pNode->mNumMeshes]; @@ -180,15 +186,34 @@ void MMDImporter::CreateDataFromImport(const pmx::PmxModel* pModel, aiScene* pSc indexStart += indexCount; } + // create textures, may be dummy? + /* + pScene->mNumTextures = pModel->texture_count; + pScene->mTextures = new aiTexture*[pScene->mNumTextures]; + for( unsigned int i = 0; i < pScene->mNumTextures; ++i) { + aiTexture *tex = new aiTexture; + pScene->mTextures[i] = tex; + strcpy(tex->achFormatHint, "png"); + tex->mHeight = 0; + ifstream file(pModel->textures[i], ios::binary | ios::ate); + streamsize size = file.tellg(); + file.seekg(0, ios::beg); + char *buffer = new char[size]; + file.read(buffer, size); + if(file.bad()) { + string err("PMX: Can't open texture file"); + err.append(pModel->textures[i]); + throw DeadlyExportError(err); + } + tex->pcData = (aiTexel*)buffer; + } + */ + + // create materials pScene->mNumMaterials = pModel->material_count; pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials]; for( unsigned int i = 0; i < pScene->mNumMaterials; i++ ) { - aiMaterial* mat = new aiMaterial; - pScene->mMaterials[i] = mat; - aiString name(pModel->materials[i].material_name); - mat->AddProperty(&name, AI_MATKEY_NAME); - aiColor3D color(0.01*i, 0.01*i, 0.01*i); - mat->AddProperty(&color, 1, AI_MATKEY_COLOR_DIFFUSE); + pScene->mMaterials[i] = CreateMaterial(&pModel->materials[i], pModel); } // Convert everything to OpenGL space @@ -238,17 +263,47 @@ aiMesh* MMDImporter::CreateMesh(const pmx::PmxModel* pModel, const int indexStar const float* normal = v->normal; pMesh->mNormals[index].Set(normal[0], normal[1], normal[2]); pMesh->mTextureCoords[0][index].x = v->uv[0]; - pMesh->mTextureCoords[0][index].y = v->uv[1]; + pMesh->mTextureCoords[0][index].y = -v->uv[1]; for( int i = 1; i <= pModel->setting.uv; i++ ) { // TODO: wrong here? use quaternion transform? - pMesh->mTextureCoords[i][index].x = v->uva[i][2] - v->uva[i][0]; - pMesh->mTextureCoords[i][index].y = v->uva[i][3] - v->uva[i][1]; + pMesh->mTextureCoords[i][index].x = v->uva[i][0]; + pMesh->mTextureCoords[i][index].y = v->uva[i][1]; } } return pMesh; } +// ------------------------------------------------------------------------------------------------ +aiMaterial* MMDImporter::CreateMaterial(const pmx::PmxMaterial* pMat, const pmx::PmxModel* pModel) +{ + aiMaterial *mat = new aiMaterial(); + aiString name(pMat->material_english_name); + mat->AddProperty(&name, AI_MATKEY_NAME); + + aiColor3D diffuse(pMat->diffuse[0], pMat->diffuse[1], pMat->diffuse[2]); + mat->AddProperty(&diffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + aiColor3D specular(pMat->specular[0], pMat->specular[1], pMat->specular[2]); + mat->AddProperty(&specular, 1, AI_MATKEY_COLOR_SPECULAR); + aiColor3D ambient(pMat->ambient[0], pMat->ambient[1], pMat->ambient[2]); + mat->AddProperty(&ambient, 1, AI_MATKEY_COLOR_AMBIENT); + + float opacity = pMat->diffuse[3]; + mat->AddProperty(&opacity, 1, AI_MATKEY_OPACITY); + float shininess = pMat->specularlity; + mat->AddProperty(&shininess, 1, AI_MATKEY_SHININESS_STRENGTH); + + aiString texture_path(pModel->textures[pMat->diffuse_texture_index]); + mat->AddProperty(&texture_path, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0)); + int mapping_uvwsrc = 0; + mat->AddProperty(&mapping_uvwsrc, 1, AI_MATKEY_UVWSRC(aiTextureType_DIFFUSE, 0)); + int mapping_mode = aiTextureMapMode_Mirror; + mat->AddProperty(&mapping_mode, 1, AI_MATKEY_MAPPINGMODE_U(aiTextureType_DIFFUSE, 0)); + mat->AddProperty(&mapping_mode, 1, AI_MATKEY_MAPPINGMODE_V(aiTextureType_DIFFUSE, 0)); + + return mat; +} + // ------------------------------------------------------------------------------------------------ } // Namespace Assimp diff --git a/code/MMDImporter.h b/code/MMDImporter.h index 66e9c1fd4..e660abaaa 100644 --- a/code/MMDImporter.h +++ b/code/MMDImporter.h @@ -46,17 +46,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include struct aiMesh; -struct aiNode; namespace Assimp { -/* -namespace MMDFile { - struct Object; - struct Model; -} -*/ - // ------------------------------------------------------------------------------------------------ /// \class MMDImporter /// \brief Imports MMD a pmx/pmd/vmd file @@ -87,11 +79,12 @@ private: //! \brief Create the mesh aiMesh* CreateMesh(const pmx::PmxModel* pModel, const int indexStart, const int indexCount); + //! \brief Create the material + aiMaterial* CreateMaterial(const pmx::PmxMaterial* pMat, const pmx::PmxModel* pModel); + private: //! Data buffer std::vector m_Buffer; - //! Pointer to root object instance - //MMDFile::Object *m_pRootObject; //! Absolute pathname of model in file system std::string m_strAbsPath; }; From 0c0ca403b5ee7fb68807c0451b87c971e3f9b43c Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Sun, 5 Mar 2017 20:40:41 +0800 Subject: [PATCH 05/23] Convert to OpenGL space by three processes. --- code/MMDImporter.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/code/MMDImporter.cpp b/code/MMDImporter.cpp index 681608516..e55dc8d2f 100644 --- a/code/MMDImporter.cpp +++ b/code/MMDImporter.cpp @@ -218,10 +218,13 @@ void MMDImporter::CreateDataFromImport(const pmx::PmxModel* pModel, aiScene* pSc // Convert everything to OpenGL space MakeLeftHandedProcess convertProcess; - convertProcess.Execute( pScene); + convertProcess.Execute(pScene); - FlipWindingOrderProcess flipper; - flipper.Execute(pScene); + FlipUVsProcess uvFlipper; + uvFlipper.Execute(pScene); + + FlipWindingOrderProcess windingFlipper; + windingFlipper.Execute(pScene); } @@ -263,7 +266,7 @@ aiMesh* MMDImporter::CreateMesh(const pmx::PmxModel* pModel, const int indexStar const float* normal = v->normal; pMesh->mNormals[index].Set(normal[0], normal[1], normal[2]); pMesh->mTextureCoords[0][index].x = v->uv[0]; - pMesh->mTextureCoords[0][index].y = -v->uv[1]; + pMesh->mTextureCoords[0][index].y = v->uv[1]; for( int i = 1; i <= pModel->setting.uv; i++ ) { // TODO: wrong here? use quaternion transform? pMesh->mTextureCoords[i][index].x = v->uva[i][0]; @@ -297,9 +300,6 @@ aiMaterial* MMDImporter::CreateMaterial(const pmx::PmxMaterial* pMat, const pmx: mat->AddProperty(&texture_path, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0)); int mapping_uvwsrc = 0; mat->AddProperty(&mapping_uvwsrc, 1, AI_MATKEY_UVWSRC(aiTextureType_DIFFUSE, 0)); - int mapping_mode = aiTextureMapMode_Mirror; - mat->AddProperty(&mapping_mode, 1, AI_MATKEY_MAPPINGMODE_U(aiTextureType_DIFFUSE, 0)); - mat->AddProperty(&mapping_mode, 1, AI_MATKEY_MAPPINGMODE_V(aiTextureType_DIFFUSE, 0)); return mat; } From 34906071fc7bbd7b9d051f53255088a8911be68b Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Sun, 5 Mar 2017 22:59:53 +0800 Subject: [PATCH 06/23] remove dependency of ICU library --- FindICU.cmake | 690 ------------------------------------------ code/CMakeLists.txt | 6 - code/MMDPmxParser.cpp | 65 ++-- code/MMDPmxParser.h | 45 ++- 4 files changed, 44 insertions(+), 762 deletions(-) delete mode 100644 FindICU.cmake diff --git a/FindICU.cmake b/FindICU.cmake deleted file mode 100644 index 59dd891af..000000000 --- a/FindICU.cmake +++ /dev/null @@ -1,690 +0,0 @@ -# This module can find the International Components for Unicode (ICU) libraries -# -# Requirements: -# - CMake >= 2.8.3 (for new version of find_package_handle_standard_args) -# -# The following variables will be defined for your use: -# - ICU_FOUND : were all of your specified components found? -# - ICU_INCLUDE_DIRS : ICU include directory -# - ICU_LIBRARIES : ICU libraries -# - ICU_VERSION : complete version of ICU (x.y.z) -# - ICU_VERSION_MAJOR : major version of ICU -# - ICU_VERSION_MINOR : minor version of ICU -# - ICU_VERSION_PATCH : patch version of ICU -# - ICU__FOUND : were found? (FALSE for non specified component if it is not a dependency) -# -# For windows or non standard installation, define ICU_ROOT_DIR variable to point to the root installation of ICU. Two ways: -# - run cmake with -DICU_ROOT_DIR= -# - define an environment variable with the same name before running cmake -# With cmake-gui, before pressing "Configure": -# 1) Press "Add Entry" button -# 2) Add a new entry defined as: -# - Name: ICU_ROOT_DIR -# - Type: choose PATH in the selection list -# - Press "..." button and select the root installation of ICU -# -# Example Usage: -# -# 1. Copy this file in the root of your project source directory -# 2. Then, tell CMake to search this non-standard module in your project directory by adding to your CMakeLists.txt: -# set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}) -# 3. Finally call find_package() once, here are some examples to pick from -# -# Require ICU 4.4 or later -# find_package(ICU 4.4 REQUIRED) -# -# if(ICU_FOUND) -# add_executable(myapp myapp.c) -# include_directories(${ICU_INCLUDE_DIRS}) -# target_link_libraries(myapp ${ICU_LIBRARIES}) -# # with CMake >= 3.0.0, the last two lines can be replaced by the following -# target_link_libraries(myapp ICU::ICU) -# endif(ICU_FOUND) - -########## ########## - -find_package(PkgConfig QUIET) - -########## Private ########## -if(NOT DEFINED ICU_PUBLIC_VAR_NS) - set(ICU_PUBLIC_VAR_NS "ICU") # Prefix for all ICU relative public variables -endif(NOT DEFINED ICU_PUBLIC_VAR_NS) -if(NOT DEFINED ICU_PRIVATE_VAR_NS) - set(ICU_PRIVATE_VAR_NS "_${ICU_PUBLIC_VAR_NS}") # Prefix for all ICU relative internal variables -endif(NOT DEFINED ICU_PRIVATE_VAR_NS) -if(NOT DEFINED PC_ICU_PRIVATE_VAR_NS) - set(PC_ICU_PRIVATE_VAR_NS "_PC${ICU_PRIVATE_VAR_NS}") # Prefix for all pkg-config relative internal variables -endif(NOT DEFINED PC_ICU_PRIVATE_VAR_NS) - -set(${ICU_PRIVATE_VAR_NS}_HINTS ) -# -# for future removal -if(DEFINED ENV{ICU_ROOT}) - list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS "$ENV{ICU_ROOT}") - message(AUTHOR_WARNING "ENV{ICU_ROOT} is deprecated in favor of ENV{ICU_ROOT_DIR}") -endif(DEFINED ENV{ICU_ROOT}) -if (DEFINED ICU_ROOT) - list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS "${ICU_ROOT}") - message(AUTHOR_WARNING "ICU_ROOT is deprecated in favor of ICU_ROOT_DIR") -endif(DEFINED ICU_ROOT) -# -if(DEFINED ENV{ICU_ROOT_DIR}) - list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS "$ENV{ICU_ROOT_DIR}") -endif(DEFINED ENV{ICU_ROOT_DIR}) -if (DEFINED ICU_ROOT_DIR) - list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS "${ICU_ROOT_DIR}") -endif(DEFINED ICU_ROOT_DIR) - -set(${ICU_PRIVATE_VAR_NS}_COMPONENTS ) -# ... -macro(_icu_declare_component _NAME) - list(APPEND ${ICU_PRIVATE_VAR_NS}_COMPONENTS ${_NAME}) - set("${ICU_PRIVATE_VAR_NS}_COMPONENTS_${_NAME}" ${ARGN}) -endmacro(_icu_declare_component) - -_icu_declare_component(data icudata) -_icu_declare_component(uc icuuc) # Common and Data libraries -_icu_declare_component(i18n icui18n icuin) # Internationalization library -_icu_declare_component(io icuio ustdio) # Stream and I/O Library -_icu_declare_component(le icule) # Layout library -_icu_declare_component(lx iculx) # Paragraph Layout library - -########## Public ########## -set(${ICU_PUBLIC_VAR_NS}_FOUND FALSE) -set(${ICU_PUBLIC_VAR_NS}_LIBRARIES ) -set(${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS ) -set(${ICU_PUBLIC_VAR_NS}_C_FLAGS "") -set(${ICU_PUBLIC_VAR_NS}_CXX_FLAGS "") -set(${ICU_PUBLIC_VAR_NS}_CPP_FLAGS "") -set(${ICU_PUBLIC_VAR_NS}_C_SHARED_FLAGS "") -set(${ICU_PUBLIC_VAR_NS}_CXX_SHARED_FLAGS "") -set(${ICU_PUBLIC_VAR_NS}_CPP_SHARED_FLAGS "") - -foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PRIVATE_VAR_NS}_COMPONENTS}) - string(TOUPPER "${${ICU_PRIVATE_VAR_NS}_COMPONENT}" ${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT) - set("${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_FOUND" FALSE) # may be done in the _icu_declare_component macro -endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT) - -# Check components -if(NOT ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS) # uc required at least - set(${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS uc) -else(NOT ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS) - list(APPEND ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS uc) - list(REMOVE_DUPLICATES ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS) - foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS}) - if(NOT DEFINED ${ICU_PRIVATE_VAR_NS}_COMPONENTS_${${ICU_PRIVATE_VAR_NS}_COMPONENT}) - message(FATAL_ERROR "Unknown ICU component: ${${ICU_PRIVATE_VAR_NS}_COMPONENT}") - endif(NOT DEFINED ${ICU_PRIVATE_VAR_NS}_COMPONENTS_${${ICU_PRIVATE_VAR_NS}_COMPONENT}) - endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT) -endif(NOT ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS) - -# if pkg-config is available check components dependencies and append `pkg-config icu- --variable=prefix` to hints -if(PKG_CONFIG_FOUND) - set(${ICU_PRIVATE_VAR_NS}_COMPONENTS_DUP ${${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS}) - foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PRIVATE_VAR_NS}_COMPONENTS_DUP}) - pkg_check_modules(${PC_ICU_PRIVATE_VAR_NS} "icu-${${ICU_PRIVATE_VAR_NS}_COMPONENT}" QUIET) - - if(${PC_ICU_PRIVATE_VAR_NS}_FOUND) - list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS ${${PC_ICU_PRIVATE_VAR_NS}_PREFIX}) - foreach(${PC_ICU_PRIVATE_VAR_NS}_LIBRARY ${${PC_ICU_PRIVATE_VAR_NS}_LIBRARIES}) - string(REGEX REPLACE "^icu" "" ${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY ${${PC_ICU_PRIVATE_VAR_NS}_LIBRARY}) - if(NOT ${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY STREQUAL "data") - list(FIND ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS ${${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY} ${ICU_PRIVATE_VAR_NS}_COMPONENT_INDEX) - if(${ICU_PRIVATE_VAR_NS}_COMPONENT_INDEX EQUAL -1) - message(WARNING "Missing component dependency: ${${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY}. Add it to your find_package(ICU) line as COMPONENTS to fix this warning.") - list(APPEND ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS ${${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY}) - endif(${ICU_PRIVATE_VAR_NS}_COMPONENT_INDEX EQUAL -1) - endif(NOT ${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY STREQUAL "data") - endforeach(${PC_ICU_PRIVATE_VAR_NS}_LIBRARY) - endif(${PC_ICU_PRIVATE_VAR_NS}_FOUND) - endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT) -endif(PKG_CONFIG_FOUND) -# list(APPEND ${ICU_PRIVATE_VAR_NS}_HINTS ENV ICU_ROOT_DIR) -# message("${ICU_PRIVATE_VAR_NS}_HINTS = ${${ICU_PRIVATE_VAR_NS}_HINTS}") - -# Includes -find_path( - ${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR - NAMES unicode/utypes.h utypes.h - HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS} - PATH_SUFFIXES "include" - DOC "Include directories for ICU" -) - -if(${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR) - ########## ########## - if(EXISTS "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/uvernum.h") # ICU >= 4.4 - file(READ "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/uvernum.h" ${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS) - elseif(EXISTS "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/uversion.h") # ICU [2;4.4[ - file(READ "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/uversion.h" ${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS) - elseif(EXISTS "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/utypes.h") # ICU [1.4;2[ - file(READ "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/unicode/utypes.h" ${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS) - elseif(EXISTS "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/utypes.h") # ICU 1.3 - file(READ "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}/utypes.h" ${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS) - else() - message(FATAL_ERROR "ICU version header not found") - endif() - - if(${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS MATCHES ".*# *define *ICU_VERSION *\"([0-9]+)\".*") # ICU 1.3 - # [1.3;1.4[ as #define ICU_VERSION "3" (no patch version, ie all 1.3.X versions will be detected as 1.3.0) - set(${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR "1") - set(${ICU_PUBLIC_VAR_NS}_VERSION_MINOR "${CMAKE_MATCH_1}") - set(${ICU_PUBLIC_VAR_NS}_VERSION_PATCH "0") - elseif(${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS MATCHES ".*# *define *U_ICU_VERSION_MAJOR_NUM *([0-9]+).*") - # - # Since version 4.9.1, ICU release version numbering was totaly changed, see: - # - http://site.icu-project.org/download/49 - # - http://userguide.icu-project.org/design#TOC-Version-Numbers-in-ICU - # - set(${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR "${CMAKE_MATCH_1}") - string(REGEX REPLACE ".*# *define *U_ICU_VERSION_MINOR_NUM *([0-9]+).*" "\\1" ${ICU_PUBLIC_VAR_NS}_VERSION_MINOR "${${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS}") - string(REGEX REPLACE ".*# *define *U_ICU_VERSION_PATCHLEVEL_NUM *([0-9]+).*" "\\1" ${ICU_PUBLIC_VAR_NS}_VERSION_PATCH "${${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS}") - elseif(${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS MATCHES ".*# *define *U_ICU_VERSION *\"(([0-9]+)(\\.[0-9]+)*)\".*") # ICU [1.4;1.8[ - # [1.4;1.8[ as #define U_ICU_VERSION "1.4.1.2" but it seems that some 1.4.[12](?:\.\d)? have releasing error and appears as 1.4.0 - set(${ICU_PRIVATE_VAR_NS}_FULL_VERSION "${CMAKE_MATCH_1}") # copy CMAKE_MATCH_1, no longer valid on the following if - if(${ICU_PRIVATE_VAR_NS}_FULL_VERSION MATCHES "^([0-9]+)\\.([0-9]+)$") - set(${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR "${CMAKE_MATCH_1}") - set(${ICU_PUBLIC_VAR_NS}_VERSION_MINOR "${CMAKE_MATCH_2}") - set(${ICU_PUBLIC_VAR_NS}_VERSION_PATCH "0") - elseif(${ICU_PRIVATE_VAR_NS}_FULL_VERSION MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)") - set(${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR "${CMAKE_MATCH_1}") - set(${ICU_PUBLIC_VAR_NS}_VERSION_MINOR "${CMAKE_MATCH_2}") - set(${ICU_PUBLIC_VAR_NS}_VERSION_PATCH "${CMAKE_MATCH_3}") - endif() - else() - message(FATAL_ERROR "failed to detect ICU version") - endif() - set(${ICU_PUBLIC_VAR_NS}_VERSION "${${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR}.${${ICU_PUBLIC_VAR_NS}_VERSION_MINOR}.${${ICU_PUBLIC_VAR_NS}_VERSION_PATCH}") - ########## ########## -endif(${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR) - -# Check libraries -if(MSVC) - include(SelectLibraryConfigurations) -endif(MSVC) -foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS}) - string(TOUPPER "${${ICU_PRIVATE_VAR_NS}_COMPONENT}" ${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT) - if(MSVC) - set(${ICU_PRIVATE_VAR_NS}_POSSIBLE_RELEASE_NAMES ) - set(${ICU_PRIVATE_VAR_NS}_POSSIBLE_DEBUG_NAMES ) - foreach(${ICU_PRIVATE_VAR_NS}_BASE_NAME ${${ICU_PRIVATE_VAR_NS}_COMPONENTS_${${ICU_PRIVATE_VAR_NS}_COMPONENT}}) - list(APPEND ${ICU_PRIVATE_VAR_NS}_POSSIBLE_RELEASE_NAMES "${${ICU_PRIVATE_VAR_NS}_BASE_NAME}") - list(APPEND ${ICU_PRIVATE_VAR_NS}_POSSIBLE_DEBUG_NAMES "${${ICU_PRIVATE_VAR_NS}_BASE_NAME}d") - list(APPEND ${ICU_PRIVATE_VAR_NS}_POSSIBLE_RELEASE_NAMES "${${ICU_PRIVATE_VAR_NS}_BASE_NAME}${${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR}${${ICU_PUBLIC_VAR_NS}_VERSION_MINOR}") - list(APPEND ${ICU_PRIVATE_VAR_NS}_POSSIBLE_DEBUG_NAMES "${${ICU_PRIVATE_VAR_NS}_BASE_NAME}${${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR}${${ICU_PUBLIC_VAR_NS}_VERSION_MINOR}d") - endforeach(${ICU_PRIVATE_VAR_NS}_BASE_NAME) - - find_library( - ${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_RELEASE - NAMES ${${ICU_PRIVATE_VAR_NS}_POSSIBLE_RELEASE_NAMES} - HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS} - DOC "Release library for ICU ${${ICU_PRIVATE_VAR_NS}_COMPONENT} component" - ) - find_library( - ${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_DEBUG - NAMES ${${ICU_PRIVATE_VAR_NS}_POSSIBLE_DEBUG_NAMES} - HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS} - DOC "Debug library for ICU ${${ICU_PRIVATE_VAR_NS}_COMPONENT} component" - ) - - select_library_configurations("${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}") - list(APPEND ${ICU_PUBLIC_VAR_NS}_LIBRARY ${${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY}) - else(MSVC) - find_library( - ${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY - NAMES ${${ICU_PRIVATE_VAR_NS}_COMPONENTS_${${ICU_PRIVATE_VAR_NS}_COMPONENT}} - PATHS ${${ICU_PRIVATE_VAR_NS}_HINTS} - DOC "Library for ICU ${${ICU_PRIVATE_VAR_NS}_COMPONENT} component" - ) - - if(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY) - set("${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_FOUND" TRUE) - list(APPEND ${ICU_PUBLIC_VAR_NS}_LIBRARY ${${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY}) - endif(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY) - endif(MSVC) -endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT) - -# Try to find out compiler flags -find_program(${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE icu-config HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS}) -if(${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE) - execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cflags OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_C_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cxxflags OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_CXX_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cppflags OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_CPP_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) - - execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cflags-dynamic OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_C_SHARED_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cxxflags-dynamic OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_CXX_SHARED_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cppflags-dynamic OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_CPP_SHARED_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) -endif(${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE) - -# Check find_package arguments -include(FindPackageHandleStandardArgs) -if(${ICU_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${ICU_PUBLIC_VAR_NS}_FIND_QUIETLY) - find_package_handle_standard_args( - ${ICU_PUBLIC_VAR_NS} - REQUIRED_VARS ${ICU_PUBLIC_VAR_NS}_LIBRARY ${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR - VERSION_VAR ${ICU_PUBLIC_VAR_NS}_VERSION - ) -else(${ICU_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${ICU_PUBLIC_VAR_NS}_FIND_QUIETLY) - find_package_handle_standard_args(${ICU_PUBLIC_VAR_NS} "Could NOT find ICU" ${ICU_PUBLIC_VAR_NS}_LIBRARY ${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR) -endif(${ICU_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${ICU_PUBLIC_VAR_NS}_FIND_QUIETLY) - -if(${ICU_PUBLIC_VAR_NS}_FOUND) - # - # for compatibility with previous versions, alias old ICU_(MAJOR|MINOR|PATCH)_VERSION to ICU_VERSION_$1 - set(${ICU_PUBLIC_VAR_NS}_MAJOR_VERSION ${${ICU_PUBLIC_VAR_NS}_VERSION_MAJOR}) - set(${ICU_PUBLIC_VAR_NS}_MINOR_VERSION ${${ICU_PUBLIC_VAR_NS}_VERSION_MINOR}) - set(${ICU_PUBLIC_VAR_NS}_PATCH_VERSION ${${ICU_PUBLIC_VAR_NS}_VERSION_PATCH}) - # - set(${ICU_PUBLIC_VAR_NS}_LIBRARIES ${${ICU_PUBLIC_VAR_NS}_LIBRARY}) - set(${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS ${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}) - - if(NOT CMAKE_VERSION VERSION_LESS "3.0.0") - if(NOT TARGET ICU::ICU) - add_library(ICU::ICU INTERFACE IMPORTED) - endif(NOT TARGET ICU::ICU) - set_target_properties(ICU::ICU PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}") - foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS}) - string(TOUPPER "${${ICU_PRIVATE_VAR_NS}_COMPONENT}" ${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT) - add_library("ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" UNKNOWN IMPORTED) - if(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_RELEASE) - set_property(TARGET "ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) - set_target_properties("ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" PROPERTIES IMPORTED_LOCATION_RELEASE "${${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_RELEASE}") - endif(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_RELEASE) - if(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_DEBUG) - set_property(TARGET "ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG) - set_target_properties("ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" PROPERTIES IMPORTED_LOCATION_DEBUG "${${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_DEBUG}") - endif(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY_DEBUG) - if(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY) - set_target_properties("ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" PROPERTIES IMPORTED_LOCATION "${${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY}") - endif(${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_LIBRARY) - set_property(TARGET ICU::ICU APPEND PROPERTY INTERFACE_LINK_LIBRARIES "ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}") -# set_target_properties("ICU::${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}" PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR}") - endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT) - endif(NOT CMAKE_VERSION VERSION_LESS "3.0.0") -endif(${ICU_PUBLIC_VAR_NS}_FOUND) - -mark_as_advanced( - ${ICU_PUBLIC_VAR_NS}_INCLUDE_DIR - ${ICU_PUBLIC_VAR_NS}_LIBRARY -) - -########## ########## - -########## ########## - -########## Private ########## -function(_icu_extract_locale_from_rb _BUNDLE_SOURCE _RETURN_VAR_NAME) - file(READ "${_BUNDLE_SOURCE}" _BUNDLE_CONTENTS) - string(REGEX REPLACE "//[^\n]*\n" "" _BUNDLE_CONTENTS_WITHOUT_COMMENTS ${_BUNDLE_CONTENTS}) - string(REGEX REPLACE "[ \t\n]" "" _BUNDLE_CONTENTS_WITHOUT_COMMENTS_AND_SPACES ${_BUNDLE_CONTENTS_WITHOUT_COMMENTS}) - string(REGEX MATCH "^([a-zA-Z_-]+)(:table)?{" LOCALE_FOUND ${_BUNDLE_CONTENTS_WITHOUT_COMMENTS_AND_SPACES}) - set("${_RETURN_VAR_NAME}" "${CMAKE_MATCH_1}" PARENT_SCOPE) -endfunction(_icu_extract_locale_from_rb) - -########## Public ########## - -# -# Prototype: -# icu_generate_resource_bundle([NAME ] [PACKAGE] [DESTINATION ] [FILES ]) -# -# Common arguments: -# - NAME : name of output package and to create dummy targets -# - FILES ... : list of resource bundles sources -# - DEPENDS ... : required to package as library (shared or static), a list of cmake parent targets to link to -# Note: only (PREVIOUSLY DECLARED) add_executable and add_library as dependencies -# - DESTINATION : optional, directory where to install final binary file(s) -# - FORMAT : optional, one of none (ICU4C binary format, default), java (plain java) or xliff (XML), see below -# -# Arguments depending on FORMAT: -# - none (default): -# * PACKAGE : if present, package all resource bundles together. Default is to stop after building individual *.res files -# * TYPE : one of : -# + common or archive (default) : archive all ressource bundles into a single .dat file -# + library or dll : assemble all ressource bundles into a separate and loadable library (.dll/.so) -# + static : integrate all ressource bundles to targets designed by DEPENDS parameter (as a static library) -# * NO_SHARED_FLAGS : only with TYPE in ['library', 'dll', 'static'], do not append ICU_C(XX)_SHARED_FLAGS to targets given as DEPENDS argument -# - JAVA: -# * BUNDLE : required, prefix for generated classnames -# - XLIFF: -# (none) -# - -# -# For an archive, the idea is to generate the following dependencies: -# -# root.txt => root.res \ -# | -# en.txt => en.res | -# | => pkglist.txt => application.dat -# fr.txt => fr.res | -# | -# and so on / -# -# Lengend: 'A => B' means B depends on A -# -# Steps (correspond to arrows): -# 1) genrb (from .txt to .res) -# 2) generate a file text (pkglist.txt) with all .res files to put together -# 3) build final archive (from *.res/pkglist.txt to .dat) -# - -function(icu_generate_resource_bundle) - - ##### ##### - find_program(${ICU_PUBLIC_VAR_NS}_GENRB_EXECUTABLE genrb HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS}) - find_program(${ICU_PUBLIC_VAR_NS}_PKGDATA_EXECUTABLE pkgdata HINTS ${${ICU_PRIVATE_VAR_NS}_HINTS}) - - if(NOT ${ICU_PUBLIC_VAR_NS}_GENRB_EXECUTABLE) - message(FATAL_ERROR "genrb not found") - endif(NOT ${ICU_PUBLIC_VAR_NS}_GENRB_EXECUTABLE) - if(NOT ${ICU_PUBLIC_VAR_NS}_PKGDATA_EXECUTABLE) - message(FATAL_ERROR "pkgdata not found") - endif(NOT ${ICU_PUBLIC_VAR_NS}_PKGDATA_EXECUTABLE) - ##### ##### - - ##### ##### - set(TARGET_SEPARATOR "+") - set(__FUNCTION__ "icu_generate_resource_bundle") - set(PACKAGE_TARGET_PREFIX "ICU${TARGET_SEPARATOR}PKG") - set(RESOURCE_TARGET_PREFIX "ICU${TARGET_SEPARATOR}RB") - ##### ##### - - ##### ##### - # filename extension of built resource bundle (without dot) - set(BUNDLES__SUFFIX "res") - set(BUNDLES_JAVA_SUFFIX "java") - set(BUNDLES_XLIFF_SUFFIX "xlf") - # alias: none (default) = common = archive ; dll = library ; static - set(PKGDATA__ALIAS "") - set(PKGDATA_COMMON_ALIAS "") - set(PKGDATA_ARCHIVE_ALIAS "") - set(PKGDATA_DLL_ALIAS "LIBRARY") - set(PKGDATA_LIBRARY_ALIAS "LIBRARY") - set(PKGDATA_STATIC_ALIAS "STATIC") - # filename prefix of built package - set(PKGDATA__PREFIX "") - set(PKGDATA_LIBRARY_PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}") - set(PKGDATA_STATIC_PREFIX "${CMAKE_STATIC_LIBRARY_PREFIX}") - # filename extension of built package (with dot) - set(PKGDATA__SUFFIX ".dat") - set(PKGDATA_LIBRARY_SUFFIX "${CMAKE_SHARED_LIBRARY_SUFFIX}") - set(PKGDATA_STATIC_SUFFIX "${CMAKE_STATIC_LIBRARY_SUFFIX}") - # pkgdata option mode specific - set(PKGDATA__OPTIONS "-m" "common") - set(PKGDATA_STATIC_OPTIONS "-m" "static") - set(PKGDATA_LIBRARY_OPTIONS "-m" "library") - # cmake library type for output package - set(PKGDATA_LIBRARY__TYPE "") - set(PKGDATA_LIBRARY_STATIC_TYPE STATIC) - set(PKGDATA_LIBRARY_LIBRARY_TYPE SHARED) - ##### ##### - - include(CMakeParseArguments) - cmake_parse_arguments( - PARSED_ARGS # output variable name - # options (true/false) (default value: false) - "PACKAGE;NO_SHARED_FLAGS" - # univalued parameters (default value: "") - "NAME;DESTINATION;TYPE;FORMAT;BUNDLE" - # multivalued parameters (default value: "") - "FILES;DEPENDS" - ${ARGN} - ) - - # assert(${PARSED_ARGS_NAME} != "") - if(NOT PARSED_ARGS_NAME) - message(FATAL_ERROR "${__FUNCTION__}(): no name given, NAME parameter missing") - endif(NOT PARSED_ARGS_NAME) - - # assert(length(PARSED_ARGS_FILES) > 0) - list(LENGTH PARSED_ARGS_FILES PARSED_ARGS_FILES_LEN) - if(PARSED_ARGS_FILES_LEN LESS 1) - message(FATAL_ERROR "${__FUNCTION__}() expects at least 1 resource bundle as FILES argument, 0 given") - endif(PARSED_ARGS_FILES_LEN LESS 1) - - string(TOUPPER "${PARSED_ARGS_FORMAT}" UPPER_FORMAT) - # assert(${UPPER_FORMAT} in ['', 'java', 'xlif']) - if(NOT DEFINED BUNDLES_${UPPER_FORMAT}_SUFFIX) - message(FATAL_ERROR "${__FUNCTION__}(): unknown FORMAT '${PARSED_ARGS_FORMAT}'") - endif(NOT DEFINED BUNDLES_${UPPER_FORMAT}_SUFFIX) - - if(UPPER_FORMAT STREQUAL "JAVA") - # assert(${PARSED_ARGS_BUNDLE} != "") - if(NOT PARSED_ARGS_BUNDLE) - message(FATAL_ERROR "${__FUNCTION__}(): java bundle name expected, BUNDLE parameter missing") - endif(NOT PARSED_ARGS_BUNDLE) - endif(UPPER_FORMAT STREQUAL "JAVA") - - if(PARSED_ARGS_PACKAGE) - # assert(${PARSED_ARGS_FORMAT} == "") - if(PARSED_ARGS_FORMAT) - message(FATAL_ERROR "${__FUNCTION__}(): packaging is only supported for binary format, not xlif neither java outputs") - endif(PARSED_ARGS_FORMAT) - - string(TOUPPER "${PARSED_ARGS_TYPE}" UPPER_MODE) - # assert(${UPPER_MODE} in ['', 'common', 'archive', 'dll', library']) - if(NOT DEFINED PKGDATA_${UPPER_MODE}_ALIAS) - message(FATAL_ERROR "${__FUNCTION__}(): unknown TYPE '${PARSED_ARGS_TYPE}'") - else(NOT DEFINED PKGDATA_${UPPER_MODE}_ALIAS) - set(TYPE "${PKGDATA_${UPPER_MODE}_ALIAS}") - endif(NOT DEFINED PKGDATA_${UPPER_MODE}_ALIAS) - - # Package name: strip file extension if present - get_filename_component(PACKAGE_NAME_WE ${PARSED_ARGS_NAME} NAME_WE) - # Target name to build package - set(PACKAGE_TARGET_NAME "${PACKAGE_TARGET_PREFIX}${TARGET_SEPARATOR}${PACKAGE_NAME_WE}") - # Target name to build intermediate list file - set(PACKAGE_LIST_TARGET_NAME "${PACKAGE_TARGET_NAME}${TARGET_SEPARATOR}PKGLIST") - # Directory (absolute) to set as "current directory" for genrb (does not include package directory, -p) - # We make our "cook" there to prevent any conflict - if(DEFINED CMAKE_PLATFORM_ROOT_BIN) # CMake < 2.8.10 - set(RESOURCE_GENRB_CHDIR_DIR "${CMAKE_PLATFORM_ROOT_BIN}/${PACKAGE_TARGET_NAME}.dir/") - else(DEFINED CMAKE_PLATFORM_ROOT_BIN) # CMake >= 2.8.10 - set(RESOURCE_GENRB_CHDIR_DIR "${CMAKE_PLATFORM_INFO_DIR}/${PACKAGE_TARGET_NAME}.dir/") - endif(DEFINED CMAKE_PLATFORM_ROOT_BIN) - # Directory (absolute) where resource bundles are built: concatenation of RESOURCE_GENRB_CHDIR_DIR and package name - set(RESOURCE_OUTPUT_DIR "${RESOURCE_GENRB_CHDIR_DIR}/${PACKAGE_NAME_WE}/") - # Output (relative) path for built package - if(MSVC AND TYPE STREQUAL PKGDATA_LIBRARY_ALIAS) - set(PACKAGE_OUTPUT_PATH "${RESOURCE_GENRB_CHDIR_DIR}/${PACKAGE_NAME_WE}/${PKGDATA_${TYPE}_PREFIX}${PACKAGE_NAME_WE}${PKGDATA_${TYPE}_SUFFIX}") - else(MSVC AND TYPE STREQUAL PKGDATA_LIBRARY_ALIAS) - set(PACKAGE_OUTPUT_PATH "${RESOURCE_GENRB_CHDIR_DIR}/${PKGDATA_${TYPE}_PREFIX}${PACKAGE_NAME_WE}${PKGDATA_${TYPE}_SUFFIX}") - endif(MSVC AND TYPE STREQUAL PKGDATA_LIBRARY_ALIAS) - # Output (absolute) path for the list file - set(PACKAGE_LIST_OUTPUT_PATH "${RESOURCE_GENRB_CHDIR_DIR}/pkglist.txt") - - file(MAKE_DIRECTORY "${RESOURCE_OUTPUT_DIR}") - else(PARSED_ARGS_PACKAGE) - set(RESOURCE_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/") -# set(RESOURCE_GENRB_CHDIR_DIR "UNUSED") - endif(PARSED_ARGS_PACKAGE) - - set(TARGET_RESOURCES ) - set(COMPILED_RESOURCES_PATH ) - set(COMPILED_RESOURCES_BASENAME ) - foreach(RESOURCE_SOURCE ${PARSED_ARGS_FILES}) - _icu_extract_locale_from_rb(${RESOURCE_SOURCE} RESOURCE_NAME_WE) - get_filename_component(SOURCE_BASENAME ${RESOURCE_SOURCE} NAME) - get_filename_component(ABSOLUTE_SOURCE ${RESOURCE_SOURCE} ABSOLUTE) - - if(UPPER_FORMAT STREQUAL "XLIFF") - if(RESOURCE_NAME_WE STREQUAL "root") - set(XLIFF_LANGUAGE "en") - else(RESOURCE_NAME_WE STREQUAL "root") - string(REGEX REPLACE "[^a-z].*$" "" XLIFF_LANGUAGE "${RESOURCE_NAME_WE}") - endif(RESOURCE_NAME_WE STREQUAL "root") - endif(UPPER_FORMAT STREQUAL "XLIFF") - - ##### ##### - set(RESOURCE_TARGET_NAME "${RESOURCE_TARGET_PREFIX}${TARGET_SEPARATOR}${PARSED_ARGS_NAME}${TARGET_SEPARATOR}${RESOURCE_NAME_WE}") - - set(RESOURCE_OUTPUT__PATH "${RESOURCE_NAME_WE}.res") - if(RESOURCE_NAME_WE STREQUAL "root") - set(RESOURCE_OUTPUT_JAVA_PATH "${PARSED_ARGS_BUNDLE}.java") - else(RESOURCE_NAME_WE STREQUAL "root") - set(RESOURCE_OUTPUT_JAVA_PATH "${PARSED_ARGS_BUNDLE}_${RESOURCE_NAME_WE}.java") - endif(RESOURCE_NAME_WE STREQUAL "root") - set(RESOURCE_OUTPUT_XLIFF_PATH "${RESOURCE_NAME_WE}.xlf") - - set(GENRB__OPTIONS "") - set(GENRB_JAVA_OPTIONS "-j" "-b" "${PARSED_ARGS_BUNDLE}") - set(GENRB_XLIFF_OPTIONS "-x" "-l" "${XLIFF_LANGUAGE}") - ##### ##### - - # build .txt from .res - if(PARSED_ARGS_PACKAGE) - add_custom_command( - OUTPUT "${RESOURCE_OUTPUT_DIR}${RESOURCE_OUTPUT_${UPPER_FORMAT}_PATH}" - COMMAND ${CMAKE_COMMAND} -E chdir ${RESOURCE_GENRB_CHDIR_DIR} ${${ICU_PUBLIC_VAR_NS}_GENRB_EXECUTABLE} ${GENRB_${UPPER_FORMAT}_OPTIONS} -d ${PACKAGE_NAME_WE} ${ABSOLUTE_SOURCE} - DEPENDS ${RESOURCE_SOURCE} - ) - else(PARSED_ARGS_PACKAGE) - add_custom_command( - OUTPUT "${RESOURCE_OUTPUT_DIR}${RESOURCE_OUTPUT_${UPPER_FORMAT}_PATH}" - COMMAND ${${ICU_PUBLIC_VAR_NS}_GENRB_EXECUTABLE} ${GENRB_${UPPER_FORMAT}_OPTIONS} -d ${RESOURCE_OUTPUT_DIR} ${ABSOLUTE_SOURCE} - DEPENDS ${RESOURCE_SOURCE} - ) - endif(PARSED_ARGS_PACKAGE) - # dummy target (ICU+RB++) for each locale to build the .res file from its .txt by the add_custom_command above - add_custom_target( - "${RESOURCE_TARGET_NAME}" ALL - COMMENT "" - DEPENDS "${RESOURCE_OUTPUT_DIR}${RESOURCE_OUTPUT_${UPPER_FORMAT}_PATH}" - SOURCES ${RESOURCE_SOURCE} - ) - - if(PARSED_ARGS_DESTINATION AND NOT PARSED_ARGS_PACKAGE) - install(FILES "${RESOURCE_OUTPUT_DIR}${RESOURCE_OUTPUT_${UPPER_FORMAT}_PATH}" DESTINATION ${PARSED_ARGS_DESTINATION} PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) - endif(PARSED_ARGS_DESTINATION AND NOT PARSED_ARGS_PACKAGE) - - list(APPEND TARGET_RESOURCES "${RESOURCE_TARGET_NAME}") - list(APPEND COMPILED_RESOURCES_PATH "${RESOURCE_OUTPUT_DIR}${RESOURCE_OUTPUT_${UPPER_FORMAT}_PATH}") - list(APPEND COMPILED_RESOURCES_BASENAME "${RESOURCE_NAME_WE}.${BUNDLES_${UPPER_FORMAT}_SUFFIX}") - endforeach(RESOURCE_SOURCE) - # convert semicolon separated list to a space separated list - # NOTE: if the pkglist.txt file starts (or ends?) with a whitespace, pkgdata add an undefined symbol (named _) for it - string(REPLACE ";" " " COMPILED_RESOURCES_BASENAME "${COMPILED_RESOURCES_BASENAME}") - - if(PARSED_ARGS_PACKAGE) - # create a text file (pkglist.txt) with the list of the *.res to package together - add_custom_command( - OUTPUT "${PACKAGE_LIST_OUTPUT_PATH}" - COMMAND ${CMAKE_COMMAND} -E echo "${COMPILED_RESOURCES_BASENAME}" > "${PACKAGE_LIST_OUTPUT_PATH}" - DEPENDS ${COMPILED_RESOURCES_PATH} - ) - # run pkgdata from pkglist.txt - add_custom_command( - OUTPUT "${PACKAGE_OUTPUT_PATH}" - COMMAND ${CMAKE_COMMAND} -E chdir ${RESOURCE_GENRB_CHDIR_DIR} ${${ICU_PUBLIC_VAR_NS}_PKGDATA_EXECUTABLE} -F ${PKGDATA_${TYPE}_OPTIONS} -s ${PACKAGE_NAME_WE} -p ${PACKAGE_NAME_WE} ${PACKAGE_LIST_OUTPUT_PATH} - DEPENDS "${PACKAGE_LIST_OUTPUT_PATH}" - VERBATIM - ) - if(PKGDATA_LIBRARY_${TYPE}_TYPE) - # assert(${PARSED_ARGS_DEPENDS} != "") - if(NOT PARSED_ARGS_DEPENDS) - message(FATAL_ERROR "${__FUNCTION__}(): static and library mode imply a list of targets to link to, DEPENDS parameter missing") - endif(NOT PARSED_ARGS_DEPENDS) - add_library(${PACKAGE_TARGET_NAME} ${PKGDATA_LIBRARY_${TYPE}_TYPE} IMPORTED) - if(MSVC) - string(REGEX REPLACE "${PKGDATA_LIBRARY_SUFFIX}\$" "${CMAKE_IMPORT_LIBRARY_SUFFIX}" PACKAGE_OUTPUT_LIB "${PACKAGE_OUTPUT_PATH}") - set_target_properties(${PACKAGE_TARGET_NAME} PROPERTIES IMPORTED_LOCATION ${PACKAGE_OUTPUT_PATH} IMPORTED_IMPLIB ${PACKAGE_OUTPUT_LIB}) - else(MSVC) - set_target_properties(${PACKAGE_TARGET_NAME} PROPERTIES IMPORTED_LOCATION ${PACKAGE_OUTPUT_PATH}) - endif(MSVC) - foreach(DEPENDENCY ${PARSED_ARGS_DEPENDS}) - target_link_libraries(${DEPENDENCY} ${PACKAGE_TARGET_NAME}) - if(NOT PARSED_ARGS_NO_SHARED_FLAGS) - get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) - list(LENGTH "${ENABLED_LANGUAGES}" ENABLED_LANGUAGES_LENGTH) - if(ENABLED_LANGUAGES_LENGTH GREATER 1) - message(WARNING "Project has more than one language enabled, skip automatic shared flags appending") - else(ENABLED_LANGUAGES_LENGTH GREATER 1) - set_property(TARGET "${DEPENDENCY}" APPEND PROPERTY COMPILE_FLAGS "${${ICU_PUBLIC_VAR_NS}_${ENABLED_LANGUAGES}_SHARED_FLAGS}") - endif(ENABLED_LANGUAGES_LENGTH GREATER 1) - endif(NOT PARSED_ARGS_NO_SHARED_FLAGS) - endforeach(DEPENDENCY) - # http://www.mail-archive.com/cmake-commits@cmake.org/msg01135.html - set(PACKAGE_INTERMEDIATE_TARGET_NAME "${PACKAGE_TARGET_NAME}${TARGET_SEPARATOR}DUMMY") - # dummy intermediate target (ICU+PKG++DUMMY) to link the package to the produced library by running pkgdata (see add_custom_command above) - add_custom_target( - ${PACKAGE_INTERMEDIATE_TARGET_NAME} - COMMENT "" - DEPENDS "${PACKAGE_OUTPUT_PATH}" - ) - add_dependencies("${PACKAGE_TARGET_NAME}" "${PACKAGE_INTERMEDIATE_TARGET_NAME}") - else(PKGDATA_LIBRARY_${TYPE}_TYPE) - # dummy target (ICU+PKG+) to run pkgdata (see add_custom_command above) - add_custom_target( - "${PACKAGE_TARGET_NAME}" ALL - COMMENT "" - DEPENDS "${PACKAGE_OUTPUT_PATH}" - ) - endif(PKGDATA_LIBRARY_${TYPE}_TYPE) - # dummy target (ICU+PKG++PKGLIST) to build the file pkglist.txt - add_custom_target( - "${PACKAGE_LIST_TARGET_NAME}" ALL - COMMENT "" - DEPENDS "${PACKAGE_LIST_OUTPUT_PATH}" - ) - # package => pkglist.txt - add_dependencies("${PACKAGE_TARGET_NAME}" "${PACKAGE_LIST_TARGET_NAME}") - # pkglist.txt => *.res - add_dependencies("${PACKAGE_LIST_TARGET_NAME}" ${TARGET_RESOURCES}) - - if(PARSED_ARGS_DESTINATION) - install(FILES "${PACKAGE_OUTPUT_PATH}" DESTINATION ${PARSED_ARGS_DESTINATION} PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) - endif(PARSED_ARGS_DESTINATION) - endif(PARSED_ARGS_PACKAGE) - -endfunction(icu_generate_resource_bundle) - -########## ########## - -########## ########## - -if(${ICU_PUBLIC_VAR_NS}_DEBUG) - - function(icudebug _VARNAME) - if(DEFINED ${ICU_PUBLIC_VAR_NS}_${_VARNAME}) - message("${ICU_PUBLIC_VAR_NS}_${_VARNAME} = ${${ICU_PUBLIC_VAR_NS}_${_VARNAME}}") - else(DEFINED ${ICU_PUBLIC_VAR_NS}_${_VARNAME}) - message("${ICU_PUBLIC_VAR_NS}_${_VARNAME} = ") - endif(DEFINED ${ICU_PUBLIC_VAR_NS}_${_VARNAME}) - endfunction(icudebug) - - # IN (args) - icudebug("FIND_COMPONENTS") - icudebug("FIND_REQUIRED") - icudebug("FIND_QUIETLY") - icudebug("FIND_VERSION") - - # OUT - # Found - icudebug("FOUND") - # Flags - icudebug("C_FLAGS") - icudebug("CPP_FLAGS") - icudebug("CXX_FLAGS") - icudebug("C_SHARED_FLAGS") - icudebug("CPP_SHARED_FLAGS") - icudebug("CXX_SHARED_FLAGS") - # Linking - icudebug("INCLUDE_DIRS") - icudebug("LIBRARIES") - # Version - icudebug("VERSION_MAJOR") - icudebug("VERSION_MINOR") - icudebug("VERSION_PATCH") - icudebug("VERSION") - # _(FOUND|LIBRARY) - set(${ICU_PRIVATE_VAR_NS}_COMPONENT_VARIABLES "FOUND" "LIBRARY" "LIBRARY_RELEASE" "LIBRARY_DEBUG") - foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PRIVATE_VAR_NS}_COMPONENTS}) - string(TOUPPER "${${ICU_PRIVATE_VAR_NS}_COMPONENT}" ${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT) - foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT_VARIABLE ${${ICU_PRIVATE_VAR_NS}_COMPONENT_VARIABLES}) - icudebug("${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_${${ICU_PRIVATE_VAR_NS}_COMPONENT_VARIABLE}") - endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT_VARIABLE) - endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT) - -endif(${ICU_PUBLIC_VAR_NS}_DEBUG) - -########## ########## diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 7987147f5..495c988d2 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -667,12 +667,6 @@ ADD_ASSIMP_IMPORTER( MMD MMDVmdParser.h ) -# use custom FindICU.cmake -set(CMAKE_MODULE_PATH_BACKUP ${CMAKE_MODULE_PATH}) -set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}) -find_package(ICU COMPONENTS uc io REQUIRED) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH_BACKUP}) - SET( Step_SRCS StepExporter.h StepExporter.cpp diff --git a/code/MMDPmxParser.cpp b/code/MMDPmxParser.cpp index b656a4ef4..15696711a 100644 --- a/code/MMDPmxParser.cpp +++ b/code/MMDPmxParser.cpp @@ -1,10 +1,7 @@ #include #include "MMDPmxParser.h" -#ifndef __unix__ -#include "EncodingHelper.h" -#else -#include -#endif +#include "Exceptional.h" +#include "../contrib/ConvertUTF/ConvertUTF.h" namespace pmx { @@ -43,58 +40,44 @@ namespace pmx } /// 文字列を読み込む - utfstring ReadString(std::istream *stream, uint8_t encoding) + std::string ReadString(std::istream *stream, uint8_t encoding) { -#ifndef __unix__ - oguna::EncodingConverter converter = oguna::EncodingConverter(); -#endif int size; stream->read((char*) &size, sizeof(int)); std::vector buffer; if (size == 0) { -#ifndef __unix__ - return utfstring(L""); -#else - return utfstring(""); -#endif + return std::string(""); } buffer.reserve(size); stream->read((char*) buffer.data(), size); if (encoding == 0) { - // UTF16 -#ifndef __unix__ - return utfstring((wchar_t*) buffer.data(), size / 2); -#else - utfstring result; - std::vector outbuf; - outbuf.reserve(size*2); + // UTF16 to UTF8 + std::string result; - // Always remember to set U_ZERO_ERROR before calling ucnv_convert(), - // otherwise the function will fail. - UErrorCode err = U_ZERO_ERROR; - size = ucnv_convert("UTF-8", "UTF-16LE", (char*)outbuf.data(), outbuf.capacity(), buffer.data(), size, &err); - if(!U_SUCCESS(err)) { - std::cout << "oops, something wrong?" << std::endl; - std::cout << u_errorName(err) << std::endl; - exit(-1); + const char* sourceStart = buffer.data(); + const unsigned int targetSize = size * 3; + char* targetStart = new char[targetSize](); + const char* targetReserved = targetStart; + ConversionFlags flags = ConversionFlags::lenientConversion; + + ConversionResult conversionResult; + if( ( conversionResult = ConvertUTF16toUTF8( + (const UTF16**)&sourceStart, (const UTF16*)(sourceStart + size), + (UTF8**)&targetStart, (UTF8*)(targetStart + targetSize), + flags) ) != ConversionResult::conversionOK) { + throw DeadlyImportError( "Convert " + std::string(sourceStart) + " to UTF8 is not valid." ); } - - result.assign((const char*)outbuf.data(), size); + + result.assign(targetReserved, targetSize); + delete[] targetReserved; return result; -#endif } else { - // UTF8 -#ifndef __unix__ - utfstring result; - converter.Utf8ToUtf16(buffer.data(), size, &result); - return result; -#else - return utfstring((const char*)buffer.data(), size); -#endif + // the name is already UTF8 + return std::string((const char*)buffer.data(), size); } } @@ -538,7 +521,7 @@ namespace pmx // テクスチャ stream->read((char*) &texture_count, sizeof(int)); - this->textures = std::make_unique(texture_count); + this->textures = std::make_unique(texture_count); for (int i = 0; i < texture_count; i++) { this->textures[i] = ReadString(stream, setting.encoding); diff --git a/code/MMDPmxParser.h b/code/MMDPmxParser.h index 5b64370b6..d161eb6b2 100644 --- a/code/MMDPmxParser.h +++ b/code/MMDPmxParser.h @@ -9,11 +9,6 @@ namespace pmx { -#ifndef __unix__ -#define utfstring std::wstring -#else -#define utfstring std::string -#endif /// インデックス設定 class PmxSetting { @@ -227,9 +222,9 @@ namespace pmx } /// モデル名 - utfstring material_name; + std::string material_name; /// モデル英名 - utfstring material_english_name; + std::string material_english_name; /// 減衰色 float diffuse[4]; /// 光沢色 @@ -255,7 +250,7 @@ namespace pmx /// トゥーンテクスチャインデックス int toon_texture_index; /// メモ - utfstring memo; + std::string memo; /// 頂点インデックス数 int index_count; void Read(std::istream *stream, PmxSetting *setting); @@ -313,9 +308,9 @@ namespace pmx } /// ボーン名 - utfstring bone_name; + std::string bone_name; /// ボーン英名 - utfstring bone_english_name; + std::string bone_english_name; /// 位置 float position[3]; /// 親ボーンインデックス @@ -517,9 +512,9 @@ namespace pmx { } /// モーフ名 - utfstring morph_name; + std::string morph_name; /// モーフ英名 - utfstring morph_english_name; + std::string morph_english_name; /// カテゴリ MorphCategory category; /// モーフタイプ @@ -569,9 +564,9 @@ namespace pmx { } /// 枠名 - utfstring frame_name; + std::string frame_name; /// 枠英名 - utfstring frame_english_name; + std::string frame_english_name; /// 特殊枠フラグ uint8_t frame_flag; /// 枠内要素数 @@ -603,9 +598,9 @@ namespace pmx } } /// 剛体名 - utfstring girid_body_name; + std::string girid_body_name; /// 剛体英名 - utfstring girid_body_english_name; + std::string girid_body_english_name; /// 関連ボーンインデックス int target_bone; /// グループ @@ -670,8 +665,8 @@ namespace pmx class PmxJoint { public: - utfstring joint_name; - utfstring joint_english_name; + std::string joint_name; + std::string joint_english_name; PmxJointType joint_type; PmxJointParam param; void Read(std::istream *stream, PmxSetting *setting); @@ -739,8 +734,8 @@ namespace pmx , anchor_count(0) , pin_vertex_count(0) {} - utfstring soft_body_name; - utfstring soft_body_english_name; + std::string soft_body_name; + std::string soft_body_english_name; uint8_t shape; int target_material; uint8_t group; @@ -806,13 +801,13 @@ namespace pmx /// 設定 PmxSetting setting; /// モデル名 - utfstring model_name; + std::string model_name; /// モデル英名 - utfstring model_english_name; + std::string model_english_name; /// コメント - utfstring model_comment; + std::string model_comment; /// 英語コメント - utfstring model_english_comment; + std::string model_english_comment; /// 頂点数 int vertex_count; /// 頂点配列 @@ -824,7 +819,7 @@ namespace pmx /// テクスチャ数 int texture_count; /// テクスチャ配列 - std::unique_ptr< utfstring []> textures; + std::unique_ptr< std::string []> textures; /// マテリアル数 int material_count; /// マテリアル From 83db3fc0848115a81cac3660a9dfb322268955c1 Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Sun, 5 Mar 2017 23:14:21 +0800 Subject: [PATCH 07/23] fixup! remove dependency of ICU library --- code/CMakeLists.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 56534683f..010f62d4c 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -866,11 +866,9 @@ IF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) INCLUDE_DIRECTORIES(${C4D_INCLUDES}) ENDIF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) -INCLUDE_DIRECTORIES(${ICU_INCLUDE_DIRS}) - ADD_LIBRARY( assimp ${assimp_src} ) -TARGET_LINK_LIBRARIES(assimp ${ZLIB_LIBRARIES} ${OPENDDL_PARSER_LIBRARIES} ${ICU_LIBRARIES} ) +TARGET_LINK_LIBRARIES(assimp ${ZLIB_LIBRARIES} ${OPENDDL_PARSER_LIBRARIES} ) if(ANDROID AND ASSIMP_ANDROID_JNIIOSYSTEM) set(ASSIMP_ANDROID_JNIIOSYSTEM_PATH port/AndroidJNI) From c0c480a4cb881652773e12279261675c03dcf899 Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Sun, 5 Mar 2017 23:36:36 +0800 Subject: [PATCH 08/23] fixup! merge master and mv FindDevIL to cmake-modules --- code/MMDPmxParser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/MMDPmxParser.cpp b/code/MMDPmxParser.cpp index 15696711a..f115f80f8 100644 --- a/code/MMDPmxParser.cpp +++ b/code/MMDPmxParser.cpp @@ -57,7 +57,7 @@ namespace pmx std::string result; const char* sourceStart = buffer.data(); - const unsigned int targetSize = size * 3; + const unsigned int targetSize = size * 3; // enough to encode char* targetStart = new char[targetSize](); const char* targetReserved = targetStart; ConversionFlags flags = ConversionFlags::lenientConversion; @@ -70,7 +70,7 @@ namespace pmx throw DeadlyImportError( "Convert " + std::string(sourceStart) + " to UTF8 is not valid." ); } - result.assign(targetReserved, targetSize); + result.assign(targetReserved, targetStart - targetReserved); delete[] targetReserved; return result; } From ba357e74d274e7847e31a41e5aba2a976d81d9fd Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Wed, 8 Mar 2017 21:13:54 +0800 Subject: [PATCH 09/23] fix the exporter of the qt viewer --- tools/assimp_qt_viewer/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/assimp_qt_viewer/mainwindow.cpp b/tools/assimp_qt_viewer/mainwindow.cpp index cb4c4a67e..9b6e231bd 100644 --- a/tools/assimp_qt_viewer/mainwindow.cpp +++ b/tools/assimp_qt_viewer/mainwindow.cpp @@ -295,7 +295,7 @@ aiReturn rv; // begin export time_begin = QTime::currentTime(); - rv = exporter.Export(mScene, format_id.toLocal8Bit(), filename.toLocal8Bit()); + rv = exporter.Export(mScene, format_id.toLocal8Bit(), filename.toLocal8Bit(), aiProcess_FlipUVs); ui->lblExportTime->setText(QString("%1").arg(time_begin.secsTo(QTime::currentTime()))); if(rv == aiReturn_SUCCESS) LogInfo("Export done: " + filename); From 2d3dd1d40f59c8c95cdeff4d4044f6f6a1286e9a Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Fri, 10 Mar 2017 17:15:01 +0800 Subject: [PATCH 10/23] use SkeletonMeshBuilder to show bone positions. --- code/MMDImporter.cpp | 430 ++++++++++++++++++++++++------------------- 1 file changed, 243 insertions(+), 187 deletions(-) diff --git a/code/MMDImporter.cpp b/code/MMDImporter.cpp index e55dc8d2f..def089353 100644 --- a/code/MMDImporter.cpp +++ b/code/MMDImporter.cpp @@ -41,33 +41,32 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_MMD_IMPORTER -#include "DefaultIOSystem.h" #include "MMDImporter.h" -#include "MMDPmxParser.h" +#include "DefaultIOSystem.h" #include "MMDPmdParser.h" +#include "MMDPmxParser.h" #include "MMDVmdParser.h" +#include "SkeletonMeshBuilder.h" //#include "IOStreamBuffer.h" #include "ConvertToLHProcess.h" -#include -#include -#include -#include #include +#include +#include +#include #include #include +#include -static const aiImporterDesc desc = { - "MMD Importer", - "", - "", - "surfaces supported?", - aiImporterFlags_SupportTextFlavour, - 0, - 0, - 0, - 0, - "pmx" -}; +static const aiImporterDesc desc = {"MMD Importer", + "", + "", + "surfaces supported?", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "pmx"}; namespace Assimp { @@ -75,237 +74,294 @@ using namespace std; // ------------------------------------------------------------------------------------------------ // Default constructor -MMDImporter::MMDImporter() : - m_Buffer(), - //m_pRootObject( NULL ), - m_strAbsPath( "" ) -{ - DefaultIOSystem io; - m_strAbsPath = io.getOsSeparator(); +MMDImporter::MMDImporter() + : m_Buffer(), + // m_pRootObject( NULL ), + m_strAbsPath("") { + DefaultIOSystem io; + m_strAbsPath = io.getOsSeparator(); } // ------------------------------------------------------------------------------------------------ // Destructor. -MMDImporter::~MMDImporter() -{ - //delete m_pRootObject; - //m_pRootObject = NULL; +MMDImporter::~MMDImporter() { + // delete m_pRootObject; + // m_pRootObject = NULL; } // ------------------------------------------------------------------------------------------------ // Returns true, if file is an pmx file. -bool MMDImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler , bool checkSig ) const -{ - if(!checkSig) //Check File Extension - { - return SimpleExtensionCheck(pFile,"pmx"); - } - else //Check file Header - { - static const char *pTokens[] = { "PMX " }; - return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, pTokens, 1 ); - } +bool MMDImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const { + if (!checkSig) // Check File Extension + { + return SimpleExtensionCheck(pFile, "pmx"); + } else // Check file Header + { + static const char *pTokens[] = {"PMX "}; + return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, pTokens, + 1); + } } // ------------------------------------------------------------------------------------------------ -const aiImporterDesc* MMDImporter::GetInfo () const -{ - return &desc; -} +const aiImporterDesc *MMDImporter::GetInfo() const { return &desc; } // ------------------------------------------------------------------------------------------------ // MMD import implementation -void MMDImporter::InternReadFile( const std::string &file, aiScene* pScene, IOSystem* pIOHandler) -{ - // Read file by istream - std::filebuf fb; - if( !fb.open(file, std::ios::in | std::ios::binary ) ) { - throw DeadlyImportError( "Failed to open file " + file + "." ); - } +void MMDImporter::InternReadFile(const std::string &file, aiScene *pScene, + IOSystem *pIOHandler) { + // Read file by istream + std::filebuf fb; + if (!fb.open(file, std::ios::in | std::ios::binary)) { + throw DeadlyImportError("Failed to open file " + file + "."); + } - std::istream fileStream( &fb ); + std::istream fileStream(&fb); - // Get the file-size and validate it, throwing an exception when fails - fileStream.seekg(0, fileStream.end); - size_t fileSize = fileStream.tellg(); - fileStream.seekg(0, fileStream.beg); + // Get the file-size and validate it, throwing an exception when fails + fileStream.seekg(0, fileStream.end); + size_t fileSize = fileStream.tellg(); + fileStream.seekg(0, fileStream.beg); - if( fileSize < sizeof(pmx::PmxModel) ) { - throw DeadlyImportError( file + " is too small." ); - } + if (fileSize < sizeof(pmx::PmxModel)) { + throw DeadlyImportError(file + " is too small."); + } - pmx::PmxModel model; - model.Read(&fileStream); + pmx::PmxModel model; + model.Read(&fileStream); - CreateDataFromImport(&model, pScene); + CreateDataFromImport(&model, pScene); } // ------------------------------------------------------------------------------------------------ -void MMDImporter::CreateDataFromImport(const pmx::PmxModel* pModel, aiScene* pScene) -{ - if( pModel == NULL ) { - return; - } +void MMDImporter::CreateDataFromImport(const pmx::PmxModel *pModel, + aiScene *pScene) { + if (pModel == NULL) { + return; + } - aiNode *pNode = new aiNode; - if ( !pModel->model_name.empty() ) { - pNode->mName.Set(pModel->model_name); - } - else { - ai_assert(false); - } - - pScene->mRootNode = pNode; - std::cout << pScene->mRootNode->mName.C_Str() << std::endl; - std::cout << pModel->index_count << std::endl; + aiNode *pNode = new aiNode; + if (!pModel->model_name.empty()) { + pNode->mName.Set(pModel->model_name); + } else { + ai_assert(false); + } + pScene->mRootNode = pNode; + std::cout << pScene->mRootNode->mName.C_Str() << std::endl; + std::cout << pModel->index_count << std::endl; + + /* pNode = new aiNode; pScene->mRootNode->addChildren(1, &pNode); - pScene->mRootNode->mNumChildren = 1; - pNode->mParent = pScene->mRootNode; pNode->mName.Set(string(pModel->model_name) + string("_mesh")); // split mesh by materials pNode->mNumMeshes = pModel->material_count; pNode->mMeshes = new unsigned int[pNode->mNumMeshes]; - for( unsigned int index = 0; index < pNode->mNumMeshes; index++ ) { - pNode->mMeshes[index] = index; + for (unsigned int index = 0; index < pNode->mNumMeshes; index++) { + pNode->mMeshes[index] = index; } pScene->mNumMeshes = pNode->mNumMeshes; - pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; - for( unsigned int i = 0, indexStart = 0; i < pScene->mNumMeshes; i++ ) { - const int indexCount = pModel->materials[i].index_count; + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + for (unsigned int i = 0, indexStart = 0; i < pScene->mNumMeshes; i++) { + const int indexCount = pModel->materials[i].index_count; - std::cout << pModel->materials[i].material_name << std::endl; - std::cout << indexStart << " " << indexCount << std::endl; + std::cout << pModel->materials[i].material_name << std::endl; + std::cout << indexStart << " " << indexCount << std::endl; - pScene->mMeshes[i] = CreateMesh(pModel, indexStart, indexCount); - pScene->mMeshes[i]->mName = pModel->materials[i].material_name; - pScene->mMeshes[i]->mMaterialIndex = i; - indexStart += indexCount; - } - - // create textures, may be dummy? - /* - pScene->mNumTextures = pModel->texture_count; - pScene->mTextures = new aiTexture*[pScene->mNumTextures]; - for( unsigned int i = 0; i < pScene->mNumTextures; ++i) { - aiTexture *tex = new aiTexture; - pScene->mTextures[i] = tex; - strcpy(tex->achFormatHint, "png"); - tex->mHeight = 0; - ifstream file(pModel->textures[i], ios::binary | ios::ate); - streamsize size = file.tellg(); - file.seekg(0, ios::beg); - char *buffer = new char[size]; - file.read(buffer, size); - if(file.bad()) { - string err("PMX: Can't open texture file"); - err.append(pModel->textures[i]); - throw DeadlyExportError(err); - } - tex->pcData = (aiTexel*)buffer; + pScene->mMeshes[i] = CreateMesh(pModel, indexStart, indexCount); + pScene->mMeshes[i]->mName = pModel->materials[i].material_name; + pScene->mMeshes[i]->mMaterialIndex = i; + indexStart += indexCount; } */ + // create bones + aiBone *pBone = new aiBone[pModel->bone_count]; + for (auto i = 0; i < pModel->bone_count; i++) { + pBone[i].mName = pModel->bones[i].bone_name; + } + + // create node hierarchy for bone position + auto bone_root_index = 0; // Default root: Bone 0 "Body" / "全ての親" + aiNode **ppNode = new aiNode *[pModel->bone_count]; + for (auto i = 0; i < pModel->bone_count; i++) { + ppNode[i] = new aiNode(pModel->bones[i].bone_name); + } + + for (auto i = 0; i < pModel->bone_count; i++) { + const pmx::PmxBone &bone = pModel->bones[i]; + std::cout << "Bone " << i << ":" << std::endl; + std::cout << bone.bone_name << std::endl; + std::cout << bone.bone_english_name << std::endl; + std::cout << "position " << bone.position[0] << " " << bone.position[1] + << " " << bone.position[2] << std::endl; + std::cout << "parent_index " << bone.parent_index << std::endl; + std::cout << "level " << bone.level << std::endl; + std::cout << "bone_flag " << bone.bone_flag << std::endl; + std::cout << "offset " << bone.offset[0] << " " << bone.offset[1] << " " + << bone.offset[2] << std::endl; + std::cout << "target_index" << bone.target_index << std::endl; + + if (bone.parent_index < 0) { + pScene->mRootNode->addChildren(1, ppNode + i); + bone_root_index = i; + } else { + ppNode[bone.parent_index]->addChildren(1, ppNode + i); + + aiVector3D v3 = aiVector3D( + bone.position[0] - pModel->bones[bone.parent_index].position[0], + bone.position[1] - pModel->bones[bone.parent_index].position[1], + bone.position[2] - pModel->bones[bone.parent_index].position[2]); + aiMatrix4x4::Translation(v3, ppNode[i]->mTransformation); + std::cout << ppNode[i]->mTransformation.a1 << ' ' + << ppNode[i]->mTransformation.a2 << ' ' + << ppNode[i]->mTransformation.a3 << ' ' + << ppNode[i]->mTransformation.a4 << ' ' + << ppNode[i]->mTransformation.b1 << ' ' + << ppNode[i]->mTransformation.b2 << ' ' + << ppNode[i]->mTransformation.b3 << ' ' + << ppNode[i]->mTransformation.b4 << ' ' + << ppNode[i]->mTransformation.c1 << ' ' + << ppNode[i]->mTransformation.c2 << ' ' + << ppNode[i]->mTransformation.c3 << ' ' + << ppNode[i]->mTransformation.c4 << ' ' + << ppNode[i]->mTransformation.d1 << ' ' + << ppNode[i]->mTransformation.d2 << ' ' + << ppNode[i]->mTransformation.d3 << ' ' + << ppNode[i]->mTransformation.d4 << ' ' << std::endl; + } + } + + // use SkeletonMeshBuilder to generate bone vertices + SkeletonMeshBuilder dummyMeshbuild(pScene, ppNode[bone_root_index]); + + // create textures, may be dummy? + /* + pScene->mNumTextures = pModel->texture_count; + pScene->mTextures = new aiTexture*[pScene->mNumTextures]; + for( unsigned int i = 0; i < pScene->mNumTextures; ++i) { + aiTexture *tex = new aiTexture; + pScene->mTextures[i] = tex; + strcpy(tex->achFormatHint, "png"); + tex->mHeight = 0; + ifstream file(pModel->textures[i], ios::binary | ios::ate); + streamsize size = file.tellg(); + file.seekg(0, ios::beg); + char *buffer = new char[size]; + file.read(buffer, size); + if(file.bad()) { + string err("PMX: Can't open texture file"); + err.append(pModel->textures[i]); + throw DeadlyExportError(err); + } + tex->pcData = (aiTexel*)buffer; + } + */ + + /* // create materials pScene->mNumMaterials = pModel->material_count; - pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials]; - for( unsigned int i = 0; i < pScene->mNumMaterials; i++ ) { - pScene->mMaterials[i] = CreateMaterial(&pModel->materials[i], pModel); + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; + for (unsigned int i = 0; i < pScene->mNumMaterials; i++) { + pScene->mMaterials[i] = CreateMaterial(&pModel->materials[i], pModel); } - // Convert everything to OpenGL space - MakeLeftHandedProcess convertProcess; - convertProcess.Execute(pScene); + // Convert everything to OpenGL space + MakeLeftHandedProcess convertProcess; + convertProcess.Execute(pScene); - FlipUVsProcess uvFlipper; - uvFlipper.Execute(pScene); - - FlipWindingOrderProcess windingFlipper; - windingFlipper.Execute(pScene); + FlipUVsProcess uvFlipper; + uvFlipper.Execute(pScene); + FlipWindingOrderProcess windingFlipper; + windingFlipper.Execute(pScene); + */ } // ------------------------------------------------------------------------------------------------ -aiMesh* MMDImporter::CreateMesh(const pmx::PmxModel* pModel, const int indexStart, const int indexCount) -{ - aiMesh *pMesh = new aiMesh; +aiMesh *MMDImporter::CreateMesh(const pmx::PmxModel *pModel, + const int indexStart, const int indexCount) { + aiMesh *pMesh = new aiMesh; - pMesh->mNumVertices = indexCount; + pMesh->mNumVertices = indexCount; - pMesh->mNumFaces = indexCount / 3; - pMesh->mFaces = new aiFace[ pMesh->mNumFaces ]; + pMesh->mNumFaces = indexCount / 3; + pMesh->mFaces = new aiFace[pMesh->mNumFaces]; - for( unsigned int index = 0; index < pMesh->mNumFaces; index++ ) { - const int numIndices = 3; // trianglular face - pMesh->mFaces[index].mNumIndices = numIndices; - unsigned int *indices = new unsigned int[numIndices]; - indices[0] = numIndices * index; - indices[1] = numIndices * index + 1; - indices[2] = numIndices * index + 2; - pMesh->mFaces[index].mIndices = indices; + for (unsigned int index = 0; index < pMesh->mNumFaces; index++) { + const int numIndices = 3; // trianglular face + pMesh->mFaces[index].mNumIndices = numIndices; + unsigned int *indices = new unsigned int[numIndices]; + indices[0] = numIndices * index; + indices[1] = numIndices * index + 1; + indices[2] = numIndices * index + 2; + pMesh->mFaces[index].mIndices = indices; + } + + pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; + pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; + pMesh->mTextureCoords[0] = new aiVector3D[pMesh->mNumVertices]; + pMesh->mNumUVComponents[0] = 2; + + // additional UVs + for (int i = 1; i <= pModel->setting.uv; i++) { + pMesh->mTextureCoords[i] = new aiVector3D[pMesh->mNumVertices]; + pMesh->mNumUVComponents[i] = 4; + } + + for (int index = 0; index < indexCount; index++) { + const pmx::PmxVertex *v = + &pModel->vertices[pModel->indices[indexStart + index]]; + const float *position = v->position; + pMesh->mVertices[index].Set(position[0], position[1], position[2]); + const float *normal = v->normal; + pMesh->mNormals[index].Set(normal[0], normal[1], normal[2]); + pMesh->mTextureCoords[0][index].x = v->uv[0]; + pMesh->mTextureCoords[0][index].y = v->uv[1]; + for (int i = 1; i <= pModel->setting.uv; i++) { + // TODO: wrong here? use quaternion transform? + pMesh->mTextureCoords[i][index].x = v->uva[i][0]; + pMesh->mTextureCoords[i][index].y = v->uva[i][1]; } + } - pMesh->mVertices = new aiVector3D[ pMesh->mNumVertices ]; - pMesh->mNormals = new aiVector3D[ pMesh->mNumVertices ]; - pMesh->mTextureCoords[0] = new aiVector3D[ pMesh->mNumVertices ]; - pMesh->mNumUVComponents[0] = 2; - - // additional UVs - for( int i = 1; i <= pModel->setting.uv; i++ ) { - pMesh->mTextureCoords[i] = new aiVector3D[ pMesh->mNumVertices ]; - pMesh->mNumUVComponents[i] = 4; - } - - for( int index = 0; index < indexCount; index++ ) { - const pmx::PmxVertex *v = &pModel->vertices[pModel->indices[indexStart + index]]; - const float* position = v->position; - pMesh->mVertices[index].Set(position[0], position[1], position[2]); - const float* normal = v->normal; - pMesh->mNormals[index].Set(normal[0], normal[1], normal[2]); - pMesh->mTextureCoords[0][index].x = v->uv[0]; - pMesh->mTextureCoords[0][index].y = v->uv[1]; - for( int i = 1; i <= pModel->setting.uv; i++ ) { - // TODO: wrong here? use quaternion transform? - pMesh->mTextureCoords[i][index].x = v->uva[i][0]; - pMesh->mTextureCoords[i][index].y = v->uva[i][1]; - } - } - - return pMesh; + return pMesh; } // ------------------------------------------------------------------------------------------------ -aiMaterial* MMDImporter::CreateMaterial(const pmx::PmxMaterial* pMat, const pmx::PmxModel* pModel) -{ - aiMaterial *mat = new aiMaterial(); - aiString name(pMat->material_english_name); - mat->AddProperty(&name, AI_MATKEY_NAME); +aiMaterial *MMDImporter::CreateMaterial(const pmx::PmxMaterial *pMat, + const pmx::PmxModel *pModel) { + aiMaterial *mat = new aiMaterial(); + aiString name(pMat->material_english_name); + mat->AddProperty(&name, AI_MATKEY_NAME); - aiColor3D diffuse(pMat->diffuse[0], pMat->diffuse[1], pMat->diffuse[2]); - mat->AddProperty(&diffuse, 1, AI_MATKEY_COLOR_DIFFUSE); - aiColor3D specular(pMat->specular[0], pMat->specular[1], pMat->specular[2]); - mat->AddProperty(&specular, 1, AI_MATKEY_COLOR_SPECULAR); - aiColor3D ambient(pMat->ambient[0], pMat->ambient[1], pMat->ambient[2]); - mat->AddProperty(&ambient, 1, AI_MATKEY_COLOR_AMBIENT); + aiColor3D diffuse(pMat->diffuse[0], pMat->diffuse[1], pMat->diffuse[2]); + mat->AddProperty(&diffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + aiColor3D specular(pMat->specular[0], pMat->specular[1], pMat->specular[2]); + mat->AddProperty(&specular, 1, AI_MATKEY_COLOR_SPECULAR); + aiColor3D ambient(pMat->ambient[0], pMat->ambient[1], pMat->ambient[2]); + mat->AddProperty(&ambient, 1, AI_MATKEY_COLOR_AMBIENT); - float opacity = pMat->diffuse[3]; - mat->AddProperty(&opacity, 1, AI_MATKEY_OPACITY); - float shininess = pMat->specularlity; - mat->AddProperty(&shininess, 1, AI_MATKEY_SHININESS_STRENGTH); - - aiString texture_path(pModel->textures[pMat->diffuse_texture_index]); - mat->AddProperty(&texture_path, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0)); - int mapping_uvwsrc = 0; - mat->AddProperty(&mapping_uvwsrc, 1, AI_MATKEY_UVWSRC(aiTextureType_DIFFUSE, 0)); + float opacity = pMat->diffuse[3]; + mat->AddProperty(&opacity, 1, AI_MATKEY_OPACITY); + float shininess = pMat->specularlity; + mat->AddProperty(&shininess, 1, AI_MATKEY_SHININESS_STRENGTH); - return mat; + aiString texture_path(pModel->textures[pMat->diffuse_texture_index]); + mat->AddProperty(&texture_path, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0)); + int mapping_uvwsrc = 0; + mat->AddProperty(&mapping_uvwsrc, 1, + AI_MATKEY_UVWSRC(aiTextureType_DIFFUSE, 0)); + + return mat; } // ------------------------------------------------------------------------------------------------ -} // Namespace Assimp +} // Namespace Assimp #endif // !! ASSIMP_BUILD_NO_MMD_IMPORTER From 0231af43430e2a38bf3b975c478a0fad6e06d3cd Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Tue, 14 Mar 2017 02:01:26 +0800 Subject: [PATCH 11/23] skeleton almost done --- code/MMDImporter.cpp | 177 +++++++++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 75 deletions(-) diff --git a/code/MMDImporter.cpp b/code/MMDImporter.cpp index def089353..6f30b3015 100644 --- a/code/MMDImporter.cpp +++ b/code/MMDImporter.cpp @@ -152,37 +152,29 @@ void MMDImporter::CreateDataFromImport(const pmx::PmxModel *pModel, std::cout << pScene->mRootNode->mName.C_Str() << std::endl; std::cout << pModel->index_count << std::endl; - /* - pNode = new aiNode; - pScene->mRootNode->addChildren(1, &pNode); - pNode->mName.Set(string(pModel->model_name) + string("_mesh")); + pNode = new aiNode; + pScene->mRootNode->addChildren(1, &pNode); + pNode->mName.Set(string(pModel->model_name) + string("_mesh")); - // split mesh by materials - pNode->mNumMeshes = pModel->material_count; - pNode->mMeshes = new unsigned int[pNode->mNumMeshes]; - for (unsigned int index = 0; index < pNode->mNumMeshes; index++) { - pNode->mMeshes[index] = index; - } + // split mesh by materials + pNode->mNumMeshes = pModel->material_count; + pNode->mMeshes = new unsigned int[pNode->mNumMeshes]; + for (unsigned int index = 0; index < pNode->mNumMeshes; index++) { + pNode->mMeshes[index] = index; + } - pScene->mNumMeshes = pNode->mNumMeshes; - pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; - for (unsigned int i = 0, indexStart = 0; i < pScene->mNumMeshes; i++) { - const int indexCount = pModel->materials[i].index_count; + pScene->mNumMeshes = pModel->material_count; + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + for (unsigned int i = 0, indexStart = 0; i < pScene->mNumMeshes; i++) { + const int indexCount = pModel->materials[i].index_count; - std::cout << pModel->materials[i].material_name << std::endl; - std::cout << indexStart << " " << indexCount << std::endl; + std::cout << pModel->materials[i].material_name << std::endl; + std::cout << indexStart << " " << indexCount << std::endl; - pScene->mMeshes[i] = CreateMesh(pModel, indexStart, indexCount); - pScene->mMeshes[i]->mName = pModel->materials[i].material_name; - pScene->mMeshes[i]->mMaterialIndex = i; - indexStart += indexCount; - } - */ - - // create bones - aiBone *pBone = new aiBone[pModel->bone_count]; - for (auto i = 0; i < pModel->bone_count; i++) { - pBone[i].mName = pModel->bones[i].bone_name; + pScene->mMeshes[i] = CreateMesh(pModel, indexStart, indexCount); + pScene->mMeshes[i]->mName = pModel->materials[i].material_name; + pScene->mMeshes[i]->mMaterialIndex = i; + indexStart += indexCount; } // create node hierarchy for bone position @@ -197,6 +189,7 @@ void MMDImporter::CreateDataFromImport(const pmx::PmxModel *pModel, std::cout << "Bone " << i << ":" << std::endl; std::cout << bone.bone_name << std::endl; std::cout << bone.bone_english_name << std::endl; + /**/ std::cout << "position " << bone.position[0] << " " << bone.position[1] << " " << bone.position[2] << std::endl; std::cout << "parent_index " << bone.parent_index << std::endl; @@ -205,6 +198,7 @@ void MMDImporter::CreateDataFromImport(const pmx::PmxModel *pModel, std::cout << "offset " << bone.offset[0] << " " << bone.offset[1] << " " << bone.offset[2] << std::endl; std::cout << "target_index" << bone.target_index << std::endl; + /**/ if (bone.parent_index < 0) { pScene->mRootNode->addChildren(1, ppNode + i); @@ -217,58 +211,18 @@ void MMDImporter::CreateDataFromImport(const pmx::PmxModel *pModel, bone.position[1] - pModel->bones[bone.parent_index].position[1], bone.position[2] - pModel->bones[bone.parent_index].position[2]); aiMatrix4x4::Translation(v3, ppNode[i]->mTransformation); - std::cout << ppNode[i]->mTransformation.a1 << ' ' - << ppNode[i]->mTransformation.a2 << ' ' - << ppNode[i]->mTransformation.a3 << ' ' - << ppNode[i]->mTransformation.a4 << ' ' - << ppNode[i]->mTransformation.b1 << ' ' - << ppNode[i]->mTransformation.b2 << ' ' - << ppNode[i]->mTransformation.b3 << ' ' - << ppNode[i]->mTransformation.b4 << ' ' - << ppNode[i]->mTransformation.c1 << ' ' - << ppNode[i]->mTransformation.c2 << ' ' - << ppNode[i]->mTransformation.c3 << ' ' - << ppNode[i]->mTransformation.c4 << ' ' - << ppNode[i]->mTransformation.d1 << ' ' - << ppNode[i]->mTransformation.d2 << ' ' - << ppNode[i]->mTransformation.d3 << ' ' - << ppNode[i]->mTransformation.d4 << ' ' << std::endl; } } // use SkeletonMeshBuilder to generate bone vertices - SkeletonMeshBuilder dummyMeshbuild(pScene, ppNode[bone_root_index]); + // SkeletonMeshBuilder dummyMeshbuild(pScene, ppNode[bone_root_index]); - // create textures, may be dummy? - /* - pScene->mNumTextures = pModel->texture_count; - pScene->mTextures = new aiTexture*[pScene->mNumTextures]; - for( unsigned int i = 0; i < pScene->mNumTextures; ++i) { - aiTexture *tex = new aiTexture; - pScene->mTextures[i] = tex; - strcpy(tex->achFormatHint, "png"); - tex->mHeight = 0; - ifstream file(pModel->textures[i], ios::binary | ios::ate); - streamsize size = file.tellg(); - file.seekg(0, ios::beg); - char *buffer = new char[size]; - file.read(buffer, size); - if(file.bad()) { - string err("PMX: Can't open texture file"); - err.append(pModel->textures[i]); - throw DeadlyExportError(err); - } - tex->pcData = (aiTexel*)buffer; + // create materials + pScene->mNumMaterials = pModel->material_count; + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; + for (unsigned int i = 0; i < pScene->mNumMaterials; i++) { + pScene->mMaterials[i] = CreateMaterial(&pModel->materials[i], pModel); } - */ - - /* - // create materials - pScene->mNumMaterials = pModel->material_count; - pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; - for (unsigned int i = 0; i < pScene->mNumMaterials; i++) { - pScene->mMaterials[i] = CreateMaterial(&pModel->materials[i], pModel); - } // Convert everything to OpenGL space MakeLeftHandedProcess convertProcess; @@ -279,7 +233,6 @@ void MMDImporter::CreateDataFromImport(const pmx::PmxModel *pModel, FlipWindingOrderProcess windingFlipper; windingFlipper.Execute(pScene); - */ } // ------------------------------------------------------------------------------------------------ @@ -292,8 +245,8 @@ aiMesh *MMDImporter::CreateMesh(const pmx::PmxModel *pModel, pMesh->mNumFaces = indexCount / 3; pMesh->mFaces = new aiFace[pMesh->mNumFaces]; + const int numIndices = 3; // trianglular face for (unsigned int index = 0; index < pMesh->mNumFaces; index++) { - const int numIndices = 3; // trianglular face pMesh->mFaces[index].mNumIndices = numIndices; unsigned int *indices = new unsigned int[numIndices]; indices[0] = numIndices * index; @@ -313,20 +266,94 @@ aiMesh *MMDImporter::CreateMesh(const pmx::PmxModel *pModel, pMesh->mNumUVComponents[i] = 4; } + map> bone_vertex_map; + + // fill in contents and create bones for (int index = 0; index < indexCount; index++) { const pmx::PmxVertex *v = &pModel->vertices[pModel->indices[indexStart + index]]; const float *position = v->position; pMesh->mVertices[index].Set(position[0], position[1], position[2]); const float *normal = v->normal; + pMesh->mNormals[index].Set(normal[0], normal[1], normal[2]); pMesh->mTextureCoords[0][index].x = v->uv[0]; pMesh->mTextureCoords[0][index].y = v->uv[1]; + for (int i = 1; i <= pModel->setting.uv; i++) { // TODO: wrong here? use quaternion transform? pMesh->mTextureCoords[i][index].x = v->uva[i][0]; pMesh->mTextureCoords[i][index].y = v->uva[i][1]; } + + // handle bone map + const auto vsBDEF1_ptr = + dynamic_cast(v->skinning.get()); + const auto vsBDEF2_ptr = + dynamic_cast(v->skinning.get()); + const auto vsBDEF4_ptr = + dynamic_cast(v->skinning.get()); + const auto vsSDEF_ptr = + dynamic_cast(v->skinning.get()); + switch (v->skinning_type) { + case pmx::PmxVertexSkinningType::BDEF1: + bone_vertex_map[vsBDEF1_ptr->bone_index].push_back( + aiVertexWeight(index, 1.0)); + break; + case pmx::PmxVertexSkinningType::BDEF2: + bone_vertex_map[vsBDEF2_ptr->bone_index1].push_back( + aiVertexWeight(index, vsBDEF2_ptr->bone_weight)); + bone_vertex_map[vsBDEF2_ptr->bone_index2].push_back( + aiVertexWeight(index, 1.0 - vsBDEF2_ptr->bone_weight)); + break; + case pmx::PmxVertexSkinningType::BDEF4: + bone_vertex_map[vsBDEF4_ptr->bone_index1].push_back( + aiVertexWeight(index, vsBDEF4_ptr->bone_weight1)); + bone_vertex_map[vsBDEF4_ptr->bone_index2].push_back( + aiVertexWeight(index, vsBDEF4_ptr->bone_weight2)); + bone_vertex_map[vsBDEF4_ptr->bone_index3].push_back( + aiVertexWeight(index, vsBDEF4_ptr->bone_weight3)); + bone_vertex_map[vsBDEF4_ptr->bone_index4].push_back( + aiVertexWeight(index, vsBDEF4_ptr->bone_weight4)); + break; + case pmx::PmxVertexSkinningType::SDEF: // TODO: how to use sdef_c, sdef_r0, + // sdef_r1? + bone_vertex_map[vsSDEF_ptr->bone_index1].push_back( + aiVertexWeight(index, vsSDEF_ptr->bone_weight)); + bone_vertex_map[vsSDEF_ptr->bone_index2].push_back( + aiVertexWeight(index, 1.0 - vsSDEF_ptr->bone_weight)); + break; + case pmx::PmxVertexSkinningType::QDEF: + const auto vsQDEF_ptr = + dynamic_cast(v->skinning.get()); + bone_vertex_map[vsQDEF_ptr->bone_index1].push_back( + aiVertexWeight(index, vsQDEF_ptr->bone_weight1)); + bone_vertex_map[vsQDEF_ptr->bone_index2].push_back( + aiVertexWeight(index, vsQDEF_ptr->bone_weight2)); + bone_vertex_map[vsQDEF_ptr->bone_index3].push_back( + aiVertexWeight(index, vsQDEF_ptr->bone_weight3)); + bone_vertex_map[vsQDEF_ptr->bone_index4].push_back( + aiVertexWeight(index, vsQDEF_ptr->bone_weight4)); + break; + } + } + + auto bone_ptr_ptr = new aiBone *[bone_vertex_map.size()]; + pMesh->mNumBones = bone_vertex_map.size(); + pMesh->mBones = bone_ptr_ptr; + int bone_new_index = 0; + for (auto it = bone_vertex_map.begin(); it != bone_vertex_map.end(); ++it) { + auto pBone = new aiBone; + const auto &pmxBone = pModel->bones[it->first]; + pBone->mName = pmxBone.bone_name; + pBone->mOffsetMatrix = aiMatrix4x4(); + pBone->mNumWeights = it->second.size(); + pBone->mWeights = new aiVertexWeight[pBone->mNumWeights]; + pBone->mWeights = it->second.data(); + (new vector())->swap(it->second); + // copy(it->second.begin(), it->second.end(), pBone->mWeights); + bone_ptr_ptr[bone_new_index] = pBone; + bone_new_index++; } return pMesh; From 0eecff74d826dc420b85d000d9339c19f5f47102 Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Thu, 16 Mar 2017 01:25:53 +0800 Subject: [PATCH 12/23] correct node hierarchy --- code/MMDImporter.cpp | 48 ++++++++++++-------------------------------- 1 file changed, 13 insertions(+), 35 deletions(-) diff --git a/code/MMDImporter.cpp b/code/MMDImporter.cpp index 6f30b3015..b57fe55db 100644 --- a/code/MMDImporter.cpp +++ b/code/MMDImporter.cpp @@ -149,8 +149,6 @@ void MMDImporter::CreateDataFromImport(const pmx::PmxModel *pModel, } pScene->mRootNode = pNode; - std::cout << pScene->mRootNode->mName.C_Str() << std::endl; - std::cout << pModel->index_count << std::endl; pNode = new aiNode; pScene->mRootNode->addChildren(1, &pNode); @@ -168,9 +166,6 @@ void MMDImporter::CreateDataFromImport(const pmx::PmxModel *pModel, for (unsigned int i = 0, indexStart = 0; i < pScene->mNumMeshes; i++) { const int indexCount = pModel->materials[i].index_count; - std::cout << pModel->materials[i].material_name << std::endl; - std::cout << indexStart << " " << indexCount << std::endl; - pScene->mMeshes[i] = CreateMesh(pModel, indexStart, indexCount); pScene->mMeshes[i]->mName = pModel->materials[i].material_name; pScene->mMeshes[i]->mMaterialIndex = i; @@ -178,7 +173,6 @@ void MMDImporter::CreateDataFromImport(const pmx::PmxModel *pModel, } // create node hierarchy for bone position - auto bone_root_index = 0; // Default root: Bone 0 "Body" / "全ての親" aiNode **ppNode = new aiNode *[pModel->bone_count]; for (auto i = 0; i < pModel->bone_count; i++) { ppNode[i] = new aiNode(pModel->bones[i].bone_name); @@ -186,23 +180,9 @@ void MMDImporter::CreateDataFromImport(const pmx::PmxModel *pModel, for (auto i = 0; i < pModel->bone_count; i++) { const pmx::PmxBone &bone = pModel->bones[i]; - std::cout << "Bone " << i << ":" << std::endl; - std::cout << bone.bone_name << std::endl; - std::cout << bone.bone_english_name << std::endl; - /**/ - std::cout << "position " << bone.position[0] << " " << bone.position[1] - << " " << bone.position[2] << std::endl; - std::cout << "parent_index " << bone.parent_index << std::endl; - std::cout << "level " << bone.level << std::endl; - std::cout << "bone_flag " << bone.bone_flag << std::endl; - std::cout << "offset " << bone.offset[0] << " " << bone.offset[1] << " " - << bone.offset[2] << std::endl; - std::cout << "target_index" << bone.target_index << std::endl; - /**/ if (bone.parent_index < 0) { pScene->mRootNode->addChildren(1, ppNode + i); - bone_root_index = i; } else { ppNode[bone.parent_index]->addChildren(1, ppNode + i); @@ -214,9 +194,6 @@ void MMDImporter::CreateDataFromImport(const pmx::PmxModel *pModel, } } - // use SkeletonMeshBuilder to generate bone vertices - // SkeletonMeshBuilder dummyMeshbuild(pScene, ppNode[bone_root_index]); - // create materials pScene->mNumMaterials = pModel->material_count; pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; @@ -338,22 +315,23 @@ aiMesh *MMDImporter::CreateMesh(const pmx::PmxModel *pModel, } } - auto bone_ptr_ptr = new aiBone *[bone_vertex_map.size()]; - pMesh->mNumBones = bone_vertex_map.size(); + // make all bones for each mesh + // assign bone weights to skinned bones (otherwise just initialize) + auto bone_ptr_ptr = new aiBone *[pModel->bone_count]; + pMesh->mNumBones = pModel->bone_count; pMesh->mBones = bone_ptr_ptr; - int bone_new_index = 0; - for (auto it = bone_vertex_map.begin(); it != bone_vertex_map.end(); ++it) { + for (auto ii = 0; ii < pModel->bone_count; ++ii) { auto pBone = new aiBone; - const auto &pmxBone = pModel->bones[it->first]; + const auto &pmxBone = pModel->bones[ii]; pBone->mName = pmxBone.bone_name; pBone->mOffsetMatrix = aiMatrix4x4(); - pBone->mNumWeights = it->second.size(); - pBone->mWeights = new aiVertexWeight[pBone->mNumWeights]; - pBone->mWeights = it->second.data(); - (new vector())->swap(it->second); - // copy(it->second.begin(), it->second.end(), pBone->mWeights); - bone_ptr_ptr[bone_new_index] = pBone; - bone_new_index++; + auto it = bone_vertex_map.find(ii); + if (it != bone_vertex_map.end()) { + pBone->mNumWeights = it->second.size(); + pBone->mWeights = it->second.data(); + it->second.swap(*(new vector)); + } + bone_ptr_ptr[ii] = pBone; } return pMesh; From e5a3038abdedfba815412aae1611e31579ef15d3 Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Tue, 21 Mar 2017 16:21:49 +0800 Subject: [PATCH 13/23] try to fill in vertex weights to dae exportor --- code/ColladaExporter.cpp | 80 ++++++++++++++++++++++++++++++++++++++++ code/ColladaExporter.h | 6 +++ 2 files changed, 86 insertions(+) diff --git a/code/ColladaExporter.cpp b/code/ColladaExporter.cpp index 7f9540aa1..8c5b03e77 100644 --- a/code/ColladaExporter.cpp +++ b/code/ColladaExporter.cpp @@ -133,6 +133,7 @@ void ColladaExporter::WriteFile() WriteLightsLibrary(); WriteMaterials(); WriteGeometryLibrary(); + WriteControllerLibrary(); WriteSceneLibrary(); @@ -787,6 +788,85 @@ void ColladaExporter::WriteMaterials() } } +// ------------------------------------------------------------------------------------------------ +// Writes the controller library +void ColladaExporter::WriteControllerLibrary() +{ + mOutput << startstr << "" << endstr; + PushTag(); + + for( size_t a = 0; a < mScene->mNumMeshes; ++a) + WriteController( a); + + PopTag(); + mOutput << startstr << "" << endstr; +} + +// ------------------------------------------------------------------------------------------------ +// Writes a skin controller of the given mesh +void WriteController( size_t pIndex) +{ + const aiMesh* mesh = mScene->mMeshes[pIndex]; + const std::string idstr = GetMeshId( pIndex); + const std::string idstrEscaped = XMLEscape(idstr); + + if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 ) + return; + + if ( mesh->mNumBones == 0 ) + return; + + mOutput << startstr << ""<< endstr; + PushTag(); + + mOutput << startstr << "" << endstr; + PushTag(); + + // bind pose matrix + mOutput << startstr << "" << endstr; + PushTag(); + + // I think it is identity in general cases. + aiMatrix4x4 mat(); + mOutput << startstr; + mOutput << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4; + mOutput << mat.b1 << " " << mat.b2 << " " << mat.b3 << " " << mat.b4; + mOutput << mat.c1 << " " << mat.c2 << " " << mat.c3 << " " << mat.c4; + mOutput << mat.d1 << " " << mat.d2 << " " << mat.d3 << " " << mat.d4; + mOutput << endstr; + + PopTag(); + mOutput << startstr << "" << endstr; + + mOutput << startstr << "mNumBones << "\">"; + + for( size_t i = 0; i < mesh->mNumBones; ++i ) + mOutput << XMLEscape(mesh->mBones[i].mName) << " "; + + mOutput << "" << endstr; + + mOutput << startstr << "" << endstr; + PushTag(); + + mOutput << startstr << "mNumBones << "\" stride=\"" << 1 << "\">"; + PushTag(); + + mOutput << startstr << "param name=\"JOINT\" type=\"Name\">" << endstr; + + PopTag(); + mOutput << "" << endstr; + + PopTag(); + mOutput << startstr << "" << endstr; + + PopTag(); + mOutput << startstr << "" << endstr; + + PopTag(); + mOutput << startstr << "" << endstr; +} + // ------------------------------------------------------------------------------------------------ // Writes the geometry library void ColladaExporter::WriteGeometryLibrary() diff --git a/code/ColladaExporter.h b/code/ColladaExporter.h index e8bd9b71f..9521e61ad 100644 --- a/code/ColladaExporter.h +++ b/code/ColladaExporter.h @@ -101,6 +101,12 @@ protected: void WriteSpotLight(const aiLight *const light); void WriteAmbienttLight(const aiLight *const light); + /// Writes the controller library + void WriteControllerLibrary(); + + /// Writes a skin controller of the given mesh + void WriteController( size_t pIndex); + /// Writes the geometry library void WriteGeometryLibrary(); From 5bf974ae3b86bac89ca250b79a456d90ea1eb50d Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Wed, 22 Mar 2017 17:06:55 +0800 Subject: [PATCH 14/23] keep writing dae skinning --- code/ColladaExporter.cpp | 19 +++++++++++++++++++ code/ColladaExporter.h | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/code/ColladaExporter.cpp b/code/ColladaExporter.cpp index 8c5b03e77..278ded66a 100644 --- a/code/ColladaExporter.cpp +++ b/code/ColladaExporter.cpp @@ -860,6 +860,15 @@ void WriteController( size_t pIndex) PopTag(); mOutput << startstr << "" << endstr; + std::vector bind_poses; + bind_poses.reserve(mesh->mNumBones * 16); + for( size_t i = 0; i< mesh->mNumBones; ++i) + for( size_t j = 0; j < 4; ++j) + bind_poses.insert(bind_poses.end(), mesh->mBones[i]->mOffsetMatrix[0], mesh->mBones[i]->mOffsetMatrix[0] + 4); + + WriteFloatArray( idstr + "-skin-bind_poses", FloatType_Mat4x4, (const ai_real*) bind_poses.data(), bind_poses.size()); + + PopTag(); mOutput << startstr << "" << endstr; @@ -1031,6 +1040,8 @@ void ColladaExporter::WriteFloatArray( const std::string& pIdString, FloatDataTy case FloatType_TexCoord2: floatsPerElement = 2; break; case FloatType_TexCoord3: floatsPerElement = 3; break; case FloatType_Color: floatsPerElement = 3; break; + case FloatType_Mat4x4: floatsPerElement = 16; break; + case FloatType_Weight: floatsPerElement = 1; break; default: return; } @@ -1099,6 +1110,14 @@ void ColladaExporter::WriteFloatArray( const std::string& pIdString, FloatDataTy mOutput << startstr << "" << endstr; mOutput << startstr << "" << endstr; break; + + case FloatType_Mat4x4: + mOutput << startstr << "" << endstr; + break; + + case FloatType_Weight: + mOutput << startstr << "" << endstr; + break; } PopTag(); diff --git a/code/ColladaExporter.h b/code/ColladaExporter.h index 9521e61ad..695b00bfd 100644 --- a/code/ColladaExporter.h +++ b/code/ColladaExporter.h @@ -113,7 +113,7 @@ protected: /// Writes the given mesh void WriteGeometry( size_t pIndex); - enum FloatDataType { FloatType_Vector, FloatType_TexCoord2, FloatType_TexCoord3, FloatType_Color }; + enum FloatDataType { FloatType_Vector, FloatType_TexCoord2, FloatType_TexCoord3, FloatType_Color, FloatType_Mat4x4, FloatType_Weight }; /// Writes a float array of the given type void WriteFloatArray( const std::string& pIdString, FloatDataType pType, const ai_real* pData, size_t pElementCount); From 845d206959993392a08ab756a78246cbae96c9c0 Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Wed, 22 Mar 2017 21:11:17 +0800 Subject: [PATCH 15/23] Fill in mParent for each node in Assbin Loader --- code/AssbinLoader.cpp | 10 +++++++--- code/AssbinLoader.h | 2 +- code/ColladaExporter.cpp | 8 ++++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/code/AssbinLoader.cpp b/code/AssbinLoader.cpp index 7a17d5ad5..5686d45b9 100644 --- a/code/AssbinLoader.cpp +++ b/code/AssbinLoader.cpp @@ -196,7 +196,7 @@ template void ReadBounds( IOStream * stream, T* /*p*/, unsigned int stream->Seek( sizeof(T) * n, aiOrigin_CUR ); } -void AssbinImporter::ReadBinaryNode( IOStream * stream, aiNode** node ) +void AssbinImporter::ReadBinaryNode( IOStream * stream, aiNode** node, aiNode* parent ) { uint32_t chunkID = Read(stream); ai_assert(chunkID == ASSBIN_CHUNK_AINODE); @@ -208,6 +208,10 @@ void AssbinImporter::ReadBinaryNode( IOStream * stream, aiNode** node ) (*node)->mTransformation = Read(stream); (*node)->mNumChildren = Read(stream); (*node)->mNumMeshes = Read(stream); + if(parent) + { + (*node)->mParent = parent; + } if ((*node)->mNumMeshes) { @@ -221,7 +225,7 @@ void AssbinImporter::ReadBinaryNode( IOStream * stream, aiNode** node ) { (*node)->mChildren = new aiNode*[(*node)->mNumChildren]; for (unsigned int i = 0; i < (*node)->mNumChildren; ++i) { - ReadBinaryNode( stream, &(*node)->mChildren[i] ); + ReadBinaryNode( stream, &(*node)->mChildren[i], *node ); } } @@ -569,7 +573,7 @@ void AssbinImporter::ReadBinaryScene( IOStream * stream, aiScene* scene ) // Read node graph scene->mRootNode = new aiNode[1]; - ReadBinaryNode( stream, &scene->mRootNode ); + ReadBinaryNode( stream, &scene->mRootNode, (aiNode*)NULL ); // Read all meshes if (scene->mNumMeshes) diff --git a/code/AssbinLoader.h b/code/AssbinLoader.h index e8c8dd0cb..7e098caec 100644 --- a/code/AssbinLoader.h +++ b/code/AssbinLoader.h @@ -86,7 +86,7 @@ public: IOSystem* pIOHandler ); void ReadBinaryScene( IOStream * stream, aiScene* pScene ); - void ReadBinaryNode( IOStream * stream, aiNode** mRootNode ); + void ReadBinaryNode( IOStream * stream, aiNode** mRootNode, aiNode* parent ); void ReadBinaryMesh( IOStream * stream, aiMesh* mesh ); void ReadBinaryBone( IOStream * stream, aiBone* bone ); void ReadBinaryMaterial(IOStream * stream, aiMaterial* mat); diff --git a/code/ColladaExporter.cpp b/code/ColladaExporter.cpp index 278ded66a..e373d8cf7 100644 --- a/code/ColladaExporter.cpp +++ b/code/ColladaExporter.cpp @@ -804,7 +804,7 @@ void ColladaExporter::WriteControllerLibrary() // ------------------------------------------------------------------------------------------------ // Writes a skin controller of the given mesh -void WriteController( size_t pIndex) +void ColladaExporter::WriteController( size_t pIndex) { const aiMesh* mesh = mScene->mMeshes[pIndex]; const std::string idstr = GetMeshId( pIndex); @@ -820,7 +820,7 @@ void WriteController( size_t pIndex) mOutput << "name=\"skinCluster" << pIndex << "\">"<< endstr; PushTag(); - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); // bind pose matrix @@ -828,7 +828,7 @@ void WriteController( size_t pIndex) PushTag(); // I think it is identity in general cases. - aiMatrix4x4 mat(); + aiMatrix4x4 mat; mOutput << startstr; mOutput << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4; mOutput << mat.b1 << " " << mat.b2 << " " << mat.b3 << " " << mat.b4; @@ -842,7 +842,7 @@ void WriteController( size_t pIndex) mOutput << startstr << "mNumBones << "\">"; for( size_t i = 0; i < mesh->mNumBones; ++i ) - mOutput << XMLEscape(mesh->mBones[i].mName) << " "; + mOutput << XMLEscape(mesh->mBones[i]->mName.C_Str()) << " "; mOutput << "" << endstr; From f10f2f5814409b60552313b15cf58dff5338e6ce Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Thu, 23 Mar 2017 04:33:53 +0800 Subject: [PATCH 16/23] Almost finish vertex weghts --- code/ColladaExporter.cpp | 113 ++++++++++++++++++++++++++++++++++----- 1 file changed, 99 insertions(+), 14 deletions(-) diff --git a/code/ColladaExporter.cpp b/code/ColladaExporter.cpp index e373d8cf7..39c9147d4 100644 --- a/code/ColladaExporter.cpp +++ b/code/ColladaExporter.cpp @@ -59,6 +59,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include +#include using namespace Assimp; @@ -829,17 +831,18 @@ void ColladaExporter::WriteController( size_t pIndex) // I think it is identity in general cases. aiMatrix4x4 mat; - mOutput << startstr; - mOutput << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4; - mOutput << mat.b1 << " " << mat.b2 << " " << mat.b3 << " " << mat.b4; - mOutput << mat.c1 << " " << mat.c2 << " " << mat.c3 << " " << mat.c4; - mOutput << mat.d1 << " " << mat.d2 << " " << mat.d3 << " " << mat.d4; - mOutput << endstr; + mOutput << startstr << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4 << endstr; + mOutput << startstr << mat.b1 << " " << mat.b2 << " " << mat.b3 << " " << mat.b4 << endstr; + mOutput << startstr << mat.c1 << " " << mat.c2 << " " << mat.c3 << " " << mat.c4 << endstr; + mOutput << startstr << mat.d1 << " " << mat.d2 << " " << mat.d3 << " " << mat.d4 << endstr; PopTag(); mOutput << startstr << "" << endstr; - mOutput << startstr << "mNumBones << "\">"; + mOutput << startstr << "" << endstr; + PushTag(); + + mOutput << startstr << "mNumBones << "\">"; for( size_t i = 0; i < mesh->mNumBones; ++i ) mOutput << XMLEscape(mesh->mBones[i]->mName.C_Str()) << " "; @@ -849,25 +852,107 @@ void ColladaExporter::WriteController( size_t pIndex) mOutput << startstr << "" << endstr; PushTag(); - mOutput << startstr << "mNumBones << "\" stride=\"" << 1 << "\">"; + mOutput << startstr << "mNumBones << "\" stride=\"" << 1 << "\">" << endstr; PushTag(); - mOutput << startstr << "param name=\"JOINT\" type=\"Name\">" << endstr; + mOutput << startstr << "" << endstr; PopTag(); - mOutput << "" << endstr; + mOutput << startstr << "" << endstr; PopTag(); mOutput << startstr << "" << endstr; + PopTag(); + mOutput << startstr << "" << endstr; + std::vector bind_poses; bind_poses.reserve(mesh->mNumBones * 16); - for( size_t i = 0; i< mesh->mNumBones; ++i) + for( size_t i = 0; i < mesh->mNumBones; ++i) for( size_t j = 0; j < 4; ++j) - bind_poses.insert(bind_poses.end(), mesh->mBones[i]->mOffsetMatrix[0], mesh->mBones[i]->mOffsetMatrix[0] + 4); + bind_poses.insert(bind_poses.end(), mesh->mBones[i]->mOffsetMatrix[j], mesh->mBones[i]->mOffsetMatrix[j] + 4); - WriteFloatArray( idstr + "-skin-bind_poses", FloatType_Mat4x4, (const ai_real*) bind_poses.data(), bind_poses.size()); - + WriteFloatArray( idstr + "-skin-bind_poses", FloatType_Mat4x4, (const ai_real*) bind_poses.data(), bind_poses.size() / 16); + + bind_poses.clear(); + + std::vector skin_weights; + skin_weights.reserve(mesh->mNumVertices * mesh->mNumBones); + for( size_t i = 0; i < mesh->mNumBones; ++i) + for( size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j) + skin_weights.push_back(mesh->mBones[i]->mWeights[j].mWeight); + + WriteFloatArray( idstr + "-skin-weights", FloatType_Weight, (const ai_real*) skin_weights.data(), skin_weights.size()); + + skin_weights.clear(); + + mOutput << startstr << "" << endstr; + PushTag(); + + mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; + + PopTag(); + mOutput << startstr << "" << endstr; + + mOutput << startstr << "mNumVertices << "\">" << endstr; + PushTag(); + + mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; + + mOutput << startstr << ""; + + std::vector num_influences(mesh->mNumVertices, (ai_uint)0); + for( size_t i = 0; i < mesh->mNumBones; ++i) + for( size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j) + ++num_influences[mesh->mBones[i]->mWeights[j].mVertexId]; + + for( size_t i = 0; i < mesh->mNumVertices; ++i) + mOutput << num_influences[i] << " "; + + mOutput << "" << endstr; + + mOutput << startstr << ""; + + ai_uint joint_weight_indices_length = 0; + std::vector accum_influences; + accum_influences.reserve(num_influences.size()); + for( size_t i = 0; i < num_influences.size(); ++i) + { + accum_influences.push_back(joint_weight_indices_length); + joint_weight_indices_length += num_influences[i]; + } + + ai_uint weight_index = 0; + std::vector joint_weight_indices(2 * joint_weight_indices_length, (ai_int)-1); + for( size_t i = 0; i < mesh->mNumBones; ++i) + for( size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j) + { + unsigned int vId = mesh->mBones[i]->mWeights[j].mVertexId; + for( size_t k = 0; k < num_influences[vId]; ++k) + { + if (joint_weight_indices[2 * (accum_influences[vId] + k)] == -1) + { + joint_weight_indices[2 * (accum_influences[vId] + k)] = i; + joint_weight_indices[2 * (accum_influences[vId] + k) + 1] = weight_index; + break; + } + } + ++weight_index; + } + + for( size_t i = 0; i < joint_weight_indices.size(); ++i) + mOutput << joint_weight_indices[i] << " "; + + num_influences.clear(); + accum_influences.clear(); + joint_weight_indices.clear(); + + mOutput << "" << endstr; + + PopTag(); + mOutput << startstr << "" << endstr; PopTag(); mOutput << startstr << "" << endstr; From 314bb451bb27e080e0231fa1ceb8de557de09dae Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Thu, 23 Mar 2017 16:30:01 +0800 Subject: [PATCH 17/23] testing weights, still strange --- code/ColladaExporter.cpp | 44 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/code/ColladaExporter.cpp b/code/ColladaExporter.cpp index 39c9147d4..cf9968795 100644 --- a/code/ColladaExporter.cpp +++ b/code/ColladaExporter.cpp @@ -1228,6 +1228,40 @@ void ColladaExporter::WriteSceneLibrary() for( size_t a = 0; a < mScene->mRootNode->mNumChildren; ++a ) WriteNode( mScene, mScene->mRootNode->mChildren[a]); + for( size_t a = 0; a < mScene->mNumMeshes; ++a ) + { + const aiMesh* mesh = mScene->mMeshes[a]; + const std::string idstr = GetMeshId( a); + const std::string idstrEscaped = XMLEscape(idstr); + + if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 ) + continue; + + if ( mesh->mNumBones == 0 ) + continue; + + const std::string mesh_name_escaped = XMLEscape(mesh->mName.C_Str()); + mOutput << startstr + << "" + << endstr; + PushTag(); + + mOutput << startstr + << "" + << endstr; + PushTag(); + + mOutput << startstr << "#skeleton_root" << endstr; + + PopTag(); + mOutput << startstr << "" << endstr; + + PopTag(); + mOutput << startstr << "" << endstr; + } + PopTag(); mOutput << startstr << "" << endstr; PopTag(); @@ -1264,15 +1298,23 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode) // If the node is associated with a bone, it is a joint node (JOINT) // otherwise it is a normal node (NODE) const char * node_type; + bool is_joint, is_skeleton_root = false; if (NULL == findBone(pScene, pNode->mName.C_Str())) { node_type = "NODE"; + is_joint = false; } else { node_type = "JOINT"; + is_joint = true; + if(!pNode->mParent || NULL == findBone(pScene, pNode->mParent->mName.C_Str())) + is_skeleton_root = true; } const std::string node_name_escaped = XMLEscape(pNode->mName.data); mOutput << startstr - << "" << endstr; From 968612fea1fdf256d8592b5abcee6fa68497141a Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Fri, 24 Mar 2017 12:04:40 +0800 Subject: [PATCH 18/23] testing2 --- code/ColladaExporter.cpp | 130 +++++++++++++++++++-------------------- code/ColladaExporter.h | 2 +- code/MMDImporter.cpp | 3 +- 3 files changed, 65 insertions(+), 70 deletions(-) diff --git a/code/ColladaExporter.cpp b/code/ColladaExporter.cpp index cf9968795..330981f00 100644 --- a/code/ColladaExporter.cpp +++ b/code/ColladaExporter.cpp @@ -1224,43 +1224,13 @@ void ColladaExporter::WriteSceneLibrary() mOutput << startstr << "" << endstr; PushTag(); + // start write armature at the root node + for( size_t a = 0; a < mScene->mRootNode->mNumChildren; ++a ) + WriteNode( mScene, mScene->mRootNode->mChildren[a], true); + // start recursive write at the root node for( size_t a = 0; a < mScene->mRootNode->mNumChildren; ++a ) - WriteNode( mScene, mScene->mRootNode->mChildren[a]); - - for( size_t a = 0; a < mScene->mNumMeshes; ++a ) - { - const aiMesh* mesh = mScene->mMeshes[a]; - const std::string idstr = GetMeshId( a); - const std::string idstrEscaped = XMLEscape(idstr); - - if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 ) - continue; - - if ( mesh->mNumBones == 0 ) - continue; - - const std::string mesh_name_escaped = XMLEscape(mesh->mName.C_Str()); - mOutput << startstr - << "" - << endstr; - PushTag(); - - mOutput << startstr - << "" - << endstr; - PushTag(); - - mOutput << startstr << "#skeleton_root" << endstr; - - PopTag(); - mOutput << startstr << "" << endstr; - - PopTag(); - mOutput << startstr << "" << endstr; - } + WriteNode( mScene, mScene->mRootNode->mChildren[a], false); PopTag(); mOutput << startstr << "" << endstr; @@ -1285,7 +1255,7 @@ aiBone* findBone( const aiScene* scene, const char * name) { // ------------------------------------------------------------------------------------------------ // Recursively writes the given node -void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode) +void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode, bool need_output_joint) { // the node must have a name if (pNode->mName.length == 0) @@ -1309,13 +1279,20 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode) is_skeleton_root = true; } + if(need_output_joint ^ is_joint) + return; + const std::string node_name_escaped = XMLEscape(pNode->mName.data); mOutput << startstr << "" << endstr; PushTag(); @@ -1323,7 +1300,7 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode) // write transformation - we can directly put the matrix there // TODO: (thom) decompose into scale - rot - quad to allow addressing it by animations afterwards const aiMatrix4x4& mat = pNode->mTransformation; - mOutput << startstr << ""; + mOutput << startstr << ""; mOutput << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4 << " "; mOutput << mat.b1 << " " << mat.b2 << " " << mat.b3 << " " << mat.b4 << " "; mOutput << mat.c1 << " " << mat.c2 << " " << mat.c3 << " " << mat.c4 << " "; @@ -1351,38 +1328,55 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode) for( size_t a = 0; a < pNode->mNumMeshes; ++a ) { const aiMesh* mesh = mScene->mMeshes[pNode->mMeshes[a]]; - // do not instanciate mesh if empty. I wonder how this could happen - if( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 ) - continue; - mOutput << startstr << "mMeshes[a])) << "\">" << endstr; - PushTag(); - mOutput << startstr << "" << endstr; - PushTag(); - mOutput << startstr << "" << endstr; - PushTag(); - mOutput << startstr << "mMaterialIndex].name) << "\">" << endstr; - PushTag(); - for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a ) - { - if( mesh->HasTextureCoords( static_cast(a) ) ) - // semantic as in - // input_semantic as in - // input_set as in - mOutput << startstr << "" << endstr; - } - PopTag(); - mOutput << startstr << "" << endstr; - PopTag(); - mOutput << startstr << "" << endstr; - PopTag(); - mOutput << startstr << "" << endstr; - PopTag(); - mOutput << startstr << "" << endstr; + // do not instanciate mesh if empty. I wonder how this could happen + if( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 ) + continue; + + if( mesh->mNumBones == 0 ) + { + mOutput << startstr << "mMeshes[a])) << "\">" << endstr; + PushTag(); + } + else + { + mOutput << startstr + << "mMeshes[a])) << "-skin\">" + << endstr; + PushTag(); + + mOutput << startstr << "#skeleton_root" << endstr; + } + mOutput << startstr << "" << endstr; + PushTag(); + mOutput << startstr << "" << endstr; + PushTag(); + mOutput << startstr << "mMaterialIndex].name) << "\">" << endstr; + PushTag(); + for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a ) + { + if( mesh->HasTextureCoords( static_cast(a) ) ) + // semantic as in + // input_semantic as in + // input_set as in + mOutput << startstr << "" << endstr; + } + PopTag(); + mOutput << startstr << "" << endstr; + PopTag(); + mOutput << startstr << "" << endstr; + PopTag(); + mOutput << startstr << "" << endstr; + + PopTag(); + if( mesh->mNumBones == 0) + mOutput << startstr << "" << endstr; + else + mOutput << startstr << "" << endstr; } // recurse into subnodes for( size_t a = 0; a < pNode->mNumChildren; ++a ) - WriteNode( pScene, pNode->mChildren[a]); + WriteNode( pScene, pNode->mChildren[a], need_output_joint); PopTag(); mOutput << startstr << "" << endstr; diff --git a/code/ColladaExporter.h b/code/ColladaExporter.h index 695b00bfd..b3c08f8c4 100644 --- a/code/ColladaExporter.h +++ b/code/ColladaExporter.h @@ -122,7 +122,7 @@ protected: void WriteSceneLibrary(); /// Recursively writes the given node - void WriteNode( const aiScene* scene, aiNode* pNode); + void WriteNode( const aiScene* scene, aiNode* pNode, bool need_output_joint); /// Enters a new xml element, which increases the indentation void PushTag() { startstr.append( " "); } diff --git a/code/MMDImporter.cpp b/code/MMDImporter.cpp index b57fe55db..80e966ca6 100644 --- a/code/MMDImporter.cpp +++ b/code/MMDImporter.cpp @@ -324,7 +324,8 @@ aiMesh *MMDImporter::CreateMesh(const pmx::PmxModel *pModel, auto pBone = new aiBone; const auto &pmxBone = pModel->bones[ii]; pBone->mName = pmxBone.bone_name; - pBone->mOffsetMatrix = aiMatrix4x4(); + aiVector3D pos(pmxBone.position[0], -pmxBone.position[1], -pmxBone.position[2]); + aiMatrix4x4::Translation(pos, pBone->mOffsetMatrix); auto it = bone_vertex_map.find(ii); if (it != bone_vertex_map.end()) { pBone->mNumWeights = it->second.size(); From 59b48fb9607e3119fc918b1f9e7cb23f5ed0a1e7 Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Fri, 24 Mar 2017 14:39:34 +0800 Subject: [PATCH 19/23] finish skin controller --- code/ColladaExporter.cpp | 21 +++++---------------- code/ColladaExporter.h | 2 +- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/code/ColladaExporter.cpp b/code/ColladaExporter.cpp index 330981f00..937604815 100644 --- a/code/ColladaExporter.cpp +++ b/code/ColladaExporter.cpp @@ -1224,13 +1224,9 @@ void ColladaExporter::WriteSceneLibrary() mOutput << startstr << "" << endstr; PushTag(); - // start write armature at the root node - for( size_t a = 0; a < mScene->mRootNode->mNumChildren; ++a ) - WriteNode( mScene, mScene->mRootNode->mChildren[a], true); - // start recursive write at the root node for( size_t a = 0; a < mScene->mRootNode->mNumChildren; ++a ) - WriteNode( mScene, mScene->mRootNode->mChildren[a], false); + WriteNode( mScene, mScene->mRootNode->mChildren[a]); PopTag(); mOutput << startstr << "" << endstr; @@ -1255,7 +1251,7 @@ aiBone* findBone( const aiScene* scene, const char * name) { // ------------------------------------------------------------------------------------------------ // Recursively writes the given node -void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode, bool need_output_joint) +void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode) { // the node must have a name if (pNode->mName.length == 0) @@ -1279,19 +1275,12 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode, bool need is_skeleton_root = true; } - if(need_output_joint ^ is_joint) - return; - const std::string node_name_escaped = XMLEscape(pNode->mName.data); mOutput << startstr << "" << endstr; @@ -1376,7 +1365,7 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode, bool need // recurse into subnodes for( size_t a = 0; a < pNode->mNumChildren; ++a ) - WriteNode( pScene, pNode->mChildren[a], need_output_joint); + WriteNode( pScene, pNode->mChildren[a]); PopTag(); mOutput << startstr << "" << endstr; diff --git a/code/ColladaExporter.h b/code/ColladaExporter.h index b3c08f8c4..695b00bfd 100644 --- a/code/ColladaExporter.h +++ b/code/ColladaExporter.h @@ -122,7 +122,7 @@ protected: void WriteSceneLibrary(); /// Recursively writes the given node - void WriteNode( const aiScene* scene, aiNode* pNode, bool need_output_joint); + void WriteNode( const aiScene* scene, aiNode* pNode); /// Enters a new xml element, which increases the indentation void PushTag() { startstr.append( " "); } From ccf2bce2b0cf90d51913cbbc8ed0f7b9edc9834b Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Fri, 24 Mar 2017 15:45:50 +0800 Subject: [PATCH 20/23] correct offset matrix --- code/MMDImporter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/MMDImporter.cpp b/code/MMDImporter.cpp index 80e966ca6..baac75605 100644 --- a/code/MMDImporter.cpp +++ b/code/MMDImporter.cpp @@ -324,8 +324,8 @@ aiMesh *MMDImporter::CreateMesh(const pmx::PmxModel *pModel, auto pBone = new aiBone; const auto &pmxBone = pModel->bones[ii]; pBone->mName = pmxBone.bone_name; - aiVector3D pos(pmxBone.position[0], -pmxBone.position[1], -pmxBone.position[2]); - aiMatrix4x4::Translation(pos, pBone->mOffsetMatrix); + aiVector3D pos(pmxBone.position[0], pmxBone.position[1], pmxBone.position[2]); + aiMatrix4x4::Translation(-pos, pBone->mOffsetMatrix); auto it = bone_vertex_map.find(ii); if (it != bone_vertex_map.end()) { pBone->mNumWeights = it->second.size(); From 8cd0d3b3c707a61eebe6361b5092df8c496e233b Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Mon, 27 Mar 2017 22:16:19 +0800 Subject: [PATCH 21/23] change std::make_unique to mmd::make_unique --- code/MMDCpp14.h | 15 +++++++------- code/MMDPmdParser.h | 4 ++-- code/MMDPmxParser.cpp | 46 +++++++++++++++++++++---------------------- code/MMDVmdParser.h | 4 ++-- 4 files changed, 35 insertions(+), 34 deletions(-) diff --git a/code/MMDCpp14.h b/code/MMDCpp14.h index 212897dc2..ad112de0f 100644 --- a/code/MMDCpp14.h +++ b/code/MMDCpp14.h @@ -1,19 +1,20 @@ #pragma once #ifndef MMD_CPP14_H +#define MMD_CPP14_H #include #include #include #include -namespace std { +namespace mmd { template struct _Unique_if { - typedef unique_ptr _Single_object; + typedef std::unique_ptr _Single_object; }; template struct _Unique_if { - typedef unique_ptr _Unknown_bound; + typedef std::unique_ptr _Unknown_bound; }; template struct _Unique_if { @@ -23,14 +24,14 @@ namespace std { template typename _Unique_if::_Single_object make_unique(Args&&... args) { - return unique_ptr(new T(std::forward(args)...)); + return std::unique_ptr(new T(std::forward(args)...)); } template typename _Unique_if::_Unknown_bound make_unique(size_t n) { - typedef typename remove_extent::type U; - return unique_ptr(new U[n]()); + typedef typename std::remove_extent::type U; + return std::unique_ptr(new U[n]()); } template @@ -38,4 +39,4 @@ namespace std { make_unique(Args&&...) = delete; } -#endif \ No newline at end of file +#endif diff --git a/code/MMDPmdParser.h b/code/MMDPmdParser.h index 233e5de14..375c323df 100644 --- a/code/MMDPmdParser.h +++ b/code/MMDPmdParser.h @@ -454,7 +454,7 @@ namespace pmd /// ファイルからPmdModelを生成する static std::unique_ptr LoadFromStream(std::ifstream *stream) { - auto result = std::make_unique(); + auto result = mmd::make_unique(); char buffer[100]; // magic @@ -627,4 +627,4 @@ namespace pmd return result; } }; -} \ No newline at end of file +} diff --git a/code/MMDPmxParser.cpp b/code/MMDPmxParser.cpp index f115f80f8..352259df5 100644 --- a/code/MMDPmxParser.cpp +++ b/code/MMDPmxParser.cpp @@ -163,19 +163,19 @@ namespace pmx switch (this->skinning_type) { case PmxVertexSkinningType::BDEF1: - this->skinning = std::make_unique(); + this->skinning = mmd::make_unique(); break; case PmxVertexSkinningType::BDEF2: - this->skinning = std::make_unique(); + this->skinning = mmd::make_unique(); break; case PmxVertexSkinningType::BDEF4: - this->skinning = std::make_unique(); + this->skinning = mmd::make_unique(); break; case PmxVertexSkinningType::SDEF: - this->skinning = std::make_unique(); + this->skinning = mmd::make_unique(); break; case PmxVertexSkinningType::QDEF: - this->skinning = std::make_unique(); + this->skinning = mmd::make_unique(); break; default: throw "invalid skinning type"; @@ -254,7 +254,7 @@ namespace pmx stream->read((char*) &ik_loop, sizeof(int)); stream->read((char*) &ik_loop_angle_limit, sizeof(float)); stream->read((char*) &ik_link_count, sizeof(int)); - this->ik_links = std::make_unique(ik_link_count); + this->ik_links = mmd::make_unique(ik_link_count); for (int i = 0; i < ik_link_count; i++) { ik_links[i].Read(stream, setting); } @@ -325,28 +325,28 @@ namespace pmx switch (this->morph_type) { case MorphType::Group: - group_offsets = std::make_unique(this->offset_count); + group_offsets = mmd::make_unique(this->offset_count); for (int i = 0; i < offset_count; i++) { group_offsets[i].Read(stream, setting); } break; case MorphType::Vertex: - vertex_offsets = std::make_unique(this->offset_count); + vertex_offsets = mmd::make_unique(this->offset_count); for (int i = 0; i < offset_count; i++) { vertex_offsets[i].Read(stream, setting); } break; case MorphType::Bone: - bone_offsets = std::make_unique(this->offset_count); + bone_offsets = mmd::make_unique(this->offset_count); for (int i = 0; i < offset_count; i++) { bone_offsets[i].Read(stream, setting); } break; case MorphType::Matrial: - material_offsets = std::make_unique(this->offset_count); + material_offsets = mmd::make_unique(this->offset_count); for (int i = 0; i < offset_count; i++) { material_offsets[i].Read(stream, setting); @@ -357,7 +357,7 @@ namespace pmx case MorphType::AdditionalUV2: case MorphType::AdditionalUV3: case MorphType::AdditionalUV4: - uv_offsets = std::make_unique(this->offset_count); + uv_offsets = mmd::make_unique(this->offset_count); for (int i = 0; i < offset_count; i++) { uv_offsets[i].Read(stream, setting); @@ -386,7 +386,7 @@ namespace pmx this->frame_english_name = ReadString(stream, setting->encoding); stream->read((char*) &this->frame_flag, sizeof(uint8_t)); stream->read((char*) &this->element_count, sizeof(int)); - this->elements = std::make_unique(this->element_count); + this->elements = mmd::make_unique(this->element_count); for (int i = 0; i < this->element_count; i++) { this->elements[i].Read(stream, setting); @@ -505,7 +505,7 @@ namespace pmx // 頂点 stream->read((char*) &vertex_count, sizeof(int)); - this->vertices = std::make_unique(vertex_count); + this->vertices = mmd::make_unique(vertex_count); for (int i = 0; i < vertex_count; i++) { vertices[i].Read(stream, &setting); @@ -513,7 +513,7 @@ namespace pmx // 面 stream->read((char*) &index_count, sizeof(int)); - this->indices = std::make_unique(index_count); + this->indices = mmd::make_unique(index_count); for (int i = 0; i < index_count; i++) { this->indices[i] = ReadIndex(stream, setting.vertex_index_size); @@ -521,7 +521,7 @@ namespace pmx // テクスチャ stream->read((char*) &texture_count, sizeof(int)); - this->textures = std::make_unique(texture_count); + this->textures = mmd::make_unique(texture_count); for (int i = 0; i < texture_count; i++) { this->textures[i] = ReadString(stream, setting.encoding); @@ -529,7 +529,7 @@ namespace pmx // マテリアル stream->read((char*) &material_count, sizeof(int)); - this->materials = std::make_unique(material_count); + this->materials = mmd::make_unique(material_count); for (int i = 0; i < material_count; i++) { this->materials[i].Read(stream, &setting); @@ -537,7 +537,7 @@ namespace pmx // ボーン stream->read((char*) &this->bone_count, sizeof(int)); - this->bones = std::make_unique(this->bone_count); + this->bones = mmd::make_unique(this->bone_count); for (int i = 0; i < this->bone_count; i++) { this->bones[i].Read(stream, &setting); @@ -545,7 +545,7 @@ namespace pmx // モーフ stream->read((char*) &this->morph_count, sizeof(int)); - this->morphs = std::make_unique(this->morph_count); + this->morphs = mmd::make_unique(this->morph_count); for (int i = 0; i < this->morph_count; i++) { this->morphs[i].Read(stream, &setting); @@ -553,7 +553,7 @@ namespace pmx // 表示枠 stream->read((char*) &this->frame_count, sizeof(int)); - this->frames = std::make_unique(this->frame_count); + this->frames = mmd::make_unique(this->frame_count); for (int i = 0; i < this->frame_count; i++) { this->frames[i].Read(stream, &setting); @@ -561,7 +561,7 @@ namespace pmx // 剛体 stream->read((char*) &this->rigid_body_count, sizeof(int)); - this->rigid_bodies = std::make_unique(this->rigid_body_count); + this->rigid_bodies = mmd::make_unique(this->rigid_body_count); for (int i = 0; i < this->rigid_body_count; i++) { this->rigid_bodies[i].Read(stream, &setting); @@ -569,7 +569,7 @@ namespace pmx // ジョイント stream->read((char*) &this->joint_count, sizeof(int)); - this->joints = std::make_unique(this->joint_count); + this->joints = mmd::make_unique(this->joint_count); for (int i = 0; i < this->joint_count; i++) { this->joints[i].Read(stream, &setting); @@ -579,7 +579,7 @@ namespace pmx //if (this->version == 2.1f) //{ // stream->read((char*) &this->soft_body_count, sizeof(int)); - // this->soft_bodies = std::make_unique(this->soft_body_count); + // this->soft_bodies = mmd::make_unique(this->soft_body_count); // for (int i = 0; i < this->soft_body_count; i++) // { // this->soft_bodies[i].Read(stream, &setting); @@ -601,7 +601,7 @@ namespace pmx //std::unique_ptr ReadFromStream(std::istream *stream) //{ - // auto pmx = std::make_unique(); + // auto pmx = mmd::make_unique(); // pmx->Read(stream); // return pmx; //} diff --git a/code/MMDVmdParser.h b/code/MMDVmdParser.h index 889a579db..380a10c5d 100644 --- a/code/MMDVmdParser.h +++ b/code/MMDVmdParser.h @@ -220,7 +220,7 @@ namespace vmd { char buffer[30]; - auto result = std::make_unique(); + auto result = mmd::make_unique(); // magic and version stream->read((char*) buffer, 30); @@ -364,4 +364,4 @@ namespace vmd return true; } }; -} \ No newline at end of file +} From 7bd3242563a093f804da219a437c27cfcaf010f9 Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Mon, 27 Mar 2017 22:49:58 +0800 Subject: [PATCH 22/23] comment out override specifier --- code/MMDPmxParser.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/code/MMDPmxParser.h b/code/MMDPmxParser.h index d161eb6b2..7cb94b9b6 100644 --- a/code/MMDPmxParser.h +++ b/code/MMDPmxParser.h @@ -390,7 +390,7 @@ namespace pmx } int vertex_index; float position_offset[3]; - void Read(std::istream *stream, PmxSetting *setting) override; + void Read(std::istream *stream, PmxSetting *setting); //override; }; class PmxMorphUVOffset : public PmxMorphOffset @@ -405,7 +405,7 @@ namespace pmx } int vertex_index; float uv_offset[4]; - void Read(std::istream *stream, PmxSetting *setting) override; + void Read(std::istream *stream, PmxSetting *setting); //override; }; class PmxMorphBoneOffset : public PmxMorphOffset @@ -424,7 +424,7 @@ namespace pmx int bone_index; float translation[3]; float rotation[4]; - void Read(std::istream *stream, PmxSetting *setting) override; + void Read(std::istream *stream, PmxSetting *setting); //override; }; class PmxMorphMaterialOffset : public PmxMorphOffset @@ -457,7 +457,7 @@ namespace pmx float texture_argb[4]; float sphere_texture_argb[4]; float toon_texture_argb[4]; - void Read(std::istream *stream, PmxSetting *setting) override; + void Read(std::istream *stream, PmxSetting *setting); //override; }; class PmxMorphGroupOffset : public PmxMorphOffset @@ -469,7 +469,7 @@ namespace pmx {} int morph_index; float morph_weight; - void Read(std::istream *stream, PmxSetting *setting) override; + void Read(std::istream *stream, PmxSetting *setting); //override; }; class PmxMorphFlipOffset : public PmxMorphOffset @@ -481,7 +481,7 @@ namespace pmx {} int morph_index; float morph_value; - void Read(std::istream *stream, PmxSetting *setting) override; + void Read(std::istream *stream, PmxSetting *setting); //override; }; class PmxMorphImplusOffset : public PmxMorphOffset @@ -500,7 +500,7 @@ namespace pmx uint8_t is_local; float velocity[3]; float angular_torque[3]; - void Read(std::istream *stream, PmxSetting *setting) override; + void Read(std::istream *stream, PmxSetting *setting); //override; }; /// モーフ From 0a8966a3e62947231a94e36129742733eb425d2c Mon Sep 17 00:00:00 2001 From: aoowweenn Date: Mon, 27 Mar 2017 23:17:48 +0800 Subject: [PATCH 23/23] add .pmx sample --- test/models-nonbsd/MMD/Alicia_blade.pmx | Bin 0 -> 319682 bytes test/models-nonbsd/MMD/readme.txt | 49 ++++++++++++++++++++++++ test/unit/utPMXImporter.cpp | 2 +- 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 test/models-nonbsd/MMD/Alicia_blade.pmx create mode 100644 test/models-nonbsd/MMD/readme.txt diff --git a/test/models-nonbsd/MMD/Alicia_blade.pmx b/test/models-nonbsd/MMD/Alicia_blade.pmx new file mode 100644 index 0000000000000000000000000000000000000000..cdd7709cc9427a0be6bf01b096a29e93a6533981 GIT binary patch literal 319682 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%0D#QL7 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 literal 0 HcmV?d00001 diff --git a/test/models-nonbsd/MMD/readme.txt b/test/models-nonbsd/MMD/readme.txt new file mode 100644 index 000000000..d8c882420 --- /dev/null +++ b/test/models-nonbsd/MMD/readme.txt @@ -0,0 +1,49 @@ +———————————————————————— +ニコニ立体公式キャラクター「ニコニ立体ちゃん」 +http://3d.nicovideo.jp/alicia/ +version 4 +©dwango, inc. All rights reserved. +———————————————————————— + + +この度は、ニコニ立体公式キャラクター「ニコニ立体ちゃん」をダウンロードいただきましてありがとうございます。 + +◯ 内容物 +1. MikuMikuDance(MMD)用ファイル2種 (ニコニ立体ちゃん本体・ビーム彫刻刀) +2. MMD用モーションファイル16種 (ニコファーレのモーションキャプチャーデバイスを用いて収録) +2. FBXファイル2種 (MMD用データ、Unity用データ) + MAXファイル(オリジナルデータ) +3. Unity Package1種 +4. ニコニ立体ちゃん壁紙1種 + + +◯よくある質問 +* 「ニコニ立体ちゃん」と「アリシア・ソリッド」、正式名称はどちら? +キャラクターの本名はアリシア・ソリッドですが、クレジット等での表記はニコニ立体ちゃんが正式名称となります。 +作品発表の際には「ニコニ立体ちゃん」タグをご活用ください。 + +* ニコニ立体ちゃんを用いた、年齢制限のある二次創作物の公開は可能なのか? +利用規約にある禁止事項に抵触しない限りにおいて可能です。 + + +◯ 利用規約抜粋 +必ず利用規約( http://3d.nicovideo.jp/alicia/rule.html )をご確認ください。 +利用規約の改訂などによって下記抜粋と利用規約の内容が異なる場合は利用規約が優先されます。 + +* 非営利、営利に関わらず個人(同人サークルなど、法人を除く団体を含む)の利用が可能です。 +* 二次創作物について株式会社ドワンゴ(以下、当社)のクレジットを表記する必要はありません。 +* 改変物を配布することができます。 +* niconico内でキャラクターやモーションを利用した作品を投稿する場合は、コンテンツツリーの親作品にキャラクター( http://3d.nicovideo.jp/works/td14712 )を登録するよう努めるものとします。 + + +◯禁止事項 +* 第三者の知的財産権その他一切の権利及び名誉を侵害しないこと。 +* 当社(当社が提供するサービス等を含む)及び当社キャラクターの名誉・品位傷つける行為をしないこと。 +* 公序良俗に反する行為や目的、暴力的な表現、反社会的な行為や目的、特定の信条や宗教、政治的発言のため利用しないこと。 +* 当社の公式商品であるかのような誤解を招く利用をしないこと。 +* その他、当社が不適切と判断する行為に利用しないこと。 + + +◯ クレジット +企画: 株式会社ドワンゴ +キャラクターデザイン: 黒星紅白 +モデリング: 雨刻 \ No newline at end of file diff --git a/test/unit/utPMXImporter.cpp b/test/unit/utPMXImporter.cpp index cf70fd65d..725279e47 100644 --- a/test/unit/utPMXImporter.cpp +++ b/test/unit/utPMXImporter.cpp @@ -52,7 +52,7 @@ class utPMXImporter : public AbstractImportExportBase { public: virtual bool importerTest() { Assimp::Importer importer; - const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/../models-nonbsd/MMD/kawakaze.pmx", 0 ); + const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/../models-nonbsd/MMD/Alicia_blade.pmx", 0 ); return nullptr != scene; } };