Browse Source

Merge branch 'master' into umw_dev

Kim Kulling 7 years ago
parent
commit
f096843c45

+ 57 - 62
CMakeLists.txt

@@ -138,8 +138,7 @@ SET (PROJECT_VERSION "${ASSIMP_VERSION}")
 
 SET( ASSIMP_PACKAGE_VERSION "0" CACHE STRING "the package-specific version used for uploading the sources" )
 
-# Needed for openddl_parser config, no use of c++11 at this moment
-ADD_DEFINITIONS( -DOPENDDL_NO_USE_CPP11 )
+# Enable C++1 globally
 set_property( GLOBAL PROPERTY CXX_STANDARD 11 )
 
 # Get the current working branch
@@ -161,7 +160,7 @@ EXECUTE_PROCESS(
 )
 
 IF(NOT GIT_COMMIT_HASH)
-    SET(GIT_COMMIT_HASH 0)
+  SET(GIT_COMMIT_HASH 0)
 ENDIF(NOT GIT_COMMIT_HASH)
 
 IF(ASSIMP_DOUBLE_PRECISION)
@@ -179,10 +178,10 @@ CONFIGURE_FILE(
 )
 
 INCLUDE_DIRECTORIES(
-    ./
-	include
-    ${CMAKE_CURRENT_BINARY_DIR}
-    ${CMAKE_CURRENT_BINARY_DIR}/include
+  ./
+  include
+  ${CMAKE_CURRENT_BINARY_DIR}
+  ${CMAKE_CURRENT_BINARY_DIR}/include
 )
 
 LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake-modules" )
