Initial commit: Final state of the master project

This commit is contained in:
2017-09-16 09:41:37 +02:00
commit 696180d43b
832 changed files with 169717 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
#pragma once
#include "../../inc/glm/common.hpp"
#include <string>
#include "../../core/Hashers.h"
class BaseMaterial
{
public:
// Should return a 10-bit per channel precision vec3
virtual glm::u16vec3 GetProperties() const = 0;
// Should update the materials in such a way that GetProperties should return the given properties
virtual void SetProperties(glm::u16vec3 properties) = 0;
virtual std::string GetTypeSuffix() const = 0;
inline bool operator==(const BaseMaterial &other) const
{
auto otherV = other.GetProperties();
auto thisV = GetProperties();
return otherV == thisV;
}
};
namespace std {
template<>
struct hash<BaseMaterial> {
size_t operator()(BaseMaterial const &value) const {
glm::u16vec3 properties = value.GetProperties();
// Since materials are only allowed to store 10 bits per channel, we should be able to hash them
// perfectly in 32 bits.
#ifdef ENVIRONMENT64
return std::hash<glm::u16vec3>()(properties);
#else
unsigned short mask = 0x3F;
return (((size_t) (properties.x & mask)) << 20) | (((size_t) (properties.y & mask)) << 10) |
(size_t) (properties.z & mask);
#endif
}
};
}

View File

@@ -0,0 +1,170 @@
#pragma once
#include "../../core/BitHelper.h"
#include "../../core/Defines.h"
#include <cmath>
template <unsigned N>
class BitsMaterial
{
public:
const static size_t BYTECOUNT = N / 8 + ((N % 8) == 0 ? 0 : 1); // Calculate the space needed to store the required number of bits
const static unsigned8 BITS = N;
private:
unsigned8 mValue[BYTECOUNT];
static size_t GrabBits(size_t value, unsigned8 startBit, unsigned8 length) {
return ((BitHelper::GetLSMask<size_t>(startBit, startBit + length)) & value) >> startBit;
}
static size_t GrabBits(size_t value, unsigned8 startBit) { return GrabBits(value, startBit, N); }
inline void Init(size_t value)
{
for (unsigned8 i = 0; i < BYTECOUNT; i++)
mValue[BYTECOUNT - i - 1] = (unsigned8)GrabBits(value, i * 8, 8);
}
public:
BitsMaterial() { mValue[0] = 0; }
BitsMaterial(size_t value) { Init(value); }
// Grab some bits from the number and store them in this material
BitsMaterial(unsigned value, unsigned8 startBit) { Init(GrabBits(value, startBit)); }
~BitsMaterial() {}
bool Empty() { return mValue == 0; }
glm::u16vec3 GetProperties() const {
unsigned32 value = (unsigned32)GetValue();
return glm::u16vec3(GrabBits(value, 21, 11), GrabBits(value, 10, 11), GrabBits(value, 0, 10));
}
void SetProperties(glm::u16vec3 material)
{
unsigned32 value = (((unsigned32)material.x) << 21) | (((unsigned32)material.y & BitHelper::GetLSMask<unsigned32>(0, 11)) << 10) | (((unsigned32)material.z) & BitHelper::GetLSMask<unsigned32>(0, 10));
Init(value);
}
size_t GetValue() const {
size_t value = 0;
for (unsigned8 i = 0; i < BYTECOUNT; i++)
value |= mValue[i] << ((BYTECOUNT - i - 1) >> 3); // Get the value, shift it to the correct position, and add it to the GetValue() result
return value;
}
std::string GetTypeSuffix() const {
return "b" + std::to_string(N);
}
static float Distance(BitsMaterial a, BitsMaterial b)
{
size_t maxValue = BitHelper::Exp2(N);
return (float)std::abs((float)b.GetValue() - (float)a.GetValue()) / (float)maxValue;
}
static BitsMaterial Average(const std::vector<BitsMaterial>& values)
{
size_t sum = 0;
for (auto value : values) sum += value.GetValue();
sum /= values.size();
return BitsMaterial(sum);
}
static BitsMaterial WeightedAverage(const std::vector<BitsMaterial>& values, std::vector<float> weights)
{
float sum = 0;
for (size_t i = 0; i < values.size(); i++) sum += (float)values[i].GetValue() * weights[i];
sum /= (float)CollectionHelper::Sum(weights);
return BitsMaterial((size_t)sum);
}
void SetLS(size_t index, bool value)
{
size_t byte = index >> 8;
unsigned8 mask = BitHelper::GetLSSingleBitMask<unsigned8>(index & 0x07);
// Clear the bit
mValue[byte] &= ~mask;
// Set the bit
if (value) mValue[byte] |= mask;
}
bool GetLS(size_t index)
{
size_t byte = index >> 8;
size_t bit = index % 8;
return BitHelper::GetLS(mValue[byte], (unsigned8)bit);
}
void SetHS(size_t index, bool value)
{
SetLS(N - index - 1, value);
}
bool GetHS(size_t index)
{
return GetLS(N - index - 1);
}
std::vector<unsigned8> Serialize() const {
std::vector<unsigned8> res(BYTECOUNT);
for (size_t i = 0; i < BYTECOUNT; i++) res[i] = mValue[i];
return res;
}
void Deserialize(std::vector<unsigned8> value) {
assert(value.size() == BYTECOUNT);
for (size_t i = 0; i < BYTECOUNT; i++) mValue[i] = value[i];
}
void Serialize(std::ostream& stream) const {
stream.write((char*)&mValue[0], BYTECOUNT);
}
void Deserialize(std::istream& stream) {
stream.read((char*)&mValue[0], BYTECOUNT);
}
bool operator==(const BitsMaterial<N>& other) const {
for (unsigned i = 0; i < BYTECOUNT; i++)
if (mValue[i] != other.mValue[i]) return false;
return true;
}
bool operator<(const BitsMaterial<N>& other) const
{
for (unsigned8 i = 0; i < BYTECOUNT; i++)
if (mValue[i] != other.mValue[i]) return mValue[i] < other.mValue[i];
return false; // If they are equal, return false
}
operator unsigned() const
{
return (unsigned32)GetValue();
}
};
namespace std
{
template<unsigned N> struct hash <BitsMaterial<N>>
{
size_t operator()(BitsMaterial<N> const& value) const
{
// Check if a perfect hash is possible
if (N < (sizeof(size_t) * 8))
{
return value.GetValue();
}
else
{
// TODO: make a hash for all bytes
return value.GetValue();
}
}
};
}
template<unsigned N>
struct BitsMaterialComparer
{
bool operator()(const BitsMaterial<N>& a, const BitsMaterial<N>& b)
{
return a.GetValue() < b.GetValue();
}
};

View File

@@ -0,0 +1,56 @@
#pragma once
#include "../../core/Defines.h"
#include "../../inc/tbb/parallel_sort.h"
template<typename T>
struct Block
{
public:
Block() { mData = std::vector<T>(); }
Block(unsigned size)
{
mData = std::vector<T>(size);
}
Block(const std::vector<T>& data)
{
// Copy the given vector to mData
mData = std::vector<T>(data);
}
Block(const std::vector<T>& data, const size_t& startIndex, const size_t& endIndex)
{
mData = std::vector<T>(data.begin() + startIndex, data.begin() + endIndex);
}
~Block() {}
size_t size() const { return mData.size(); }
const T& Get(const size_t& i) const { return mData[i]; }
const std::vector<T>& GetData() const { return mData; }
void Set(const size_t& i, T v) { mData[i] = v; }
template<typename Compare>
void Sort(const Compare& comparer)
{
std::sort(mData.begin(), mData.end(), comparer);
}
template<typename Compare>
void ParallelSort(const Compare& comparer)
{
tbb::parallel_sort(mData.begin(), mData.end(), comparer);
}
bool operator==(const Block<T>& other) const
{
if (other.size() != this->size())
return false;
for (size_t i = 0; i < this->size(); i++)
if (other.Get(i) != this->Get(i)) return false;
return true;
}
const T& operator[](size_t i) const { return mData[i]; }
private:
std::vector<T> mData;
};

View File

@@ -0,0 +1,38 @@
#pragma once
#include "../../core/Defines.h"
#include "../../core/Serializer.h"
#include "../../core/CollectionHelper.h"
#include "MaterialLibrary.h"
#include "Block.h"
#include "../../inc/tbb/parallel_sort.h"
#include <vector>
#include <iterator>
template<typename T, typename Comparer = std::less<T>, unsigned8 channelsPerPixel = 3>
class BlockBasedMaterialLibrary : public MaterialLibrary<T, Comparer, channelsPerPixel>
{
private:
std::vector<Block<T>> mBlocks;
std::vector<size_t> mBlocksImportance;
public:
BlockBasedMaterialLibrary() : MaterialLibrary<T, Comparer, channelsPerPixel>() {}
BlockBasedMaterialLibrary(std::vector<unsigned char> texture, unsigned short textureSize, MaterialLibraryPointer highestMaterialIndex) : MaterialLibrary<T, Comparer, channelsPerPixel>(texture, textureSize, highestMaterialIndex)
{ }
// Copy constructor
BlockBasedMaterialLibrary(const BlockBasedMaterialLibrary& other) : MaterialLibrary<T, Comparer, channelsPerPixel>(other)
{
this->mBlocks = other.mBlocks;
this->mBlocksImportance = other.mBlocksImportance;
}
~BlockBasedMaterialLibrary() {}
//void Finalize()
//{
// std::srand(time(0));
// // TODO: optimize the texture to get the lowest cost for the blocks
// std::random_shuffle(mMaterials->begin(), mMaterials->end());
// MaterialLibrary::Finalize(false);
//}
};

