2022-04-19 01:14:40 +01:00

267 lines
9.3 KiB
C++

#ifndef VOXEL_STORAGE_FUNCS_H
#define VOXEL_STORAGE_FUNCS_H
#include "../constants/voxel_constants.h"
#include "../util/math/vector3i.h"
#include "../util/span.h"
#include <stdint.h>
namespace zylann::voxel {
inline void clip_copy_region_coord(int &src_min, int &src_max, const int src_size, int &dst_min, const int dst_size) {
// Clamp source and shrink destination for moved borders
if (src_min < 0) {
dst_min += -src_min;
src_min = 0;
}
if (src_max > src_size) {
src_max = src_size;
}
// Clamp destination and shrink source for moved borders
if (dst_min < 0) {
src_min += -dst_min;
dst_min = 0;
}
const int dst_w = src_max - src_min;
const int dst_max = dst_min + dst_w;
if (dst_max > dst_size) {
src_max -= dst_max - dst_size;
}
// It is possible the source has negative size at this point, which means there is nothing to copy.
// This must be checked by the caller.
}
// Clips coordinates that may be used to copy a sub-region of a 3D container into another 3D container.
// The result can have zero or negative size, so it must be checked before proceeding.
inline void clip_copy_region(
Vector3i &src_min, Vector3i &src_max, const Vector3i &src_size, Vector3i &dst_min, const Vector3i &dst_size) {
clip_copy_region_coord(src_min.x, src_max.x, src_size.x, dst_min.x, dst_size.x);
clip_copy_region_coord(src_min.y, src_max.y, src_size.y, dst_min.y, dst_size.y);
clip_copy_region_coord(src_min.z, src_max.z, src_size.z, dst_min.z, dst_size.z);
}
void copy_3d_region_zxy(Span<uint8_t> dst, Vector3i dst_size, Vector3i dst_min, Span<const uint8_t> src,
Vector3i src_size, Vector3i src_min, Vector3i src_max, size_t item_size);
template <typename T>
inline void copy_3d_region_zxy(Span<T> dst, Vector3i dst_size, Vector3i dst_min, Span<const T> src, Vector3i src_size,
Vector3i src_min, Vector3i src_max) {
// The `template` keyword before method name is required when compiling with GCC
copy_3d_region_zxy(dst.template reinterpret_cast_to<uint8_t>(), dst_size, dst_min,
src.template reinterpret_cast_to<const uint8_t>(), src_size, src_min, src_max, sizeof(T));
}
template <typename T>
void fill_3d_region_zxy(Span<T> dst, Vector3i dst_size, Vector3i dst_min, Vector3i dst_max, const T value) {
using namespace math;
Vector3iUtil::sort_min_max(dst_min, dst_max);
dst_min.x = clamp(dst_min.x, 0, dst_size.x);
dst_min.y = clamp(dst_min.y, 0, dst_size.y);
dst_min.z = clamp(dst_min.z, 0, dst_size.z);
dst_max.x = clamp(dst_max.x, 0, dst_size.x);
dst_max.y = clamp(dst_max.y, 0, dst_size.y);
dst_max.z = clamp(dst_max.z, 0, dst_size.z);
const Vector3i area_size = dst_max - dst_min;
if (area_size.x <= 0 || area_size.y <= 0 || area_size.z <= 0) {
// Degenerate area, we'll not copy anything.
return;
}
#ifdef DEBUG_ENABLED
ZN_ASSERT_RETURN(Vector3iUtil::get_volume(area_size) <= dst.size());
#endif
if (area_size == dst_size) {
for (unsigned int i = 0; i < dst.size(); ++i) {
dst[i] = value;
}
} else {
const unsigned int dst_row_offset = dst_size.y;
Vector3i pos;
for (pos.z = 0; pos.z < area_size.z; ++pos.z) {
unsigned int dst_ri = Vector3iUtil::get_zxy_index(dst_min + pos, dst_size);
for (pos.x = 0; pos.x < area_size.x; ++pos.x) {
// Fill row
for (pos.y = 0; pos.y < area_size.y; ++pos.y) {
dst[dst_ri + pos.y] = value;
}
dst_ri += dst_row_offset;
}
}
}
}
// https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#fundamentals-fixedconv
// Converts an int8 value into a float in the range [-1..1], which includes an exact value for 0.
// -128 is one value of the int8 which will not have a corresponding result, it will be clamped to -1.
inline float s8_to_snorm(int8_t v) {
return math::max(v / 127.f, -1.f);
}
inline float s8_to_snorm_noclamp(int8_t v) {
return v / 127.f;
}
// Converts a float value in the range [-1..1] to an int8.
// The float will be clamped if it lies outside of the expected range.
inline int8_t snorm_to_s8(float v) {
return math::clamp(v, -1.f, 1.f) * 127;
}
// Converts an int8 value into a float in the range [-1..1], which includes an exact value for 0.
// -32767 is one value of the int16 which will not have a corresponding result, it will be clamped to -1.
inline float s16_to_snorm(int16_t v) {
return math::max(v / 32767.f, -1.f);
}
inline float s16_to_snorm_noclamp(int16_t v) {
return v / 32767.f;
}
// Converts a float value in the range [-1..1] to an int16.
// The float will be clamped if it lies outside of the expected range.
inline int16_t snorm_to_s16(float v) {
return math::clamp(v, -1.f, 1.f) * 32767;
}
namespace legacy {
inline float u8_to_snorm(uint8_t v) {
return (static_cast<float>(v) - 0x7f) * constants::INV_0x7f;
}
inline float u16_to_snorm(uint16_t v) {
return (static_cast<float>(v) - 0x7fff) * constants::INV_0x7fff;
}
inline uint8_t snorm_to_u8(float v) {
return zylann::math::clamp(static_cast<int>(128.f * v + 128.f), 0, 0xff);
}
inline uint16_t snorm_to_u16(float v) {
return zylann::math::clamp(static_cast<int>(0x8000 * v + 0x8000), 0, 0xffff);
}
} // namespace legacy
inline FixedArray<uint8_t, 4> decode_weights_from_packed_u16(uint16_t packed_weights) {
FixedArray<uint8_t, 4> weights;
// SIMDable?
// weights[0] = ((packed_weights >> 0) & 0x0f) << 4;
// weights[1] = ((packed_weights >> 4) & 0x0f) << 4;
// weights[2] = ((packed_weights >> 8) & 0x0f) << 4;
// weights[3] = ((packed_weights >> 12) & 0x0f) << 4;
// Reduced but not SIMDable
weights[0] = (packed_weights & 0x0f) << 4;
weights[1] = packed_weights & 0xf0;
weights[2] = (packed_weights >> 4) & 0xf0;
weights[3] = (packed_weights >> 8) & 0xf0;
// The code above is such that the maximum uint8_t value for a weight is 240, not 255.
// We could add extra computations in order to match the range exactly,
// but as a compromise I'm not doing them because it would kinda break bijectivity and is slower.
// If this is a problem, then it could be an argument to switch to 8bit representation using 3 channels.
// weights[0] |= weights[0] >> 4;
// weights[1] |= weights[1] >> 4;
// weights[2] |= weights[2] >> 4;
// weights[3] |= weights[3] >> 4;
return weights;
}
inline FixedArray<uint8_t, 4> decode_indices_from_packed_u16(uint16_t packed_indices) {
FixedArray<uint8_t, 4> indices;
// SIMDable?
indices[0] = (packed_indices >> 0) & 0x0f;
indices[1] = (packed_indices >> 4) & 0x0f;
indices[2] = (packed_indices >> 8) & 0x0f;
indices[3] = (packed_indices >> 12) & 0x0f;
return indices;
}
inline uint16_t encode_indices_to_packed_u16(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
return (a & 0xf) | ((b & 0xf) << 4) | ((c & 0xf) << 8) | ((d & 0xf) << 12);
}
inline uint16_t encode_weights_to_packed_u16(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
return (a >> 4) | ((b >> 4) << 4) | ((c >> 4) << 8) | ((d >> 4) << 12);
}
// Checks if there are no duplicate indices in any voxel
inline void debug_check_texture_indices(FixedArray<uint8_t, 4> indices) {
FixedArray<bool, 16> checked;
fill(checked, false);
for (unsigned int i = 0; i < indices.size(); ++i) {
unsigned int ti = indices[i];
ZN_ASSERT(!checked[ti]);
checked[ti] = true;
}
}
struct IntBasis {
Vector3i x;
Vector3i y;
Vector3i z;
Vector3i get_axis(int i) const {
// TODO Optimization: could use a union with an array
switch (i) {
case Vector3i::AXIS_X:
return x;
case Vector3i::AXIS_Y:
return y;
case Vector3i::AXIS_Z:
return z;
default:
ZN_CRASH();
}
return Vector3i();
}
};
// Rotates/flips/transposes the contents of a 3D array using a basis.
// Returns the transformed size. Volume remains the same.
// The array's coordinate convention uses ZXY (index+1 does Y+1).
template <typename T>
Vector3i transform_3d_array_zxy(Span<const T> src_grid, Span<T> dst_grid, Vector3i src_size, IntBasis basis) {
ZN_ASSERT_RETURN_V(Vector3iUtil::is_unit_vector(basis.x), src_size);
ZN_ASSERT_RETURN_V(Vector3iUtil::is_unit_vector(basis.y), src_size);
ZN_ASSERT_RETURN_V(Vector3iUtil::is_unit_vector(basis.z), src_size);
ZN_ASSERT_RETURN_V(src_grid.size() == static_cast<size_t>(Vector3iUtil::get_volume(src_size)), src_size);
ZN_ASSERT_RETURN_V(dst_grid.size() == static_cast<size_t>(Vector3iUtil::get_volume(src_size)), src_size);
const int xa = basis.x.x != 0 ? 0 : basis.x.y != 0 ? 1 : 2;
const int ya = basis.y.x != 0 ? 0 : basis.y.y != 0 ? 1 : 2;
const int za = basis.z.x != 0 ? 0 : basis.z.y != 0 ? 1 : 2;
Vector3i dst_size;
dst_size[xa] = src_size.x;
dst_size[ya] = src_size.y;
dst_size[za] = src_size.z;
// If an axis is negative, it means iteration starts from the end
const int ox = basis.get_axis(xa).x < 0 ? dst_size.x - 1 : 0;
const int oy = basis.get_axis(ya).y < 0 ? dst_size.y - 1 : 0;
const int oz = basis.get_axis(za).z < 0 ? dst_size.z - 1 : 0;
int src_i = 0;
for (int z = 0; z < src_size.z; ++z) {
for (int x = 0; x < src_size.x; ++x) {
for (int y = 0; y < src_size.y; ++y) {
// TODO Optimization: can be moved in the outer loop, we only need to add a number to dst_i
const int dst_x = ox + x * basis.x.x + y * basis.y.x + z * basis.z.x;
const int dst_y = oy + x * basis.x.y + y * basis.y.y + z * basis.z.y;
const int dst_z = oz + x * basis.x.z + y * basis.y.z + z * basis.z.z;
const int dst_i = dst_y + dst_size.y * (dst_x + dst_size.x * dst_z);
dst_grid[dst_i] = src_grid[src_i];
++src_i;
}
}
}
return dst_size;
}
} // namespace zylann::voxel
#endif // VOXEL_STORAGE_FUNCS_H