@@ -192,14 +191,6 @@ SET(CPACK_COMPONENTS_ALL assimp-bin ${LIBASSIMP_COMPONENT} ${LIBASSIMP-DEV_COMPO
 SET(ASSIMP_LIBRARY_SUFFIX "" CACHE STRING "Suffix to append to library names")
 
 IF( UNIX )
-  # Ensure that we do not run into issues like http://www.tcm.phy.cam.ac.uk/sw/inodes64.html on 32 bit linux
-  IF( ${OPERATING_SYSTEM} MATCHES "Android")
-  ELSE()
-    IF ( CMAKE_SIZEOF_VOID_P EQUAL 4) # only necessary for 32-bit linux
-      #ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 )
-    ENDIF()
-  ENDIF()
-
   # Use GNUInstallDirs for Unix predefined directories
   INCLUDE(GNUInstallDirs)
 ENDIF( UNIX )
@@ -212,13 +203,13 @@ IF ((CMAKE_C_COMPILER_ID MATCHES "GNU") AND NOT CMAKE_COMPILER_IS_MINGW)
   SET(LIBSTDC++_LIBRARIES -lstdc++)
 ELSEIF(MSVC)
   # enable multi-core compilation with MSVC
-  add_compile_options(/MP)
-  if("${CMAKE_GENERATOR}" MATCHES "(Win64|IA64)")
-    add_compile_options( /bigobj )
-  endif()
+  ADD_COMPILE_OPTIONS(/MP)
+  IF("${CMAKE_GENERATOR}" MATCHES "(Win64|IA64)")
+    ADD_COMPILE_OPTIONS( /bigobj )
+  ENDIF()
   # disable "elements of array '' will be default initialized" warning on MSVC2013
   IF(MSVC12)
-    add_compile_options(/wd4351)
+    ADD_COMPILE_OPTIONS(/wd4351)
   ENDIF()
 ELSEIF ( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" )
   SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fvisibility=hidden -fPIC -Wall -Wno-long-long -std=c++11" )
@@ -229,39 +220,39 @@ ELSEIF( CMAKE_COMPILER_IS_MINGW )
   ADD_DEFINITIONS( -U__STRICT_ANSI__ )
 ENDIF()
 
-if (IOS)
-    set(CMAKE_C_FLAGS   "${CMAKE_C_FLAGS}   -fembed-bitcode -O3")
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -O3")
-endif()
+IF (IOS)
+  SET(CMAKE_C_FLAGS   "${CMAKE_C_FLAGS}   -fembed-bitcode -O3")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -O3")
+ENDIF()
 
-if (ASSIMP_COVERALLS)
-    MESSAGE(STATUS "Coveralls enabled")
-    INCLUDE(Coveralls)
-    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
-    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
-endif()
+IF (ASSIMP_COVERALLS)
+  MESSAGE(STATUS "Coveralls enabled")
+  INCLUDE(Coveralls)
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
+  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
+ENDIF()
 
-if (ASSIMP_WERROR)
+IF (ASSIMP_WERROR)
   MESSAGE(STATUS "Treating warnings as errors")
   IF (MSVC)
-    add_compile_options(/WX)
+    ADD_COMPILE_OPTIONS(/WX)
   ELSE()
     SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
     SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror")
   ENDIF()
-endif()
+ENDIF()
 
-if (ASSIMP_ASAN)
-    MESSAGE(STATUS "AddressSanitizer enabled")
-    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
-    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
-endif()
+IF (ASSIMP_ASAN)
+  MESSAGE(STATUS "AddressSanitizer enabled")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
+  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
+ENDIF()
 
-if (ASSIMP_UBSAN)
-    MESSAGE(STATUS "Undefined Behavior sanitizer enabled")
-    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all")
-    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all")
-endif()
+IF (ASSIMP_UBSAN)
+  MESSAGE(STATUS "Undefined Behavior sanitizer enabled")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all")
+  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all")
+ENDIF()
 
 INCLUDE (FindPkgMacros)
 INCLUDE (PrecompiledHeader)
@@ -294,42 +285,42 @@ ENDIF()
 IF (NOT TARGET uninstall)
   # add make uninstall capability
   CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/cmake-modules/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY)
-  add_custom_target(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
+  ADD_CUSTOM_TARGET(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
 ENDIF()
 
 # cmake configuration files
 CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimp-config.cmake.in"         "${CMAKE_CURRENT_BINARY_DIR}/assimp-config.cmake" @ONLY IMMEDIATE)
 CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimp-config-version.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/assimp-config-version.cmake" @ONLY IMMEDIATE)
-install(FILES "${CMAKE_CURRENT_BINARY_DIR}/assimp-config.cmake"             "${CMAKE_CURRENT_BINARY_DIR}/assimp-config-version.cmake" DESTINATION "${ASSIMP_LIB_INSTALL_DIR}/cmake/assimp-${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}" COMPONENT ${LIBASSIMP-DEV_COMPONENT})
+INSTALL(FILES "${CMAKE_CURRENT_BINARY_DIR}/assimp-config.cmake"             "${CMAKE_CURRENT_BINARY_DIR}/assimp-config-version.cmake" DESTINATION "${ASSIMP_LIB_INSTALL_DIR}/cmake/assimp-${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}" COMPONENT ${LIBASSIMP-DEV_COMPONENT})
 
 FIND_PACKAGE( DirectX )
 
 IF( BUILD_DOCS )
-    add_subdirectory(doc)
+  ADD_SUBDIRECTORY(doc)
 ENDIF( BUILD_DOCS )
 
 # Look for system installed irrXML
 IF ( SYSTEM_IRRXML )
-    find_package( IrrXML REQUIRED )
+  FIND_PACKAGE( IrrXML REQUIRED )
 ENDIF( SYSTEM_IRRXML )
 
 # Search for external dependencies, and build them from source if not found
 # Search for zlib
 IF ( NOT ASSIMP_BUILD_ZLIB )
-    find_package(ZLIB)
+  FIND_PACKAGE(ZLIB)
 ENDIF( NOT ASSIMP_BUILD_ZLIB )
 
 IF( NOT ZLIB_FOUND )
-  message(STATUS "compiling zlib from souces")
+  MESSAGE(STATUS "compiling zlib from souces")
   INCLUDE(CheckIncludeFile)
   INCLUDE(CheckTypeSize)
   INCLUDE(CheckFunctionExists)
   # compile from sources
-  add_subdirectory(contrib/zlib)
+  ADD_SUBDIRECTORY(contrib/zlib)
   SET(ZLIB_FOUND 1)
   SET(ZLIB_LIBRARIES zlibstatic)
   SET(ZLIB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/contrib/zlib ${CMAKE_CURRENT_BINARY_DIR}/contrib/zlib)
-else(NOT ZLIB_FOUND)
+ELSE(NOT ZLIB_FOUND)
   ADD_DEFINITIONS(-DASSIMP_BUILD_NO_OWN_ZLIB)
   SET(ZLIB_LIBRARIES_LINKED -lz)
 ENDIF(NOT ZLIB_FOUND)
@@ -371,7 +362,9 @@ IF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER)
     SET(C4D_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/contrib/Melange/includes")
 
     # pick the correct prebuilt library
-    IF(MSVC14)
+    IF(MSVC15)
+      SET(C4D_LIB_POSTFIX "_2017")
+    ELSEIF(MSVC14)
       SET(C4D_LIB_POSTFIX "_2015")
     ELSEIF(MSVC12)
       SET(C4D_LIB_POSTFIX "_2013")
@@ -412,7 +405,7 @@ ADD_SUBDIRECTORY(contrib)
 ADD_SUBDIRECTORY( code/ )
 IF ( ASSIMP_BUILD_ASSIMP_TOOLS )
   IF ( WIN32 AND DirectX_D3DX9_LIBRARY )
-    option ( ASSIMP_BUILD_ASSIMP_VIEW "If the Assimp view tool is built. (requires DirectX)" ${DirectX_FOUND} )
+    OPTION ( ASSIMP_BUILD_ASSIMP_VIEW "If the Assimp view tool is built. (requires DirectX)" ${DirectX_FOUND} )
     IF ( ASSIMP_BUILD_ASSIMP_VIEW )
       ADD_SUBDIRECTORY( tools/assimp_view/ )
     ENDIF ( ASSIMP_BUILD_ASSIMP_VIEW )
@@ -476,8 +469,8 @@ IF(CMAKE_CPACK_COMMAND AND UNIX AND ASSIMP_OPT_BUILD_PACKAGES)
   SET(CPACK_PACKAGE_INSTALL_DIRECTORY       "assimp${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}")
   SET(CPACK_RESOURCE_FILE_LICENSE           "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
 
-  string(TOUPPER ${LIBASSIMP_COMPONENT}     "LIBASSIMP_COMPONENT_UPPER")
-  string(TOUPPER ${LIBASSIMP-DEV_COMPONENT} "LIBASSIMP-DEV_COMPONENT_UPPER")
+  STRING(TOUPPER ${LIBASSIMP_COMPONENT}     "LIBASSIMP_COMPONENT_UPPER")
+  STRING(TOUPPER ${LIBASSIMP-DEV_COMPONENT} "LIBASSIMP-DEV_COMPONENT_UPPER")
 
   SET(CPACK_COMPONENT_ASSIMP-BIN_DISPLAY_NAME                       "tools")
   SET(CPACK_COMPONENT_ASSIMP-BIN_DEPENDS                            "${LIBASSIMP_COMPONENT}" )
@@ -507,8 +500,8 @@ IF(CMAKE_CPACK_COMMAND AND UNIX AND ASSIMP_OPT_BUILD_PACKAGES)
     SET(CPACK_DEBIAN_DISTRIBUTION_RELEASES lucid maverick natty oneiric precise CACHE STRING "Release code-names of the distrubiton release")
   ENDIF()
   SET(DPUT_HOST "" CACHE STRING "PPA repository to upload the debian sources")
-  include(CPack)
-  include(DebSourcePPA)
+  INCLUDE(CPack)
+  INCLUDE(DebSourcePPA)
 ENDIF()
 
 if(WIN32)
@@ -520,14 +513,16 @@ if(WIN32)
     SET(LIB_DIR "${PROJECT_SOURCE_DIR}/lib32/")
   ENDIF()
 
-  if(MSVC12)
+  IF(MSVC12)
     SET(ASSIMP_MSVC_VERSION "vc120")
-  elseif(MSVC14)
+  ELSEIF(MSVC14)
     SET(ASSIMP_MSVC_VERSION "vc140")
+  ELSEIF(MSVC15)
+    SET(ASSIMP_MSVC_VERSION "vc141")
   ENDIF(MSVC12)
 
-  if(MSVC12 OR MSVC14)
-    add_custom_target(UpdateAssimpLibsDebugSymbolsAndDLLs COMMENT "Copying Assimp Libraries ..." VERBATIM)
+  IF(MSVC12 OR MSVC14 OR MSVC15 )
+    ADD_CUSTOM_TARGET(UpdateAssimpLibsDebugSymbolsAndDLLs COMMENT "Copying Assimp Libraries ..." VERBATIM)
     IF(CMAKE_GENERATOR MATCHES "^Visual Studio")
       ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.dll	${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.dll VERBATIM)
       ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.exp	${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.exp VERBATIM)
@@ -548,5 +543,5 @@ if(WIN32)
       ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb VERBATIM)
       ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb VERBATIM)
     ENDIF()
-  ENDIF(MSVC12 OR MSVC14)
+  ENDIF(MSVC12 OR MSVC14 OR MSVC15 )
 ENDIF (WIN32)

+ 20 - 20
Readme.md

@@ -35,29 +35,29 @@ Please check our Wiki as well: https://github.com/assimp/assimp/wiki
 __Importers__:
 
 - 3D
-- 3DS
-- 3MF
+- [3DS](https://en.wikipedia.org/wiki/.3ds)
+- [3MF](https://en.wikipedia.org/wiki/3D_Manufacturing_Format)
 - AC
-- AC3D
+- [AC3D](https://en.wikipedia.org/wiki/AC3D)
 - ACC
 - AMJ
 - ASE
 - ASK
 - B3D
-- BLEND (Blender)
-- BVH
-- COB
+- [BLEND](https://en.wikipedia.org/wiki/.blend_(file_format))
+- [BVH](https://en.wikipedia.org/wiki/Biovision_Hierarchy)
 - CMS
-- DAE/Collada
-- DXF
+- COB
+- [DAE/Collada](https://en.wikipedia.org/wiki/COLLADA)
+- [DXF](https://en.wikipedia.org/wiki/AutoCAD_DXF)
 - ENFF
-- FBX
-- glTF 1.0 + GLB
-- glTF 2.0
+- [FBX](https://en.wikipedia.org/wiki/FBX)
+- [glTF 1.0](https://en.wikipedia.org/wiki/GlTF#glTF_1.0) + GLB
+- [glTF 2.0](https://en.wikipedia.org/wiki/GlTF#glTF_2.0)
 - HMB
 - IFC-STEP
 - IRR / IRRMESH
-- LWO
+- [LWO](https://en.wikipedia.org/wiki/LightWave_3D)
 - LWS
 - LXO
 - MD2
@@ -70,10 +70,10 @@ __Importers__:
 - MS3D
 - NDO
 - NFF
-- OBJ
-- OFF
-- OGEX
-- PLY
+- [OBJ](https://en.wikipedia.org/wiki/Wavefront_.obj_file)
+- [OFF](https://en.wikipedia.org/wiki/OFF_(file_format))
+- [OGEX](https://en.wikipedia.org/wiki/Open_Game_Engine_Exchange)
+- [PLY](https://en.wikipedia.org/wiki/PLY_(file_format))
 - PMX
 - PRJ
 - Q3O
@@ -82,19 +82,19 @@ __Importers__:
 - SCN
 - SIB
 - SMD
-- STL
-- STP
+- [STP](https://en.wikipedia.org/wiki/ISO_10303-21)
+- [STL](https://en.wikipedia.org/wiki/STL_(file_format))
 - TER
 - UC
 - VTA
 - X
-- X3D
+- [X3D](https://en.wikipedia.org/wiki/X3D)
 - XGL
 - ZGL
 
 Additionally, some formats are supported by dependency on non-free code or external SDKs (not built by default):
 
-- C4D (https://github.com/assimp/assimp/wiki/Cinema4D-&-Melange)
+- [C4D](https://en.wikipedia.org/wiki/Cinema_4D) (https://github.com/assimp/assimp/wiki/Cinema4D-&-Melange)
 
 __Exporters__:
 

+ 2 - 21
assimp-config.cmake.in

@@ -34,36 +34,18 @@ if( MSVC )
   else()
     set(MSVC_PREFIX "vc150")
   endif()
-  set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" FORCE)
+  set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" )
 else()
-  set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the openrave libraries" FORCE)
+  set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the openrave libraries" )
 endif()
 
 set( ASSIMP_CXX_FLAGS ) # dynamically linked library
-if( WIN32 )
-  # for visual studio linking, most of the time boost dlls will be used
-  set( ASSIMP_CXX_FLAGS " -DBOOST_ALL_DYN_LINK -DBOOST_ALL_NO_LIB")
-endif()
 set( ASSIMP_LINK_FLAGS "" )
 set( ASSIMP_LIBRARY_DIRS "${ASSIMP_ROOT_DIR}/@ASSIMP_LIB_INSTALL_DIR@")
 set( ASSIMP_INCLUDE_DIRS "${ASSIMP_ROOT_DIR}/@ASSIMP_INCLUDE_INSTALL_DIR@")
 set( ASSIMP_LIBRARIES assimp${ASSIMP_LIBRARY_SUFFIX})
 set( ASSIMP_LIBRARIES ${ASSIMP_LIBRARIES}@CMAKE_DEBUG_POSTFIX@)
 
-# search for the boost version assimp was compiled with
-#set(Boost_USE_MULTITHREAD ON)
-#set(Boost_USE_STATIC_LIBS OFF)
-#set(Boost_USE_STATIC_RUNTIME OFF)
-#find_package(Boost ${ASSIMP_Boost_VERSION} EXACT COMPONENTS thread date_time)
-#if(Boost_VERSION AND NOT "${Boost_VERSION}" STREQUAL "0")
-#	set( ASSIMP_INCLUDE_DIRS "${ASSIMP_INCLUDE_DIRS}" ${Boost_INCLUDE_DIRS})
-#else(Boost_VERSION AND NOT "${Boost_VERSION}" STREQUAL "0")
-#	message(WARNING "Failed to find Boost ${ASSIMP_Boost_VERSION} necessary for assimp")
-#endif(Boost_VERSION AND NOT "${Boost_VERSION}" STREQUAL "0")
-
-# the boost version assimp was compiled with
-set( ASSIMP_Boost_VERSION "@Boost_MAJOR_VERSION@.@Boost_MINOR_VERSION@")
-
 # for compatibility with pkg-config
 set(ASSIMP_CFLAGS_OTHER "${ASSIMP_CXX_FLAGS}")
 set(ASSIMP_LDFLAGS_OTHER "${ASSIMP_LINK_FLAGS}")
@@ -74,7 +56,6 @@ MARK_AS_ADVANCED(
   ASSIMP_LINK_FLAGS
   ASSIMP_INCLUDE_DIRS
   ASSIMP_LIBRARIES
-  ASSIMP_Boost_VERSION
   ASSIMP_CFLAGS_OTHER
   ASSIMP_LDFLAGS_OTHER
   ASSIMP_LIBRARY_SUFFIX

+ 2 - 2
code/3DSLoader.cpp

@@ -71,7 +71,7 @@ static const aiImporterDesc desc = {
     0,
     0,
     0,
-    "3ds prj"
+    "3ds prj 3DS PRJ"
 };
 
 
@@ -127,7 +127,7 @@ Discreet3DSImporter::~Discreet3DSImporter() {
 // Returns whether the class can handle the format of the given file.
 bool Discreet3DSImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const {
     std::string extension = GetExtension(pFile);
-    if(extension == "3ds" || extension == "prj" ) {
+    if(extension == "3ds" || extension == "3DS" || extension == "prj"|| extension == "PRJ" ) {
         return true;
     }
 

+ 2 - 0
code/CMakeLists.txt

@@ -186,6 +186,8 @@ SET( Common_SRCS
   Bitmap.cpp
   Version.cpp
   CreateAnimMesh.cpp
+  simd.h
+  simd.cpp
 )
 SOURCE_GROUP(Common FILES ${Common_SRCS})
 

+ 6 - 3
code/COBLoader.cpp

@@ -47,11 +47,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #ifndef ASSIMP_BUILD_NO_COB_IMPORTER
 #include "COBLoader.h"
 #include "COBScene.h"
-
+#include "ConvertToLHProcess.h"
 #include <assimp/StreamReader.h>
 #include <assimp/ParsingUtils.h>
 #include <assimp/fast_atof.h>
-
 #include <assimp/LineSplitter.h>
 #include <assimp/TinyFormatter.h>
 #include <memory>
@@ -105,7 +104,7 @@ COBImporter::~COBImporter()
 bool COBImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
     const std::string& extension = GetExtension(pFile);
-    if (extension == "cob" || extension == "scn") {
+    if (extension == "cob" || extension == "scn" || extension == "COB" || extension == "SCN") {
         return true;
     }
 
@@ -225,6 +224,9 @@ void COBImporter::InternReadFile( const std::string& pFile,
     }
 
     pScene->mRootNode = BuildNodes(*root.get(),scene,pScene);
+	//flip normals after import
+    FlipWindingOrderProcess flip;
+    flip.Execute( pScene );
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -1299,3 +1301,4 @@ void COBImporter::ReadUnit_Binary(COB::Scene& out, StreamReaderLE& reader, const
 
 
 #endif
+

+ 1 - 1
code/CalcTangentsProcess.cpp

@@ -190,7 +190,7 @@ bool CalcTangentsProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
         float tx = meshTex[p2].x - meshTex[p0].x, ty = meshTex[p2].y - meshTex[p0].y;
         float dirCorrection = (tx * sy - ty * sx) < 0.0f ? -1.0f : 1.0f;
         // when t1, t2, t3 in same position in UV space, just use default UV direction.
-        if ( 0 == sx && 0 ==sy && 0 == tx && 0 == ty ) {
+        if (  sx * ty == sy * tx ) {
             sx = 0.0; sy = 1.0;
             tx = 1.0; ty = 0.0;
         }

+ 2 - 2
code/Exporter.cpp

@@ -98,7 +98,7 @@ void ExportSceneAssbin(const char*, IOSystem*, const aiScene*, const ExportPrope
 void ExportSceneAssxml(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneX3D(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneFBX(const char*, IOSystem*, const aiScene*, const ExportProperties*);
-//void ExportSceneFBXA(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneFBXA(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportScene3MF( const char*, IOSystem*, const aiScene*, const ExportProperties* );
 
 // ------------------------------------------------------------------------------------------------
@@ -173,7 +173,7 @@ Exporter::ExportFormatEntry gExporters[] =
 
 #ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
     Exporter::ExportFormatEntry( "fbx", "Autodesk FBX (binary)", "fbx", &ExportSceneFBX, 0 ),
-    //Exporter::ExportFormatEntry( "fbxa", "Autodesk FBX (ascii)", "fbx", &ExportSceneFBXA, 0 ),
+    Exporter::ExportFormatEntry( "fbxa", "Autodesk FBX (ascii)", "fbx", &ExportSceneFBXA, 0 ),
 #endif
 
 #ifndef ASSIMP_BUILD_NO_3MF_EXPORTER

+ 56 - 88
code/FBXConverter.cpp

@@ -61,6 +61,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <memory>
 #include <iterator>
 #include <vector>
+#include <sstream>
+#include <iomanip>
 
 namespace Assimp {
 namespace FBX {
@@ -133,9 +135,7 @@ void Converter::ConvertRootNode() {
     ConvertNodes( 0L, *out->mRootNode );
 }
 
-
-void Converter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform )
-{
+void Converter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform ) {
     const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced( id, "Model" );
 
     std::vector<aiNode*> nodes;
@@ -153,14 +153,14 @@ void Converter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& pa
             }
 
             const Object* const object = con->SourceObject();
-            if ( !object ) {
+            if ( nullptr == object ) {
                 FBXImporter::LogWarn( "failed to convert source object for Model link" );
                 continue;
             }
 
             const Model* const model = dynamic_cast<const Model*>( object );
 
-            if ( model ) {
+            if ( nullptr != model ) {
                 nodes_chain.clear();
                 post_nodes_chain.clear();
 
@@ -174,7 +174,7 @@ void Converter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& pa
 
                 ai_assert( nodes_chain.size() );
 
-                const std::string& original_name = FixNodeName( model->Name() );
+                std::string original_name = FixNodeName( model->Name() );
 
                 // check if any of the nodes in the chain has the name the fbx node
                 // is supposed to have. If there is none, add another node to
@@ -189,7 +189,15 @@ void Converter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& pa
                 }
 
                 if ( !name_carrier ) {
+                    NodeNameCache::const_iterator it( std::find( mNodeNames.begin(), mNodeNames.end(), original_name ) );
+                    if ( it != mNodeNames.end() ) {
+                        original_name = original_name + std::string( "001" );
+                    }
+
+                    mNodeNames.push_back( original_name );
                     nodes_chain.push_back( new aiNode( original_name ) );
+                } else {
+                    original_name = nodes_chain.back()->mName.C_Str();
                 }
 
                 //setup metadata on newest node
@@ -250,11 +258,11 @@ void Converter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& pa
                 ConvertNodes( model->ID(), *last_parent, new_abs_transform );
 
                 if ( doc.Settings().readLights ) {
-                    ConvertLights( *model );
+                    ConvertLights( *model, original_name );
                 }
 
                 if ( doc.Settings().readCameras ) {
-                    ConvertCameras( *model );
+                    ConvertCameras( *model, original_name );
                 }
 
                 nodes.push_back( nodes_chain.front() );
@@ -278,34 +286,31 @@ void Converter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& pa
 }
 
 
-void Converter::ConvertLights( const Model& model )
-{
+void Converter::ConvertLights( const Model& model, const std::string &orig_name ) {
     const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
     for( const NodeAttribute* attr : node_attrs ) {
         const Light* const light = dynamic_cast<const Light*>( attr );
         if ( light ) {
-            ConvertLight( model, *light );
+            ConvertLight( *light, orig_name );
         }
     }
 }
 
-void Converter::ConvertCameras( const Model& model )
-{
+void Converter::ConvertCameras( const Model& model, const std::string &orig_name ) {
     const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
     for( const NodeAttribute* attr : node_attrs ) {
         const Camera* const cam = dynamic_cast<const Camera*>( attr );
         if ( cam ) {
-            ConvertCamera( model, *cam );
+            ConvertCamera( *cam, orig_name );
         }
     }
 }
 
-void Converter::ConvertLight( const Model& model, const Light& light )
-{
+void Converter::ConvertLight( const Light& light, const std::string &orig_name ) {
     lights.push_back( new aiLight() );
     aiLight* const out_light = lights.back();
 
-    out_light->mName.Set( FixNodeName( model.Name() ) );
+    out_light->mName.Set( orig_name );
 
     const float intensity = light.Intensity() / 100.0f;
     const aiVector3D& col = light.Color();
@@ -378,12 +383,12 @@ void Converter::ConvertLight( const Model& model, const Light& light )
     }
 }
 
-void Converter::ConvertCamera( const Model& model, const Camera& cam )
+void Converter::ConvertCamera( const Camera& cam, const std::string &orig_name )
 {
     cameras.push_back( new aiCamera() );
     aiCamera* const out_camera = cameras.back();
 
-    out_camera->mName.Set( FixNodeName( model.Name() ) );
+    out_camera->mName.Set( orig_name );
 
     out_camera->mAspect = cam.AspectWidth() / cam.AspectHeight();
 
@@ -397,6 +402,31 @@ void Converter::ConvertCamera( const Model& model, const Camera& cam )
     out_camera->mClipPlaneFar = cam.FarPlane();
 }
 
+static bool HasName( NodeNameCache &cache, const std::string &name ) {
+    NodeNameCache::const_iterator it( std::find( cache.begin(), cache.end(), name ) );
+    return it != cache.end();
+
+}
+void Converter::GetUniqueName( const std::string &name, std::string uniqueName ) {
+    if ( !HasName( mNodeNames, name ) ) {
+        uniqueName = name;
+        return;
+    }
+
+    int i( 0 );
+    std::string newName;
+    while ( HasName( mNodeNames, newName ) ) {
+        ++i;
+        newName.clear();
+        newName += name;
+        std::stringstream ext;
+        ext << std::setfill( '0' ) << std::setw( 3 ) << i;
+        newName += ext.str();
+    }
+    uniqueName = newName;
+    mNodeNames.push_back( uniqueName );
+}
+
 
 const char* Converter::NameTransformationComp( TransformationComp comp )
 {
@@ -738,7 +768,7 @@ void Converter::GenerateTransformationNodeChain( const Model& model, std::vector
     // not be guaranteed.
     ai_assert( NeedsComplexTransformationChain( model ) == is_complex );
 
-    const std::string& name = FixNodeName( model.Name() );
+    std::string name = FixNodeName( model.Name() );
 
     // now, if we have more than just Translation, Scaling and Rotation,
     // we need to generate a full node chain to accommodate for assimp's
@@ -786,8 +816,10 @@ void Converter::GenerateTransformationNodeChain( const Model& model, std::vector
     // else, we can just multiply the matrices together
     aiNode* nd = new aiNode();
     output_nodes.push_back( nd );
+    std::string uniqueName;
+    GetUniqueName( name, uniqueName );
 
-    nd->mName.Set( name );
+    nd->mName.Set( uniqueName );
 
     for (const auto &transform : chain) {
         nd->mTransformation = nd->mTransformation * transform;
@@ -2005,81 +2037,17 @@ void Converter::ConvertAnimations()
     }
 }
 
-void Converter::RenameNode( const std::string& fixed_name, const std::string& new_name ) {
-    if ( node_names.find( fixed_name ) == node_names.end() ) {
-        FBXImporter::LogError( "Cannot rename node " + fixed_name + ", not existing.");
-        return;
-    }
-
-    if ( node_names.find( new_name ) != node_names.end() ) {
-        FBXImporter::LogError( "Cannot rename node " + fixed_name + " to " + new_name +", name already existing." );
-        return;
-    }
-
-    ai_assert( node_names.find( fixed_name ) != node_names.end() );
-    ai_assert( node_names.find( new_name ) == node_names.end() );
-
-    renamed_nodes[ fixed_name ] = new_name;
-
-    const aiString fn( fixed_name );
-
-    for( aiCamera* cam : cameras ) {
-        if ( cam->mName == fn ) {
-            cam->mName.Set( new_name );
-            break;
-        }
-    }
-
-    for( aiLight* light : lights ) {
-        if ( light->mName == fn ) {
-            light->mName.Set( new_name );
-            break;
-        }
-    }
-
-    for( aiAnimation* anim : animations ) {
-        for ( unsigned int i = 0; i < anim->mNumChannels; ++i ) {
-            aiNodeAnim* const na = anim->mChannels[ i ];
-            if ( na->mNodeName == fn ) {
-                na->mNodeName.Set( new_name );
-                break;
-            }
-        }
-    }
-}
-
-
-std::string Converter::FixNodeName( const std::string& name )
-{
+std::string Converter::FixNodeName( const std::string& name ) {
     // strip Model:: prefix, avoiding ambiguities (i.e. don't strip if
     // this causes ambiguities, well possible between empty identifiers,
     // such as "Model::" and ""). Make sure the behaviour is consistent
     // across multiple calls to FixNodeName().
     if ( name.substr( 0, 7 ) == "Model::" ) {
         std::string temp = name.substr( 7 );
-
-        const NodeNameMap::const_iterator it = node_names.find( temp );
-        if ( it != node_names.end() ) {
-            if ( !( *it ).second ) {
-                return FixNodeName( name + "_" );
-            }
-        }
-        node_names[ temp ] = true;
-
-        const NameNameMap::const_iterator rit = renamed_nodes.find( temp );
-        return rit == renamed_nodes.end() ? temp : ( *rit ).second;
-    }
-
-    const NodeNameMap::const_iterator it = node_names.find( name );
-    if ( it != node_names.end() ) {
-        if ( ( *it ).second ) {
-            return FixNodeName( name + "_" );
-        }
+        return temp;
     }
-    node_names[ name ] = false;
 
-    const NameNameMap::const_iterator rit = renamed_nodes.find( name );
-    return rit == renamed_nodes.end() ? name : ( *rit ).second;
+    return name;
 }
 
 void Converter::ConvertAnimationStack( const AnimationStack& st )

+ 10 - 26
code/FBXConverter.h

@@ -68,6 +68,8 @@ namespace FBX {
 
 class Document;
 
+using NodeNameCache = std::vector<std::string>;
+
 /** 
  *  Convert a FBX #Document to #aiScene
  *  @param out Empty scene to be populated
@@ -117,16 +119,19 @@ private:
     void ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform = aiMatrix4x4());
 
     // ------------------------------------------------------------------------------------------------
-    void ConvertLights(const Model& model);
+    void ConvertLights(const Model& model, const std::string &orig_name );
+
+    // ------------------------------------------------------------------------------------------------
+    void ConvertCameras(const Model& model, const std::string &orig_name );
 
     // ------------------------------------------------------------------------------------------------
-    void ConvertCameras(const Model& model);
+    void ConvertLight( const Light& light, const std::string &orig_name );
 
     // ------------------------------------------------------------------------------------------------
-    void ConvertLight(const Model& model, const Light& light);
+    void ConvertCamera( const Camera& cam, const std::string &orig_name );
 
     // ------------------------------------------------------------------------------------------------
-    void ConvertCamera(const Model& model, const Camera& cam);
+    void GetUniqueName( const std::string &name, std::string uniqueName );
 
     // ------------------------------------------------------------------------------------------------
     // this returns unified names usable within assimp identifiers (i.e. no space characters -
@@ -258,18 +263,6 @@ private:
     // convert animation data to aiAnimation et al
     void ConvertAnimations();
 
-    // ------------------------------------------------------------------------------------------------
-    // rename a node already partially converted. fixed_name is a string previously returned by
-    // FixNodeName, new_name specifies the string FixNodeName should return on all further invocations
-    // which would previously have returned the old value.
-    //
-    // this also updates names in node animations, cameras and light sources and is thus slow.
-    //
-    // NOTE: the caller is responsible for ensuring that the new name is unique and does
-    // not collide with any other identifiers. The best way to ensure this is to only
-    // append to the old name, which is guaranteed to match these requirements.
-    void RenameNode(const std::string& fixed_name, const std::string& new_name);
-
     // ------------------------------------------------------------------------------------------------
     // takes a fbx node name and returns the identifier to be used in the assimp output scene.
     // the function is guaranteed to provide consistent results over multiple invocations
@@ -281,7 +274,6 @@ private:
     // XXX: better use multi_map ..
     typedef std::map<std::string, std::vector<const AnimationCurveNode*> > NodeMap;
 
-
     // ------------------------------------------------------------------------------------------------
     void ConvertAnimationStack(const AnimationStack& st);
 
@@ -432,19 +424,11 @@ private:
     typedef std::map<std::string, unsigned int> NodeAnimBitMap;
     NodeAnimBitMap node_anim_chain_bits;
 
-    // name -> has had its prefix_stripped?
-    typedef std::map<std::string, bool> NodeNameMap;
-    NodeNameMap node_names;
-
-    typedef std::map<std::string, std::string> NameNameMap;
-    NameNameMap renamed_nodes;
-
+    NodeNameCache mNodeNames;
     double anim_fps;
 
     aiScene* const out;
     const FBX::Document& doc;
-
-    std::vector<std::string> mLightNames;
 };
 
 }

+ 329 - 45
code/FBXExportNode.cpp

@@ -45,9 +45,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "FBXCommon.h"
 
 #include <assimp/StreamWriter.h> // StreamWriterLE
+#include <assimp/Exceptional.h> // DeadlyExportError
 #include <assimp/ai_assert.h>
+#include <assimp/StringUtils.h> // ai_snprintf
 
 #include <string>
+#include <ostream>
+#include <sstream> // ostringstream
 #include <memory> // shared_ptr
 
 // AddP70<type> helpers... there's no usable pattern here,
@@ -145,33 +149,174 @@ void FBX::Node::AddP70time(
 }
 
 
-// public member functions for writing to binary fbx
+// public member functions for writing nodes to stream
 
-void FBX::Node::Dump(std::shared_ptr<Assimp::IOStream> outfile)
-{
-    Assimp::StreamWriterLE outstream(outfile);
-    Dump(outstream);
+void FBX::Node::Dump(
+    std::shared_ptr<Assimp::IOStream> outfile,
+    bool binary, int indent
+) {
+    if (binary) {
+        Assimp::StreamWriterLE outstream(outfile);
+        DumpBinary(outstream);
+    } else {
+        std::ostringstream ss;
+        DumpAscii(ss, indent);
+        std::string s = ss.str();
+        outfile->Write(s.c_str(), s.size(), 1);
+    }
+}
+
+void FBX::Node::Dump(
+    Assimp::StreamWriterLE &outstream,
+    bool binary, int indent
+) {
+    if (binary) {
+        DumpBinary(outstream);
+    } else {
+        std::ostringstream ss;
+        DumpAscii(ss, indent);
+        outstream.PutString(ss.str());
+    }
+}
+
+
+// public member functions for low-level writing
+
+void FBX::Node::Begin(
+    Assimp::StreamWriterLE &s,
+    bool binary, int indent
+) {
+    if (binary) {
+        BeginBinary(s);
+    } else {
+        // assume we're at the correct place to start already
+        (void)indent;
+        std::ostringstream ss;
+        BeginAscii(ss, indent);
+        s.PutString(ss.str());
+    }
+}
+
+void FBX::Node::DumpProperties(
+    Assimp::StreamWriterLE& s,
+    bool binary, int indent
+) {
+    if (binary) {
+        DumpPropertiesBinary(s);
+    } else {
+        std::ostringstream ss;
+        DumpPropertiesAscii(ss, indent);
+        s.PutString(ss.str());
+    }
 }
 
-void FBX::Node::Dump(Assimp::StreamWriterLE &s)
+void FBX::Node::EndProperties(
+    Assimp::StreamWriterLE &s,
+    bool binary, int indent
+) {
+    EndProperties(s, binary, indent, properties.size());
+}
+
+void FBX::Node::EndProperties(
+    Assimp::StreamWriterLE &s,
+    bool binary, int indent,
+    size_t num_properties
+) {
+    if (binary) {
+        EndPropertiesBinary(s, num_properties);
+    } else {
+        // nothing to do
+        (void)indent;
+    }
+}
+
+void FBX::Node::BeginChildren(
+    Assimp::StreamWriterLE &s,
+    bool binary, int indent
+) {
+    if (binary) {
+        // nothing to do
+    } else {
+        std::ostringstream ss;
+        BeginChildrenAscii(ss, indent);
+        s.PutString(ss.str());
+    }
+}
+
+void FBX::Node::DumpChildren(
+    Assimp::StreamWriterLE& s,
+    bool binary, int indent
+) {
+    if (binary) {
+        DumpChildrenBinary(s);
+    } else {
+        std::ostringstream ss;
+        DumpChildrenAscii(ss, indent);
+        s.PutString(ss.str());
+    }
+}
+
+void FBX::Node::End(
+    Assimp::StreamWriterLE &s,
+    bool binary, int indent,
+    bool has_children
+) {
+    if (binary) {
+        EndBinary(s, has_children);
+    } else {
+        std::ostringstream ss;
+        EndAscii(ss, indent, has_children);
+        s.PutString(ss.str());
+    }
+}
+
+
+// public member functions for writing to binary fbx
+
+void FBX::Node::DumpBinary(Assimp::StreamWriterLE &s)
 {
     // write header section (with placeholders for some things)
-    Begin(s);
+    BeginBinary(s);
 
     // write properties
-    DumpProperties(s);
+    DumpPropertiesBinary(s);
 
     // go back and fill in property related placeholders
-    EndProperties(s, properties.size());
+    EndPropertiesBinary(s, properties.size());
 
     // write children
-    DumpChildren(s);
+    DumpChildrenBinary(s);
 
     // finish, filling in end offset placeholder
-    End(s, !children.empty());
+    EndBinary(s, force_has_children || !children.empty());
 }
 
-void FBX::Node::Begin(Assimp::StreamWriterLE &s)
+
+// public member functions for writing to ascii fbx
+
+void FBX::Node::DumpAscii(std::ostream &s, int indent)
+{
+    // write name
+    BeginAscii(s, indent);
+
+    // write properties
+    DumpPropertiesAscii(s, indent);
+
+    if (force_has_children || !children.empty()) {
+        // begin children (with a '{')
+        BeginChildrenAscii(s, indent + 1);
+        // write children
+        DumpChildrenAscii(s, indent + 1);
+    }
+
+    // finish (also closing the children bracket '}')
+    EndAscii(s, indent, force_has_children || !children.empty());
+}
+
+
+// private member functions for low-level writing to fbx
+
+void FBX::Node::BeginBinary(Assimp::StreamWriterLE &s)
 {
     // remember start pos so we can come back and write the end pos
     this->start_pos = s.Tell();
@@ -189,26 +334,14 @@ void FBX::Node::Begin(Assimp::StreamWriterLE &s)
     this->property_start = s.Tell();
 }
 
-void FBX::Node::DumpProperties(Assimp::StreamWriterLE& s)
+void FBX::Node::DumpPropertiesBinary(Assimp::StreamWriterLE& s)
 {
     for (auto &p : properties) {
-        p.Dump(s);
-    }
-}
-
-void FBX::Node::DumpChildren(Assimp::StreamWriterLE& s)
-{
-    for (FBX::Node& child : children) {
-        child.Dump(s);
+        p.DumpBinary(s);
     }
 }
 
-void FBX::Node::EndProperties(Assimp::StreamWriterLE &s)
-{
-    EndProperties(s, properties.size());
-}
-
-void FBX::Node::EndProperties(
+void FBX::Node::EndPropertiesBinary(
     Assimp::StreamWriterLE &s,
     size_t num_properties
 ) {
@@ -222,7 +355,14 @@ void FBX::Node::EndProperties(
     s.Seek(pos);
 }
 
-void FBX::Node::End(
+void FBX::Node::DumpChildrenBinary(Assimp::StreamWriterLE& s)
+{
+    for (FBX::Node& child : children) {
+        child.DumpBinary(s);
+    }
+}
+
+void FBX::Node::EndBinary(
     Assimp::StreamWriterLE &s,
     bool has_children
 ) {
@@ -237,48 +377,192 @@ void FBX::Node::End(
 }
 
 
-// static member functions
+void FBX::Node::BeginAscii(std::ostream& s, int indent)
+{
+    s << '\n';
+    for (int i = 0; i < indent; ++i) { s << '\t'; }
+    s << name << ": ";
+}
 
-// convenience function to create and write a property node,
-// holding a single property which is an array of values.
-// does not copy the data, so is efficient for large arrays.
+void FBX::Node::DumpPropertiesAscii(std::ostream &s, int indent)
+{
+    for (size_t i = 0; i < properties.size(); ++i) {
+        if (i > 0) { s << ", "; }
+        properties[i].DumpAscii(s, indent);
+    }
+}
+
+void FBX::Node::BeginChildrenAscii(std::ostream& s, int indent)
+{
+    // only call this if there are actually children
+    s << " {";
+    (void)indent;
+}
+
+void FBX::Node::DumpChildrenAscii(std::ostream& s, int indent)
+{
+    // children will need a lot of padding and corralling
+    if (children.size() || force_has_children) {
+        for (size_t i = 0; i < children.size(); ++i) {
+            // no compression in ascii files, so skip this node if it exists
+            if (children[i].name == "EncryptionType") { continue; }
+            // the child can dump itself
+            children[i].DumpAscii(s, indent);
+        }
+    }
+}
+
+void FBX::Node::EndAscii(std::ostream& s, int indent, bool has_children)
+{
+    if (!has_children) { return; } // nothing to do
+    s << '\n';
+    for (int i = 0; i < indent; ++i) { s << '\t'; }
+    s << "}";
+}
+
+// private helpers for static member functions
+
+// ascii property node from vector of doubles
+void FBX::Node::WritePropertyNodeAscii(
+    const std::string& name,
+    const std::vector<double>& v,
+    Assimp::StreamWriterLE& s,
+    int indent
+){
+    char buffer[32];
+    FBX::Node node(name);
+    node.Begin(s, false, indent);
+    std::string vsize = std::to_string(v.size());
+    // *<size> {
+    s.PutChar('*'); s.PutString(vsize); s.PutString(" {\n");
+    // indent + 1
+    for (int i = 0; i < indent + 1; ++i) { s.PutChar('\t'); }
+    // a: value,value,value,...
+    s.PutString("a: ");
+    int count = 0;
+    for (size_t i = 0; i < v.size(); ++i) {
+        if (i > 0) { s.PutChar(','); }
+        int len = ai_snprintf(buffer, sizeof(buffer), "%f", v[i]);
+        count += len;
+        if (count > 2048) { s.PutChar('\n'); count = 0; }
+        if (len < 0 || len > 31) {
+            // this should never happen
+            throw DeadlyExportError("failed to convert double to string");
+        }
+        for (int j = 0; j < len; ++j) { s.PutChar(buffer[j]); }
+    }
+    // }
+    s.PutChar('\n');
+    for (int i = 0; i < indent; ++i) { s.PutChar('\t'); }
+    s.PutChar('}'); s.PutChar(' ');
+    node.End(s, false, indent, false);
+}
+
+// ascii property node from vector of int32_t
+void FBX::Node::WritePropertyNodeAscii(
+    const std::string& name,
+    const std::vector<int32_t>& v,
+    Assimp::StreamWriterLE& s,
+    int indent
+){
+    char buffer[32];
+    FBX::Node node(name);
+    node.Begin(s, false, indent);
+    std::string vsize = std::to_string(v.size());
+    // *<size> {
+    s.PutChar('*'); s.PutString(vsize); s.PutString(" {\n");
+    // indent + 1
+    for (int i = 0; i < indent + 1; ++i) { s.PutChar('\t'); }
+    // a: value,value,value,...
+    s.PutString("a: ");
+    int count = 0;
+    for (size_t i = 0; i < v.size(); ++i) {
+        if (i > 0) { s.PutChar(','); }
+        int len = ai_snprintf(buffer, sizeof(buffer), "%d", v[i]);
+        count += len;
+        if (count > 2048) { s.PutChar('\n'); count = 0; }
+        if (len < 0 || len > 31) {
+            // this should never happen
+            throw DeadlyExportError("failed to convert double to string");
+        }
+        for (int j = 0; j < len; ++j) { s.PutChar(buffer[j]); }
+    }
+    // }
+    s.PutChar('\n');
+    for (int i = 0; i < indent; ++i) { s.PutChar('\t'); }
+    s.PutChar('}'); s.PutChar(' ');
+    node.End(s, false, indent, false);
+}
+
+// binary property node from vector of doubles
 // TODO: optional zip compression!
-void FBX::Node::WritePropertyNode(
+void FBX::Node::WritePropertyNodeBinary(
     const std::string& name,
     const std::vector<double>& v,
     Assimp::StreamWriterLE& s
 ){
-    Node node(name);
-    node.Begin(s);
+    FBX::Node node(name);
+    node.BeginBinary(s);
     s.PutU1('d');
     s.PutU4(uint32_t(v.size())); // number of elements
     s.PutU4(0); // no encoding (1 would be zip-compressed)
     s.PutU4(uint32_t(v.size()) * 8); // data size
     for (auto it = v.begin(); it != v.end(); ++it) { s.PutF8(*it); }
-    node.EndProperties(s, 1);
-    node.End(s, false);
+    node.EndPropertiesBinary(s, 1);
+    node.EndBinary(s, false);
 }
 
-// convenience function to create and write a property node,
-// holding a single property which is an array of values.
-// does not copy the data, so is efficient for large arrays.
+// binary property node from vector of int32_t
 // TODO: optional zip compression!
-void FBX::Node::WritePropertyNode(
+void FBX::Node::WritePropertyNodeBinary(
     const std::string& name,
     const std::vector<int32_t>& v,
     Assimp::StreamWriterLE& s
 ){
-    Node node(name);
-    node.Begin(s);
+    FBX::Node node(name);
+    node.BeginBinary(s);
     s.PutU1('i');
     s.PutU4(uint32_t(v.size())); // number of elements
     s.PutU4(0); // no encoding (1 would be zip-compressed)
     s.PutU4(uint32_t(v.size()) * 4); // data size
     for (auto it = v.begin(); it != v.end(); ++it) { s.PutI4(*it); }
-    node.EndProperties(s, 1);
-    node.End(s, false);
+    node.EndPropertiesBinary(s, 1);
+    node.EndBinary(s, false);
 }
 
+// public static member functions
+
+// convenience function to create and write a property node,
+// holding a single property which is an array of values.
+// does not copy the data, so is efficient for large arrays.
+void FBX::Node::WritePropertyNode(
+    const std::string& name,
+    const std::vector<double>& v,
+    Assimp::StreamWriterLE& s,
+    bool binary, int indent
+){
+    if (binary) {
+        FBX::Node::WritePropertyNodeBinary(name, v, s);
+    } else {
+        FBX::Node::WritePropertyNodeAscii(name, v, s, indent);
+    }
+}
+
+// convenience function to create and write a property node,
+// holding a single property which is an array of values.
+// does not copy the data, so is efficient for large arrays.
+void FBX::Node::WritePropertyNode(
+    const std::string& name,
+    const std::vector<int32_t>& v,
+    Assimp::StreamWriterLE& s,
+    bool binary, int indent
+){
+    if (binary) {
+        FBX::Node::WritePropertyNodeBinary(name, v, s);
+    } else {
+        FBX::Node::WritePropertyNodeAscii(name, v, s, indent);
+    }
+}
 
 #endif // ASSIMP_BUILD_NO_FBX_EXPORTER
 #endif // ASSIMP_BUILD_NO_EXPORT

+ 77 - 16
code/FBXExportNode.h

@@ -66,14 +66,18 @@ public: // public data members
     std::vector<FBX::Property> properties; // node properties
     std::vector<FBX::Node> children; // child nodes
 
+    // some nodes always pretend they have children...
+    bool force_has_children = false;
+
 public: // constructors
     Node() = default;
     Node(const std::string& n) : name(n) {}
-    Node(const std::string& n, const FBX::Property &p)
+
+    // convenience template to construct with properties directly
+    template <typename... More>
+    Node(const std::string& n, const More... more)
         : name(n)
-        { properties.push_back(p); }
-    Node(const std::string& n, const std::vector<FBX::Property> &pv)
-        : name(n), properties(pv) {}
+        { AddProperties(more...); }
 
 public: // functions to add properties or children
     // add a single property to the node
@@ -138,19 +142,48 @@ public: // support specifically for dealing with Properties70 nodes
 
 public: // member functions for writing data to a file or stream
 
-    // write the full node as binary data to the given file or stream
-    void Dump(std::shared_ptr<Assimp::IOStream> outfile);
-    void Dump(Assimp::StreamWriterLE &s);
+    // write the full node to the given file or stream
+    void Dump(
+        std::shared_ptr<Assimp::IOStream> outfile,
+        bool binary, int indent
+    );
+    void Dump(Assimp::StreamWriterLE &s, bool binary, int indent);
 
     // these other functions are for writing data piece by piece.
     // they must be used carefully.
     // for usage examples see FBXExporter.cpp.
-    void Begin(Assimp::StreamWriterLE &s);
-    void DumpProperties(Assimp::StreamWriterLE& s);
-    void EndProperties(Assimp::StreamWriterLE &s);
-    void EndProperties(Assimp::StreamWriterLE &s, size_t num_properties);
-    void DumpChildren(Assimp::StreamWriterLE& s);
-    void End(Assimp::StreamWriterLE &s, bool has_children);
+    void Begin(Assimp::StreamWriterLE &s, bool binary, int indent);
+    void DumpProperties(Assimp::StreamWriterLE& s, bool binary, int indent);
+    void EndProperties(Assimp::StreamWriterLE &s, bool binary, int indent);
+    void EndProperties(
+        Assimp::StreamWriterLE &s, bool binary, int indent,
+        size_t num_properties
+    );
+    void BeginChildren(Assimp::StreamWriterLE &s, bool binary, int indent);
+    void DumpChildren(Assimp::StreamWriterLE& s, bool binary, int indent);
+    void End(
+        Assimp::StreamWriterLE &s, bool binary, int indent,
+        bool has_children
+    );
+
+private: // internal functions used for writing
+
+    void DumpBinary(Assimp::StreamWriterLE &s);
+    void DumpAscii(Assimp::StreamWriterLE &s, int indent);
+    void DumpAscii(std::ostream &s, int indent);
+
+    void BeginBinary(Assimp::StreamWriterLE &s);
+    void DumpPropertiesBinary(Assimp::StreamWriterLE& s);
+    void EndPropertiesBinary(Assimp::StreamWriterLE &s);
+    void EndPropertiesBinary(Assimp::StreamWriterLE &s, size_t num_properties);
+    void DumpChildrenBinary(Assimp::StreamWriterLE& s);
+    void EndBinary(Assimp::StreamWriterLE &s, bool has_children);
+
+    void BeginAscii(std::ostream &s, int indent);
+    void DumpPropertiesAscii(std::ostream &s, int indent);
+    void BeginChildrenAscii(std::ostream &s, int indent);
+    void DumpChildrenAscii(std::ostream &s, int indent);
+    void EndAscii(std::ostream &s, int indent, bool has_children);
 
 private: // data used for binary dumps
     size_t start_pos; // starting position in stream
@@ -165,11 +198,12 @@ public: // static member functions
     static void WritePropertyNode(
         const std::string& name,
         const T value,
-        Assimp::StreamWriterLE& s
+        Assimp::StreamWriterLE& s,
+        bool binary, int indent
     ) {
         FBX::Property p(value);
         FBX::Node node(name, p);
-        node.Dump(s);
+        node.Dump(s, binary, indent);
     }
 
     // convenience function to create and write a property node,
@@ -178,17 +212,44 @@ public: // static member functions
     static void WritePropertyNode(
         const std::string& name,
         const std::vector<double>& v,
-        Assimp::StreamWriterLE& s
+        Assimp::StreamWriterLE& s,
+        bool binary, int indent
     );
 
     // convenience function to create and write a property node,
     // holding a single property which is an array of values.
     // does not copy the data, so is efficient for large arrays.
     static void WritePropertyNode(
+        const std::string& name,
+        const std::vector<int32_t>& v,
+        Assimp::StreamWriterLE& s,
+        bool binary, int indent
+    );
+
+private: // static helper functions
+    static void WritePropertyNodeAscii(
+        const std::string& name,
+        const std::vector<double>& v,
+        Assimp::StreamWriterLE& s,
+        int indent
+    );
+    static void WritePropertyNodeAscii(
+        const std::string& name,
+        const std::vector<int32_t>& v,
+        Assimp::StreamWriterLE& s,
+        int indent
+    );
+    static void WritePropertyNodeBinary(
+        const std::string& name,
+        const std::vector<double>& v,
+        Assimp::StreamWriterLE& s
+    );
+    static void WritePropertyNodeBinary(
         const std::string& name,
         const std::vector<int32_t>& v,
         Assimp::StreamWriterLE& s
     );
+
 };
 
 

+ 141 - 14
code/FBXExportProperty.cpp

@@ -48,7 +48,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include <string>
 #include <vector>
-#include <sstream> // stringstream
+#include <ostream>
+#include <locale>
+#include <sstream> // ostringstream
 
 
 // constructors for single element properties
@@ -164,18 +166,18 @@ size_t FBX::Property::size()
     }
 }
 
-void FBX::Property::Dump(Assimp::StreamWriterLE &s)
+void FBX::Property::DumpBinary(Assimp::StreamWriterLE &s)
 {
     s.PutU1(type);
-    uint8_t* d;
+    uint8_t* d = data.data();
     size_t N;
     switch (type) {
-    case 'C': s.PutU1(*(reinterpret_cast<uint8_t*>(data.data()))); return;
-    case 'Y': s.PutI2(*(reinterpret_cast<int16_t*>(data.data()))); return;
-    case 'I': s.PutI4(*(reinterpret_cast<int32_t*>(data.data()))); return;
-    case 'F': s.PutF4(*(reinterpret_cast<float*>(data.data()))); return;
-    case 'D': s.PutF8(*(reinterpret_cast<double*>(data.data()))); return;
-    case 'L': s.PutI8(*(reinterpret_cast<int64_t*>(data.data()))); return;
+    case 'C': s.PutU1(*(reinterpret_cast<uint8_t*>(d))); return;
+    case 'Y': s.PutI2(*(reinterpret_cast<int16_t*>(d))); return;
+    case 'I': s.PutI4(*(reinterpret_cast<int32_t*>(d))); return;
+    case 'F': s.PutF4(*(reinterpret_cast<float*>(d))); return;
+    case 'D': s.PutF8(*(reinterpret_cast<double*>(d))); return;
+    case 'L': s.PutI8(*(reinterpret_cast<int64_t*>(d))); return;
     case 'S':
     case 'R':
         s.PutU4(uint32_t(data.size()));
@@ -187,7 +189,6 @@ void FBX::Property::Dump(Assimp::StreamWriterLE &s)
         s.PutU4(0); // no encoding (1 would be zip-compressed)
         // TODO: compress if large?
         s.PutU4(uint32_t(data.size())); // data size
-        d = data.data();
         for (size_t i = 0; i < N; ++i) {
             s.PutI4((reinterpret_cast<int32_t*>(d))[i]);
         }
@@ -198,7 +199,6 @@ void FBX::Property::Dump(Assimp::StreamWriterLE &s)
         s.PutU4(0); // no encoding (1 would be zip-compressed)
         // TODO: compress if large?
         s.PutU4(uint32_t(data.size())); // data size
-        d = data.data();
         for (size_t i = 0; i < N; ++i) {
             s.PutI8((reinterpret_cast<int64_t*>(d))[i]);
         }
@@ -209,7 +209,6 @@ void FBX::Property::Dump(Assimp::StreamWriterLE &s)
         s.PutU4(0); // no encoding (1 would be zip-compressed)
         // TODO: compress if large?
         s.PutU4(uint32_t(data.size())); // data size
-        d = data.data();
         for (size_t i = 0; i < N; ++i) {
             s.PutF4((reinterpret_cast<float*>(d))[i]);
         }
@@ -220,18 +219,146 @@ void FBX::Property::Dump(Assimp::StreamWriterLE &s)
         s.PutU4(0); // no encoding (1 would be zip-compressed)
         // TODO: compress if large?
         s.PutU4(uint32_t(data.size())); // data size
-        d = data.data();
         for (size_t i = 0; i < N; ++i) {
             s.PutF8((reinterpret_cast<double*>(d))[i]);
         }
         return;
     default:
-        std::stringstream err;
+        std::ostringstream err;
         err << "Tried to dump property with invalid type '";
         err << type << "'!";
         throw DeadlyExportError(err.str());
     }
 }
 
+void FBX::Property::DumpAscii(Assimp::StreamWriterLE &outstream, int indent)
+{
+    std::ostringstream ss;
+    ss.imbue(std::locale::classic());
+    ss.precision(15); // this seems to match official FBX SDK exports
+    DumpAscii(ss, indent);
+    outstream.PutString(ss.str());
+}
+
+void FBX::Property::DumpAscii(std::ostream& s, int indent)
+{
+    // no writing type... or anything. just shove it into the stream.
+    uint8_t* d = data.data();
+    size_t N;
+    size_t swap = data.size();
+    size_t count = 0;
+    switch (type) {
+    case 'C':
+        if (*(reinterpret_cast<uint8_t*>(d))) { s << 'T'; }
+        else { s << 'F'; }
+        return;
+    case 'Y': s << *(reinterpret_cast<int16_t*>(d)); return;
+    case 'I': s << *(reinterpret_cast<int32_t*>(d)); return;
+    case 'F': s << *(reinterpret_cast<float*>(d)); return;
+    case 'D': s << *(reinterpret_cast<double*>(d)); return;
+    case 'L': s << *(reinterpret_cast<int64_t*>(d)); return;
+    case 'S':
+        // first search to see if it has "\x00\x01" in it -
+        // which separates fields which are reversed in the ascii version.
+        // yeah.
+        // FBX, yeah.
+        for (size_t i = 0; i < data.size(); ++i) {
+            if (data[i] == '\0') {
+                swap = i;
+                break;
+            }
+        }
+    case 'R':
+        s << '"';
+        // we might as well check this now,
+        // probably it will never happen
+        for (size_t i = 0; i < data.size(); ++i) {
+            char c = data[i];
+            if (c == '"') {
+                throw runtime_error("can't handle quotes in property string");
+            }
+        }
+        // first write the SWAPPED member (if any)
+        for (size_t i = swap + 2; i < data.size(); ++i) {
+            char c = data[i];
+            s << c;
+        }
+        // then a separator
+        if (swap != data.size()) {
+            s << "::";
+        }
+        // then the initial member
+        for (size_t i = 0; i < swap; ++i) {
+            char c = data[i];
+            s << c;
+        }
+        s << '"';
+        return;
+    case 'i':
+        N = data.size() / 4; // number of elements
+        s << '*' << N << " {\n";
+        for (int i = 0; i < indent + 1; ++i) { s << '\t'; }
+        s << "a: ";
+        for (size_t i = 0; i < N; ++i) {
+            if (i > 0) { s << ','; }
+            if (count++ > 120) { s << '\n'; count = 0; }
+            s << (reinterpret_cast<int32_t*>(d))[i];
+        }
+        s << '\n';
+        for (int i = 0; i < indent; ++i) { s << '\t'; }
+        s << "} ";
+        return;
+    case 'l':
+        N = data.size() / 8;
+        s << '*' << N << " {\n";
+        for (int i = 0; i < indent + 1; ++i) { s << '\t'; }
+        s << "a: ";
+        for (size_t i = 0; i < N; ++i) {
+            if (i > 0) { s << ','; }
+            if (count++ > 120) { s << '\n'; count = 0; }
+            s << (reinterpret_cast<int64_t*>(d))[i];
+        }
+        s << '\n';
+        for (int i = 0; i < indent; ++i) { s << '\t'; }
+        s << "} ";
+        return;
+    case 'f':
+        N = data.size() / 4;
+        s << '*' << N << " {\n";
+        for (int i = 0; i < indent + 1; ++i) { s << '\t'; }
+        s << "a: ";
+        for (size_t i = 0; i < N; ++i) {
+            if (i > 0) { s << ','; }
+            if (count++ > 120) { s << '\n'; count = 0; }
+            s << (reinterpret_cast<float*>(d))[i];
+        }
+        s << '\n';
+        for (int i = 0; i < indent; ++i) { s << '\t'; }
+        s << "} ";
+        return;
+    case 'd':
+        N = data.size() / 8;
+        s << '*' << N << " {\n";
+        for (int i = 0; i < indent + 1; ++i) { s << '\t'; }
+        s << "a: ";
+        // set precision to something that can handle doubles
+        s.precision(15);
+        for (size_t i = 0; i < N; ++i) {
+            if (i > 0) { s << ','; }
+            if (count++ > 120) { s << '\n'; count = 0; }
+            s << (reinterpret_cast<double*>(d))[i];
+        }
+        s << '\n';
+        for (int i = 0; i < indent; ++i) { s << '\t'; }
+        s << "} ";
+        return;
+    default:
+        std::ostringstream err;
+        err << "Tried to dump property with invalid type '";
+        err << type << "'!";
+        throw runtime_error(err.str());
+    }
+}
+
 #endif // ASSIMP_BUILD_NO_FBX_EXPORTER
 #endif // ASSIMP_BUILD_NO_EXPORT

+ 5 - 1
code/FBXExportProperty.h

@@ -53,6 +53,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include <string>
 #include <vector>
+#include <ostream>
 #include <type_traits> // is_void
 
 namespace FBX {
@@ -113,7 +114,10 @@ public:
     size_t size();
 
     // write this property node as binary data to the given stream
-    void Dump(Assimp::StreamWriterLE &s);
+    void DumpBinary(Assimp::StreamWriterLE &s);
+    void DumpAscii(Assimp::StreamWriterLE &s, int indent=0);
+    void DumpAscii(std::ostream &s, int indent=0);
+    // note: make sure the ostream is in classic "C" locale
 
 private:
     char type;

+ 260 - 189
code/FBXExporter.cpp

@@ -66,7 +66,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <vector>
 #include <array>
 #include <unordered_set>
-#include <iostream> // endl
 
 // RESOURCES:
 // https://code.blender.org/2013/08/fbx-binary-file-format-specification/
@@ -89,6 +88,8 @@ namespace FBX {
         "\xfa\xbc\xab\x09\xd0\xc8\xd4\x66\xb1\x76\xfb\x83\x1c\xf7\x26\x7e";
     const std::string FOOT_MAGIC =
         "\xf8\x5a\x8c\x6a\xde\xf5\xd9\x7e\xec\xe9\x0c\xe3\x75\x8f\x29\x0b";
+    const std::string COMMENT_UNDERLINE =
+        ";------------------------------------------------------------------";
 }
 
 using namespace Assimp;
@@ -115,7 +116,7 @@ namespace Assimp {
     // ---------------------------------------------------------------------
     // Worker function for exporting a scene to ASCII FBX.
     // Prototyped and registered in Exporter.cpp
-    /*void ExportSceneFBXA (
+    void ExportSceneFBXA (
         const char* pFile,
         IOSystem* pIOSystem,
         const aiScene* pScene,
@@ -126,7 +127,7 @@ namespace Assimp {
 
         // perform ascii export
         exporter.ExportAscii(pFile, pIOSystem);
-    }*/ // TODO
+    }
 
 } // end of namespace Assimp
 
@@ -194,27 +195,43 @@ void FBXExporter::ExportAscii (
         );
     }
 
-    // this isn't really necessary,
-    // but the Autodesk FBX SDK puts a similar comment at the top of the file.
-    // Theirs declares that the file copyright is owned by Autodesk...
-    std::stringstream head;
-    using std::endl;
-    head << "; FBX " << EXPORT_VERSION_STR << " project file" << endl;
-    head << "; Created by the Open Asset Import Library (Assimp)" << endl;
-    head << "; http://assimp.org" << endl;
-    head << "; -------------------------------------------------" << endl;
-    head << endl;
-    const std::string ascii_header = head.str();
-    outfile->Write(ascii_header.c_str(), ascii_header.size(), 1);
+    // write the ascii header
+    WriteAsciiHeader();
 
     // write all the sections
     WriteAllNodes();
 
+    // make sure the file ends with a newline.
+    // note: if the file is opened in text mode,
+    // this should do the right cross-platform thing.
+    outfile->Write("\n", 1, 1);
+
     // explicitly release file pointer,
     // so we don't have to rely on class destruction.
     outfile.reset();
 }
 
+void FBXExporter::WriteAsciiHeader()
+{
+    // basically just a comment at the top of the file
+    std::stringstream head;
+    head << "; FBX " << EXPORT_VERSION_STR << " project file\n";
+    head << "; Created by the Open Asset Import Library (Assimp)\n";
+    head << "; http://assimp.org\n";
+    head << "; -------------------------------------------------\n";
+    const std::string ascii_header = head.str();
+    outfile->Write(ascii_header.c_str(), ascii_header.size(), 1);
+}
+
+void FBXExporter::WriteAsciiSectionHeader(const std::string& title)
+{
+    StreamWriterLE outstream(outfile);
+    std::stringstream s;
+    s << "\n\n; " << title << '\n';
+    s << FBX::COMMENT_UNDERLINE << "\n";
+    outstream.PutString(s.str());
+}
+
 void FBXExporter::WriteBinaryHeader()
 {
     // first a specific sequence of 23 bytes, always the same
@@ -294,28 +311,39 @@ void FBXExporter::WriteAllNodes ()
 //FBXHeaderExtension top-level node
 void FBXExporter::WriteHeaderExtension ()
 {
+    if (!binary) {
+        // no title, follows directly from the top comment
+    }
     FBX::Node n("FBXHeaderExtension");
     StreamWriterLE outstream(outfile);
+    int indent = 0;
 
     // begin node
-    n.Begin(outstream);
+    n.Begin(outstream, binary, indent);
 
     // write properties
     // (none)
 
     // finish properties
-    n.EndProperties(outstream, 0);
+    n.EndProperties(outstream, binary, indent, 0);
+
+    // begin children
+    n.BeginChildren(outstream, binary, indent);
+
+    indent = 1;
 
     // write child nodes
     FBX::Node::WritePropertyNode(
-        "FBXHeaderVersion", int32_t(1003), outstream
+        "FBXHeaderVersion", int32_t(1003), outstream, binary, indent
     );
     FBX::Node::WritePropertyNode(
-        "FBXVersion", int32_t(EXPORT_VERSION_INT), outstream
-    );
-    FBX::Node::WritePropertyNode(
-        "EncryptionType", int32_t(0), outstream
+        "FBXVersion", int32_t(EXPORT_VERSION_INT), outstream, binary, indent
     );
+    if (binary) {
+        FBX::Node::WritePropertyNode(
+            "EncryptionType", int32_t(0), outstream, binary, indent
+        );
+    }
 
     FBX::Node CreationTimeStamp("CreationTimeStamp");
     time_t rawtime;
@@ -329,36 +357,50 @@ void FBXExporter::WriteHeaderExtension ()
     CreationTimeStamp.AddChild("Minute", int32_t(now->tm_min));
     CreationTimeStamp.AddChild("Second", int32_t(now->tm_sec));
     CreationTimeStamp.AddChild("Millisecond", int32_t(0));
-    CreationTimeStamp.Dump(outstream);
+    CreationTimeStamp.Dump(outstream, binary, indent);
 
     std::stringstream creator;
     creator << "Open Asset Import Library (Assimp) " << aiGetVersionMajor()
             << "." << aiGetVersionMinor() << "." << aiGetVersionRevision();
-    FBX::Node::WritePropertyNode("Creator", creator.str(), outstream);
+    FBX::Node::WritePropertyNode(
+        "Creator", creator.str(), outstream, binary, indent
+    );
 
-    FBX::Node sceneinfo("SceneInfo");
+    //FBX::Node sceneinfo("SceneInfo");
     //sceneinfo.AddProperty("GlobalInfo" + FBX::SEPARATOR + "SceneInfo");
     // not sure if any of this is actually needed,
     // so just write an empty node for now.
-    sceneinfo.Dump(outstream);
+    //sceneinfo.Dump(outstream, binary, indent);
+
+    indent = 0;
 
     // finish node
-    n.End(outstream, true);
+    n.End(outstream, binary, indent, true);
 
     // that's it for FBXHeaderExtension...
+    if (!binary) { return; }
 
     // but binary files also need top-level FileID, CreationTime, Creator:
     std::vector<uint8_t> raw(GENERIC_FILEID.size());
     for (size_t i = 0; i < GENERIC_FILEID.size(); ++i) {
         raw[i] = uint8_t(GENERIC_FILEID[i]);
     }
-    FBX::Node::WritePropertyNode("FileId", raw, outstream);
-    FBX::Node::WritePropertyNode("CreationTime", GENERIC_CTIME, outstream);
-    FBX::Node::WritePropertyNode("Creator", creator.str(), outstream);
+    FBX::Node::WritePropertyNode(
+        "FileId", raw, outstream, binary, indent
+    );
+    FBX::Node::WritePropertyNode(
+        "CreationTime", GENERIC_CTIME, outstream, binary, indent
+    );
+    FBX::Node::WritePropertyNode(
+        "Creator", creator.str(), outstream, binary, indent
+    );
 }
 
 void FBXExporter::WriteGlobalSettings ()
 {
+    if (!binary) {
+        // no title, follows directly from the header extension
+    }
     FBX::Node gs("GlobalSettings");
     gs.AddChild("Version", int32_t(1000));
 
@@ -385,11 +427,15 @@ void FBXExporter::WriteGlobalSettings ()
     p.AddP70int("CurrentTimeMarker", -1);
     gs.AddChild(p);
 
-    gs.Dump(outfile);
+    gs.Dump(outfile, binary, 0);
 }
 
 void FBXExporter::WriteDocuments ()
 {
+    if (!binary) {
+        WriteAsciiSectionHeader("Documents Description");
+    }
+    
     // not sure what the use of multiple documents would be,
     // or whether any end-application supports it
     FBX::Node docs("Documents");
@@ -411,15 +457,19 @@ void FBXExporter::WriteDocuments ()
     doc.AddChild("RootNode", int64_t(0));
 
     docs.AddChild(doc);
-    docs.Dump(outfile);
+    docs.Dump(outfile, binary, 0);
 }
 
 void FBXExporter::WriteReferences ()
 {
+    if (!binary) {
+        WriteAsciiSectionHeader("Document References");
+    }
     // always empty for now.
     // not really sure what this is for.
     FBX::Node n("References");
-    n.Dump(outfile);
+    n.force_has_children = true;
+    n.Dump(outfile, binary, 0);
 }
 
 
@@ -468,9 +518,6 @@ size_t count_images(const aiScene* scene) {
             }
         }
     }
-    //for (auto &s : images) {
-    //    std::cout << "found image: " << s << std::endl;
-    //}
     return images.size();
 }
 
@@ -510,6 +557,11 @@ void FBXExporter::WriteDefinitions ()
     // determining how many of each type of object there are
     // and specifying the base properties to use when otherwise unspecified.
 
+    // ascii section header
+    if (!binary) {
+        WriteAsciiSectionHeader("Object definitions");
+    }
+
     // we need to count the objects
     int32_t count;
     int32_t total_count = 0;
@@ -520,7 +572,7 @@ void FBXExporter::WriteDefinitions ()
 
     // GlobalSettings
     // this seems to always be here in Maya exports
-    n = FBX::Node("ObjectType", Property("GlobalSettings"));
+    n = FBX::Node("ObjectType", "GlobalSettings");
     count = 1;
     n.AddChild("Count", count);
     object_nodes.push_back(n);
@@ -531,9 +583,9 @@ void FBXExporter::WriteDefinitions ()
     // but no harm seems to come of leaving it out.
     count = mScene->mNumAnimations;
     if (count) {
-        n = FBX::Node("ObjectType", Property("AnimationStack"));
+        n = FBX::Node("ObjectType", "AnimationStack");
         n.AddChild("Count", count);
-        pt = FBX::Node("PropertyTemplate", Property("FbxAnimStack"));
+        pt = FBX::Node("PropertyTemplate", "FbxAnimStack");
         p = FBX::Node("Properties70");
         p.AddP70string("Description", "");
         p.AddP70time("LocalStart", 0);
@@ -553,9 +605,9 @@ void FBXExporter::WriteDefinitions ()
     // so there will be one per aiAnimation
     count = mScene->mNumAnimations;
     if (count) {
-        n = FBX::Node("ObjectType", Property("AnimationLayer"));
+        n = FBX::Node("ObjectType", "AnimationLayer");
         n.AddChild("Count", count);
-        pt = FBX::Node("PropertyTemplate", Property("FBXAnimLayer"));
+        pt = FBX::Node("PropertyTemplate", "FBXAnimLayer");
         p = FBX::Node("Properties70");
         p.AddP70("Weight", "Number", "", "A", double(100));
         p.AddP70bool("Mute", 0);
@@ -583,9 +635,9 @@ void FBXExporter::WriteDefinitions ()
     count = 1; // TODO: select properly
     if (count) {
         // FbxSkeleton
-        n = FBX::Node("ObjectType", Property("NodeAttribute"));
+        n = FBX::Node("ObjectType", "NodeAttribute");
         n.AddChild("Count", count);
-        pt = FBX::Node("PropertyTemplate", Property("FbxSkeleton"));
+        pt = FBX::Node("PropertyTemplate", "FbxSkeleton");
         p = FBX::Node("Properties70");
         p.AddP70color("Color", 0.8, 0.8, 0.8);
         p.AddP70double("Size", 33.333333333333);
@@ -601,9 +653,9 @@ void FBXExporter::WriteDefinitions ()
     // <~~ node heirarchy
     count = int32_t(count_nodes(mScene->mRootNode)) - 1; // (not counting root node)
     if (count) {
-        n = FBX::Node("ObjectType", Property("Model"));
+        n = FBX::Node("ObjectType", "Model");
         n.AddChild("Count", count);
-        pt = FBX::Node("PropertyTemplate", Property("FbxNode"));
+        pt = FBX::Node("PropertyTemplate", "FbxNode");
         p = FBX::Node("Properties70");
         p.AddP70enum("QuaternionInterpolate", 0);
         p.AddP70vector("RotationOffset", 0.0, 0.0, 0.0);
@@ -698,9 +750,9 @@ void FBXExporter::WriteDefinitions ()
     // <~~ aiMesh
     count = mScene->mNumMeshes;
     if (count) {
-        n = FBX::Node("ObjectType", Property("Geometry"));
+        n = FBX::Node("ObjectType", "Geometry");
         n.AddChild("Count", count);
-        pt = FBX::Node("PropertyTemplate", Property("FbxMesh"));
+        pt = FBX::Node("PropertyTemplate", "FbxMesh");
         p = FBX::Node("Properties70");
         p.AddP70color("Color", 0, 0, 0);
         p.AddP70vector("BBoxMin", 0, 0, 0);
@@ -724,7 +776,7 @@ void FBXExporter::WriteDefinitions ()
     count = mScene->mNumMaterials;
     if (count) {
         bool has_phong = has_phong_mat(mScene);
-        n = FBX::Node("ObjectType", Property("Material"));
+        n = FBX::Node("ObjectType", "Material");
         n.AddChild("Count", count);
         pt = FBX::Node("PropertyTemplate");
         if (has_phong) {
@@ -771,9 +823,9 @@ void FBXExporter::WriteDefinitions ()
     // one for each image file.
     count = int32_t(count_images(mScene));
     if (count) {
-        n = FBX::Node("ObjectType", Property("Video"));
+        n = FBX::Node("ObjectType", "Video");
         n.AddChild("Count", count);
-        pt = FBX::Node("PropertyTemplate", Property("FbxVideo"));
+        pt = FBX::Node("PropertyTemplate", "FbxVideo");
         p = FBX::Node("Properties70");
         p.AddP70bool("ImageSequence", 0);
         p.AddP70int("ImageSequenceOffset", 0);
@@ -800,9 +852,9 @@ void FBXExporter::WriteDefinitions ()
     // <~~ aiTexture
     count = int32_t(count_textures(mScene));
     if (count) {
-        n = FBX::Node("ObjectType", Property("Texture"));
+        n = FBX::Node("ObjectType", "Texture");
         n.AddChild("Count", count);
-        pt = FBX::Node("PropertyTemplate", Property("FbxFileTexture"));
+        pt = FBX::Node("PropertyTemplate", "FbxFileTexture");
         p = FBX::Node("Properties70");
         p.AddP70enum("TextureTypeUse", 0);
         p.AddP70numberA("Texture alpha", 1.0);
@@ -829,9 +881,9 @@ void FBXExporter::WriteDefinitions ()
     // AnimationCurveNode / FbxAnimCurveNode
     count = mScene->mNumAnimations * 3;
     if (count) {
-        n = FBX::Node("ObjectType", Property("AnimationCurveNode"));
+        n = FBX::Node("ObjectType", "AnimationCurveNode");
         n.AddChild("Count", count);
-        pt = FBX::Node("PropertyTemplate", Property("FbxAnimCurveNode"));
+        pt = FBX::Node("PropertyTemplate", "FbxAnimCurveNode");
         p = FBX::Node("Properties70");
         p.AddP70("d", "Compound", "", "");
         pt.AddChild(p);
@@ -843,7 +895,7 @@ void FBXExporter::WriteDefinitions ()
     // AnimationCurve / FbxAnimCurve
     count = mScene->mNumAnimations * 9;
     if (count) {
-        n = FBX::Node("ObjectType", Property("AnimationCurve"));
+        n = FBX::Node("ObjectType", "AnimationCurve");
         n.AddChild("Count", count);
         object_nodes.push_back(n);
         total_count += count;
@@ -856,7 +908,7 @@ void FBXExporter::WriteDefinitions ()
         if (mesh->HasBones()) { ++count; }
     }
     if (count) {
-        n = FBX::Node("ObjectType", Property("Pose"));
+        n = FBX::Node("ObjectType", "Pose");
         n.AddChild("Count", count);
         object_nodes.push_back(n);
         total_count += count;
@@ -865,7 +917,7 @@ void FBXExporter::WriteDefinitions ()
     // Deformer
     count = int32_t(count_deformers(mScene));
     if (count) {
-        n = FBX::Node("ObjectType", Property("Deformer"));
+        n = FBX::Node("ObjectType", "Deformer");
         n.AddChild("Count", count);
         object_nodes.push_back(n);
         total_count += count;
@@ -874,9 +926,9 @@ void FBXExporter::WriteDefinitions ()
     // (template)
     count = 0;
     if (count) {
-        n = FBX::Node("ObjectType", Property(""));
+        n = FBX::Node("ObjectType", "");
         n.AddChild("Count", count);
-        pt = FBX::Node("PropertyTemplate", Property(""));
+        pt = FBX::Node("PropertyTemplate", "");
         p = FBX::Node("Properties70");
         pt.AddChild(p);
         n.AddChild(pt);
@@ -889,7 +941,7 @@ void FBXExporter::WriteDefinitions ()
     defs.AddChild("Version", int32_t(100));
     defs.AddChild("Count", int32_t(total_count));
     for (auto &n : object_nodes) { defs.AddChild(n); }
-    defs.Dump(outfile);
+    defs.Dump(outfile, binary, 0);
 }
 
 
@@ -935,14 +987,20 @@ int64_t to_ktime(double ticks, const aiAnimation* anim) {
 
 void FBXExporter::WriteObjects ()
 {
+    if (!binary) {
+        WriteAsciiSectionHeader("Object properties");
+    }
     // numbers should match those given in definitions! make sure to check
     StreamWriterLE outstream(outfile);
     FBX::Node object_node("Objects");
-    object_node.Begin(outstream);
-    object_node.EndProperties(outstream);
+    int indent = 0;
+    object_node.Begin(outstream, binary, indent);
+    object_node.EndProperties(outstream, binary, indent);
+    object_node.BeginChildren(outstream, binary, indent);
 
     // geometry (aiMesh)
     mesh_uids.clear();
+    indent = 1;
     for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
         // it's all about this mesh
         aiMesh* m = mScene->mMeshes[mi];
@@ -954,9 +1012,11 @@ void FBXExporter::WriteObjects ()
         n.AddProperty(uid);
         n.AddProperty(FBX::SEPARATOR + "Geometry");
         n.AddProperty("Mesh");
-        n.Begin(outstream);
-        n.DumpProperties(outstream);
-        n.EndProperties(outstream);
+        n.Begin(outstream, binary, indent);
+        n.DumpProperties(outstream, binary, indent);
+        n.EndProperties(outstream, binary, indent);
+        n.BeginChildren(outstream, binary, indent);
+        indent = 2;
 
         // output vertex data - each vertex should be unique (probably)
         std::vector<double> flattened_vertices;
@@ -980,7 +1040,7 @@ void FBXExporter::WriteObjects ()
             }
         }
         FBX::Node::WritePropertyNode(
-            "Vertices", flattened_vertices, outstream
+            "Vertices", flattened_vertices, outstream, binary, indent
         );
 
         // output polygon data as a flattened array of vertex indices.
@@ -996,30 +1056,38 @@ void FBXExporter::WriteObjects ()
             );
         }
         FBX::Node::WritePropertyNode(
-            "PolygonVertexIndex", polygon_data, outstream
+            "PolygonVertexIndex", polygon_data, outstream, binary, indent
         );
 
         // here could be edges but they're insane.
         // it's optional anyway, so let's ignore it.
 
         FBX::Node::WritePropertyNode(
-            "GeometryVersion", int32_t(124), outstream
+            "GeometryVersion", int32_t(124), outstream, binary, indent
         );
 
         // normals, if any
         if (m->HasNormals()) {
-            FBX::Node normals("LayerElementNormal", Property(int32_t(0)));
-            normals.Begin(outstream);
-            normals.DumpProperties(outstream);
-            normals.EndProperties(outstream);
-            FBX::Node::WritePropertyNode("Version", int32_t(101), outstream);
-            FBX::Node::WritePropertyNode("Name", "", outstream);
+            FBX::Node normals("LayerElementNormal", int32_t(0));
+            normals.Begin(outstream, binary, indent);
+            normals.DumpProperties(outstream, binary, indent);
+            normals.EndProperties(outstream, binary, indent);
+            normals.BeginChildren(outstream, binary, indent);
+            indent = 3;
             FBX::Node::WritePropertyNode(
-                "MappingInformationType", "ByPolygonVertex", outstream
+                "Version", int32_t(101), outstream, binary, indent
+            );
+            FBX::Node::WritePropertyNode(
+                "Name", "", outstream, binary, indent
+            );
+            FBX::Node::WritePropertyNode(
+                "MappingInformationType", "ByPolygonVertex",
+                outstream, binary, indent
             );
             // TODO: vertex-normals or indexed normals when appropriate
             FBX::Node::WritePropertyNode(
-                "ReferenceInformationType", "Direct", outstream
+                "ReferenceInformationType", "Direct",
+                outstream, binary, indent
             );
             std::vector<double> normal_data;
             normal_data.reserve(3 * polygon_data.size());
@@ -1032,10 +1100,13 @@ void FBXExporter::WriteObjects ()
                     normal_data.push_back(n.z);
                 }
             }
-            FBX::Node::WritePropertyNode("Normals", normal_data, outstream);
+            FBX::Node::WritePropertyNode(
+                "Normals", normal_data, outstream, binary, indent
+            );
             // note: version 102 has a NormalsW also... not sure what it is,
             // so we can stick with version 101 for now.
-            normals.End(outstream, true);
+            indent = 2;
+            normals.End(outstream, binary, indent, true);
         }
 
         // uvs, if any
@@ -1055,19 +1126,27 @@ void FBXExporter::WriteObjects ()
                 err << " but may be incorrectly interpreted on load.";
                 DefaultLogger::get()->warn(err.str());
             }
-            FBX::Node uv("LayerElementUV", Property(int32_t(uvi)));
-            uv.Begin(outstream);
-            uv.DumpProperties(outstream);
-            uv.EndProperties(outstream);
-            FBX::Node::WritePropertyNode("Version", int32_t(101), outstream);
+            FBX::Node uv("LayerElementUV", int32_t(uvi));
+            uv.Begin(outstream, binary, indent);
+            uv.DumpProperties(outstream, binary, indent);
+            uv.EndProperties(outstream, binary, indent);
+            uv.BeginChildren(outstream, binary, indent);
+            indent = 3;
+            FBX::Node::WritePropertyNode(
+                "Version", int32_t(101), outstream, binary, indent
+            );
             // it doesn't seem like assimp keeps the uv map name,
             // so just leave it blank.
-            FBX::Node::WritePropertyNode("Name", "", outstream);
             FBX::Node::WritePropertyNode(
-                "MappingInformationType", "ByPolygonVertex", outstream
+                "Name", "", outstream, binary, indent
+            );
+            FBX::Node::WritePropertyNode(
+                "MappingInformationType", "ByPolygonVertex",
+                outstream, binary, indent
             );
             FBX::Node::WritePropertyNode(
-                "ReferenceInformationType", "IndexToDirect", outstream
+                "ReferenceInformationType", "IndexToDirect",
+                outstream, binary, indent
             );
 
             std::vector<double> uv_data;
@@ -1092,27 +1171,32 @@ void FBXExporter::WriteObjects ()
                     }
                 }
             }
-            FBX::Node::WritePropertyNode("UV", uv_data, outstream);
-            FBX::Node::WritePropertyNode("UVIndex", uv_indices, outstream);
-            uv.End(outstream, true);
+            FBX::Node::WritePropertyNode(
+                "UV", uv_data, outstream, binary, indent
+            );
+            FBX::Node::WritePropertyNode(
+                "UVIndex", uv_indices, outstream, binary, indent
+            );
+            indent = 2;
+            uv.End(outstream, binary, indent, true);
         }
 
         // i'm not really sure why this material section exists,
         // as the material is linked via "Connections".
         // it seems to always have the same "0" value.
-        FBX::Node mat("LayerElementMaterial", Property(int32_t(0)));
+        FBX::Node mat("LayerElementMaterial", int32_t(0));
         mat.AddChild("Version", int32_t(101));
         mat.AddChild("Name", "");
         mat.AddChild("MappingInformationType", "AllSame");
         mat.AddChild("ReferenceInformationType", "IndexToDirect");
         std::vector<int32_t> mat_indices = {0};
         mat.AddChild("Materials", mat_indices);
-        mat.Dump(outstream);
+        mat.Dump(outstream, binary, indent);
 
         // finally we have the layer specifications,
         // which select the normals / UV set / etc to use.
         // TODO: handle multiple uv sets correctly?
-        FBX::Node layer("Layer", Property(int32_t(0)));
+        FBX::Node layer("Layer", int32_t(0));
         layer.AddChild("Version", int32_t(100));
         FBX::Node le("LayerElement");
         le.AddChild("Type", "LayerElementNormal");
@@ -1126,10 +1210,11 @@ void FBXExporter::WriteObjects ()
         le.AddChild("Type", "LayerElementUV");
         le.AddChild("TypedIndex", int32_t(0));
         layer.AddChild(le);
-        layer.Dump(outstream);
+        layer.Dump(outstream, binary, indent);
 
         // finish the node record
-        n.End(outstream, true);
+        indent = 1;
+        n.End(outstream, binary, indent, true);
     }
 
     // aiMaterial
@@ -1273,7 +1358,7 @@ void FBXExporter::WriteObjects ()
 
         n.AddChild(p);
 
-        n.Dump(outstream);
+        n.Dump(outstream, binary, indent);
     }
 
     // we need to look up all the images we're using,
@@ -1321,7 +1406,7 @@ void FBXExporter::WriteObjects ()
         n.AddChild("UseMipMap", int32_t(0));
         n.AddChild("Filename", path);
         n.AddChild("RelativeFilename", path);
-        n.Dump(outstream);
+        n.Dump(outstream, binary, indent);
     }
 
     // Textures
@@ -1408,14 +1493,12 @@ void FBXExporter::WriteObjects ()
             const int64_t texture_uid = generate_uid();
 
             // link the texture to the material
-            FBX::Node c("C");
-            c.AddProperties("OP", texture_uid, material_uid, prop_name);
-            connections.push_back(c);
+            connections.emplace_back(
+                "C", "OP", texture_uid, material_uid, prop_name
+            );
 
             // link the image data to the texture
-            c = FBX::Node("C");
-            c.AddProperties("OO", image_uid, texture_uid);
-            connections.push_back(c);
+            connections.emplace_back("C", "OO", image_uid, texture_uid);
 
             // now write the actual texture node
             FBX::Node tnode("Texture");
@@ -1438,11 +1521,11 @@ void FBXExporter::WriteObjects ()
             tnode.AddChild("RelativeFilename", texture_path);
             tnode.AddChild("ModelUVTranslation", double(0.0), double(0.0));
             tnode.AddChild("ModelUVScaling", double(1.0), double(1.0));
-            tnode.AddChild("Texture_Alpha_Soutce", "None");
+            tnode.AddChild("Texture_Alpha_Source", "None");
             tnode.AddChild(
                 "Cropping", int32_t(0), int32_t(0), int32_t(0), int32_t(0)
             );
-            tnode.Dump(outstream);
+            tnode.Dump(outstream, binary, indent);
         }
     }
 
@@ -1595,12 +1678,10 @@ void FBXExporter::WriteObjects ()
         // "acuracy"... this is not a typo....
         dnode.AddChild("Link_DeformAcuracy", double(50));
         dnode.AddChild("SkinningType", "Linear"); // TODO: other modes?
-        dnode.Dump(outstream);
+        dnode.Dump(outstream, binary, indent);
 
         // connect it
-        FBX::Node c("C");
-        c.AddProperties("OO", deformer_uid, mesh_uids[mi]);
-        connections.push_back(c); // TODO: emplace_back
+        connections.emplace_back("C", "OO", deformer_uid, mesh_uids[mi]);
 
         // we will be indexing by vertex...
         // but there might be a different number of "vertices"
@@ -1703,7 +1784,7 @@ void FBXExporter::WriteObjects ()
 
             // this should be the same as the bone's mOffsetMatrix.
             // if it's not the same, the skeleton isn't in the bind pose.
-            const float epsilon = 1e-5f; // some error is to be expected
+            const float epsilon = 1e-4f; // some error is to be expected
             bool bone_xform_okay = true;
             if (b && ! tr.Equal(b->mOffsetMatrix, epsilon)) {
                 not_in_bind_pose.insert(b);
@@ -1741,17 +1822,17 @@ void FBXExporter::WriteObjects ()
             // there's not really any way around this at the moment.
 
             // done
-            sdnode.Dump(outstream);
+            sdnode.Dump(outstream, binary, indent);
 
             // lastly, connect to the parent deformer
-            c = FBX::Node("C");
-            c.AddProperties("OO", subdeformer_uid, deformer_uid);
-            connections.push_back(c); // TODO: emplace_back
+            connections.emplace_back(
+                "C", "OO", subdeformer_uid, deformer_uid
+            );
 
             // we also need to connect the limb node to the subdeformer.
-            c = FBX::Node("C");
-            c.AddProperties("OO", node_uids[bone_node], subdeformer_uid);
-            connections.push_back(c); // TODO: emplace_back
+            connections.emplace_back(
+                "C", "OO", node_uids[bone_node], subdeformer_uid
+            );
         }
 
         // if we cannot create a valid FBX file, simply die.
@@ -1859,7 +1940,7 @@ void FBXExporter::WriteObjects ()
         }
 
         // now write it
-        bpnode.Dump(outstream);
+        bpnode.Dump(outstream, binary, indent);
     }*/
 
     // TODO: cameras, lights
@@ -1918,11 +1999,8 @@ void FBXExporter::WriteObjects ()
 
         // this node absurdly always pretends it has children
         // (in this case it does, but just in case...)
-        asnode.Begin(outstream);
-        asnode.DumpProperties(outstream);
-        asnode.EndProperties(outstream);
-        asnode.DumpChildren(outstream);
-        asnode.End(outstream, true);
+        asnode.force_has_children = true;
+        asnode.Dump(outstream, binary, indent);
 
         // note: animation stacks are not connected to anything
     }
@@ -1936,16 +2014,13 @@ void FBXExporter::WriteObjects ()
         alnode.AddProperties(animlayer_uid, FBX::SEPARATOR + "AnimLayer", "");
 
         // this node absurdly always pretends it has children
-        alnode.Begin(outstream);
-        alnode.DumpProperties(outstream);
-        alnode.EndProperties(outstream);
-        alnode.DumpChildren(outstream);
-        alnode.End(outstream, true);
+        alnode.force_has_children = true;
+        alnode.Dump(outstream, binary, indent);
 
         // connect to the relevant animstack
-        FBX::Node c("C");
-        c.AddProperties("OO", animlayer_uid, animation_stack_uids[ai]);
-        connections.push_back(c); // TODO: emplace_back
+        connections.emplace_back(
+            "C", "OO", animlayer_uid, animation_stack_uids[ai]
+        );
     }
 
     // AnimCurveNode - three per aiNodeAnim
@@ -2057,7 +2132,8 @@ void FBXExporter::WriteObjects ()
         }
     }
 
-    object_node.End(outstream, true);
+    indent = 0;
+    object_node.End(outstream, binary, indent, true);
 }
 
 // convenience map of magic node name strings to FBX properties,
@@ -2083,13 +2159,14 @@ const std::map<std::string,std::pair<std::string,char>> transform_types = {
 };
 
 // write a single model node to the stream
-void WriteModelNode(
+void FBXExporter::WriteModelNode(
     StreamWriterLE& outstream,
+    bool binary,
     const aiNode* node,
     int64_t node_uid,
     const std::string& type,
     const std::vector<std::pair<std::string,aiVector3D>>& transform_chain,
-    TransformInheritance inherit_type=TransformInheritance_RSrs
+    TransformInheritance inherit_type
 ){
     const aiVector3D zero = {0, 0, 0};
     const aiVector3D one = {1, 1, 1};
@@ -2157,7 +2234,7 @@ void WriteModelNode(
     m.AddChild("Shading", Property(true));
     m.AddChild("Culling", Property("CullingOff"));
 
-    m.Dump(outstream);
+    m.Dump(outstream, binary, 1);
 }
 
 // wrapper for WriteModelNodes to create and pass a blank transform chain
@@ -2181,15 +2258,6 @@ void FBXExporter::WriteModelNodes(
     // first collapse any expanded transformation chains created by FBX import.
     std::string node_name(node->mName.C_Str());
     if (node_name.find(MAGIC_NODE_TAG) != std::string::npos) {
-        if (node->mNumChildren != 1) {
-            // this should never happen
-            std::stringstream err;
-            err << "FBX transformation node should have exactly 1 child,";
-            err << " but " << node->mNumChildren << " found";
-            err << " on node \"" << node_name << "\"!";
-            throw DeadlyExportError(err.str());
-        }
-        aiNode* next_node = node->mChildren[0];
         auto pos = node_name.find(MAGIC_NODE_TAG) + MAGIC_NODE_TAG.size() + 1;
         std::string type_name = node_name.substr(pos);
         auto elem = transform_types.find(type_name);
@@ -2223,10 +2291,16 @@ void FBXExporter::WriteModelNodes(
             err << elem->second.second;
             throw DeadlyExportError(err.str());
         }
-        // now just continue to the next node
-        WriteModelNodes(
-            outstream, next_node, parent_uid, limbnodes, transform_chain
-        );
+        // now continue on to any child nodes
+        for (unsigned i = 0; i < node->mNumChildren; ++i) {
+            WriteModelNodes(
+                outstream,
+                node->mChildren[i],
+                parent_uid,
+                limbnodes,
+                transform_chain
+            );
+        }
         return;
     }
 
@@ -2240,9 +2314,7 @@ void FBXExporter::WriteModelNodes(
             node_uid = generate_uid();
             node_uids[node] = node_uid;
         }
-        FBX::Node c("C");
-        c.AddProperties("OO", node_uid, parent_uid);
-        connections.push_back(c);
+        connections.emplace_back("C", "OO", node_uid, parent_uid);
     }
 
     // what type of node is this?
@@ -2250,21 +2322,23 @@ void FBXExporter::WriteModelNodes(
         // handled later
     } else if (node->mNumMeshes == 1) {
         // connect to child mesh, which should have been written previously
-        FBX::Node c("C");
-        c.AddProperties("OO", mesh_uids[node->mMeshes[0]], node_uid);
-        connections.push_back(c);
+        connections.emplace_back(
+            "C", "OO", mesh_uids[node->mMeshes[0]], node_uid
+        );
         // also connect to the material for the child mesh
-        c = FBX::Node("C");
-        c.AddProperties(
-            "OO",
+        connections.emplace_back(
+            "C", "OO",
             material_uids[mScene->mMeshes[node->mMeshes[0]]->mMaterialIndex],
             node_uid
         );
-        connections.push_back(c);
         // write model node
-        WriteModelNode(outstream, node, node_uid, "Mesh", transform_chain);
+        WriteModelNode(
+            outstream, binary, node, node_uid, "Mesh", transform_chain
+        );
     } else if (limbnodes.count(node)) {
-        WriteModelNode(outstream, node, node_uid, "LimbNode", transform_chain);
+        WriteModelNode(
+            outstream, binary, node, node_uid, "LimbNode", transform_chain
+        );
         // we also need to write a nodeattribute to mark it as a skeleton
         int64_t node_attribute_uid = generate_uid();
         FBX::Node na("NodeAttribute");
@@ -2272,14 +2346,14 @@ void FBXExporter::WriteModelNodes(
             node_attribute_uid, FBX::SEPARATOR + "NodeAttribute", "LimbNode"
         );
         na.AddChild("TypeFlags", Property("Skeleton"));
-        na.Dump(outstream);
+        na.Dump(outstream, binary, 1);
         // and connect them
-        FBX::Node c("C");
-        c.AddProperties("OO", node_attribute_uid, node_uid);
-        connections.push_back(c);
+        connections.emplace_back("C", "OO", node_attribute_uid, node_uid);
     } else {
         // generate a null node so we can add children to it
-        WriteModelNode(outstream, node, node_uid, "Null", transform_chain);
+        WriteModelNode(
+            outstream, binary, node, node_uid, "Null", transform_chain
+        );
     }
 
     // if more than one child mesh, make nodes for each mesh
@@ -2288,23 +2362,19 @@ void FBXExporter::WriteModelNodes(
             // make a new model node
             int64_t new_node_uid = generate_uid();
             // connect to parent node
-            FBX::Node c("C");
-            c.AddProperties("OO", new_node_uid, node_uid);
-            connections.push_back(c);
+            connections.emplace_back("C", "OO", new_node_uid, node_uid);
             // connect to child mesh, which should have been written previously
-            c = FBX::Node("C");
-            c.AddProperties("OO", mesh_uids[node->mMeshes[i]], new_node_uid);
-            connections.push_back(c);
+            connections.emplace_back(
+                "C", "OO", mesh_uids[node->mMeshes[i]], new_node_uid
+            );
             // also connect to the material for the child mesh
-            c = FBX::Node("C");
-            c.AddProperties(
-                "OO",
+            connections.emplace_back(
+                "C", "OO",
                 material_uids[
                     mScene->mMeshes[node->mMeshes[i]]->mMaterialIndex
                 ],
                 new_node_uid
             );
-            connections.push_back(c);
             // write model node
             FBX::Node m("Model");
             // take name from mesh name, if it exists
@@ -2315,7 +2385,7 @@ void FBXExporter::WriteModelNodes(
             FBX::Node p("Properties70");
             p.AddP70enum("InheritType", 1);
             m.AddChild(p);
-            m.Dump(outstream);
+            m.Dump(outstream, binary, 1);
         }
     }
 
@@ -2344,15 +2414,11 @@ void FBXExporter::WriteAnimationCurveNode(
     p.AddP70numberA("d|Y", default_value.y);
     p.AddP70numberA("d|Z", default_value.z);
     n.AddChild(p);
-    n.Dump(outstream);
+    n.Dump(outstream, binary, 1);
     // connect to layer
-    FBX::Node cl("C");
-    cl.AddProperties("OO", uid, layer_uid);
-    this->connections.push_back(cl); // TODO: emplace_back
+    this->connections.emplace_back("C", "OO", uid, layer_uid);
     // connect to bone
-    FBX::Node cb("C");
-    cb.AddProperties("OP", uid, node_uid, property_name);
-    this->connections.push_back(cb); // TODO: emplace_back
+    this->connections.emplace_back("C", "OP", uid, node_uid, property_name);
 }
 
 
@@ -2379,10 +2445,10 @@ void FBXExporter::WriteAnimationCurve(
         "KeyAttrRefCount",
         std::vector<int32_t>{static_cast<int32_t>(times.size())}
     );
-    n.Dump(outstream);
-    FBX::Node c("C");
-    c.AddProperties("OP", curve_uid, curvenode_uid, property_link);
-    this->connections.push_back(c); // TODO: emplace_back
+    n.Dump(outstream, binary, 1);
+    this->connections.emplace_back(
+        "C", "OP", curve_uid, curvenode_uid, property_link
+    );
 }
 
 
@@ -2390,13 +2456,18 @@ void FBXExporter::WriteConnections ()
 {
     // we should have completed the connection graph already,
     // so basically just dump it here
+    if (!binary) {
+        WriteAsciiSectionHeader("Object connections");
+    }
+    // TODO: comments with names in the ascii version
     FBX::Node conn("Connections");
     StreamWriterLE outstream(outfile);
-    conn.Begin(outstream);
+    conn.Begin(outstream, binary, 0);
+    conn.BeginChildren(outstream, binary, 0);
     for (auto &n : connections) {
-        n.Dump(outstream);
+        n.Dump(outstream, binary, 1);
     }
-    conn.End(outstream, !connections.empty());
+    conn.End(outstream, binary, 0, !connections.empty());
     connections.clear();
 }
 

+ 14 - 0
code/FBXExporter.h

@@ -48,6 +48,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
 
 #include "FBXExportNode.h" // FBX::Node
+#include "FBXCommon.h" // FBX::TransformInheritance
 
 #include <assimp/types.h>
 //#include <assimp/material.h>
@@ -104,6 +105,9 @@ namespace Assimp
         void WriteBinaryHeader();
         void WriteBinaryFooter();
 
+        // ascii files have a comment at the top
+        void WriteAsciiHeader();
+
         // WriteAllNodes does the actual export.
         // It just calls all the Write<Section> methods below in order.
         void WriteAllNodes();
@@ -126,6 +130,7 @@ namespace Assimp
         // WriteTakes(); // deprecated since at least 2015 (fbx 7.4)
 
         // helpers
+        void WriteAsciiSectionHeader(const std::string& title);
         void WriteModelNodes(
             Assimp::StreamWriterLE& s,
             const aiNode* node,
@@ -139,6 +144,15 @@ namespace Assimp
             const std::unordered_set<const aiNode*>& limbnodes,
             std::vector<std::pair<std::string,aiVector3D>>& transform_chain
         );
+        void WriteModelNode( // nor this
+            StreamWriterLE& s,
+            bool binary,
+            const aiNode* node,
+            int64_t node_uid,
+            const std::string& type,
+            const std::vector<std::pair<std::string,aiVector3D>>& xfm_chain,
+            FBX::TransformInheritance ti_type=FBX::TransformInheritance_RSrs
+        );
         void WriteAnimationCurveNode(
             StreamWriterLE& outstream,
             int64_t uid,

+ 23 - 10
code/ScenePrivate.h

@@ -42,22 +42,25 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 /** @file Stuff to deal with aiScene::mPrivate
  */
+#pragma once
 #ifndef AI_SCENEPRIVATE_H_INCLUDED
 #define AI_SCENEPRIVATE_H_INCLUDED
 
+#include <assimp/ai_assert.h>
 #include <assimp/scene.h>
 
-namespace Assimp    {
+namespace Assimp {
 
+// Forward declarations
 class Importer;
 
 struct ScenePrivateData {
-
     ScenePrivateData()
-        : mOrigImporter()
-        , mPPStepsApplied()
-        , mIsCopy()
-    {}
+    : mOrigImporter( nullptr )
+    , mPPStepsApplied( 0 )
+    , mIsCopy( false ) {
+        // empty
+    }
 
     // Importer that originally loaded the scene though the C-API
     // If set, this object is owned by this private data instance.
@@ -75,14 +78,24 @@ struct ScenePrivateData {
 };
 
 // Access private data stored in the scene
-inline ScenePrivateData* ScenePriv(aiScene* in) {
+inline
+ScenePrivateData* ScenePriv(aiScene* in) {
+    ai_assert( nullptr != in );
+    if ( nullptr == in ) {
+        return nullptr;
+    }
     return static_cast<ScenePrivateData*>(in->mPrivate);
 }
 
-inline const ScenePrivateData* ScenePriv(const aiScene* in) {
+inline
+const ScenePrivateData* ScenePriv(const aiScene* in) {
+    ai_assert( nullptr != in );
+    if ( nullptr == in ) {
+        return nullptr;
+    }
     return static_cast<const ScenePrivateData*>(in->mPrivate);
 }
 
-}
+} // Namespace Assimp
 
-#endif
+#endif // AI_SCENEPRIVATE_H_INCLUDED

+ 3 - 5
code/glTF2Exporter.cpp

@@ -316,11 +316,9 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref<Texture>& texture, aiTe
             std::string path = tex.C_Str();
 
             if (path.size() > 0) {
-                if (path[0] != '*') {
-                    std::map<std::string, unsigned int>::iterator it = mTexturesByPath.find(path);
-                    if (it != mTexturesByPath.end()) {
-                        texture = mAsset->textures.Get(it->second);
-                    }
+                std::map<std::string, unsigned int>::iterator it = mTexturesByPath.find(path);
+                if (it != mTexturesByPath.end()) {
+                    texture = mAsset->textures.Get(it->second);
                 }
 
                 if (!texture) {

+ 78 - 0
code/simd.cpp

@@ -0,0 +1,78 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2018, 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 "simd.h"
+
+namespace Assimp {
+
+bool CPUSupportsSSE2() {
+#if defined(__x86_64__) || defined(_M_X64)
+    //* x86_64 always has SSE2 instructions */
+    return true;
+#elif defined(__GNUC__) && defined(i386)
+    // for GCC x86 we check cpuid
+    unsigned int d;
+    __asm__(
+        "pushl %%ebx\n\t"
+        "cpuid\n\t"
+        "popl %%ebx\n\t"
+        : "=d" ( d )
+        :"a" ( 1 ) );
+    return ( d & 0x04000000 ) != 0;
+#elif (defined(_MSC_VER) && defined(_M_IX86))
+    // also check cpuid for MSVC x86
+    unsigned int d;
+    __asm {
+        xor     eax, eax
+        inc eax
+        push ebx
+        cpuid
+        pop ebx
+        mov d, edx
+    }
+    return ( d & 0x04000000 ) != 0;
+#else
+    return false;
+#endif
+}
+
+} // Namespace Assimp

+ 53 - 0
code/simd.h

@@ -0,0 +1,53 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2018, 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.
+---------------------------------------------------------------------------
+*/
+#pragma once
+
+#include <assimp/defs.h>
+
+namespace Assimp {
+
+/// @brief  Checks if the platform supports SSE2 optimization
+/// @return true, if SSE2 is supported. false if SSE2 is not supported.
+bool ASSIMP_API CPUSupportsSSE2();
+
+} // Namespace Assimp

+ 1 - 1
include/assimp/StreamReader.h

@@ -283,7 +283,6 @@ public:
         return *this;
     }
 
-private:
     // ---------------------------------------------------------------------
     /** Generic read method. ByteSwap::Swap(T*) *must* be defined */
     template <typename T>
@@ -300,6 +299,7 @@ private:
         return f;
     }
 
+private:
     // ---------------------------------------------------------------------
     void InternBegin() {
         if (!stream) {

+ 6 - 2
include/assimp/StreamWriter.h

@@ -203,6 +203,12 @@ public:
         Put(n);
     }
 
+    // ---------------------------------------------------------------------
+    /** Write a single character to the stream */
+    void PutChar(char c)    {
+        Put(c);
+    }
+
     // ---------------------------------------------------------------------
     /** Write an aiString to the stream */
     void PutString(const aiString& s)
@@ -249,8 +255,6 @@ public:
         cursor = new_cursor;
     }
 
-private:
-
     // ---------------------------------------------------------------------
     /** Generic write method. ByteSwap::Swap(T*) *must* be defined */
     template <typename T>

+ 1 - 0
test/CMakeLists.txt

@@ -53,6 +53,7 @@ endif()
 LINK_DIRECTORIES( ${Assimp_BINARY_DIR} ${AssetImporter_BINARY_DIR}/lib )
 
 SET( COMMON
+  unit/utSimd.cpp
   unit/utIOSystem.cpp
   unit/utIOStreamBuffer.cpp
   unit/utIssues.cpp

+ 1 - 1
test/unit/AbstractImportExportBase.cpp

@@ -45,5 +45,5 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 using namespace ::Assimp;
 
 AbstractImportExportBase::~AbstractImportExportBase() {
-        // empty
+    // empty
 }

+ 1 - 3
test/unit/utAnim.cpp

@@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2018, assimp team
 
-
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -106,4 +104,4 @@ TEST_F( utAnim, aiAnimationTest ) {
         ok = false;
     }
     EXPECT_TRUE( ok );
-}
+}

+ 62 - 0
test/unit/utSimd.cpp

@@ -0,0 +1,62 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2018, 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 "simd.h"
+
+using namespace ::Assimp;
+
+class utSimd : public ::testing::Test {
+protected:
+    // empty
+};
+
+TEST_F( utSimd, SSE2SupportedTest ) {
+    bool isSupported;
+
+    isSupported = CPUSupportsSSE2();
+    if ( isSupported ) {
+        std::cout << "Supported" << std::endl;
+    } else {
+        std::cout << "Not supported" << std::endl;
+    }
+}

+ 78 - 29
tools/assimp_cmd/Info.cpp

@@ -46,12 +46,31 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include "Main.h"
 
+#include <cstdio>
+#include <iostream>
+#include <string>
+
 const char* AICMD_MSG_INFO_HELP_E =
 "assimp info <file> [-r] [-v]\n"
 "\tPrint basic structure of a 3D model\n"
 "\t-r,--raw: No postprocessing, do a raw import\n"
 "\t-v,--verbose: Print verbose info such as node transform data\n";
 
+const std::string TREE_BRANCH_ASCII = "|-";
+const std::string TREE_BRANCH_UTF8 = "\xe2\x94\x9c\xe2\x95\xb4";
+const std::string TREE_STOP_ASCII = "'-";
+const std::string TREE_STOP_UTF8 = "\xe2\x94\x94\xe2\x95\xb4";
+const std::string TREE_CONTINUE_ASCII = "| ";
+const std::string TREE_CONTINUE_UTF8 = "\xe2\x94\x82 ";
+
+// note: by default this is outputing utf-8 text.
+// this is well supported on pretty much any linux terminal.
+// if this causes problems on some platform,
+// put an #ifdef to use the ascii version for that platform.
+const std::string TREE_BRANCH = TREE_BRANCH_UTF8;
+const std::string TREE_STOP = TREE_STOP_UTF8;
+const std::string TREE_CONTINUE = TREE_CONTINUE_UTF8;
+
 
 // -----------------------------------------------------------------------------------
 unsigned int CountNodes(const aiNode* root)
@@ -184,46 +203,77 @@ std::string FindPTypes(const aiScene* scene)
 }
 
 // -----------------------------------------------------------------------------------
-void PrintHierarchy(const aiNode* root, unsigned int maxnest, unsigned int maxline,
-					unsigned int cline, bool verbose, unsigned int cnest=0)
-{
-	if (cline++ >= maxline || cnest >= maxnest) {
-		return;
+// Prettily print the node graph to stdout
+void PrintHierarchy(
+	const aiNode* node,
+	const std::string &indent,
+	bool verbose,
+	bool last = false,
+	bool first = true
+){
+	// tree visualization
+	std::string branchchar;
+	if (first) { branchchar = ""; }
+	else if (last) { branchchar = TREE_STOP; } // "'-"
+	else { branchchar = TREE_BRANCH; } // "|-"
+
+	// print the indent and the branch character and the name
+	std::cout << indent << branchchar << node->mName.C_Str();
+
+	// if there are meshes attached, indicate this
+	if (node->mNumMeshes) {
+		std::cout << " (mesh ";
+		bool sep = false;
+		for (size_t i=0; i < node->mNumMeshes; ++i) {
+			unsigned int mesh_index = node->mMeshes[i];
+			if (sep) { std::cout << ", "; }
+			std::cout << mesh_index;
+			sep = true;
+		}
+		std::cout << ")";
 	}
 
-	for(unsigned int i = 0; i < cnest; ++i) {
-		printf("-- ");
-	}
-	printf("\'%s\', meshes: %u\n",root->mName.data,root->mNumMeshes);
+	// finish the line
+	std::cout << std::endl;
 
+	// in verbose mode, print the transform data as well
 	if (verbose) {
-		// print the actual transform
-		//printf(",");
+		// indent to use
+		std::string indentadd;
+		if (last) { indentadd += "  "; }
+		else { indentadd += TREE_CONTINUE; } // "| "..
+		if (node->mNumChildren == 0) { indentadd += "  "; }
+		else { indentadd += TREE_CONTINUE; } // .."| "
 		aiVector3D s, r, t;
-		root->mTransformation.Decompose(s, r, t);
+		node->mTransformation.Decompose(s, r, t);
 		if (s.x != 1.0 || s.y != 1.0 || s.z != 1.0) {
-			for(unsigned int i = 0; i < cnest; ++i) { printf("   "); }
-			printf("      S:[%f %f %f]\n", s.x, s.y, s.z);
+			std::cout << indent << indentadd;
+			printf("  S:[%f %f %f]\n", s.x, s.y, s.z);
 		}
 		if (r.x || r.y || r.z) {
-			for(unsigned int i = 0; i < cnest; ++i) { printf("   "); }
-			printf("      R:[%f %f %f]\n", r.x, r.y, r.z);
+			std::cout << indent << indentadd;
+			printf("  R:[%f %f %f]\n", r.x, r.y, r.z);
 		}
 		if (t.x || t.y || t.z) {
-			for(unsigned int i = 0; i < cnest; ++i) { printf("   "); }
-			printf("      T:[%f %f %f]\n", t.x, t.y, t.z);
+			std::cout << indent << indentadd;
+			printf("  T:[%f %f %f]\n", t.x, t.y, t.z);
 		}
 	}
-	//printf("\n");
 
-	for (unsigned int i = 0; i < root->mNumChildren; ++i ) {
-		PrintHierarchy(root->mChildren[i],maxnest,maxline,cline,verbose,cnest+1);
-		if(i == root->mNumChildren-1) {
-			for(unsigned int i = 0; i < cnest; ++i) {
-				printf("   ");
-			}
-			printf("<--\n");
-		}
+	// and recurse
+	std::string nextIndent;
+	if (first) { nextIndent = indent; }
+	else if (last) { nextIndent = indent + "  "; }
+	else { nextIndent = indent + TREE_CONTINUE; } // "| "
+	for (size_t i = 0; i < node->mNumChildren; ++i) {
+		bool lastone = (i == node->mNumChildren - 1);
+		PrintHierarchy(
+			node->mChildren[i],
+			nextIndent,
+			verbose,
+			lastone,
+			false
+		);
 	}
 }
 
@@ -406,8 +456,7 @@ int Assimp_Info (const char* const* params, unsigned int num)
 
 	// node hierarchy
 	printf("\nNode hierarchy:\n");
-	unsigned int cline=0;
-	PrintHierarchy(scene->mRootNode,20,1000,cline,verbose);
+	PrintHierarchy(scene->mRootNode,"",verbose);
 
 	printf("\n");
 	return 0;

+ 29 - 1
tools/assimp_qt_viewer/glview.cpp

@@ -560,6 +560,30 @@ void CGLView::Enable_Textures(const bool pEnable)
 	}
 }
 
+void CGLView::Enable_Axes(const bool pEnable){
+	if(pEnable)
+	{
+        this->mAxesEnabled = true;
+    }
+    else
+    {
+        this->mAxesEnabled = false;
+    }
+}
+
+void CGLView::Enable_Reload_Textures(const bool pEnable)
+{
+	if(pEnable)
+	{
+        this->mReloadTexturesEnabled = true;
+//      this->mScene->ImportTextures(this->mScene->pScenePath);
+	}
+    else
+    {
+        this->mReloadTexturesEnabled = false;
+    }
+}
+
 /********************************************************************/
 /*********************** Override functions ************************/
 /********************************************************************/
@@ -609,6 +633,7 @@ void CGLView::drawCoordSystem() {
     // Z, -Z
     qglColor(QColor(Qt::blue)), glVertex3f(0.0, 0.0, 0.0), glVertex3f(0.0, 0.0, 100000.0);
     qglColor(QColor(Qt::yellow)), glVertex3f(0.0, 0.0, 0.0), glVertex3f(0.0, 0.0, -100000.0);
+    qglColor(QColor(Qt::white));
     glEnd();
 }
 
@@ -629,7 +654,10 @@ void CGLView::paintGL()
     if ( mLightingEnabled ) {
         glDisable( GL_LIGHTING );///TODO: display list
     }
-    drawCoordSystem();
+    if (this->mAxesEnabled == true)
+    {
+        drawCoordSystem();
+    }
 
 	glDisable(GL_COLOR_MATERIAL);
 	if(mLightingEnabled) glEnable(GL_LIGHTING);

+ 9 - 2
tools/assimp_qt_viewer/glview.hpp

@@ -75,7 +75,9 @@ private:
 	};
 
 public:
-
+    bool mAxesEnabled = true;
+	// Textures
+    bool mReloadTexturesEnabled = false; // If true then textures will reload when the window is activated.
 	/// \enum ELightType
 	/// Type of light source.
 	enum class ELightType { Directional, Point, Spot };
@@ -155,7 +157,6 @@ private:
 	GLdouble mCamera_Viewport_AspectRatio;///< Specifies the aspect ratio that determines the field of view in the x direction. The aspect ratio is the ratio of x (width) to y (height).
 	// Lighting
 	bool mLightingEnabled = false;///< If true then OpenGL lighting is enabled (glEnable(GL_LIGHTING)), if false - disabled.
-	// Textures
 	///TODO: map is goooood, but not for case when one image can be used in different materials with difference in: texture transformation, targeting of the
 	/// texture (ambient or emission, or even height map), texture properties.
 	QMap<QString, GLuint> mTexture_IDMap;///< Map image filenames to textures ID's.
@@ -306,6 +307,12 @@ public:
 	/// \param [in] pEnable - if true then enable textures, false - disable textures.
 	void Enable_Textures(const bool pEnable);
 
+	void Enable_Axes(const bool pEnable);
+	/// \fn void Enable_Textures(const bool pEnable)
+	/// Control textures drawing.
+	/// \param [in] pEnable - if true then enable textures, false - disable textures.
+	void Enable_Reload_Textures(const bool pEnable);
+
 	/********************************************************************/
 	/******************** Lighting control functions ********************/
 	/********************************************************************/

+ 20 - 0
tools/assimp_qt_viewer/mainwindow.cpp

@@ -48,6 +48,7 @@ QTime time_begin = QTime::currentTime();
 		ui->cbxLighting->setChecked(true);	mGLView->Lighting_Enable();
 		ui->cbxBBox->setChecked(false);		mGLView->Enable_SceneBBox(false);
 		ui->cbxTextures->setChecked(true);	mGLView->Enable_Textures(true);
+		ui->cbxReloadTextures->setChecked(true);	mGLView->Enable_Reload_Textures(false);
 		//
 		// Fill info labels
 		//
@@ -194,6 +195,13 @@ GLfloat step;
 /********************************************************************/
 /********************** Constructor/Destructor **********************/
 /********************************************************************/
+bool MainWindow::event(QEvent *e)
+{
+    if (e->type() == QEvent::WindowActivate && this->mGLView->mReloadTexturesEnabled == true) {
+	    qInfo() << "Window Activated";
+    }
+    return QWidget::event(e);
+}
 
 MainWindow::MainWindow(QWidget *parent)
 	: QMainWindow(parent), ui(new Ui::MainWindow),
@@ -364,6 +372,18 @@ void MainWindow::on_cbxBBox_clicked(bool checked)
 	mGLView->updateGL();
 }
 
+void MainWindow::on_cbxDrawAxes_clicked(bool checked)
+{
+	mGLView->Enable_Axes(checked);
+	mGLView->updateGL();
+}
+
+void MainWindow::on_cbxReloadTextures_clicked(bool checked)
+{
+	mGLView->Enable_Reload_Textures(checked);
+	mGLView->updateGL();
+}
+
 void MainWindow::on_cbxTextures_clicked(bool checked)
 {
 	mGLView->Enable_Textures(checked);

+ 3 - 1
tools/assimp_qt_viewer/mainwindow.hpp

@@ -90,7 +90,7 @@ protected:
 	/// \param [in] pEvent - pointer to event data.
 	void keyPressEvent(QKeyEvent* pEvent) override;
 
-
+	bool event(QEvent*);
 public:
 
 	/********************************************************************/
@@ -133,4 +133,6 @@ private slots:
 	void on_lstCamera_clicked(const QModelIndex &index);
 	void on_cbxBBox_clicked(bool checked);
 	void on_cbxTextures_clicked(bool checked);
+	void on_cbxDrawAxes_clicked(bool checked);
+	void on_cbxReloadTextures_clicked(bool checked);
 };

+ 21 - 1
tools/assimp_qt_viewer/mainwindow.ui

@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>641</width>
-    <height>734</height>
+    <height>778</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -501,6 +501,23 @@
             </property>
            </widget>
           </item>
+          <item row="3" column="0">
+           <widget class="QCheckBox" name="cbxDrawAxes">
+            <property name="text">
+             <string>Show Axes</string>
+            </property>
+            <property name="checked">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+          <item row="4" column="0">
+           <widget class="QCheckBox" name="cbxReloadTextures">
+            <property name="text">
+             <string>Live Reload Textures</string>
+            </property>
+           </widget>
+          </item>
          </layout>
         </widget>
        </widget>
@@ -513,4 +530,7 @@
  <layoutdefault spacing="6" margin="11"/>
  <resources/>
  <connections/>
+ <slots>
+  <signal>installEventFilter()</signal>
+ </slots>
 </ui>