1
0
Fork 0

Break parts in custom entities and display

windows
May B. 2020-11-10 18:17:21 +01:00
parent d7012d1554
commit 18c840b945
43 changed files with 1015 additions and 266 deletions

View File

@ -7,10 +7,11 @@ set(SIMD_LEVEL "avx2" CACHE STRING "SIMD processor acceleration (sse2, sse4.1, a
option(USE_FMA "Use fma" 1)
option(LOG_DEBUG "Show debug logs" 0)
option(LOG_TRACE "Show trace logs" 0)
option(NATIVE "Build with -march=native" 0)
find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
endif(CCACHE_FOUND)
add_subdirectory("deps/glfw")
@ -29,14 +30,17 @@ add_compile_definitions(FIXED_WINDOW=${FIXED_WINDOW} LOG_DEBUG=${LOG_DEBUG} LOG_
if(PROFILING)
add_compile_definitions(TRACY_ENABLE=1)
endif(PROFILING)
if(NATIVE)
add_definitions(-march=native)
endif()
if(SIMD_LEVEL EQUAL "avx2")
add_compile_definitions(FN_COMPILE_AVX2=1)
add_compile_definitions(FN_COMPILE_AVX2=1)
elseif(SIMD_LEVEL EQUAL "avx512f")
add_compile_definitions(FN_COMPILE_AVX512=1)
add_compile_definitions(FN_COMPILE_AVX512=1)
endif()
add_definitions(-m${SIMD_LEVEL})
if(USE_FMA)
add_definitions(-mfma)
add_definitions(-mfma)
endif(USE_FMA)
file(GLOB_RECURSE CORE_SOURCES "src/core/*.cpp" "deps/tracy/TracyClient.cpp")
@ -72,6 +76,13 @@ target_include_directories(univerxel-client PRIVATE ${CORE_HEADERS} ${CLIENT_HEA
# Resource client files + default zstd.dict
file(COPY resource/content DESTINATION ${CMAKE_BINARY_DIR})
# Serialize entity model
file(GLOB_RECURSE MODELS_SOURCES "src/client/render/api/Models.cpp")
add_executable(generate_models EXCLUDE_FROM_ALL "src/tools/generate_models.cpp" ${MODELS_SOURCES})
target_compile_features(generate_models PUBLIC cxx_std_17)
target_link_libraries(generate_models glm::glm_static)
target_include_directories(generate_models PRIVATE "deps/robin_hood" "deps/meshoptimizer")
# Docs
add_custom_target(docs
COMMAND doxygen ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile

View File

@ -101,6 +101,7 @@ CMAKE_BUILD_TYPE | Level of optimization | `Release`
PROFILING | Tracy profiling | `0`
LOG_DEBUG | Debug logs | `0`
LOG_TRACE | Trace logs | `0`
NATIVE | Optimize for native CPU | `0`
1. Build Make
```sh

View File

@ -34,7 +34,9 @@
- [x] Transparency
- [~] Entities
- [ ] Collide
- [ ] Get models
- [x] Get models
- [ ] Save in world
- [ ] Rotate
- [ ] Reduce compile unit count
- [~] Review documentation
- [ ] Clean kick
@ -81,6 +83,7 @@
- [x] QUIC protocol
- [~] 8 for 1 contouring problem
- [ ] Use in memory protocol (to replace server_handle)
- [ ] Remove SharedUniverse
- [ ] Octree
- [ ] Better Lod
- [ ] VK
@ -95,7 +98,8 @@
- https://www.youtube.com/watch?v=iikdcAA7cww
- Toon shading
- Eye adaptation
- [ ] Correct sRGB conversions
- GLSL gamma correction
- https://github.com/skurmedel/shaders/blob/master/glsl/gamma_correct.glsl
- [ ] Post processing
- https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing
- Bloom

View File

@ -6,7 +6,6 @@
// _/_____/
//
// Fast & memory efficient hashtable based on robin hood hashing for C++11/14/17/20
// version 3.8.0
// https://github.com/martinus/robin-hood-hashing
//
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
@ -36,7 +35,7 @@
// see https://semver.org/
#define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes
#define ROBIN_HOOD_VERSION_MINOR 8 // for adding functionality in a backwards-compatible manner
#define ROBIN_HOOD_VERSION_MINOR 9 // for adding functionality in a backwards-compatible manner
#define ROBIN_HOOD_VERSION_PATCH 0 // for backwards-compatible bug fixes
#include <algorithm>
@ -132,46 +131,32 @@ static Counts& counts() {
#endif
// count leading/trailing bits
#if ((defined __i386 || defined __x86_64__) && defined __BMI__) || defined _M_IX86 || defined _M_X64
#if !defined(ROBIN_HOOD_DISABLE_INTRINSICS)
# ifdef _MSC_VER
# if ROBIN_HOOD(BITNESS) == 32
# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward
# else
# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward64
# endif
# include <intrin.h>
# pragma intrinsic(ROBIN_HOOD(BITSCANFORWARD))
# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) \
[](size_t mask) noexcept -> int { \
unsigned long index; \
return ROBIN_HOOD(BITSCANFORWARD)(&index, mask) ? static_cast<int>(index) \
: ROBIN_HOOD(BITNESS); \
}(x)
# else
# include <x86intrin.h>
# if ROBIN_HOOD(BITNESS) == 32
# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzl
# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzl
# else
# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzll
# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzll
# endif
# define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) ((x) ? ROBIN_HOOD(CLZ)(x) : ROBIN_HOOD(BITNESS))
# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ((x) ? ROBIN_HOOD(CTZ)(x) : ROBIN_HOOD(BITNESS))
# endif
# if ROBIN_HOOD(BITNESS) == 32
# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() _tzcnt_u32
# else
# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() _tzcnt_u64
# endif
# if defined __AVX2__ || defined __BMI__
# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ROBIN_HOOD(CTZ)(x)
# else
# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ROBIN_HOOD(CTZ)(x)
# endif
#elif defined _MSC_VER
# if ROBIN_HOOD(BITNESS) == 32
# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward
# else
# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward64
# endif
# include <intrin.h>
# pragma intrinsic(ROBIN_HOOD(BITSCANFORWARD))
# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) \
[](size_t mask) noexcept -> int { \
unsigned long index; \
return ROBIN_HOOD(BITSCANFORWARD)(&index, mask) ? static_cast<int>(index) \
: ROBIN_HOOD(BITNESS); \
}(x)
#else
# if ROBIN_HOOD(BITNESS) == 32
# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzl
# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzl
# else
# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzll
# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzll
# endif
# define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) ((x) ? ROBIN_HOOD(CLZ)(x) : ROBIN_HOOD(BITNESS))
# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ((x) ? ROBIN_HOOD(CTZ)(x) : ROBIN_HOOD(BITNESS))
#endif
// fallthrough
@ -195,6 +180,17 @@ static Counts& counts() {
# define ROBIN_HOOD_UNLIKELY(condition) __builtin_expect(condition, 0)
#endif
// detect if native wchar_t type is availiable in MSVC
#ifdef _MSC_VER
# ifdef _NATIVE_WCHAR_T_DEFINED
# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1
# else
# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 0
# endif
#else
# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1
#endif
// workaround missing "is_trivially_copyable" in g++ < 5.0
// See https://stackoverflow.com/a/31798726/48181
#if defined(__GNUC__) && __GNUC__ < 5
@ -294,6 +290,13 @@ using index_sequence_for = make_index_sequence<sizeof...(T)>;
namespace detail {
// make sure we static_cast to the correct type for hash_int
#if ROBIN_HOOD(BITNESS) == 64
using SizeT = uint64_t;
#else
using SizeT = uint32_t;
#endif
template <typename T>
T rotr(T x, unsigned k) {
return (x >> k) | (x << (8U * sizeof(T) - k));
@ -315,14 +318,14 @@ inline T reinterpret_cast_no_cast_align_warning(void const* ptr) noexcept {
// make sure this is not inlined as it is slow and dramatically enlarges code, thus making other
// inlinings more difficult. Throws are also generally the slow path.
template <typename E, typename... Args>
ROBIN_HOOD(NOINLINE)
[[noreturn]] ROBIN_HOOD(NOINLINE)
#if ROBIN_HOOD(HAS_EXCEPTIONS)
void doThrow(Args&&... args) {
void doThrow(Args&&... args) {
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
throw E(std::forward<Args>(args)...);
}
#else
void doThrow(Args&&... ROBIN_HOOD_UNUSED(args) /*unused*/) {
void doThrow(Args&&... ROBIN_HOOD_UNUSED(args) /*unused*/) {
abort();
}
#endif
@ -388,7 +391,7 @@ public:
void reset() noexcept {
while (mListForFree) {
T* tmp = *mListForFree;
free(mListForFree);
std::free(mListForFree);
mListForFree = reinterpret_cast_no_cast_align_warning<T**>(tmp);
}
mHead = nullptr;
@ -423,7 +426,7 @@ public:
// calculate number of available elements in ptr
if (numBytes < ALIGNMENT + ALIGNED_SIZE) {
// not enough data for at least one element. Free and return.
free(ptr);
std::free(ptr);
} else {
add(ptr, numBytes);
}
@ -490,7 +493,7 @@ private:
// alloc new memory: [prev |T, T, ... T]
// std::cout << (sizeof(T*) + ALIGNED_SIZE * numElementsToAlloc) << " bytes" << std::endl;
size_t const bytes = ALIGNMENT + ALIGNED_SIZE * numElementsToAlloc;
add(assertNotNull<std::bad_alloc>(malloc(bytes)), bytes);
add(assertNotNull<std::bad_alloc>(std::malloc(bytes)), bytes);
return mHead;
}
@ -526,7 +529,7 @@ struct NodeAllocator<T, MinSize, MaxSize, true> {
// we are not using the data, so just free it.
void addOrFree(void* ptr, size_t ROBIN_HOOD_UNUSED(numBytes) /*unused*/) noexcept {
free(ptr);
std::free(ptr);
}
};
@ -670,7 +673,7 @@ inline constexpr bool operator>=(pair<A, B> const& x, pair<A, B> const& y) {
return !(x < y);
}
inline size_t hash_bytes(void const* ptr, size_t const len) noexcept {
inline size_t hash_bytes(void const* ptr, size_t len) noexcept {
static constexpr uint64_t m = UINT64_C(0xc6a4a7935bd1e995);
static constexpr uint64_t seed = UINT64_C(0xe17a1465);
static constexpr unsigned int r = 47;
@ -730,8 +733,8 @@ inline size_t hash_int(uint64_t x) noexcept {
//
// Instead of shifts, we use rotations so we don't lose any bits.
//
// Added a final multiplcation with a constant for more mixing. It is most important that the
// lower bits are well mixed.
// Added a final multiplcation with a constant for more mixing. It is most important that
// the lower bits are well mixed.
auto h1 = x * UINT64_C(0xA24BAED4963EE407);
auto h2 = detail::rotr(x, 32U) * UINT64_C(0x9FB21C651E98DF25);
auto h = detail::rotr(h1 + h2, 32U);
@ -739,14 +742,14 @@ inline size_t hash_int(uint64_t x) noexcept {
}
// A thin wrapper around std::hash, performing an additional simple mixing step of the result.
template <typename T>
template <typename T, typename Enable = void>
struct hash : public std::hash<T> {
size_t operator()(T const& obj) const
noexcept(noexcept(std::declval<std::hash<T>>().operator()(std::declval<T const&>()))) {
// call base hash
auto result = std::hash<T>::operator()(obj);
// return mixed of that, to be save against identity has
return hash_int(static_cast<uint64_t>(result));
return hash_int(static_cast<detail::SizeT>(result));
}
};
@ -769,21 +772,29 @@ struct hash<std::basic_string_view<CharT>> {
template <class T>
struct hash<T*> {
size_t operator()(T* ptr) const noexcept {
return hash_int(reinterpret_cast<size_t>(ptr));
return hash_int(reinterpret_cast<detail::SizeT>(ptr));
}
};
template <class T>
struct hash<std::unique_ptr<T>> {
size_t operator()(std::unique_ptr<T> const& ptr) const noexcept {
return hash_int(reinterpret_cast<size_t>(ptr.get()));
return hash_int(reinterpret_cast<detail::SizeT>(ptr.get()));
}
};
template <class T>
struct hash<std::shared_ptr<T>> {
size_t operator()(std::shared_ptr<T> const& ptr) const noexcept {
return hash_int(reinterpret_cast<size_t>(ptr.get()));
return hash_int(reinterpret_cast<detail::SizeT>(ptr.get()));
}
};
template <typename Enum>
struct hash<Enum, typename std::enable_if<std::is_enum<Enum>::value>::type> {
size_t operator()(Enum e) const noexcept {
using Underlying = typename std::underlying_type<Enum>::type;
return hash<Underlying>{}(static_cast<Underlying>(e));
}
};
@ -806,7 +817,9 @@ ROBIN_HOOD_HASH_INT(signed char);
ROBIN_HOOD_HASH_INT(unsigned char);
ROBIN_HOOD_HASH_INT(char16_t);
ROBIN_HOOD_HASH_INT(char32_t);
#if ROBIN_HOOD(HAS_NATIVE_WCHART)
ROBIN_HOOD_HASH_INT(wchar_t);
#endif
ROBIN_HOOD_HASH_INT(short);
ROBIN_HOOD_HASH_INT(unsigned short);
ROBIN_HOOD_HASH_INT(int);
@ -914,7 +927,8 @@ private:
static constexpr size_t InitialNumElements = sizeof(uint64_t);
static constexpr uint32_t InitialInfoNumBits = 5;
static constexpr uint8_t InitialInfoInc = 1U << InitialInfoNumBits;
static constexpr uint8_t InitialInfoHashShift = sizeof(size_t) * 8 - InitialInfoNumBits;
static constexpr size_t InfoMask = InitialInfoInc - 1U;
static constexpr uint8_t InitialInfoHashShift = 0;
using DataPool = detail::NodeAllocator<value_type, 4, 16384, IsFlat>;
// type needs to be wider than uint8_t.
@ -1247,7 +1261,7 @@ private:
Iter operator++(int) noexcept {
Iter tmp = *this;
++(*this);
return std::move(tmp);
return tmp;
}
reference operator*() const {
@ -1278,13 +1292,29 @@ private:
mInfo += sizeof(size_t);
mKeyVals += sizeof(size_t);
}
#if ROBIN_HOOD(LITTLE_ENDIAN)
auto inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8;
#if defined(ROBIN_HOOD_DISABLE_INTRINSICS)
// we know for certain that within the next 8 bytes we'll find a non-zero one.
if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load<uint32_t>(mInfo))) {
mInfo += 4;
mKeyVals += 4;
}
if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load<uint16_t>(mInfo))) {
mInfo += 2;
mKeyVals += 2;
}
if (ROBIN_HOOD_UNLIKELY(0U == *mInfo)) {
mInfo += 1;
mKeyVals += 1;
}
#else
# if ROBIN_HOOD(LITTLE_ENDIAN)
auto inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8;
# else
auto inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8;
#endif
# endif
mInfo += inc;
mKeyVals += inc;
#endif
}
friend class Table<IsFlat, MaxLoadFactor100, key_type, mapped_type, hasher, key_equal>;
@ -1306,10 +1336,11 @@ private:
typename std::conditional<std::is_same<::robin_hood::hash<key_type>, hasher>::value,
::robin_hood::detail::identity_hash<size_t>,
::robin_hood::hash<size_t>>::type;
*idx = Mix{}(WHash::operator()(key));
*info = mInfoInc + static_cast<InfoType>(*idx >> mInfoHashShift);
*idx &= mMask;
// the lower InitialInfoNumBits are reserved for info.
auto h = Mix{}(WHash::operator()(key));
*info = mInfoInc + static_cast<InfoType>((h & InfoMask) >> mInfoHashShift);
*idx = (h >> InitialInfoNumBits) & mMask;
}
// forwards the index by one, wrapping around at the end
@ -1448,13 +1479,19 @@ public:
using iterator = Iter<false>;
using const_iterator = Iter<true>;
Table() noexcept(noexcept(Hash()) && noexcept(KeyEqual()))
: WHash()
, WKeyEqual() {
ROBIN_HOOD_TRACE(this)
}
// Creates an empty hash map. Nothing is allocated yet, this happens at the first insert.
// This tremendously speeds up ctor & dtor of a map that never receives an element. The
// penalty is payed at the first insert, and not before. Lookup of this empty map works
// because everybody points to DummyInfoByte::b. parameter bucket_count is dictated by the
// standard, but we can ignore it.
explicit Table(
size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{},
size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/, const Hash& h = Hash{},
const KeyEqual& equal = KeyEqual{}) noexcept(noexcept(Hash(h)) && noexcept(KeyEqual(equal)))
: WHash(h)
, WKeyEqual(equal) {
@ -1535,7 +1572,7 @@ public:
auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1);
mKeyVals = static_cast<Node*>(detail::assertNotNull<std::bad_alloc>(
malloc(calcNumBytesTotal(numElementsWithBuffer))));
std::malloc(calcNumBytesTotal(numElementsWithBuffer))));
// no need for calloc because clonData does memcpy
mInfo = reinterpret_cast<uint8_t*>(mKeyVals + numElementsWithBuffer);
mNumElements = o.mNumElements;
@ -1583,12 +1620,12 @@ public:
// no luck: we don't have the same array size allocated, so we need to realloc.
if (0 != mMask) {
// only deallocate if we actually have data!
free(mKeyVals);
std::free(mKeyVals);
}
auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1);
mKeyVals = static_cast<Node*>(detail::assertNotNull<std::bad_alloc>(
malloc(calcNumBytesTotal(numElementsWithBuffer))));
std::malloc(calcNumBytesTotal(numElementsWithBuffer))));
// no need for calloc here because cloneData performs a memcpy.
mInfo = reinterpret_cast<uint8_t*>(mKeyVals + numElementsWithBuffer);
@ -2058,8 +2095,13 @@ private:
}
}
// don't destroy old data: put it into the pool instead
DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElementsWithBuffer));
// this check is not necessary as it's guarded by the previous if, but it helps silence
// g++'s overeager "attempt to free a non-heap object 'map'
// [-Werror=free-nonheap-object]" warning.
if (oldKeyVals != reinterpret_cast_no_cast_align_warning<Node*>(&mMask)) {
// don't destroy old data: put it into the pool instead
DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElementsWithBuffer));
}
}
}
@ -2103,7 +2145,7 @@ private:
// calloc also zeroes everything
mKeyVals = reinterpret_cast<Node*>(detail::assertNotNull<std::bad_alloc>(
calloc(1, calcNumBytesTotal(numElementsWithBuffer))));
std::calloc(1, calcNumBytesTotal(numElementsWithBuffer))));
mInfo = reinterpret_cast<uint8_t*>(mKeyVals + numElementsWithBuffer);
// set sentinel
@ -2290,7 +2332,7 @@ private:
// reports a compile error: attempt to free a non-heap object fm
// [-Werror=free-nonheap-object]
if (mKeyVals != reinterpret_cast_no_cast_align_warning<Node*>(&mMask)) {
free(mKeyVals);
std::free(mKeyVals);
}
}

View File

@ -218,5 +218,4 @@ void main() {
float ratio = exp(vs.Depth * 0.69)-1;
color = mix(color, vec4(pow(FogColor, vec3(2.2)), 1), clamp(ratio, 0, 1));
#endif
color = pow(color, vec4(vec3(1.0 / 2.2), 1));
}

View File

@ -144,44 +144,30 @@ void Client::run(server_handle* const localHandle) {
frustum = {camera.getFrustum()};
}
const auto offset = state.position.raw_as_long();
{ // Chunks
std::vector<glm::vec3> occlusion;
std::vector<glm::vec3> occlusion;
if (options.culling > 0) {
const auto ratio = options.culling * 2;
occlusion.reserve(glm::pow2(ratio * 2 - 1));
const auto [ch, cv] = player.getAngles();
const auto max_v = tan(options.camera.fov / 2.), max_h = Window::RATIO * max_v;
for(int iv = -ratio + 1; iv < ratio; iv++) {
const auto v = cv + max_v * iv / ratio;
for(int ih = -ratio + 1; ih < ratio; ih++) {
const auto h = ch + max_h * ih / ratio;
occlusion.emplace_back(cos(v) * sin(h), sin(v), cos(v) * cos(h));
}
}
}
{ // Solid
const auto pass = pipeline->beginWorldPass(true);
const auto draw = [&](glm::mat4 model, render::LodModel *const buffer, const contouring::Abstract::area_info &area, const voxel_pos &pos) {
reports.models_count++;
reports.tris_count += pass(buffer, model, glm::vec4(pos, std::get<1>(area)), std::get<2>(area));
};
if (options.culling > 0) {
const auto ratio = options.culling * 2;
occlusion.reserve(glm::pow2(ratio * 2 - 1));
const auto [ch, cv] = player.getAngles();
const auto max_v = tan(options.camera.fov / 2.), max_h = Window::RATIO * max_v;
for(int iv = -ratio + 1; iv < ratio; iv++) {
const auto v = cv + max_v * iv / ratio;
for(int ih = -ratio + 1; ih < ratio; ih++) {
const auto h = ch + max_h * ih / ratio;
occlusion.emplace_back(cos(v) * sin(h), sin(v), cos(v) * cos(h));
}
}
}
{ // Solid
const auto pass = pipeline->beginWorldPass(true);
const auto draw = [&](glm::mat4 model, render::LodModel *const buffer, const contouring::Abstract::area_info &area, const voxel_pos &pos) {
reports.models_count++;
reports.tris_count += pass(buffer, model, glm::vec4(pos, std::get<1>(area)), std::get<2>(area));
};
if (options.culling > 0) {
state.contouring->getModels(draw, player.position, options.camera.far, occlusion, offset, options.voxel_density, true);
} else {
state.contouring->getModels(draw, frustum, offset, options.voxel_density, true);
}
}
if (options.renderer.voxel.transparency) {
const auto pass = pipeline->beginWorldPass(false);
const auto draw = [&](glm::mat4 model, render::LodModel *const buffer, const contouring::Abstract::area_info &area, const voxel_pos &pos) {
reports.models_count++;
reports.tris_count += pass(buffer, model, glm::vec4(pos, std::get<1>(area)), std::get<2>(area));
};
if (options.culling > 0) {
state.contouring->getModels(draw, player.position, options.camera.far, occlusion, offset, options.voxel_density, false);
} else {
state.contouring->getModels(draw, frustum, offset, options.voxel_density, false);
}
state.contouring->getModels(draw, player.position, options.camera.far, occlusion, offset, options.voxel_density, true);
} else {
state.contouring->getModels(draw, frustum, offset, options.voxel_density, true);
}
}
{ // Entities
@ -203,6 +189,18 @@ void Client::run(server_handle* const localHandle) {
reports.tris_count += pass(model, options.editor.tool.shape, color);
}
}
if (options.renderer.voxel.transparency) {
const auto pass = pipeline->beginWorldPass(false);
const auto draw = [&](glm::mat4 model, render::LodModel *const buffer, const contouring::Abstract::area_info &area, const voxel_pos &pos) {
reports.models_count++;
reports.tris_count += pass(buffer, model, glm::vec4(pos, std::get<1>(area)), std::get<2>(area));
};
if (options.culling > 0) {
state.contouring->getModels(draw, player.position, options.camera.far, occlusion, offset, options.voxel_density, false);
} else {
state.contouring->getModels(draw, frustum, offset, options.voxel_density, false);
}
}
pipeline->postProcess();
render::UI::Get()->render();
pipeline->endFrame();

View File

@ -31,6 +31,12 @@ namespace contouring {
virtual void onNotify(const area_<chunk_pos> &pos, const chunk_pos &offset, const world::ChunkContainer &data) = 0;
/// Display ImGui config
virtual void onGui() = 0;
/// Register entity from model
virtual void onEntityLoad(size_t id, std::istream&) = 0;
/// Register entity from area
virtual void onEntityLoad(size_t id, const glm::ucvec3& size, const std::vector<std::shared_ptr<world::Chunk>>&) = 0;
/// Free entity model
virtual void onEntityUnload(size_t id) = 0;
/// Get options
virtual std::string getOptions() const = 0;
/// Get camera recommended far range

View File

@ -11,6 +11,12 @@
#include "optimizer.hpp"
namespace contouring {
constexpr auto PACK = [](const render::VertexData &v) {
return render::PackedVertexData(meshopt_quantizeHalf(v.Position.x), meshopt_quantizeHalf(v.Position.y),
meshopt_quantizeHalf(v.Position.z), v.Material,
meshopt_quantizeHalf(v.Normal.x), meshopt_quantizeHalf(v.Normal.y),
meshopt_quantizeHalf(v.Normal.z));
};
const std::vector<std::pair<float, float>> LEVELS = {{.001f, 9e-1f}, {.01f, 5e-1f}, {.1f, 1e-1f}, {.2, 1e-2f}, {.5, 5e-3f}};
FlatDualMC::FlatDualMC(const std::string &str) : Abstract() {
@ -43,10 +49,31 @@ namespace contouring {
#endif
std::vector<render::VertexData> tmp;
while (running) {
std::pair<area_<chunk_pos>, surrounding::corners> ctx;
loadQueue.wait();
if (loadQueue.pop(ctx)) {
ZoneScopedN("ProcessContouring");
if (entity_area_job_t job; entityLoadQueue.pop(job)) {
ZoneScopedN("Entity");
render::Model::Data data;
typeof(render::Model::Data::indices) idx;
for (uint8_t x = 0; x < job.size.x; x++) {
for (uint8_t y = 0; y < job.size.y; y++) {
for (uint8_t z = 0; z < job.size.z; z++) {
surrounding::corners sur;
surrounding::load(sur, job.size, glm::ucvec3(x, y, z), job.area);
idx.clear();
render(sur, idx, tmp, Layer::Both); //MAYBE: write inplace with sub-vector
simplify(idx, tmp);
data.indices.reserve(data.indices.size() + idx.size());
const auto idx_start = data.vertices.size();
std::transform(idx.begin(), idx.end(), std::back_inserter(data.indices), [&](glm::u16 i) { return i + idx_start; });
data.vertices.reserve(data.vertices.size() + tmp.size());
const auto vert_offset = glm::vec3(x * CHUNK_LENGTH, y * CHUNK_LENGTH, z * CHUNK_LENGTH);
std::transform(tmp.begin(), tmp.end(), std::back_inserter(data.vertices), [&](render::VertexData v) {
v.Position += vert_offset; return PACK(v); });
}}}
optimize_fetch(data);
entityLoadedQueue.emplace(job.id, data);
} else if (std::pair<area_<chunk_pos>, surrounding::corners> ctx; loadQueue.pop(ctx)) {
ZoneScopedN("Chunk");
std::pair<render::LodModel::LodData, render::LodModel::LodData> data;
if (transparency) {
render(ctx.second, data.first, tmp, Layer::Solid);
@ -56,26 +83,12 @@ namespace contouring {
}
//TODO: direct upload with vulkan
loadedQueue.emplace(ctx.first, data);
} else {
loadQueue.wait();
}
}
});
}
{ //TODO: render for chunk(s)
auto pack = [](const glm::vec3 &pos) {
return render::PackedVertexData(meshopt_quantizeHalf(pos.x), meshopt_quantizeHalf(pos.y),
meshopt_quantizeHalf(pos.z), world::materials::textures_map[7],
meshopt_quantizeHalf(pos.x), meshopt_quantizeHalf(pos.y),
meshopt_quantizeHalf(pos.z));
};
playerModel = render::Model::Create(render::Model::Data({
pack({1, 0, 0}), pack({0, 1, 0}), pack({0, 0, 1}),
pack({-1, 0, 0}), pack({0, -1, 0}), pack({0, 0, -1})
}, {
0, 1, 2, 0, 5, 1, 0, 2, 4, 0, 4, 5,
1, 3, 2, 5, 3, 1, 2, 3, 4, 4, 3, 5
}));
}
}
FlatDualMC::~FlatDualMC() {
running = false;
@ -85,7 +98,7 @@ namespace contouring {
if (worker.joinable())
worker.join();
}
//TODO: prefer unique_ptr
for(auto& buffer: buffers) {
for(auto& val: buffer.second.second) {
@ -149,12 +162,36 @@ namespace contouring {
}
}
void FlatDualMC::onEntityLoad(size_t id, std::istream &in) {
if(!entities.get(id))
entities.set_emplace(id, nullptr);
entityLoadedQueue.emplace(id, render::Model::Data(in));
}
void FlatDualMC::onEntityLoad(size_t id, const glm::ucvec3& size, const std::vector<std::shared_ptr<world::Chunk>>& area) {
if(!entities.get(id))
entities.set_emplace(id, nullptr);
entityLoadQueue.emplace(entity_area_job_t{id, size, area});
loadQueue.notify_one();
}
void FlatDualMC::onEntityUnload(size_t id) {
entities.erase(id);
}
void FlatDualMC::update(const voxel_pos& pos, const world::client::area_map& areas) {
ZoneScopedN("Ct");
std::pair<area_<chunk_pos>, std::pair<render::LodModel::LodData, render::LodModel::LodData>> out;
TracyPlot("CtLoad", static_cast<int64_t>(loadQueue.size()));
TracyPlot("CtLoad", static_cast<int64_t>(loadQueue.size() + entityLoadQueue.size()));
//MAYBE: clear out of range loadQueue.trim(keepDistance * keepDistance)
TracyPlot("CtLoaded", static_cast<int64_t>(loadedQueue.size()));
TracyPlot("CtLoaded", static_cast<int64_t>(loadedQueue.size() + entityLoadedQueue.size()));
{
std::pair<size_t, render::Model::Data> out;
for(auto handle = entityLoadedQueue.extractor(); handle.first(out);) {
if (auto entity = entities.get(out.first))
*entity = render::Model::Create(out.second);
}
}
std::pair<area_<chunk_pos>, std::pair<render::LodModel::LodData, render::LodModel::LodData>> out;
for(auto handle = loadedQueue.extractor(); handle.first(out);) {
const auto bufferSolid = out.second.first.first.empty() ? NULL : render::LodModel::Create(out.second.first).release();
const auto bufferTrans = out.second.second.first.empty() ? NULL : render::LodModel::Create(out.second.second).release();
@ -265,7 +302,7 @@ namespace contouring {
}
}
void FlatDualMC::render(const surrounding::corners &surrounding, render::LodModel::LodData &out, std::vector<render::VertexData> &tmp, Layer layer) const {
void FlatDualMC::render(const surrounding::corners &surrounding, typeof(render::Model::Data::indices) &out, std::vector<render::VertexData> &tmp, Layer layer) const {
const int SIZE = CHUNK_LENGTH + 3;
std::array<dualmc::DualMC<float>::Point, SIZE * SIZE * SIZE> grid;
{
@ -304,7 +341,6 @@ namespace contouring {
builder.buildTris(&grid.front(), SIZE, SIZE, SIZE, iso, world::materials::textures_map.cbegin(), world::materials::roughness.cbegin(),
manifold, dmc_vertices, dmc_tris);
auto &data = out.first;
tmp.clear();
tmp.reserve(dmc_vertices.size());
constexpr auto HALF_MANTISSA = 10;
@ -313,20 +349,20 @@ namespace contouring {
meshopt_quantizeFloat(v.z, HALF_MANTISSA)), v.w, glm::vec3(0));
});
data.indices.reserve(dmc_tris.size() * 3);
out.reserve(dmc_tris.size() * 3);
for (const auto& t: dmc_tris) {
glm::vec3 edge1 = tmp[t.i1].Position - tmp[t.i0].Position;
glm::vec3 edge2 = tmp[t.i2].Position - tmp[t.i0].Position;
glm::vec3 normal = glm::normalize(glm::cross(edge1, edge2));
if(!reordering || glm::length2(edge1) > glm::length2(edge2)) {
data.indices.push_back(t.i0);
data.indices.push_back(t.i1);
data.indices.push_back(t.i2);
out.push_back(t.i0);
out.push_back(t.i1);
out.push_back(t.i2);
} else {
data.indices.push_back(t.i2);
data.indices.push_back(t.i0);
data.indices.push_back(t.i1);
out.push_back(t.i2);
out.push_back(t.i0);
out.push_back(t.i1);
}
tmp[t.i0].Normal += normal;
@ -337,17 +373,15 @@ namespace contouring {
for(auto& v: tmp) {
v.Normal = glm::normalize(v.Normal);
}
out.second = simplify_lod(data.indices, tmp, loadedLevels);
std::transform(tmp.begin(), tmp.end(), std::back_inserter(data.vertices), [](const render::VertexData &v) {
return render::PackedVertexData(meshopt_quantizeHalf(v.Position.x), meshopt_quantizeHalf(v.Position.y),
meshopt_quantizeHalf(v.Position.z), v.Material,
meshopt_quantizeHalf(v.Normal.x), meshopt_quantizeHalf(v.Normal.y),
meshopt_quantizeHalf(v.Normal.z));
});
optimize_fetch(data);
}
}
void FlatDualMC::render(const surrounding::corners &surrounding, render::LodModel::LodData &out, std::vector<render::VertexData> &tmp, Layer layer) const {
render(surrounding, out.first.indices, tmp, layer);
out.second = simplify_lod(out.first.indices, tmp, loadedLevels);
std::transform(tmp.begin(), tmp.end(), std::back_inserter(out.first.vertices), PACK);
optimize_fetch(out.first);
}
void FlatDualMC::getModels(draw_call out, const std::optional<geometry::Frustum> &frustum, const glm::llvec3& offset, int density, bool solid) {
const auto scaling = glm::scale(glm::mat4(1), glm::vec3(1.f / density));
@ -385,8 +419,9 @@ namespace contouring {
}
}
render::Model* FlatDualMC::getEntityModel(size_t) {
return playerModel.get();
render::Model* FlatDualMC::getEntityModel(size_t id) {
if (auto ptr = entities.get(id))
return ptr->get();
return nullptr;
}
}

View File

@ -31,6 +31,13 @@ namespace contouring {
/// @note notify for chunks entering view while moving
void onNotify(const area_<chunk_pos> &, const chunk_pos &, const world::ChunkContainer &) override;
/// Register entity from model
void onEntityLoad(size_t id, std::istream&) override;
/// Register entity from area
void onEntityLoad(size_t id, const glm::ucvec3& size, const std::vector<std::shared_ptr<world::Chunk>>&) override;
/// Free entity model
void onEntityUnload(size_t id) override;
/// Get buffers in frustum with model matrices
/// @note buffers invalidated after update
void getModels(draw_call draw, const std::optional<geometry::Frustum> &frustum, const glm::llvec3 &offset, int density, bool solid) override;
@ -44,10 +51,13 @@ namespace contouring {
//FIXME: use unique_ptr
robin_hood::unordered_map<area_id, robin_hood::pair<area_info, robin_hood::unordered_map<chunk_pos, std::pair<render::LodModel *, render::LodModel *>>>> buffers;
std::unique_ptr<render::Model> playerModel;
data::generational::view_vector<std::unique_ptr<render::Model>> entities;
safe_priority_queue_map<area_<chunk_pos>, surrounding::corners, int, area_hash> loadQueue;
safe_queue<std::pair<area_<chunk_pos>, std::pair<render::LodModel::LodData, render::LodModel::LodData>>> loadedQueue;
struct entity_area_job_t { size_t id; glm::ucvec3 size; std::vector<std::shared_ptr<world::Chunk>> area; };
safe_queue<entity_area_job_t> entityLoadQueue;
safe_queue<std::pair<size_t, render::Model::Data>> entityLoadedQueue;
bool running = true;
std::vector<std::thread> workers;
@ -75,6 +85,7 @@ namespace contouring {
return static_cast<int>(a) & static_cast<int>(b);
}
void render(const surrounding::corners &surrounding, typeof(render::Model::Data::indices)& idx, std::vector<render::VertexData>& ver, Layer layer) const;
void render(const surrounding::corners &surrounding, render::LodModel::LodData& out, std::vector<render::VertexData>& tmp, Layer layer) const;
};
}

View File

@ -35,4 +35,10 @@ inline std::vector<size_t> simplify_lod(std::vector<I> &indices, const std::vect
indices.reserve(indices.size() + full.size());
indices.insert(indices.end(), full.begin(), full.end());
return offsets;
}
template <typename I>
inline void simplify(std::vector<I> &indices, const std::vector<render::VertexData> &vertices) {
ZoneScopedN("LOD");
simplify_buffer(indices, indices, vertices);
meshopt_optimizeVertexCache(indices.data(), indices.data(), indices.size(), vertices.size());
}

View File

@ -27,4 +27,14 @@ namespace contouring::surrounding {
}
return true;
}
void load(corners &out, const glm::ucvec3 &areaSize, const glm::ucvec3 &chunkPos, const std::vector<std::shared_ptr<world::Chunk>> &area) {
for (size_t i = 0; i < 8; i++) {
const auto pos = chunkPos + glm::ucvec3(g_corner_offsets[i]);
if (pos.x < areaSize.x && pos.y < areaSize.y && pos.z < areaSize.z) {
out[i] = std::dynamic_pointer_cast<world::client::EdittableChunk>(area.at(glm::toIdx(pos, areaSize)));
} else {
out[i] = world::client::EMPTY_CHUNK;
}
}
}
}

View File

@ -16,4 +16,5 @@ namespace contouring::surrounding {
};
bool load(corners &out, const chunk_pos &chunkPos, const world::ChunkContainer &chunks);
void load(corners &out, const glm::ucvec3 &areaSize, const glm::ucvec3 &chunkPos, const std::vector<std::shared_ptr<world::Chunk>> &area);
}

View File

@ -226,7 +226,7 @@ UI::Actions UI::draw(config::client::options &options, state::state &state, cons
}
ImGui::EndCombo();
}
ImGui::SliderInt("Radius", &options.editor.tool.radius, 0, 10);
ImGui::SliderInt("Radius", &options.editor.tool.radius, 1, 15);
const auto shapeId = (size_t)options.editor.tool.shape;
if (ImGui::BeginCombo("Shape", world::action::SHAPES[shapeId].c_str())) {
for (size_t i = 0; i < world::action::SHAPES.size(); i++) {

View File

@ -43,7 +43,7 @@ const std::pair<std::vector<glm::vec3>, std::vector<glm::vec4>> ColoredShape::LI
void indexVBO(const std::vector<PackedVertexData> &in_vertices, std::vector<glm::u16> &out_indices, std::vector<PackedVertexData> &out_vertices);
Model::Data::Data(const std::vector<PackedVertexData> &vs, const std::vector<glm::u16> &indices): indices(indices), vertices(vs) { }
Model::Data::Data(std::ifstream& in) {
Model::Data::Data(std::istream& in) {
{
size_t i_size;
in.read(reinterpret_cast<char *>(&i_size), sizeof(i_size));
@ -55,7 +55,7 @@ Model::Data::Data(std::ifstream& in) {
vertices.resize(v_size, PackedVertexData(0, 0, 0, 0, 0, 0, 0, 0));
in.read(reinterpret_cast<char *>(vertices.data()), sizeof(PackedVertexData) * v_size);
}
void Model::Data::serialize(std::ofstream& out) {
void Model::Data::serialize(std::ostream& out) {
{
size_t i_size = indices.size();
out.write(reinterpret_cast<char *>(&i_size), sizeof(i_size));

View File

@ -70,7 +70,7 @@ public:
Data() { }
Data(const std::vector<PackedVertexData> &vertices, const std::vector<glm::u16> &indices);
Data(const std::vector<PackedVertexData> &vertices) { index(vertices); }
Data(std::ifstream &in);
Data(std::istream &in);
void index(const std::vector<PackedVertexData> &vertices);
bool empty() const {
@ -81,7 +81,7 @@ public:
vertices.clear();
}
void serialize(std::ofstream &out);
void serialize(std::ostream &out);
};
static _FORCE_INLINE_ std::unique_ptr<Model> Create(const Data& data) {

View File

@ -101,13 +101,12 @@ void Renderer::loadUI(Window& w) {
void Renderer::beginFrame() {
TracyGpuZone("Render");
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_FRAMEBUFFER_SRGB);
}
std::function<size_t(render::LodModel *const, glm::mat4, glm::vec4, float)> Renderer::beginWorldPass(bool solid) {
if (solid) {
WorldPass->useIt();
WorldPass->start(this);
}
WorldPass->useIt();
WorldPass->start(this);
return [&](render::LodModel *const buf, glm::mat4 model, glm::vec4 sph, float curv) {
WorldPass->setup(model, sph, curv);
return dynamic_cast<LodModel *const>(buf)->draw();
@ -133,6 +132,7 @@ std::function<size_t(glm::mat4, world::action::Shape, glm::vec4)> Renderer::begi
}
void Renderer::postProcess() {
glDisable(GL_FRAMEBUFFER_SRGB);
if(SkyEnable) {
SkyPass->draw(this);
}

View File

@ -65,9 +65,9 @@ Pipeline::Pipeline(VkDevice device, const PhysicalDeviceInfo &info, const render
if (hasSamples) {
colorDepthSubpass.pResolveAttachments = &colorAttachmentResolveRef;
}
std::array<VkSubpassDescription, 3> subpasses = {colorDepthSubpass, colorDepthSubpass, colorDepthSubpass};
std::array<VkSubpassDescription, 4> subpasses = {colorDepthSubpass, colorDepthSubpass, colorDepthSubpass, colorDepthSubpass};
std::array<VkSubpassDependency, 5> dependencies{};
std::array<VkSubpassDependency, 7> dependencies{};
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
dependencies[0].dstSubpass = 0;
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
@ -108,6 +108,22 @@ Pipeline::Pipeline(VkDevice device, const PhysicalDeviceInfo &info, const render
dependencies[4].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
dependencies[4].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
dependencies[5].srcSubpass = 2;
dependencies[5].dstSubpass = 3;
dependencies[5].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependencies[5].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
dependencies[5].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependencies[5].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
dependencies[5].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
dependencies[6].srcSubpass = 2;
dependencies[6].dstSubpass = 3;
dependencies[6].srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
dependencies[6].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
dependencies[6].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
dependencies[6].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
dependencies[6].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
VkRenderPassCreateInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
std::vector<VkAttachmentDescription> attachments = {colorAttachment, depthAttachment};
@ -507,6 +523,99 @@ Pipeline::Pipeline(VkDevice device, const PhysicalDeviceInfo &info, const render
FATAL("Failed to create graphics pipeline!");
}
}
{ // Transparent World pipeline
VkPushConstantRange pushRange{};
pushRange.offset = 0;
pushRange.size = sizeof(UniqueCurvaturePush);
pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
setLayout(transparentWorldPass, {voxelDescriptorSet}, {pushRange});
struct SpeData {
bool fog;
bool pbr;
bool triplanar;
bool stochastic;
bool blend;
bool curvature;
bool curv_depth;
int32_t unitSize;
} speData;
std::array<VkSpecializationMapEntry, 8> speIndex;
speData.fog = options.voxel.fog;
speIndex[0].constantID = 0;
speIndex[0].offset = offsetof(SpeData, fog);
speIndex[0].size = sizeof(SpeData::fog);
speData.pbr = options.voxel.pbr;
speIndex[1].constantID = 1;
speIndex[1].offset = offsetof(SpeData, pbr);
speIndex[1].size = sizeof(SpeData::pbr);
speData.triplanar = options.voxel.triplanar;
speIndex[2].constantID = 2;
speIndex[2].offset = offsetof(SpeData, triplanar);
speIndex[2].size = sizeof(SpeData::triplanar);
speData.stochastic = options.voxel.stochastic;
speIndex[3].constantID = 3;
speIndex[3].offset = offsetof(SpeData, stochastic);
speIndex[3].size = sizeof(SpeData::stochastic);
speData.blend = options.voxel.blend;
speIndex[4].constantID = 4;
speIndex[4].offset = offsetof(SpeData, blend);
speIndex[4].size = sizeof(SpeData::blend);
speData.curvature = options.voxel.curvature;
speIndex[5].constantID = 5;
speIndex[5].offset = offsetof(SpeData, curvature);
speIndex[5].size = sizeof(SpeData::curvature);
speData.curv_depth = options.voxel.curv_depth;
speIndex[6].constantID = 6;
speIndex[6].offset = offsetof(SpeData, curv_depth);
speIndex[6].size = sizeof(SpeData::curv_depth);
speData.unitSize = 8; //TODO: load from world.voxel_density
speIndex[7].constantID = 16;
speIndex[7].offset = offsetof(SpeData, unitSize);
speIndex[7].size = sizeof(SpeData::unitSize);
VkSpecializationInfo specialization{};
specialization.dataSize = sizeof(SpeData);
specialization.pData = &speData;
specialization.mapEntryCount = speIndex.size();
specialization.pMapEntries = speIndex.data();
auto withGeometry = options.voxel.geometry && info.features.geometryShader;
auto shaderStages = setShaders(transparentWorldPass, withGeometry ? "Voxel.geo" : "Voxel", withGeometry, &specialization);
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
auto bindingDescription = Model::getBindingDescription();
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
auto attributeDescriptions = Model::getAttributeDescriptions();
vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size();
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
VkGraphicsPipelineCreateInfo pipelineInfo{};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = shaderStages.size();
pipelineInfo.pStages = shaderStages.data();
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &trisInputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pDepthStencilState = &depthStencil;
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.pDynamicState = nullptr;
pipelineInfo.layout = transparentWorldPass.layout;
pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 2;
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
pipelineInfo.basePipelineIndex = -1;
if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, ALLOC, &transparentWorldPass.pipeline) != VK_SUCCESS) {
FATAL("Failed to create graphics pipeline!");
}
}
{ // Sky pipeline
setLayout(skyPass, {skyDescriptorSet}, {});
auto shaderStages = setShaders(skyPass, "Sky");
@ -537,7 +646,7 @@ Pipeline::Pipeline(VkDevice device, const PhysicalDeviceInfo &info, const render
pipelineInfo.layout = skyPass.layout;
pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 2;
pipelineInfo.subpass = 3;
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
pipelineInfo.basePipelineIndex = -1;

View File

@ -26,6 +26,7 @@ public:
constexpr const Subpass &getEntitiesPass() const { return worldPass; }
constexpr VkDescriptorSetLayout getIndicDescriptorSet() const { return indicDescriptorSet; }
constexpr const Subpass &getIndicPass() const { return indicPass; }
constexpr const Subpass& getTransparentWorldPass() const { return transparentWorldPass; }
constexpr VkDescriptorSetLayout getSkyDescriptorSet() const { return skyDescriptorSet; }
constexpr const Subpass& getSkyPass() const { return skyPass; }
@ -42,6 +43,7 @@ private:
Subpass worldPass;
Subpass indicPass;
Subpass transparentWorldPass;
Subpass skyPass;
VkRenderPass uiRenderPass;

View File

@ -454,10 +454,8 @@ void Renderer::beginFrame() {
std::function<size_t(render::LodModel *const, glm::mat4, glm::vec4, float)> Renderer::beginWorldPass(bool solid) {
assert(currentImage < swapChain->getImageViews().size());
auto& pass = pipeline->getWorldPass();
if (solid) {
commandCenter->startWorldPass(currentImage, pass);
}
auto& pass = solid ? pipeline->getWorldPass() : pipeline->getTransparentWorldPass();
commandCenter->startWorldPass(currentImage, pass);
return [&](render::LodModel *const rBuffer, glm::mat4 model, glm::vec4 sphere, float curv) {
auto buffer = dynamic_cast<render::vk::LodModel *const>(rBuffer);
buffer->setLastUse(currentImage);

View File

@ -13,13 +13,13 @@ public:
std::optional<Faces> update(float deltaTime, bool animate) override {
for (auto it = futureEdits.begin(); it != futureEdits.end();) {
it->second -= deltaTime;
if (it->second <= 0 && animate) {
invalidate(it->first.idx);
edits.emplace(it->first.idx, it->first);
it->second.second -= deltaTime;
if (it->second.second <= 0 && animate) {
invalidate(it->first);
edits.emplace(it->first, it->second.first);
it = futureEdits.erase(it);
} else {
it++;
++it;
}
}
return EdittableChunk::update(deltaTime, animate);
@ -32,7 +32,7 @@ public:
}
/// Add future edit
void addFutureEdit(Chunk::Edit edit, float delay) { futureEdits.emplace_back(edit, delay); }
void addFutureEdit(Chunk::Edit edit, float delay) { futureEdits.insert_or_assign(edit.idx, std::make_pair(EditBody(edit), delay)); }
protected:
/// Is temporary average
@ -41,8 +41,7 @@ protected:
bool isMajorant = false;
/// Temporary animated changes
/// MAYBE: sort by delay
std::vector<std::pair<Chunk::Edit, float>> futureEdits;
robin_hood::unordered_map<chunk_voxel_idx, std::pair<Chunk::EditBody, float>> futureEdits;
};
/// Chunk full of air

View File

@ -158,7 +158,9 @@ bool DistantUniverse::onPacket(const data::out_view& buf, net::PacketFlags) {
packet.read(serverDistance);
auto predictable = false;
packet.read(predictable);
if (!predictable) {
if (predictable) {
packet.read(floodfillLimit);
} else {
options.editHandling = false;
options.editPrediction = false;
}
@ -293,9 +295,8 @@ bool DistantUniverse::onPacket(const data::out_view& buf, net::PacketFlags) {
if (!fill)
break;
world::iterator::Apply<Chunk>(areas, *fill, [](std::shared_ptr<Chunk> &ck, chunk_pos, chunk_voxel_idx idx, Voxel, Voxel next, float delay) {
ck->apply(Chunk::Edit{next, delay, idx});
});
world::iterator::ApplySplit<Chunk>(areas, *fill, floodfillLimit, [](std::shared_ptr<Chunk> &ck, chunk_pos, chunk_voxel_idx idx,
Voxel, Voxel next, float delay) { ck->apply(Chunk::Edit{next, delay, idx}); });
break;
}
@ -334,16 +335,21 @@ bool DistantUniverse::onPacket(const data::out_view& buf, net::PacketFlags) {
entities.clear();
while(!packet.isFull()) {
size_t index;
glm::vec3 size;
glm::usvec3 size;
glm::vec3 scale;
if(packet.read(index) && packet.read(size) && packet.read(scale)) {
entities.set_emplace(index, size, scale);
uint8_t flags;
if(packet.read(index) && packet.read(size) && packet.read(scale) && packet.read(flags)) {
const bool permanant = (flags & (1 << 0)) != 0;
const bool area = (flags & (1 << 1)) != 0;
entities.set(index, Entity(size, scale, permanant, area));
//MAYBE: just update
}
}
break;
}
case server_packet_type::ENTITIES: {
entities.remove([](size_t, Entity &entity) { entity.instances.clear(); return false; });
while(!packet.isFull()) {
size_t entity_idx;
size_t count;
@ -351,7 +357,8 @@ bool DistantUniverse::onPacket(const data::out_view& buf, net::PacketFlags) {
break;
if (auto entity = entities.get(entity_idx)) {
entity->instances.clear();
if (count == 0)
continue;
for (size_t i = 0; i < count && !packet.isFull(); i++) {
size_t idx;
glm::ifvec3 pos;
@ -359,11 +366,85 @@ bool DistantUniverse::onPacket(const data::out_view& buf, net::PacketFlags) {
if (packet.read(idx) && packet.read(pos) && packet.read(vel))
entity->instances.set_emplace(idx, Universe::Entity::Instance{pos, vel});
}
if (dict.has_value()) {
if (auto area = std::get_if<Universe::Entity::area_t>(&entity->shape)) {
if (area->capacity())
continue;
const auto scale = glm::lvec3(1) + glm::divide(entity->size);
area->reserve(scale.x * scale.y * scale.z);
} else if (auto model = std::get_if<Universe::Entity::model_t>(&entity->shape)) {
if (model->capacity())
continue;
model->reserve(8);
}
peer.send(PacketWriter::Of(client_packet_type::MISSING_ENTITY, entity_idx));
}
} else {
LOG_W("Unknown entity type " << entity_idx);
packet.skip(count * (sizeof(size_t) + sizeof(glm::ifvec3) + sizeof(glm::vec3)));
}
}
entities.remove([&](size_t id, const Entity &entity) {
if (!entity.permanant && entity.instances.empty()) {
contouring->onEntityUnload(id);
return true;
}
return false;
});
break;
}
case server_packet_type::ENTITY_SHAPE: {
assert(dict.has_value());
size_t id;
bool is_area;
if (!packet.read(id) || !packet.read(is_area))
break;
if (auto it = entities.get(id)) {
if (is_area) {
auto area = std::get_if<Universe::Entity::area_t>(&it->shape);
assert(area);
area->clear();
glm::i64 i = 0;
while (!packet.isFull()) {
i++;
size_t size = 0;
if (!packet.read(size))
break;
std::vector<char> buffer;
if(auto err = dict.value().decompress(packet.readPart(size), buffer)) {
LOG_E("Corrupted entity chunk packet " << err.value());
break;
}
data::vec_istream idata(buffer);
std::istream iss(&idata);
area->push_back(std::make_shared<Chunk>(iss));
}
auto scale = glm::lvec3(1) + glm::divide(it->size);
if (i != scale.x * scale.y * scale.z) {
LOG_E("Corrupted entity area");
break;
}
contouring->onEntityLoad(id, scale, *area);
} else {
auto model = std::get_if<Universe::Entity::model_t>(&it->shape);
assert(model);
auto remain = packet.readAll();
model->resize(remain.size());
//MAYBE: avoid storing model
memcpy(model->data(), remain.data(), remain.size());
data::vec_istream idata(*model);
std::istream iss(&idata);
contouring->onEntityLoad(id, iss);
}
} else {
LOG_W("Shape for unknown entity type " << id);
}
break;
}
@ -384,9 +465,8 @@ void DistantUniverse::emit(const action::packet &action) {
if (options.editPrediction) {
ZoneScopedN("Fill");
const auto keepDelay = 5 + (peer.getRTT() / 20000.f); // 5s + 50RTT
world::iterator::Apply<Chunk>(areas, *fill, [&](std::shared_ptr<Chunk> &ck, chunk_pos, chunk_voxel_idx idx, Voxel, Voxel next, float delay) {
ck->addFutureEdit(Chunk::Edit{next, keepDelay - delay * 2, idx}, delay);
});
world::iterator::ApplySplit<Chunk>(areas, *fill, floodfillLimit, [&](std::shared_ptr<Chunk> &ck, chunk_pos, chunk_voxel_idx idx,
Voxel, Voxel next, float delay) { ck->addFutureEdit(Chunk::Edit{next, keepDelay - delay * 2, idx}, delay); });
}
} else {
LOG_W("Bad action " << action.index());

View File

@ -35,9 +35,19 @@ namespace world::client {
/// Entities without generation
struct Entity {
Entity(glm::vec3 size, glm::vec3 scale): size(size), scale(scale) { }
glm::vec3 size;
Entity(glm::usvec3 size, glm::vec3 scale, bool permanant, bool area): size(size), scale(scale), permanant(permanant) {
if (area) {
shape = Universe::Entity::area_t();
} else {
shape = Universe::Entity::model_t();
}
}
std::variant<Universe::Entity::area_t, Universe::Entity::model_t> shape;
glm::usvec3 size;
glm::vec3 scale;
bool permanant;
data::generational::view_vector<Universe::Entity::Instance> instances;
};
data::generational::view_vector<Entity> entities;
@ -51,6 +61,7 @@ namespace world::client {
options options;
ushort serverDistance;
size_t floodfillLimit = CHUNK_SIZE;
net::client::Client peer;
};

View File

@ -1,6 +1,7 @@
#include "LocalUniverse.hpp"
#include "../../core/data/math.hpp"
#include "../../core/data/mem.hpp"
#include "../contouring/Abstract.hpp"
#include "../Window.hpp"
#include "../../core/utils/logger.hpp"
@ -32,6 +33,23 @@ void LocalUniverse::update(voxel_pos pos, float) {
onMessage(handle->message.value());
handle->message = std::nullopt;
}
if (handle->entityChange) {
handle->entities->iter([&](entity_id eId, const Entity &entity) {
if (entity.instances.size() == 0)
return;
if (auto area = std::get_if<Entity::area_t>(&entity.shape)) {
contouring->onEntityLoad(eId.index, glm::lvec3(1) + glm::divide(entity.size), *area);
} else {
auto model = std::get_if<Entity::model_t>(&entity.shape);
assert(model);
data::vec_istream idata(*model);
std::istream iss(&idata);
contouring->onEntityLoad(eId.index, iss);
}
});
handle->entityChange = false;
}
const auto cur_chunk = glm::divide(pos);
const auto chunkChange = cur_chunk != last_chunk;

View File

@ -133,6 +133,12 @@ namespace data::generational {
assert(contains(idx));
return entries[idx.index].value.value();
}
T* directly_at(size_t index) {
if (index >= entries.size() || !entries.at(index).value.has_value())
return nullptr;
return &entries[index].value.value();
}
bool contains(id idx) const {
return idx.index < entries.size() &&
@ -279,6 +285,19 @@ namespace data::generational {
return false;
}
void erase(size_t i) {
if(i < this->size())
this->at(i) = std::nullopt;
}
template<typename extractor>
void remove(extractor fn) {
for (size_t i = 0; i < this->size(); i++) {
if(this->at(i).has_value() && fn(i, this->at(i).value())) {
erase(i);
}
}
}
size_t count() const {
return std::count_if(this->begin(), this->end(),
[](const std::optional<T> &e) { return e.has_value(); });

View File

@ -60,9 +60,15 @@ namespace glm {
constexpr idx inline toIdx(glm::u8 x, glm::u8 y, glm::u8 z) {
return (x * IDX_LENGTH + y) * IDX_LENGTH + z;
}
constexpr idx inline toIdx(glm::u8 x, glm::u8 y, glm::u8 z, glm::u8 sy, glm::u8 sz) {
return x * sy * sz + y * sz + z;
}
constexpr idx inline toIdx(ucvec3 pos) {
return toIdx(pos.x, pos.y, pos.z);
}
constexpr idx inline toIdx(ucvec3 pos, ucvec3 size) {
return toIdx(pos.x, pos.y, pos.z, size.y, size.z);
}
constexpr std::pair<lvec3, idx> inline splitIdx(const llvec3 &value, const ucvec3 &m = ucvec3(IDX_LENGTH)) {
return {divide(value, m), toIdx(rem(value.x, m.x), rem(value.y, m.y), rem(value.z, m.z))};
}

View File

@ -9,8 +9,8 @@ namespace data {
/// Map vector to istream
struct vec_istream: std::streambuf {
vec_istream(std::vector<char> &vec) {
this->setg(&vec[0], &vec[0], &vec[0] + vec.size());
vec_istream(const std::vector<char> &vec) {
this->setg((char*)&vec[0], (char*)&vec[0], (char*)&vec[0] + vec.size());
}
};

View File

@ -53,19 +53,22 @@ enum class server_packet_type: uint8_t {
EDITS = 20,
/// Declare entities types
/// {size_t(index), vec3(size), vec3(scale)}
/// TODO: zstd<chunk rle>
/// {size_t(index), usvec3(size), vec3(scale), flags(000000, area, permanant)}[]
ENTITY_TYPES = 32,
/// Update entities instances position and velocity
/// {size_t(entity), size_t(count), {size_t(index), ifvec3(pos), vec3(velocity)}[]}[]
ENTITIES = 33,
/// Entity type model data
/// {size_t(entity), bool(area), render::Data::Model || {size_t(size), zstd<chunk rle>}[]}
ENTITY_SHAPE = 34,
/// World compression dictionary
/// zstd dict
COMPRESSION = 64,
/// Server capabilities
/// ushort(loadDistance), bool(predictable)
/// ushort(loadDistance), bool(predictable), (if predictable) size_t(floodfillLimit)
//MAYBE: use uint8_t flags
CAPABILITIES = 65,
@ -79,6 +82,10 @@ enum class client_packet_type: uint8_t {
/// actions::FillShape
FILL_SHAPE = 0,
/// Request entity chunks
/// size_t(id)
MISSING_ENTITY = 6,
/// Request missing regions
/// area_id, region_pos[]
MISSING_REGIONS = 7,

View File

@ -10,7 +10,7 @@ namespace net {
/// Helper to write allocated packets
class PacketWriter final {
public:
PacketWriter(size_t size): buffer((uint8_t*)malloc(size), size) { }
PacketWriter(size_t size): buffer((uint8_t*)malloc(size), size), visible_size(size) { }
PacketWriter(uint8_t type, size_t data_size): PacketWriter(sizeof(type) + data_size) {
write(type);
}
@ -29,14 +29,47 @@ public:
write(&d, sizeof(d));
}
constexpr size_t getCursor() const { return buffer.cur; }
void writeAt(size_t pos, const void* data, size_t len) {
assert(pos + len <= buffer.siz);
memcpy(buffer.ptr + pos, data, len);
}
template<typename D>
void writeAt(size_t pos, const D& d) {
writeAt(pos, &d, sizeof(d));
}
void reserve(size_t target) {
if (target >= buffer.siz - buffer.cur) {
const auto size = target + buffer.cur;
buffer.ptr = (uint8_t *)realloc(buffer.ptr, size);
buffer.siz = size;
}
}
void resize(size_t target) {
reserve(target);
visible_size = target;
}
void writePush(const void* data, size_t len) {
if (buffer.cur + len > buffer.siz)
resize(buffer.cur + len);
write(data, len);
}
template<typename D>
void writePush(const D& d) {
writePush(&d, sizeof(d));
}
struct varying_part {
varying_part(data::in_view &buffer): buffer(buffer), visible_size(0) { }
varying_part(data::in_view &buffer, size_t& p_size): buffer(buffer), parent_size(p_size), visible_size(0) { }
~varying_part() {
buffer.siz = buffer.cur + visible_size;
buffer.cur = buffer.siz;
buffer.cur += visible_size;
parent_size += visible_size;
}
data::in_view &buffer;
size_t &parent_size;
size_t visible_size;
constexpr size_t size() const { return visible_size; }
@ -55,17 +88,17 @@ public:
};
/// Only from resize, write, resize down
varying_part varying() {
return varying_part(buffer);
return varying_part(buffer, visible_size);
}
bool isFull() const {
return buffer.isDone();
return buffer.cur >= visible_size;
}
[[nodiscard]]
data::out_buffer finish() {
assert(isFull());
data::out_buffer cpy(buffer.ptr, buffer.siz);
data::out_buffer cpy(buffer.ptr, visible_size);
buffer.ptr = nullptr;
buffer.siz = 0;
return cpy;
@ -96,6 +129,7 @@ public:
private:
data::in_view buffer;
size_t visible_size;
};
/// Helper to read out_view
@ -123,9 +157,11 @@ public:
return !isFull();
}
data::out_view readPart(size_t size) {
return data::out_view(buffer.readFrom(size), size);
}
data::out_view readAll() {
const size_t remain = buffer.size() - buffer.cur;
return data::out_view(buffer.readFrom(remain), remain);
return readPart(buffer.size() - buffer.cur);
}
bool isFull() const {

View File

@ -14,4 +14,5 @@ struct server_handle {
std::function<world::Universe::ray_result(const geometry::Ray &ray)> raycast;
std::optional<voxel_pos> teleport;
std::optional<std::string> message;
bool entityChange = false;
};

View File

@ -17,7 +17,7 @@ std::optional<Faces> EdittableChunk::update(float deltaTime, bool animate) {
invalidate(it->first);
it = edits.erase(it);
} else {
it++;
++it;
}
}

View File

@ -56,9 +56,21 @@ namespace world {
/// Entities commun properties
struct Entity {
Entity(const glm::vec3& size = glm::vec3(1), const glm::vec3& scale = glm::vec3(1)): size(size), scale(scale) { };
glm::vec3 size;
/// Linear cube of Chunk
/// Size: 1+divide(size)
using area_t = std::vector<std::shared_ptr<Chunk>>;
/// render::Model::Data serialized
using model_t = std::vector<char>;
Entity(const glm::usvec3& size = glm::usvec3(1), const glm::vec3& scale = glm::vec3(1), bool permanant = true):
size(size), scale(scale), permanant(permanant) { }
Entity(const model_t& model, const glm::usvec3& size = glm::usvec3(1), const glm::vec3& scale = glm::vec3(1), bool permanant = true):
size(size), scale(scale), permanant(permanant) { shape = model; }
std::variant<area_t, model_t> shape;
glm::usvec3 size;
glm::vec3 scale;
bool permanant;
struct Instance {
glm::ifvec3 pos;
glm::vec3 velocity;

View File

@ -12,12 +12,12 @@ namespace world {
uint16_t value;
using material_t = uint_fast16_t;
using density_t = uint_fast8_t;
constexpr static const density_t DENSITY_MAX = (1 << 3)-1;
constexpr static const material_t MATERIAL_MAX = (1 << 12)-1;
static constexpr density_t DENSITY_MAX = (1 << 3)-1;
static constexpr material_t MATERIAL_MAX = (1 << 12)-1;
constexpr static const uint16_t DENSITY_MASK = 0b0111;
constexpr static const uint16_t MATERIAL_MASK = 0b0111'1111'1111'1000;
constexpr static const uint16_t SWAP_MASK = 0b1000'0000'0000'0000;
static constexpr uint16_t DENSITY_MASK = 0b0111;
static constexpr uint16_t MATERIAL_MASK = 0b0111'1111'1111'1000;
static constexpr uint16_t SWAP_MASK = 0b1000'0000'0000'0000;
Voxel(uint16_t value = 0): value(value) { }
Voxel(material_t material, density_t density, bool swap = false) {
@ -43,7 +43,7 @@ namespace world {
}
/// Quantity of element on [0, 1]
constexpr inline float density_ratio() const {
return density() * 1.f / world::Voxel::DENSITY_MAX;
return density() * (1.f / world::Voxel::DENSITY_MAX);
}
/// Swap value

View File

@ -2,7 +2,7 @@
using namespace world::iterator;
std::unique_ptr<Abstract> world::iterator::Get(world::action::Shape shape, uint16_t radius) {
std::unique_ptr<AbstractFill> world::iterator::Get(world::action::Shape shape, uint16_t radius) {
switch (shape) {
case world::action::Shape::Cube:
return std::make_unique<Cube>(radius);
@ -11,7 +11,18 @@ std::unique_ptr<Abstract> world::iterator::Get(world::action::Shape shape, uint1
case world::action::Shape::SmoothSphere:
return std::make_unique<SmoothSphere>(radius);
default:
return std::unique_ptr<Abstract>(nullptr);
return std::unique_ptr<AbstractFill>(nullptr);
}
}
std::unique_ptr<AbstractBorder> world::iterator::GetBorder(world::action::Shape shape, uint16_t radius) {
switch (shape) {
case world::action::Shape::Cube:
return std::make_unique<CubeBorder>(radius);
case world::action::Shape::RawSphere:
case world::action::Shape::SmoothSphere:
return std::make_unique<SphereBorder>(radius);
default:
return std::unique_ptr<AbstractBorder>(nullptr);
}
}
@ -36,6 +47,33 @@ bool Cube::next(pair& out) {
}
return true;
}
bool CubeBorder::next(glm::ivec3& out) {
if (face > 5)
return false;
out = pos;
const auto getAxis = [&](glm::ivec3 &in, int offset) -> int& {
const auto v = (face / 2 + offset) % 3;
return v == 0 ? in.x : (v == 1 ? in.y : in.z);
};
const auto r0_max = radius - (face > 1);
if (auto& r0 = getAxis(pos, 0); r0 < r0_max) {
r0++;
} else {
r0 = -r0_max;
const auto r1_max = radius - (face > 3);
if (auto &r1 = getAxis(pos, 1); r1 < r1_max) {
r1++;
} else {
r1 = -r1_max;
getAxis(pos, 2) = -radius;
face++;
getAxis(pos, 2) = (face % 2 == 0) ? -radius : radius;
}
}
return true;
}
bool RawSphere::next(pair& out) {
const auto delta = [](int radius, int pos) -> int {
@ -94,4 +132,24 @@ bool SmoothSphere::next(pair& out) {
out.first = pos;
return true;
}
constexpr float PI = 3.14159;
bool SphereBorder::next(glm::ivec3& out) {
if (i >= max_i)
return false;
const float si = sin(PI * i / (float)max_i);
const float sj = sin(2 * PI * j / (float)max_j);
const float ci = cos(PI * i / (float)max_i);
const float cj = cos(2 * PI * j / (float)max_j);
out = glm::vec3(si * cj, si * sj, ci) * (radius + .5f);
if (j < max_j - 2) {
j++;
} else {
j = 0;
i++;
}
return true;
}

View File

@ -2,51 +2,141 @@
#include "position.h"
#include "actions.hpp"
#include <queue>
namespace world::iterator {
using pair = std::pair<glm::ivec3, float>;
class Abstract {
class AbstractFill {
public:
virtual bool next(pair&) = 0;
protected:
static constexpr uint32_t Diam(uint16_t radius) { return radius * 2 + 1; }
};
class AbstractBorder {
public:
virtual bool next(glm::ivec3&) = 0;
};
/// From -radius to radius
std::unique_ptr<Abstract> Get(action::Shape, uint16_t radius);
std::unique_ptr<AbstractFill> Get(action::Shape, uint16_t radius);
/// Border sampling from -radius to radius
std::unique_ptr<AbstractBorder> GetBorder(action::Shape, uint16_t radius);
template<typename Chunk, typename Area>
inline bool GetChunk(Area& chunks, const std::pair<chunk_pos, chunk_voxel_idx>& split, Voxel& out,
typename std::shared_ptr<Chunk>& ck = nullptr, chunk_pos& ck_pos = chunk_pos(INT32_MAX)
) {
if (split.first != ck_pos && chunks.inRange(split.first)) {
if(auto it = chunks.find(split.first); it != chunks.end()) {
ck = std::dynamic_pointer_cast<Chunk>(it->second);
ck_pos = split.first;
}
}
if (split.first == ck_pos) {
out = ck->get(split.second);
return true;
}
return false;
}
/// Apply shape on areas
template<typename Chunk, typename area_map, typename CB>
void Apply(area_map &areas, action::FillShape fill, const CB& callback) {
if(const auto it = areas.find(fill.pos.first); it != areas.end()) {
auto &chunks = it->second->setChunks();
auto iterator = Get(fill.shape, fill.radius);
pair point;
typename std::shared_ptr<Chunk> ck = nullptr;
chunk_pos ck_pos = chunk_pos(INT32_MAX);
while (iterator->next(point)) {
const voxel_pos offset = point.first;
const auto split = glm::splitIdx(fill.pos.second + offset);
if (split.first != ck_pos && chunks.inRange(split.first)) {
if(auto it = chunks.find(split.first); it != chunks.end()) {
ck = std::dynamic_pointer_cast<Chunk>(it->second);
ck_pos = split.first;
}
}
if (split.first == ck_pos) {
auto prev = ck->get(split.second);
const auto next = prev.filled(fill.val, point.second);
if (prev.value != next.value) {
callback(ck, ck_pos, split.second, prev, next, glm::length2(offset) / fill.radius * .05f);
}
inline void Apply(area_map &areas, action::FillShape fill, const CB& callback) {
const auto it = areas.find(fill.pos.first);
if (it == areas.end())
return;
auto &chunks = it->second->setChunks();
auto iterator = Get(fill.shape, fill.radius);
pair point;
typename std::shared_ptr<Chunk> ck = nullptr;
chunk_pos ck_pos = chunk_pos(INT32_MAX);
while (iterator->next(point)) {
const voxel_pos offset = point.first;
const auto split = glm::splitIdx(fill.pos.second + offset);
if (Voxel prev; GetChunk<Chunk>(chunks, split, prev, ck, ck_pos)) {
const auto next = prev.filled(fill.val, point.second);
if (prev.value != next.value) {
callback(ck, ck_pos, split.second, prev, next, glm::length2(offset) / fill.radius * .05f);
}
}
}
}
/// Find parts using flood fill
template<typename Chunk, typename area_map, typename CB>
inline bool Split(area_map &areas, action::FillShape fill, size_t floodFillLimit, const CB& callback) {
if (floodFillLimit == 0 || fill.val.is_solid())
return false;
class Cube final: public Abstract {
const auto it = areas.find(fill.pos.first);
if (it == areas.end())
return false;
// Check for subpart break
robin_hood::unordered_set<voxel_pos> joints;
auto &chunks = it->second->setChunks();
auto iterator = GetBorder(fill.shape, fill.radius+1);
glm::ivec3 point;
typename std::shared_ptr<Chunk> ck = nullptr;
chunk_pos ck_pos = chunk_pos(INT32_MAX);
const auto cleanVoxel = Voxel(world::materials::AIR, fill.val.density());
while (iterator->next(point)) {
const auto full = fill.pos.second + voxel_pos(point);
if (Voxel v; GetChunk<Chunk>(chunks, glm::splitIdx(full), v, ck, ck_pos) && v.is_solid())
joints.insert(full);
}
bool added = false;
while (!joints.empty()) {
std::queue<voxel_pos> todo;
todo.push(*joints.begin());
robin_hood::unordered_set<voxel_pos> part;
part.insert(todo.front());
size_t i = 0;
for (; !todo.empty() && i < floodFillLimit; i++) {
const auto full = todo.front();
todo.pop();
joints.erase(full);
if (Voxel v; GetChunk<Chunk>(chunks, glm::splitIdx(full), v, ck, ck_pos) && v.is_solid()) {
constexpr std::array<voxel_pos, 6> DIRS = {
voxel_pos(1, 0, 0), voxel_pos(-1, 0, 0), voxel_pos(0, 1, 0),
voxel_pos(0, -1, 0), voxel_pos(0, 0, 1), voxel_pos(0, 0, -1)};
//MAYBE: diag
for (auto dir : DIRS) {
const auto off = full + dir;
if (part.insert(off).second)
todo.push(off);
}
}
}
if (todo.empty()) {
added = true;
callback(part, cleanVoxel, chunks, ck, ck_pos, it->second);
}
}
return added;
}
/// Call Apply then Split
template<typename Chunk, typename area_map, typename CB>
inline void ApplySplit(area_map &areas, action::FillShape fill, size_t floodFillLimit, const CB& callback) {
Apply<Chunk>(areas, fill, callback);
Split<Chunk>(areas, fill, floodFillLimit, [&](const robin_hood::unordered_set<voxel_pos>& part, Voxel next,
world::ChunkContainer& chunks, std::shared_ptr<Chunk>& ck, chunk_pos& ck_pos, std::shared_ptr<Area>&) {
for(auto full: part) {
const auto split = glm::splitIdx(full);
if (Voxel prev; world::iterator::GetChunk<Chunk>(chunks, split, prev, ck, ck_pos) && prev.is_solid()) {
if (prev.value != next.value) {
callback(ck, ck_pos, split.second, prev, next, fill.radius * .05f);
}
}
}
});
}
class Cube final: public AbstractFill {
public:
bool next(pair&) override;
@ -56,9 +146,20 @@ private:
const uint16_t radius;
glm::ivec3 pos;
};
class CubeBorder final: public AbstractBorder {
public:
bool next(glm::ivec3&) override;
CubeBorder(uint16_t radius): radius(radius), pos(-radius, -radius, -radius), face(0) { }
private:
const uint16_t radius;
glm::ivec3 pos;
int face;
};
/// Interger sphere
class RawSphere final: public Abstract {
class RawSphere final: public AbstractFill {
public:
bool next(pair&) override;
@ -70,9 +171,20 @@ private:
int dy;
int dz;
};
class SphereBorder final: public AbstractBorder {
public:
bool next(glm::ivec3&) override;
SphereBorder(uint16_t radius): radius(radius), max_i(radius*3), max_j(radius*4), i(0), j(0) { }
private:
const uint16_t radius;
const uint max_i, max_j;
uint i, j;
};
/// Anti-aliased sphere
class SmoothSphere final: public Abstract {
class SmoothSphere final: public AbstractFill {
public:
bool next(pair&) override;

View File

@ -0,0 +1,8 @@
#pragma once
#include <vector>
#include <stdint.h>
namespace world::models {
static const std::vector<char> player = {24,0,0,0,0,0,0,0,0,0,1,0,2,0,0,0,5,0,1,0,0,0,2,0,4,0,0,0,4,0,5,0,1,0,3,0,2,0,5,0,3,0,1,0,2,0,3,0,4,0,4,0,3,0,5,0,6,0,0,0,0,0,0,0,0,60,0,0,0,0,8,0,0,60,0,0,0,0,0,0,0,0,0,60,0,0,8,0,0,0,0,60,0,0,0,0,0,0,0,0,0,60,8,0,0,0,0,0,0,60,0,0,0,-68,0,0,0,0,8,0,0,-68,0,0,0,0,0,0,0,0,0,-68,0,0,8,0,0,0,0,-68,0,0,0,0,0,0,0,0,0,-68,8,0,0,0,0,0,0,-68,0,0};
}

View File

@ -25,6 +25,7 @@ public:
world.loadDistance = config["world"]["load_distance"].value_or(world.loadDistance);
world.keepDistance = config["world"]["keep_distance"].value_or(world.keepDistance);
world.folderPath = config["world"]["path"].value_or(world.folderPath);
world.floodFillLimit = config["world"]["max_part_size"].value_or<int64_t>(world.floodFillLimit);
}
toml::table save() {
@ -43,7 +44,8 @@ public:
{"world", toml::table({
{"load_distance", world.loadDistance},
{"keep_distance", world.keepDistance},
{"path", world.folderPath}
{"path", world.folderPath},
{"max_part_size", (int64_t)world.floodFillLimit}
})}
});
}

View File

@ -9,6 +9,7 @@ namespace world::server {
/// Server size chunk
class Chunk: public virtual world::Chunk {
public:
Chunk(): world::Chunk() { }
Chunk(const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd);
Chunk(std::istream& str, bool rle = RLE);
virtual ~Chunk();
@ -27,9 +28,6 @@ namespace world::server {
/// Voxel swap bit indicate perfect majority
Voxel write(std::ostream& str, bool rle = RLE) const;
protected:
Chunk(): world::Chunk() { }
private:
/// Modified by player
bool modified = false;

View File

@ -9,6 +9,7 @@ namespace world::server {
// Server and Client merged chunk
class SharedChunk final: public Chunk, public world::client::EdittableChunk {
public:
SharedChunk(): world::Chunk(), Chunk(), world::client::EdittableChunk() { }
SharedChunk(const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd): world::Chunk(), Chunk(pos, rnd), world::client::EdittableChunk() { }
SharedChunk(std::istream &str, bool rle = RLE): world::Chunk(str, rle), Chunk(), world::client::EdittableChunk() { }

View File

@ -30,6 +30,7 @@ SharedUniverse::SharedUniverse(const options &o, server_handle *const localHandl
localHandle->raycast = std::function([&](const geometry::Ray& ray){
return this->raycast(ray); });
localHandle->running = true;
localHandle->entityChange = true;
}
SharedUniverse::~SharedUniverse() {
LOG_D("Breaking shared universe");
@ -50,6 +51,10 @@ void SharedUniverse::broadcastMessage(const std::string& text) {
localHandle->message = text;
Universe::broadcastMessage(text);
}
void SharedUniverse::broadcastEntities() {
localHandle->entityChange = true;
Universe::broadcastEntities();
}
std::shared_ptr<Chunk> SharedUniverse::createChunk(const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd) const {
return std::make_shared<SharedChunk>(pos, rnd);
@ -57,3 +62,6 @@ std::shared_ptr<Chunk> SharedUniverse::createChunk(const chunk_pos &pos, const s
std::shared_ptr<Chunk> SharedUniverse::createChunk(std::istream &str) const {
return std::make_shared<SharedChunk>(str);
}
std::shared_ptr<Chunk> SharedUniverse::createChunk() const {
return std::make_shared<SharedChunk>();
}

View File

@ -15,11 +15,13 @@ namespace world::server {
protected:
std::shared_ptr<Chunk> createChunk(const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd) const override;
std::shared_ptr<Chunk> createChunk(std::istream &str) const override;
std::shared_ptr<Chunk> createChunk() const override;
void loadChunk(area_<chunk_pos>, chunk_pos, const world::ChunkContainer &) override;
void updateChunk(area_map::iterator &, world::ChunkContainer::iterator &, chunk_pos, float deltaTime) override;
void broadcastMessage(const std::string&) override;
void broadcastEntities() override;
private:
server_handle *const localHandle;

View File

@ -9,6 +9,7 @@
#include "../../core/world/raycast.hpp"
#include "../../core/world/iterators.hpp"
#include "../../core/world/actions.hpp"
#include "../../core/world/models.hpp"
#include "../../core/net/io.hpp"
using namespace world::server;
@ -66,7 +67,7 @@ Universe::Universe(const Universe::options &options): host(options.connection,
{
[[maybe_unused]]
const auto type_id = entities.emplace(glm::vec3(1), glm::vec3(2));
const auto type_id = entities.emplace(world::models::player, glm::usvec3(1), glm::vec3(2));
assert(type_id == PLAYER_ENTITY_ID);
}
@ -411,7 +412,7 @@ void Universe::update(float deltaTime) {
{ // Update entities
ZoneScopedN("Entities");
size_t item_count = 0;
entities.for_each([&](entity_id type, Entity &val) {
entities.remove([&](entity_id type, Entity &val) {
val.instances.remove([&](entity_id, Entity::Instance &inst) {
if (type == PLAYER_ENTITY_ID) {
//MAYBE: update players ?
@ -428,6 +429,7 @@ void Universe::update(float deltaTime) {
//MAYBE: Store in region ?
//MAYBE: Save to files
});
return !val.permanant && val.instances.empty();
});
TracyPlot("EntityCount", static_cast<int64_t>(item_count));
@ -472,6 +474,9 @@ std::optional<uint16_t> Universe::onConnect(net::server::Peer* peer) {
auto packet = PacketWriter(server_packet_type::CAPABILITIES, sizeof(loadDistance) + sizeof(bool));
packet.write(loadDistance);
packet.write(PREDICTABLE);
if constexpr (PREDICTABLE) {
packet.write(floodFillLimit);
}
peer->send(packet.finish());
}
@ -485,17 +490,7 @@ std::optional<uint16_t> Universe::onConnect(net::server::Peer* peer) {
peer->send(packet.finish());
movedPlayers.insert(client->instanceId);
}
{
constexpr auto ITEM_SIZE = sizeof(entity_id::index) + sizeof(glm::vec3) * 2;
auto packet = PacketWriter(server_packet_type::ENTITY_TYPES, ITEM_SIZE * entities.size());
entities.iter([&](entity_id id, const Entity &entity) {
packet.write(id.index);
packet.write(entity.size);
packet.write(entity.scale);
});
//MAYBE: sendBroadcast
peer->send(packet.finish(), net::server::queue::ENTITY);
}
broadcastEntities();
broadcastMessage("> Player" + std::to_string(client->instanceId.index) + " has joined us");
broadcastAreas();
return std::nullopt;
@ -633,6 +628,45 @@ bool Universe::onPacket(net::server::Peer *peer, const data::out_view &buf, net:
}
break;
}
case client_packet_type::MISSING_ENTITY: {
size_t id;
if (!packet.read(id))
break;
if (auto entity = entities.directly_at(id)) {
if (const auto area = std::get_if<Entity::area_t>(&entity->shape)) {
auto packet = PacketWriter(server_packet_type::ENTITY_SHAPE, sizeof(id) + sizeof(bool) + sizeof(size_t));
packet.write(id);
packet.write(true);
for (auto it = area->begin(); it != area->end(); ++it) {
std::ostringstream out;
std::dynamic_pointer_cast<Chunk>(*it)->write(out);
auto size_pos = packet.getCursor();
size_t size = 0;
packet.writePush(size);
{
auto vec = packet.varying();
dict_write_ctx.compress(out.str(), vec);
size = vec.size();
}
packet.writeAt(size_pos, size);
}
peer->send(packet.finish(), net::server::CHUNK);
} else {
const auto model = std::get_if<Entity::model_t>(&entity->shape);
assert(model);
auto packet = PacketWriter(server_packet_type::ENTITY_SHAPE, sizeof(id) + sizeof(bool) + model->size());
//MAYBE: prefix model with id+false
packet.write(id);
packet.write(false);
packet.write(model->data(), model->size());
peer->send(packet.finish(), net::server::CHUNK);
}
} else {
LOG_T("Bad entity request " << id);
}
break;
}
default:
LOG_T("Bad packet from " << peer->getAddress());
break;
@ -665,6 +699,22 @@ data::out_buffer Universe::serializeChunk(area_<chunk_pos> id, const std::shared
void Universe::broadcastMessage(const std::string& text) {
host.sendBroadcast(net::PacketWriter::Of(net::server_packet_type::MESSAGE, text.data(), text.size()));
}
void Universe::broadcastEntities() {
constexpr auto ITEM_SIZE = sizeof(entity_id::index) + sizeof(glm::usvec3) + sizeof(glm::vec3) + sizeof(uint8_t);
auto packet = net::PacketWriter(net::server_packet_type::ENTITY_TYPES, ITEM_SIZE * entities.size());
entities.iter([&](entity_id id, const Entity &entity) {
packet.write(id.index);
packet.write(entity.size);
packet.write(entity.scale);
uint8_t flags = 0;
if (entity.permanant)
flags |= 1;
if (std::holds_alternative<Entity::area_t>(entity.shape))
flags |= 2;
packet.write(flags);
});
host.sendBroadcast(packet.finish(), net::server::queue::ENTITY);
}
void Universe::updateChunk(area_map::iterator &, world::ChunkContainer::iterator &, chunk_pos, float /*deltaTime*/) {}
void Universe::loadChunk(area_<chunk_pos>, chunk_pos, const world::ChunkContainer &) {}
@ -672,6 +722,7 @@ void Universe::loadChunk(area_<chunk_pos>, chunk_pos, const world::ChunkContaine
void Universe::setOptions(const Universe::options& options) {
loadDistance = options.loadDistance;
keepDistance = options.keepDistance;
floodFillLimit = options.floodFillLimit;
}
Universe::ray_result Universe::raycast(const geometry::Ray &ray) const {
@ -698,7 +749,8 @@ world::ItemList Universe::set(const area_<voxel_pos>& pos, int radius, action::S
return data && !data->handleEdits;
});
robin_hood::unordered_map<chunk_pos, std::vector<Chunk::Edit>> edits;
world::iterator::Apply<Chunk>(areas, world::action::FillShape(pos, val, shape, radius),
const auto fill = world::action::FillShape(pos, val, shape, radius);
world::iterator::Apply<Chunk>(areas, fill,
[&](std::shared_ptr<Chunk>& ck, chunk_pos ck_pos, chunk_voxel_idx idx, Voxel /*prev*/, Voxel next, float delay) {
if (stupidClient)
edits[ck_pos].push_back(Chunk::Edit{next, delay, idx});
@ -706,6 +758,47 @@ world::ItemList Universe::set(const area_<voxel_pos>& pos, int radius, action::S
//TODO: inventory
ck->replace(idx, next, delay);
});
if (world::iterator::Split<Chunk>(areas, fill, floodFillLimit, [&](const robin_hood::unordered_set<voxel_pos>& part, Voxel cleanVoxel,
world::ChunkContainer& chunks, std::shared_ptr<Chunk>& ck, chunk_pos& ck_pos, std::shared_ptr<Area>& it
){
voxel_pos min = voxel_pos(INT64_MAX);
voxel_pos max = voxel_pos(INT64_MIN);
for(auto full: part) {
min = glm::min(min, full);
max = glm::max(max, full);
}
const auto size = max - min;
const chunk_pos scale = chunk_pos(1) + glm::divide(size);
const auto type = entities.emplace(size, glm::vec3(1), false);
auto &entity = entities.at(type);
auto &area = std::get<Entity::area_t>(entity.shape);
{
const auto chunk_count = scale.x * scale.y * scale.z;
area.reserve(chunk_count);
for (long i = 0; i < chunk_count; i++)
area.push_back(createChunk());
}
std::shared_ptr<Chunk> ck_dest = nullptr;
chunk_pos ck_pos_dest = chunk_pos(INT32_MAX);
for(auto full: part) {
const auto split = glm::splitIdx(full);
if (Voxel v; world::iterator::GetChunk<Chunk>(chunks, split, v, ck, ck_pos) && v.is_solid()) {
if (stupidClient)
edits[ck_pos].push_back(Chunk::Edit{cleanVoxel, fill.radius * .05f, split.second});
ck->replace(split.second, cleanVoxel);
const auto spl = glm::splitIdx(full - min);
if (spl.first != ck_pos_dest) {
ck_dest = std::dynamic_pointer_cast<Chunk>(area.at(glm::toIdx(spl.first, scale)));
assert(ck_dest);
ck_pos_dest = spl.first;
}
ck_dest->set(spl.second, v);
}
}
entity.instances.emplace(Entity::Instance{it->getOffset() + min, glm::vec3(0)});
})) { broadcastEntities(); }
if (stupidClient && !edits.empty()) {
ZoneScopedN("Packet");
size_t size = sizeof(area_id);
@ -789,4 +882,7 @@ std::shared_ptr<Chunk> Universe::createChunk(const chunk_pos &pos, const std::un
}
std::shared_ptr<Chunk> Universe::createChunk(std::istream &str) const {
return std::make_shared<Chunk>(str);
}
std::shared_ptr<Chunk> Universe::createChunk() const {
return std::make_shared<Chunk>();
}

View File

@ -30,6 +30,9 @@ namespace world::server {
std::string folderPath = "world";
net::exposure connection = net::exposure{"", 4242, 1, "content/key.pem", "content/cert.pem"};
/// Iteration count in part break search
size_t floodFillLimit = CHUNK_SIZE;
};
Universe(const options &);
@ -76,11 +79,13 @@ namespace world::server {
void broadcastAreas();
data::out_buffer serializeChunk(area_<chunk_pos>, const std::shared_ptr<Chunk>&);
virtual void broadcastMessage(const std::string &);
virtual void broadcastEntities();
using area_map = robin_hood::unordered_map<area_id, std::shared_ptr<Area>>;
virtual std::shared_ptr<Chunk> createChunk(const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd) const;
virtual std::shared_ptr<Chunk> createChunk(std::istream &str) const;
virtual std::shared_ptr<Chunk> createChunk() const;
virtual void updateChunk(area_map::iterator&, world::ChunkContainer::iterator&, chunk_pos, float deltaTime);
virtual void loadChunk(area_<chunk_pos>, chunk_pos, const world::ChunkContainer &);
@ -109,6 +114,7 @@ namespace world::server {
ushort loadDistance;
ushort keepDistance;
std::string folderPath;
size_t floodFillLimit;
net::server::Server host;

View File

@ -0,0 +1,36 @@
/**
* \file generate_models.cpp
* \brief Serialize model for core/world/models.hpp
*/
#define STANDALONE 1
#include "../client/render/api/Models.hpp"
#include "../core/world/materials.hpp"
#include <meshoptimizer.h>
#include <sstream>
#include <iostream>
/// Entry point
int main(int, char *[]){
const auto pack = [](const glm::vec3 &pos) {
return render::PackedVertexData(meshopt_quantizeHalf(pos.x), meshopt_quantizeHalf(pos.y),
meshopt_quantizeHalf(pos.z), world::materials::textures_map[7],
meshopt_quantizeHalf(pos.x), meshopt_quantizeHalf(pos.y),
meshopt_quantizeHalf(pos.z));
};
auto model = render::Model::Data({
pack({1, 0, 0}), pack({0, 1, 0}), pack({0, 0, 1}),
pack({-1, 0, 0}), pack({0, -1, 0}), pack({0, 0, -1})
}, {
0, 1, 2, 0, 5, 1, 0, 2, 4, 0, 4, 5,
1, 3, 2, 5, 3, 1, 2, 3, 4, 4, 3, 5
});
std::ostringstream sstr;
model.serialize(sstr);
for (auto& el: sstr.str()) {
std::cout << (int)el << ',';
}
std::cout << std::endl;
return 0;
}