+ * The corresponding functions for use with the plain-c API are:
+ * #aiSetImportPropertyInteger,
+ * #aiSetImportPropertyFloat,
+ * #aiSetImportPropertyString
+ */
+#pragma once
+#ifndef AI_CONFIG_H_INC
+#define AI_CONFIG_H_INC
+
+
+// ###########################################################################
+// LIBRARY SETTINGS
+// General, global settings
+// ###########################################################################
+
+// ---------------------------------------------------------------------------
+/** @brief Enables time measurements.
+ *
+ * If enabled, measures the time needed for each part of the loading
+ * process (i.e. IO time, importing, postprocessing, ..) and dumps
+ * these timings to the DefaultLogger. See the @link perf Performance
+ * Page@endlink for more information on this topic.
+ *
+ * Property type: bool. Default value: false.
+ */
+#define AI_CONFIG_GLOB_MEASURE_TIME \
+ "GLOB_MEASURE_TIME"
+
+
+// ---------------------------------------------------------------------------
+/** @brief Global setting to disable generation of skeleton dummy meshes
+ *
+ * Skeleton dummy meshes are generated as a visualization aid in cases which
+ * the input data contains no geometry, but only animation data.
+ * Property data type: bool. Default value: false
+ */
+// ---------------------------------------------------------------------------
+#define AI_CONFIG_IMPORT_NO_SKELETON_MESHES \
+ "IMPORT_NO_SKELETON_MESHES"
+
+
+
+# if 0 // not implemented yet
+// ---------------------------------------------------------------------------
+/** @brief Set Assimp's multithreading policy.
+ *
+ * This setting is ignored if Assimp was built without boost.thread
+ * support (ASSIMP_BUILD_NO_THREADING, which is implied by ASSIMP_BUILD_BOOST_WORKAROUND).
+ * Possible values are: -1 to let Assimp decide what to do, 0 to disable
+ * multithreading entirely and any number larger than 0 to force a specific
+ * number of threads. Assimp is always free to ignore this settings, which is
+ * merely a hint. Usually, the default value (-1) will be fine. However, if
+ * Assimp is used concurrently from multiple user threads, it might be useful
+ * to limit each Importer instance to a specific number of cores.
+ *
+ * For more information, see the @link threading Threading page@endlink.
+ * Property type: int, default value: -1.
+ */
+#define AI_CONFIG_GLOB_MULTITHREADING \
+ "GLOB_MULTITHREADING"
+#endif
+
+// ###########################################################################
+// POST PROCESSING SETTINGS
+// Various stuff to fine-tune the behavior of a specific post processing step.
+// ###########################################################################
+
+
+// ---------------------------------------------------------------------------
+/** @brief Maximum bone count per mesh for the SplitbyBoneCount step.
+ *
+ * Meshes are split until the maximum number of bones is reached. The default
+ * value is AI_SBBC_DEFAULT_MAX_BONES, which may be altered at
+ * compile-time.
+ * Property data type: integer.
+ */
+// ---------------------------------------------------------------------------
+#define AI_CONFIG_PP_SBBC_MAX_BONES \
+ "PP_SBBC_MAX_BONES"
+
+
+// default limit for bone count
+#if (!defined AI_SBBC_DEFAULT_MAX_BONES)
+# define AI_SBBC_DEFAULT_MAX_BONES 60
+#endif
+
+
+// ---------------------------------------------------------------------------
+/** @brief Specifies the maximum angle that may be between two vertex tangents
+ * that their tangents and bi-tangents are smoothed.
+ *
+ * This applies to the CalcTangentSpace-Step. The angle is specified
+ * in degrees. The maximum value is 175.
+ * Property type: float. Default value: 45 degrees
+ */
+#define AI_CONFIG_PP_CT_MAX_SMOOTHING_ANGLE \
+ "PP_CT_MAX_SMOOTHING_ANGLE"
+
+// ---------------------------------------------------------------------------
+/** @brief Source UV channel for tangent space computation.
+ *
+ * The specified channel must exist or an error will be raised.
+ * Property type: integer. Default value: 0
+ */
+// ---------------------------------------------------------------------------
+#define AI_CONFIG_PP_CT_TEXTURE_CHANNEL_INDEX \
+ "PP_CT_TEXTURE_CHANNEL_INDEX"
+
+// ---------------------------------------------------------------------------
+/** @brief Specifies the maximum angle that may be between two face normals
+ * at the same vertex position that their are smoothed together.
+ *
+ * Sometimes referred to as 'crease angle'.
+ * This applies to the GenSmoothNormals-Step. The angle is specified
+ * in degrees, so 180 is PI. The default value is 175 degrees (all vertex
+ * normals are smoothed). The maximum value is 175, too. Property type: float.
+ * Warning: setting this option may cause a severe loss of performance. The
+ * performance is unaffected if the #AI_CONFIG_FAVOUR_SPEED flag is set but
+ * the output quality may be reduced.
+ */
+#define AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE \
+ "PP_GSN_MAX_SMOOTHING_ANGLE"
+
+
+// ---------------------------------------------------------------------------
+/** @brief Sets the colormap (= palette) to be used to decode embedded
+ * textures in MDL (Quake or 3DGS) files.
+ *
+ * This must be a valid path to a file. The file is 768 (256*3) bytes
+ * large and contains RGB triplets for each of the 256 palette entries.
+ * The default value is colormap.lmp. If the file is not found,
+ * a default palette (from Quake 1) is used.
+ * Property type: string.
+ */
+#define AI_CONFIG_IMPORT_MDL_COLORMAP \
+ "IMPORT_MDL_COLORMAP"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the #aiProcess_RemoveRedundantMaterials step to
+ * keep materials matching a name in a given list.
+ *
+ * This is a list of 1 to n strings, ' ' serves as delimiter character.
+ * Identifiers containing whitespaces must be enclosed in *single*
+ * quotation marks. For example:
+ * "keep-me and_me_to anotherMaterialToBeKept \'name with whitespace\'".
+ * If a material matches on of these names, it will not be modified or
+ * removed by the postprocessing step nor will other materials be replaced
+ * by a reference to it.
+ * This option might be useful if you are using some magic material names
+ * to pass additional semantics through the content pipeline. This ensures
+ * they won't be optimized away, but a general optimization is still
+ * performed for materials not contained in the list.
+ * Property type: String. Default value: n/a
+ * @note Linefeeds, tabs or carriage returns are treated as whitespace.
+ * Material names are case sensitive.
+ */
+#define AI_CONFIG_PP_RRM_EXCLUDE_LIST \
+ "PP_RRM_EXCLUDE_LIST"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the #aiProcess_PreTransformVertices step to
+ * keep the scene hierarchy. Meshes are moved to worldspace, but
+ * no optimization is performed (read: meshes with equal materials are not
+ * joined. The total number of meshes won't change).
+ *
+ * This option could be of use for you if the scene hierarchy contains
+ * important additional information which you intend to parse.
+ * For rendering, you can still render all meshes in the scene without
+ * any transformations.
+ * Property type: bool. Default value: false.
+ */
+#define AI_CONFIG_PP_PTV_KEEP_HIERARCHY \
+ "PP_PTV_KEEP_HIERARCHY"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the #aiProcess_PreTransformVertices step to normalize
+ * all vertex components into the [-1,1] range. That is, a bounding box
+ * for the whole scene is computed, the maximum component is taken and all
+ * meshes are scaled appropriately (uniformly of course!).
+ * This might be useful if you don't know the spatial dimension of the input
+ * data*/
+#define AI_CONFIG_PP_PTV_NORMALIZE \
+ "PP_PTV_NORMALIZE"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the #aiProcess_PreTransformVertices step to use
+ * a users defined matrix as the scene root node transformation before
+ * transforming vertices.
+ * Property type: bool. Default value: false.
+ */
+#define AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION \
+ "PP_PTV_ADD_ROOT_TRANSFORMATION"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the #aiProcess_PreTransformVertices step to use
+ * a users defined matrix as the scene root node transformation before
+ * transforming vertices. This property correspond to the 'a1' component
+ * of the transformation matrix.
+ * Property type: aiMatrix4x4.
+ */
+#define AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION \
+ "PP_PTV_ROOT_TRANSFORMATION"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the #aiProcess_FindDegenerates step to
+ * remove degenerated primitives from the import - immediately.
+ *
+ * The default behaviour converts degenerated triangles to lines and
+ * degenerated lines to points. See the documentation to the
+ * #aiProcess_FindDegenerates step for a detailed example of the various ways
+ * to get rid of these lines and points if you don't want them.
+ * Property type: bool. Default value: false.
+ */
+#define AI_CONFIG_PP_FD_REMOVE \
+ "PP_FD_REMOVE"
+
+// ---------------------------------------------------------------------------
+/**
+ * @brief Configures the #aiProcess_FindDegenerates to check the area of a
+ * trinagle to be greates than e-6. If this is not the case the triangle will
+ * be removed if #AI_CONFIG_PP_FD_REMOVE is set to true.
+ */
+#define AI_CONFIG_PP_FD_CHECKAREA \
+ "PP_FD_CHECKAREA"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the #aiProcess_OptimizeGraph step to preserve nodes
+ * matching a name in a given list.
+ *
+ * This is a list of 1 to n strings, ' ' serves as delimiter character.
+ * Identifiers containing whitespaces must be enclosed in *single*
+ * quotation marks. For example:
+ * "keep-me and_me_to anotherNodeToBeKept \'name with whitespace\'".
+ * If a node matches on of these names, it will not be modified or
+ * removed by the postprocessing step.
+ * This option might be useful if you are using some magic node names
+ * to pass additional semantics through the content pipeline. This ensures
+ * they won't be optimized away, but a general optimization is still
+ * performed for nodes not contained in the list.
+ * Property type: String. Default value: n/a
+ * @note Linefeeds, tabs or carriage returns are treated as whitespace.
+ * Node names are case sensitive.
+ */
+#define AI_CONFIG_PP_OG_EXCLUDE_LIST \
+ "PP_OG_EXCLUDE_LIST"
+
+// ---------------------------------------------------------------------------
+/** @brief Set the maximum number of triangles in a mesh.
+ *
+ * This is used by the "SplitLargeMeshes" PostProcess-Step to determine
+ * whether a mesh must be split or not.
+ * @note The default value is AI_SLM_DEFAULT_MAX_TRIANGLES
+ * Property type: integer.
+ */
+#define AI_CONFIG_PP_SLM_TRIANGLE_LIMIT \
+ "PP_SLM_TRIANGLE_LIMIT"
+
+// default value for AI_CONFIG_PP_SLM_TRIANGLE_LIMIT
+#if (!defined AI_SLM_DEFAULT_MAX_TRIANGLES)
+# define AI_SLM_DEFAULT_MAX_TRIANGLES 1000000
+#endif
+
+// ---------------------------------------------------------------------------
+/** @brief Set the maximum number of vertices in a mesh.
+ *
+ * This is used by the "SplitLargeMeshes" PostProcess-Step to determine
+ * whether a mesh must be split or not.
+ * @note The default value is AI_SLM_DEFAULT_MAX_VERTICES
+ * Property type: integer.
+ */
+#define AI_CONFIG_PP_SLM_VERTEX_LIMIT \
+ "PP_SLM_VERTEX_LIMIT"
+
+// default value for AI_CONFIG_PP_SLM_VERTEX_LIMIT
+#if (!defined AI_SLM_DEFAULT_MAX_VERTICES)
+# define AI_SLM_DEFAULT_MAX_VERTICES 1000000
+#endif
+
+// ---------------------------------------------------------------------------
+/** @brief Set the maximum number of bones affecting a single vertex
+ *
+ * This is used by the #aiProcess_LimitBoneWeights PostProcess-Step.
+ * @note The default value is AI_LMW_MAX_WEIGHTS
+ * Property type: integer.*/
+#define AI_CONFIG_PP_LBW_MAX_WEIGHTS \
+ "PP_LBW_MAX_WEIGHTS"
+
+// default value for AI_CONFIG_PP_LBW_MAX_WEIGHTS
+#if (!defined AI_LMW_MAX_WEIGHTS)
+# define AI_LMW_MAX_WEIGHTS 0x4
+#endif // !! AI_LMW_MAX_WEIGHTS
+
+// ---------------------------------------------------------------------------
+/** @brief Lower the deboning threshold in order to remove more bones.
+ *
+ * This is used by the #aiProcess_Debone PostProcess-Step.
+ * @note The default value is AI_DEBONE_THRESHOLD
+ * Property type: float.*/
+#define AI_CONFIG_PP_DB_THRESHOLD \
+ "PP_DB_THRESHOLD"
+
+// default value for AI_CONFIG_PP_LBW_MAX_WEIGHTS
+#if (!defined AI_DEBONE_THRESHOLD)
+# define AI_DEBONE_THRESHOLD 1.0f
+#endif // !! AI_DEBONE_THRESHOLD
+
+// ---------------------------------------------------------------------------
+/** @brief Require all bones qualify for deboning before removing any
+ *
+ * This is used by the #aiProcess_Debone PostProcess-Step.
+ * @note The default value is 0
+ * Property type: bool.*/
+#define AI_CONFIG_PP_DB_ALL_OR_NONE \
+ "PP_DB_ALL_OR_NONE"
+
+/** @brief Default value for the #AI_CONFIG_PP_ICL_PTCACHE_SIZE property
+ */
+#ifndef PP_ICL_PTCACHE_SIZE
+# define PP_ICL_PTCACHE_SIZE 12
+#endif
+
+// ---------------------------------------------------------------------------
+/** @brief Set the size of the post-transform vertex cache to optimize the
+ * vertices for. This configures the #aiProcess_ImproveCacheLocality step.
+ *
+ * The size is given in vertices. Of course you can't know how the vertex
+ * format will exactly look like after the import returns, but you can still
+ * guess what your meshes will probably have.
+ * @note The default value is #PP_ICL_PTCACHE_SIZE. That results in slight
+ * performance improvements for most nVidia/AMD cards since 2002.
+ * Property type: integer.
+ */
+#define AI_CONFIG_PP_ICL_PTCACHE_SIZE "PP_ICL_PTCACHE_SIZE"
+
+// ---------------------------------------------------------------------------
+/** @brief Enumerates components of the aiScene and aiMesh data structures
+ * that can be excluded from the import using the #aiProcess_RemoveComponent step.
+ *
+ * See the documentation to #aiProcess_RemoveComponent for more details.
+ */
+enum aiComponent
+{
+ /** Normal vectors */
+#ifdef SWIG
+ aiComponent_NORMALS = 0x2,
+#else
+ aiComponent_NORMALS = 0x2u,
+#endif
+
+ /** Tangents and bitangents go always together ... */
+#ifdef SWIG
+ aiComponent_TANGENTS_AND_BITANGENTS = 0x4,
+#else
+ aiComponent_TANGENTS_AND_BITANGENTS = 0x4u,
+#endif
+
+ /** ALL color sets
+ * Use aiComponent_COLORn(N) to specify the N'th set */
+ aiComponent_COLORS = 0x8,
+
+ /** ALL texture UV sets
+ * aiComponent_TEXCOORDn(N) to specify the N'th set */
+ aiComponent_TEXCOORDS = 0x10,
+
+ /** Removes all bone weights from all meshes.
+ * The scenegraph nodes corresponding to the bones are NOT removed.
+ * use the #aiProcess_OptimizeGraph step to do this */
+ aiComponent_BONEWEIGHTS = 0x20,
+
+ /** Removes all node animations (aiScene::mAnimations).
+ * The corresponding scenegraph nodes are NOT removed.
+ * use the #aiProcess_OptimizeGraph step to do this */
+ aiComponent_ANIMATIONS = 0x40,
+
+ /** Removes all embedded textures (aiScene::mTextures) */
+ aiComponent_TEXTURES = 0x80,
+
+ /** Removes all light sources (aiScene::mLights).
+ * The corresponding scenegraph nodes are NOT removed.
+ * use the #aiProcess_OptimizeGraph step to do this */
+ aiComponent_LIGHTS = 0x100,
+
+ /** Removes all cameras (aiScene::mCameras).
+ * The corresponding scenegraph nodes are NOT removed.
+ * use the #aiProcess_OptimizeGraph step to do this */
+ aiComponent_CAMERAS = 0x200,
+
+ /** Removes all meshes (aiScene::mMeshes). */
+ aiComponent_MESHES = 0x400,
+
+ /** Removes all materials. One default material will
+ * be generated, so aiScene::mNumMaterials will be 1. */
+ aiComponent_MATERIALS = 0x800,
+
+
+ /** This value is not used. It is just there to force the
+ * compiler to map this enum to a 32 Bit integer. */
+#ifndef SWIG
+ _aiComponent_Force32Bit = 0x9fffffff
+#endif
+};
+
+// Remove a specific color channel 'n'
+#define aiComponent_COLORSn(n) (1u << (n+20u))
+
+// Remove a specific UV channel 'n'
+#define aiComponent_TEXCOORDSn(n) (1u << (n+25u))
+
+// ---------------------------------------------------------------------------
+/** @brief Input parameter to the #aiProcess_RemoveComponent step:
+ * Specifies the parts of the data structure to be removed.
+ *
+ * See the documentation to this step for further details. The property
+ * is expected to be an integer, a bitwise combination of the
+ * #aiComponent flags defined above in this header. The default
+ * value is 0. Important: if no valid mesh is remaining after the
+ * step has been executed (e.g you thought it was funny to specify ALL
+ * of the flags defined above) the import FAILS. Mainly because there is
+ * no data to work on anymore ...
+ */
+#define AI_CONFIG_PP_RVC_FLAGS \
+ "PP_RVC_FLAGS"
+
+// ---------------------------------------------------------------------------
+/** @brief Input parameter to the #aiProcess_SortByPType step:
+ * Specifies which primitive types are removed by the step.
+ *
+ * This is a bitwise combination of the aiPrimitiveType flags.
+ * Specifying all of them is illegal, of course. A typical use would
+ * be to exclude all line and point meshes from the import. This
+ * is an integer property, its default value is 0.
+ */
+#define AI_CONFIG_PP_SBP_REMOVE \
+ "PP_SBP_REMOVE"
+
+// ---------------------------------------------------------------------------
+/** @brief Input parameter to the #aiProcess_FindInvalidData step:
+ * Specifies the floating-point accuracy for animation values. The step
+ * checks for animation tracks where all frame values are absolutely equal
+ * and removes them. This tweakable controls the epsilon for floating-point
+ * comparisons - two keys are considered equal if the invariant
+ * abs(n0-n1)>epsilon holds true for all vector respectively quaternion
+ * components. The default value is 0.f - comparisons are exact then.
+ */
+#define AI_CONFIG_PP_FID_ANIM_ACCURACY \
+ "PP_FID_ANIM_ACCURACY"
+
+// ---------------------------------------------------------------------------
+/** @brief Input parameter to the #aiProcess_FindInvalidData step:
+ * Set to true to ignore texture coordinates. This may be useful if you have
+ * to assign different kind of textures like one for the summer or one for the winter.
+ */
+#define AI_CONFIG_PP_FID_IGNORE_TEXTURECOORDS \
+ "PP_FID_IGNORE_TEXTURECOORDS"
+
+// TransformUVCoords evaluates UV scalings
+#define AI_UVTRAFO_SCALING 0x1
+
+// TransformUVCoords evaluates UV rotations
+#define AI_UVTRAFO_ROTATION 0x2
+
+// TransformUVCoords evaluates UV translation
+#define AI_UVTRAFO_TRANSLATION 0x4
+
+// Everything baked together -> default value
+#define AI_UVTRAFO_ALL (AI_UVTRAFO_SCALING | AI_UVTRAFO_ROTATION | AI_UVTRAFO_TRANSLATION)
+
+// ---------------------------------------------------------------------------
+/** @brief Input parameter to the #aiProcess_TransformUVCoords step:
+ * Specifies which UV transformations are evaluated.
+ *
+ * This is a bitwise combination of the AI_UVTRAFO_XXX flags (integer
+ * property, of course). By default all transformations are enabled
+ * (AI_UVTRAFO_ALL).
+ */
+#define AI_CONFIG_PP_TUV_EVALUATE \
+ "PP_TUV_EVALUATE"
+
+// ---------------------------------------------------------------------------
+/** @brief A hint to assimp to favour speed against import quality.
+ *
+ * Enabling this option may result in faster loading, but it needn't.
+ * It represents just a hint to loaders and post-processing steps to use
+ * faster code paths, if possible.
+ * This property is expected to be an integer, != 0 stands for true.
+ * The default value is 0.
+ */
+#define AI_CONFIG_FAVOUR_SPEED \
+ "FAVOUR_SPEED"
+
+
+// ###########################################################################
+// IMPORTER SETTINGS
+// Various stuff to fine-tune the behaviour of specific importer plugins.
+// ###########################################################################
+
+// ---------------------------------------------------------------------------
+/** @brief Importers which parse JSON may use this to obtain a pointer to a
+ * rapidjson::IRemoteSchemaDocumentProvider.
+ *
+ * The default value is nullptr
+ * Property type: void*
+ */
+#define AI_CONFIG_IMPORT_SCHEMA_DOCUMENT_PROVIDER \
+ "IMPORT_SCHEMA_DOCUMENT_PROVIDER"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will merge all geometry layers present
+ * in the source file or take only the first.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS \
+ "IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will read all materials present in the
+ * source file or take only the referenced materials.
+ *
+ * This is void unless IMPORT_FBX_READ_MATERIALS=1.
+ *
+ * The default value is false (0)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS \
+ "IMPORT_FBX_READ_ALL_MATERIALS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will read materials.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_READ_MATERIALS \
+ "IMPORT_FBX_READ_MATERIALS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will read embedded textures.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_READ_TEXTURES \
+ "IMPORT_FBX_READ_TEXTURES"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will read cameras.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_READ_CAMERAS \
+ "IMPORT_FBX_READ_CAMERAS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will read light sources.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_READ_LIGHTS \
+ "IMPORT_FBX_READ_LIGHTS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will read animations.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS \
+ "IMPORT_FBX_READ_ANIMATIONS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will read weights.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_READ_WEIGHTS \
+ "IMPORT_FBX_READ_WEIGHTS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will act in strict mode in which only
+ * FBX 2013 is supported and any other sub formats are rejected. FBX 2013
+ * is the primary target for the importer, so this format is best
+ * supported and well-tested.
+ *
+ * The default value is false (0)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_STRICT_MODE \
+ "IMPORT_FBX_STRICT_MODE"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will preserve pivot points for
+ * transformations (as extra nodes). If set to false, pivots and offsets
+ * will be evaluated whenever possible.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS \
+ "IMPORT_FBX_PRESERVE_PIVOTS"
+
+// ---------------------------------------------------------------------------
+/** @brief Specifies whether the importer will drop empty animation curves or
+ * animation curves which match the bind pose transformation over their
+ * entire defined range.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES \
+ "IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will use the legacy embedded texture naming.
+ *
+ * The default value is false (0)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING \
+ "AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING"
+
+// ---------------------------------------------------------------------------
+/** @brief Set wether the importer shall not remove empty bones.
+ *
+ * Empty bone are often used to define connections for other models.
+ */
+#define AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES \
+ "AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES"
+
+
+// ---------------------------------------------------------------------------
+/** @brief Set wether the FBX importer shall convert the unit from cm to m.
+ */
+#define AI_CONFIG_FBX_CONVERT_TO_M \
+ "AI_CONFIG_FBX_CONVERT_TO_M"
+
+// ---------------------------------------------------------------------------
+/** @brief Will enable the skeleton structo to store bone data.
+ *
+ * This will decouple the bone coupling to the mesh. This feature is
+ * experimental.
+ */
+#define AI_CONFIG_FBX_USE_SKELETON_BONE_CONTAINER \
+ "AI_CONFIG_FBX_USE_SKELETON_BONE_CONTAINER"
+
+// ---------------------------------------------------------------------------
+/** @brief Set the vertex animation keyframe to be imported
+ *
+ * ASSIMP does not support vertex keyframes (only bone animation is supported).
+ * The library reads only one frame of models with vertex animations.
+ * By default this is the first frame.
+ * \note The default value is 0. This option applies to all importers.
+ * However, it is also possible to override the global setting
+ * for a specific loader. You can use the AI_CONFIG_IMPORT_XXX_KEYFRAME
+ * options (where XXX is a placeholder for the file format for which you
+ * want to override the global setting).
+ * Property type: integer.
+ */
+#define AI_CONFIG_IMPORT_GLOBAL_KEYFRAME "IMPORT_GLOBAL_KEYFRAME"
+
+#define AI_CONFIG_IMPORT_MD3_KEYFRAME "IMPORT_MD3_KEYFRAME"
+#define AI_CONFIG_IMPORT_MD2_KEYFRAME "IMPORT_MD2_KEYFRAME"
+#define AI_CONFIG_IMPORT_MDL_KEYFRAME "IMPORT_MDL_KEYFRAME"
+#define AI_CONFIG_IMPORT_MDC_KEYFRAME "IMPORT_MDC_KEYFRAME"
+#define AI_CONFIG_IMPORT_SMD_KEYFRAME "IMPORT_SMD_KEYFRAME"
+#define AI_CONFIG_IMPORT_UNREAL_KEYFRAME "IMPORT_UNREAL_KEYFRAME"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the MDL (HL1) importer will read animations.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATIONS "IMPORT_MDL_HL1_READ_ANIMATIONS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the MDL (HL1) importer will read animation events.
+ * \note This property requires AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATIONS to be set to true.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATION_EVENTS "IMPORT_MDL_HL1_READ_ANIMATION_EVENTS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the MDL (HL1) importer will read blend controllers.
+ * \note This property requires AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATIONS to be set to true.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_MDL_HL1_READ_BLEND_CONTROLLERS "IMPORT_MDL_HL1_READ_BLEND_CONTROLLERS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the MDL (HL1) importer will read sequence transition graph.
+ * \note This property requires AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATIONS to be set to true.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_MDL_HL1_READ_SEQUENCE_TRANSITIONS "IMPORT_MDL_HL1_READ_SEQUENCE_TRANSITIONS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the MDL (HL1) importer will read attachments info.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_MDL_HL1_READ_ATTACHMENTS "IMPORT_MDL_HL1_READ_ATTACHMENTS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the MDL (HL1) importer will read bone controllers info.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_MDL_HL1_READ_BONE_CONTROLLERS "IMPORT_MDL_HL1_READ_BONE_CONTROLLERS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the MDL (HL1) importer will read hitboxes info.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_MDL_HL1_READ_HITBOXES "IMPORT_MDL_HL1_READ_HITBOXES"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the MDL (HL1) importer will read miscellaneous global model info.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_MDL_HL1_READ_MISC_GLOBAL_INFO "IMPORT_MDL_HL1_READ_MISC_GLOBAL_INFO"
+
+// ---------------------------------------------------------------------------
+/** Smd load multiple animations
+ *
+ * Property type: bool. Default value: true.
+ */
+#define AI_CONFIG_IMPORT_SMD_LOAD_ANIMATION_LIST "IMPORT_SMD_LOAD_ANIMATION_LIST"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the AC loader to collect all surfaces which have the
+ * "Backface cull" flag set in separate meshes.
+ *
+ * Property type: bool. Default value: true.
+ */
+#define AI_CONFIG_IMPORT_AC_SEPARATE_BFCULL \
+ "IMPORT_AC_SEPARATE_BFCULL"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures whether the AC loader evaluates subdivision surfaces (
+ * indicated by the presence of the 'subdiv' attribute in the file). By
+ * default, Assimp performs the subdivision using the standard
+ * Catmull-Clark algorithm
+ *
+ * * Property type: bool. Default value: true.
+ */
+#define AI_CONFIG_IMPORT_AC_EVAL_SUBDIVISION \
+ "IMPORT_AC_EVAL_SUBDIVISION"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the UNREAL 3D loader to separate faces with different
+ * surface flags (e.g. two-sided vs. single-sided).
+ *
+ * * Property type: bool. Default value: true.
+ */
+#define AI_CONFIG_IMPORT_UNREAL_HANDLE_FLAGS \
+ "UNREAL_HANDLE_FLAGS"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the terragen import plugin to compute uv's for
+ * terrains, if not given. Furthermore a default texture is assigned.
+ *
+ * UV coordinates for terrains are so simple to compute that you'll usually
+ * want to compute them on your own, if you need them. This option is intended
+ * for model viewers which want to offer an easy way to apply textures to
+ * terrains.
+ * * Property type: bool. Default value: false.
+ */
+#define AI_CONFIG_IMPORT_TER_MAKE_UVS \
+ "IMPORT_TER_MAKE_UVS"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the ASE loader to always reconstruct normal vectors
+ * basing on the smoothing groups loaded from the file.
+ *
+ * Some ASE files have carry invalid normals, other don't.
+ * * Property type: bool. Default value: true.
+ */
+#define AI_CONFIG_IMPORT_ASE_RECONSTRUCT_NORMALS \
+ "IMPORT_ASE_RECONSTRUCT_NORMALS"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the M3D loader to detect and process multi-part
+ * Quake player models.
+ *
+ * These models usually consist of 3 files, lower.md3, upper.md3 and
+ * head.md3. If this property is set to true, Assimp will try to load and
+ * combine all three files if one of them is loaded.
+ * Property type: bool. Default value: true.
+ */
+#define AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART \
+ "IMPORT_MD3_HANDLE_MULTIPART"
+
+// ---------------------------------------------------------------------------
+/** @brief Tells the MD3 loader which skin files to load.
+ *
+ * When loading MD3 files, Assimp checks whether a file
+ * [md3_file_name]_[skin_name].skin is existing. These files are used by
+ * Quake III to be able to assign different skins (e.g. red and blue team)
+ * to models. 'default', 'red', 'blue' are typical skin names.
+ * Property type: String. Default value: "default".
+ */
+#define AI_CONFIG_IMPORT_MD3_SKIN_NAME \
+ "IMPORT_MD3_SKIN_NAME"
+
+// ---------------------------------------------------------------------------
+/** @brief Specify if to try load Quake 3 shader files. This also controls
+ * original surface name handling: when disabled it will be used unchanged.
+ *
+ * Property type: bool. Default value: true.
+ */
+#define AI_CONFIG_IMPORT_MD3_LOAD_SHADERS \
+ "IMPORT_MD3_LOAD_SHADERS"
+
+// ---------------------------------------------------------------------------
+/** @brief Specify the Quake 3 shader file to be used for a particular
+ * MD3 file. This can also be a search path.
+ *
+ * By default Assimp's behaviour is as follows: If a MD3 file
+ * any_path/models/any_q3_subdir/model_name/file_name.md3 is
+ * loaded, the library tries to locate the corresponding shader file in
+ * any_path/scripts/model_name.shader. This property overrides this
+ * behaviour. It can either specify a full path to the shader to be loaded
+ * or alternatively the path (relative or absolute) to the directory where
+ * the shaders for all MD3s to be loaded reside. Assimp attempts to open
+ * IMPORT_MD3_SHADER_SRC/model_name.shader first, IMPORT_MD3_SHADER_SRC/file_name.shader
+ * is the fallback file. Note that IMPORT_MD3_SHADER_SRC should have a terminal (back)slash.
+ * Property type: String. Default value: n/a.
+ */
+#define AI_CONFIG_IMPORT_MD3_SHADER_SRC \
+ "IMPORT_MD3_SHADER_SRC"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the LWO loader to load just one layer from the model.
+ *
+ * LWO files consist of layers and in some cases it could be useful to load
+ * only one of them. This property can be either a string - which specifies
+ * the name of the layer - or an integer - the index of the layer. If the
+ * property is not set the whole LWO model is loaded. Loading fails if the
+ * requested layer is not available. The layer index is zero-based and the
+ * layer name may not be empty.
+ * Property type: Integer. Default value: all layers are loaded.
+ */
+#define AI_CONFIG_IMPORT_LWO_ONE_LAYER_ONLY \
+ "IMPORT_LWO_ONE_LAYER_ONLY"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the MD5 loader to not load the MD5ANIM file for
+ * a MD5MESH file automatically.
+ *
+ * The default strategy is to look for a file with the same name but the
+ * MD5ANIM extension in the same directory. If it is found, it is loaded
+ * and combined with the MD5MESH file. This configuration option can be
+ * used to disable this behaviour.
+ *
+ * * Property type: bool. Default value: false.
+ */
+#define AI_CONFIG_IMPORT_MD5_NO_ANIM_AUTOLOAD \
+ "IMPORT_MD5_NO_ANIM_AUTOLOAD"
+
+// ---------------------------------------------------------------------------
+/** @brief Defines the begin of the time range for which the LWS loader
+ * evaluates animations and computes aiNodeAnim's.
+ *
+ * Assimp provides full conversion of LightWave's envelope system, including
+ * pre and post conditions. The loader computes linearly subsampled animation
+ * chanels with the frame rate given in the LWS file. This property defines
+ * the start time. Note: animation channels are only generated if a node
+ * has at least one envelope with more tan one key assigned. This property.
+ * is given in frames, '0' is the first frame. By default, if this property
+ * is not set, the importer takes the animation start from the input LWS
+ * file ('FirstFrame' line)
+ * Property type: Integer. Default value: taken from file.
+ *
+ * @see AI_CONFIG_IMPORT_LWS_ANIM_END - end of the imported time range
+ */
+#define AI_CONFIG_IMPORT_LWS_ANIM_START \
+ "IMPORT_LWS_ANIM_START"
+#define AI_CONFIG_IMPORT_LWS_ANIM_END \
+ "IMPORT_LWS_ANIM_END"
+
+// ---------------------------------------------------------------------------
+/** @brief Defines the output frame rate of the IRR loader.
+ *
+ * IRR animations are difficult to convert for Assimp and there will
+ * always be a loss of quality. This setting defines how many keys per second
+ * are returned by the converter.
+ * Property type: integer. Default value: 100
+ */
+#define AI_CONFIG_IMPORT_IRR_ANIM_FPS \
+ "IMPORT_IRR_ANIM_FPS"
+
+// ---------------------------------------------------------------------------
+/** @brief Ogre Importer will try to find referenced materials from this file.
+ *
+ * Ogre meshes reference with material names, this does not tell Assimp the file
+ * where it is located in. Assimp will try to find the source file in the following
+ * order: .material, .material and
+ * lastly the material name defined by this config property.
+ *
+ * Property type: String. Default value: Scene.material.
+ */
+#define AI_CONFIG_IMPORT_OGRE_MATERIAL_FILE \
+ "IMPORT_OGRE_MATERIAL_FILE"
+
+// ---------------------------------------------------------------------------
+/** @brief Ogre Importer detect the texture usage from its filename.
+ *
+ * Ogre material texture units do not define texture type, the textures usage
+ * depends on the used shader or Ogre's fixed pipeline. If this config property
+ * is true Assimp will try to detect the type from the textures filename postfix:
+ * _n, _nrm, _nrml, _normal, _normals and _normalmap for normal map, _s, _spec,
+ * _specular and _specularmap for specular map, _l, _light, _lightmap, _occ
+ * and _occlusion for light map, _disp and _displacement for displacement map.
+ * The matching is case insensitive. Post fix is taken between the last
+ * underscore and the last period.
+ * Default behavior is to detect type from lower cased texture unit name by
+ * matching against: normalmap, specularmap, lightmap and displacementmap.
+ * For both cases if no match is found aiTextureType_DIFFUSE is used.
+ *
+ * Property type: Bool. Default value: false.
+ */
+#define AI_CONFIG_IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME \
+ "IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME"
+
+ /** @brief Specifies whether the Android JNI asset extraction is supported.
+ *
+ * Turn on this option if you want to manage assets in native
+ * Android application without having to keep the internal directory and asset
+ * manager pointer.
+ */
+ #define AI_CONFIG_ANDROID_JNI_ASSIMP_MANAGER_SUPPORT "AI_CONFIG_ANDROID_JNI_ASSIMP_MANAGER_SUPPORT"
+
+// ---------------------------------------------------------------------------
+/** @brief Specifies whether the IFC loader skips over IfcSpace elements.
+ *
+ * IfcSpace elements (and their geometric representations) are used to
+ * represent, well, free space in a building storey.
+ * Property type: Bool. Default value: true.
+ */
+#define AI_CONFIG_IMPORT_IFC_SKIP_SPACE_REPRESENTATIONS "IMPORT_IFC_SKIP_SPACE_REPRESENTATIONS"
+
+// ---------------------------------------------------------------------------
+/** @brief Specifies whether the IFC loader will use its own, custom triangulation
+ * algorithm to triangulate wall and floor meshes.
+ *
+ * If this property is set to false, walls will be either triangulated by
+ * #aiProcess_Triangulate or will be passed through as huge polygons with
+ * faked holes (i.e. holes that are connected with the outer boundary using
+ * a dummy edge). It is highly recommended to set this property to true
+ * if you want triangulated data because #aiProcess_Triangulate is known to
+ * have problems with the kind of polygons that the IFC loader spits out for
+ * complicated meshes.
+ * Property type: Bool. Default value: true.
+ */
+#define AI_CONFIG_IMPORT_IFC_CUSTOM_TRIANGULATION "IMPORT_IFC_CUSTOM_TRIANGULATION"
+
+// ---------------------------------------------------------------------------
+/** @brief Set the tessellation conic angle for IFC smoothing curves.
+ *
+ * This is used by the IFC importer to determine the tessellation parameter
+ * for smoothing curves.
+ * @note The default value is AI_IMPORT_IFC_DEFAULT_SMOOTHING_ANGLE and the
+ * accepted values are in range [5.0, 120.0].
+ * Property type: Float.
+ */
+#define AI_CONFIG_IMPORT_IFC_SMOOTHING_ANGLE "IMPORT_IFC_SMOOTHING_ANGLE"
+
+// default value for AI_CONFIG_IMPORT_IFC_SMOOTHING_ANGLE
+#if (!defined AI_IMPORT_IFC_DEFAULT_SMOOTHING_ANGLE)
+# define AI_IMPORT_IFC_DEFAULT_SMOOTHING_ANGLE 10.0f
+#endif
+
+// ---------------------------------------------------------------------------
+/** @brief Set the tessellation for IFC cylindrical shapes.
+ *
+ * This is used by the IFC importer to determine the tessellation parameter
+ * for cylindrical shapes, i.e. the number of segments used to approximate a circle.
+ * @note The default value is AI_IMPORT_IFC_DEFAULT_CYLINDRICAL_TESSELLATION and the
+ * accepted values are in range [3, 180].
+ * Property type: Integer.
+ */
+#define AI_CONFIG_IMPORT_IFC_CYLINDRICAL_TESSELLATION "IMPORT_IFC_CYLINDRICAL_TESSELLATION"
+
+// default value for AI_CONFIG_IMPORT_IFC_CYLINDRICAL_TESSELLATION
+#if (!defined AI_IMPORT_IFC_DEFAULT_CYLINDRICAL_TESSELLATION)
+# define AI_IMPORT_IFC_DEFAULT_CYLINDRICAL_TESSELLATION 32
+#endif
+
+// ---------------------------------------------------------------------------
+/** @brief Specifies whether the Collada loader will ignore the provided up direction.
+ *
+ * If this property is set to true, the up direction provided in the file header will
+ * be ignored and the file will be loaded as is.
+ * Property type: Bool. Default value: false.
+ */
+#define AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION "IMPORT_COLLADA_IGNORE_UP_DIRECTION"
+
+// ---------------------------------------------------------------------------
+/** @brief Specifies whether the Collada loader should use Collada names.
+ *
+ * If this property is set to true, the Collada names will be used as the node and
+ * mesh names. The default is to use the id tag (resp. sid tag, if no id tag is present)
+ * instead.
+ * Property type: Bool. Default value: false.
+ */
+#define AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES "IMPORT_COLLADA_USE_COLLADA_NAMES"
+
+// ---------- All the Export defines ------------
+
+/** @brief Specifies the xfile use double for real values of float
+ *
+ * Property type: Bool. Default value: false.
+ */
+
+#define AI_CONFIG_EXPORT_XFILE_64BIT "EXPORT_XFILE_64BIT"
+
+/** @brief Specifies whether the assimp export shall be able to export point clouds
+ *
+ * When this flag is not defined the render data has to contain valid faces.
+ * Point clouds are only a collection of vertices which have nor spatial organization
+ * by a face and the validation process will remove them. Enabling this feature will
+ * switch off the flag and enable the functionality to export pure point clouds.
+ */
+#define AI_CONFIG_EXPORT_POINT_CLOUDS "EXPORT_POINT_CLOUDS"
+
+/**
+ * @brief Specifies the blob name, assimp uses for exporting.
+ *
+ * Some formats require auxiliary files to be written, that need to be linked back into
+ * the original file. For example, OBJ files export materials to a separate MTL file and
+ * use the `mtllib` keyword to reference this file.
+ *
+ * When exporting blobs using #ExportToBlob, assimp does not know the name of the blob
+ * file and thus outputs `mtllib $blobfile.mtl`, which might not be desired, since the
+ * MTL file might be called differently.
+ *
+ * This property can be used to give the exporter a hint on how to use the magic
+ * `$blobfile` keyword. If the exporter detects the keyword and is provided with a name
+ * for the blob, it instead uses this name.
+ */
+#define AI_CONFIG_EXPORT_BLOB_NAME "EXPORT_BLOB_NAME"
+
+/**
+ * @brief Specifies a gobal key factor for scale, float value
+ */
+#define AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY "GLOBAL_SCALE_FACTOR"
+
+#if (!defined AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT)
+# define AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT 1.0f
+#endif // !! AI_DEBONE_THRESHOLD
+
+#define AI_CONFIG_APP_SCALE_KEY "APP_SCALE_FACTOR"
+
+#if (!defined AI_CONFIG_APP_SCALE_KEY)
+# define AI_CONFIG_APP_SCALE_KEY 1.0
+#endif // AI_CONFIG_APP_SCALE_KEY
+
+
+// ---------- All the Build/Compile-time defines ------------
+
+/** @brief Specifies if double precision is supported inside assimp
+ *
+ * Property type: Bool. Default value: undefined.
+ */
+
+/* #undef ASSIMP_DOUBLE_PRECISION */
+
+#endif // !! AI_CONFIG_H_INC
diff --git a/3rd/assimp/code/.editorconfig b/3rd/assimp/code/.editorconfig
new file mode 100644
index 00000000..4a194a31
--- /dev/null
+++ b/3rd/assimp/code/.editorconfig
@@ -0,0 +1,8 @@
+# See for details
+
+[*.{h,hpp,c,cpp}]
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_size = 4
+indent_style = space
diff --git a/3rd/assimp/code/AssetLib/3DS/3DSConverter.cpp b/3rd/assimp/code/AssetLib/3DS/3DSConverter.cpp
new file mode 100644
index 00000000..5a01429e
--- /dev/null
+++ b/3rd/assimp/code/AssetLib/3DS/3DSConverter.cpp
@@ -0,0 +1,807 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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.
+---------------------------------------------------------------------------
+*/
+
+/** @file Implementation of the 3ds importer class */
+
+#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
+
+// internal headers
+#include "3DSLoader.h"
+#include "Common/TargetAnimation.h"
+#include
+#include
+#include
+#include
+#include
+
+using namespace Assimp;
+
+static const unsigned int NotSet = 0xcdcdcdcd;
+
+// ------------------------------------------------------------------------------------------------
+// Setup final material indices, generae a default material if necessary
+void Discreet3DSImporter::ReplaceDefaultMaterial() {
+ // Try to find an existing material that matches the
+ // typical default material setting:
+ // - no textures
+ // - diffuse color (in grey!)
+ // NOTE: This is here to workaround the fact that some
+ // exporters are writing a default material, too.
+ unsigned int idx(NotSet);
+ for (unsigned int i = 0; i < mScene->mMaterials.size(); ++i) {
+ std::string s = mScene->mMaterials[i].mName;
+ for (char & it : s) {
+ it = static_cast(::tolower(static_cast(it)));
+ }
+
+ if (std::string::npos == s.find("default")) continue;
+
+ if (mScene->mMaterials[i].mDiffuse.r !=
+ mScene->mMaterials[i].mDiffuse.g ||
+ mScene->mMaterials[i].mDiffuse.r !=
+ mScene->mMaterials[i].mDiffuse.b) continue;
+
+ if (ContainsTextures(i)) {
+ continue;
+ }
+ idx = i;
+ }
+ if (NotSet == idx) {
+ idx = (unsigned int)mScene->mMaterials.size();
+ }
+
+ // now iterate through all meshes and through all faces and
+ // find all faces that are using the default material
+ unsigned int cnt = 0;
+ for (std::vector::iterator
+ i = mScene->mMeshes.begin();
+ i != mScene->mMeshes.end(); ++i) {
+ for (std::vector::iterator
+ a = (*i).mFaceMaterials.begin();
+ a != (*i).mFaceMaterials.end(); ++a) {
+ // NOTE: The additional check seems to be necessary,
+ // some exporters seem to generate invalid data here
+ if (0xcdcdcdcd == (*a)) {
+ (*a) = idx;
+ ++cnt;
+ } else if ((*a) >= mScene->mMaterials.size()) {
+ (*a) = idx;
+ ASSIMP_LOG_WARN("Material index overflow in 3DS file. Using default material");
+ ++cnt;
+ }
+ }
+ }
+ if (cnt && idx == mScene->mMaterials.size()) {
+ // We need to create our own default material
+ D3DS::Material sMat("%%%DEFAULT");
+ sMat.mDiffuse = aiColor3D(0.3f, 0.3f, 0.3f);
+ mScene->mMaterials.push_back(sMat);
+
+ ASSIMP_LOG_INFO("3DS: Generating default material");
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Check whether all indices are valid. Otherwise we'd crash before the validation step is reached
+void Discreet3DSImporter::CheckIndices(D3DS::Mesh &sMesh) {
+ for (std::vector::iterator i = sMesh.mFaces.begin(); i != sMesh.mFaces.end(); ++i) {
+ // check whether all indices are in range
+ for (unsigned int a = 0; a < 3; ++a) {
+ if ((*i).mIndices[a] >= sMesh.mPositions.size()) {
+ ASSIMP_LOG_WARN("3DS: Vertex index overflow)");
+ (*i).mIndices[a] = (uint32_t)sMesh.mPositions.size() - 1;
+ }
+ if (!sMesh.mTexCoords.empty() && (*i).mIndices[a] >= sMesh.mTexCoords.size()) {
+ ASSIMP_LOG_WARN("3DS: Texture coordinate index overflow)");
+ (*i).mIndices[a] = (uint32_t)sMesh.mTexCoords.size() - 1;
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Generate out unique verbose format representation
+void Discreet3DSImporter::MakeUnique(D3DS::Mesh &sMesh) {
+ // TODO: really necessary? I don't think. Just a waste of memory and time
+ // to do it now in a separate buffer.
+
+ // Allocate output storage
+ std::vector vNew(sMesh.mFaces.size() * 3);
+ std::vector vNew2;
+ if (sMesh.mTexCoords.size())
+ vNew2.resize(sMesh.mFaces.size() * 3);
+
+ for (unsigned int i = 0, base = 0; i < sMesh.mFaces.size(); ++i) {
+ D3DS::Face &face = sMesh.mFaces[i];
+
+ // Positions
+ for (unsigned int a = 0; a < 3; ++a, ++base) {
+ vNew[base] = sMesh.mPositions[face.mIndices[a]];
+ if (sMesh.mTexCoords.size())
+ vNew2[base] = sMesh.mTexCoords[face.mIndices[a]];
+
+ face.mIndices[a] = base;
+ }
+ }
+ sMesh.mPositions = vNew;
+ sMesh.mTexCoords = vNew2;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Convert a 3DS texture to texture keys in an aiMaterial
+void CopyTexture(aiMaterial &mat, D3DS::Texture &texture, aiTextureType type) {
+ // Setup the texture name
+ aiString tex;
+ tex.Set(texture.mMapName);
+ mat.AddProperty(&tex, AI_MATKEY_TEXTURE(type, 0));
+
+ // Setup the texture blend factor
+ if (is_not_qnan(texture.mTextureBlend))
+ mat.AddProperty(&texture.mTextureBlend, 1, AI_MATKEY_TEXBLEND(type, 0));
+
+ // Setup the texture mapping mode
+ int mapMode = static_cast(texture.mMapMode);
+ mat.AddProperty(&mapMode, 1, AI_MATKEY_MAPPINGMODE_U(type, 0));
+ mat.AddProperty(&mapMode, 1, AI_MATKEY_MAPPINGMODE_V(type, 0));
+
+ // Mirroring - double the scaling values
+ // FIXME: this is not really correct ...
+ if (texture.mMapMode == aiTextureMapMode_Mirror) {
+ texture.mScaleU *= 2.0;
+ texture.mScaleV *= 2.0;
+ texture.mOffsetU /= 2.0;
+ texture.mOffsetV /= 2.0;
+ }
+
+ // Setup texture UV transformations
+ mat.AddProperty(&texture.mOffsetU, 5, AI_MATKEY_UVTRANSFORM(type, 0));
+}
+
+// ------------------------------------------------------------------------------------------------
+// Convert a 3DS material to an aiMaterial
+void Discreet3DSImporter::ConvertMaterial(D3DS::Material &oldMat,
+ aiMaterial &mat) {
+ // NOTE: Pass the background image to the viewer by bypassing the
+ // material system. This is an evil hack, never do it again!
+ if (0 != mBackgroundImage.length() && bHasBG) {
+ aiString tex;
+ tex.Set(mBackgroundImage);
+ mat.AddProperty(&tex, AI_MATKEY_GLOBAL_BACKGROUND_IMAGE);
+
+ // Be sure this is only done for the first material
+ mBackgroundImage = std::string();
+ }
+
+ // At first add the base ambient color of the scene to the material
+ oldMat.mAmbient.r += mClrAmbient.r;
+ oldMat.mAmbient.g += mClrAmbient.g;
+ oldMat.mAmbient.b += mClrAmbient.b;
+
+ aiString name;
+ name.Set(oldMat.mName);
+ mat.AddProperty(&name, AI_MATKEY_NAME);
+
+ // Material colors
+ mat.AddProperty(&oldMat.mAmbient, 1, AI_MATKEY_COLOR_AMBIENT);
+ mat.AddProperty(&oldMat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
+ mat.AddProperty(&oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR);
+ mat.AddProperty(&oldMat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE);
+
+ // Phong shininess and shininess strength
+ if (D3DS::Discreet3DS::Phong == oldMat.mShading ||
+ D3DS::Discreet3DS::Metal == oldMat.mShading) {
+ if (!oldMat.mSpecularExponent || !oldMat.mShininessStrength) {
+ oldMat.mShading = D3DS::Discreet3DS::Gouraud;
+ } else {
+ mat.AddProperty(&oldMat.mSpecularExponent, 1, AI_MATKEY_SHININESS);
+ mat.AddProperty(&oldMat.mShininessStrength, 1, AI_MATKEY_SHININESS_STRENGTH);
+ }
+ }
+
+ // Opacity
+ mat.AddProperty(&oldMat.mTransparency, 1, AI_MATKEY_OPACITY);
+
+ // Bump height scaling
+ mat.AddProperty(&oldMat.mBumpHeight, 1, AI_MATKEY_BUMPSCALING);
+
+ // Two sided rendering?
+ if (oldMat.mTwoSided) {
+ int i = 1;
+ mat.AddProperty(&i, 1, AI_MATKEY_TWOSIDED);
+ }
+
+ // Shading mode
+ aiShadingMode eShading = aiShadingMode_NoShading;
+ switch (oldMat.mShading) {
+ case D3DS::Discreet3DS::Flat:
+ eShading = aiShadingMode_Flat;
+ break;
+
+ // I don't know what "Wire" shading should be,
+ // assume it is simple lambertian diffuse shading
+ case D3DS::Discreet3DS::Wire: {
+ // Set the wireframe flag
+ unsigned int iWire = 1;
+ mat.AddProperty((int *)&iWire, 1, AI_MATKEY_ENABLE_WIREFRAME);
+ }
+
+ case D3DS::Discreet3DS::Gouraud:
+ eShading = aiShadingMode_Gouraud;
+ break;
+
+ // assume cook-torrance shading for metals.
+ case D3DS::Discreet3DS::Phong:
+ eShading = aiShadingMode_Phong;
+ break;
+
+ case D3DS::Discreet3DS::Metal:
+ eShading = aiShadingMode_CookTorrance;
+ break;
+
+ // FIX to workaround a warning with GCC 4 who complained
+ // about a missing case Blinn: here - Blinn isn't a valid
+ // value in the 3DS Loader, it is just needed for ASE
+ case D3DS::Discreet3DS::Blinn:
+ eShading = aiShadingMode_Blinn;
+ break;
+ }
+ int eShading_ = static_cast(eShading);
+ mat.AddProperty(&eShading_, 1, AI_MATKEY_SHADING_MODEL);
+
+ // DIFFUSE texture
+ if (oldMat.sTexDiffuse.mMapName.length() > 0)
+ CopyTexture(mat, oldMat.sTexDiffuse, aiTextureType_DIFFUSE);
+
+ // SPECULAR texture
+ if (oldMat.sTexSpecular.mMapName.length() > 0)
+ CopyTexture(mat, oldMat.sTexSpecular, aiTextureType_SPECULAR);
+
+ // OPACITY texture
+ if (oldMat.sTexOpacity.mMapName.length() > 0)
+ CopyTexture(mat, oldMat.sTexOpacity, aiTextureType_OPACITY);
+
+ // EMISSIVE texture
+ if (oldMat.sTexEmissive.mMapName.length() > 0)
+ CopyTexture(mat, oldMat.sTexEmissive, aiTextureType_EMISSIVE);
+
+ // BUMP texture
+ if (oldMat.sTexBump.mMapName.length() > 0)
+ CopyTexture(mat, oldMat.sTexBump, aiTextureType_HEIGHT);
+
+ // SHININESS texture
+ if (oldMat.sTexShininess.mMapName.length() > 0)
+ CopyTexture(mat, oldMat.sTexShininess, aiTextureType_SHININESS);
+
+ // REFLECTION texture
+ if (oldMat.sTexReflective.mMapName.length() > 0)
+ CopyTexture(mat, oldMat.sTexReflective, aiTextureType_REFLECTION);
+
+ // Store the name of the material itself, too
+ if (oldMat.mName.length()) {
+ aiString tex;
+ tex.Set(oldMat.mName);
+ mat.AddProperty(&tex, AI_MATKEY_NAME);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Split meshes by their materials and generate output aiMesh'es
+void Discreet3DSImporter::ConvertMeshes(aiScene *pcOut) {
+ std::vector avOutMeshes;
+ avOutMeshes.reserve(mScene->mMeshes.size() * 2);
+
+ unsigned int iFaceCnt = 0, num = 0;
+ aiString name;
+
+ // we need to split all meshes by their materials
+ for (std::vector::iterator i = mScene->mMeshes.begin(); i != mScene->mMeshes.end(); ++i) {
+ std::unique_ptr[]> aiSplit(new std::vector[mScene->mMaterials.size()]);
+
+ name.length = ASSIMP_itoa10(name.data, num++);
+
+ unsigned int iNum = 0;
+ for (std::vector::const_iterator a = (*i).mFaceMaterials.begin();
+ a != (*i).mFaceMaterials.end(); ++a, ++iNum) {
+ aiSplit[*a].push_back(iNum);
+ }
+ // now generate submeshes
+ for (unsigned int p = 0; p < mScene->mMaterials.size(); ++p) {
+ if (aiSplit[p].empty()) {
+ continue;
+ }
+ aiMesh *meshOut = new aiMesh();
+ meshOut->mName = name;
+ meshOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
+
+ // be sure to setup the correct material index
+ meshOut->mMaterialIndex = p;
+
+ // use the color data as temporary storage
+ meshOut->mColors[0] = (aiColor4D *)(&*i);
+ avOutMeshes.push_back(meshOut);
+
+ // convert vertices
+ meshOut->mNumFaces = (unsigned int)aiSplit[p].size();
+ meshOut->mNumVertices = meshOut->mNumFaces * 3;
+
+ // allocate enough storage for faces
+ meshOut->mFaces = new aiFace[meshOut->mNumFaces];
+ iFaceCnt += meshOut->mNumFaces;
+
+ meshOut->mVertices = new aiVector3D[meshOut->mNumVertices];
+ meshOut->mNormals = new aiVector3D[meshOut->mNumVertices];
+ if ((*i).mTexCoords.size()) {
+ meshOut->mTextureCoords[0] = new aiVector3D[meshOut->mNumVertices];
+ }
+ for (unsigned int q = 0, base = 0; q < aiSplit[p].size(); ++q) {
+ unsigned int index = aiSplit[p][q];
+ aiFace &face = meshOut->mFaces[q];
+
+ face.mIndices = new unsigned int[3];
+ face.mNumIndices = 3;
+
+ for (unsigned int a = 0; a < 3; ++a, ++base) {
+ unsigned int idx = (*i).mFaces[index].mIndices[a];
+ meshOut->mVertices[base] = (*i).mPositions[idx];
+ meshOut->mNormals[base] = (*i).mNormals[idx];
+
+ if ((*i).mTexCoords.size())
+ meshOut->mTextureCoords[0][base] = (*i).mTexCoords[idx];
+
+ face.mIndices[a] = base;
+ }
+ }
+ }
+ }
+
+ // Copy them to the output array
+ pcOut->mNumMeshes = (unsigned int)avOutMeshes.size();
+ pcOut->mMeshes = new aiMesh *[pcOut->mNumMeshes]();
+ for (unsigned int a = 0; a < pcOut->mNumMeshes; ++a) {
+ pcOut->mMeshes[a] = avOutMeshes[a];
+ }
+
+ // We should have at least one face here
+ if (!iFaceCnt) {
+ throw DeadlyImportError("No faces loaded. The mesh is empty");
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Add a node to the scenegraph and setup its final transformation
+void Discreet3DSImporter::AddNodeToGraph(aiScene *pcSOut, aiNode *pcOut,
+ D3DS::Node *pcIn, aiMatrix4x4 & /*absTrafo*/) {
+ std::vector iArray;
+ iArray.reserve(3);
+
+ aiMatrix4x4 abs;
+
+ // Find all meshes with the same name as the node
+ for (unsigned int a = 0; a < pcSOut->mNumMeshes; ++a) {
+ const D3DS::Mesh *pcMesh = (const D3DS::Mesh *)pcSOut->mMeshes[a]->mColors[0];
+ ai_assert(nullptr != pcMesh);
+
+ if (pcIn->mName == pcMesh->mName)
+ iArray.push_back(a);
+ }
+ if (!iArray.empty()) {
+ // The matrix should be identical for all meshes with the
+ // same name. It HAS to be identical for all meshes .....
+ D3DS::Mesh *imesh = ((D3DS::Mesh *)pcSOut->mMeshes[iArray[0]]->mColors[0]);
+
+ // Compute the inverse of the transformation matrix to move the
+ // vertices back to their relative and local space
+ aiMatrix4x4 mInv = imesh->mMat, mInvTransposed = imesh->mMat;
+ mInv.Inverse();
+ mInvTransposed.Transpose();
+ aiVector3D pivot = pcIn->vPivot;
+
+ pcOut->mNumMeshes = (unsigned int)iArray.size();
+ pcOut->mMeshes = new unsigned int[iArray.size()];
+ for (unsigned int i = 0; i < iArray.size(); ++i) {
+ const unsigned int iIndex = iArray[i];
+ aiMesh *const mesh = pcSOut->mMeshes[iIndex];
+
+ if (mesh->mColors[1] == nullptr) {
+ // Transform the vertices back into their local space
+ // fixme: consider computing normals after this, so we don't need to transform them
+ const aiVector3D *const pvEnd = mesh->mVertices + mesh->mNumVertices;
+ aiVector3D *pvCurrent = mesh->mVertices, *t2 = mesh->mNormals;
+
+ for (; pvCurrent != pvEnd; ++pvCurrent, ++t2) {
+ *pvCurrent = mInv * (*pvCurrent);
+ *t2 = mInvTransposed * (*t2);
+ }
+
+ // Handle negative transformation matrix determinant -> invert vertex x
+ if (imesh->mMat.Determinant() < 0.0f) {
+ /* we *must* have normals */
+ for (pvCurrent = mesh->mVertices, t2 = mesh->mNormals; pvCurrent != pvEnd; ++pvCurrent, ++t2) {
+ pvCurrent->x *= -1.f;
+ t2->x *= -1.f;
+ }
+ ASSIMP_LOG_INFO("3DS: Flipping mesh X-Axis");
+ }
+
+ // Handle pivot point
+ if (pivot.x || pivot.y || pivot.z) {
+ for (pvCurrent = mesh->mVertices; pvCurrent != pvEnd; ++pvCurrent) {
+ *pvCurrent -= pivot;
+ }
+ }
+
+ mesh->mColors[1] = (aiColor4D *)1;
+ } else
+ mesh->mColors[1] = (aiColor4D *)1;
+
+ // Setup the mesh index
+ pcOut->mMeshes[i] = iIndex;
+ }
+ }
+
+ // Setup the name of the node
+ // First instance keeps its name otherwise something might break, all others will be postfixed with their instance number
+ if (pcIn->mInstanceNumber > 1) {
+ char tmp[12];
+ ASSIMP_itoa10(tmp, pcIn->mInstanceNumber);
+ std::string tempStr = pcIn->mName + "_inst_";
+ tempStr += tmp;
+ pcOut->mName.Set(tempStr);
+ } else
+ pcOut->mName.Set(pcIn->mName);
+
+ // Now build the transformation matrix of the node
+ // ROTATION
+ if (pcIn->aRotationKeys.size()) {
+
+ // FIX to get to Assimp's quaternion conventions
+ for (std::vector::iterator it = pcIn->aRotationKeys.begin(); it != pcIn->aRotationKeys.end(); ++it) {
+ (*it).mValue.w *= -1.f;
+ }
+
+ pcOut->mTransformation = aiMatrix4x4(pcIn->aRotationKeys[0].mValue.GetMatrix());
+ } else if (pcIn->aCameraRollKeys.size()) {
+ aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(-pcIn->aCameraRollKeys[0].mValue),
+ pcOut->mTransformation);
+ }
+
+ // SCALING
+ aiMatrix4x4 &m = pcOut->mTransformation;
+ if (pcIn->aScalingKeys.size()) {
+ const aiVector3D &v = pcIn->aScalingKeys[0].mValue;
+ m.a1 *= v.x;
+ m.b1 *= v.x;
+ m.c1 *= v.x;
+ m.a2 *= v.y;
+ m.b2 *= v.y;
+ m.c2 *= v.y;
+ m.a3 *= v.z;
+ m.b3 *= v.z;
+ m.c3 *= v.z;
+ }
+
+ // TRANSLATION
+ if (pcIn->aPositionKeys.size()) {
+ const aiVector3D &v = pcIn->aPositionKeys[0].mValue;
+ m.a4 += v.x;
+ m.b4 += v.y;
+ m.c4 += v.z;
+ }
+
+ // Generate animation channels for the node
+ if (pcIn->aPositionKeys.size() > 1 || pcIn->aRotationKeys.size() > 1 ||
+ pcIn->aScalingKeys.size() > 1 || pcIn->aCameraRollKeys.size() > 1 ||
+ pcIn->aTargetPositionKeys.size() > 1) {
+ aiAnimation *anim = pcSOut->mAnimations[0];
+ ai_assert(nullptr != anim);
+
+ if (pcIn->aCameraRollKeys.size() > 1) {
+ ASSIMP_LOG_VERBOSE_DEBUG("3DS: Converting camera roll track ...");
+
+ // Camera roll keys - in fact they're just rotations
+ // around the camera's z axis. The angles are given
+ // in degrees (and they're clockwise).
+ pcIn->aRotationKeys.resize(pcIn->aCameraRollKeys.size());
+ for (unsigned int i = 0; i < pcIn->aCameraRollKeys.size(); ++i) {
+ aiQuatKey &q = pcIn->aRotationKeys[i];
+ aiFloatKey &f = pcIn->aCameraRollKeys[i];
+
+ q.mTime = f.mTime;
+
+ // FIX to get to Assimp quaternion conventions
+ q.mValue = aiQuaternion(0.f, 0.f, AI_DEG_TO_RAD(/*-*/ f.mValue));
+ }
+ }
+#if 0
+ if (pcIn->aTargetPositionKeys.size() > 1)
+ {
+ ASSIMP_LOG_VERBOSE_DEBUG("3DS: Converting target track ...");
+
+ // Camera or spot light - need to convert the separate
+ // target position channel to our representation
+ TargetAnimationHelper helper;
+
+ if (pcIn->aPositionKeys.empty())
+ {
+ // We can just pass zero here ...
+ helper.SetFixedMainAnimationChannel(aiVector3D());
+ }
+ else helper.SetMainAnimationChannel(&pcIn->aPositionKeys);
+ helper.SetTargetAnimationChannel(&pcIn->aTargetPositionKeys);
+
+ // Do the conversion
+ std::vector distanceTrack;
+ helper.Process(&distanceTrack);
+
+ // Now add a new node as child, name it .Target
+ // and assign the distance track to it. This is that the
+ // information where the target is and how it moves is
+ // not lost
+ D3DS::Node* nd = new D3DS::Node();
+ pcIn->push_back(nd);
+
+ nd->mName = pcIn->mName + ".Target";
+
+ aiNodeAnim* nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim();
+ nda->mNodeName.Set(nd->mName);
+
+ nda->mNumPositionKeys = (unsigned int)distanceTrack.size();
+ nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys];
+ ::memcpy(nda->mPositionKeys,&distanceTrack[0],
+ sizeof(aiVectorKey)*nda->mNumPositionKeys);
+ }
+#endif
+
+ // Cameras or lights define their transformation in their parent node and in the
+ // corresponding light or camera chunks. However, we read and process the latter
+ // to to be able to return valid cameras/lights even if no scenegraph is given.
+ for (unsigned int n = 0; n < pcSOut->mNumCameras; ++n) {
+ if (pcSOut->mCameras[n]->mName == pcOut->mName) {
+ pcSOut->mCameras[n]->mLookAt = aiVector3D(0.f, 0.f, 1.f);
+ }
+ }
+ for (unsigned int n = 0; n < pcSOut->mNumLights; ++n) {
+ if (pcSOut->mLights[n]->mName == pcOut->mName) {
+ pcSOut->mLights[n]->mDirection = aiVector3D(0.f, 0.f, 1.f);
+ }
+ }
+
+ // Allocate a new node anim and setup its name
+ aiNodeAnim *nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim();
+ nda->mNodeName.Set(pcIn->mName);
+
+ // POSITION keys
+ if (pcIn->aPositionKeys.size() > 0) {
+ nda->mNumPositionKeys = (unsigned int)pcIn->aPositionKeys.size();
+ nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys];
+ ::memcpy(nda->mPositionKeys, &pcIn->aPositionKeys[0],
+ sizeof(aiVectorKey) * nda->mNumPositionKeys);
+ }
+
+ // ROTATION keys
+ if (pcIn->aRotationKeys.size() > 0) {
+ nda->mNumRotationKeys = (unsigned int)pcIn->aRotationKeys.size();
+ nda->mRotationKeys = new aiQuatKey[nda->mNumRotationKeys];
+
+ // Rotations are quaternion offsets
+ aiQuaternion abs1;
+ for (unsigned int n = 0; n < nda->mNumRotationKeys; ++n) {
+ const aiQuatKey &q = pcIn->aRotationKeys[n];
+
+ abs1 = (n ? abs1 * q.mValue : q.mValue);
+ nda->mRotationKeys[n].mTime = q.mTime;
+ nda->mRotationKeys[n].mValue = abs1.Normalize();
+ }
+ }
+
+ // SCALING keys
+ if (pcIn->aScalingKeys.size() > 0) {
+ nda->mNumScalingKeys = (unsigned int)pcIn->aScalingKeys.size();
+ nda->mScalingKeys = new aiVectorKey[nda->mNumScalingKeys];
+ ::memcpy(nda->mScalingKeys, &pcIn->aScalingKeys[0],
+ sizeof(aiVectorKey) * nda->mNumScalingKeys);
+ }
+ }
+
+ // Allocate storage for children
+ pcOut->mNumChildren = (unsigned int)pcIn->mChildren.size();
+ pcOut->mChildren = new aiNode *[pcIn->mChildren.size()];
+
+ // Recursively process all children
+ const unsigned int size = static_cast(pcIn->mChildren.size());
+ for (unsigned int i = 0; i < size; ++i) {
+ pcOut->mChildren[i] = new aiNode();
+ pcOut->mChildren[i]->mParent = pcOut;
+ AddNodeToGraph(pcSOut, pcOut->mChildren[i], pcIn->mChildren[i], abs);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Find out how many node animation channels we'll have finally
+void CountTracks(D3DS::Node *node, unsigned int &cnt) {
+ //////////////////////////////////////////////////////////////////////////////
+ // We will never generate more than one channel for a node, so
+ // this is rather easy here.
+
+ if (node->aPositionKeys.size() > 1 || node->aRotationKeys.size() > 1 ||
+ node->aScalingKeys.size() > 1 || node->aCameraRollKeys.size() > 1 ||
+ node->aTargetPositionKeys.size() > 1) {
+ ++cnt;
+
+ // account for the additional channel for the camera/spotlight target position
+ if (node->aTargetPositionKeys.size() > 1) ++cnt;
+ }
+
+ // Recursively process all children
+ for (unsigned int i = 0; i < node->mChildren.size(); ++i)
+ CountTracks(node->mChildren[i], cnt);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Generate the output node graph
+void Discreet3DSImporter::GenerateNodeGraph(aiScene *pcOut) {
+ pcOut->mRootNode = new aiNode();
+ if (0 == mRootNode->mChildren.size()) {
+ //////////////////////////////////////////////////////////////////////////////
+ // It seems the file is so messed up that it has not even a hierarchy.
+ // generate a flat hiearachy which looks like this:
+ //
+ // ROOT_NODE
+ // |
+ // ----------------------------------------
+ // | | | | |
+ // MESH_0 MESH_1 MESH_2 ... MESH_N CAMERA_0 ....
+ //
+ ASSIMP_LOG_WARN("No hierarchy information has been found in the file. ");
+
+ pcOut->mRootNode->mNumChildren = pcOut->mNumMeshes +
+ static_cast(mScene->mCameras.size() + mScene->mLights.size());
+
+ pcOut->mRootNode->mChildren = new aiNode *[pcOut->mRootNode->mNumChildren];
+ pcOut->mRootNode->mName.Set("<3DSDummyRoot>");
+
+ // Build dummy nodes for all meshes
+ unsigned int a = 0;
+ for (unsigned int i = 0; i < pcOut->mNumMeshes; ++i, ++a) {
+ aiNode *pcNode = pcOut->mRootNode->mChildren[a] = new aiNode();
+ pcNode->mParent = pcOut->mRootNode;
+ pcNode->mMeshes = new unsigned int[1];
+ pcNode->mMeshes[0] = i;
+ pcNode->mNumMeshes = 1;
+
+ // Build a name for the node
+ pcNode->mName.length = ai_snprintf(pcNode->mName.data, MAXLEN, "3DSMesh_%u", i);
+ }
+
+ // Build dummy nodes for all cameras
+ for (unsigned int i = 0; i < (unsigned int)mScene->mCameras.size(); ++i, ++a) {
+ aiNode *pcNode = pcOut->mRootNode->mChildren[a] = new aiNode();
+ pcNode->mParent = pcOut->mRootNode;
+
+ // Build a name for the node
+ pcNode->mName = mScene->mCameras[i]->mName;
+ }
+
+ // Build dummy nodes for all lights
+ for (unsigned int i = 0; i < (unsigned int)mScene->mLights.size(); ++i, ++a) {
+ aiNode *pcNode = pcOut->mRootNode->mChildren[a] = new aiNode();
+ pcNode->mParent = pcOut->mRootNode;
+
+ // Build a name for the node
+ pcNode->mName = mScene->mLights[i]->mName;
+ }
+ } else {
+ // First of all: find out how many scaling, rotation and translation
+ // animation tracks we'll have afterwards
+ unsigned int numChannel = 0;
+ CountTracks(mRootNode, numChannel);
+
+ if (numChannel) {
+ // Allocate a primary animation channel
+ pcOut->mNumAnimations = 1;
+ pcOut->mAnimations = new aiAnimation *[1];
+ aiAnimation *anim = pcOut->mAnimations[0] = new aiAnimation();
+
+ anim->mName.Set("3DSMasterAnim");
+
+ // Allocate enough storage for all node animation channels,
+ // but don't set the mNumChannels member - we'll use it to
+ // index into the array
+ anim->mChannels = new aiNodeAnim *[numChannel];
+ }
+
+ aiMatrix4x4 m;
+ AddNodeToGraph(pcOut, pcOut->mRootNode, mRootNode, m);
+ }
+
+ // We used the first and second vertex color set to store some temporary values so we need to cleanup here
+ for (unsigned int a = 0; a < pcOut->mNumMeshes; ++a) {
+ pcOut->mMeshes[a]->mColors[0] = nullptr;
+ pcOut->mMeshes[a]->mColors[1] = nullptr;
+ }
+
+ pcOut->mRootNode->mTransformation = aiMatrix4x4(
+ 1.f, 0.f, 0.f, 0.f,
+ 0.f, 0.f, 1.f, 0.f,
+ 0.f, -1.f, 0.f, 0.f,
+ 0.f, 0.f, 0.f, 1.f) *
+ pcOut->mRootNode->mTransformation;
+
+ // If the root node is unnamed name it "<3DSRoot>"
+ if (::strstr(pcOut->mRootNode->mName.data, "UNNAMED") ||
+ (pcOut->mRootNode->mName.data[0] == '$' && pcOut->mRootNode->mName.data[1] == '$')) {
+ pcOut->mRootNode->mName.Set("<3DSRoot>");
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Convert all meshes in the scene and generate the final output scene.
+void Discreet3DSImporter::ConvertScene(aiScene *pcOut) {
+ // Allocate enough storage for all output materials
+ pcOut->mNumMaterials = (unsigned int)mScene->mMaterials.size();
+ pcOut->mMaterials = new aiMaterial *[pcOut->mNumMaterials];
+
+ // ... and convert the 3DS materials to aiMaterial's
+ for (unsigned int i = 0; i < pcOut->mNumMaterials; ++i) {
+ aiMaterial *pcNew = new aiMaterial();
+ ConvertMaterial(mScene->mMaterials[i], *pcNew);
+ pcOut->mMaterials[i] = pcNew;
+ }
+
+ // Generate the output mesh list
+ ConvertMeshes(pcOut);
+
+ // Now copy all light sources to the output scene
+ pcOut->mNumLights = (unsigned int)mScene->mLights.size();
+ if (pcOut->mNumLights) {
+ pcOut->mLights = new aiLight *[pcOut->mNumLights];
+ ::memcpy(pcOut->mLights, &mScene->mLights[0], sizeof(void *) * pcOut->mNumLights);
+ }
+
+ // Now copy all cameras to the output scene
+ pcOut->mNumCameras = (unsigned int)mScene->mCameras.size();
+ if (pcOut->mNumCameras) {
+ pcOut->mCameras = new aiCamera *[pcOut->mNumCameras];
+ ::memcpy(pcOut->mCameras, &mScene->mCameras[0], sizeof(void *) * pcOut->mNumCameras);
+ }
+}
+
+#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER
diff --git a/3rd/assimp/code/AssetLib/3DS/3DSExporter.cpp b/3rd/assimp/code/AssetLib/3DS/3DSExporter.cpp
new file mode 100644
index 00000000..71588f93
--- /dev/null
+++ b/3rd/assimp/code/AssetLib/3DS/3DSExporter.cpp
@@ -0,0 +1,584 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2022, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the
+ following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+ contributors may be used to endorse or promote products
+ derived from this software without specific prior
+ written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+#ifndef ASSIMP_BUILD_NO_EXPORT
+#ifndef ASSIMP_BUILD_NO_3DS_EXPORTER
+
+#include "AssetLib/3DS/3DSExporter.h"
+#include "AssetLib/3DS/3DSHelper.h"
+#include "AssetLib/3DS/3DSLoader.h"
+#include "PostProcessing/SplitLargeMeshes.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+namespace Assimp {
+
+using namespace D3DS;
+
+namespace {
+
+//////////////////////////////////////////////////////////////////////////////////////
+// Scope utility to write a 3DS file chunk.
+//
+// Upon construction, the chunk header is written with the chunk type (flags)
+// filled out, but the chunk size left empty. Upon destruction, the correct chunk
+// size based on the then-position of the output stream cursor is filled in.
+class ChunkWriter {
+ enum {
+ CHUNK_SIZE_NOT_SET = 0xdeadbeef,
+ SIZE_OFFSET = 2
+ };
+
+public:
+ ChunkWriter(StreamWriterLE &writer, uint16_t chunk_type) :
+ writer(writer) {
+ chunk_start_pos = writer.GetCurrentPos();
+ writer.PutU2(chunk_type);
+ writer.PutU4((uint32_t)CHUNK_SIZE_NOT_SET);
+ }
+
+ ~ChunkWriter() {
+ std::size_t head_pos = writer.GetCurrentPos();
+
+ ai_assert(head_pos > chunk_start_pos);
+ const std::size_t chunk_size = head_pos - chunk_start_pos;
+
+ writer.SetCurrentPos(chunk_start_pos + SIZE_OFFSET);
+ writer.PutU4(static_cast(chunk_size));
+ writer.SetCurrentPos(head_pos);
+ }
+
+private:
+ StreamWriterLE &writer;
+ std::size_t chunk_start_pos;
+};
+
+// Return an unique name for a given |mesh| attached to |node| that
+// preserves the mesh's given name if it has one. |index| is the index
+// of the mesh in |aiScene::mMeshes|.
+std::string GetMeshName(const aiMesh &mesh, unsigned int index, const aiNode &node) {
+ static const char underscore = '_';
+ char postfix[10] = { 0 };
+ ASSIMP_itoa10(postfix, index);
+
+ std::string result = node.mName.C_Str();
+ if (mesh.mName.length > 0) {
+ result += underscore;
+ result += mesh.mName.C_Str();
+ }
+ return result + underscore + postfix;
+}
+
+// Return an unique name for a given |mat| with original position |index|
+// in |aiScene::mMaterials|. The name preserves the original material
+// name if possible.
+std::string GetMaterialName(const aiMaterial &mat, unsigned int index) {
+ static const std::string underscore = "_";
+ char postfix[10] = { 0 };
+ ASSIMP_itoa10(postfix, index);
+
+ aiString mat_name;
+ if (AI_SUCCESS == mat.Get(AI_MATKEY_NAME, mat_name)) {
+ return mat_name.C_Str() + underscore + postfix;
+ }
+
+ return "Material" + underscore + postfix;
+}
+
+// Collect world transformations for each node
+void CollectTrafos(const aiNode *node, std::map &trafos) {
+ const aiMatrix4x4 &parent = node->mParent ? trafos[node->mParent] : aiMatrix4x4();
+ trafos[node] = parent * node->mTransformation;
+ for (unsigned int i = 0; i < node->mNumChildren; ++i) {
+ CollectTrafos(node->mChildren[i], trafos);
+ }
+}
+
+// Generate a flat list of the meshes (by index) assigned to each node
+void CollectMeshes(const aiNode *node, std::multimap &meshes) {
+ for (unsigned int i = 0; i < node->mNumMeshes; ++i) {
+ meshes.insert(std::make_pair(node, node->mMeshes[i]));
+ }
+ for (unsigned int i = 0; i < node->mNumChildren; ++i) {
+ CollectMeshes(node->mChildren[i], meshes);
+ }
+}
+} // namespace
+
+// ------------------------------------------------------------------------------------------------
+// Worker function for exporting a scene to 3DS. Prototyped and registered in Exporter.cpp
+void ExportScene3DS(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties * /*pProperties*/) {
+ std::shared_ptr outfile(pIOSystem->Open(pFile, "wb"));
+ if (!outfile) {
+ throw DeadlyExportError("Could not open output .3ds file: " + std::string(pFile));
+ }
+
+ // TODO: This extra copy should be avoided and all of this made a preprocess
+ // requirement of the 3DS exporter.
+ //
+ // 3DS meshes can be max 0xffff (16 Bit) vertices and faces, respectively.
+ // SplitLargeMeshes can do this, but it requires the correct limit to be set
+ // which is not possible with the current way of specifying preprocess steps
+ // in |Exporter::ExportFormatEntry|.
+ aiScene *scenecopy_tmp;
+ SceneCombiner::CopyScene(&scenecopy_tmp, pScene);
+ std::unique_ptr scenecopy(scenecopy_tmp);
+
+ SplitLargeMeshesProcess_Triangle tri_splitter;
+ tri_splitter.SetLimit(0xffff);
+ tri_splitter.Execute(scenecopy.get());
+
+ SplitLargeMeshesProcess_Vertex vert_splitter;
+ vert_splitter.SetLimit(0xffff);
+ vert_splitter.Execute(scenecopy.get());
+
+ // Invoke the actual exporter
+ Discreet3DSExporter exporter(outfile, scenecopy.get());
+}
+
+} // end of namespace Assimp
+
+// ------------------------------------------------------------------------------------------------
+Discreet3DSExporter::Discreet3DSExporter(std::shared_ptr &outfile, const aiScene *scene) :
+ scene(scene), writer(outfile) {
+ CollectTrafos(scene->mRootNode, trafos);
+ CollectMeshes(scene->mRootNode, meshes);
+
+ ChunkWriter curRootChunk(writer, Discreet3DS::CHUNK_MAIN);
+
+ {
+ ChunkWriter curChunk(writer, Discreet3DS::CHUNK_OBJMESH);
+ WriteMaterials();
+ WriteMeshes();
+
+ {
+ ChunkWriter curChunk1(writer, Discreet3DS::CHUNK_MASTER_SCALE);
+ writer.PutF4(1.0f);
+ }
+ }
+
+ {
+ ChunkWriter curChunk(writer, Discreet3DS::CHUNK_KEYFRAMER);
+ WriteHierarchy(*scene->mRootNode, -1, -1);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+Discreet3DSExporter::~Discreet3DSExporter() {
+ // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+int Discreet3DSExporter::WriteHierarchy(const aiNode &node, int seq, int sibling_level) {
+ // 3DS scene hierarchy is serialized as in http://www.martinreddy.net/gfx/3d/3DS.spec
+ {
+ ChunkWriter curRootChunk(writer, Discreet3DS::CHUNK_TRACKINFO);
+ {
+ ChunkWriter curChunk(writer, Discreet3DS::CHUNK_TRACKOBJNAME);
+
+ // Assimp node names are unique and distinct from all mesh-node
+ // names we generate; thus we can use them as-is
+ WriteString(node.mName);
+
+ // Two unknown int16 values - it is even unclear if 0 is a safe value
+ // but luckily importers do not know better either.
+ writer.PutI4(0);
+
+ int16_t hierarchy_pos = static_cast(seq);
+ if (sibling_level != -1) {
+ hierarchy_pos = (uint16_t)sibling_level;
+ }
+
+ // Write the hierarchy position
+ writer.PutI2(hierarchy_pos);
+ }
+ }
+
+ // TODO: write transformation chunks
+
+ ++seq;
+ sibling_level = seq;
+
+ // Write all children
+ for (unsigned int i = 0; i < node.mNumChildren; ++i) {
+ seq = WriteHierarchy(*node.mChildren[i], seq, i == 0 ? -1 : sibling_level);
+ }
+
+ // Write all meshes as separate nodes to be able to reference the meshes by name
+ for (unsigned int i = 0; i < node.mNumMeshes; ++i) {
+ const bool first_child = node.mNumChildren == 0 && i == 0;
+
+ const unsigned int mesh_idx = node.mMeshes[i];
+ const aiMesh &mesh = *scene->mMeshes[mesh_idx];
+
+ ChunkWriter curChunk(writer, Discreet3DS::CHUNK_TRACKINFO);
+ {
+ ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKOBJNAME);
+ WriteString(GetMeshName(mesh, mesh_idx, node));
+
+ writer.PutI4(0);
+ writer.PutI2(static_cast(first_child ? seq : sibling_level));
+ ++seq;
+ }
+ }
+ return seq;
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteMaterials() {
+ for (unsigned int i = 0; i < scene->mNumMaterials; ++i) {
+ ChunkWriter curRootChunk(writer, Discreet3DS::CHUNK_MAT_MATERIAL);
+ const aiMaterial &mat = *scene->mMaterials[i];
+
+ {
+ ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_MATNAME);
+ const std::string &name = GetMaterialName(mat, i);
+ WriteString(name);
+ }
+
+ aiColor3D color;
+ if (mat.Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) {
+ ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_DIFFUSE);
+ WriteColor(color);
+ }
+
+ if (mat.Get(AI_MATKEY_COLOR_SPECULAR, color) == AI_SUCCESS) {
+ ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_SPECULAR);
+ WriteColor(color);
+ }
+
+ if (mat.Get(AI_MATKEY_COLOR_AMBIENT, color) == AI_SUCCESS) {
+ ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_AMBIENT);
+ WriteColor(color);
+ }
+
+ float f;
+ if (mat.Get(AI_MATKEY_OPACITY, f) == AI_SUCCESS) {
+ ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_TRANSPARENCY);
+ WritePercentChunk(1.0f - f);
+ }
+
+ if (mat.Get(AI_MATKEY_COLOR_EMISSIVE, color) == AI_SUCCESS) {
+ ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_SELF_ILLUM);
+ WriteColor(color);
+ }
+
+ aiShadingMode shading_mode = aiShadingMode_Flat;
+ if (mat.Get(AI_MATKEY_SHADING_MODEL, shading_mode) == AI_SUCCESS) {
+ ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHADING);
+
+ Discreet3DS::shadetype3ds shading_mode_out;
+ switch (shading_mode) {
+ case aiShadingMode_Flat:
+ case aiShadingMode_NoShading:
+ shading_mode_out = Discreet3DS::Flat;
+ break;
+
+ case aiShadingMode_Gouraud:
+ case aiShadingMode_Toon:
+ case aiShadingMode_OrenNayar:
+ case aiShadingMode_Minnaert:
+ shading_mode_out = Discreet3DS::Gouraud;
+ break;
+
+ case aiShadingMode_Phong:
+ case aiShadingMode_Blinn:
+ case aiShadingMode_CookTorrance:
+ case aiShadingMode_Fresnel:
+ case aiShadingMode_PBR_BRDF: // Possibly should be Discreet3DS::Metal in some cases but this is undocumented
+ shading_mode_out = Discreet3DS::Phong;
+ break;
+
+ default:
+ shading_mode_out = Discreet3DS::Flat;
+ ai_assert(false);
+ };
+ writer.PutU2(static_cast(shading_mode_out));
+ }
+
+ if (mat.Get(AI_MATKEY_SHININESS, f) == AI_SUCCESS) {
+ ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHININESS);
+ WritePercentChunk(f);
+ }
+
+ if (mat.Get(AI_MATKEY_SHININESS_STRENGTH, f) == AI_SUCCESS) {
+ ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHININESS_PERCENT);
+ WritePercentChunk(f);
+ }
+
+ int twosided;
+ if (mat.Get(AI_MATKEY_TWOSIDED, twosided) == AI_SUCCESS && twosided != 0) {
+ ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_TWO_SIDE);
+ writer.PutI2(1);
+ }
+
+ // Fallback to BASE_COLOR if no DIFFUSE
+ if (!WriteTexture(mat, aiTextureType_DIFFUSE, Discreet3DS::CHUNK_MAT_TEXTURE))
+ WriteTexture(mat, aiTextureType_BASE_COLOR, Discreet3DS::CHUNK_MAT_TEXTURE);
+
+ WriteTexture(mat, aiTextureType_HEIGHT, Discreet3DS::CHUNK_MAT_BUMPMAP);
+ WriteTexture(mat, aiTextureType_OPACITY, Discreet3DS::CHUNK_MAT_OPACMAP);
+ WriteTexture(mat, aiTextureType_SHININESS, Discreet3DS::CHUNK_MAT_MAT_SHINMAP);
+ WriteTexture(mat, aiTextureType_SPECULAR, Discreet3DS::CHUNK_MAT_SPECMAP);
+ WriteTexture(mat, aiTextureType_EMISSIVE, Discreet3DS::CHUNK_MAT_SELFIMAP);
+ WriteTexture(mat, aiTextureType_REFLECTION, Discreet3DS::CHUNK_MAT_REFLMAP);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// returns true if the texture existed
+bool Discreet3DSExporter::WriteTexture(const aiMaterial &mat, aiTextureType type, uint16_t chunk_flags) {
+ aiString path;
+ aiTextureMapMode map_mode[2] = {
+ aiTextureMapMode_Wrap, aiTextureMapMode_Wrap
+ };
+ ai_real blend = 1.0;
+ if (mat.GetTexture(type, 0, &path, nullptr, nullptr, &blend, nullptr, map_mode) != AI_SUCCESS || !path.length) {
+ return false;
+ }
+
+ // TODO: handle embedded textures properly
+ if (path.data[0] == '*') {
+ ASSIMP_LOG_ERROR("Ignoring embedded texture for export: ", path.C_Str());
+ return false;
+ }
+
+ ChunkWriter chunk(writer, chunk_flags);
+ {
+ ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAPFILE);
+ WriteString(path);
+ }
+
+ WritePercentChunk(blend);
+
+ {
+ ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_MAP_TILING);
+ uint16_t val = 0; // WRAP
+ if (map_mode[0] == aiTextureMapMode_Mirror) {
+ val = 0x2;
+ } else if (map_mode[0] == aiTextureMapMode_Decal) {
+ val = 0x10;
+ }
+ writer.PutU2(val);
+ }
+ // TODO: export texture transformation (i.e. UV offset, scale, rotation)
+ return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteMeshes() {
+ // NOTE: 3DS allows for instances. However:
+ // i) not all importers support reading them
+ // ii) instances are not as flexible as they are in assimp, in particular,
+ // nodes can carry (and instance) only one mesh.
+ //
+ // This exporter currently deep clones all instanced meshes, i.e. for each mesh
+ // attached to a node a full TRIMESH chunk is written to the file.
+ //
+ // Furthermore, the TRIMESH is transformed into world space so that it will
+ // appear correctly if importers don't read the scene hierarchy at all.
+ for (MeshesByNodeMap::const_iterator it = meshes.begin(); it != meshes.end(); ++it) {
+ const aiNode &node = *(*it).first;
+ const unsigned int mesh_idx = (*it).second;
+
+ const aiMesh &mesh = *scene->mMeshes[mesh_idx];
+
+ // This should not happen if the SLM step is correctly executed
+ // before the scene is handed to the exporter
+ ai_assert(mesh.mNumVertices <= 0xffff);
+ ai_assert(mesh.mNumFaces <= 0xffff);
+
+ const aiMatrix4x4 &trafo = trafos[&node];
+
+ ChunkWriter chunk(writer, Discreet3DS::CHUNK_OBJBLOCK);
+
+ // Mesh name is tied to the node it is attached to so it can later be referenced
+ const std::string &name = GetMeshName(mesh, mesh_idx, node);
+ WriteString(name);
+
+ // TRIMESH chunk
+ ChunkWriter chunk2(writer, Discreet3DS::CHUNK_TRIMESH);
+
+ // Vertices in world space
+ {
+ ChunkWriter curChunk(writer, Discreet3DS::CHUNK_VERTLIST);
+
+ const uint16_t count = static_cast(mesh.mNumVertices);
+ writer.PutU2(count);
+ for (unsigned int i = 0; i < mesh.mNumVertices; ++i) {
+ const aiVector3D &v = mesh.mVertices[i];
+ writer.PutF4(v.x);
+ writer.PutF4(v.y);
+ writer.PutF4(v.z);
+ }
+ }
+
+ // UV coordinates
+ if (mesh.HasTextureCoords(0)) {
+ ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAPLIST);
+ const uint16_t count = static_cast(mesh.mNumVertices);
+ writer.PutU2(count);
+
+ for (unsigned int i = 0; i < mesh.mNumVertices; ++i) {
+ const aiVector3D &v = mesh.mTextureCoords[0][i];
+ writer.PutF4(v.x);
+ writer.PutF4(v.y);
+ }
+ }
+
+ // Faces (indices)
+ {
+ ChunkWriter curChunk(writer, Discreet3DS::CHUNK_FACELIST);
+
+ ai_assert(mesh.mNumFaces <= 0xffff);
+
+ // Count triangles, discard lines and points
+ uint16_t count = 0;
+ for (unsigned int i = 0; i < mesh.mNumFaces; ++i) {
+ const aiFace &f = mesh.mFaces[i];
+ if (f.mNumIndices < 3) {
+ continue;
+ }
+ // TRIANGULATE step is a pre-requisite so we should not see polys here
+ ai_assert(f.mNumIndices == 3);
+ ++count;
+ }
+
+ writer.PutU2(count);
+ for (unsigned int i = 0; i < mesh.mNumFaces; ++i) {
+ const aiFace &f = mesh.mFaces[i];
+ if (f.mNumIndices < 3) {
+ continue;
+ }
+
+ for (unsigned int j = 0; j < 3; ++j) {
+ ai_assert(f.mIndices[j] <= 0xffff);
+ writer.PutI2(static_cast(f.mIndices[j]));
+ }
+
+ // Edge visibility flag
+ writer.PutI2(0x0);
+ }
+
+ // TODO: write smoothing groups (CHUNK_SMOOLIST)
+
+ WriteFaceMaterialChunk(mesh);
+ }
+
+ // Transformation matrix by which the mesh vertices have been pre-transformed with.
+ {
+ ChunkWriter curChunk(writer, Discreet3DS::CHUNK_TRMATRIX);
+ // Store rotation 3x3 matrix row wise
+ for (unsigned int r = 0; r < 3; ++r) {
+ for (unsigned int c = 0; c < 3; ++c) {
+ writer.PutF4(trafo[r][c]);
+ }
+ }
+ // Store translation sub vector column wise
+ for (unsigned int r = 0; r < 3; ++r) {
+ writer.PutF4(trafo[r][3]);
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteFaceMaterialChunk(const aiMesh &mesh) {
+ ChunkWriter curChunk(writer, Discreet3DS::CHUNK_FACEMAT);
+ const std::string &name = GetMaterialName(*scene->mMaterials[mesh.mMaterialIndex], mesh.mMaterialIndex);
+ WriteString(name);
+
+ // Because assimp splits meshes by material, only a single
+ // FACEMAT chunk needs to be written
+ ai_assert(mesh.mNumFaces <= 0xffff);
+ const uint16_t count = static_cast(mesh.mNumFaces);
+ writer.PutU2(count);
+
+ for (unsigned int i = 0; i < mesh.mNumFaces; ++i) {
+ writer.PutU2(static_cast(i));
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteString(const std::string &s) {
+ for (std::string::const_iterator it = s.begin(); it != s.end(); ++it) {
+ writer.PutI1(*it);
+ }
+ writer.PutI1('\0');
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteString(const aiString &s) {
+ for (std::size_t i = 0; i < s.length; ++i) {
+ writer.PutI1(s.data[i]);
+ }
+ writer.PutI1('\0');
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteColor(const aiColor3D &color) {
+ ChunkWriter curChunk(writer, Discreet3DS::CHUNK_RGBF);
+ writer.PutF4(color.r);
+ writer.PutF4(color.g);
+ writer.PutF4(color.b);
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WritePercentChunk(float f) {
+ ChunkWriter curChunk(writer, Discreet3DS::CHUNK_PERCENTF);
+ writer.PutF4(f);
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WritePercentChunk(double f) {
+ ChunkWriter ccurChunkhunk(writer, Discreet3DS::CHUNK_PERCENTD);
+ writer.PutF8(f);
+}
+
+#endif // ASSIMP_BUILD_NO_3DS_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT
diff --git a/3rd/assimp/code/AssetLib/3DS/3DSExporter.h b/3rd/assimp/code/AssetLib/3DS/3DSExporter.h
new file mode 100644
index 00000000..82ec3512
--- /dev/null
+++ b/3rd/assimp/code/AssetLib/3DS/3DSExporter.h
@@ -0,0 +1,98 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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.
+
+----------------------------------------------------------------------
+*/
+
+/** @file 3DSExporter.h
+ * 3DS Exporter Main Header
+ */
+#ifndef AI_3DSEXPORTER_H_INC
+#define AI_3DSEXPORTER_H_INC
+
+#include