From 18c840b9455a24678bf1dd19e335c40a5c403e30 Mon Sep 17 00:00:00 2001 From: Shu Date: Tue, 10 Nov 2020 18:17:21 +0100 Subject: [PATCH] Break parts in custom entities and display --- CMakeLists.txt | 19 ++- README.md | 1 + TODO.md | 8 +- deps/robin_hood/robin_hood.h | 180 ++++++++++++++++---------- resource/content/shaders/Voxel.fs | 1 - src/client/Client.cpp | 72 +++++------ src/client/contouring/Abstract.hpp | 6 + src/client/contouring/FlatDualMC.cpp | 125 +++++++++++------- src/client/contouring/FlatDualMC.hpp | 13 +- src/client/contouring/optimizer.hpp | 6 + src/client/contouring/surrounding.cpp | 10 ++ src/client/contouring/surrounding.hpp | 1 + src/client/render/UI.cpp | 2 +- src/client/render/api/Models.cpp | 4 +- src/client/render/api/Models.hpp | 4 +- src/client/render/gl/Renderer.cpp | 8 +- src/client/render/vk/Pipeline.cpp | 115 +++++++++++++++- src/client/render/vk/Pipeline.hpp | 2 + src/client/render/vk/Renderer.cpp | 6 +- src/client/world/Chunk.hpp | 15 +-- src/client/world/DistantUniverse.cpp | 102 +++++++++++++-- src/client/world/DistantUniverse.hpp | 15 ++- src/client/world/LocalUniverse.cpp | 18 +++ src/core/data/generational.hpp | 19 +++ src/core/data/math.hpp | 6 + src/core/data/mem.hpp | 4 +- src/core/net/data.hpp | 13 +- src/core/net/io.hpp | 54 ++++++-- src/core/server_handle.hpp | 1 + src/core/world/EdittableChunk.cpp | 2 +- src/core/world/Universe.hpp | 16 ++- src/core/world/Voxel.hpp | 12 +- src/core/world/iterators.cpp | 62 ++++++++- src/core/world/iterators.hpp | 166 ++++++++++++++++++++---- src/core/world/models.hpp | 8 ++ src/server/config.hpp | 4 +- src/server/world/Chunk.hpp | 4 +- src/server/world/SharedParts.hpp | 1 + src/server/world/SharedUniverse.cpp | 8 ++ src/server/world/SharedUniverse.hpp | 2 + src/server/world/Universe.cpp | 124 ++++++++++++++++-- src/server/world/Universe.hpp | 6 + src/tools/generate_models.cpp | 36 ++++++ 43 files changed, 1015 insertions(+), 266 deletions(-) create mode 100644 src/core/world/models.hpp create mode 100644 src/tools/generate_models.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a70c4e..14adae7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/README.md b/README.md index f41dd63..295b601 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/TODO.md b/TODO.md index 95eab38..1d36f0e 100644 --- a/TODO.md +++ b/TODO.md @@ -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 diff --git a/deps/robin_hood/robin_hood.h b/deps/robin_hood/robin_hood.h index 906004b..52e7c3e 100644 --- a/deps/robin_hood/robin_hood.h +++ b/deps/robin_hood/robin_hood.h @@ -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 . @@ -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 @@ -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 +# 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(index) \ + : ROBIN_HOOD(BITNESS); \ + }(x) # else -# include +# 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 -# 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(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; 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 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 -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)...); } #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(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(malloc(bytes)), bytes); + add(assertNotNull(std::malloc(bytes)), bytes); return mHead; } @@ -526,7 +529,7 @@ struct NodeAllocator { // 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 const& x, pair 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 +template struct hash : public std::hash { size_t operator()(T const& obj) const noexcept(noexcept(std::declval>().operator()(std::declval()))) { // call base hash auto result = std::hash::operator()(obj); // return mixed of that, to be save against identity has - return hash_int(static_cast(result)); + return hash_int(static_cast(result)); } }; @@ -769,21 +772,29 @@ struct hash> { template struct hash { size_t operator()(T* ptr) const noexcept { - return hash_int(reinterpret_cast(ptr)); + return hash_int(reinterpret_cast(ptr)); } }; template struct hash> { size_t operator()(std::unique_ptr const& ptr) const noexcept { - return hash_int(reinterpret_cast(ptr.get())); + return hash_int(reinterpret_cast(ptr.get())); } }; template struct hash> { size_t operator()(std::shared_ptr const& ptr) const noexcept { - return hash_int(reinterpret_cast(ptr.get())); + return hash_int(reinterpret_cast(ptr.get())); + } +}; + +template +struct hash::value>::type> { + size_t operator()(Enum e) const noexcept { + using Underlying = typename std::underlying_type::type; + return hash{}(static_cast(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; // 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(mInfo))) { + mInfo += 4; + mKeyVals += 4; + } + if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(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; @@ -1306,10 +1336,11 @@ private: typename std::conditional, hasher>::value, ::robin_hood::detail::identity_hash, ::robin_hood::hash>::type; - *idx = Mix{}(WHash::operator()(key)); - *info = mInfoInc + static_cast(*idx >> mInfoHashShift); - *idx &= mMask; + // the lower InitialInfoNumBits are reserved for info. + auto h = Mix{}(WHash::operator()(key)); + *info = mInfoInc + static_cast((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; using const_iterator = Iter; + 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(detail::assertNotNull( - malloc(calcNumBytesTotal(numElementsWithBuffer)))); + std::malloc(calcNumBytesTotal(numElementsWithBuffer)))); // no need for calloc because clonData does memcpy mInfo = reinterpret_cast(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(detail::assertNotNull( - malloc(calcNumBytesTotal(numElementsWithBuffer)))); + std::malloc(calcNumBytesTotal(numElementsWithBuffer)))); // no need for calloc here because cloneData performs a memcpy. mInfo = reinterpret_cast(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(&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(detail::assertNotNull( - calloc(1, calcNumBytesTotal(numElementsWithBuffer)))); + std::calloc(1, calcNumBytesTotal(numElementsWithBuffer)))); mInfo = reinterpret_cast(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(&mMask)) { - free(mKeyVals); + std::free(mKeyVals); } } diff --git a/resource/content/shaders/Voxel.fs b/resource/content/shaders/Voxel.fs index 2d481f0..62e01bd 100644 --- a/resource/content/shaders/Voxel.fs +++ b/resource/content/shaders/Voxel.fs @@ -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)); } \ No newline at end of file diff --git a/src/client/Client.cpp b/src/client/Client.cpp index 7d5f7e9..52d1d20 100644 --- a/src/client/Client.cpp +++ b/src/client/Client.cpp @@ -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 occlusion; + std::vector 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(); diff --git a/src/client/contouring/Abstract.hpp b/src/client/contouring/Abstract.hpp index be11705..052a33c 100644 --- a/src/client/contouring/Abstract.hpp +++ b/src/client/contouring/Abstract.hpp @@ -31,6 +31,12 @@ namespace contouring { virtual void onNotify(const area_ &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>&) = 0; + /// Free entity model + virtual void onEntityUnload(size_t id) = 0; /// Get options virtual std::string getOptions() const = 0; /// Get camera recommended far range diff --git a/src/client/contouring/FlatDualMC.cpp b/src/client/contouring/FlatDualMC.cpp index 83d6f5c..32e9d24 100644 --- a/src/client/contouring/FlatDualMC.cpp +++ b/src/client/contouring/FlatDualMC.cpp @@ -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> 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 tmp; while (running) { - std::pair, 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, surrounding::corners> ctx; loadQueue.pop(ctx)) { + ZoneScopedN("Chunk"); std::pair 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>& 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, std::pair> out; - TracyPlot("CtLoad", static_cast(loadQueue.size())); + TracyPlot("CtLoad", static_cast(loadQueue.size() + entityLoadQueue.size())); //MAYBE: clear out of range loadQueue.trim(keepDistance * keepDistance) - TracyPlot("CtLoaded", static_cast(loadedQueue.size())); + TracyPlot("CtLoaded", static_cast(loadedQueue.size() + entityLoadedQueue.size())); + + { + std::pair out; + for(auto handle = entityLoadedQueue.extractor(); handle.first(out);) { + if (auto entity = entities.get(out.first)) + *entity = render::Model::Create(out.second); + } + } + + std::pair, std::pair> 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 &tmp, Layer layer) const { + void FlatDualMC::render(const surrounding::corners &surrounding, typeof(render::Model::Data::indices) &out, std::vector &tmp, Layer layer) const { const int SIZE = CHUNK_LENGTH + 3; std::array::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 &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 &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; } - } diff --git a/src/client/contouring/FlatDualMC.hpp b/src/client/contouring/FlatDualMC.hpp index 3c88f06..434a9d2 100644 --- a/src/client/contouring/FlatDualMC.hpp +++ b/src/client/contouring/FlatDualMC.hpp @@ -31,6 +31,13 @@ namespace contouring { /// @note notify for chunks entering view while moving void onNotify(const area_ &, 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>&) 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 &frustum, const glm::llvec3 &offset, int density, bool solid) override; @@ -44,10 +51,13 @@ namespace contouring { //FIXME: use unique_ptr robin_hood::unordered_map>>> buffers; - std::unique_ptr playerModel; + data::generational::view_vector> entities; safe_priority_queue_map, surrounding::corners, int, area_hash> loadQueue; safe_queue, std::pair>> loadedQueue; + struct entity_area_job_t { size_t id; glm::ucvec3 size; std::vector> area; }; + safe_queue entityLoadQueue; + safe_queue> entityLoadedQueue; bool running = true; std::vector workers; @@ -75,6 +85,7 @@ namespace contouring { return static_cast(a) & static_cast(b); } + void render(const surrounding::corners &surrounding, typeof(render::Model::Data::indices)& idx, std::vector& ver, Layer layer) const; void render(const surrounding::corners &surrounding, render::LodModel::LodData& out, std::vector& tmp, Layer layer) const; }; } diff --git a/src/client/contouring/optimizer.hpp b/src/client/contouring/optimizer.hpp index 21d2a5e..67d09db 100644 --- a/src/client/contouring/optimizer.hpp +++ b/src/client/contouring/optimizer.hpp @@ -35,4 +35,10 @@ inline std::vector simplify_lod(std::vector &indices, const std::vect indices.reserve(indices.size() + full.size()); indices.insert(indices.end(), full.begin(), full.end()); return offsets; +} +template +inline void simplify(std::vector &indices, const std::vector &vertices) { + ZoneScopedN("LOD"); + simplify_buffer(indices, indices, vertices); + meshopt_optimizeVertexCache(indices.data(), indices.data(), indices.size(), vertices.size()); } \ No newline at end of file diff --git a/src/client/contouring/surrounding.cpp b/src/client/contouring/surrounding.cpp index 5fb0367..6aabbc1 100644 --- a/src/client/contouring/surrounding.cpp +++ b/src/client/contouring/surrounding.cpp @@ -27,4 +27,14 @@ namespace contouring::surrounding { } return true; } + void load(corners &out, const glm::ucvec3 &areaSize, const glm::ucvec3 &chunkPos, const std::vector> &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(area.at(glm::toIdx(pos, areaSize))); + } else { + out[i] = world::client::EMPTY_CHUNK; + } + } + } } diff --git a/src/client/contouring/surrounding.hpp b/src/client/contouring/surrounding.hpp index 01682aa..202c7a9 100644 --- a/src/client/contouring/surrounding.hpp +++ b/src/client/contouring/surrounding.hpp @@ -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> &area); } diff --git a/src/client/render/UI.cpp b/src/client/render/UI.cpp index b4c1ede..48ab327 100644 --- a/src/client/render/UI.cpp +++ b/src/client/render/UI.cpp @@ -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++) { diff --git a/src/client/render/api/Models.cpp b/src/client/render/api/Models.cpp index a96f7ba..a4f018f 100644 --- a/src/client/render/api/Models.cpp +++ b/src/client/render/api/Models.cpp @@ -43,7 +43,7 @@ const std::pair, std::vector> ColoredShape::LI void indexVBO(const std::vector &in_vertices, std::vector &out_indices, std::vector &out_vertices); Model::Data::Data(const std::vector &vs, const std::vector &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(&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(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(&i_size), sizeof(i_size)); diff --git a/src/client/render/api/Models.hpp b/src/client/render/api/Models.hpp index 314e523..30200fb 100644 --- a/src/client/render/api/Models.hpp +++ b/src/client/render/api/Models.hpp @@ -70,7 +70,7 @@ public: Data() { } Data(const std::vector &vertices, const std::vector &indices); Data(const std::vector &vertices) { index(vertices); } - Data(std::ifstream &in); + Data(std::istream &in); void index(const std::vector &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 Create(const Data& data) { diff --git a/src/client/render/gl/Renderer.cpp b/src/client/render/gl/Renderer.cpp index 2ad2816..0eefdbf 100644 --- a/src/client/render/gl/Renderer.cpp +++ b/src/client/render/gl/Renderer.cpp @@ -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 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(buf)->draw(); @@ -133,6 +132,7 @@ std::function Renderer::begi } void Renderer::postProcess() { + glDisable(GL_FRAMEBUFFER_SRGB); if(SkyEnable) { SkyPass->draw(this); } diff --git a/src/client/render/vk/Pipeline.cpp b/src/client/render/vk/Pipeline.cpp index 1994baa..f59767a 100644 --- a/src/client/render/vk/Pipeline.cpp +++ b/src/client/render/vk/Pipeline.cpp @@ -65,9 +65,9 @@ Pipeline::Pipeline(VkDevice device, const PhysicalDeviceInfo &info, const render if (hasSamples) { colorDepthSubpass.pResolveAttachments = &colorAttachmentResolveRef; } - std::array subpasses = {colorDepthSubpass, colorDepthSubpass, colorDepthSubpass}; + std::array subpasses = {colorDepthSubpass, colorDepthSubpass, colorDepthSubpass, colorDepthSubpass}; - std::array dependencies{}; + std::array 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 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 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; diff --git a/src/client/render/vk/Pipeline.hpp b/src/client/render/vk/Pipeline.hpp index cdb0c1b..7c6067c 100644 --- a/src/client/render/vk/Pipeline.hpp +++ b/src/client/render/vk/Pipeline.hpp @@ -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; diff --git a/src/client/render/vk/Renderer.cpp b/src/client/render/vk/Renderer.cpp index 6e7fdd0..97bcd16 100644 --- a/src/client/render/vk/Renderer.cpp +++ b/src/client/render/vk/Renderer.cpp @@ -454,10 +454,8 @@ void Renderer::beginFrame() { std::function 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(rBuffer); buffer->setLastUse(currentImage); diff --git a/src/client/world/Chunk.hpp b/src/client/world/Chunk.hpp index 9139608..be376ec 100644 --- a/src/client/world/Chunk.hpp +++ b/src/client/world/Chunk.hpp @@ -13,13 +13,13 @@ public: std::optional 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> futureEdits; + robin_hood::unordered_map> futureEdits; }; /// Chunk full of air diff --git a/src/client/world/DistantUniverse.cpp b/src/client/world/DistantUniverse.cpp index e38b958..b6c6e1a 100644 --- a/src/client/world/DistantUniverse.cpp +++ b/src/client/world/DistantUniverse.cpp @@ -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(areas, *fill, [](std::shared_ptr &ck, chunk_pos, chunk_voxel_idx idx, Voxel, Voxel next, float delay) { - ck->apply(Chunk::Edit{next, delay, idx}); - }); + world::iterator::ApplySplit(areas, *fill, floodfillLimit, [](std::shared_ptr &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(&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(&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(&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 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(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(&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(areas, *fill, [&](std::shared_ptr &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(areas, *fill, floodfillLimit, [&](std::shared_ptr &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()); diff --git a/src/client/world/DistantUniverse.hpp b/src/client/world/DistantUniverse.hpp index a8525e5..2f712e4 100644 --- a/src/client/world/DistantUniverse.hpp +++ b/src/client/world/DistantUniverse.hpp @@ -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 shape; + glm::usvec3 size; glm::vec3 scale; + bool permanant; + data::generational::view_vector instances; }; data::generational::view_vector entities; @@ -51,6 +61,7 @@ namespace world::client { options options; ushort serverDistance; + size_t floodfillLimit = CHUNK_SIZE; net::client::Client peer; }; diff --git a/src/client/world/LocalUniverse.cpp b/src/client/world/LocalUniverse.cpp index 2f55b1f..e7b3662 100644 --- a/src/client/world/LocalUniverse.cpp +++ b/src/client/world/LocalUniverse.cpp @@ -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.shape)) { + contouring->onEntityLoad(eId.index, glm::lvec3(1) + glm::divide(entity.size), *area); + } else { + auto model = std::get_if(&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; diff --git a/src/core/data/generational.hpp b/src/core/data/generational.hpp index 103ac6b..ffe0c2c 100644 --- a/src/core/data/generational.hpp +++ b/src/core/data/generational.hpp @@ -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 + 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 &e) { return e.has_value(); }); diff --git a/src/core/data/math.hpp b/src/core/data/math.hpp index 75f3b0c..ba1302e 100644 --- a/src/core/data/math.hpp +++ b/src/core/data/math.hpp @@ -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 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))}; } diff --git a/src/core/data/mem.hpp b/src/core/data/mem.hpp index fc8ed6f..235c267 100644 --- a/src/core/data/mem.hpp +++ b/src/core/data/mem.hpp @@ -9,8 +9,8 @@ namespace data { /// Map vector to istream struct vec_istream: std::streambuf { - vec_istream(std::vector &vec) { - this->setg(&vec[0], &vec[0], &vec[0] + vec.size()); + vec_istream(const std::vector &vec) { + this->setg((char*)&vec[0], (char*)&vec[0], (char*)&vec[0] + vec.size()); } }; diff --git a/src/core/net/data.hpp b/src/core/net/data.hpp index 7740990..8fe6294 100644 --- a/src/core/net/data.hpp +++ b/src/core/net/data.hpp @@ -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 + /// {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}[]} + 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, diff --git a/src/core/net/io.hpp b/src/core/net/io.hpp index 8e7323b..d84984c 100644 --- a/src/core/net/io.hpp +++ b/src/core/net/io.hpp @@ -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 + 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 + 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 { diff --git a/src/core/server_handle.hpp b/src/core/server_handle.hpp index 4a4546e..0adf54a 100644 --- a/src/core/server_handle.hpp +++ b/src/core/server_handle.hpp @@ -14,4 +14,5 @@ struct server_handle { std::function raycast; std::optional teleport; std::optional message; + bool entityChange = false; }; diff --git a/src/core/world/EdittableChunk.cpp b/src/core/world/EdittableChunk.cpp index ea01ed1..d497be0 100644 --- a/src/core/world/EdittableChunk.cpp +++ b/src/core/world/EdittableChunk.cpp @@ -17,7 +17,7 @@ std::optional EdittableChunk::update(float deltaTime, bool animate) { invalidate(it->first); it = edits.erase(it); } else { - it++; + ++it; } } diff --git a/src/core/world/Universe.hpp b/src/core/world/Universe.hpp index 25fdf85..c234203 100644 --- a/src/core/world/Universe.hpp +++ b/src/core/world/Universe.hpp @@ -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>; + /// render::Model::Data serialized + using model_t = std::vector; + + 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 shape; + glm::usvec3 size; glm::vec3 scale; + bool permanant; struct Instance { glm::ifvec3 pos; glm::vec3 velocity; diff --git a/src/core/world/Voxel.hpp b/src/core/world/Voxel.hpp index 9e91f49..589b77d 100644 --- a/src/core/world/Voxel.hpp +++ b/src/core/world/Voxel.hpp @@ -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 diff --git a/src/core/world/iterators.cpp b/src/core/world/iterators.cpp index d233b29..68cce68 100644 --- a/src/core/world/iterators.cpp +++ b/src/core/world/iterators.cpp @@ -2,7 +2,7 @@ using namespace world::iterator; -std::unique_ptr world::iterator::Get(world::action::Shape shape, uint16_t radius) { +std::unique_ptr world::iterator::Get(world::action::Shape shape, uint16_t radius) { switch (shape) { case world::action::Shape::Cube: return std::make_unique(radius); @@ -11,7 +11,18 @@ std::unique_ptr world::iterator::Get(world::action::Shape shape, uint1 case world::action::Shape::SmoothSphere: return std::make_unique(radius); default: - return std::unique_ptr(nullptr); + return std::unique_ptr(nullptr); + } +} +std::unique_ptr world::iterator::GetBorder(world::action::Shape shape, uint16_t radius) { + switch (shape) { + case world::action::Shape::Cube: + return std::make_unique(radius); + case world::action::Shape::RawSphere: + case world::action::Shape::SmoothSphere: + return std::make_unique(radius); + default: + return std::unique_ptr(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; } \ No newline at end of file diff --git a/src/core/world/iterators.hpp b/src/core/world/iterators.hpp index 918f48d..7a702cc 100644 --- a/src/core/world/iterators.hpp +++ b/src/core/world/iterators.hpp @@ -2,51 +2,141 @@ #include "position.h" #include "actions.hpp" +#include namespace world::iterator { using pair = std::pair; -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 Get(action::Shape, uint16_t radius); +std::unique_ptr Get(action::Shape, uint16_t radius); +/// Border sampling from -radius to radius +std::unique_ptr GetBorder(action::Shape, uint16_t radius); +template +inline bool GetChunk(Area& chunks, const std::pair& split, Voxel& out, + typename std::shared_ptr& 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(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 -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 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(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 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(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 +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 joints; + auto &chunks = it->second->setChunks(); + auto iterator = GetBorder(fill.shape, fill.radius+1); + glm::ivec3 point; + typename std::shared_ptr 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(chunks, glm::splitIdx(full), v, ck, ck_pos) && v.is_solid()) + joints.insert(full); + } + bool added = false; + while (!joints.empty()) { + std::queue todo; + todo.push(*joints.begin()); + robin_hood::unordered_set 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(chunks, glm::splitIdx(full), v, ck, ck_pos) && v.is_solid()) { + constexpr std::array 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 +inline void ApplySplit(area_map &areas, action::FillShape fill, size_t floodFillLimit, const CB& callback) { + Apply(areas, fill, callback); + Split(areas, fill, floodFillLimit, [&](const robin_hood::unordered_set& part, Voxel next, + world::ChunkContainer& chunks, std::shared_ptr& ck, chunk_pos& ck_pos, std::shared_ptr&) { + for(auto full: part) { + const auto split = glm::splitIdx(full); + if (Voxel prev; world::iterator::GetChunk(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; diff --git a/src/core/world/models.hpp b/src/core/world/models.hpp new file mode 100644 index 0000000..2a43ccf --- /dev/null +++ b/src/core/world/models.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +namespace world::models { + static const std::vector 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}; +} \ No newline at end of file diff --git a/src/server/config.hpp b/src/server/config.hpp index b650a5b..ea2ffa8 100644 --- a/src/server/config.hpp +++ b/src/server/config.hpp @@ -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(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} })} }); } diff --git a/src/server/world/Chunk.hpp b/src/server/world/Chunk.hpp index 98aecc5..123427f 100644 --- a/src/server/world/Chunk.hpp +++ b/src/server/world/Chunk.hpp @@ -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 &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; diff --git a/src/server/world/SharedParts.hpp b/src/server/world/SharedParts.hpp index e2999b1..da5edb6 100644 --- a/src/server/world/SharedParts.hpp +++ b/src/server/world/SharedParts.hpp @@ -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 &rnd): world::Chunk(), Chunk(pos, rnd), world::client::EdittableChunk() { } SharedChunk(std::istream &str, bool rle = RLE): world::Chunk(str, rle), Chunk(), world::client::EdittableChunk() { } diff --git a/src/server/world/SharedUniverse.cpp b/src/server/world/SharedUniverse.cpp index 5ccd9ac..f6306a1 100644 --- a/src/server/world/SharedUniverse.cpp +++ b/src/server/world/SharedUniverse.cpp @@ -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 SharedUniverse::createChunk(const chunk_pos &pos, const std::unique_ptr &rnd) const { return std::make_shared(pos, rnd); @@ -57,3 +62,6 @@ std::shared_ptr SharedUniverse::createChunk(const chunk_pos &pos, const s std::shared_ptr SharedUniverse::createChunk(std::istream &str) const { return std::make_shared(str); } +std::shared_ptr SharedUniverse::createChunk() const { + return std::make_shared(); +} \ No newline at end of file diff --git a/src/server/world/SharedUniverse.hpp b/src/server/world/SharedUniverse.hpp index b8d7bd6..05c1204 100644 --- a/src/server/world/SharedUniverse.hpp +++ b/src/server/world/SharedUniverse.hpp @@ -15,11 +15,13 @@ namespace world::server { protected: std::shared_ptr createChunk(const chunk_pos &pos, const std::unique_ptr &rnd) const override; std::shared_ptr createChunk(std::istream &str) const override; + std::shared_ptr createChunk() const override; void loadChunk(area_, 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; diff --git a/src/server/world/Universe.cpp b/src/server/world/Universe.cpp index fde1011..9985f26 100644 --- a/src/server/world/Universe.cpp +++ b/src/server/world/Universe.cpp @@ -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(item_count)); @@ -472,6 +474,9 @@ std::optional 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 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->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(*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->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_ 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.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, const world::ChunkContainer &) {} @@ -672,6 +722,7 @@ void Universe::loadChunk(area_, 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_& pos, int radius, action::S return data && !data->handleEdits; }); robin_hood::unordered_map> edits; - world::iterator::Apply(areas, world::action::FillShape(pos, val, shape, radius), + const auto fill = world::action::FillShape(pos, val, shape, radius); + world::iterator::Apply(areas, fill, [&](std::shared_ptr& 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_& pos, int radius, action::S //TODO: inventory ck->replace(idx, next, delay); }); + if (world::iterator::Split(areas, fill, floodFillLimit, [&](const robin_hood::unordered_set& part, Voxel cleanVoxel, + world::ChunkContainer& chunks, std::shared_ptr& ck, chunk_pos& ck_pos, std::shared_ptr& 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.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 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(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(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 Universe::createChunk(const chunk_pos &pos, const std::un } std::shared_ptr Universe::createChunk(std::istream &str) const { return std::make_shared(str); +} +std::shared_ptr Universe::createChunk() const { + return std::make_shared(); } \ No newline at end of file diff --git a/src/server/world/Universe.hpp b/src/server/world/Universe.hpp index 08ad513..ea1b8dc 100644 --- a/src/server/world/Universe.hpp +++ b/src/server/world/Universe.hpp @@ -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_, const std::shared_ptr&); virtual void broadcastMessage(const std::string &); + virtual void broadcastEntities(); using area_map = robin_hood::unordered_map>; virtual std::shared_ptr createChunk(const chunk_pos &pos, const std::unique_ptr &rnd) const; virtual std::shared_ptr createChunk(std::istream &str) const; + virtual std::shared_ptr createChunk() const; virtual void updateChunk(area_map::iterator&, world::ChunkContainer::iterator&, chunk_pos, float deltaTime); virtual void loadChunk(area_, 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; diff --git a/src/tools/generate_models.cpp b/src/tools/generate_models.cpp new file mode 100644 index 0000000..a017e89 --- /dev/null +++ b/src/tools/generate_models.cpp @@ -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 +#include +#include + +/// 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; +} \ No newline at end of file