View File

@@ -0,0 +1,131 @@
#include "Color.h"
#include "../../core/ColorHelper.h"
#include "../../core/CollectionHelper.h"
#include "../../core/MathHelper.h"
#include "../../inc/tbb/parallel_reduce.h"
#include "../../inc/tbb/parallel_for.h"
#include "../../inc/tbb/blocked_range.h"
#include "../../core/Serializer.h"
#include <algorithm>
#include <cmath>
Color::Color()
{
mColor = glm::u8vec3(0);
}
Color::Color(glm::u8vec3 color)
{
mColor = color;
}
glm::u16vec3 Color::GetProperties() const
{
return glm::u16vec3(mColor);
}
void Color::SetProperties(glm::u16vec3 color)
{
mColor = glm::u8vec3(color);
}
glm::u8vec3 Color::GetColor() const
{
return mColor;
}
void Color::SetColor(glm::u8vec3 color)
{
mColor = color;;
}
glm::vec3 Color::GetLAB() const
{
return ColorHelper::RGBtoLAB(mColor);
}
Color Color::Average(const std::vector<Color>& colors)
{
glm::vec3 colorLabSum;
for (auto color : colors)
colorLabSum += ColorHelper::RGBtoLAB(color.GetColor());
glm::vec3 colorLabAvg = colorLabSum / (float)colors.size();
return Color(ColorHelper::LABtoRGB(colorLabAvg));
}
Color Color::WeightedAverage(const std::vector<Color>& colors, const std::vector<float>& weights)
{
// Calculate the weighted average
glm::vec3 colorLabSum;
std::vector<glm::vec3> labColors(colors.size());
float weightSum = 0;
for (unsigned i = 0; i < colors.size(); i++)
{
labColors[i] = colors[i].GetLAB();
colorLabSum += labColors[i] * weights[i];
weightSum += weights[i];
}
glm::vec3 colorLabAvg = colorLabSum / weightSum;
glm::vec3 res = colorLabAvg;
return Color(ColorHelper::LABtoRGB(res));
}
float Color::Distance(const Color& color1, const Color& color2)
{
return ColorHelper::GetDeltaEFromRGB(color1.GetColor(), color2.GetColor()) / 130.f;
}
Color Color::Interpolate(const Color& color1, const Color& color2, float value)
{
glm::u8vec3 hsv1 = ColorHelper::RGBtoHSV(color1.GetColor());
glm::u8vec3 hsv2 = ColorHelper::RGBtoHSV(color2.GetColor());
glm::u8vec3 hsvRes;
hsvRes.r = MathHelper::lerp(value, hsv1.r, hsv2.r);
hsvRes.g = MathHelper::lerp(value, hsv1.g, hsv2.g);
hsvRes.b = MathHelper::lerp(value, hsv1.b, hsv2.b);
return Color(ColorHelper::HSVtoRGB(hsvRes));
}
std::vector<unsigned8> Color::Serialize() const
{
std::vector<unsigned8> res(3);
res[0] = mColor.r;
res[1] = mColor.g;
res[2] = mColor.b;
return res;
}
void Color::Deserialize(const std::vector<unsigned8>& value)
{
assert(value.size() == 3);
mColor.r = value[0];
mColor.g = value[1];
mColor.b = value[2];
}
bool Color::operator==(const Color& color) const
{
return this->GetColor() == color.GetColor();
}
bool Color::operator!=(const Color& other) const
{
return !(*this == other);
}
Color& Color::operator=(const unsigned& value)
{
mColor.x = (value & 0x00FF0000) >> 16;
mColor.y = (value & 0x0000FF00) >> 8;
mColor.z = (value & 0x000000FF);
return *this;
}
Color::operator unsigned() const
{
return mColor.x << 16 | mColor.y << 8 | mColor.z;
}
std::string Color::GetTypeSuffix() const { return "c"; }

View File

@@ -0,0 +1,109 @@
#pragma once
#include "NearestFinder.h"
#include "../../inc/glm/common.hpp"
#include "../../core/Hashers.h"
#include "../../core/Comparers.h"
#include "../../core/ColorHelper.h"
class Color
{
public:
const static unsigned8 CHANNELSPERPIXEL = 3;
const static unsigned8 BITS = 24;
Color();
Color(glm::u8vec3 color);
glm::u16vec3 GetProperties() const;
void SetProperties(glm::u16vec3 color);
glm::vec3 GetLAB() const;
static Color Average(const std::vector<Color>& colors);
static Color WeightedAverage(const std::vector<Color>& colors, const std::vector<float>& weights);
// Interpolates between the two colors. Value should be in range [0, 1), and it use to determine where to interpolate
static Color Interpolate(const Color& color1, const Color& color2, float value);
// Returns the color that is nearest to the given color (O(N))
static float Distance(const Color& color1, const Color& color2);
glm::u8vec3 GetColor() const;
void SetColor(glm::u8vec3 color);
inline unsigned8 GetR() const { return mColor.r; }
inline unsigned8 GetG() const { return mColor.g; }
inline unsigned8 GetB() const { return mColor.b; }
std::string GetTypeSuffix() const;
std::vector<unsigned8> Serialize() const;
void Deserialize(const std::vector<unsigned8>& value);
bool operator==(const Color& color) const;
bool operator!=(const Color& color) const;
// Assignment operator, used for easy access to different kinds of materials
Color& operator=(const unsigned& source);
operator unsigned() const;
unsigned8 operator[](unsigned8 i) const
{
return mColor[i];
}
private:
glm::u8vec3 mColor;
};
namespace std {
template<>
struct hash<Color> {
size_t operator()(const Color &value) const {
glm::u8vec3 color = value.GetColor();
return std::hash<glm::u8vec3>()(color);
}
};
};
struct ColorCompare
{
bool operator()(const Color& color1, const Color& color2) const
{
return u8vec3comparer()(color1.GetColor(), color2.GetColor());
}
};
template<> struct NearestFinder<Color>
{
Color operator()(const Color& source, const std::vector<Color>& colors)
{
glm::vec3 lab = source.GetLAB();
Color nearestColor;
float minDistance = std::numeric_limits<float>::max();
for (auto color : colors)
{
float distance = ColorHelper::GetDeltaEFromLAB(color.GetLAB(), lab);
if (distance < minDistance)
{
minDistance = distance;
nearestColor = color;
}
}
return nearestColor;
}
};
template<> struct ParallelNearestFinder<Color>
{
Color operator()(const Color& source, const std::vector<Color>& colors)
{
// Calculate all distances:
glm::vec3 lab = source.GetLAB();
std::vector<float> distances(colors.size());
tbb::parallel_for(size_t(0), colors.size(), [&](size_t i)
{
float res = ColorHelper::GetDeltaEFromLAB(colors[i].GetLAB(), lab);
if (std::isnan(res)) res = std::numeric_limits<float>::max();
distances[i] = res;
});
return colors[CollectionHelper::MinIndex(distances)];
}
};

View File

@@ -0,0 +1,6 @@
#pragma once
#include "ColorAndValue.h"
#include "SmallNormal.h"
typedef ColorAndValue<SmallNormal> ColorAndNormal;
typedef ColorAndValueCompare<SmallNormal, NormalCompare> ColorAndNormalCompare;

View File

@@ -0,0 +1,9 @@
#pragma once
#include "ColorAndValue.h"
#include "SmallNormal.h"
#include "BitsMaterial.h"
typedef MaterialPair<SmallNormal, BitsMaterial<8>> NormalAndValue;
typedef MaterialPairCompare<SmallNormal, NormalCompare, BitsMaterial<8>, BitsMaterialComparer<8>> NormalAndValueCompare;
typedef ColorAndValue<NormalAndValue> ColorAndNormalAndValue;
typedef ColorAndValueCompare<NormalAndValue, NormalAndValueCompare> ColorAndNormalAndValueCompare;

View File

@@ -0,0 +1,6 @@
#pragma once
#include "ColorAndValue.h"
#include "BitsMaterial.h"
typedef ColorAndValue<BitsMaterial<16>> ColorAndOpacity;
typedef ColorAndValueCompare<BitsMaterial<16>, BitsMaterialComparer<16>> ColorAndOpacityCompare;

View File

