VOXCONVERT: use the new volume import feature

and also allow to import both sides
master
Martin Gerhardy 2022-03-31 22:57:46 +02:00
parent 53c02d08dd
commit c18e4e89c7
6 changed files with 86 additions and 18 deletions

View File

@ -49,6 +49,23 @@ Import input images as heightmaps (default).
\fB\--image-as-plane\fR
Import input images as planes.
.TP
\fB\--image-as-volume\fR
Import given input image as volume. Uses a depth map to make a volume out of the
image. The depth map R channel is using values from 0 (black) to white (255)
resulting in voxel heights from 1 to max-height (see --image-as-volume-max-depth).
The \fB\--input\fR with e.g. \fBsomeimage.png\fR will pick the depth map next to
the image path called \fBsomeimage-dm.png\fR as depth map.
.TP
\fB\--image-as-volume-max-depth\fR
Default is 8 - see --image-as-volume.
.TP
\fB\--image-as-volume-both-sides\fR
Importing image as volume and use the depth map for both sides.
.TP
\fB\--input|-i <file>\fR
Allow to specify input files.

View File

@ -8,6 +8,16 @@ Generate a lod scaled by 50% from the input model.
`./vengi-voxconvert -s --input infile.vox --output output.vox`
## Import 2d image as volume
Imports a 2d image and applies depth to it.
`./vengi-voxconvert --image-as-volume --image-as-volume-max-depth 8 --image-as-volume-both-sides true --input infile.png --output output.vox`
Import given input image as volume. Uses a depth map to make a volume out of the image. The depth map R channel is using values from 0 (black) to white (255) resulting in voxel heights from 1 to max-height (see `--image-as-volume-max-depth`).
The `--input` with e.g. `infile.png` will pick the depth map next to the image path called `infile-dm.png` as depth map.
## Merge several models
Merge several models into one:

View File

@ -8,6 +8,9 @@
* `--filter <filter>`: will filter out layers not mentioned in the expression. E.g. `1-2,4` will handle layer 1, 2 and 4. It is the same as `1,2,4`. The first layer is `0`. See the layers note below.
* `--force`: overwrite existing files
* `--image-as-heightmap`: import input images as heightmap (default)
* `--image-as-volume`: import given input image as volume. Uses a depth map to make a volume out of the image.
* `--image-as-volume-max-depth`: importing image as volume max depth
* `--image-as-volume-both-sides`: importing image as volume and use the depth map for both sides
* `--image-as-plane`: import input images as planes
* `--input <file>`: allows to specify input files. You can specify more than one file
* `--merge`: will merge a multi layer volume (like `vox`, `qb` or `qbt`) into a single volume of the target file

View File

