/** * @file */ #include "VoxConvert.h" #include "core/Color.h" #include "core/Enum.h" #include "core/GameConfig.h" #include "core/StringUtil.h" #include "core/Tokenizer.h" #include "core/Var.h" #include "command/Command.h" #include "core/collection/DynamicArray.h" #include "core/collection/Set.h" #include "core/concurrent/Concurrency.h" #include "image/Image.h" #include "io/FileStream.h" #include "io/Filesystem.h" #include "metric/Metric.h" #include "core/EventBus.h" #include "core/TimeProvider.h" #include "voxel/RawVolume.h" #include "voxel/Region.h" #include "voxelformat/SceneGraphNode.h" #include "voxelformat/SceneGraphUtil.h" #include "voxelformat/VolumeFormat.h" #include "voxelformat/Format.h" #include "voxelformat/SceneGraph.h" #include "voxelgenerator/LUAGenerator.h" #include "voxelutil/ImageUtils.h" #include "voxelutil/VolumeCropper.h" #include "voxelutil/VolumeRescaler.h" #include "voxelutil/VolumeResizer.h" #include "voxelutil/VolumeRotator.h" #include "voxelutil/VolumeSplitter.h" #include "voxelutil/VolumeVisitor.h" #include #include #define MaxHeightmapWidth 1024 #define MaxHeightmapHeight 1024 VoxConvert::VoxConvert(const metric::MetricPtr& metric, const io::FilesystemPtr& filesystem, const core::EventBusPtr& eventBus, const core::TimeProviderPtr& timeProvider) : Super(metric, filesystem, eventBus, timeProvider, core::cpus()) { init(ORGANISATION, "voxconvert"); } app::AppState VoxConvert::onConstruct() { const app::AppState state = Super::onConstruct(); registerArg("--crop").setDescription("Reduce the volumes to their real voxel sizes"); registerArg("--dump").setDescription("Dump the scene graph of the input file"); registerArg("--export-layers").setDescription("Export all the layers of a scene into single files"); registerArg("--export-palette").setDescription("Export the used palette data into an image"); registerArg("--filter").setDescription("Layer filter. For example '1-4,6'"); registerArg("--force").setShort("-f").setDescription("Overwrite existing files"); registerArg("--image-as-plane").setDescription("Import given input images as planes"); 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"); registerArg("--image-as-volume-both-sides").setDefaultValue("false").setDescription("Importing image as volume for both sides"); registerArg("--image-as-heightmap").setDescription("Import given input images as heightmaps"); registerArg("--input").setShort("-i").setDescription("Allow to specify input files"); registerArg("--merge").setShort("-m").setDescription("Merge layers into one volume"); registerArg("--mirror").setDescription("Mirror by the given axis (x, y or z)"); registerArg("--output").setShort("-o").setDescription("Allow to specify the output file"); registerArg("--pivot").setDescription("Change the pivots of the volume layers"); registerArg("--rotate").setDescription("Rotate by 90 degree at the given axis (x, y or z)"); registerArg("--resize").setDescription("Resize the volume by the given x (right), y (up) and z (back) values"); registerArg("--scale").setShort("-s").setDescription("Scale layer to 50% of its original size"); registerArg("--script").setDefaultValue("script.lua").setDescription("Apply the given lua script to the output volume"); registerArg("--split").setDescription("Slices the volumes into pieces of the given size "); registerArg("--translate").setShort("-t").setDescription("Translate the volumes by x (right), y (up), z (back)"); _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"); _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"); _frame = core::Var::get(cfg::VoxformatFrame, "0", core::CV_NOPERSIST, "Which frame to import for formats that support this - starting at 0"); _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"); core::Var::get(cfg::VoxformatTransform, "false", core::CV_NOPERSIST, "Apply the scene graph transform to mesh exports"); _palette = core::Var::get(cfg::VoxelPalette, voxel::Palette::getDefaultPaletteName(), "This is the NAME part of palette-.png or absolute png file to use (1x256)"); if (!filesystem()->registerPath("scripts/")) { Log::warn("Failed to register lua generator script path"); } return state; } 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); } 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"); } app::AppState VoxConvert::onInit() { const app::AppState state = Super::onInit(); if (state != app::AppState::Running) { return state; } if (_argc < 2) { _logLevelVar->setVal(SDL_LOG_PRIORITY_INFO); Log::init(); usage(); return app::AppState::InitFailure; } core::String infilesstr; core::DynamicArray 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 { Log::error("No input file was specified"); return app::AppState::InitFailure; } core::String outfile; if (hasArg("--output")) { outfile = getArgVal("--output"); } _mergeVolumes = hasArg("--merge"); _scaleVolumes = hasArg("--scale"); _mirrorVolumes = hasArg("--mirror"); _rotateVolumes = hasArg("--rotate"); _translateVolumes = hasArg("--translate"); _exportPalette = hasArg("--export-palette"); _exportLayers = hasArg("--export-layers"); _changePivot = hasArg("--pivot"); _cropVolumes = hasArg("--crop"); _splitVolumes = hasArg("--split"); _dumpSceneGraph = hasArg("--dump"); _resizeVolumes = hasArg("--resize"); Log::info("Options"); if (voxelformat::isMeshFormat(outfile)) { 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()); if (!outfile.empty()) { Log::info("* output files: - %s", outfile.c_str()); } if (io::isImage(outfile) && infiles.size() == 1) { voxel::Palette palette; if (!voxelformat::importPalette(infiles[0], palette)) { 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; } core::String scriptParameters; if (hasArg("--script")) { scriptParameters = getArgVal("--script"); 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())) { voxel::initPalette(palette); } 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; } } 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; } } else if (!_exportLayers && !_exportPalette && !_dumpSceneGraph) { Log::error("No output specified"); return app::AppState::InitFailure; } voxelformat::SceneGraph sceneGraph; for (const core::String& infile : infiles) { if (filesystem()->isReadableDir(infile)) { core::DynamicArray 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) { if (entry.type != io::Filesystem::DirEntry::Type::file) { continue; } const core::String fullpath = core::string::path(infile, entry.name); if (!handleInputFile(fullpath, sceneGraph, infiles.size() > 1)) { ++success; } } if (success == 0) { Log::error("Could not find a valid input file in directory %s", infile.c_str()); return app::AppState::InitFailure; } } else { if (!handleInputFile(infile, sceneGraph, infiles.size() > 1)) { return app::AppState::InitFailure; } } } const bool applyFilter = hasArg("--filter"); if (applyFilter) { if (infiles.size() == 1u) { filterVolumes(sceneGraph); } else { Log::warn("Don't apply layer filters for multiple input files"); } } if (_exportLayers) { 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]); } if (_mergeVolumes) { Log::info("Merge layers"); const voxelformat::SceneGraph::MergedVolumePalette &merged = sceneGraph.merge(); if (merged.first == nullptr) { Log::error("Failed to merge volumes"); return app::AppState::InitFailure; } sceneGraph.clear(); voxelformat::SceneGraphNode node; node.setPalette(merged.second); node.setVolume(merged.first, true); node.setName(infilesstr); sceneGraph.emplace(core::move(node)); } if (_scaleVolumes) { scale(sceneGraph); } if (_resizeVolumes) { resize(getArgIvec3("--resize"), sceneGraph); } if (_mirrorVolumes) { mirror(getArgVal("--mirror"), sceneGraph); } if (_rotateVolumes) { rotate(getArgVal("--rotate"), sceneGraph); } if (_translateVolumes) { translate(getArgIvec3("--translate"), sceneGraph); } if (!scriptParameters.empty()) { script(scriptParameters, sceneGraph); } if (_changePivot) { pivot(getArgIvec3("--pivot"), sceneGraph); } if (_cropVolumes) { crop(sceneGraph); } if (_splitVolumes) { split(getArgIvec3("--split"), sceneGraph); } 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()); } return state; } 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()); } return core::string::path(core::string::extractPath(inputfile), core::string::sanitizeFilename(name)); } bool VoxConvert::handleInputFile(const core::String &infile, voxelformat::SceneGraph &sceneGraph, bool multipleInputs) { Log::info("-- current input file: %s", infile.c_str()); 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; } const bool importAsPlane = hasArg("--image-as-plane"); const bool importAsVolume = hasArg("--image-as-volume"); const bool importAsHeightmap = hasArg("--image-as-heightmap"); if (importAsHeightmap || (!importAsPlane && !importAsVolume)) { 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); voxelformat::SceneGraphNode node; node.setVolume(volume, true); node.setName(infile); sceneGraph.emplace(core::move(node)); } if (importAsVolume) { voxelformat::SceneGraphNode node; const int maxDepth = glm::clamp(core::string::toInt(getArgVal("--image-as-volume-max-depth")), 1, 255); const bool bothSides = core::string::toBool(getArgVal("--image-as-volume-both-sides")); node.setVolume(voxelutil::importAsVolume(image, maxDepth, bothSides), true); node.setName(infile); sceneGraph.emplace(core::move(node)); } if (importAsPlane) { voxelformat::SceneGraphNode node; node.setVolume(voxelutil::importAsPlane(image), true); node.setName(infile); sceneGraph.emplace(core::move(node)); } } else { io::FileStream inputFileStream(inputFile); voxelformat::SceneGraph newSceneGraph; if (!voxelformat::loadFormat(inputFile->name(), inputFileStream, newSceneGraph)) { return false; } int parent = sceneGraph.root().id(); if (multipleInputs) { voxelformat::SceneGraphNode groupNode(voxelformat::SceneGraphNodeType::Group); groupNode.setName(core::string::extractFilename(infile)); parent = sceneGraph.emplace(core::move(groupNode), parent); } voxelformat::addSceneGraphNodes(sceneGraph, newSceneGraph, parent); if (_dumpSceneGraph) { dump(sceneGraph); } } if (_exportPalette) { const core::String &paletteFile = core::string::stripExtension(infile) + ".png"; sceneGraph.firstPalette().save(paletteFile.c_str()); } return true; } void VoxConvert::exportLayersIntoSingleObjects(voxelformat::SceneGraph& sceneGraph, const core::String &inputfile) { Log::info("Export layers into single objects"); int n = 0; for (voxelformat::SceneGraphNode& node : sceneGraph) { voxelformat::SceneGraph newSceneGraph; voxelformat::SceneGraphNode newNode; voxelformat::copyNode(node, newNode, false); newSceneGraph.emplace(core::move(newNode)); const core::String& filename = getFilenameForLayerName(inputfile, node.name(), n); if (voxelformat::saveFormat(filesystem()->open(filename, io::FileMode::SysWrite), newSceneGraph)) { Log::info(" .. %s", filename.c_str()); } else { Log::error(" .. %s", filename.c_str()); } } } 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; } void VoxConvert::split(const glm::ivec3 &size, voxelformat::SceneGraph& sceneGraph) { Log::info("split volumes at %i:%i:%i", size.x, size.y, size.z); const voxelformat::SceneGraph::MergedVolumePalette &merged = sceneGraph.merge(); sceneGraph.clear(); core::DynamicArray rawVolumes; voxelutil::splitVolume(merged.first, size, rawVolumes); delete merged.first; for (voxel::RawVolume *v : rawVolumes) { voxelformat::SceneGraphNode node; node.setVolume(v, true); node.setPalette(merged.second); sceneGraph.emplace(core::move(node)); } } int VoxConvert::dumpNode_r(const voxelformat::SceneGraph& sceneGraph, int nodeId, int indent) { const voxelformat::SceneGraphNode& node = sceneGraph.node(nodeId); static const char* NodeTypeStr[] { "Root", "Model", "Group", "Camera", "Unknown" }; static_assert(core::enumVal(voxelformat::SceneGraphNodeType::Max) == lengthof(NodeTypeStr), "Array sizes don't match Max"); const voxelformat::SceneGraphNodeType type = node.type(); 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)]); int voxels = 0; if (type == voxelformat::SceneGraphNodeType::Model) { voxel::RawVolume *v = node.volume(); Log::info("%*s |- volume: %s", indent, " ", v != nullptr ? v->region().toString().c_str() : "no volume"); if (v) { voxelutil::visitVolume(*v, [&](int, int, int, const voxel::Voxel &) { ++voxels; }); } Log::info("%*s |- voxels: %i", indent, " ", voxels); } for (const auto & entry : node.properties()) { Log::info("%*s |- %s: %s", indent, " ", entry->key.c_str(), entry->value.c_str()); } 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"); Log::info("%*s |- interpolation: %s", indent, " ", voxelformat::InterpolationTypeStr[core::enumVal(kf.interpolation)]); 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); } Log::info("%*s |- children: %i", indent, " ", (int)node.children().size()); for (int children : node.children()) { voxels += dumpNode_r(sceneGraph, children, indent + 2); } return voxels; } void VoxConvert::dump(const voxelformat::SceneGraph& sceneGraph) { int voxels = dumpNode_r(sceneGraph, sceneGraph.root().id(), 0); Log::info("Voxel count: %i", voxels); } void VoxConvert::crop(voxelformat::SceneGraph& sceneGraph) { Log::info("Crop volumes"); for (voxelformat::SceneGraphNode& node : sceneGraph) { node.setVolume(voxelutil::cropVolume(node.volume()), true); } } void VoxConvert::pivot(const glm::ivec3& pivot, voxelformat::SceneGraph& sceneGraph) { Log::info("Set pivot to %i:%i:%i", pivot.x, pivot.y, pivot.z); for (voxelformat::SceneGraphNode& node : sceneGraph) { node.setPivot(_frame->intVal(), pivot, node.region().getDimensionsInVoxels()); } } void VoxConvert::script(const core::String &scriptParameters, voxelformat::SceneGraph& sceneGraph) { voxelgenerator::LUAGenerator script; if (!script.init()) { Log::warn("Failed to initialize the script bindings"); } else { core::DynamicArray 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 argsInfo; if (!script.argumentInfo(luaScript, argsInfo)) { Log::warn("Failed to get argument details"); } core::DynamicArray 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()); for (voxelformat::SceneGraphNode& node : sceneGraph) { voxel::Region dirtyRegion = voxel::Region::InvalidRegion; script.exec(luaScript, sceneGraph, node.id(), node.region(), voxel, dirtyRegion, args); } } } script.shutdown(); } void VoxConvert::scale(voxelformat::SceneGraph& sceneGraph) { Log::info("Scale layers"); for (voxelformat::SceneGraphNode& node : sceneGraph) { const voxel::Region srcRegion = node.region(); 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); voxelutil::rescaleVolume(*node.volume(), *destVolume); node.setVolume(destVolume, true); } } } void VoxConvert::resize(const glm::ivec3 &size, voxelformat::SceneGraph& sceneGraph) { Log::info("Resize layers"); for (voxelformat::SceneGraphNode& node : sceneGraph) { node.setVolume(voxelutil::resize(node.volume(), size), true); } } void VoxConvert::filterVolumes(voxelformat::SceneGraph& sceneGraph) { const core::String &filter = getArgVal("--filter"); if (filter.empty()) { Log::warn("No filter specified"); return; } core::Set layers; core::DynamicArray 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); } } for (int i = 0; i < (int)sceneGraph.size(); ++i) { if (!layers.has(i)) { sceneGraph[i]->release(); Log::debug("Remove layer %i - not part of the filter expression", i); } } Log::info("Filtered layers: %i", (int)layers.size()); } void VoxConvert::mirror(const core::String& axisStr, voxelformat::SceneGraph& sceneGraph) { const math::Axis axis = math::toAxis(axisStr); if (axis == math::Axis::None) { return; } Log::info("Mirror on axis %c", axisStr[0]); for (voxelformat::SceneGraphNode &node : sceneGraph) { node.setVolume(voxelutil::mirrorAxis(node.volume(), axis), true); } } void VoxConvert::rotate(const core::String& axisStr, voxelformat::SceneGraph& sceneGraph) { const math::Axis axis = math::toAxis(axisStr); if (axis == math::Axis::None) { return; } Log::info("Rotate on axis %c", axisStr[0]); for (voxelformat::SceneGraphNode &node : sceneGraph) { node.setVolume(voxelutil::rotateAxis(node.volume(), axis), true); } } void VoxConvert::translate(const glm::ivec3& pos, voxelformat::SceneGraph& sceneGraph) { Log::info("Translate by %i:%i:%i", pos.x, pos.y, pos.z); for (voxelformat::SceneGraphNode &node : sceneGraph) { node.translate(pos); } } int main(int argc, char *argv[]) { const core::EventBusPtr& eventBus = std::make_shared(); const io::FilesystemPtr& filesystem = std::make_shared(); const core::TimeProviderPtr& timeProvider = std::make_shared(); const metric::MetricPtr& metric = std::make_shared(); VoxConvert app(metric, filesystem, eventBus, timeProvider); return app.startMainLoop(argc, argv); }