@@ -0,0 +1,110 @@
#pragma once
#include "NearestFinder.h"
#include "Color.h"
#include "MaterialPair.h"
#include "../../core/ColorHelper.h"
#include "../../inc/glm/glm.hpp"
#include "../../core/CollectionHelper.h"
#include "../../core/BitHelper.h"
template<typename T>
struct ParallelNearestFinder<MaterialPair<Color, T>>
{
MaterialPair<Color, T> operator()(const MaterialPair<Color, T>& source, const std::vector<MaterialPair<Color, T>>& values) const
{
// Get the color differences
glm::vec3 lab = source.GetFirst().GetLAB();
std::vector<float> distances(values.size());
tbb::parallel_for(size_t(0), values.size(), [&](size_t i)
{
float res = ColorHelper::GetDeltaEFromLAB(values[i].GetFirst().GetLAB(), lab);
if (std::isnan(res)) res = std::numeric_limits<float>::max();
distances[i] = res;
});
size_t colorMinIndex = CollectionHelper::MinIndex(distances);
float colorMinDistance = distances[colorMinIndex];
float epsilon = 0.2f;
std::vector<size_t> minIndices;
float maxAllowedError = colorMinDistance + epsilon;
for (size_t i = 0; i < values.size(); i++)
if (distances[i] <= maxAllowedError) minIndices.push_back(i);
std::vector<T> minValues;
for (size_t i = 0; i < minIndices.size(); i++) minValues.push_back(values[minIndices[i]].GetSecond());
T minValue = ParallelNearestFinder<T>()(source.GetSecond(), minValues);
for (size_t i = 0; i < minIndices.size(); i++) if (values[minIndices[i]].GetSecond() == minValue) return values[minIndices[i]];
return values[minIndices[0]];
}
};
template<typename T>
struct NearestFinder<MaterialPair<Color, T>>
{
MaterialPair<Color, T> operator()(const MaterialPair<Color, T>& source, const std::vector<MaterialPair<Color, T>>& values) const
{
glm::vec3 lab = source.GetFirst().GetLAB();
float minDistance = std::numeric_limits<float>::max();
std::vector<float> distances(values.size());
Color lastColor = values[values.size() - 1].GetFirst();
for (size_t i = 0; i < values.size(); i++)
{
const Color& color = values[i].GetFirst();
if (color == lastColor)
distances[i] = distances[i - 1];
else
{
distances[i] = ColorHelper::GetDeltaEFromLAB(color.GetLAB(), lab);
lastColor = color;
if (distances[i] < minDistance) minDistance = distances[i];
}
}
float epsilon = 0.5f;
std::vector<size_t> allowedColors;
float maxAllowedError = minDistance + epsilon;
for (size_t i = 0; i < values.size(); i++)
if (distances[i] <= maxAllowedError) allowedColors.push_back(i);
std::vector<size_t> minIndices;
for (size_t i = 0; i < values.size(); i++)
if (distances[i] <= maxAllowedError) minIndices.push_back(i);
std::vector<T> minValues;
for (size_t i = 0; i < minIndices.size(); i++) minValues.push_back(values[minIndices[i]].GetSecond());
T minValue = NearestFinder<T>()(source.GetSecond(), minValues);
for (size_t i = 0; i < minIndices.size(); i++) if (values[minIndices[i]].GetSecond() == minValue) return values[minIndices[i]];
return values[minIndices[0]];
}
};
//// Assignment operator, used for easy access to different kinds of materials
//ColorAndValue& operator=(const unsigned& source)
//{
// mValue = source & 0x3FF;
// glm::u8vec3 color;
// color.r = (source >> (10 + 14 - 1)) & 0xFE;
// color.g = (source >> (10 + 7 - 1)) & 0xFE;
// color.b = (source >> (10 - 1)) & 0xFE;
// mColor = Color(color);
//}
//operator unsigned() const
//{
// // Value is stored in the 10 lowest siginificant bits
// unsigned32 res = (unsigned32)mValue;
// // Colors are stored in the 21 highest significant bits (7 bits per channel, doesn't fit otherwise...)
// res |= ((unsigned32)mColor.GetR() & 0xFE) << (10 + 14 - 1);
// res |= ((unsigned32)mColor.GetG() & 0xFE) << (10 + 7 - 1);
// res |= ((unsigned32)mColor.GetB() & 0xFE) << (10 - 1);
// return res;
//}
template<typename T> using ColorAndValue = MaterialPair<Color, T>;
template<typename T, typename TCompare> using ColorAndValueCompare = MaterialPairCompare<Color, ColorCompare, T, TCompare>;

View File

@@ -0,0 +1,23 @@
#include "ColorChannel.h"
ColorChannel::ColorChannel() { mValue = 0; }
ColorChannel::ColorChannel(unsigned8 value) { mValue = value; }
ColorChannel::~ColorChannel() {}
std::vector<unsigned8> ColorChannel::Serialize() const
{
return std::vector<unsigned8>(1, mValue);
}
void ColorChannel::Deserialize(const std::vector<unsigned8>& value)
{
assert(value.size() == 1);
mValue = value[0];
}
glm::u16vec3 ColorChannel::GetProperties() const { return glm::u16vec3(mValue); }
void ColorChannel::SetProperties(glm::u16vec3 material) { mValue = (unsigned char)material.x; }
unsigned8 ColorChannel::GetValue() const { return mValue; }
ColorChannel::operator unsigned() const { return (unsigned)mValue; }

View File

@@ -0,0 +1,40 @@
#pragma once
#include <vector>
#include <string>
#include "../../inc/glm/common.hpp"
#include "../../core/Defines.h"
class ColorChannel
{
public:
ColorChannel();
ColorChannel(unsigned8 value);
~ColorChannel();
glm::u16vec3 GetProperties() const;
void SetProperties(glm::u16vec3 material);
std::vector<unsigned8> Serialize() const;
void Deserialize(const std::vector<unsigned8>& value);
unsigned8 GetValue() const;
std::string GetTypeSuffix() const { return "cc"; }
operator unsigned() const;
private:
unsigned8 mValue;
};
namespace std
{
template<>
struct hash<ColorChannel>
{
size_t operator()(const ColorChannel &value) const
{
return value.GetValue();
}
};
}

View File

