/*
This file is part of Magnum .
Copyright © 2010 , 2011 , 2012 , 2013 , 2014 , 2015 , 2016 , 2017 , 2018 , 2019 ,
2020 , 2021 , 2022 Vladimír Vondruš < mosra @ centrum . cz >
Permission is hereby granted , free of charge , to any person obtaining a
copy of this software and associated documentation files ( the " Software " ) ,
to deal in the Software without restriction , including without limitation
the rights to use , copy , modify , merge , publish , distribute , sublicense ,
and / or sell copies of the Software , and to permit persons to whom the
Software is furnished to do so , subject to the following conditions :
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software .
THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING
FROM , OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE .
*/
# include <sstream>
# include <Corrade/Containers/Optional.h>
# include <Corrade/Containers/Pair.h>
# include <Corrade/Containers/Triple.h>
# include <Corrade/Utility/Arguments.h>
# include <Corrade/Utility/DebugStl.h> /** @todo remove once Arguments is std::string-free */
# include <Corrade/Utility/Format.h>
# include <Corrade/Utility/Path.h>
# include "Magnum/MeshTools/Concatenate.h"
# include "Magnum/MeshTools/Reference.h"
# include "Magnum/MeshTools/RemoveDuplicates.h"
# include "Magnum/MeshTools/Transform.h"
# include "Magnum/SceneTools/FlattenMeshHierarchy.h"
# include "Magnum/Trade/AbstractImporter.h"
# include "Magnum/Trade/MeshData.h"
# include "Magnum/Trade/AbstractImageConverter.h"
# include "Magnum/Trade/AbstractSceneConverter.h"
# include "Magnum/Implementation/converterUtilities.h"
# include "Magnum/SceneTools/Implementation/sceneConverterUtilities.h"
namespace Magnum {
/** @page magnum-sceneconverter Scene conversion utility
@ brief Converts scenes of different formats
@ m_since { 2020 , 06 }
@ tableofcontents
@ m_footernavigation
@ m_keywords { magnum - sceneconverter sceneconverter }
This utility is built if ` MAGNUM_WITH_SCENECONVERTER ` is enabled when building
Magnum . To use this utility with CMake , you need to request the
` sceneconverter ` component of the ` Magnum ` package and use the
` Magnum : : sceneconverter ` target for example in a custom command :
@ code { . cmake }
find_package ( Magnum REQUIRED imageconverter )
add_custom_command ( OUTPUT . . . COMMAND Magnum : : sceneconverter . . . )
@ endcode
See @ ref building and @ ref cmake and the @ ref Trade namespace for more
information . There ' s also a corresponding @ ref magnum - imageconverter " image conversion utility " .
@ section magnum - sceneconverter - example Example usage
Listing contents of a glTF file , implicitly using
@ relativeref { Trade , AnySceneImporter } that delegates to
@ relativeref { Trade , GltfImporter } , @ relativeref { Trade , AssimpImporter } or
@ ref file - formats " any other plugin capable of glTF import " depending on what ' s
available :
@ m_class { m - code - figure }
@ parblock
@ code { . sh }
magnum - sceneconverter - - info Box . gltf
@ endcode
< b > < / b >
@ m_class { m - nopad }
@ include sceneconverter - info . ansi
@ endparblock
Converting an OBJ file to a PLY , implicitly using
@ relativeref { Trade , AnySceneConverter } that delegates to
@ relativeref { Trade , StanfordSceneConverter } or
@ ref file - formats " any other plugin capable of PLY export " depending on what ' s
available :
@ code { . sh }
magnum - sceneconverter chair . obj chair . ply
@ endcode
Processing an OBJ file with @ relativeref { Trade , MeshOptimizerSceneConverter } ,
setting @ ref Trade - MeshOptimizerSceneConverter - configuration " plugin-specific configuration options "
to reduce the index count to half , saving as a PLY , with verbose output showing
the processing stats :
@ code { . sh }
magnum - sceneconverter chair . obj - C MeshOptimizerSceneConverter \
- c simplify = true , simplifyTargetIndexCountThreshold = 0.5 chair . ply - v
@ endcode
@ section magnum - sceneconverter - usage Full usage documentation
@ code { . sh }
magnum - sceneconverter [ - h | - - help ] [ - I | - - importer PLUGIN ]
[ - C | - - converter PLUGIN ] . . . [ - - plugin - dir DIR ] [ - - map ]
[ - - only - mesh - attributes N1 , N2 - N3 … ] [ - - remove - duplicate - vertices ]
[ - - remove - duplicate - vertices - fuzzy EPSILON ]
[ - i | - - importer - options key = val , key2 = val2 , … ]
[ - c | - - converter - options key = val , key2 = val2 , … ] . . . [ - - mesh ID ]
[ - - mesh - level INDEX ] [ - - concatenate - meshes ] [ - - info - animations ]
[ - - info - images ] [ - - info - lights ] [ - - info - cameras ] [ - - info - materials ]
[ - - info - meshes ] [ - - info - objects ] [ - - info - scenes ] [ - - info - skins ]
[ - - info - textures ] [ - - info ] [ - - color on | 4 bit | off | auto ] [ - - bounds ]
[ - v | - - verbose ] [ - - profile ] [ - - ] input output
@ endcode
Arguments :
- ` input ` - - - input file
- ` output ` - - - output file ; ignored if ` - - info ` is present
- ` - h ` , ` - - help ` - - - display this help message and exit
- ` - I ` , ` - - importer PLUGIN ` - - - scene importer plugin ( default :
@ ref Trade : : AnySceneImporter " AnySceneImporter " )
- ` - C ` , ` - - converter PLUGIN ` - - - scene converter plugin ( s )
- ` - - plugin - dir DIR ` - - - override base plugin dir
- ` - - map ` - - - memory - map the input for zero - copy import ( works only for
standalone files )
- ` - - only - mesh - attributes N1 , N2 - N3 … ` - - - include only mesh attributes of
given IDs in the output . See @ ref Utility : : String : : parseNumberSequence ( )
for syntax description .
- ` - - remove - duplicate - vertices ` - - - remove duplicate vertices using
@ ref MeshTools : : removeDuplicates ( const Trade : : MeshData & ) in all meshes
after import
- ` - - remove - duplicate - vertices - fuzzy EPSILON ` - - - remove duplicate vertices
using @ ref MeshTools : : removeDuplicatesFuzzy ( const Trade : : MeshData & , Float , Double )
in all meshes after import
- ` - i ` , ` - - importer - options key = val , key2 = val2 , … ` - - - configuration options to
pass to the importer
- ` - c ` , ` - - converter - options key = val , key2 = val2 , … ` - - - configuration options
to pass to the converter ( s )
- ` - - mesh ID ` - - - convert just a single mesh instead of the whole scene
- ` - - mesh - level LEVEL ` - - - level to select for single - mesh conversion
- ` - - concatenate - meshes ` - - flatten mesh hierarchy and concatenate them all
together @ m_class { m - label m - warning } * * experimental * *
- ` - - info - animations ` - - - print into about animations in the input file and
exit
- ` - - info - images ` - - - print into about images in the input file and exit
- ` - - info - lights ` - - - print into about lights in the input file and exit
- ` - - info - cameras ` - - - print into about cameras in the input file and exit
- ` - - info - materials ` - - - print into about materials in the input file and
exit
- ` - - info - meshes ` - - - print into about meshes in the input file and exit
- ` - - info - objects ` - - - print into about objects in the input file and exit
- ` - - info - scenes ` - - - print into about scenes in the input file and exit
- ` - - info - skins ` - - - print into about skins in the input file and exit
- ` - - info - textures ` - - - print into about textures in the input file and exit
- ` - - info ` - - - print info about everything in the input file and exit , same
as specifying all other ` - - info - * ` options together
- ` - - color ` - - - colored output for ` - - info ` ( default : ` auto ` )
- ` - - bounds ` - - - show bounds of known attributes in ` - - info ` output
- ` - v ` , ` - - verbose ` - - - verbose output from importer and converter plugins
- ` - - profile ` - - - measure import and conversion time
If any of the ` - - info - * ` options are given , the utility will print information
about given data present in the file . In this case no conversion is done and
output file doesn ' t need to be specified . In case one data references another
and both ` - - info - * ` options are specified , the output will also list reference
count ( for example , ` - - info - scenes ` together with ` - - info - meshes ` will print
how many objects reference given mesh ) .
The ` - i ` / ` - - importer - options ` and ` - c ` / ` - - converter - options ` arguments
accept a comma - separated list of key / value pairs to set in the importer /
converter plugin configuration . If the ` = ` character is omitted , it ' s
equivalent to saying ` key = true ` ; configuration subgroups are delimited with
` / ` .
It ' s possible to specify the ` - C ` / ` - - converter ` option ( and correspondingly
also ` - c ` / ` - - converter - options ` ) multiple times in order to chain more
converters together . All converters in the chain have to support the
@ ref Trade : : SceneConverterFeature : : ConvertMesh feature ,
the last converter either @ ref Trade : : SceneConverterFeature : : ConvertMesh or
@ ref Trade : : SceneConverterFeature : : ConvertMeshToFile . If the last converter
doesn ' t support conversion to a file ,
@ ref Trade : : AnySceneConverter " AnySceneConverter " is used to save its output ;
if no ` - C ` / ` - - converter ` is specified ,
@ ref Trade : : AnySceneConverter " AnySceneConverter " is used .
If ` - - concatenate - meshes ` is given , all meshes of the input file are
concatenated into a single mesh using @ ref MeshTools : : concatenate ( ) , with the
scene hierarchy transformation baked in using
@ ref SceneTools : : flattenMeshHierarchy3D ( ) . Only attributes that are present in
the first mesh are taken , if ` - - only - mesh - attributes ` is specified as well , the
IDs reference attributes of the first mesh .
*/
}
using namespace Magnum ;
using namespace Containers : : Literals ;
namespace {
bool isInfoRequested ( const Utility : : Arguments & args ) {
return args . isSet ( " info-animations " ) | |
args . isSet ( " info-images " ) | |
args . isSet ( " info-lights " ) | |
args . isSet ( " info-cameras " ) | |
args . isSet ( " info-materials " ) | |
args . isSet ( " info-meshes " ) | |
args . isSet ( " info-objects " ) | |
args . isSet ( " info-scenes " ) | |
args . isSet ( " info-skins " ) | |
args . isSet ( " info-textures " ) | |
args . isSet ( " info " ) ;
}
}
int main ( int argc , char * * argv ) {
Utility : : Arguments args ;
args . addArgument ( " input " ) . setHelp ( " input " , " input file " )
. addArgument ( " output " ) . setHelp ( " output " , " output file; ignored if --info is present " )
. addOption ( ' I ' , " importer " , " AnySceneImporter " ) . setHelp ( " importer " , " scene importer plugin " , " PLUGIN " )
. addArrayOption ( ' C ' , " converter " ) . setHelp ( " converter " , " scene converter plugin(s) " , " PLUGIN " )
. addOption ( " plugin-dir " ) . setHelp ( " plugin-dir " , " override base plugin dir " , " DIR " )
# if defined(CORRADE_TARGET_UNIX) || (defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT))
. addBooleanOption ( " map " ) . setHelp ( " map " , " memory-map the input for zero-copy import (works only for standalone files) " )
# endif
. addOption ( " only-mesh-attributes " ) . setHelp ( " only-mesh-attributes " , " include only mesh attributes of given IDs in the output " , " N1,N2-N3… " )
. addBooleanOption ( " remove-duplicate-vertices " ) . setHelp ( " remove-duplicate-vertices " , " remove duplicate vertices in all meshes after import " )
. addOption ( " remove-duplicate-vertices-fuzzy " ) . setHelp ( " remove-duplicate-vertices-fuzzy " , " remove duplicate vertices with fuzzy comparison in all meshes after import " , " EPSILON " )
. addOption ( ' i ' , " importer-options " ) . setHelp ( " importer-options " , " configuration options to pass to the importer " , " key=val,key2=val2,… " )
. addArrayOption ( ' c ' , " converter-options " ) . setHelp ( " converter-options " , " configuration options to pass to the converter(s) " , " key=val,key2=val2,… " )
. addOption ( " mesh " ) . setHelp ( " mesh " , " convert just a single mesh instead of the whole scene, ignored if --concatenate-meshes is specified " , " ID " )
. addOption ( " mesh-level " ) . setHelp ( " mesh-level " , " level to select for single-mesh conversion " , " index " )
. addBooleanOption ( " concatenate-meshes " ) . setHelp ( " concatenate-meshes " , " flatten mesh hierarchy and concatenate them all together " )
. addBooleanOption ( " info-animations " ) . setHelp ( " info-animations " , " print info about animations in the input file and exit " )
. addBooleanOption ( " info-images " ) . setHelp ( " info-images " , " print info about images in the input file and exit " )
. addBooleanOption ( " info-lights " ) . setHelp ( " info-lights " , " print info about images in the input file and exit " )
. addBooleanOption ( " info-cameras " ) . setHelp ( " info-cameras " , " print info about cameras in the input file and exit " )
. addBooleanOption ( " info-materials " ) . setHelp ( " info-materials " , " print info about materials in the input file and exit " )
. addBooleanOption ( " info-meshes " ) . setHelp ( " info-meshes " , " print info about meshes in the input file and exit " )
. addBooleanOption ( " info-objects " ) . setHelp ( " info-objects " , " print info about objects in the input file and exit " )
. addBooleanOption ( " info-scenes " ) . setHelp ( " info-scenes " , " print info about scenes in the input file and exit " )
. addBooleanOption ( " info-skins " ) . setHelp ( " info-skins " , " print info about skins in the input file and exit " )
. addBooleanOption ( " info-textures " ) . setHelp ( " info-textures " , " print info about textures in the input file and exit " )
. addBooleanOption ( " info " ) . setHelp ( " info " , " print info about everything in the input file and exit, same as specifying all other --info-* options together " )
. addOption ( " color " , " auto " ) . setHelp ( " color " , " colored output for --info " , " on|4bit|off|auto " )
. addBooleanOption ( " bounds " ) . setHelp ( " bounds " , " show bounds of known attributes in --info output " )
. addBooleanOption ( ' v ' , " verbose " ) . setHelp ( " verbose " , " verbose output from importer and converter plugins " )
. addBooleanOption ( " profile " ) . setHelp ( " profile " , " measure import and conversion time " )
. setParseErrorCallback ( [ ] ( const Utility : : Arguments & args , Utility : : Arguments : : ParseError error , const std : : string & key ) {
/* If --info is passed, we don't need the output argument */
if ( error = = Utility : : Arguments : : ParseError : : MissingArgument & &
key = = " output " & & isInfoRequested ( args ) ) return true ;
/* Handle all other errors as usual */
return false ;
} )
. setGlobalHelp ( R " (Converts scenes of different formats.
If any of the - - info - * options are given , the utility will print information
about given data present in the file . In this case no conversion is done and
output file doesn ' t need to be specified . In case one data references another
and both - - info - * options are specified , the output will also list reference
count ( for example , - - info - scenes together with - - info - meshes will print how
many objects reference given mesh ) .
The - i / - - importer - options and - c / - - converter - options arguments accept a
comma - separated list of key / value pairs to set in the importer / converter
plugin configuration . If the = character is omitted , it ' s equivalent to saying
key = true ; configuration subgroups are delimited with / .
It ' s possible to specify the - C / - - converter option ( and correspondingly also
- c / - - converter - options ) multiple times in order to chain more converters
together . All converters in the chain have to support the ConvertMesh feature ,
the last converter either ConvertMesh or ConvertMeshToFile . If the last
converter doesn ' t support conversion to a file , AnySceneConverter is used to
save its output ; if no - C / - - converter is specified , AnySceneConverter is
used .
If - - concatenate - meshes is given , all meshes of the input file are concatenated
into a single mesh , with the scene hierarchy transformation baked in . Only
attributes that are present in the first mesh are taken , if
- - only - mesh - attributes is specified as well , the IDs reference attributes of
the first mesh . ) " )
. parse ( argc , argv ) ;
/* Colored output. Enable only if a TTY. */
Debug : : Flags useColor ;
bool useColor24 ;
if ( args . value ( " color " ) = = " on " ) {
useColor = Debug : : Flags { } ;
useColor24 = true ;
} else if ( args . value ( " color " ) = = " 4bit " ) {
useColor = Debug : : Flags { } ;
useColor24 = false ;
} else if ( args . value ( " color " ) = = " off " ) {
useColor = Debug : : Flag : : DisableColors ;
useColor24 = false ;
} else if ( Debug : : isTty ( ) ) {
useColor = Debug : : Flags { } ;
/* https://unix.stackexchange.com/a/450366, not perfect but good enough
I ' d say */
/** @todo make this more robust and put directly on Debug,
including a " Disable 24 colors " flag */
const Containers : : StringView colorterm = std : : getenv ( " COLORTERM " ) ;
useColor24 = colorterm = = " truecolor " _s | | colorterm = = " 24bit " _s ;
} else {
useColor = Debug : : Flag : : DisableColors ;
useColor24 = false ;
}
/* Generic checks */
if ( args . value < Containers : : StringView > ( " output " ) ) {
/* Not an error in this case, it should be possible to just append
- - info * to existing command line without having to remove anything .
But print a warning at least , it could also be a mistyped option . */
if ( isInfoRequested ( args ) )
Warning { } < < " Ignoring output file for --info: " < < args . value < Containers : : StringView > ( " output " ) ;
}
if ( args . isSet ( " concatenate-meshes " ) & & args . value < Containers : : StringView > ( " mesh " ) ) {
Error { } < < " The --mesh and --concatenate-meshes options are mutually exclusive " ;
return 1 ;
}
if ( args . value < Containers : : StringView > ( " mesh-level " ) & & ! args . value < Containers : : StringView > ( " mesh " ) ) {
Error { } < < " The --mesh-level option can only be used with --mesh " ;
return 1 ;
}
/** @todo remove this once only-attributes can work with attribute names
and thus for more meshes */
if ( args . value < Containers : : StringView > ( " only-mesh-attributes " ) & & ! args . value < Containers : : StringView > ( " mesh " ) & & ! args . isSet ( " concatenate-meshes " ) ) {
Error { } < < " The --only-mesh-attributes option can only be used with --mesh or --concatenate-meshes " ;
return 1 ;
}
/* Importer manager */
PluginManager : : Manager < Trade : : AbstractImporter > importerManager {
args . value ( " plugin-dir " ) . empty ( ) ? Containers : : String { } :
Utility : : Path : : join ( args . value ( " plugin-dir " ) , Utility : : Path : : split ( Trade : : AbstractImporter : : pluginSearchPaths ( ) . back ( ) ) . second ( ) ) } ;
/* Image converter manager for potential dependencies. Needs to be
constructed before the scene converter manager for proper destruction
order . */
PluginManager : : Manager < Trade : : AbstractImageConverter > imageConverterManager {
args . value ( " plugin-dir " ) . empty ( ) ? Containers : : String { } :
Utility : : Path : : join ( args . value ( " plugin-dir " ) , Trade : : AbstractImageConverter : : pluginSearchPaths ( ) . back ( ) ) } ;
/* Scene converter manager, register the image converter manager with it */
PluginManager : : Manager < Trade : : AbstractSceneConverter > converterManager {
args . value ( " plugin-dir " ) . empty ( ) ? Containers : : String { } :
Utility : : Path : : join ( args . value ( " plugin-dir " ) , Utility : : Path : : split ( Trade : : AbstractSceneConverter : : pluginSearchPaths ( ) . back ( ) ) . second ( ) ) } ;
converterManager . registerExternalManager ( imageConverterManager ) ;
Containers : : Pointer < Trade : : AbstractImporter > importer = importerManager . loadAndInstantiate ( args . value ( " importer " ) ) ;
if ( ! importer ) {
Debug { } < < " Available importer plugins: " < < " , " _s . join ( importerManager . aliasList ( ) ) ;
return 1 ;
}
/* Set options, if passed */
if ( args . isSet ( " verbose " ) ) importer - > addFlags ( Trade : : ImporterFlag : : Verbose ) ;
Implementation : : setOptions ( * importer , " AnySceneImporter " , args . value ( " importer-options " ) ) ;
/* Wow, C++, you suck. This implicitly initializes to random shit?!
Also , because of addSupportedImporterContents ( ) it ' s not really possible
to distinguish between time spent importing and time spent converting .
So it ' s lumped into a single variable . Steps that are really just
conversion are measured separately . */
std : : chrono : : high_resolution_clock : : duration importConversionTime { } ;
/* Open the file or map it if requested */
# if defined(CORRADE_TARGET_UNIX) || (defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT))
Containers : : Optional < Containers : : Array < const char , Utility : : Path : : MapDeleter > > mapped ;
if ( args . isSet ( " map " ) ) {
Trade : : Implementation : : Duration d { importConversionTime } ;
if ( ! ( mapped = Utility : : Path : : mapRead ( args . value ( " input " ) ) ) | | ! importer - > openMemory ( * mapped ) ) {
Error ( ) < < " Cannot memory-map file " < < args . value ( " input " ) ;
return 3 ;
}
} else
# endif
{
Trade : : Implementation : : Duration d { importConversionTime } ;
if ( ! importer - > openFile ( args . value ( " input " ) ) ) {
Error ( ) < < " Cannot open file " < < args . value ( " input " ) ;
return 3 ;
}
}
/* Print file info, if requested */
if ( isInfoRequested ( args ) ) {
const bool error = SceneTools : : Implementation : : printInfo ( useColor , useColor24 , args , * importer , importConversionTime ) ;
if ( args . isSet ( " profile " ) ) {
Debug { } < < " Import took " < < UnsignedInt ( std : : chrono : : duration_cast < std : : chrono : : milliseconds > ( importConversionTime ) . count ( ) ) / 1.0e3 f < < " seconds " ;
}
return error ? 1 : 0 ;
}
/* Wow, C++, you suck. This implicitly initializes to random shit?! */
std : : chrono : : high_resolution_clock : : duration conversionTime { } ;
/* Take a single mesh or concatenate all meshes together, if requested.
After that , the importer is changed to one that contains just a single
mesh . . . */
bool singleMesh = false ;
if ( args . isSet ( " concatenate-meshes " ) | | args . value < Containers : : StringView > ( " mesh " ) ) {
singleMesh = true ;
/* ... and subsequent conversion deals with just meshes, throwing away
materials and everything else ( if present ) . */
Containers : : Optional < Trade : : MeshData > mesh ;
/* Concatenate all meshes together */
if ( args . isSet ( " concatenate-meshes " ) ) {
if ( ! importer - > meshCount ( ) ) {
Error { } < < " No meshes found in " < < args . value ( " input " ) ;
return 1 ;
}
Containers : : Array < Trade : : MeshData > meshes ;
arrayReserve ( meshes , importer - > meshCount ( ) ) ;
/** @todo handle mesh levels here, once any plugin is capable of
importing them */
for ( std : : size_t i = 0 , iMax = importer - > meshCount ( ) ; i ! = iMax ; + + i ) {
Trade : : Implementation : : Duration d { importConversionTime } ;
Containers : : Optional < Trade : : MeshData > meshToConcatenate = importer - > mesh ( i ) ;
if ( ! meshToConcatenate ) {
Error { } < < " Cannot import mesh " < < i ;
return 1 ;
}
arrayAppend ( meshes , * std : : move ( meshToConcatenate ) ) ;
}
/* If there's a scene, use it to flatten mesh hierarchy. If not,
assume all meshes are in the root . */
/** @todo make it possible to choose the scene */
if ( importer - > defaultScene ( ) ! = - 1 ) {
Containers : : Optional < Trade : : SceneData > scene ;
{
Trade : : Implementation : : Duration d { importConversionTime } ;
if ( ! ( scene = importer - > scene ( importer - > defaultScene ( ) ) ) ) {
Error { } < < " Cannot import scene " < < importer - > defaultScene ( ) < < " for mesh concatenation " ;
return 1 ;
}
}
Containers : : Array < Trade : : MeshData > flattenedMeshes ;
{
Trade : : Implementation : : Duration d { conversionTime } ;
/** @todo once there are 2D scenes, check the scene is 3D */
for ( const Containers : : Triple < UnsignedInt , Int , Matrix4 > & meshTransformation : SceneTools : : flattenMeshHierarchy3D ( * scene ) )
arrayAppend ( flattenedMeshes , MeshTools : : transform3D ( meshes [ meshTransformation . first ( ) ] , meshTransformation . third ( ) ) ) ;
}
meshes = std : : move ( flattenedMeshes ) ;
}
{
Trade : : Implementation : : Duration d { conversionTime } ;
/** @todo this will assert if the meshes have incompatible primitives
( such as some triangles , some lines ) , or if they have
loops / strips / fans - - handle that explicitly */
mesh = MeshTools : : concatenate ( meshes ) ;
}
/* Otherwise import just one */
} else {
Trade : : Implementation : : Duration d { importConversionTime } ;
if ( ! ( mesh = importer - > mesh ( args . value < UnsignedInt > ( " mesh " ) , args . value < UnsignedInt > ( " mesh-level " ) ) ) ) {
Error { } < < " Cannot import the mesh " ;
return 4 ;
}
}
/* Filter mesh attributes, if requested */
/** @todo move outside of the --mesh / --concatenate-meshes branch once
it ' s possible to filter attributes by name */
if ( Containers : : StringView onlyAttributes = args . value < Containers : : StringView > ( " only-mesh-attributes " ) ) {
const Containers : : Optional < Containers : : Array < UnsignedInt > > only = Utility : : String : : parseNumberSequence ( onlyAttributes , 0 , mesh - > attributeCount ( ) ) ;
if ( ! only ) return 2 ;
/** @todo use MeshTools::filterOnlyAttributes() once it has a
rvalue overload that transfers ownership */
Containers : : Array < Trade : : MeshAttributeData > attributes ;
arrayReserve ( attributes , only - > size ( ) ) ;
for ( UnsignedInt i : * only )
arrayAppend ( attributes , mesh - > attributeData ( i ) ) ;
const Trade : : MeshIndexData indices { mesh - > indices ( ) } ;
const UnsignedInt vertexCount = mesh - > vertexCount ( ) ;
mesh = Trade : : MeshData { mesh - > primitive ( ) ,
mesh - > releaseIndexData ( ) , indices ,
mesh - > releaseVertexData ( ) , std : : move ( attributes ) ,
vertexCount } ;
}
/* Create an importer instance that contains just the single mesh for
further step , without any other data . Simpler than having to
special - case the single - mesh case in all following steps . */
struct SingleMeshImporter : Trade : : AbstractImporter {
explicit SingleMeshImporter ( Trade : : MeshData & & mesh ) : mesh { std : : move ( mesh ) } { }
Trade : : ImporterFeatures doFeatures ( ) const override { return { } ; } /* LCOV_EXCL_LINE */
bool doIsOpened ( ) const override { return true ; }
void doClose ( ) override { } /* LCOV_EXCL_LINE */
UnsignedInt doMeshCount ( ) const override { return 1 ; }
Containers : : Optional < Trade : : MeshData > doMesh ( UnsignedInt , UnsignedInt ) override {
return MeshTools : : reference ( mesh ) ;
}
Trade : : MeshData mesh ;
} ;
importer . emplace < SingleMeshImporter > ( * std : : move ( mesh ) ) ;
}
/* Operations to perform on all meshes in the importer. If there are any,
meshes are supplied manually to the converter from the array below . */
Containers : : Array < Trade : : MeshData > meshes ;
if ( args . isSet ( " remove-duplicate-vertices " ) | |
args . value < Containers : : StringView > ( " remove-duplicate-vertices-fuzzy " ) )
{
for ( UnsignedInt i = 0 ; i ! = importer - > meshCount ( ) ; + + i ) {
Containers : : Optional < Trade : : MeshData > mesh ;
{
/** @todo handle mesh levels here, once any plugin is capable
of importing them */
Trade : : Implementation : : Duration d { importConversionTime } ;
if ( ! ( mesh = importer - > mesh ( i ) ) ) {
Error { } < < " Cannot import mesh " < < i ;
return 1 ;
}
}
const UnsignedInt beforeVertexCount = mesh - > vertexCount ( ) ;
const bool fuzzy = ! ! args . value < Containers : : StringView > ( " remove-duplicate-vertices-fuzzy " ) ;
/** @todo accept two values for float and double fuzzy comparison,
or maybe also different for positions , normals and texcoords ?
ugh . . . */
if ( fuzzy ) {
Trade : : Implementation : : Duration d { conversionTime } ;
mesh = MeshTools : : removeDuplicatesFuzzy ( * std : : move ( mesh ) , args . value < Float > ( " remove-duplicate-vertices-fuzzy " ) ) ;
} else {
Trade : : Implementation : : Duration d { conversionTime } ;
mesh = MeshTools : : removeDuplicates ( * std : : move ( mesh ) ) ;
}
if ( args . isSet ( " verbose " ) ) {
Debug d ;
/* Mesh index 0 would be confusing in case of
- - concatenate - meshes and plain wrong with - - mesh , so
don ' t even print it */
if ( singleMesh )
d < < ( fuzzy ? " Fuzzy duplicate removal: " : " Duplicate removal: " ) ;
else
d < < " Mesh " < < i < < ( fuzzy ? " fuzzy duplicate removal: " : " duplicate removal: " ) ;
d < < beforeVertexCount < < " -> " < < mesh - > vertexCount ( ) < < " vertices " ;
}
arrayAppend ( meshes , * std : : move ( mesh ) ) ;
}
}
/* Assume there's always one passed --converter option less, and the last
is implicitly AnySceneConverter . All converters except the last one are
expected to support ConvertMesh and the mesh is " piped " from one to the
other . If the last converter supports ConvertMeshToFile instead of
ConvertMesh , it ' s used instead of the last implicit AnySceneConverter . */
for ( std : : size_t i = 0 , converterCount = args . arrayValueCount ( " converter " ) ; i < = converterCount ; + + i ) {
/* Load converter plugin */
const Containers : : StringView converterName = i = = converterCount ?
" AnySceneConverter " _s : args . arrayValue < Containers : : StringView > ( " converter " , i ) ;
Containers : : Pointer < Trade : : AbstractSceneConverter > converter = converterManager . loadAndInstantiate ( converterName ) ;
if ( ! converter ) {
Debug { } < < " Available converter plugins: " < < " , " _s . join ( converterManager . aliasList ( ) ) ;
return 2 ;
}
/* Set options, if passed */
if ( args . isSet ( " verbose " ) ) converter - > addFlags ( Trade : : SceneConverterFlag : : Verbose ) ;
if ( i < args . arrayValueCount ( " converter-options " ) )
Implementation : : setOptions ( * converter , " AnySceneConverter " , args . arrayValue ( " converter-options " , i ) ) ;
/* Decide if this is the last converter, capable of saving to a file */
const bool isLastConverter = i + 1 > = converterCount & & ( converter - > features ( ) & ( Trade : : SceneConverterFeature : : ConvertMeshToFile | Trade : : SceneConverterFeature : : ConvertMultipleToFile ) ) ;
/* No verbose output for just one converter */
if ( converterCount > 1 & & args . isSet ( " verbose " ) ) {
if ( isLastConverter ) {
Debug { } < < " Saving output ( " < < Debug : : nospace < < ( i + 1 ) < < Debug : : nospace < < " / " < < Debug : : nospace < < converterCount < < Debug : : nospace < < " ) with " < < converterName < < Debug : : nospace < < " ... " ;
} else {
CORRADE_INTERNAL_ASSERT ( i < converterCount ) ;
Debug { } < < " Processing ( " < < Debug : : nospace < < ( i + 1 ) < < Debug : : nospace < < " / " < < Debug : : nospace < < converterCount < < Debug : : nospace < < " ) with " < < converterName < < Debug : : nospace < < " ... " ;
}
}
/* This is the last --converter (or the implicit AnySceneConverter at
the end ) , output to a file */
if ( isLastConverter ) {
{
Trade : : Implementation : : Duration d { conversionTime } ;
if ( ! converter - > beginFile ( args . value ( " output " ) ) ) {
Error { } < < " Cannot begin conversion of file " < < args . value ( " output " ) ;
return 1 ;
}
}
/* This is not the last converter, expect that it's capable of
converting to an importer instance ( or a MeshData wrapped in an
importer instance ) */
} else {
if ( ! ( converter - > features ( ) & ( Trade : : SceneConverterFeature : : ConvertMesh | Trade : : SceneConverterFeature : : ConvertMultiple ) ) ) {
Error { } < < converterName < < " doesn't support importer conversion, only " < < converter - > features ( ) ;
return 6 ;
}
{
Trade : : Implementation : : Duration d { conversionTime } ;
if ( ! converter - > begin ( ) ) {
Error { } < < " Cannot begin importer conversion " ;
return 1 ;
}
}
}
/* Contents to convert, by default all of them */
/** @todo make it possible to filter this on the command line, once the
converters receive this for SceneData , MaterialData and TextureData
as well */
Trade : : SceneContents contents = ~ Trade : : SceneContents { } ;
/* If there are any loose meshes from previous conversion steps, add
them directly instead , and clear the array so the next iteration ( if
any ) takes them from the importer instead */
if ( meshes ) {
if ( ! ( Trade : : sceneContentsFor ( * converter ) & Trade : : SceneContent : : Meshes ) ) {
Warning { } < < " Ignoring " < < meshes . size ( ) < < " meshes not supported by the converter " ;
} else for ( UnsignedInt i = 0 ; i ! = meshes . size ( ) ; + + i ) {
Trade : : Implementation : : Duration d { conversionTime } ;
if ( ! converter - > add ( meshes [ i ] ) ) {
Error { } < < " Cannot add mesh " < < i ;
return 1 ;
}
}
/* Ensure the meshes are not added by addSupportedImporterContents()
below . Do this also in case the converter actually doesn ' t
support mesh addition , as it would otherwise cause two warnings
about the same thing being printed . */
contents & = ~ Trade : : SceneContent : : Meshes ;
/* Delete the list to avoid adding them again for the next
converter ( at which point they would be stale ) */
/** @todo this line is untested, needs two chained conversion steps
that each change the output to verify the old meshes don ' t get
reused in the next step again */
meshes = { } ;
}
{
Trade : : Implementation : : Duration d { importConversionTime } ;
if ( ! converter - > addSupportedImporterContents ( * importer , contents ) ) {
Error { } < < " Cannot add importer contents " ;
return 5 ;
}
}
/* This is the last --converter (or the implicit AnySceneConverter at
the end ) , end the file and exit the loop */
if ( isLastConverter ) {
Trade : : Implementation : : Duration d { conversionTime } ;
if ( ! converter - > endFile ( ) ) {
Error { } < < " Cannot end conversion of file " < < args . value ( " output " ) ;
return 5 ;
}
break ;
/* This is not the last converter, save the resulting importer instance
for the next loop iteration . By design , the importer should not
depend on any data from the converter instance , only on the
converter plugin , so we should be fine replacing the converter with
a different one in the next iteration and keeping just the importer
returned from it . */
} else {
Trade : : Implementation : : Duration d { conversionTime } ;
if ( ! ( importer = converter - > end ( ) ) ) {
Error { } < < " Cannot end importer conversion " ;
return 1 ;
}
}
}
if ( args . isSet ( " profile " ) ) {
Debug { } < < " Import and conversion took " < < UnsignedInt ( std : : chrono : : duration_cast < std : : chrono : : milliseconds > ( importConversionTime ) . count ( ) ) / 1.0e3 f < < " seconds, conversion "
< < UnsignedInt ( std : : chrono : : duration_cast < std : : chrono : : milliseconds > ( conversionTime ) . count ( ) ) / 1.0e3 f < < " seconds " ;
}
}