VOXEDIT: allow to change and create the palette at runtime
parent
430a646d63
commit
4b353830d5
Binary file not shown.
After Width: | Height: | Size: 7.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
|
@ -1,3 +1,4 @@
|
|||
createpalette images/01.png
|
||||
importplane images/01.png
|
||||
importplane images/02.png
|
||||
importplane images/03.png
|
||||
|
|
|
@ -70,9 +70,8 @@ int Color::getClosestMatch(const glm::vec4& color, const std::vector<glm::vec4>&
|
|||
const float val = weightHue * glm::pow(dH, 2) +
|
||||
weightValue * glm::pow(dV, 2) +
|
||||
weightSaturation * glm::pow(dS, 2);
|
||||
const float curDistance = sqrtf(val);
|
||||
if (curDistance < minDistance) {
|
||||
minDistance = curDistance;
|
||||
if (val < minDistance) {
|
||||
minDistance = val;
|
||||
minIndex = i;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ ImagePtr loadImage(const std::string& filename, bool async) {
|
|||
return loadImage(file, async);
|
||||
}
|
||||
|
||||
bool Image::load(uint8_t* buffer, int length) {
|
||||
bool Image::load(const uint8_t* buffer, int length) {
|
||||
if (!buffer || length <= 0) {
|
||||
_state = io::IOSTATE_FAILED;
|
||||
Log::debug("Failed to load image %s: buffer empty", _name.c_str());
|
||||
|
|
|
@ -26,7 +26,7 @@ public:
|
|||
~Image();
|
||||
|
||||
bool load(const io::FilePtr& file);
|
||||
bool load(uint8_t* buffer, int length);
|
||||
bool load(const uint8_t* buffer, int length);
|
||||
|
||||
static void flipVerticalRGBA(uint8_t *pixels, int w, int h);
|
||||
static bool writePng(const char *name, const uint8_t *buffer, int width, int height, int depth);
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
*/
|
||||
|
||||
#include "MaterialColor.h"
|
||||
#include "image/Image.h"
|
||||
#include "core/Singleton.h"
|
||||
#include "core/App.h"
|
||||
#include "math/Random.h"
|
||||
|
@ -19,40 +18,28 @@ namespace voxel {
|
|||
|
||||
class MaterialColor {
|
||||
private:
|
||||
image::Image _image;
|
||||
MaterialColorArray _materialColors;
|
||||
std::unordered_map<VoxelType, MaterialColorIndices, EnumClassHash> _colorMapping;
|
||||
bool _initialized = false;
|
||||
bool _dirty = false;
|
||||
public:
|
||||
MaterialColor() :
|
||||
_image("**palette**") {
|
||||
MaterialColor() {
|
||||
}
|
||||
|
||||
bool init(const io::FilePtr& paletteFile, const io::FilePtr& luaFile) {
|
||||
bool init(const uint8_t* paletteBuffer, size_t paletteBufferSize, const std::string& luaString) {
|
||||
if (_initialized) {
|
||||
Log::debug("MaterialColors are already initialized");
|
||||
return true;
|
||||
}
|
||||
_initialized = true;
|
||||
if (!_image.load(paletteFile)) {
|
||||
Log::error("MaterialColors: failed to load image");
|
||||
return false;
|
||||
}
|
||||
if (!_image.isLoaded()) {
|
||||
Log::error("MaterialColors: image not fully loaded");
|
||||
return false;
|
||||
}
|
||||
const int colors = _image.width() * _image.height();
|
||||
_dirty = true;
|
||||
const int colors = paletteBufferSize / 4;
|
||||
if (colors != 256) {
|
||||
Log::error("Palette image has invalid dimensions: %i:%i", _image.width(), _image.height());
|
||||
return false;
|
||||
}
|
||||
if (_image.depth() != 4) {
|
||||
Log::error("Palette image has invalid depth: %i", _image.depth());
|
||||
Log::error("Palette image has invalid dimensions - we need 256x1(depth: 4)");
|
||||
return false;
|
||||
}
|
||||
_materialColors.reserve(colors);
|
||||
const uint32_t* paletteData = (const uint32_t*)_image.data();
|
||||
const uint32_t* paletteData = (const uint32_t*)paletteBuffer;
|
||||
for (int i = 0; i < colors; ++i) {
|
||||
_materialColors.emplace_back(core::Color::fromRGBA(*paletteData));
|
||||
++paletteData;
|
||||
|
@ -68,6 +55,11 @@ public:
|
|||
// 0 is VoxelType::Air - don't add it
|
||||
std::iota(std::begin(generic), std::end(generic), 1);
|
||||
|
||||
if (luaString.empty()) {
|
||||
Log::warn("No materials defined in lua script");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<luaL_Reg> funcs;
|
||||
static_assert((int)voxel::VoxelType::Air == 0, "Air must be 0");
|
||||
for (int i = (int)voxel::VoxelType::Air + 1; i < (int)voxel::VoxelType::Max; ++i) {
|
||||
|
@ -110,19 +102,14 @@ public:
|
|||
funcs.push_back({ nullptr, nullptr });
|
||||
lua.newGlobalData<MaterialColor>("MaterialColor", this);
|
||||
lua.reg("MAT", &funcs.front());
|
||||
const std::string& luaString = luaFile->load();
|
||||
if (luaString.empty()) {
|
||||
Log::error("Could not load lua script file: %s", luaFile->fileName().c_str());
|
||||
return false;
|
||||
}
|
||||
if (!lua.load(luaString)) {
|
||||
Log::error("Could not load lua script: %s. Failed with error: %s",
|
||||
luaFile->fileName().c_str(), lua.error().c_str());
|
||||
Log::error("Could not load lua script. Failed with error: %s",
|
||||
lua.error().c_str());
|
||||
return false;
|
||||
}
|
||||
if (!lua.execute("init")) {
|
||||
Log::error("Could not execute lua script file: %s. Failed with error: %s",
|
||||
luaFile->fileName().c_str(), lua.error().c_str());
|
||||
Log::error("Could not execute lua script. Failed with error: %s",
|
||||
lua.error().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -137,6 +124,21 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
_materialColors.clear();
|
||||
_colorMapping.clear();
|
||||
_initialized = false;
|
||||
_dirty = false;
|
||||
}
|
||||
|
||||
inline void markClean() {
|
||||
_dirty = false;
|
||||
}
|
||||
|
||||
inline bool isDirty() const {
|
||||
return _dirty;
|
||||
}
|
||||
|
||||
inline const MaterialColorArray& getColors() const {
|
||||
core_assert_msg(_initialized, "Material colors are not yet initialized");
|
||||
core_assert_msg(!_materialColors.empty(), "Failed to initialize the material colors");
|
||||
|
@ -200,16 +202,45 @@ static MaterialColor& getInstance() {
|
|||
return color;
|
||||
}
|
||||
|
||||
bool initMaterialColors(const uint8_t* paletteBuffer, size_t paletteBufferSize, const std::string& luaBuffer) {
|
||||
return getInstance().init(paletteBuffer, paletteBufferSize, luaBuffer);
|
||||
}
|
||||
|
||||
bool overrideMaterialColors(const uint8_t* paletteBuffer, size_t paletteBufferSize, const std::string& luaBuffer) {
|
||||
shutdownMaterialColors();
|
||||
if (!initMaterialColors(paletteBuffer, paletteBufferSize, luaBuffer)) {
|
||||
return initDefaultMaterialColors();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool materialColorChanged() {
|
||||
return getInstance().isDirty();
|
||||
}
|
||||
|
||||
void materialColorMarkClean() {
|
||||
getInstance().markClean();
|
||||
}
|
||||
|
||||
void shutdownMaterialColors() {
|
||||
return getInstance().shutdown();
|
||||
}
|
||||
|
||||
bool initMaterialColors(const io::FilePtr& paletteFile, const io::FilePtr& luaFile) {
|
||||
if (!paletteFile->exists()) {
|
||||
Log::error("Failed to load %s", paletteFile->name().c_str());
|
||||
Log::error("%s doesn't exist", paletteFile->name().c_str());
|
||||
return false;
|
||||
}
|
||||
if (!luaFile->exists()) {
|
||||
Log::error("Failed to load %s", luaFile->name().c_str());
|
||||
return false;
|
||||
}
|
||||
return getInstance().init(paletteFile, luaFile);
|
||||
const image::ImagePtr& img = image::loadImage(paletteFile, false);
|
||||
if (!img->isLoaded()) {
|
||||
Log::error("Failed to load image %s", paletteFile->name().c_str());
|
||||
return false;
|
||||
}
|
||||
return initMaterialColors(img->data(), img->width() * img->height() * img->depth(), luaFile->load());
|
||||
}
|
||||
|
||||
bool initDefaultMaterialColors() {
|
||||
|
@ -244,4 +275,41 @@ Voxel createRandomColorVoxel(VoxelType type, math::Random& random) {
|
|||
return getInstance().createRandomColorVoxel(type, random);
|
||||
}
|
||||
|
||||
bool createPalette(const image::ImagePtr& image, uint32_t *colorsBuffer, int colors) {
|
||||
if (!image || !image->isLoaded()) {
|
||||
return false;
|
||||
}
|
||||
const int imageWidth = image->width();
|
||||
const int imageHeight = image->height();
|
||||
Log::info("Create palette for image: %s", image->name().c_str());
|
||||
uint16_t paletteIndex = 0;
|
||||
std::unordered_set<uint32_t> colorset;
|
||||
for (int x = 0; x < imageWidth; ++x) {
|
||||
for (int y = 0; y < imageHeight; ++y) {
|
||||
const uint8_t* data = image->at(x, y);
|
||||
uint32_t rgba = *(uint32_t*)data;
|
||||
if (colorset.insert(rgba).second) {
|
||||
if (paletteIndex >= colors) {
|
||||
Log::info("palette indices exceeded");
|
||||
return false;
|
||||
}
|
||||
colorsBuffer[paletteIndex++] = rgba;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool createPaletteFile(const image::ImagePtr& image, const char *paletteFile) {
|
||||
if (!image || !image->isLoaded() || paletteFile == nullptr) {
|
||||
return false;
|
||||
}
|
||||
uint32_t buf[256];
|
||||
if (!createPalette(image, buf, lengthof(buf))) {
|
||||
return false;
|
||||
}
|
||||
const image::ImagePtr& paletteImg = image::createEmptyImage("**palette**");
|
||||
return paletteImg->writePng(paletteFile, (const uint8_t*)buf, 256, 1, 4);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "voxel/polyvox/Voxel.h"
|
||||
#include "io/File.h"
|
||||
#include "image/Image.h"
|
||||
|
||||
#include <glm/fwd.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
|
@ -23,8 +24,17 @@ typedef std::vector<uint8_t> MaterialColorIndices;
|
|||
|
||||
extern bool initDefaultMaterialColors();
|
||||
extern bool initMaterialColors(const io::FilePtr& paletteFile, const io::FilePtr& luaFile);
|
||||
extern bool initMaterialColors(const uint8_t* paletteBuffer, size_t paletteBufferSize, const std::string& luaBuffer);
|
||||
extern bool overrideMaterialColors(const uint8_t* paletteBuffer, size_t paletteBufferSize, const std::string& luaBuffer);
|
||||
extern void shutdownMaterialColors();
|
||||
extern void materialColorMarkClean();
|
||||
extern bool materialColorChanged();
|
||||
extern const MaterialColorArray& getMaterialColors();
|
||||
extern const glm::vec4& getMaterialColor(const Voxel& voxel);
|
||||
|
||||
extern bool createPalette(const image::ImagePtr& image, uint32_t *colorsBuffer, int colors);
|
||||
extern bool createPaletteFile(const image::ImagePtr& image, const char *paletteFile);
|
||||
|
||||
/**
|
||||
* @brief Get all known material color indices for the given VoxelType
|
||||
* @param type The VoxelType to get the indices for
|
||||
|
|
|
@ -116,6 +116,14 @@ bool RawVolumeRenderer::update(int idx) {
|
|||
std::vector<voxel::VoxelVertex> vertices;
|
||||
std::vector<voxel::IndexType> indices;
|
||||
|
||||
if (voxel::materialColorChanged()) {
|
||||
shader::Materialblock::Data materialBlock;
|
||||
memcpy(materialBlock.materialcolor, &voxel::getMaterialColors().front(), sizeof(materialBlock.materialcolor));
|
||||
_materialBlock.update(materialBlock);
|
||||
// TODO: updating the global state is crap - what about others - use an event
|
||||
voxel::materialColorMarkClean();
|
||||
}
|
||||
|
||||
voxel::IndexType offset = (voxel::IndexType)0;
|
||||
for (auto& i : _meshes) {
|
||||
const Meshes& meshes = i.second;
|
||||
|
|
|
@ -117,6 +117,7 @@ set(FILES
|
|||
voxedit/images/05.png
|
||||
voxedit/images/06.png
|
||||
voxedit/images/07.png
|
||||
voxedit/images/avatar.png
|
||||
|
||||
voxedit/voxedit-keybindings.cfg
|
||||
|
||||
|
|
|
@ -24,13 +24,20 @@ set(TEST_SRCS
|
|||
tests/LayerManagerTest.cpp
|
||||
tests/MementoHandlerTest.cpp
|
||||
tests/ModifierTest.cpp
|
||||
tests/ImageUtilsTest.cpp
|
||||
)
|
||||
set(TEST_FILES
|
||||
tests/test-palette-in.png
|
||||
)
|
||||
|
||||
gtest_suite_sources(tests
|
||||
${TEST_SRCS}
|
||||
)
|
||||
gtest_suite_deps(tests ${LIB})
|
||||
gtest_suite_files(tests ${TEST_FILES})
|
||||
|
||||
gtest_suite_begin(tests-${LIB} TEMPLATE ${ROOT_DIR}/src/modules/core/tests/main.cpp.in)
|
||||
gtest_suite_sources(tests-${LIB} ${TEST_SRCS} ../../../modules/core/tests/AbstractTest.cpp)
|
||||
gtest_suite_files(tests-${LIB} ${TEST_FILES})
|
||||
gtest_suite_deps(tests-${LIB} ${LIB})
|
||||
gtest_suite_end(tests-${LIB})
|
||||
|
|
|
@ -139,6 +139,22 @@ bool SceneManager::voxelizeModel(const video::MeshPtr& meshPtr) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool SceneManager::importPalette(const std::string& file) {
|
||||
const image::ImagePtr& img = image::loadImage(file, false);
|
||||
if (!img->isLoaded()) {
|
||||
return false;
|
||||
}
|
||||
uint32_t buf[256];
|
||||
if (!voxel::createPalette(img, buf, 256)) {
|
||||
return false;
|
||||
}
|
||||
if (!voxel::overrideMaterialColors((const uint8_t*)buf, sizeof(buf), "")) {
|
||||
Log::warn("Failed to import palette for image %s", file.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SceneManager::importAsPlane(const std::string& file) {
|
||||
const image::ImagePtr& img = image::loadImage(file, false);
|
||||
if (!img->isLoaded()) {
|
||||
|
@ -701,6 +717,14 @@ void SceneManager::construct() {
|
|||
moveCursor(x, y, z);
|
||||
}).setHelp("Move the cursor by the specified offsets");
|
||||
|
||||
core::Command::registerCommand("createpalette", [this] (const core::CmdArgs& args) {
|
||||
if (args.size() != 1) {
|
||||
Log::info("Expected to get an image file name as parameter");
|
||||
return;
|
||||
}
|
||||
importPalette(args[0]);
|
||||
});
|
||||
|
||||
core::Command::registerCommand("cursor", [this] (const core::CmdArgs& args) {
|
||||
if (args.size() < 3) {
|
||||
Log::info("Expected to get x, y and z coordinates");
|
||||
|
|
|
@ -189,6 +189,7 @@ public:
|
|||
bool voxelizeModel(const video::MeshPtr& mesh);
|
||||
bool importHeightmap(const std::string& file);
|
||||
bool importAsPlane(const std::string& file);
|
||||
bool importPalette(const std::string& file);
|
||||
bool exportModel(const std::string& file);
|
||||
bool save(const std::string& file, bool autosave = false);
|
||||
bool load(const std::string& file);
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#include "core/tests/AbstractTest.h"
|
||||
#include "../tool/ImageUtils.h"
|
||||
#include "voxel/MaterialColor.h"
|
||||
|
||||
namespace voxedit {
|
||||
|
||||
class ImageUtilsTest: public core::AbstractTest {
|
||||
};
|
||||
|
||||
TEST_F(ImageUtilsTest, testCreateAndLoadPalette) {
|
||||
const image::ImagePtr& img = image::loadImage("test-palette-in.png", false);
|
||||
ASSERT_TRUE(img->isLoaded()) << "Failed to load image: " << img->name();
|
||||
uint32_t buf[256];
|
||||
EXPECT_TRUE(voxel::createPalette(img, buf, 256)) << "Failed to create palette image";
|
||||
EXPECT_TRUE(voxel::overrideMaterialColors((const uint8_t*)buf, sizeof(buf), ""));
|
||||
}
|
||||
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
#include "core/Color.h"
|
||||
#include "core/Log.h"
|
||||
#include "core/GLM.h"
|
||||
#include <unordered_set>
|
||||
|
||||
namespace voxedit {
|
||||
|
||||
|
|
Loading…
Reference in New Issue