@@ -0,0 +1,277 @@
#pragma once
#include "../../core/Defines.h"
#include "../../core/Serializer.h"
#include "../../core/CollectionHelper.h"
#include "MaterialLibraryPointer.h"
#include "../../inc/tbb/parallel_sort.h"
#include <vector>
#include <iterator>
// T should be some type with a method called "Serialize()".
// This method should return some type that can be iterated over using operator [], and each item of the iteration should be an unsigned char (unsigned8) to put in the texture colors.
// If sizeof(result) for the value is used, it should give the number of bytes needed to store in pixels.
// An example of a working return type is std::vector<unsigned8>, glm::u8vec3, and unsigned char[].
template<typename T, typename Comparer = std::less<T>, unsigned8 channelsPerPixel = 3>
class MaterialLibrary
{
private:
std::map<T, MaterialLibraryPointer, Comparer> mMaterialPointers;
bool finalized = false;
unsigned short mTextureSize;
protected:
const size_t SIZE_OF_MATERIAL = sizeof(T);
const unsigned32 PIXELS_PER_MATERIAL = (unsigned32)(SIZE_OF_MATERIAL / channelsPerPixel + (SIZE_OF_MATERIAL % channelsPerPixel == 0 ? 0 : 1));
std::vector<T>* mMaterials;
inline MaterialLibraryPointer WrapSetIndex(size_t setIndex) const
{
assert(finalized);
setIndex *= PIXELS_PER_MATERIAL;
return MaterialLibraryPointer(
(unsigned16)(setIndex % (size_t)mTextureSize),
(unsigned16)(setIndex / (size_t)mTextureSize)
);
}
inline size_t GetSetIndex(MaterialLibraryPointer textureIndex) const { assert(finalized); return (textureIndex.x + textureIndex.y * mTextureSize) / PIXELS_PER_MATERIAL; }
inline void RebuildMaterialPointers()
{
assert(finalized);
mMaterialPointers.clear();
// Build the result material pointers map:
for (size_t i = 0; i < mMaterials->size(); i++)
mMaterialPointers.insert(std::pair<T, MaterialLibraryPointer>(mMaterials->at(i), WrapSetIndex(i)));
}
public:
MaterialLibrary() :
mMaterialPointers(std::map<T, MaterialLibraryPointer, Comparer>()),
finalized(false),
mTextureSize(0),
mMaterials(new std::vector<T>())
{}
MaterialLibrary(std::vector<unsigned char> texture, unsigned short textureSize, MaterialLibraryPointer highestMaterialIndex) : MaterialLibrary()
{
ReadTexture(texture, textureSize, highestMaterialIndex);
}
// Copy constructor
MaterialLibrary(const MaterialLibrary& other) :
mMaterialPointers(std::map<T, MaterialLibraryPointer, Comparer>(other.mMaterialPointers)),
finalized(other.finalized),
mTextureSize(other.mTextureSize),
mMaterials(new std::vector<T>(*other.mMaterials))
{}
~MaterialLibrary() { delete mMaterials; }
void Serialize(std::ostream& file)
{
unsigned16 materialTextureSize = GetTextureSize();
MaterialLibraryPointer maxTextureIndex = GetMaxTextureIndex();
std::vector<unsigned8> texture = GetTexture();
// The material texture size and max texture index used to be stored in one 32 bit unsigned integer.
// However, this caused problems if the material texture contained more then 1024 colors.
// Therefore, we now use 48 bits for this, precluded by an empty header in the old style, to ensure that the new style can be recognized correctly.
Serializer<unsigned32>::Serialize(0, file);
Serializer<unsigned16>::Serialize(materialTextureSize, file);
Serializer<MaterialLibraryPointer>::Serialize(maxTextureIndex, file);
Serializer<unsigned8*>::Serialize(&texture[0], (size_t)materialTextureSize * (size_t)materialTextureSize * (size_t)channelsPerPixel, file);
}
void Deserialize(std::istream& file)
{
unsigned16 materialTextureSize = 0;
MaterialLibraryPointer maxTextureIndex = MaterialLibraryPointer(0);
// If the first unsigned32 of a material library is 0, then the new style information is placed after it.
// If it isn't 0, then use the old style
unsigned materialTextureSizeSummary;
Serializer<unsigned>::Deserialize(materialTextureSizeSummary, file);
if (materialTextureSizeSummary == 0)
{
// New style (supports bigger libraries)
Serializer<unsigned16>::Deserialize(materialTextureSize, file);
Serializer<MaterialLibraryPointer>::Deserialize(maxTextureIndex, file);
}
else
{
// Old style
unsigned mask1 = BitHelper::GetLSMask<unsigned32>(20, 30);
unsigned mask2 = BitHelper::GetLSMask<unsigned32>(10, 20);
unsigned mask3 = BitHelper::GetLSMask<unsigned32>(0, 10);
maxTextureIndex.x = (mask1 & materialTextureSizeSummary) >> 20;
maxTextureIndex.y = (mask2 & materialTextureSizeSummary) >> 10;
materialTextureSize = BitHelper::CeilToNearestPowerOfTwo(mask3 & materialTextureSizeSummary);
}
size_t textureArraySize = (size_t)materialTextureSize * (size_t)materialTextureSize * (size_t)channelsPerPixel;
std::vector<unsigned8> texture(textureArraySize);
Serializer<unsigned8*>::Deserialize(&texture[0], textureArraySize, file);
ReadTexture(texture, materialTextureSize, maxTextureIndex);
}
void AddMaterial(const T& material) {
if(!finalized) mMaterials->push_back(material);
}
void RemoveMaterial(const T& material) { if (!finalized) mMaterials->erase(material); }
void Finalize(bool filterUnique = true)
{
if (finalized) return;
if (filterUnique)
CollectionHelper::Unique(*mMaterials, Comparer());
mTextureSize = GetTextureSize();
finalized = true;
RebuildMaterialPointers();
}
bool IsFinalized() const { return finalized; }
std::vector<T> GetMaterials() const
{
std::vector<T> materials(mMaterials->size());
tbb::parallel_for(size_t(0), mMaterials->size(), [&](size_t i)
{
materials[i] = mMaterials->at(i);
});
return materials;
}
T GetNearestMaterial(T material) const {
return NearestFinder<T>()(material, *mMaterials);
}
T GetMaterial(MaterialLibraryPointer materialPointer)
{
assert(finalized);
return mMaterials->at(GetSetIndex(materialPointer));
}
std::vector<std::pair<T, MaterialLibraryPointer>> GetMaterialTextureIndices() const
{
assert(finalized);
std::vector<std::pair<T, MaterialLibraryPointer>> materials;
size_t setIndex = 0;
for (T material : (*mMaterials))
{
auto textureIndex = WrapSetIndex(setIndex++);
materials.push_back(std::pair<T, MaterialLibraryPointer>(material, textureIndex));
}
return materials;
}
MaterialLibraryPointer GetTextureIndex(const T& material) const
{
assert(finalized);
auto materialIt = mMaterialPointers.find(material);
if (materialIt == mMaterialPointers.end())
return GetTextureIndex(GetNearestMaterial(material));
return materialIt->second;
}
bool Contains(const T& material) const
{
auto materialIt = mMaterialPointers.find(material);
return materialIt != mMaterialPointers.end();
}
MaterialLibraryPointer GetMaxTextureIndex() const
{
return WrapSetIndex(mMaterials->size());
}
unsigned short GetTextureSize() const {
if (finalized) return mTextureSize;
if (mMaterials->empty())
return 0;
size_t requiredPixels = PIXELS_PER_MATERIAL * mMaterials->size();
if (requiredPixels < 4) requiredPixels = 4; // Make sure the texture is always 2x2
unsigned16 v = (unsigned16)std::ceil(std::sqrt(double(requiredPixels)));
return BitHelper::CeilToNearestPowerOfTwo(v);
}
std::vector<unsigned8> GetTexture() const
{
std::vector<unsigned8> texture(mTextureSize * mTextureSize * channelsPerPixel);
size_t i = 0;
for (T material : (*mMaterials))
{
auto materialProperties = material.Serialize();
unsigned props = (unsigned)materialProperties.size();
unsigned prop = 0;
assert(i + channelsPerPixel * PIXELS_PER_MATERIAL <= texture.size());
for (unsigned pixel = 0; pixel < PIXELS_PER_MATERIAL; pixel++)
{
for (unsigned8 channel = 0; channel < channelsPerPixel; channel++)
{
if (prop < props)
texture[i + channel] = materialProperties[prop];
prop++;
}
i += channelsPerPixel;
}
}
return texture;
}
void ReadTexture(std::vector<unsigned char> texture, unsigned textureSize, MaterialLibraryPointer maxTextureIndex)
{
mTextureSize = textureSize;
unsigned props = PIXELS_PER_MATERIAL * channelsPerPixel;
std::vector<unsigned8> curMatProps(SIZE_OF_MATERIAL);
unsigned maxIndex = maxTextureIndex.x + maxTextureIndex.y * textureSize;
bool endFound = maxIndex != 0;
bool firstEmptyMaterialFound = false;
size_t i = 0;
while (!(endFound && i >= maxIndex * channelsPerPixel))
{
// Read the materials from the current pixels
for (size_t j = 0; j < SIZE_OF_MATERIAL; j++)
curMatProps[j] = texture[i + j];
if (!endFound)
{
bool allZero = true;
for (unsigned char prop : curMatProps)
if (prop != 0)
{
allZero = false;
break;
}
if (firstEmptyMaterialFound && allZero) endFound = true;
firstEmptyMaterialFound |= allZero;
}
T curMat;
curMat.Deserialize(curMatProps);
AddMaterial(curMat);
i += props;
}
finalized = true;
RebuildMaterialPointers();
}
bool operator==(const MaterialLibrary& other) const
{
// Check if the material count and finalized state are equal
if (this->IsFinalized() != other.IsFinalized()
|| this->mMaterials->size() != other.mMaterials->size())
return false;
// Check if the materials are equal
for (size_t i = 0; i < mMaterials->size(); i++)
if (this->mMaterials->at(i) != other.mMaterials->at(i))
return false;
return true;
}
bool operator!=(const MaterialLibrary& other) const
{
return !(*this == other);
}
};

View File

@@ -0,0 +1,55 @@
#pragma once
#include "../../inc/glm/common.hpp"
#include "../../core/Defines.h"
#include "../../core/BitHelper.h"
#include <vector>
class MaterialLibraryPointer :
public glm::u16vec2
{
public:
MaterialLibraryPointer() : glm::u16vec2() {}
MaterialLibraryPointer(const unsigned16 v) : glm::u16vec2(v) {}
MaterialLibraryPointer(const unsigned16 x, const unsigned16 y) : glm::u16vec2(x, y) {}
~MaterialLibraryPointer() {}
MaterialLibraryPointer& operator=(const unsigned& source)
{
x = (source & 0xFFFF0000) >> 16;
y = source & 0x0000FFFF;
return *this;
}
std::vector<unsigned8> Serialize()
{
return BitHelper::SplitInBytes(this->operator unsigned());
}
operator unsigned() const
{
return (((unsigned)x) << 16) | y;
}
bool operator==(const MaterialLibraryPointer& other) const
{
return x == other.x && y == other.y;
}
bool operator!=(const MaterialLibraryPointer& other) const
{
return !(*this == other);
}
};
// Perfect hash
namespace std
{
template<>
struct hash<MaterialLibraryPointer>
{
size_t operator()(MaterialLibraryPointer const &value) const
{
return (unsigned) value;
}
};
}

View File

