2020-05-17 08:42:43 -07:00
/**
* @ file
*/
# include "VoxConvert.h"
# include "core/Color.h"
2022-01-19 10:25:18 -08:00
# include "core/Enum.h"
2021-12-15 12:34:30 -08:00
# include "core/GameConfig.h"
2021-11-30 12:18:28 -08:00
# include "core/StringUtil.h"
2021-12-15 13:31:35 -08:00
# include "core/Tokenizer.h"
2020-05-17 08:42:43 -07:00
# include "core/Var.h"
2020-08-30 13:36:16 -07:00
# include "command/Command.h"
2021-12-17 06:46:55 -08:00
# include "core/collection/DynamicArray.h"
# include "core/collection/Set.h"
2022-03-07 13:22:01 -08:00
# include "core/concurrent/Concurrency.h"
2021-11-30 12:18:28 -08:00
# include "image/Image.h"
2021-12-03 11:26:26 -08:00
# include "io/FileStream.h"
2020-08-30 13:16:13 -07:00
# include "io/Filesystem.h"
2020-08-30 12:49:29 -07:00
# include "metric/Metric.h"
2020-05-17 08:42:43 -07:00
# include "core/EventBus.h"
# include "core/TimeProvider.h"
2021-12-17 14:31:50 -08:00
# include "voxel/RawVolume.h"
2022-03-30 10:54:40 -07:00
# include "voxel/Region.h"
2022-01-19 10:25:18 -08:00
# include "voxelformat/SceneGraphNode.h"
2022-03-13 03:32:17 -07:00
# include "voxelformat/SceneGraphUtil.h"
2020-08-04 09:02:29 -07:00
# include "voxelformat/VolumeFormat.h"
2021-12-03 11:26:26 -08:00
# include "voxelformat/Format.h"
2022-01-05 11:50:28 -08:00
# include "voxelformat/SceneGraph.h"
2021-12-15 13:31:35 -08:00
# include "voxelgenerator/LUAGenerator.h"
2021-12-15 12:08:09 -08:00
# include "voxelutil/ImageUtils.h"
2022-01-04 06:04:58 -08:00
# include "voxelutil/VolumeCropper.h"
2020-05-22 11:53:28 -07:00
# include "voxelutil/VolumeRescaler.h"
2022-01-21 13:19:19 -08:00
# include "voxelutil/VolumeResizer.h"
2021-12-17 14:31:50 -08:00
# include "voxelutil/VolumeRotator.h"
2022-01-04 06:37:38 -08:00
# include "voxelutil/VolumeSplitter.h"
2022-04-04 10:44:53 -07:00
# include "voxelutil/VolumeVisitor.h"
2020-05-17 08:42:43 -07:00
2022-03-27 12:23:01 -07:00
# include <glm/gtc/quaternion.hpp>
# include <glm/trigonometric.hpp>
2022-02-02 11:55:26 -08:00
# define MaxHeightmapWidth 1024
# define MaxHeightmapHeight 1024
2020-05-17 08:42:43 -07:00
VoxConvert : : VoxConvert ( const metric : : MetricPtr & metric , const io : : FilesystemPtr & filesystem , const core : : EventBusPtr & eventBus , const core : : TimeProviderPtr & timeProvider ) :
2022-03-07 13:22:01 -08:00
Super ( metric , filesystem , eventBus , timeProvider , core : : cpus ( ) ) {
2020-05-17 08:42:43 -07:00
init ( ORGANISATION , " voxconvert " ) ;
}
2020-08-30 13:46:21 -07:00
app : : AppState VoxConvert : : onConstruct ( ) {
const app : : AppState state = Super : : onConstruct ( ) ;
2022-01-19 10:00:29 -08:00
registerArg ( " --crop " ) . setDescription ( " Reduce the volumes to their real voxel sizes " ) ;
2022-01-19 10:25:18 -08:00
registerArg ( " --dump " ) . setDescription ( " Dump the scene graph of the input file " ) ;
2022-01-15 01:38:49 -08:00
registerArg ( " --export-layers " ) . setDescription ( " Export all the layers of a scene into single files " ) ;
2022-05-23 11:21:36 -07:00
registerArg ( " --export-palette " ) . setDescription ( " Export the used palette data into an image " ) ;
2021-12-17 07:49:49 -08:00
registerArg ( " --filter " ) . setDescription ( " Layer filter. For example '1-4,6' " ) ;
2021-12-16 10:24:35 -08:00
registerArg ( " --force " ) . setShort ( " -f " ) . setDescription ( " Overwrite existing files " ) ;
2022-03-15 23:54:08 -07:00
registerArg ( " --image-as-plane " ) . setDescription ( " Import given input images as planes " ) ;
2022-03-31 13:57:46 -07:00
registerArg ( " --image-as-volume " ) . setDescription ( " Import given input image as volume " ) ;
registerArg ( " --image-as-volume-max-depth " ) . setDefaultValue ( " 8 " ) . setDescription ( " Importing image as volume max depth " ) ;
2022-05-01 12:27:48 -07:00
registerArg ( " --image-as-volume-both-sides " ) . setDefaultValue ( " false " ) . setDescription ( " Importing image as volume for both sides " ) ;
2022-03-15 23:54:08 -07:00
registerArg ( " --image-as-heightmap " ) . setDescription ( " Import given input images as heightmaps " ) ;
2021-12-21 08:07:34 -08:00
registerArg ( " --input " ) . setShort ( " -i " ) . setDescription ( " Allow to specify input files " ) ;
2020-05-18 06:38:44 -07:00
registerArg ( " --merge " ) . setShort ( " -m " ) . setDescription ( " Merge layers into one volume " ) ;
2021-12-17 14:31:50 -08:00
registerArg ( " --mirror " ) . setDescription ( " Mirror by the given axis (x, y or z) " ) ;
2021-12-21 08:07:34 -08:00
registerArg ( " --output " ) . setShort ( " -o " ) . setDescription ( " Allow to specify the output file " ) ;
2021-12-22 07:07:11 -08:00
registerArg ( " --pivot " ) . setDescription ( " Change the pivots of the volume layers " ) ;
2021-12-17 14:35:22 -08:00
registerArg ( " --rotate " ) . setDescription ( " Rotate by 90 degree at the given axis (x, y or z) " ) ;
2022-01-21 13:19:19 -08:00
registerArg ( " --resize " ) . setDescription ( " Resize the volume by the given x (right), y (up) and z (back) values " ) ;
2020-05-22 11:53:28 -07:00
registerArg ( " --scale " ) . setShort ( " -s " ) . setDescription ( " Scale layer to 50% of its original size " ) ;
2021-12-15 13:31:35 -08:00
registerArg ( " --script " ) . setDefaultValue ( " script.lua " ) . setDescription ( " Apply the given lua script to the output volume " ) ;
2022-01-19 10:00:29 -08:00
registerArg ( " --split " ) . setDescription ( " Slices the volumes into pieces of the given size <x:y:z> " ) ;
2021-12-21 08:50:19 -08:00
registerArg ( " --translate " ) . setShort ( " -t " ) . setDescription ( " Translate the volumes by x (right), y (up), z (back) " ) ;
2020-06-07 11:12:08 -07:00
2022-02-22 09:57:54 -08:00
_mergeQuads = core : : Var : : get ( cfg : : VoxformatMergequads , " true " , core : : CV_NOPERSIST , " Merge similar quads to optimize the mesh " ) ;
_reuseVertices = core : : Var : : get ( cfg : : VoxformatReusevertices , " true " , core : : CV_NOPERSIST , " Reuse vertices or always create new ones " ) ;
_ambientOcclusion = core : : Var : : get ( cfg : : VoxformatAmbientocclusion , " false " , core : : CV_NOPERSIST , " Extra vertices for ambient occlusion " ) ;
2022-03-05 07:04:22 -08:00
_scale = core : : Var : : get ( cfg : : VoxformatScale , " 1.0 " , core : : CV_NOPERSIST , " Scale the vertices on all axis by the given factor " ) ;
_scaleX = core : : Var : : get ( cfg : : VoxformatScaleX , " 1.0 " , core : : CV_NOPERSIST , " Scale the vertices on X axis by the given factor " ) ;
_scaleY = core : : Var : : get ( cfg : : VoxformatScaleY , " 1.0 " , core : : CV_NOPERSIST , " Scale the vertices on Y axis by the given factor " ) ;
_scaleZ = core : : Var : : get ( cfg : : VoxformatScaleZ , " 1.0 " , core : : CV_NOPERSIST , " Scale the vertices on Z axis by the given factor " ) ;
2022-02-28 13:18:45 -08:00
_frame = core : : Var : : get ( cfg : : VoxformatFrame , " 0 " , core : : CV_NOPERSIST , " Which frame to import for formats that support this - starting at 0 " ) ;
2022-02-22 09:57:54 -08:00
_quads = core : : Var : : get ( cfg : : VoxformatQuads , " true " , core : : CV_NOPERSIST , " Export as quads. If this false, triangles will be used. " ) ;
_withColor = core : : Var : : get ( cfg : : VoxformatWithcolor , " true " , core : : CV_NOPERSIST , " Export with vertex colors " ) ;
_withTexCoords = core : : Var : : get ( cfg : : VoxformatWithtexcoords , " true " , core : : CV_NOPERSIST , " Export with uv coordinates of the palette image " ) ;
2022-03-02 08:13:22 -08:00
core : : Var : : get ( cfg : : VoxformatTransform , " false " , core : : CV_NOPERSIST , " Apply the scene graph transform to mesh exports " ) ;
2022-04-20 08:23:08 -07:00
_palette = core : : Var : : get ( cfg : : VoxelPalette , voxel : : Palette : : getDefaultPaletteName ( ) , " This is the NAME part of palette-<NAME>.png or absolute png file to use (1x256) " ) ;
2020-06-07 11:12:08 -07:00
2021-12-15 13:31:35 -08:00
if ( ! filesystem ( ) - > registerPath ( " scripts/ " ) ) {
Log : : warn ( " Failed to register lua generator script path " ) ;
}
2020-05-18 06:38:44 -07:00
return state ;
}
2021-12-08 10:19:47 -08:00
void VoxConvert : : usage ( ) const {
Super : : usage ( ) ;
Log : : info ( " Load support: " ) ;
for ( const io : : FormatDescription * desc = voxelformat : : SUPPORTED_VOXEL_FORMATS_LOAD ; desc - > ext ! = nullptr ; + + desc ) {
Log : : info ( " * %s (*.%s) " , desc - > name , desc - > ext ) ;
}
Log : : info ( " Save support: " ) ;
for ( const io : : FormatDescription * desc = voxelformat : : SUPPORTED_VOXEL_FORMATS_SAVE ; desc - > ext ! = nullptr ; + + desc ) {
Log : : info ( " * %s (*.%s) " , desc - > name , desc - > ext ) ;
}
2022-03-30 12:06:24 -07:00
Log : : info ( " Links: " ) ;
Log : : info ( " * Bug reports: https://github.com/mgerhardy/vengi " ) ;
Log : : info ( " * Twitter: https://twitter.com/MartinGerhardy " ) ;
Log : : info ( " * Discord: https://discord.gg/AgjCPXy " ) ;
2021-12-08 10:19:47 -08:00
}
2020-08-30 13:46:21 -07:00
app : : AppState VoxConvert : : onInit ( ) {
const app : : AppState state = Super : : onInit ( ) ;
if ( state ! = app : : AppState : : Running ) {
2020-05-17 08:42:43 -07:00
return state ;
}
if ( _argc < 2 ) {
_logLevelVar - > setVal ( SDL_LOG_PRIORITY_INFO ) ;
Log : : init ( ) ;
usage ( ) ;
2020-08-30 13:46:21 -07:00
return app : : AppState : : InitFailure ;
2020-05-17 08:42:43 -07:00
}
2021-12-21 08:07:34 -08:00
core : : String infilesstr ;
core : : DynamicArray < core : : String > infiles ;
if ( hasArg ( " --input " ) ) {
int argn = 0 ;
for ( ; ; ) {
const core : : String & val = getArgVal ( " --input " , " " , & argn ) ;
if ( val . empty ( ) ) {
break ;
}
infiles . push_back ( val ) ;
if ( ! infilesstr . empty ( ) ) {
infilesstr + = " , " ;
}
infilesstr + = val ;
}
} else {
2022-01-15 04:35:16 -08:00
Log : : error ( " No input file was specified " ) ;
return app : : AppState : : InitFailure ;
2021-12-21 08:07:34 -08:00
}
core : : String outfile ;
if ( hasArg ( " --output " ) ) {
outfile = getArgVal ( " --output " ) ;
}
2020-05-17 08:42:43 -07:00
2022-05-23 11:21:36 -07:00
_mergeVolumes = hasArg ( " --merge " ) ;
_scaleVolumes = hasArg ( " --scale " ) ;
_mirrorVolumes = hasArg ( " --mirror " ) ;
_rotateVolumes = hasArg ( " --rotate " ) ;
2022-02-02 13:54:05 -08:00
_translateVolumes = hasArg ( " --translate " ) ;
2022-05-23 11:21:36 -07:00
_exportPalette = hasArg ( " --export-palette " ) ;
_exportLayers = hasArg ( " --export-layers " ) ;
_changePivot = hasArg ( " --pivot " ) ;
_cropVolumes = hasArg ( " --crop " ) ;
_splitVolumes = hasArg ( " --split " ) ;
_dumpSceneGraph = hasArg ( " --dump " ) ;
_resizeVolumes = hasArg ( " --resize " ) ;
2020-10-01 09:16:42 -07:00
Log : : info ( " Options " ) ;
if ( voxelformat : : isMeshFormat ( outfile ) ) {
2022-05-23 11:21:36 -07:00
Log : : info ( " * mergeQuads: - %s " , _mergeQuads - > strVal ( ) . c_str ( ) ) ;
Log : : info ( " * reuseVertices: - %s " , _reuseVertices - > strVal ( ) . c_str ( ) ) ;
Log : : info ( " * ambientOcclusion: - %s " , _ambientOcclusion - > strVal ( ) . c_str ( ) ) ;
Log : : info ( " * scale: - %s " , _scale - > strVal ( ) . c_str ( ) ) ;
Log : : info ( " * scaleX: - %s " , _scaleX - > strVal ( ) . c_str ( ) ) ;
Log : : info ( " * scaleY: - %s " , _scaleY - > strVal ( ) . c_str ( ) ) ;
Log : : info ( " * scaleZ: - %s " , _scaleZ - > strVal ( ) . c_str ( ) ) ;
Log : : info ( " * quads: - %s " , _quads - > strVal ( ) . c_str ( ) ) ;
Log : : info ( " * withColor: - %s " , _withColor - > strVal ( ) . c_str ( ) ) ;
Log : : info ( " * withTexCoords: - %s " , _withTexCoords - > strVal ( ) . c_str ( ) ) ;
}
if ( ! _palette - > strVal ( ) . empty ( ) ) {
Log : : info ( " * palette: - %s " , _palette - > strVal ( ) . c_str ( ) ) ;
}
Log : : info ( " * input files: - %s " , infilesstr . c_str ( ) ) ;
2022-01-15 04:35:53 -08:00
if ( ! outfile . empty ( ) ) {
2022-05-23 11:21:36 -07:00
Log : : info ( " * output files: - %s " , outfile . c_str ( ) ) ;
2022-01-15 04:35:53 -08:00
}
2022-03-17 02:14:37 -07:00
if ( io : : isImage ( outfile ) & & infiles . size ( ) = = 1 ) {
voxel : : Palette palette ;
2022-03-16 13:38:09 -07:00
if ( ! voxelformat : : importPalette ( infiles [ 0 ] , palette ) ) {
2022-03-17 02:14:37 -07:00
Log : : error ( " Failed to import the palette from %s " , infiles [ 0 ] . c_str ( ) ) ;
return app : : AppState : : InitFailure ;
}
if ( palette . save ( outfile . c_str ( ) ) ) {
Log : : info ( " Saved palette with %i colors to %s " , palette . colorCount , outfile . c_str ( ) ) ;
return state ;
}
Log : : error ( " Failed to write %s " , outfile . c_str ( ) ) ;
return app : : AppState : : InitFailure ;
}
2021-12-15 13:31:35 -08:00
core : : String scriptParameters ;
if ( hasArg ( " --script " ) ) {
scriptParameters = getArgVal ( " --script " ) ;
2022-05-23 11:21:36 -07:00
Log : : info ( " * script: - %s " , scriptParameters . c_str ( ) ) ;
}
Log : : info ( " * dump scene graph: - %s " , ( _dumpSceneGraph ? " true " : " false " ) ) ;
Log : : info ( " * merge volumes: - %s " , ( _mergeVolumes ? " true " : " false " ) ) ;
Log : : info ( " * scale volumes: - %s " , ( _scaleVolumes ? " true " : " false " ) ) ;
Log : : info ( " * crop volumes: - %s " , ( _cropVolumes ? " true " : " false " ) ) ;
Log : : info ( " * split volumes: - %s " , ( _splitVolumes ? " true " : " false " ) ) ;
Log : : info ( " * change pivot: - %s " , ( _changePivot ? " true " : " false " ) ) ;
Log : : info ( " * mirror volumes: - %s " , ( _mirrorVolumes ? " true " : " false " ) ) ;
Log : : info ( " * translate volumes: - %s " , ( _translateVolumes ? " true " : " false " ) ) ;
Log : : info ( " * rotate volumes: - %s " , ( _rotateVolumes ? " true " : " false " ) ) ;
Log : : info ( " * export palette: - %s " , ( _exportPalette ? " true " : " false " ) ) ;
Log : : info ( " * export layers: - %s " , ( _exportLayers ? " true " : " false " ) ) ;
Log : : info ( " * resize volumes: - %s " , ( _resizeVolumes ? " true " : " false " ) ) ;
voxel : : Palette palette ;
if ( palette . load ( _palette - > strVal ( ) . c_str ( ) ) ) {
2022-02-28 10:03:15 -08:00
voxel : : initPalette ( palette ) ;
2021-11-30 11:39:06 -08:00
}
2022-01-15 04:35:53 -08:00
io : : FilePtr outputFile ;
if ( ! outfile . empty ( ) ) {
const bool outfileExists = filesystem ( ) - > open ( outfile ) - > exists ( ) ;
if ( outfileExists ) {
if ( ! hasArg ( " --force " ) ) {
Log : : error ( " Given output file '%s' already exists " , outfile . c_str ( ) ) ;
return app : : AppState : : InitFailure ;
}
2020-06-11 10:37:59 -07:00
}
2022-01-15 04:35:53 -08:00
outputFile = filesystem ( ) - > open ( outfile , io : : FileMode : : SysWrite ) ;
if ( ! outputFile - > validHandle ( ) ) {
Log : : error ( " Could not open target file: %s " , outfile . c_str ( ) ) ;
return app : : AppState : : InitFailure ;
}
2022-02-02 13:54:05 -08:00
} else if ( ! _exportLayers & & ! _exportPalette & & ! _dumpSceneGraph ) {
2022-01-15 04:35:53 -08:00
Log : : error ( " No output specified " ) ;
2021-12-19 10:01:07 -08:00
return app : : AppState : : InitFailure ;
}
2022-03-16 13:17:52 -07:00
voxelformat : : SceneGraph sceneGraph ;
2021-12-21 08:07:34 -08:00
for ( const core : : String & infile : infiles ) {
2022-02-02 11:43:44 -08:00
if ( filesystem ( ) - > isReadableDir ( infile ) ) {
core : : DynamicArray < io : : Filesystem : : DirEntry > entities ;
filesystem ( ) - > list ( infile , entities ) ;
Log : : info ( " Found %i entries in dir %s " , ( int ) entities . size ( ) , infile . c_str ( ) ) ;
int success = 0 ;
for ( const io : : Filesystem : : DirEntry & entry : entities ) {
2022-02-02 12:25:12 -08:00
if ( entry . type ! = io : : Filesystem : : DirEntry : : Type : : file ) {
continue ;
}
2022-02-02 12:58:27 -08:00
const core : : String fullpath = core : : string : : path ( infile , entry . name ) ;
2022-03-12 05:09:41 -08:00
if ( ! handleInputFile ( fullpath , sceneGraph , infiles . size ( ) > 1 ) ) {
2022-02-02 11:43:44 -08:00
+ + success ;
}
2021-12-21 08:07:34 -08:00
}
2022-02-02 11:43:44 -08:00
if ( success = = 0 ) {
Log : : error ( " Could not find a valid input file in directory %s " , infile . c_str ( ) ) ;
2021-12-21 08:07:34 -08:00
return app : : AppState : : InitFailure ;
}
} else {
2022-03-12 05:09:41 -08:00
if ( ! handleInputFile ( infile , sceneGraph , infiles . size ( ) > 1 ) ) {
2021-12-21 08:07:34 -08:00
return app : : AppState : : InitFailure ;
}
2022-01-15 04:35:53 -08:00
}
2021-12-22 11:36:38 -08:00
}
const bool applyFilter = hasArg ( " --filter " ) ;
if ( applyFilter ) {
if ( infiles . size ( ) = = 1u ) {
2022-01-06 03:36:22 -08:00
filterVolumes ( sceneGraph ) ;
2021-12-22 11:36:38 -08:00
} else {
Log : : warn ( " Don't apply layer filters for multiple input files " ) ;
}
2020-05-22 11:53:28 -07:00
}
2022-02-02 13:54:05 -08:00
if ( _exportLayers ) {
2022-01-15 01:38:49 -08:00
if ( infiles . size ( ) > 1 ) {
Log : : warn ( " The format and path of the first input file is used for exporting all layers " ) ;
}
exportLayersIntoSingleObjects ( sceneGraph , infiles [ 0 ] ) ;
}
2022-02-02 13:54:05 -08:00
if ( _mergeVolumes ) {
2021-12-21 10:47:48 -08:00
Log : : info ( " Merge layers " ) ;
2022-05-09 08:42:30 -07:00
const voxelformat : : SceneGraph : : MergedVolumePalette & merged = sceneGraph . merge ( ) ;
if ( merged . first = = nullptr ) {
2021-12-21 10:47:48 -08:00
Log : : error ( " Failed to merge volumes " ) ;
return app : : AppState : : InitFailure ;
2021-12-15 13:31:35 -08:00
}
2022-01-06 03:36:22 -08:00
sceneGraph . clear ( ) ;
2022-03-16 13:17:52 -07:00
voxelformat : : SceneGraphNode node ;
2022-05-09 08:42:30 -07:00
node . setPalette ( merged . second ) ;
node . setVolume ( merged . first , true ) ;
2022-01-06 03:36:22 -08:00
node . setName ( infilesstr ) ;
2022-01-07 13:26:08 -08:00
sceneGraph . emplace ( core : : move ( node ) ) ;
2021-12-21 10:47:48 -08:00
}
2021-12-15 13:31:35 -08:00
2022-02-02 13:54:05 -08:00
if ( _scaleVolumes ) {
2022-01-06 03:36:22 -08:00
scale ( sceneGraph ) ;
2021-12-15 13:31:35 -08:00
}
2022-02-02 13:54:05 -08:00
if ( _resizeVolumes ) {
2022-01-21 13:19:19 -08:00
resize ( getArgIvec3 ( " --resize " ) , sceneGraph ) ;
}
2022-02-02 13:54:05 -08:00
if ( _mirrorVolumes ) {
2022-01-06 03:36:22 -08:00
mirror ( getArgVal ( " --mirror " ) , sceneGraph ) ;
2021-12-17 14:31:50 -08:00
}
2022-02-02 13:54:05 -08:00
if ( _rotateVolumes ) {
2022-01-06 03:36:22 -08:00
rotate ( getArgVal ( " --rotate " ) , sceneGraph ) ;
2021-12-17 14:35:22 -08:00
}
2022-02-02 13:54:05 -08:00
if ( _translateVolumes ) {
2022-01-06 03:36:22 -08:00
translate ( getArgIvec3 ( " --translate " ) , sceneGraph ) ;
2021-12-21 08:50:19 -08:00
}
2021-12-21 10:47:48 -08:00
if ( ! scriptParameters . empty ( ) ) {
2022-01-06 03:36:22 -08:00
script ( scriptParameters , sceneGraph ) ;
2021-12-21 10:47:48 -08:00
}
2022-02-02 13:54:05 -08:00
if ( _changePivot ) {
2022-01-06 03:36:22 -08:00
pivot ( getArgIvec3 ( " --pivot " ) , sceneGraph ) ;
2021-12-22 07:07:11 -08:00
}
2022-02-02 13:54:05 -08:00
if ( _cropVolumes ) {
2022-01-06 03:36:22 -08:00
crop ( sceneGraph ) ;
2022-01-04 06:04:58 -08:00
}
2022-02-02 13:54:05 -08:00
if ( _splitVolumes ) {
2022-01-06 03:36:22 -08:00
split ( getArgIvec3 ( " --split " ) , sceneGraph ) ;
2022-01-04 06:37:38 -08:00
}
2022-01-15 04:35:53 -08:00
if ( outputFile ) {
Log : : debug ( " Save %i volumes " , ( int ) sceneGraph . size ( ) ) ;
if ( ! voxelformat : : saveFormat ( outputFile , sceneGraph ) ) {
Log : : error ( " Failed to write to output file '%s' " , outfile . c_str ( ) ) ;
return app : : AppState : : InitFailure ;
}
Log : : info ( " Wrote output file %s " , outputFile - > name ( ) . c_str ( ) ) ;
2020-05-17 08:42:43 -07:00
}
return state ;
}
2022-01-15 01:38:49 -08:00
core : : String VoxConvert : : getFilenameForLayerName ( const core : : String & inputfile , const core : : String & layerName , int id ) {
const core : : String & ext = core : : string : : extractExtension ( inputfile ) ;
core : : String name ;
if ( layerName . empty ( ) ) {
name = core : : string : : format ( " layer-%i.%s " , id , ext . c_str ( ) ) ;
} else {
name = core : : string : : format ( " %s.%s " , layerName . c_str ( ) , ext . c_str ( ) ) ;
}
2022-02-10 12:49:06 -08:00
return core : : string : : path ( core : : string : : extractPath ( inputfile ) , core : : string : : sanitizeFilename ( name ) ) ;
2022-01-15 01:38:49 -08:00
}
2022-03-16 13:17:52 -07:00
bool VoxConvert : : handleInputFile ( const core : : String & infile , voxelformat : : SceneGraph & sceneGraph , bool multipleInputs ) {
2022-02-02 13:54:05 -08:00
Log : : info ( " -- current input file: %s " , infile . c_str ( ) ) ;
2022-02-02 11:43:44 -08:00
const io : : FilePtr inputFile = filesystem ( ) - > open ( infile , io : : FileMode : : SysRead ) ;
if ( ! inputFile - > exists ( ) ) {
Log : : error ( " Given input file '%s' does not exist " , infile . c_str ( ) ) ;
_exitCode = 127 ;
return false ;
}
const bool inputIsImage = inputFile - > isAnyOf ( io : : format : : images ( ) ) ;
if ( inputIsImage ) {
const image : : ImagePtr & image = image : : loadImage ( inputFile , false ) ;
if ( ! image | | ! image - > isLoaded ( ) ) {
Log : : error ( " Couldn't load image %s " , infile . c_str ( ) ) ;
return false ;
}
2022-03-15 23:54:08 -07:00
const bool importAsPlane = hasArg ( " --image-as-plane " ) ;
2022-03-31 13:57:46 -07:00
const bool importAsVolume = hasArg ( " --image-as-volume " ) ;
2022-03-15 23:54:08 -07:00
const bool importAsHeightmap = hasArg ( " --image-as-heightmap " ) ;
2022-03-31 13:57:46 -07:00
if ( importAsHeightmap | | ( ! importAsPlane & & ! importAsVolume ) ) {
2022-03-15 23:54:08 -07:00
Log : : info ( " Generate from heightmap (%i:%i) " , image - > width ( ) , image - > height ( ) ) ;
if ( image - > width ( ) > MaxHeightmapWidth | | image - > height ( ) > = MaxHeightmapHeight ) {
Log : : warn ( " Skip creating heightmap - image dimensions exceeds the max allowed boundaries " ) ;
return false ;
}
voxel : : Region region ( 0 , 0 , 0 , image - > width ( ) , 255 , image - > height ( ) ) ;
voxel : : RawVolume * volume = new voxel : : RawVolume ( region ) ;
voxel : : RawVolumeWrapper wrapper ( volume ) ;
const voxel : : Voxel dirtVoxel = voxel : : createColorVoxel ( voxel : : VoxelType : : Dirt , 0 ) ;
const voxel : : Voxel grassVoxel = voxel : : createColorVoxel ( voxel : : VoxelType : : Grass , 0 ) ;
voxelutil : : importHeightmap ( wrapper , image , dirtVoxel , grassVoxel ) ;
2022-03-16 13:17:52 -07:00
voxelformat : : SceneGraphNode node ;
2022-03-15 23:54:08 -07:00
node . setVolume ( volume , true ) ;
node . setName ( infile ) ;
sceneGraph . emplace ( core : : move ( node ) ) ;
}
2022-03-31 13:57:46 -07:00
if ( importAsVolume ) {
voxelformat : : SceneGraphNode node ;
2022-04-29 08:21:36 -07:00
const int maxDepth = glm : : clamp ( core : : string : : toInt ( getArgVal ( " --image-as-volume-max-depth " ) ) , 1 , 255 ) ;
2022-03-31 13:57:46 -07:00
const bool bothSides = core : : string : : toBool ( getArgVal ( " --image-as-volume-both-sides " ) ) ;
2022-03-31 14:09:13 -07:00
node . setVolume ( voxelutil : : importAsVolume ( image , maxDepth , bothSides ) , true ) ;
2022-03-31 13:57:46 -07:00
node . setName ( infile ) ;
sceneGraph . emplace ( core : : move ( node ) ) ;
}
2022-03-15 23:54:08 -07:00
if ( importAsPlane ) {
2022-03-16 13:17:52 -07:00
voxelformat : : SceneGraphNode node ;
2022-03-15 23:54:08 -07:00
node . setVolume ( voxelutil : : importAsPlane ( image ) , true ) ;
node . setName ( infile ) ;
sceneGraph . emplace ( core : : move ( node ) ) ;
2022-02-02 11:55:26 -08:00
}
2022-02-02 11:43:44 -08:00
} else {
2022-02-19 05:32:09 -08:00
io : : FileStream inputFileStream ( inputFile ) ;
2022-03-16 13:17:52 -07:00
voxelformat : : SceneGraph newSceneGraph ;
2022-02-02 11:43:44 -08:00
if ( ! voxelformat : : loadFormat ( inputFile - > name ( ) , inputFileStream , newSceneGraph ) ) {
return false ;
}
2022-03-12 05:09:41 -08:00
int parent = sceneGraph . root ( ) . id ( ) ;
if ( multipleInputs ) {
2022-03-16 13:17:52 -07:00
voxelformat : : SceneGraphNode groupNode ( voxelformat : : SceneGraphNodeType : : Group ) ;
2022-03-12 05:09:41 -08:00
groupNode . setName ( core : : string : : extractFilename ( infile ) ) ;
parent = sceneGraph . emplace ( core : : move ( groupNode ) , parent ) ;
2022-02-02 11:43:44 -08:00
}
2022-03-16 13:17:52 -07:00
voxelformat : : addSceneGraphNodes ( sceneGraph , newSceneGraph , parent ) ;
2022-03-12 05:09:41 -08:00
if ( _dumpSceneGraph ) {
dump ( sceneGraph ) ;
2022-02-02 11:43:44 -08:00
}
}
2022-02-02 13:54:05 -08:00
if ( _exportPalette ) {
2022-02-02 11:43:44 -08:00
const core : : String & paletteFile = core : : string : : stripExtension ( infile ) + " .png " ;
2022-05-19 10:43:25 -07:00
sceneGraph . firstPalette ( ) . save ( paletteFile . c_str ( ) ) ;
2022-02-02 11:43:44 -08:00
}
return true ;
}
2022-03-16 13:17:52 -07:00
void VoxConvert : : exportLayersIntoSingleObjects ( voxelformat : : SceneGraph & sceneGraph , const core : : String & inputfile ) {
2022-01-15 01:38:49 -08:00
Log : : info ( " Export layers into single objects " ) ;
int n = 0 ;
2022-03-16 13:17:52 -07:00
for ( voxelformat : : SceneGraphNode & node : sceneGraph ) {
voxelformat : : SceneGraph newSceneGraph ;
voxelformat : : SceneGraphNode newNode ;
2022-05-09 09:10:49 -07:00
voxelformat : : copyNode ( node , newNode , false ) ;
2022-01-15 01:38:49 -08:00
newSceneGraph . emplace ( core : : move ( newNode ) ) ;
const core : : String & filename = getFilenameForLayerName ( inputfile , node . name ( ) , n ) ;
2022-01-15 04:35:53 -08:00
if ( voxelformat : : saveFormat ( filesystem ( ) - > open ( filename , io : : FileMode : : SysWrite ) , newSceneGraph ) ) {
2022-01-15 01:38:49 -08:00
Log : : info ( " .. %s " , filename . c_str ( ) ) ;
} else {
Log : : error ( " .. %s " , filename . c_str ( ) ) ;
}
}
}
2022-01-04 06:13:04 -08:00
glm : : ivec3 VoxConvert : : getArgIvec3 ( const core : : String & name ) {
const core : : String & arguments = getArgVal ( name ) ;
glm : : ivec3 t ( 0 ) ;
SDL_sscanf ( arguments . c_str ( ) , " %i:%i:%i " , & t . x , & t . y , & t . z ) ;
return t ;
}
2022-03-16 13:17:52 -07:00
void VoxConvert : : split ( const glm : : ivec3 & size , voxelformat : : SceneGraph & sceneGraph ) {
2022-01-04 06:37:38 -08:00
Log : : info ( " split volumes at %i:%i:%i " , size . x , size . y , size . z ) ;
2022-05-09 08:42:30 -07:00
const voxelformat : : SceneGraph : : MergedVolumePalette & merged = sceneGraph . merge ( ) ;
2022-01-06 04:33:48 -08:00
sceneGraph . clear ( ) ;
2022-01-04 06:37:38 -08:00
core : : DynamicArray < voxel : : RawVolume * > rawVolumes ;
2022-05-09 08:42:30 -07:00
voxelutil : : splitVolume ( merged . first , size , rawVolumes ) ;
delete merged . first ;
2022-01-04 06:37:38 -08:00
for ( voxel : : RawVolume * v : rawVolumes ) {
2022-03-16 13:17:52 -07:00
voxelformat : : SceneGraphNode node ;
2022-01-06 03:36:22 -08:00
node . setVolume ( v , true ) ;
2022-05-09 08:42:30 -07:00
node . setPalette ( merged . second ) ;
2022-01-07 13:26:08 -08:00
sceneGraph . emplace ( core : : move ( node ) ) ;
2022-01-04 06:37:38 -08:00
}
}
2022-04-04 10:44:53 -07:00
int VoxConvert : : dumpNode_r ( const voxelformat : : SceneGraph & sceneGraph , int nodeId , int indent ) {
2022-03-16 13:17:52 -07:00
const voxelformat : : SceneGraphNode & node = sceneGraph . node ( nodeId ) ;
2022-01-19 10:25:18 -08:00
static const char * NodeTypeStr [ ] {
" Root " ,
" Model " ,
" Group " ,
" Camera " ,
" Unknown "
} ;
2022-03-16 13:17:52 -07:00
static_assert ( core : : enumVal ( voxelformat : : SceneGraphNodeType : : Max ) = = lengthof ( NodeTypeStr ) , " Array sizes don't match Max " ) ;
2022-03-27 12:23:01 -07:00
2022-03-16 13:17:52 -07:00
const voxelformat : : SceneGraphNodeType type = node . type ( ) ;
2022-01-19 10:25:18 -08:00
Log : : info ( " %*sNode: %i (parent %i) " , indent , " " , nodeId , node . parent ( ) ) ;
Log : : info ( " %*s |- name: %s " , indent , " " , node . name ( ) . c_str ( ) ) ;
Log : : info ( " %*s |- type: %s " , indent , " " , NodeTypeStr [ core : : enumVal ( type ) ] ) ;
2022-04-04 10:44:53 -07:00
int voxels = 0 ;
2022-03-16 13:17:52 -07:00
if ( type = = voxelformat : : SceneGraphNodeType : : Model ) {
2022-04-04 10:44:53 -07:00
voxel : : RawVolume * v = node . volume ( ) ;
Log : : info ( " %*s |- volume: %s " , indent , " " , v ! = nullptr ? v - > region ( ) . toString ( ) . c_str ( ) : " no volume " ) ;
if ( v ) {
2022-04-04 10:47:51 -07:00
voxelutil : : visitVolume ( * v , [ & ] ( int , int , int , const voxel : : Voxel & ) { + + voxels ; } ) ;
2022-04-04 10:44:53 -07:00
}
2022-04-04 10:53:09 -07:00
Log : : info ( " %*s |- voxels: %i " , indent , " " , voxels ) ;
2022-01-19 10:25:18 -08:00
}
for ( const auto & entry : node . properties ( ) ) {
Log : : info ( " %*s |- %s: %s " , indent , " " , entry - > key . c_str ( ) , entry - > value . c_str ( ) ) ;
}
2022-03-27 12:23:01 -07:00
for ( const voxelformat : : SceneGraphKeyFrame & kf : node . keyFrames ( ) ) {
Log : : info ( " %*s |- keyframe: %i " , indent , " " , kf . frame ) ;
Log : : info ( " %*s |- long rotation: %s " , indent , " " , kf . longRotation ? " true " : " false " ) ;
2022-04-06 23:35:08 -07:00
Log : : info ( " %*s |- interpolation: %s " , indent , " " , voxelformat : : InterpolationTypeStr [ core : : enumVal ( kf . interpolation ) ] ) ;
2022-03-27 12:23:01 -07:00
Log : : info ( " %*s |- transform " , indent , " " ) ;
const glm : : vec3 & pivot = kf . transform . pivot ( ) ;
Log : : info ( " %*s |- pivot %f:%f:%f " , indent , " " , pivot . x , pivot . y , pivot . z ) ;
const glm : : vec3 & tr = kf . transform . translation ( ) ;
Log : : info ( " %*s |- translation %f:%f:%f " , indent , " " , tr . x , tr . y , tr . z ) ;
const glm : : quat & rt = kf . transform . orientation ( ) ;
const glm : : vec3 & rtEuler = glm : : degrees ( glm : : eulerAngles ( rt ) ) ;
Log : : info ( " %*s |- orientation %f:%f:%f:%f " , indent , " " , rt . x , rt . y , rt . z , rt . w ) ;
Log : : info ( " %*s |- euler %f:%f:%f " , indent , " " , rtEuler . x , rtEuler . y , rtEuler . z ) ;
const float sc = kf . transform . scale ( ) ;
Log : : info ( " %*s |- scale %f " , indent , " " , sc ) ;
}
2022-01-19 10:25:18 -08:00
Log : : info ( " %*s |- children: %i " , indent , " " , ( int ) node . children ( ) . size ( ) ) ;
for ( int children : node . children ( ) ) {
2022-04-04 10:44:53 -07:00
voxels + = dumpNode_r ( sceneGraph , children , indent + 2 ) ;
2022-01-19 10:25:18 -08:00
}
2022-04-04 10:44:53 -07:00
return voxels ;
2022-01-19 10:25:18 -08:00
}
2022-03-16 13:17:52 -07:00
void VoxConvert : : dump ( const voxelformat : : SceneGraph & sceneGraph ) {
2022-04-04 10:44:53 -07:00
int voxels = dumpNode_r ( sceneGraph , sceneGraph . root ( ) . id ( ) , 0 ) ;
Log : : info ( " Voxel count: %i " , voxels ) ;
2022-01-19 10:25:18 -08:00
}
2022-03-16 13:17:52 -07:00
void VoxConvert : : crop ( voxelformat : : SceneGraph & sceneGraph ) {
2022-01-04 06:04:58 -08:00
Log : : info ( " Crop volumes " ) ;
2022-03-16 13:17:52 -07:00
for ( voxelformat : : SceneGraphNode & node : sceneGraph ) {
2022-03-16 13:05:53 -07:00
node . setVolume ( voxelutil : : cropVolume ( node . volume ( ) ) , true ) ;
2022-01-04 06:04:58 -08:00
}
}
2022-03-16 13:17:52 -07:00
void VoxConvert : : pivot ( const glm : : ivec3 & pivot , voxelformat : : SceneGraph & sceneGraph ) {
2021-12-22 07:07:11 -08:00
Log : : info ( " Set pivot to %i:%i:%i " , pivot . x , pivot . y , pivot . z ) ;
2022-03-16 13:17:52 -07:00
for ( voxelformat : : SceneGraphNode & node : sceneGraph ) {
2022-02-28 13:18:45 -08:00
node . setPivot ( _frame - > intVal ( ) , pivot , node . region ( ) . getDimensionsInVoxels ( ) ) ;
2021-12-22 07:07:11 -08:00
}
}
2022-03-16 13:17:52 -07:00
void VoxConvert : : script ( const core : : String & scriptParameters , voxelformat : : SceneGraph & sceneGraph ) {
2021-12-21 10:47:48 -08:00
voxelgenerator : : LUAGenerator script ;
if ( ! script . init ( ) ) {
Log : : warn ( " Failed to initialize the script bindings " ) ;
} else {
core : : DynamicArray < core : : String > tokens ;
core : : string : : splitString ( scriptParameters , tokens ) ;
const core : : String & luaScript = script . load ( tokens [ 0 ] ) ;
if ( luaScript . empty ( ) ) {
Log : : error ( " Failed to load %s " , tokens [ 0 ] . c_str ( ) ) ;
} else {
const voxel : : Voxel voxel = voxel : : createVoxel ( voxel : : VoxelType : : Generic , 1 ) ;
core : : DynamicArray < voxelgenerator : : LUAParameterDescription > argsInfo ;
if ( ! script . argumentInfo ( luaScript , argsInfo ) ) {
Log : : warn ( " Failed to get argument details " ) ;
}
core : : DynamicArray < core : : String > args ( tokens . size ( ) - 1 ) ;
for ( size_t i = 1 ; i < tokens . size ( ) ; + + i ) {
args [ i - 1 ] = tokens [ i ] ;
}
Log : : info ( " Execute script %s " , tokens [ 0 ] . c_str ( ) ) ;
2022-03-16 13:17:52 -07:00
for ( voxelformat : : SceneGraphNode & node : sceneGraph ) {
2022-03-30 10:54:40 -07:00
voxel : : Region dirtyRegion = voxel : : Region : : InvalidRegion ;
script . exec ( luaScript , sceneGraph , node . id ( ) , node . region ( ) , voxel , dirtyRegion , args ) ;
2021-12-21 10:47:48 -08:00
}
}
}
script . shutdown ( ) ;
}
2022-03-16 13:17:52 -07:00
void VoxConvert : : scale ( voxelformat : : SceneGraph & sceneGraph ) {
2021-12-21 10:47:48 -08:00
Log : : info ( " Scale layers " ) ;
2022-03-16 13:17:52 -07:00
for ( voxelformat : : SceneGraphNode & node : sceneGraph ) {
2022-01-06 04:33:48 -08:00
const voxel : : Region srcRegion = node . region ( ) ;
2021-12-21 10:47:48 -08:00
const glm : : ivec3 & targetDimensionsHalf = ( srcRegion . getDimensionsInVoxels ( ) / 2 ) - 1 ;
const voxel : : Region destRegion ( srcRegion . getLowerCorner ( ) , srcRegion . getLowerCorner ( ) + targetDimensionsHalf ) ;
if ( destRegion . isValid ( ) ) {
voxel : : RawVolume * destVolume = new voxel : : RawVolume ( destRegion ) ;
2022-03-16 13:05:53 -07:00
voxelutil : : rescaleVolume ( * node . volume ( ) , * destVolume ) ;
2022-01-06 04:33:48 -08:00
node . setVolume ( destVolume , true ) ;
2021-12-21 10:47:48 -08:00
}
}
}
2022-03-16 13:17:52 -07:00
void VoxConvert : : resize ( const glm : : ivec3 & size , voxelformat : : SceneGraph & sceneGraph ) {
2022-01-21 13:19:19 -08:00
Log : : info ( " Resize layers " ) ;
2022-03-16 13:17:52 -07:00
for ( voxelformat : : SceneGraphNode & node : sceneGraph ) {
2022-03-16 13:05:53 -07:00
node . setVolume ( voxelutil : : resize ( node . volume ( ) , size ) , true ) ;
2022-01-21 13:19:19 -08:00
}
}
2022-03-16 13:17:52 -07:00
void VoxConvert : : filterVolumes ( voxelformat : : SceneGraph & sceneGraph ) {
2021-12-17 07:49:49 -08:00
const core : : String & filter = getArgVal ( " --filter " ) ;
2021-12-17 06:46:55 -08:00
if ( filter . empty ( ) ) {
Log : : warn ( " No filter specified " ) ;
return ;
}
core : : Set < int > layers ;
core : : DynamicArray < core : : String > tokens ;
core : : string : : splitString ( filter , tokens , " , " ) ;
for ( const core : : String & token : tokens ) {
if ( token . contains ( " - " ) ) {
const int start = token . toInt ( ) ;
const size_t index = token . find ( " - " ) ;
const core : : String & endString = token . substr ( index + 1 ) ;
const int end = endString . toInt ( ) ;
for ( int layer = start ; layer < = end ; + + layer ) {
layers . insert ( layer ) ;
}
} else {
const int layer = token . toInt ( ) ;
layers . insert ( layer ) ;
}
}
2022-01-06 04:33:48 -08:00
for ( int i = 0 ; i < ( int ) sceneGraph . size ( ) ; + + i ) {
2021-12-17 06:46:55 -08:00
if ( ! layers . has ( i ) ) {
2022-01-11 13:54:04 -08:00
sceneGraph [ i ] - > release ( ) ;
2021-12-26 07:17:32 -08:00
Log : : debug ( " Remove layer %i - not part of the filter expression " , i ) ;
2021-12-17 06:46:55 -08:00
}
}
2021-12-17 07:49:49 -08:00
Log : : info ( " Filtered layers: %i " , ( int ) layers . size ( ) ) ;
2021-12-17 06:46:55 -08:00
}
2022-03-16 13:17:52 -07:00
void VoxConvert : : mirror ( const core : : String & axisStr , voxelformat : : SceneGraph & sceneGraph ) {
2022-01-05 08:52:30 -08:00
const math : : Axis axis = math : : toAxis ( axisStr ) ;
2021-12-17 14:35:22 -08:00
if ( axis = = math : : Axis : : None ) {
return ;
}
2021-12-18 05:14:10 -08:00
Log : : info ( " Mirror on axis %c " , axisStr [ 0 ] ) ;
2022-03-16 13:17:52 -07:00
for ( voxelformat : : SceneGraphNode & node : sceneGraph ) {
2022-03-16 13:05:53 -07:00
node . setVolume ( voxelutil : : mirrorAxis ( node . volume ( ) , axis ) , true ) ;
2021-12-17 14:35:22 -08:00
}
}
2022-03-16 13:17:52 -07:00
void VoxConvert : : rotate ( const core : : String & axisStr , voxelformat : : SceneGraph & sceneGraph ) {
2022-01-05 08:52:30 -08:00
const math : : Axis axis = math : : toAxis ( axisStr ) ;
2021-12-17 14:35:22 -08:00
if ( axis = = math : : Axis : : None ) {
return ;
}
2021-12-18 05:14:10 -08:00
Log : : info ( " Rotate on axis %c " , axisStr [ 0 ] ) ;
2022-03-16 13:17:52 -07:00
for ( voxelformat : : SceneGraphNode & node : sceneGraph ) {
2022-03-16 13:05:53 -07:00
node . setVolume ( voxelutil : : rotateAxis ( node . volume ( ) , axis ) , true ) ;
2021-12-17 14:31:50 -08:00
}
}
2022-03-16 13:17:52 -07:00
void VoxConvert : : translate ( const glm : : ivec3 & pos , voxelformat : : SceneGraph & sceneGraph ) {
2021-12-21 08:50:19 -08:00
Log : : info ( " Translate by %i:%i:%i " , pos . x , pos . y , pos . z ) ;
2022-03-16 13:17:52 -07:00
for ( voxelformat : : SceneGraphNode & node : sceneGraph ) {
2022-01-06 04:33:48 -08:00
node . translate ( pos ) ;
2021-12-21 08:50:19 -08:00
}
}
2020-05-17 08:42:43 -07:00
int main ( int argc , char * argv [ ] ) {
const core : : EventBusPtr & eventBus = std : : make_shared < core : : EventBus > ( ) ;
const io : : FilesystemPtr & filesystem = std : : make_shared < io : : Filesystem > ( ) ;
const core : : TimeProviderPtr & timeProvider = std : : make_shared < core : : TimeProvider > ( ) ;
const metric : : MetricPtr & metric = std : : make_shared < metric : : Metric > ( ) ;
VoxConvert app ( metric , filesystem , eventBus , timeProvider ) ;
return app . startMainLoop ( argc , argv ) ;
}