vengi/src/tools/voxconvert/VoxConvert.cpp

228 lines
9.1 KiB
C++
Raw Normal View History

/**
* @file
*/
#include "VoxConvert.h"
#include "core/Color.h"
#include "core/GameConfig.h"
#include "core/StringUtil.h"
#include "core/Var.h"
#include "command/Command.h"
#include "image/Image.h"
#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"
#include "core/EventBus.h"
#include "core/TimeProvider.h"
#include "voxel/MaterialColor.h"
2020-08-04 09:02:29 -07:00
#include "voxelformat/VolumeFormat.h"
#include "voxelformat/Format.h"
#include "voxelutil/ImageUtils.h"
2020-05-22 11:53:28 -07:00
#include "voxelutil/VolumeRescaler.h"
VoxConvert::VoxConvert(const metric::MetricPtr& metric, const io::FilesystemPtr& filesystem, const core::EventBusPtr& eventBus, const core::TimeProviderPtr& timeProvider) :
Super(metric, filesystem, eventBus, timeProvider) {
init(ORGANISATION, "voxconvert");
_initialLogLevel = SDL_LOG_PRIORITY_ERROR;
}
2020-08-30 13:46:21 -07:00
app::AppState VoxConvert::onConstruct() {
const app::AppState state = Super::onConstruct();
registerArg("--merge").setShort("-m").setDescription("Merge layers into one volume");
registerArg("--src-palette").setShort("-p").setDescription("Keep the source palette and don't perform quantization");
2020-05-22 11:53:28 -07:00
registerArg("--scale").setShort("-s").setDescription("Scale layer to 50% of its original size");
registerArg("--force").setShort("-f").setDescription("Overwrite existing files");
registerArg("--export-palette").setDescription("Export the used palette data into an image. Use in combination with --src-palette");
_mergeQuads = core::Var::get(cfg::VoxformatMergequads, "true", core::CV_NOPERSIST);
2020-10-01 09:16:21 -07:00
_mergeQuads->setHelp("Merge similar quads to optimize the mesh");
_reuseVertices = core::Var::get(cfg::VoxformatReusevertices, "true", core::CV_NOPERSIST);
2020-10-01 09:16:21 -07:00
_reuseVertices->setHelp("Reuse vertices or always create new ones");
_ambientOcclusion = core::Var::get(cfg::VoxformatAmbientocclusion, "false", core::CV_NOPERSIST);
2020-10-01 09:16:21 -07:00
_ambientOcclusion->setHelp("Extra vertices for ambient occlusion");
_scale = core::Var::get(cfg::VoxformatScale, "1.0", core::CV_NOPERSIST);
2020-10-01 09:16:21 -07:00
_scale->setHelp("Scale the vertices by the given factor");
_quads = core::Var::get(cfg::VoxformatQuads, "true", core::CV_NOPERSIST);
2020-10-01 09:16:21 -07:00
_quads->setHelp("Export as quads. If this false, triangles will be used.");
_withColor = core::Var::get(cfg::VoxformatWithcolor, "true", core::CV_NOPERSIST);
2020-10-01 09:16:21 -07:00
_withColor->setHelp("Export with vertex colors");
_withTexCoords = core::Var::get(cfg::VoxformatWithtexcoords, "true", core::CV_NOPERSIST);
2020-10-01 09:16:21 -07:00
_withTexCoords->setHelp("Export with uv coordinates of the palette image");
_palette = core::Var::get("palette", voxel::getDefaultPaletteName());
2020-10-01 09:16:21 -07:00
_palette->setHelp("This is the NAME part of palette-<NAME>.png or absolute png file to use (1x256)");
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);
}
}
2020-08-30 13:46:21 -07:00
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();
2020-08-30 13:46:21 -07:00
return app::AppState::InitFailure;
}
const core::String infile = _argv[_argc - 2];
const core::String outfile = _argv[_argc - 1];
const bool mergeVolumes = hasArg("--merge") || hasArg("-m");
const bool scaleVolumes = hasArg("--scale") || hasArg("-s");
const bool srcPalette = hasArg("--src-palette") || hasArg("-p");
const bool exportPalette = hasArg("--export-palette");
Log::info("Options");
if (voxelformat::isMeshFormat(outfile)) {
Log::info("* palette: - %s", _palette->strVal().c_str());
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("* quads: - %s", _quads->strVal().c_str());
Log::info("* withColor: - %s", _withColor->strVal().c_str());
Log::info("* withTexCoords: - %s", _withTexCoords->strVal().c_str());
}
Log::info("* infile: - %s", infile.c_str());
Log::info("* outfile: - %s", outfile.c_str());
Log::info("* merge volumes: - %s", (mergeVolumes ? "true" : "false"));
Log::info("* scale volumes: - %s", (scaleVolumes ? "true" : "false"));
Log::info("* use source file palette: - %s", (srcPalette ? "true" : "false"));
Log::info("* export used palette as image: - %s", (exportPalette ? "true" : "false"));
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;
2020-08-30 13:46:21 -07:00
return app::AppState::InitFailure;
}
const bool inputIsImage = inputFile->isAnyOf(io::format::images());
Log::info("* generate from heightmap: - %s", (inputIsImage ? "true" : "false"));
io::FilePtr paletteFile = filesystem()->open(core::string::format("palette-%s.png", _palette->strVal().c_str()));
if (!paletteFile->exists()) {
paletteFile = filesystem()->open(_palette->strVal());
}
if (!voxel::initMaterialColors(paletteFile, io::FilePtr())) {
Log::error("Failed to init default material colors");
return app::AppState::InitFailure;
}
if (!inputIsImage && srcPalette) {
core::Array<uint32_t, 256> palette;
io::FileStream palStream(inputFile.get());
2021-12-03 23:24:49 -08:00
const size_t numColors = voxelformat::loadVolumePalette(inputFile->name(), palStream, palette);
if (numColors == 0) {
Log::error("Failed to load palette from input file");
return app::AppState::InitFailure;
}
if (!voxel::initMaterialColors((const uint8_t*)palette.begin(), numColors, "")) {
Log::error("Failed to initialize material colors from input file");
return app::AppState::InitFailure;
}
if (exportPalette) {
const core::String &paletteFile = core::string::stripExtension(infile) + ".png";
image::Image img(paletteFile);
img.loadRGBA((const uint8_t*)palette.begin(), (int)numColors * 4, (int)numColors, 1);
if (!img.writePng()) {
Log::warn("Failed to write the palette file");
}
}
}
const io::FilePtr outputFile = filesystem()->open(outfile, io::FileMode::SysWrite);
if (!outputFile->validHandle()) {
Log::error("Could not open target file: %s", outfile.c_str());
2020-08-30 13:46:21 -07:00
return app::AppState::InitFailure;
}
if (outputFile->length() > 0) {
if (!hasArg("--force") && !hasArg("-f")) {
Log::error("Given output file '%s' already exists", outfile.c_str());
2020-08-30 13:46:21 -07:00
return app::AppState::InitFailure;
}
}
voxel::VoxelVolumes volumes;
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 app::AppState::InitFailure;
}
voxel::Region region(0, 0, 0, image->width(), 255, image->height());
voxel::RawVolume* volume = new voxel::RawVolume(region);
volumes.push_back(voxel::VoxelVolume(volume, infile, true, glm::ivec3(0)));
voxel::RawVolumeWrapper wrapper(volume);
voxelutil::importHeightmap(wrapper, image);
} else {
io::FileStream inputFileStream(inputFile.get());
if (!voxelformat::loadVolumeFormat(inputFile->name(), inputFileStream, volumes)) {
Log::error("Failed to load given input file");
return app::AppState::InitFailure;
}
}
if (mergeVolumes) {
2020-10-01 14:43:21 -07:00
Log::info("Merge layers");
voxel::RawVolume* merged = volumes.merge();
if (merged == nullptr) {
Log::error("Failed to merge volumes");
2020-08-30 13:46:21 -07:00
return app::AppState::InitFailure;
}
voxelformat::clearVolumes(volumes);
volumes.push_back(voxel::VoxelVolume(merged));
}
if (scaleVolumes) {
2020-10-01 14:43:21 -07:00
Log::info("Scale layers");
2020-05-22 11:53:28 -07:00
for (auto& v : volumes) {
const voxel::Region srcRegion = v.volume->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);
rescaleVolume(*v.volume, *destVolume);
delete v.volume;
v.volume = destVolume;
}
2020-05-22 11:53:28 -07:00
}
}
Log::debug("Save");
if (!voxelformat::saveFormat(outputFile, volumes)) {
voxelformat::clearVolumes(volumes);
Log::error("Failed to write to output file '%s'", outfile.c_str());
2020-08-30 13:46:21 -07:00
return app::AppState::InitFailure;
}
Log::info("Wrote output file %s", outputFile->name().c_str());
voxelformat::clearVolumes(volumes);
return state;
}
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);
}