@@ -0,0 +1,169 @@
#pragma once
#include "Color.h"
#include "../../core/ColorHelper.h"
#include "../../inc/glm/glm.hpp"
#include "../../core/Hashers.h"
#include "../../core/Comparers.h"
#include "../../core/CollectionHelper.h"
#include "../../core/Serializer.h"
#include "../../core/BitHelper.h"
template<typename U, typename V>
class MaterialPair
{
private:
U mFirst;
V mSecond;
public:
static const unsigned8 BITS = U::BITS + V::BITS;
static const unsigned8 CHANNELSPERPIXEL = BITS % 32 == 0 ? 4 : 3;
MaterialPair() : mFirst(U()), mSecond(V()) {}
MaterialPair(U first, V second) : mFirst(first), mSecond(second) {}
MaterialPair(std::pair<U, V> pair) : mFirst(pair.first), mSecond(pair.second) {}
template<typename UC, typename VC>
MaterialPair(UC first, VC second) : MaterialPair(U(first), V(second)) {}
const U& GetFirst() const { return mFirst; }
void SetFirst(U value) { mFirst = value; }
const V& GetSecond() const { return mSecond; }
void SetSecond(V value) { mSecond = value; }
static MaterialPair Average(const std::vector<MaterialPair>& values)
{
std::vector<U> firsts(values.size());
std::vector<V> seconds(values.size());
for (size_t i = 0; i < values.size(); i++)
{
firsts[i] = values[i].GetFirst();
seconds[i] = values[i].GetSecond();
}
return MaterialPair(U::Average(first), V::Average(seconds));
}
static MaterialPair WeightedAverage(const std::vector<MaterialPair>& values, const std::vector<float>& weights)
{
std::vector<U> firsts(values.size());
std::vector<V> seconds(values.size());
for (size_t i = 0; i < values.size(); i++)
{
firsts[i] = values[i].GetFirst();
seconds[i] = values[i].GetSecond();
}
return MaterialPair(U::WeightedAverage(firsts, weights), V::WeightedAverage(seconds, weights));
}
static float Distance(const MaterialPair& a, const MaterialPair& b)
{
return (U::Distance(a.GetFirst(), a.GetSecond()) + V::Distance(a.GetSecond(), b.GetSecond())) * 0.5f;
}
std::string GetTypeSuffix() const { return GetFirst().GetTypeSuffix() + GetSecond().GetTypeSuffix(); }
std::vector<unsigned8> Serialize() const {
std::vector<unsigned8> firstSerialized = GetFirst().Serialize();
std::vector<unsigned8> secondSerialized = GetSecond().Serialize();
firstSerialized.insert(firstSerialized.end(), secondSerialized.begin(), secondSerialized.end());
return firstSerialized;
};
void Deserialize(const std::vector<unsigned8>& value)
{
mFirst.Deserialize(std::vector<unsigned8>(value.begin(), value.begin() + sizeof(mFirst)));
mSecond.Deserialize(std::vector<unsigned8>(value.begin() + sizeof(mFirst), value.end()));
}
bool operator==(const MaterialPair& v) const { return v.mFirst == mFirst && v.mSecond == mSecond; }
bool operator!=(const MaterialPair& n) const { return !(n == *this); }
//// Assignment operator, used for easy access to different kinds of materials
//ColorAndValue& operator=(const unsigned& source)
//{
// mValue = source & 0x3FF;
// glm::u8vec3 color;
// color.r = (source >> (10 + 14 - 1)) & 0xFE;
// color.g = (source >> (10 + 7 - 1)) & 0xFE;
// color.b = (source >> (10 - 1)) & 0xFE;
// mColor = Color(color);
//}
//operator unsigned() const
//{
// // Value is stored in the 10 lowest siginificant bits
// unsigned32 res = (unsigned32)mValue;
// // Colors are stored in the 21 highest significant bits (7 bits per channel, doesn't fit otherwise...)
// res |= ((unsigned32)mColor.GetR() & 0xFE) << (10 + 14 - 1);
// res |= ((unsigned32)mColor.GetG() & 0xFE) << (10 + 7 - 1);
// res |= ((unsigned32)mColor.GetB() & 0xFE) << (10 - 1);
// return res;
//}
};
template<typename U, typename V>
struct NearestFinder<MaterialPair<U, V>>
{
MaterialPair<U, V> operator()(const MaterialPair<U, V>& source, const std::vector<MaterialPair<U, V>>& values)
{
std::vector<float> distances(values.size());
// Distances are defined as the sum.
for (size_t i = 0; i < values.size(); i++)
distances[i] = U::Distance(source.GetFirst(), values[i].GetFirst()) + V::Distance(source.GetSecond(), values[i].GetSecond());
auto minIt = std::min_element(distances.begin(), distances.end());
size_t minIndex = std::distance(distances.begin(), minIt);
return values[minIndex];
}
};
template<typename U, typename V>
struct ParallelNearestFinder<MaterialPair<U, V>>
{
MaterialPair<U, V> operator()(const MaterialPair<U, V>& source, const std::vector<MaterialPair<U, V>>& values)
{
std::vector<float> distances(values.size());
// Distances are defined as the sum.
for (size_t i = 0; i < values.size(); i++)
distances[i] = U::Distance(source.GetFirst(), values[i].GetFirst()) + V::Distance(source.GetSecond(), values[i].GetSecond());
auto minIt = std::min_element(distances.begin(), distances.end());
size_t minIndex = std::distance(distances.begin(), minIt);
return values[minIndex];
}
};
namespace std {
template<typename U, typename V>
struct hash<MaterialPair<U, V>> {
size_t operator()(const MaterialPair<U, V> &value) const {
#ifdef ENVIRONMENT64
size_t cHash = std::hash<U>()(value.GetFirst());
size_t nHash = std::hash<V>()(value.GetSecond());
return cHash | BitHelper::CircularShiftLeft<size_t>(nHash, V::BITS);
#else
return (unsigned32)value;
#endif
}
};
}
template<typename U, typename UCompare, typename V, typename VCompare>
struct MaterialPairCompare
{
bool operator()(const MaterialPair<U, V>& a, const MaterialPair<U, V>& b) const
{
if (a.GetFirst() != b.GetFirst()) return UCompare()(a.GetFirst(), b.GetFirst());
return VCompare()(a.GetSecond(), b.GetSecond());
}
};
template<typename U, typename V>
struct Serializer<MaterialPair<U, V>>
{
static void Serialize(const MaterialPair<U, V>& value, std::ostream& out)
{
Serializer<U>::Serialize(value.GetFirst(), out);
Serializer<V>::Serialize(value.GetSecond(), out);
}
static void Deserialize(MaterialPair<U, V>& value, std::istream& in)
{
U first; Serializer<U>::Deserialize(first, in); value.SetFirst(first);
V second; Serializer<V>::Deserialize(second, in); value.SetSecond(second);
}
};

View File

@@ -0,0 +1,18 @@
#pragma once
#include <map>
#include <vector>
template<typename T, typename Comparer>
class BaseQuantizer
{
public:
virtual std::map<T, T, Comparer>* QuantizeMaterials(std::vector<T> materials) const = 0;
virtual std::string GetQuantizerDescriptor() const = 0;
};
template<typename T>
class QuickQuantizer
{
public:
virtual T Quantize(const T& material) const = 0;
};

View File

@@ -0,0 +1,48 @@
#pragma once
#include <map>
#include <vector>
#include "BaseQuantizer.h"
#include "ColorQuantizer/BaseColorQuantizer.h"
#include "../ColorAndNormal.h"
#include "../ColorAndOpacity.h"
#include "../ColorAndNormalAndValue.h"
#include "../../../core/CollectionHelper.h"
// Basically quantizes the colors using the given color quantizer and leaves the values as-is (assuming they are quantized enough already)
// Deleting this object also deletes the associated color quantizer
template<typename T, typename TCompare>
class ColorAndValueQuantizer : public BaseQuantizer<ColorAndValue<T>, ColorAndValueCompare<T, TCompare>>
{
private:
BaseColorQuantizer* mColorQuantizer;
public:
ColorAndValueQuantizer(BaseColorQuantizer* colorQuantizer)
: mColorQuantizer(colorQuantizer)
{}
~ColorAndValueQuantizer()
{
delete mColorQuantizer;
}
virtual std::map<ColorAndValue<T>, ColorAndValue<T>, ColorAndValueCompare<T, TCompare>>* QuantizeMaterials(std::vector<ColorAndValue<T>> materials) const
{
std::vector<Color> colors(materials.size());
for (size_t i = 0; i < materials.size(); i++) colors[i] = materials[i].GetFirst();
CollectionHelper::Unique(colors, ColorCompare());
auto quantizedColors = mColorQuantizer->QuantizeMaterials(colors);
std::map<ColorAndValue<T>, ColorAndValue<T>, ColorAndValueCompare<T, TCompare>>* res = new std::map<ColorAndValue<T>, ColorAndValue<T>, ColorAndValueCompare<T, TCompare>>();
for (auto mat : materials)
{
ColorAndValue<T> replacer(quantizedColors->at(mat.GetFirst()), mat.GetSecond());
res->insert(std::make_pair(mat, replacer));
}
delete quantizedColors;
return res;
}
virtual std::string GetQuantizerDescriptor() const { return mColorQuantizer->GetQuantizerDescriptor(); }
};
typedef ColorAndValueQuantizer<ColorAndNormal, ColorAndNormalCompare> ColorAndNormalQuantizer;
typedef ColorAndValueQuantizer<ColorAndOpacity, ColorAndOpacityCompare> ColorAndOpacityQuantizer;
typedef ColorAndValueQuantizer<ColorAndNormalAndValue, ColorAndNormalAndValueCompare> ColorAndNormalAndValueQuantizer;

View File