@ -57,9 +57,15 @@ void importHeightmap(voxel::RawVolumeWrapper& volume, const image::ImagePtr& ima
voxel::RawVolume* importAsPlane(const image::ImagePtr& image, uint8_t thickness) {
if (thickness <= 0) {
Log::error("Thickness can't be 0");
return nullptr;
}
if (!image || !image->isLoaded()) {
Log::error("No color image given");
return nullptr;
}
if (image->depth() != 4) {
Log::error("Expected to get an rgba image");
return nullptr;
}
const int imageWidth = image->width();
@ -71,8 +77,6 @@ voxel::RawVolume* importAsPlane(const image::ImagePtr& image, uint8_t thickness)
Log::info("Import image as plane: w(%i), h(%i), d(%i)", imageWidth, imageHeight, thickness);
const voxel::Region region(0, 0, 0, imageWidth - 1, imageHeight - 1, thickness - 1);
const voxel::Palette &palette = voxel::getPalette();
core::DynamicArray<glm::vec4> materialColors;
palette.toVec4f(materialColors);
voxel::RawVolume* volume = new voxel::RawVolume(region);
for (int x = 0; x < imageWidth; ++x) {
for (int y = 0; y < imageHeight; ++y) {
@ -81,7 +85,7 @@ voxel::RawVolume* importAsPlane(const image::ImagePtr& image, uint8_t thickness)
if (data[3] == 0) {
continue;
}
const uint8_t index = core::Color::getClosestMatch(color, materialColors);
const uint8_t index = palette.getClosestMatch(color);
const voxel::Voxel voxel = voxel::createVoxel(voxel::VoxelType::Generic, index);
for (int z = 0; z < thickness; ++z) {
volume->setVoxel(x, (imageHeight - 1) - y, z, voxel);
@ -91,8 +95,8 @@ voxel::RawVolume* importAsPlane(const image::ImagePtr& image, uint8_t thickness)
return volume;
}
voxel::RawVolume* importAsVolume(const image::ImagePtr& image, const image::ImagePtr& heightmap, uint8_t maxHeight) {
if (maxHeight <= 0) {
voxel::RawVolume* importAsVolume(const image::ImagePtr& image, const image::ImagePtr& heightmap, uint8_t maxDepth, bool bothSides) {
if (maxDepth <= 0) {
Log::error("Max height can't be 0");
return nullptr;
}
@ -110,31 +114,44 @@ voxel::RawVolume* importAsVolume(const image::ImagePtr& image, const image::Imag
}
const int imageWidth = image->width();
const int imageHeight = image->height();
if (imageWidth * imageHeight * maxHeight > 1024 * 1024 * 4) {
Log::warn("Did not import plane - max volume size of 1024x1024 (thickness 4) exceeded (%i:%i:%i)", imageWidth, imageHeight, maxHeight);
int volumeDepth = bothSides ? maxDepth * 2 : maxDepth;
if (volumeDepth % 2 == 0) {
Log::warn("Make max volume depth uneven");
volumeDepth++;
}
if (imageWidth * imageHeight * volumeDepth > 1024 * 1024 * 4) {
Log::warn("Did not import plane - max volume size of 1024x1024 (depth 4) exceeded (%i:%i:%i)", imageWidth, imageHeight, volumeDepth);
return nullptr;
}
Log::info("Import image as volume: w(%i), h(%i), d(%i)", imageWidth, imageHeight, maxHeight);
const voxel::Region region(0, 0, 0, imageWidth - 1, imageHeight - 1, maxHeight - 1);
Log::info("Import image as volume: w(%i), h(%i), d(%i)", imageWidth, imageHeight, volumeDepth);
const voxel::Region region(0, 0, 0, imageWidth - 1, imageHeight - 1, volumeDepth - 1);
const voxel::Palette &palette = voxel::getPalette();
core::DynamicArray<glm::vec4> materialColors;
palette.toVec4f(materialColors);
voxel::RawVolume* volume = new voxel::RawVolume(region);
for (int x = 0; x < imageWidth; ++x) {
for (int y = 0; y < imageHeight; ++y) {
const uint8_t* data = image->at(x, y);
const glm::vec4& color = core::Color::fromRGBA(data[0], data[1], data[2], data[3]);
if (data[3] == 0) {
continue;
}
const uint8_t index = core::Color::getClosestMatch(color, materialColors);
const glm::vec4& color = core::Color::fromRGBA(data[0], data[1], data[2], data[3]);
const uint8_t index = palette.getClosestMatch(color);
const voxel::Voxel voxel = voxel::createVoxel(voxel::VoxelType::Generic, index);
const uint8_t* heightdata = heightmap->at(x, y);
const float thickness = (float)*heightdata;
const float maxthickness = maxHeight;
const float maxthickness = maxDepth;
const float height = thickness * maxthickness / 255.0f;
for (int z = 0; z < (int)glm::ceil(height); ++z) {
volume->setVoxel(x, (imageHeight - 1) - y, z, voxel);
if (bothSides) {
const int heighti = (int)glm::ceil(height/ 2.0f);
const int minZ = maxDepth - heighti;
const int maxZ = maxDepth + heighti;
for (int z = minZ; z <= maxZ; ++z) {
volume->setVoxel(x, (imageHeight - 1) - y, z, voxel);
}
} else {
const int heighti = (int)glm::ceil(height);
for (int z = 0; z < heighti; ++z) {
volume->setVoxel(x, (imageHeight - 1) - y, z, voxel);
}
}
}
}

View File

@ -11,6 +11,6 @@ namespace voxelutil {
extern void importHeightmap(voxel::RawVolumeWrapper& volume, const image::ImagePtr& image, const voxel::Voxel &underground, const voxel::Voxel &surface);
extern voxel::RawVolume* importAsPlane(const image::ImagePtr& image, uint8_t thickness = 1);
extern voxel::RawVolume* importAsVolume(const image::ImagePtr& image, const image::ImagePtr& heightmap, uint8_t maxHeight);
extern voxel::RawVolume* importAsVolume(const image::ImagePtr& image, const image::ImagePtr& heightmap, uint8_t maxDepth, bool bothSides = true);
}

View File

@ -54,6 +54,9 @@ app::AppState VoxConvert::onConstruct() {
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("true").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");
@ -384,8 +387,9 @@ bool VoxConvert::handleInputFile(const core::String &infile, voxelformat::SceneG
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) {
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");
@ -402,6 +406,23 @@ bool VoxConvert::handleInputFile(const core::String &infile, voxelformat::SceneG
node.setName(infile);
sceneGraph.emplace(core::move(node));
}
if (importAsVolume) {
const core::String &extinfile = core::string::extractExtension(infile);
core::String baseinfile = core::string::stripExtension(infile);
baseinfile.append("-dm.");
baseinfile.append(extinfile);
const image::ImagePtr& heightmap = image::loadImage(baseinfile, false);
if (!heightmap || !heightmap->isLoaded()) {
Log::error("Couldn't load heightmap %s", baseinfile.c_str());
return false;
}
voxelformat::SceneGraphNode node;
const int maxDepth = core::string::toInt(getArgVal("--image-as-volume-max-depth"));
const bool bothSides = core::string::toBool(getArgVal("--image-as-volume-both-sides"));
node.setVolume(voxelutil::importAsVolume(image, heightmap, maxDepth, bothSides), true);
node.setName(infile);
sceneGraph.emplace(core::move(node));
}
if (importAsPlane) {
voxelformat::SceneGraphNode node;
node.setVolume(voxelutil::importAsPlane(image), true);