@@ -0,0 +1,53 @@
#include "BaseColorQuantizer.h"
#include "../../../../inc/tbb/parallel_for.h"
std::map<Color, Color, ColorCompare>* BaseColorQuantizer::QuantizeMaterials(std::vector<Color> materials) const
{
// Convert the "Colors" to u8vec3
std::vector<glm::u8vec3> colors(materials.size());
tbb::parallel_for(size_t(0), materials.size(), [&](size_t i)
{
colors[i] = materials[i].GetColor();
});
// Quantize the colors
auto quantizedColors = QuantizeColors(colors);
//PrintQuantizationStatistics(quantizedColors);
// Find out what the quantized materials are
std::map<Color, Color, ColorCompare>* quantizedMaterials = new std::map<Color, Color, ColorCompare>();
for (auto quantizedColor : *quantizedColors)
{
quantizedMaterials->insert(std::pair<Color, Color>(Color(quantizedColor.first), Color(quantizedColor.second)));
glm::vec3 error = glm::abs(glm::vec3(quantizedColor.first) - glm::vec3(quantizedColor.second));
}
delete quantizedColors;
return quantizedMaterials;
}
void BaseColorQuantizer::PrintQuantizationStatistics(std::map<glm::u8vec3, glm::u8vec3, u8vec3comparer>* quantizedColors) const
{
glm::vec3 sumError(0);
unsigned maxError = 0;
glm::uvec3 maxErrorValue(0);
float sumDeltaE = 0;
float maxDeltaE = 0;
for (auto quantizedColor : *quantizedColors)
{
glm::vec3 error = glm::abs(glm::vec3(quantizedColor.first) - glm::vec3(quantizedColor.second));
sumError += error;
unsigned errorU = error.r + error.g + error.b;
if (errorU > maxError)
{
maxError = errorU;
maxErrorValue = error;
}
float deltaE = ColorHelper::GetDeltaEFromRGB(quantizedColor.first, quantizedColor.second);
if (deltaE == deltaE) // Only sum if it is not NaN...
sumDeltaE += deltaE;
if (deltaE > maxDeltaE) maxDeltaE = deltaE;
}
glm::vec3 meanError = sumError / float(quantizedColors->size());
float meanDeltaE = sumDeltaE / float(quantizedColors->size());
printf("Mean errors: (%f, %f, %f), Max errors: (%u, %u, %u), Mean delta-E: %f, Max delta-E: %f\n", meanError.x, meanError.y, meanError.z, maxErrorValue.x, maxErrorValue.y, maxErrorValue.z, meanDeltaE, maxDeltaE);
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include "../BaseQuantizer.h"
#include "../../Color.h"
class BaseColorQuantizer : public BaseQuantizer<Color, ColorCompare>
{
public:
virtual ~BaseColorQuantizer() {}
void PrintQuantizationStatistics(std::map<glm::u8vec3, glm::u8vec3, u8vec3comparer>* quantizedColors) const;
std::map<Color, Color, ColorCompare>* QuantizeMaterials(std::vector<Color> materials) const override;
virtual std::map<glm::u8vec3, glm::u8vec3, u8vec3comparer>* QuantizeColors(std::vector<glm::u8vec3> colors) const = 0;
};

View File

@@ -0,0 +1,28 @@
#include "ColorBitCutter.h"
ColorBitCutter::ColorBitCutter(unsigned8 bitCount)
{
assert(bitCount <= 8);
mBitCount = bitCount;
}
ColorBitCutter::~ColorBitCutter() {}
std::map<glm::u8vec3, glm::u8vec3, u8vec3comparer>* ColorBitCutter::QuantizeColors(std::vector<glm::u8vec3> colors) const
{
std::map<glm::u8vec3, glm::u8vec3, u8vec3comparer>* res = new std::map<glm::u8vec3, glm::u8vec3, u8vec3comparer>();
unsigned8 mask = BitHelper::GetHSMask<unsigned8>(0, mBitCount);
for (glm::u8vec3 color : colors)
{
glm::u8vec3 replacementColor = glm::u8vec3(
color.x & mask,
color.y & mask,
color.z & mask);
res->insert(std::pair<glm::u8vec3, glm::u8vec3>(color, replacementColor));
}
return res;
}
std::string ColorBitCutter::GetQuantizerDescriptor() const
{
return "b" + std::to_string(mBitCount);
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include <map>
#include "../../../../inc/glm/common.hpp"
#include "../../../../core/Comparers.h"
#include "BaseColorQuantizer.h"
class ColorBitCutter : public BaseColorQuantizer
{
public:
ColorBitCutter(unsigned8 bitCount);
~ColorBitCutter() override;
std::map<glm::u8vec3, glm::u8vec3, u8vec3comparer>* QuantizeColors(std::vector<glm::u8vec3> colors) const override;
std::string GetQuantizerDescriptor() const override;
private:
unsigned8 mBitCount;
};

View File

@@ -0,0 +1,96 @@
#include "MaxErrorClusterer.h"
#include <vector>
#include <string>
#include <forward_list>
#include "../../../../inc/glm/geometric.hpp"
// Quantize the colors in CIELAB-space clusters that have a maximum size maxDistance.
MaxErrorClusterer::MaxErrorClusterer(float maxDistance)
{
mMaxDistance = maxDistance;
}
MaxErrorClusterer::~MaxErrorClusterer() { }
std::map<glm::u8vec3, glm::u8vec3, u8vec3comparer>* MaxErrorClusterer::QuantizeColors(std::vector<glm::u8vec3> colors) const
{
// Transform all colors to CIELAB space:
ListNode<Color>* firstUnclusteredColor = new ListNode<Color>();
ListNode<Color>* cur = firstUnclusteredColor;
for (auto rgb : colors)
{
Color color(rgb);
cur->color = color;
cur->next = new ListNode<Color>();
cur = cur->next;
}
auto res = new std::map<glm::u8vec3, glm::u8vec3, u8vec3comparer>();
while (firstUnclusteredColor != NULL)
{
Color curColor = firstUnclusteredColor->color;
// Pop the head of the "list"
{
auto nextUnclusteredColor = firstUnclusteredColor->next;
delete firstUnclusteredColor;
firstUnclusteredColor = nextUnclusteredColor;
}
// Create a new cluster for this unclustered color
auto cluster = std::vector<Color>();
cluster.push_back(curColor);
// Loop through the unclustered colors
if (firstUnclusteredColor != NULL)
{
cur = firstUnclusteredColor;
ListNode<Color>* last = NULL;
ListNode<Color>* next = cur->next;
while (cur != NULL)
{
next = cur->next;
if (ColorHelper::GetDeltaEFromLAB(cur->color.lab, curColor.lab) <= mMaxDistance)
{
// If we are close enough to the last color, add the current color to the cluster
cluster.push_back(cur->color);
// Delete the current node from the list of unclustered nodes
delete cur;
// If this isn't the first node in the list, make sure the last node points to this node
if (last != NULL)
{
last->next = next;
cur = last;
}
else
{ // If this is the first node, we just deleted it, so replace the current first node by the next node
firstUnclusteredColor = next;
}
}
else
{
// If this node wasn't add to the cluster, the last node should be updated before advancing
last = cur;
}
// Advance one position
cur = next;
}
}
// Calculate the center of the current cluster
glm::vec3 clusterSum(0);
glm::u8vec3 clusterReplacementColor = curColor.rgb;
for (auto color : cluster)
res->insert(std::pair<glm::u8vec3, glm::u8vec3>(color.rgb, clusterReplacementColor));
}
return res;
}
std::string MaxErrorClusterer::GetQuantizerDescriptor() const
{
char str[7];
sprintf(str, "%.2f", mMaxDistance);
auto formattedMaxDistance = std::string(str);
return "de" + formattedMaxDistance;
}

View File

@@ -0,0 +1,43 @@
#pragma once
#include "BaseColorQuantizer.h"
#include "../../../../core/ColorHelper.h"
class MaxErrorClusterer :
public BaseColorQuantizer
{
public:
// Quantize the colors in CIELAB-space clusters that have a maximum size maxDistance.
// This is done using a greedy clustering algorithm that adds all colors to the cluster of a color if they're less than half the distance away.
MaxErrorClusterer(float maxDistance);
~MaxErrorClusterer() override;
std::map<glm::u8vec3, glm::u8vec3, u8vec3comparer>* QuantizeColors(std::vector<glm::u8vec3> colors) const override;
std::string GetQuantizerDescriptor() const override;
protected:
float mMaxDistance;
private:
template<typename T>
struct ListNode
{
public:
T color;
ListNode<T>* next = NULL;
ListNode(T value) { color = value; }
ListNode() { }
};
struct Color
{
glm::u8vec3 rgb;
glm::vec3 lab;
Color(glm::u8vec3 rgb)
{
this->rgb = rgb;
lab = ColorHelper::RGBtoLAB(rgb);
}
Color() : Color(glm::u8vec3(0)) {}
};
};

View File

@@ -0,0 +1,20 @@
#include "XiangCIELABClusterer.h"
#include "../../../../core/ColorHelper.h"
#include <string>
XiangCIELABClusterer::XiangCIELABClusterer(unsigned quantizedColorCount) : XiangClusterer(quantizedColorCount) {}
XiangCIELABClusterer::~XiangCIELABClusterer() {}
glm::vec3 XiangCIELABClusterer::ScaleColor(glm::u8vec3 color) const
{
return ColorHelper::RGBtoLAB(color);
}
glm::u8vec3 XiangCIELABClusterer::ScaleBackColor(glm::vec3 color) const
{
return ColorHelper::LABtoRGB(color);
}
std::string XiangCIELABClusterer::GetQuantizerDescriptor() const
{
return "lab" + std::to_string(mQuantizedColorCount);
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include "XiangClusterer.h"
class XiangCIELABClusterer :
public XiangClusterer
{
public:
// Quantize the colors to the given amount of colors, using the default RGB scaling, 0.5:1.0:0.25.
XiangCIELABClusterer(unsigned quantizedColorCount);
virtual ~XiangCIELABClusterer() override;
std::string GetQuantizerDescriptor() const override;
protected:
glm::vec3 ScaleColor(glm::u8vec3 color) const override;
glm::u8vec3 ScaleBackColor(glm::vec3 color) const override;
};

View File

@@ -0,0 +1,104 @@
#include "XiangClusterer.h"
#include <vector>
#include <string>
#include "../../../../inc/glm/geometric.hpp"
XiangClusterer::XiangClusterer(unsigned quantizedColorCount, glm::vec3 colorScale)
{
mQuantizedColorCount = quantizedColorCount;
mColorScale = colorScale;
mInvColorScale = 1.0f / mColorScale;
}
XiangClusterer::XiangClusterer(unsigned quantizedColorCount) : XiangClusterer(quantizedColorCount, glm::vec3(0.5, 1.0, 0.25)) {}
XiangClusterer::~XiangClusterer()
{
}
std::map<glm::u8vec3, glm::u8vec3, u8vec3comparer>* XiangClusterer::QuantizeColors(std::vector<glm::u8vec3> colors) const
{
// Set the initial cluster head to the first color in the set
std::vector<glm::vec3> h(mQuantizedColorCount);
h[0] = *colors.begin();
// Initialize all colors to be in the same cluster (cluster 0)
std::vector<ClusterColor*> B(colors.size());
int i = 0;
for (auto color : colors)
{
ClusterColor* cur = new ClusterColor();
cur->originalColor = color;
cur->color = ScaleColor(color);
cur->centerDistance = glm::distance(glm::vec3(color), h[0]);
cur->clusterID = 0;
B[i++] = cur;
}
ClusterColor* max = B[0];
// Start the algorithm: in each iteration create a new cluster
for (unsigned x = 1; x < mQuantizedColorCount; x++)
{
// Find the point with the maximum distance to the cluster center
for (auto color : B)
if (color->centerDistance > max->centerDistance)
max = color;
// Define this point as the center of a new cluster
max->clusterID = x;
h[x] = max->color;
// Move all colors that are closer to this new cluster than to their current cluster center over to the new cluster.
for (auto color : B)
{
float newDistance = glm::distance(color->color, h[x]);
if (newDistance < color->centerDistance)
{
color->clusterID = x;
color->centerDistance = newDistance;
}
}
}
// Find all cluster centers:
std::vector<glm::vec3> clusterSums(mQuantizedColorCount);
std::vector<float> invClusterCounts(mQuantizedColorCount);
for (auto color : B)
{
clusterSums[color->clusterID] += color->color;
invClusterCounts[color->clusterID]++;
}
// Inverse the cluster counts:
for (size_t i = 0; i < invClusterCounts.size(); i++) invClusterCounts[i] = 1.0f / invClusterCounts[i];
// Calculate the color replacers
std::vector<glm::u8vec3> clusterReplacers(mQuantizedColorCount);
for (unsigned i = 0; i < mQuantizedColorCount; i++)
clusterReplacers[i] = ScaleBackColor(clusterSums[i] * invClusterCounts[i]);
// Now that we have the clusters, build the replacement map
auto replacements = new std::map<glm::u8vec3, glm::u8vec3, u8vec3comparer>();
for (auto color : B)
replacements->insert(std::pair<glm::u8vec3, glm::u8vec3>(color->originalColor, clusterReplacers[color->clusterID]));
for (auto color : B)
delete color;
return replacements;
}
glm::vec3 XiangClusterer::ScaleColor(glm::u8vec3 color) const
{
return glm::vec3(color) * mColorScale;
}
glm::u8vec3 XiangClusterer::ScaleBackColor(glm::vec3 color) const
{
return glm::u8vec3(glm::round(color * mInvColorScale));
}
std::string XiangClusterer::GetQuantizerDescriptor() const
{
return std::to_string(mQuantizedColorCount);
}

View File

@@ -0,0 +1,32 @@
#pragma once
#include "BaseColorQuantizer.h"
class XiangClusterer :
public BaseColorQuantizer
{
public:
XiangClusterer(unsigned quantizedColorCount, glm::vec3 colorScale);
// Quantize the colors to the given amount of colors, using the default RGB scaling, 0.5:1.0:0.25.
XiangClusterer(unsigned quantizedColorCount);
~XiangClusterer() override;
std::map<glm::u8vec3, glm::u8vec3, u8vec3comparer>* QuantizeColors(std::vector<glm::u8vec3> colors) const override;
std::string GetQuantizerDescriptor() const override;
protected:
virtual glm::vec3 ScaleColor(glm::u8vec3 color) const;
virtual glm::u8vec3 ScaleBackColor(glm::vec3 color) const;
unsigned mQuantizedColorCount;
private:
glm::vec3 mColorScale;
glm::vec3 mInvColorScale;
struct ClusterColor
{
public:
glm::u8vec3 originalColor;
glm::vec3 color;
float centerDistance;
int clusterID;
};
};

View File

@@ -0,0 +1,52 @@
#pragma once
#include <map>
#include <vector>
#include "NormalQuantizer.h"
#include "../../../core/BitHelper.h"
NormalQuantizer::NormalQuantizer(unsigned8 bits): mBits(bits)
{
// Bits must be an even number, as there are two channels for an ONV
assert(bits % 2 == 0);
// Precalculate the channel mask
unsigned32 channelStartHS = 32 - SmallNormal::BITS / 2;
mChannelMask = BitHelper::GetHSMask<unsigned32>(channelStartHS, channelStartHS + mBits / 2);
}
std::map<SmallNormal, SmallNormal, NormalCompare>* NormalQuantizer::QuantizeMaterials(std::vector<SmallNormal> normals) const
{
std::map<SmallNormal, SmallNormal, NormalCompare>* res = new std::map<SmallNormal, SmallNormal, NormalCompare>();
for (const SmallNormal& normal : normals)
{
res->insert(std::make_pair(normal, Quantize(normal)));
}
if (mBits <= QUANTIZE_ALL_UP_TO)
{
unsigned32 maxPerChannel = 1 << (mBits / 2);
unsigned8 shift = (SmallNormal::BITS - mBits) / 2;
for (unsigned32 x = 0; x < maxPerChannel; x++)
for (unsigned32 y = 0; y < maxPerChannel; y++)
{
SmallNormal v;
v.SetXComponent(x << shift);
v.SetYComponent(y << shift);
res->insert(std::make_pair(v, v));
}
}
return res;
}
SmallNormal NormalQuantizer::Quantize(const SmallNormal& normal) const
{
SmallNormal quantized;
quantized.SetXComponent(normal.GetXComponent() & mChannelMask);
quantized.SetYComponent(normal.GetYComponent() & mChannelMask);
return quantized;
}
std::string NormalQuantizer::GetQuantizerDescriptor() const
{
return std::to_string(mBits);
}

View File

@@ -0,0 +1,20 @@
#pragma once
#include <map>
#include <vector>
#include "../SmallNormal.h"
#include "BaseQuantizer.h"
// Super simple quantizer: takes the original value, sets some bits to zero so that only the given number of bits remain
class NormalQuantizer : public BaseQuantizer<SmallNormal, NormalCompare>, public QuickQuantizer<SmallNormal>
{
public:
NormalQuantizer(unsigned8 bits);
std::map<SmallNormal, SmallNormal, NormalCompare>* QuantizeMaterials(std::vector<SmallNormal> materials) const override;
std::string GetQuantizerDescriptor() const override;
SmallNormal Quantize(const SmallNormal& normal) const override;
private:
// if the number of bits is smaller than or equal to this number, all possible quantization values will be added.
static const unsigned8 QUANTIZE_ALL_UP_TO = 14;
unsigned8 mBits;
unsigned32 mChannelMask;
};

View File

@@ -0,0 +1,38 @@
#pragma once
#include <vector>
#include <assert.h>
#include "../../core/CollectionHelper.h"
#include "../../inc/tbb/parallel_for.h"
template<typename T>
struct NearestFinder
{
T operator()(const T& source, const std::vector<T>& values) const
{
assert(!values.empty());
float min = T::Distance(source, values[0]);
size_t minIndex = 0;
for (size_t i = 0; i < values.size(); i++)
{
float distance = T::Distance(source, values[i]);
if (distance < min)
{
min = distance;
minIndex = i;
}
}
return values[minIndex];
}
};
template<typename T>
struct ParallelNearestFinder
{
T operator()(const T& source, const std::vector<T>& values) const
{
assert(!values.empty());
std::vector<float> distances(values.size());
tbb::parallel_for(size_t(0), values.size(), [&](size_t i) { distances[i] = T::Distance(source, values[i]); });
return values[CollectionHelper::MinIndex(distances)];
}
};

View File

@@ -0,0 +1,88 @@
#pragma once
#include "../../core/Defines.h"
#include <functional>
#include <vector>
#include "../../core/BitHelper.h"
// Sort of like signed int, but the sign is stored in the first bit of the value, instead of storing it as 0xFFFFFFFF
struct SignedIntMaterial
{
private:
const static unsigned VALUE_MASK = 0x7FFFFFFF;
const static unsigned SIGN_MASK = 0x80000000;
unsigned mValue;
inline unsigned GetValue() const { return mValue & VALUE_MASK; }
inline void SetValue(unsigned value)
{
mValue &= ~VALUE_MASK;
mValue |= value & VALUE_MASK;
}
inline bool IsNegative() const { return (mValue & SIGN_MASK) != 0; }
inline void SetIsNegative(bool isNegative)
{
mValue &= ~SIGN_MASK;
if (isNegative) mValue |= SIGN_MASK;
}
public:
SignedIntMaterial() : mValue(0) {}
SignedIntMaterial(unsigned value, bool negative)
{
SetValue(value);
SetIsNegative(negative);
}
SignedIntMaterial(int value) : SignedIntMaterial(abs(value), value < 0) {}
~SignedIntMaterial() {}
SignedIntMaterial& operator=(const int& source)
{
mValue = source;
return *this;
}
std::vector<unsigned8> Serialize()
{
return BitHelper::SplitInBytes(this->operator unsigned());
}
operator unsigned() const
{
return (unsigned)mValue;
}
operator int() const
{
return (int)GetValue() * (IsNegative() ? -1 : 1);
}
bool operator==(const SignedIntMaterial& other) const
{
return mValue == other.mValue;
}
bool operator!=(const SignedIntMaterial& other) const
{
return !(*this == other);
}
bool operator<(const SignedIntMaterial& other) const
{
return this->operator int() < (int)other;
}
};
// Perfect hash
namespace std
{
template<>
struct hash<SignedIntMaterial>
{
size_t operator()(SignedIntMaterial const &value) const
{
return (unsigned)value;
}
};
}

View File

@@ -0,0 +1,185 @@
#pragma once
#include "BaseMaterial.h"
#include "../../inc/glm/glm.hpp"
#include "../../core/Hashers.h"
#include "../../core/Comparers.h"
#include "../../core/CollectionHelper.h"
#include "../../core/Serializer.h"
#include "../../core/BitHelper.h"
// 8 bit normal representation using octahedron normal vectors
// https://knarkowicz.wordpress.com/2014/04/16/octahedron-normal-vector-encoding/
// Technique proposed in:
// Meyer, Q., Süßmuth, J., Sußner, G., Stamminger, M., & Greiner, G. (2010, June).
// On Floating Point Normal Vectors.
// In Computer Graphics Forum (Vol. 29, No. 4, pp. 1405-1409). Blackwell Publishing Ltd.
class SmallNormal
{
public:
static const unsigned8 BITS = 12;
static const unsigned8 BYTES = BITS / 8 + (((BITS % 8) == 0) ? 0 : 1);
static const unsigned8 CHANNELSPERPIXEL = BYTES;
static const unsigned32 SINGLECOORDMASK = ((1 << (BITS / 2)) - 1);
static const unsigned32 MAXCOORDVALUE = SINGLECOORDMASK;
private:
unsigned8 mData[BYTES];
inline unsigned32 normal() const
{
unsigned32 res = 0;
BitHelper::JoinBytes(mData, res, 0, BYTES);
return res;
}
inline void normal(unsigned32 value)
{
BitHelper::SplitInBytesAndMove(value, mData, 0, BYTES);
}
inline static float DiscrMult() { return (float)MAXCOORDVALUE; }
// Wraps a vector between [-1, 1]
glm::vec2 Wrap(glm::vec2 v) const
{
return (1.0f - glm::abs(glm::vec2(v.y, v.x))) * (glm::vec2(v.x >= 0 ? 1 : -1, v.y >= 0 ? 1 : -1));
}
public:
SmallNormal() { normal(0); }
SmallNormal(glm::vec3 normal)
{
Set(normal);
}
// Returns the x component of the octahedral normal vector as an unsigned integer between 0 and MAXCOORDVALUE
unsigned32 GetXComponent() const { return normal() >> (BITS >> 1); }
// Returns the y component of the octahedral normal vector as an unsigned integer between 0 and MAXCOORDVALUE
unsigned32 GetYComponent() const { return normal() & SINGLECOORDMASK; }
// Sets the x component of the octahedral normal vector as an unsigned integer. Should be between 0 and MAXCOORDVALUE
void SetXComponent(unsigned32 value) { assert(value <= MAXCOORDVALUE); unsigned32 mNormal = normal(); mNormal &= SINGLECOORDMASK; mNormal |= value << (BITS / 2); normal(mNormal); }
// Sets the y component of the octahedral normal vector as an unsigned integer. Should be between 0 and MAXCOORDVALUE
void SetYComponent(unsigned32 value) { assert(value <= MAXCOORDVALUE); unsigned32 mNormal = normal(); mNormal &= ~SINGLECOORDMASK; mNormal |= value & SINGLECOORDMASK; normal(mNormal); }
void Set(glm::vec3 n)
{
n /= (abs(n.x) + abs(n.y) + abs(n.z));
glm::vec2 res(n.x, n.y);
if (n.z < 0)
res = Wrap(res);
res = res * 0.5f + 0.5f;
SetXComponent((unsigned32)(res.x * DiscrMult()));
SetYComponent((unsigned32)(res.y * DiscrMult()));
//if (n.x == 0 && n.y == 0 && n.z == 0) mNormal = 0;
//else
//{
// n = glm::normalize(n);
// // Calculate spherical coordinates, normalized between 0 and 1
// glm::vec2 sphericalCoords = ((glm::vec2(atan2(n.y, n.x) / mPi, n.z) + 1.0f) * 0.5f);
// // Store them using 4 bits per channel
// SetXComponent((unsigned8)(sphericalCoords.x * 16.0f));
// SetYComponent((unsigned8)(sphericalCoords.y * 16.0f));
//}
}
glm::vec3 Get() const
{
glm::vec2 enc(((float)GetXComponent()) / DiscrMult(), ((float)GetYComponent()) / DiscrMult());
enc = enc * 2.0f - 1.0f;
glm::vec3 n(enc.x, enc.y, 1.0 - abs(enc.x) - abs(enc.y));
if (n.z < 0)
{
glm::vec2 wrapped = Wrap(enc);
n.x = wrapped.x;
n.y = wrapped.y;
}
return glm::normalize(n);
//// Extract the spherical coordinates
//glm::vec2 sphericalCoords(((float)GetXComponent()) / 16.0f, ((float)GetYComponent()) / 16.0f);
//// Normalize back
//sphericalCoords = (sphericalCoords * 2.0f) - 1.0f;
//glm::vec2 scth(sin(sphericalCoords.x), cos(sphericalCoords.y));
//glm::vec2 scphi(sqrt(1.0 - sphericalCoords.y*sphericalCoords.y), sphericalCoords.y);
//return glm::normalize(glm::vec3(scth.y * scphi.x, scth.x * scphi.x, scphi.y));
}
unsigned32 GetValue() const { return normal(); }
void SetValue(unsigned32 value) { normal(value); }
glm::u16vec3 GetProperties() const { return glm::u16vec3(0, 0, normal()); }
void SetProperties(glm::u16vec3 props) { normal(props.z); }
static SmallNormal Average(const std::vector<SmallNormal>& normals)
{
glm::vec3 sum(0);
for (SmallNormal n : normals)
sum += n.Get();
if (sum.x == 0 && sum.y == 0 && sum.z == 0) return SmallNormal();
return SmallNormal(glm::normalize(sum / (float)normals.size()));
}
static SmallNormal WeightedAverage(const std::vector<SmallNormal>& normals, const std::vector<float>& weights)
{
glm::vec3 sum(0);
for (size_t i = 0; i < normals.size(); i++)
sum += normals[i].Get() * weights[i];
if (sum.x == 0 && sum.y == 0 && sum.z == 0) return SmallNormal();
return SmallNormal(glm::normalize(sum / CollectionHelper::Sum(weights)));
}
static float Distance(const SmallNormal a, const SmallNormal b)
{
return glm::distance(a.Get(), b.Get()) * 0.5f;
}
std::string GetTypeSuffix() const { return "n"; }
std::vector<unsigned8> Serialize() const
{
std::vector<unsigned8> res(BYTES);
for (unsigned8 i = 0; i < BYTES; i++) res[i] = mData[i];
return res;
}
void Deserialize(const std::vector<unsigned8>& value) {
assert(value.size() == BYTES);
for (unsigned8 i = 0; i < BYTES; i++) mData[i] = value[i];
}
void Serialize(std::ostream& stream) const { Serializer<unsigned8*>::Serialize(&mData[0], BYTES, stream); }
void Deserialize(std::istream& stream) { Serializer<unsigned8*>::Deserialize(&mData[0], BYTES, stream); }
bool operator==(const SmallNormal& n) const { return n.normal() == normal(); }
bool operator!=(const SmallNormal& n) const { return !(n == *this); }
// Assignment operator, used for easy access to different kinds of materials
SmallNormal& operator=(const unsigned& source) { normal(source); }
operator unsigned() const { return normal(); }
static std::vector<SmallNormal> GetAll()
{
std::vector<SmallNormal> all(((size_t)1) << BITS);
for (unsigned32 i = 0; i < (unsigned32)all.size(); i++)
{
SmallNormal n;
n.SetValue(i);
all[i] = n;
}
return all;
}
};
namespace std {
template<>
struct hash<SmallNormal> {
size_t operator()(const SmallNormal &value) const {
return value.GetValue();
}
};
}
struct NormalCompare
{
bool operator()(const SmallNormal& n1, const SmallNormal& n2) const
{
return n1.GetValue() < n2.GetValue();
}
};