1
0
Fork 0

Compare commits

...

11 Commits

Author SHA1 Message Date
May B. bce746ca01 Merge workers and other fixs 2020-08-05 15:14:57 +02:00
May B. 310efea675 Basic entities 2020-08-04 20:34:47 +02:00
May B. ff9942691a Generational id 2020-08-04 17:44:53 +02:00
May B. 8d0c9194b0 Areas onload and index 2020-08-03 21:42:09 +02:00
May B. a05378c274 Time constant move, relative planar 2020-08-03 19:55:03 +02:00
May B. f7b34f74f8 Area position 2020-08-03 18:15:02 +02:00
May B. 5c6ccd48e5 Multiverse: Areas
No position for now
2020-08-02 22:15:53 +02:00
May B. f75afb5e1e Voxel bitpacking 2020-08-02 11:47:25 +02:00
May B. 7d125b81d1 Push It To The Limit + HastyNoise 2020-08-01 23:31:01 +02:00
May B. 6ace01afca clang-tidy 2020-08-01 01:17:09 +02:00
May B. 21b95ba5fe Logger, libguarded 2020-08-01 00:11:08 +02:00
67 changed files with 2069 additions and 715 deletions

6
.vscode/tasks.json vendored
View File

@ -10,7 +10,7 @@
"label": "cmake",
"type": "shell",
"command": "cmake",
"args": [".."],
"args": ["-DFIXED_WINDOW=1", ".."],
"options": {
"cwd": "${workspaceRoot}/build"
}
@ -19,7 +19,7 @@
"label": "cmake info",
"type": "shell",
"command": "cmake",
"args": ["-DCMAKE_BUILD_TYPE=RelWithDebInfo", ".."],
"args": ["-DFIXED_WINDOW=1", "-DCMAKE_BUILD_TYPE=RelWithDebInfo", ".."],
"options": {
"cwd": "${workspaceRoot}/build"
}
@ -28,7 +28,7 @@
"label": "cmake debug",
"type": "shell",
"command": "cmake",
"args": ["-DCMAKE_BUILD_TYPE=Debug", ".."],
"args": ["-DFIXED_WINDOW=1", "-DCMAKE_BUILD_TYPE=Debug", ".."],
"options": {
"cwd": "${workspaceRoot}/build"
}

View File

@ -4,13 +4,15 @@ project (univerxel VERSION 0.0.1)
cmake_policy(SET CMP0072 NEW)
find_package(OpenGL REQUIRED)
option(PROFILING "Build with profiling" OFF)
option(PROFILING "Build with profiling" 0)
option(FIXED_WINDOW "Lock window size: Force floating on i3" 0)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
set(CMAKE_CXX_FLAGS "-Wall -Wextra")
# set(CMAKE_CXX_CLANG_TIDY "clang-tidy;-checks=clang-analyzer-*,cppcoreguidelines-*,performance-*,readability-*,-readability-braces-around-statements,-readability-uppercase-literal-suffix")
find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
@ -44,18 +46,14 @@ set(INCLUDE_LIBS
"include/toml++"
"include/Remotery/lib"
"include/robin_hood"
"include/libguarded"
)
add_executable(univerxel "src/main.cpp" ${SOURCES} ${INCLUDE_SOURCES})
target_compile_features(univerxel PUBLIC cxx_std_17)
target_link_libraries(univerxel ${LINKED_LIBS})
target_include_directories(univerxel PRIVATE ${INCLUDE_LIBS})
if(PROFILING)
target_compile_definitions(univerxel PRIVATE RMT_ENABLED=1 RMT_USE_OPENGL=1)
else(PROFILING)
target_compile_definitions(univerxel PRIVATE RMT_ENABLED=0)
endif(PROFILING)
add_dependencies(univerxel generate_dictionary)
target_compile_definitions(univerxel PRIVATE RMT_ENABLED=${PROFILING} RMT_USE_OPENGL=${PROFILING} FIXED_WINDOW=${FIXED_WINDOW} HN_USE_FILESYSTEM=1)
file(COPY content/shaders DESTINATION ${CMAKE_BINARY_DIR}/content)
file(COPY content/textures DESTINATION ${CMAKE_BINARY_DIR}/content)
@ -68,14 +66,13 @@ add_custom_target(docs
)
# Zstd dictionary
file(GLOB SMP_SOURCES "src/chunk_sampler.cpp" "src/world/Chunk.cpp" "include/FastNoiseSIMD/*.cpp")
add_executable(chunk_sampler EXCLUDE_FROM_ALL ${SMP_SOURCES})
target_compile_features(chunk_sampler PUBLIC cxx_std_17)
target_link_libraries(chunk_sampler ${LINKED_LIBS})
target_include_directories(chunk_sampler PRIVATE "include/FastNoiseSIMD")
file(GLOB SMP_SOURCES "src/zstd_sampler.cpp" "src/world/Chunk.cpp" "include/FastNoiseSIMD/*.cpp")
add_executable(zstd_sampler EXCLUDE_FROM_ALL ${SMP_SOURCES})
target_compile_features(zstd_sampler PUBLIC cxx_std_17)
target_link_libraries(zstd_sampler ${LINKED_LIBS})
target_include_directories(zstd_sampler PRIVATE "include/FastNoiseSIMD")
# target_compile_definitions(zstd_sampler PRIVATE HN_USE_FILESYSTEM=1)
add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/content/zstd.dict"
COMMAND "${CMAKE_BINARY_DIR}/chunk_sampler"
COMMAND zstd --train "${CMAKE_BINARY_DIR}/samples/*" -o "${CMAKE_BINARY_DIR}/content/zstd.dict"
DEPENDS chunk_sampler)
COMMAND "${CMAKE_BINARY_DIR}/zstd_sampler" DEPENDS zstd_sampler)
add_custom_target(generate_dictionary DEPENDS "${CMAKE_BINARY_DIR}/content/zstd.dict")

36
TODO.md
View File

@ -4,10 +4,9 @@
- [x] Generate noise
- [x] Density
- [x] Robin hood map
- [ ] In memory RLE
- [ ] Octree world
- [ ] Remove density
- [x] Serialize
- [ ] Variable length encoding
- [x] Group files
- [x] Zstd + custom grouping
- [~] Find best region size
@ -15,13 +14,21 @@
- [x] Low memory: Keep only ifstream
- [x] High memory: Save multiple
- [x] Unload unused
- [ ] In memory RLE
- [x] Edition
- [ ] Entity
- [ ] Planet
- [ ] CubicSphere
- [ ] Healpix
- [ ] Galaxy
- [~] Entity
- [x] Basic
- [ ] Instanced
- https://learnopengl.com/Advanced-OpenGL/Instancing
- [ ] Inheritance
- [ ] ECS
- [ ] Relative to area
- [x] Area
- [x] Offset
- [ ] Rotation
- [ ] Planet
- [ ] CubicSphere
- [ ] Healpix
- [ ] Galaxy
- [ ] Biomes
- https://imgur.com/kM8b5Zq
- https://imgur.com/a/bh2iy
@ -30,14 +37,14 @@
- Valgrind
- Xtree-memory
- [ ] sanitizer
- [ ] clang-tidy
- [ ] cmake
- https://github.com/microsoft/GSL/blob/master/include/gsl/pointers
- [x] clang-tidy
- [ ] clang -fall
- [ ] Server
- [ ] ZeroMQ
- [ ] Mutex guard
- [ ] Shared mutex
- [ ] Logger
- [x] Logger
- [ ] FastNoiseSIMD / HastyNoise double precision
- [x] Generational identifier
- [ ] Limit map usage
## Rendering
- [x] Render triangle
@ -59,6 +66,7 @@
- [ ] Deferred
- [ ] Cascaded shadow maps
- [ ] RayCast
- [x] Float precision problem
## Contouring
- [x] Box contouring

View File

@ -6,5 +6,5 @@ layout(location = 0) out vec4 color;
in vec4 Color;
void main(){
color = Color;
color = vec4(Color.xyz, .5);
}

View File

@ -10,14 +10,14 @@ uniform sampler2DArray HOSAtlas;
uniform mat4 View;
uniform vec3 FogColor;
#ifdef BLEND
#ifdef GEOMETRY
in GeometryData
#else
in VertexData
#endif
{
vec3 Position_worldspace;
#ifdef BLEND
vec3 Position_modelspace;
#ifdef GEOMETRY
flat uint Materials[3];
vec3 MaterialRatio;
#else
@ -39,6 +39,7 @@ vec3 expand(vec3 v) {
}
vec4 getTexture(sampler2DArray sample, vec2 UV) {
#ifdef GEOMETRY
#ifdef BLEND
vec4 colx = texture(sample, vec3(UV, vs.Materials[0]));
if(vs.Materials[1] == vs.Materials[0]) {
@ -50,6 +51,12 @@ vec4 getTexture(sampler2DArray sample, vec2 UV) {
vs.Materials[2] == vs.Materials[1] ? mix(coly, colx, vs.MaterialRatio.x) :
colx * vs.MaterialRatio.x + coly * vs.MaterialRatio.y + texture(sample, vec3(UV, vs.Materials[2])) * vs.MaterialRatio.z);
}
#else
int mainMaterial = vs.MaterialRatio.x >= vs.MaterialRatio.y ?
(vs.MaterialRatio.x >= vs.MaterialRatio.z ? 0 : 2) :
(vs.MaterialRatio.y >= vs.MaterialRatio.z ? 1 : 2);
return texture(sample, vec3(UV, vs.Materials[mainMaterial]));
#endif
#else
return texture(sample, vec3(UV, vs.Material));
#endif
@ -71,9 +78,9 @@ void main() {
vec3 blendWeights = abs(vs.FaceNormal_modelspace);
blendWeights = blendWeights - plateauSize;
blendWeights = pow(max(blendWeights, 0), vec3(transitionSpeed));
vec2 UVx = vs.Position_worldspace.yz * texScale;
vec2 UVy = vs.Position_worldspace.zx * texScale;
vec2 UVz = vs.Position_worldspace.xy * texScale;
vec2 UVx = vs.Position_modelspace.yz * texScale;
vec2 UVy = vs.Position_modelspace.zx * texScale;
vec2 UVz = vs.Position_modelspace.xy * texScale;
vec3 tex = getTriTexture(TextureAtlas, UVx, UVy, UVz, blendWeights);
@ -95,7 +102,7 @@ void main() {
// Cheap planar
vec3 blendWeights = abs(vs.FaceNormal_modelspace);
vec3 nrm = normalize(pow(blendWeights, vec3(80)));
vec2 UV = (vec2(vs.Position_worldspace.xy * nrm.z) + vec2(vs.Position_worldspace.yz * nrm.x) + vec2(vs.Position_worldspace.zx * nrm.y)) * texScale;
vec2 UV = (vec2(vs.Position_modelspace.xy * nrm.z) + vec2(vs.Position_modelspace.yz * nrm.x) + vec2(vs.Position_modelspace.zx * nrm.y)) * texScale;
vec3 tex = getTexture(TextureAtlas, UV).rgb;
#ifdef PBR

View File

@ -5,7 +5,7 @@ layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;
in VertexData {
vec3 Position_worldspace;
vec3 Position_modelspace;
flat uint Material;
vec3 FaceNormal_modelspace;
#ifdef PBR
@ -19,7 +19,7 @@ in VertexData {
} vs_in[];
out GeometryData {
vec3 Position_worldspace;
vec3 Position_modelspace;
flat uint Materials[3];
vec3 MaterialRatio;
vec3 FaceNormal_modelspace;
@ -37,7 +37,7 @@ void main() {
for(int i = 0; i < gl_in.length(); i++) {
gl_Position = gl_in[i].gl_Position;
gs.Position_worldspace = vs_in[i].Position_worldspace;
gs.Position_modelspace = vs_in[i].Position_modelspace;
gs.FaceNormal_modelspace = vs_in[i].FaceNormal_modelspace;
gs.FaceNormal_worldspace = vs_in[i].FaceNormal_worldspace;

View File

@ -5,7 +5,7 @@ layout(location = 1) in uint Material_model;
layout(location = 2) in vec3 Normal_modelspace;
out VertexData {
vec3 Position_worldspace;
vec3 Position_modelspace;
flat uint Material;
vec3 FaceNormal_modelspace;
#ifdef PBR
@ -27,10 +27,11 @@ uniform float FogDepth;
void main(){
gl_Position = MVP * vec4(Position_modelspace, 1);
vs.Position_worldspace = (Model * vec4(Position_modelspace,1)).xyz;
vs.Position_modelspace = Position_modelspace;
vec3 Position_worldspace = (Model * vec4(Position_modelspace,1)).xyz;
#ifdef FOG
vs.Depth = length((View * vec4(vs.Position_worldspace,1)).xyz) / FogDepth;
vs.Depth = length((View * vec4(Position_worldspace,1)).xyz) / FogDepth;
#endif
vs.Material = Material_model;

View File

@ -0,0 +1,25 @@
Copyright (c) 2016, Ansel Sermersheim
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,198 @@
/***********************************************************************
*
* Copyright (c) 2015-2020 Ansel Sermersheim
*
* This file is part of CsLibGuarded.
*
* CsLibGuarded is free software, released under the BSD 2-Clause license.
* For license details refer to LICENSE provided with this project.
*
* CopperSpice is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* https://opensource.org/licenses/BSD-2-Clause
*
***********************************************************************/
#ifndef CSLIBGUARDED_PLAIN_GUARDED_H
#define CSLIBGUARDED_PLAIN_GUARDED_H
#include <memory>
#include <mutex>
namespace libguarded
{
/**
\headerfile cs_plain_guarded.h <CsLibGuarded/cs_plain_guarded.h>
This templated class wraps an object and allows only one thread at a
time to access the protected object.
This class will use std::mutex for the internal locking mechanism by
default. Other classes which are useful for the mutex type are
std::recursive_mutex, std::timed_mutex, and
std::recursive_timed_mutex.
The handle returned by the various lock methods is moveable but not
copyable.
*/
template <typename T, typename M = std::mutex>
class plain_guarded
{
private:
class deleter;
public:
using handle = std::unique_ptr<T, deleter>;
/**
Construct a guarded object. This constructor will accept any
number of parameters, all of which are forwarded to the
constructor of T.
*/
template <typename... Us>
plain_guarded(Us &&... data);
/**
Acquire a handle to the protected object. As a side effect, the
protected object will be locked from access by any other
thread. The lock will be automatically released when the handle
is destroyed.
*/
[[nodiscard]] handle lock();
/**
Attempt to acquire a handle to the protected object. Returns a
null handle if the object is already locked. As a side effect,
the protected object will be locked from access by any other
thread. The lock will be automatically released when the handle
is destroyed.
*/
[[nodiscard]] handle try_lock();
/**
Attempt to acquire a handle to the protected object. As a side
effect, the protected object will be locked from access by any
other thread. The lock will be automatically released when the
handle is destroyed.
Returns a null handle if the object is already locked, and does
not become available for locking before the time duration has
elapsed.
Calling this method requires that the underlying mutex type M
supports the try_lock_for method. This is not true if M is the
default std::mutex.
*/
template <class Duration>
[[nodiscard]] handle try_lock_for(const Duration &duration);
/**
Attempt to acquire a handle to the protected object. As a side
effect, the protected object will be locked from access by any other
thread. The lock will be automatically released when the handle is
destroyed.
Returns a null handle if the object is already locked, and does not
become available for locking before reaching the specified timepoint.
Calling this method requires that the underlying mutex type M
supports the try_lock_until method. This is not true if M is the
default std::mutex.
*/
template <class TimePoint>
[[nodiscard]] handle try_lock_until(const TimePoint &timepoint);
private:
T m_obj;
M m_mutex;
};
template <typename T, typename M>
class plain_guarded<T, M>::deleter
{
public:
using pointer = T *;
deleter(std::unique_lock<M> lock);
void operator()(T *ptr);
private:
std::unique_lock<M> m_lock;
};
template <typename T, typename M>
plain_guarded<T, M>::deleter::deleter(std::unique_lock<M> lock)
: m_lock(std::move(lock))
{
}
template <typename T, typename M>
void plain_guarded<T, M>::deleter::operator()(T *)
{
if (m_lock.owns_lock()) {
m_lock.unlock();
}
}
template <typename T, typename M>
template <typename... Us>
plain_guarded<T, M>::plain_guarded(Us &&... data)
: m_obj(std::forward<Us>(data)...)
{
}
template <typename T, typename M>
auto plain_guarded<T, M>::lock() -> handle
{
std::unique_lock<M> lock(m_mutex);
return handle(&m_obj, deleter(std::move(lock)));
}
template <typename T, typename M>
auto plain_guarded<T, M>::try_lock() -> handle
{
std::unique_lock<M> lock(m_mutex, std::try_to_lock);
if (lock.owns_lock()) {
return handle(&m_obj, deleter(std::move(lock)));
} else {
return handle(nullptr, deleter(std::move(lock)));
}
}
template <typename T, typename M>
template <typename Duration>
auto plain_guarded<T, M>::try_lock_for(const Duration &d) -> handle
{
std::unique_lock<M> lock(m_mutex, d);
if (lock.owns_lock()) {
return handle(&m_obj, deleter(std::move(lock)));
} else {
return handle(nullptr, deleter(std::move(lock)));
}
}
template <typename T, typename M>
template <typename TimePoint>
auto plain_guarded<T, M>::try_lock_until(const TimePoint &tp) -> handle
{
std::unique_lock<M> lock(m_mutex, tp);
if (lock.owns_lock()) {
return handle(&m_obj, deleter(std::move(lock)));
} else {
return handle(nullptr, deleter(std::move(lock)));
}
}
template <typename T, typename M = std::mutex>
using guarded [[deprecated("renamed to plain_guarded")]] = plain_guarded<T, M>;
} // namespace libguarded
#endif

View File

@ -0,0 +1,234 @@
/***********************************************************************
*
* Copyright (c) 2015-2020 Ansel Sermersheim
*
* This file is part of CsLibGuarded.
*
* CsLibGuarded is free software, released under the BSD 2-Clause license.
* For license details refer to LICENSE provided with this project.
*
* CopperSpice is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* https://opensource.org/licenses/BSD-2-Clause
*
***********************************************************************/
#ifndef CSLIBGUARDED_SHARED_GUARDED_H
#define CSLIBGUARDED_SHARED_GUARDED_H
#include <memory>
#include <shared_mutex>
namespace libguarded
{
/**
\headerfile cs_shared_guarded.h <CsLibGuarded/cs_shared_guarded.h>
This templated class wraps an object and allows only one thread at
a time to modify the protected object.
This class will use std::shared_timed_mutex for the internal
locking mechanism by default. In C++17 the std::shared_mutex class
is also available.
The handle returned by the various lock methods is moveable but not
copyable.
*/
template <typename T, typename M = std::shared_timed_mutex, typename L = std::shared_lock<M>>
class shared_guarded
{
private:
class deleter;
class shared_deleter;
public:
using handle = std::unique_ptr<T, deleter>;
using shared_handle = std::unique_ptr<const T, shared_deleter>;
template <typename... Us>
shared_guarded(Us &&... data);
// exclusive access
[[nodiscard]] handle lock();
[[nodiscard]] handle try_lock();
template <class Duration>
[[nodiscard]] handle try_lock_for(const Duration &duration);
template <class TimePoint>
[[nodiscard]] handle try_lock_until(const TimePoint &timepoint);
// shared access, note "shared" in method names
[[nodiscard]] shared_handle lock_shared() const;
[[nodiscard]] shared_handle try_lock_shared() const;
template <class Duration>
[[nodiscard]] shared_handle try_lock_shared_for(const Duration &duration) const;
template <class TimePoint>
[[nodiscard]] shared_handle try_lock_shared_until(const TimePoint &timepoint) const;
private:
T m_obj;
mutable M m_mutex;
};
template <typename T, typename M, typename L>
class shared_guarded<T, M, L>::deleter
{
public:
using pointer = T *;
deleter(std::unique_lock<M> lock);
void operator()(T *ptr);
private:
std::unique_lock<M> m_lock;
};
template <typename T, typename M, typename L>
shared_guarded<T, M, L>::deleter::deleter(std::unique_lock<M> lock)
: m_lock(std::move(lock))
{
}
template <typename T, typename M, typename L>
void shared_guarded<T, M, L>::deleter::operator()(T *)
{
if (m_lock.owns_lock()) {
m_lock.unlock();
}
}
template <typename T, typename M, typename L>
class shared_guarded<T, M, L>::shared_deleter
{
public:
using pointer = const T *;
shared_deleter(L lock);
void operator()(const T *ptr);
private:
L m_lock;
};
template <typename T, typename M, typename L>
shared_guarded<T, M, L>::shared_deleter::shared_deleter(L lock)
: m_lock(std::move(lock))
{
}
template <typename T, typename M, typename L>
void shared_guarded<T, M, L>::shared_deleter::operator()(const T *)
{
if (m_lock.owns_lock()) {
m_lock.unlock();
}
}
template <typename T, typename M, typename L>
template <typename... Us>
shared_guarded<T, M, L>::shared_guarded(Us &&... data)
: m_obj(std::forward<Us>(data)...)
{
}
template <typename T, typename M, typename L>
auto shared_guarded<T, M, L>::lock() -> handle
{
std::unique_lock<M> lock(m_mutex);
return handle(&m_obj, deleter(std::move(lock)));
}
template <typename T, typename M, typename L>
auto shared_guarded<T, M, L>::try_lock() -> handle
{
std::unique_lock<M> lock(m_mutex, std::try_to_lock);
if (lock.owns_lock()) {
return handle(&m_obj, deleter(std::move(lock)));
} else {
return handle(nullptr, deleter(std::move(lock)));
}
}
template <typename T, typename M, typename L>
template <typename Duration>
auto shared_guarded<T, M, L>::try_lock_for(const Duration &duration) -> handle
{
std::unique_lock<M> lock(m_mutex, duration);
if (lock.owns_lock()) {
return handle(&m_obj, deleter(std::move(lock)));
} else {
return handle(nullptr, deleter(std::move(lock)));
}
}
template <typename T, typename M, typename L>
template <typename TimePoint>
auto shared_guarded<T, M, L>::try_lock_until(const TimePoint &timepoint) -> handle
{
std::unique_lock<M> lock(m_mutex, timepoint);
if (lock.owns_lock()) {
return handle(&m_obj, deleter(std::move(lock)));
} else {
return handle(nullptr, deleter(std::move(lock)));
}
}
template <typename T, typename M, typename L>
auto shared_guarded<T, M, L>::lock_shared() const -> shared_handle
{
L lock(m_mutex);
return shared_handle(&m_obj, shared_deleter(std::move(lock)));
}
template <typename T, typename M, typename L>
auto shared_guarded<T, M, L>::try_lock_shared() const -> shared_handle
{
L lock(m_mutex, std::try_to_lock);
if (lock.owns_lock()) {
return shared_handle(&m_obj, shared_deleter(std::move(lock)));
} else {
return shared_handle(nullptr, shared_deleter(std::move(lock)));
}
}
template <typename T, typename M, typename L>
template <typename Duration>
auto shared_guarded<T, M, L>::try_lock_shared_for(const Duration &d) const -> shared_handle
{
L lock(m_mutex, d);
if (lock.owns_lock()) {
return shared_handle(&m_obj, shared_deleter(std::move(lock)));
} else {
return shared_handle(nullptr, shared_deleter(std::move(lock)));
}
}
template <typename T, typename M, typename L>
template <typename TimePoint>
auto shared_guarded<T, M, L>::try_lock_shared_until(const TimePoint &tp) const -> shared_handle
{
L lock(m_mutex, tp);
if (lock.owns_lock()) {
return shared_handle(&m_obj, shared_deleter(std::move(lock)));
} else {
return shared_handle(nullptr, shared_deleter(std::move(lock)));
}
}
} // namespace libguarded
#endif

View File

@ -1,32 +0,0 @@
/**
* \file chunk_sampler.cpp
* \brief Generate uncompressed chunks
* \author Maelys Bois
* \version 0.0.1
*
* Generate random uncompressed chunks for Zstd dictionary training.
*/
#include "world/Chunk.hpp"
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <ctime>
#include <filesystem>
/// Entry point
int main(int, char *[])
{
std::srand(std::time(nullptr));
world::Generator generator(std::rand());
std::filesystem::create_directories("samples");
for (size_t i = 0; i < 10000; i++)
{
world::Chunk chunk(chunk_pos(std::rand(), std::rand(), std::rand()), generator);
std::ofstream out("samples/" + std::to_string(i));
chunk.write(out);
out.close();
}
return 0;
}

View File

@ -4,7 +4,6 @@
#include "../data/geometry/Frustum.hpp"
#include "../data/geometry/Faces.hpp"
#include "../world/forward.h"
typedef glm::vec3 camera_pos;
/// Mesh creation
namespace contouring {
@ -16,20 +15,23 @@ namespace contouring {
/// Each frame ping.
/// Mostly used for cleanup and to flush buffers data using main thread
virtual void update(const camera_pos &pos) = 0;
virtual void update(const voxel_pos &pos, const world::area_map &areas) = 0;
/// Chunk data change
virtual void onUpdate(const chunk_pos &pos, const world::chunk_map& data, geometry::Faces neighbors) = 0;
/// @param offset priority position offset
virtual void onUpdate(const area_<chunk_pos> &pos, const chunk_pos &offset, const world::ChunkContainer &data, geometry::Faces neighbors) = 0;
/// Chunk existante ping
/// @note notify for chunks entering view while moving
virtual void onNotify(const chunk_pos &pos, const world::chunk_map &data) = 0;
virtual void onNotify(const area_<chunk_pos> &pos, const chunk_pos &offset, const world::ChunkContainer &data) = 0;
/// Display ImGui config
virtual void onGui() = 0;
/// Get options
virtual std::string getOptions() = 0;
virtual std::string getOptions() const = 0;
// Get camera recommended far range
virtual std::pair<float, float> getFarRange() const = 0;
/// Get buffers in frustum with model matrices
/// @note buffers invalidated after update
virtual void getModels(std::vector<std::pair<glm::mat4, buffer::Abstract *const>> &buffers, const std::optional<geometry::Frustum>& frustum, float scale) = 0;
virtual void getModels(std::vector<std::pair<glm::mat4, buffer::Abstract *const>> &buffers, const std::optional<geometry::Frustum>& frustum, const glm::llvec3& offset, int density) = 0;
};
}

View File

@ -1,23 +1,42 @@
#include "AbstractFlat.hpp"
#include <imgui.h>
#include <imgui.h> // NOLINT
#include <toml.h>
#include "../world/Chunk.hpp"
#include "../world/Area.hpp"
namespace contouring {
void AbstractFlat::update(const camera_pos& pos) {
center = glm::divide(pos, chunk_voxel_pos(CHUNK_LENGTH));
auto it = buffers.begin();
while (it != buffers.end()) { // Remove out of range buffers
if (inKeepRange(it->first)) {
it++;
} else {
if(it->second != NULL)
delete it->second;
size_t AbstractFlat::clear(const voxel_pos& pos, const world::area_map& areas) {
size_t buffer_count = 0;
auto it_a = buffers.begin();
while (it_a != buffers.end()) { // Remove out of range buffers
if (const auto area = areas.find(it_a->first); area != areas.end()) {
//Update
it_a->second.first = area->second->getOffset();
const auto center = glm::divide(pos - it_a->second.first.as_voxel());
auto &bfs = it_a->second.second;
auto it = bfs.begin();
while(it != bfs.end()) {
if (glm::length2(center - it->first) > glm::pow2(keepDistance)) {
if(it->second != NULL)
delete it->second;
it = buffers.erase(it);
it = bfs.erase(it);
} else {
buffer_count++;
++it;
}
}
++it_a;
} else {
for(auto& buffer: it_a->second.second) {
if(buffer.second != NULL)
delete buffer.second;
}
it_a = buffers.erase(it_a);
}
}
return buffer_count;
}
AbstractFlat::AbstractFlat(const std::string& str): Abstract() {
@ -25,7 +44,7 @@ namespace contouring {
loadDistance = opt["load_distance"].value_or(loadDistance);
keepDistance = opt["keep_distance"].value_or(keepDistance);
}
std::string AbstractFlat::getOptions() {
std::string AbstractFlat::getOptions() const {
std::ostringstream ss;
ss << toml::table({
{"load_distance", loadDistance},
@ -33,17 +52,22 @@ namespace contouring {
});
return ss.str();
}
std::pair<float, float> AbstractFlat::getFarRange() const {
return std::make_pair((loadDistance - 1.5) * CHUNK_LENGTH, (keepDistance + .5) * CHUNK_LENGTH);
}
void AbstractFlat::onGui() {
ImGui::SliderInt("Load Distance", &loadDistance, 1, keepDistance);
ImGui::SliderInt("Keep Distance", &keepDistance, loadDistance+1, 21);
}
void AbstractFlat::getModels(std::vector<std::pair<glm::mat4, buffer::Abstract *const>> &out, const std::optional<geometry::Frustum> &frustum, float scale) {
const auto scaling = glm::scale(glm::mat4(1), glm::vec3(scale));
for (const auto [pos, buffer] : buffers) {
if (buffer != NULL && (!frustum.has_value() || frustum.value().contains(geometry::Box::fromMin(scale * glm::vec3(pos) * glm::vec3(CHUNK_LENGTH), scale * glm::vec3(CHUNK_LENGTH)))))
out.emplace_back(glm::translate(scaling, glm::vec3(pos) * glm::vec3(CHUNK_LENGTH)), buffer);
}
void AbstractFlat::getModels(std::vector<std::pair<glm::mat4, buffer::Abstract *const>> &out, const std::optional<geometry::Frustum> &frustum, const glm::llvec3& offset, int density) {
const auto scaling = glm::scale(glm::mat4(1), glm::vec3(1.f / density));
for (const auto [_, area] : buffers) {
for (const auto [pos, buffer] : area.second) {
const glm::vec3 fPos = (glm::vec3(area.first.raw_as_long() + glm::multiply(pos) - offset * glm::llvec3(density)) + area.first.offset) / glm::vec3(density);
if (buffer != NULL && (!frustum.has_value() || frustum.value().contains(geometry::Box::fromMin(fPos, glm::vec3(CHUNK_LENGTH / (float)density)))))
out.emplace_back(glm::translate(scaling, fPos * (float)density), buffer);
}}
}
}

View File

@ -10,28 +10,20 @@ namespace contouring {
AbstractFlat(const std::string &str);
virtual ~AbstractFlat() {}
/// Each frame ping. Used to clear out of range
void update(const camera_pos &) override;
/// Display ImGui config
void onGui() override;
std::string getOptions() override;
std::string getOptions() const override;
std::pair<float, float> getFarRange() const override;
/// Get buffers in frustum with model matrices
/// @note buffers invalidated after update
void getModels(std::vector<std::pair<glm::mat4, buffer::Abstract *const>> &out, const std::optional<geometry::Frustum> &frustum, float scale) override;
void getModels(std::vector<std::pair<glm::mat4, buffer::Abstract *const>> &out, const std::optional<geometry::Frustum> &frustum, const glm::llvec3& offset, int density) override;
protected:
bool inline inLoadRange(const chunk_pos& point) const {
return glm::length2(center - point) <= loadDistance * loadDistance;
}
bool inline inKeepRange(const chunk_pos& point) const {
return glm::length2(center - point) <= keepDistance * keepDistance;
}
size_t clear(const voxel_pos &, const world::area_map &areas);
robin_hood::unordered_map<chunk_pos, buffer::Abstract *> buffers;
chunk_pos center = chunk_pos(INT_MAX);
robin_hood::unordered_map<area_id, robin_hood::pair<area_pos, robin_hood::unordered_map<chunk_pos, buffer::Abstract *>>> buffers;
int loadDistance = 3;
int keepDistance = 4;

View File

@ -9,11 +9,12 @@ namespace contouring {
Dummy(): Abstract() { }
virtual ~Dummy() { }
void update(const camera_pos &) override { }
void onUpdate(const chunk_pos &, const world::chunk_map &, geometry::Faces) override {}
void onNotify(const chunk_pos &, const world::chunk_map &) override { }
void update(const voxel_pos &, const world::area_map &) override {}
void onUpdate(const area_<chunk_pos> &, const chunk_pos &, const world::ChunkContainer &, geometry::Faces) override {}
void onNotify(const area_<chunk_pos> &, const chunk_pos &, const world::ChunkContainer &) override {}
void onGui() override { }
std::string getOptions() override { return ""; }
void getModels(std::vector<std::pair<glm::mat4, buffer::Abstract *const>> &, const std::optional<geometry::Frustum>&, float) override { }
std::string getOptions() const override { return ""; }
std::pair<float, float> getFarRange() const override { return std::make_pair(0, 0); }
void getModels(std::vector<std::pair<glm::mat4, buffer::Abstract *const>> &, const std::optional<geometry::Frustum>&, const glm::llvec3&, int) override { }
};
}

View File

@ -2,8 +2,8 @@
#include "../world/Chunk.hpp"
#include "../world/materials.hpp"
#include <Remotery.h>
#include <imgui.h>
#include <Remotery.h> // NOLINT
#include <imgui.h> // NOLINT
#include <toml.h>
#include "dualmc.h"
@ -16,7 +16,7 @@ namespace contouring {
for (size_t i = 1; i <= 2; i++) {
workers.emplace_back([&] {
while (running) {
std::pair<chunk_pos, surrounding::corners> ctx;
std::pair<area_<chunk_pos>, surrounding::corners> ctx;
loadQueue.wait();
if (loadQueue.pop(ctx)) {
rmt_ScopedCPUSample(ProcessContouring, 0);
@ -30,7 +30,7 @@ namespace contouring {
}
FlatDualMC::~FlatDualMC() {
running = false;
loadQueue.notify();
loadQueue.notify_all();
for(auto& worker: workers) {
if (worker.joinable())
@ -38,7 +38,7 @@ namespace contouring {
}
}
std::string FlatDualMC::getOptions() {
std::string FlatDualMC::getOptions() const {
std::ostringstream ss;
ss << toml::table({
{"load_distance", loadDistance},
@ -49,51 +49,52 @@ namespace contouring {
return ss.str();
}
void FlatDualMC::enqueue(const chunk_pos &pos, const world::chunk_map &data) {
void FlatDualMC::enqueue(const area_<chunk_pos> &pos, const chunk_pos &offset, const world::ChunkContainer &data) {
rmt_ScopedCPUSample(EnqueueContouring, RMTSF_Aggregate);
const auto dist2 = glm::length2(pos - center);
const auto dist2 = glm::length2(offset - pos.second);
if (dist2 <= loadDistance * loadDistance) {
surrounding::corners surrounding;
if(surrounding::load(surrounding, pos, data)) {
if(surrounding::load(surrounding, pos.second, data)) {
loadQueue.push(pos, surrounding, -dist2);
}
}
}
void FlatDualMC::onUpdate(const chunk_pos &pos, const world::chunk_map &data, geometry::Faces neighbors) {
enqueue(pos, data);
void FlatDualMC::onUpdate(const area_<chunk_pos> &pos, const chunk_pos &offset, const world::ChunkContainer &data, geometry::Faces neighbors) {
enqueue(pos, offset, data);
if (neighbors && (geometry::Faces::Left | geometry::Faces::Down | geometry::Faces::Backward)) {
for (size_t i = 1; i < 8; i++) {
enqueue(pos - surrounding::g_corner_offsets[i], data);
enqueue(std::make_pair(pos.first, pos.second - surrounding::g_corner_offsets[i]), offset, data);
}
}
}
void FlatDualMC::onNotify(const chunk_pos &pos, const world::chunk_map &data) {
if (buffers.find(pos) == buffers.end()) {
enqueue(pos, data);
void FlatDualMC::onNotify(const area_<chunk_pos> &pos, const chunk_pos &offset, const world::ChunkContainer &data) {
const auto it = buffers.find(pos.first);
if(it == buffers.end() || it->second.second.find(pos.second) == it->second.second.end()) {
enqueue(pos, offset, data);
}
}
void FlatDualMC::update(const camera_pos& pos) {
AbstractFlat::update(pos);
std::pair<chunk_pos, buffer::ShortIndexed::Data> out;
void FlatDualMC::update(const voxel_pos& pos, const world::area_map& areas) {
std::pair<area_<chunk_pos>, buffer::ShortIndexed::Data> out;
reports.load.push(loadQueue.size());
//MAYBE: clear out of range loadQueue.trim(keepDistance * keepDistance)
reports.loaded.push(loadedQueue.size());
while(loadedQueue.pop(out)) {
const auto buffer = new buffer::ShortIndexed(GL_TRIANGLES, out.second);
const auto it = buffers.find(out.first);
if (it != buffers.end()) {
auto &bfs = buffers[out.first.first].second; //NOTE: buffer.first uninitialized (will be set in clear())
if (const auto it = bfs.find(out.first.second); it != bfs.end()) {
if(it->second != NULL)
delete it->second;
it->second = buffer;
} else {
buffers.emplace(out.first, buffer);
bfs.emplace(out.first.second, buffer);
}
}
reports.count.push(buffers.size());
size_t count = AbstractFlat::clear(pos, areas);
reports.count.push(count);
}
void FlatDualMC::onGui() {
@ -109,15 +110,16 @@ namespace contouring {
void FlatDualMC::render(const surrounding::corners &surrounding, buffer::ShortIndexed::Data &out) const {
const int SIZE = CHUNK_LENGTH + 3;
std::vector<dualmc::DualMC<float>::Point> grid;
std::array<dualmc::DualMC<float>::Point, SIZE * SIZE * SIZE> grid;
{
grid.reserve(SIZE * SIZE * SIZE);
for (int z = 0; z < SIZE; z++) {
for (int y = 0; y < SIZE; y++) {
for (int x = 0; x < SIZE; x++) {
auto &cell = grid[((z * SIZE) + y) * SIZE + x];
const auto &chunk = surrounding[(z >= CHUNK_LENGTH) + (y >= CHUNK_LENGTH)*2 + (x >= CHUNK_LENGTH)*4];
const auto &voxel = chunk->getAt(chunk_voxel_pos(x % CHUNK_LENGTH, y % CHUNK_LENGTH, z % CHUNK_LENGTH));
grid.emplace_back(voxel.Density * 1.f / UCHAR_MAX, voxel.Material);
const auto &voxel = chunk->get(glm::toIdx(x % CHUNK_LENGTH, y % CHUNK_LENGTH, z % CHUNK_LENGTH));
cell.x = voxel.density() * 1.f / world::Voxel::DENSITY_MAX;
cell.w = voxel.material();
}}}
}
{

View File

@ -9,8 +9,6 @@
#include "../render/buffer/ShortIndexed.hpp"
#include <thread>
#define REPORT_BUFFER_SIZE 128
using namespace data;
namespace contouring {
/// Dual Marching Cube 1:1 contouring
@ -19,32 +17,32 @@ namespace contouring {
FlatDualMC(const std::string&);
virtual ~FlatDualMC();
void update(const camera_pos&) override;
void update(const voxel_pos&, const world::area_map&) override;
void onGui() override;
std::string getOptions() override;
std::string getOptions() const override;
/// Chunk data change
void onUpdate(const chunk_pos &, const world::chunk_map &, geometry::Faces) override;
void onUpdate(const area_<chunk_pos> &, const chunk_pos &, const world::ChunkContainer &, geometry::Faces) override;
/// Chunk existante ping
/// @note notify for chunks entering view while moving
void onNotify(const chunk_pos &, const world::chunk_map &) override;
void onNotify(const area_<chunk_pos> &, const chunk_pos &, const world::ChunkContainer &) override;
protected:
safe_priority_queue_map<chunk_pos, surrounding::corners, int> loadQueue;
safe_queue<std::pair<chunk_pos, buffer::ShortIndexed::Data>> loadedQueue;
safe_priority_queue_map<area_<chunk_pos>, surrounding::corners, int, area_hash> loadQueue;
safe_queue<std::pair<area_<chunk_pos>, buffer::ShortIndexed::Data>> loadedQueue;
struct report {
circular_buffer<float> count = circular_buffer<float>(REPORT_BUFFER_SIZE, 0); // MAYBE: store int
circular_buffer<float> load = circular_buffer<float>(REPORT_BUFFER_SIZE, 0);
circular_buffer<float> loaded = circular_buffer<float>(REPORT_BUFFER_SIZE, 0);
report_buffer count;
report_buffer load;
report_buffer loaded;
} reports;
bool running = true;
std::vector<std::thread> workers;
void enqueue(const chunk_pos &, const world::chunk_map &);
void enqueue(const area_<chunk_pos> &, const chunk_pos &offset, const world::ChunkContainer &);
float iso = .1f;
bool manifold = true;

View File

@ -2,8 +2,8 @@
#include "boxing.hpp"
#include "../world/Chunk.hpp"
#include <Remotery.h>
#include <imgui.h>
#include <Remotery.h> // NOLINT
#include <imgui.h> // NOLINT
using namespace geometry;
namespace contouring {
@ -11,7 +11,7 @@ namespace contouring {
for (size_t i = 1; i <= 4; i++) {
workers.emplace_back([&] {
while (running) {
std::pair<chunk_pos, surrounding::faces> ctx;
std::pair<area_<chunk_pos>, surrounding::faces> ctx;
loadQueue.wait();
if (loadQueue.pop(ctx)) {
rmt_ScopedCPUSample(ProcessContouring, 0);
@ -28,7 +28,7 @@ namespace contouring {
}
FlatSurroundingBox::~FlatSurroundingBox() {
running = false;
loadQueue.notify();
loadQueue.notify_all();
for(auto& worker: workers) {
if (worker.joinable())
@ -36,63 +36,64 @@ namespace contouring {
}
}
void FlatSurroundingBox::enqueue(const chunk_pos &pos, const world::chunk_map &data) {
void FlatSurroundingBox::enqueue(const area_<chunk_pos> &pos, const chunk_pos& offset, const world::ChunkContainer &data) {
rmt_ScopedCPUSample(EnqueueContouring, RMTSF_Aggregate);
const auto dist2 = glm::length2(pos - center);
const auto dist2 = glm::length2(offset - pos.second);
if (dist2 <= loadDistance * loadDistance) {
surrounding::faces surrounding;
if(surrounding::load(surrounding, pos, data)) {
if(surrounding::load(surrounding, pos.second, data)) {
loadQueue.push(pos, surrounding, -dist2);
}
}
}
void FlatSurroundingBox::onUpdate(const chunk_pos &pos, const world::chunk_map &data, Faces neighbors) {
enqueue(pos, data);
void FlatSurroundingBox::onUpdate(const area_<chunk_pos> &pos, const chunk_pos& offset, const world::ChunkContainer &data, Faces neighbors) {
enqueue(pos, offset, data);
if (neighbors && Faces::Right)
enqueue(pos + g_face_offsets[static_cast<int>(Face::Right)], data);
enqueue(std::make_pair(pos.first, pos.second + g_face_offsets[static_cast<int>(Face::Right)]), offset, data);
if (neighbors && Faces::Left)
enqueue(pos + g_face_offsets[static_cast<int>(Face::Left)], data);
enqueue(std::make_pair(pos.first, pos.second + g_face_offsets[static_cast<int>(Face::Left)]), offset, data);
if (neighbors && Faces::Up)
enqueue(pos + g_face_offsets[static_cast<int>(Face::Up)], data);
enqueue(std::make_pair(pos.first, pos.second + g_face_offsets[static_cast<int>(Face::Up)]), offset, data);
if (neighbors && Faces::Down)
enqueue(pos + g_face_offsets[static_cast<int>(Face::Down)], data);
enqueue(std::make_pair(pos.first, pos.second + g_face_offsets[static_cast<int>(Face::Down)]), offset, data);
if (neighbors && Faces::Forward)
enqueue(pos + g_face_offsets[static_cast<int>(Face::Forward)], data);
enqueue(std::make_pair(pos.first, pos.second + g_face_offsets[static_cast<int>(Face::Forward)]), offset, data);
if (neighbors && Faces::Backward)
enqueue(pos + g_face_offsets[static_cast<int>(Face::Backward)], data);
enqueue(std::make_pair(pos.first, pos.second + g_face_offsets[static_cast<int>(Face::Backward)]), offset, data);
}
void FlatSurroundingBox::onNotify(const chunk_pos &pos, const world::chunk_map &data) {
if (buffers.find(pos) == buffers.end()) {
enqueue(pos, data);
void FlatSurroundingBox::onNotify(const area_<chunk_pos> &pos, const chunk_pos& offset, const world::ChunkContainer &data) {
const auto it = buffers.find(pos.first);
if(it == buffers.end() || it->second.second.find(pos.second) == it->second.second.end()) {
enqueue(pos, offset, data);
}
}
void FlatSurroundingBox::update(const camera_pos& pos) {
AbstractFlat::update(pos);
std::pair<chunk_pos, buffer::ShortIndexed::Data> out;
void FlatSurroundingBox::update(const voxel_pos& pos, const world::area_map& areas) {
std::pair<area_<chunk_pos>, buffer::ShortIndexed::Data> out;
reports.load.push(loadQueue.size());
//MAYBE: clear out of range loadQueue.trim(keepDistance * keepDistance)
reports.loaded.push(loadedQueue.size());
while(loadedQueue.pop(out)) {
const auto buffer = new buffer::ShortIndexed(GL_TRIANGLES, out.second);
const auto it = buffers.find(out.first);
if (it != buffers.end()) {
auto& bfs = buffers[out.first.first].second; //NOTE: buffer.first uninitialized
if (const auto it = bfs.find(out.first.second); it != bfs.end()) {
if(it->second != NULL)
delete it->second;
it->second = buffer;
} else {
buffers.emplace(out.first, buffer);
bfs.emplace(out.first.second, buffer);
}
}
reports.count.push(buffers.size());
size_t count = AbstractFlat::clear(pos, areas);
reports.count.push(count);
}
void FlatSurroundingBox::onGui() {
@ -104,22 +105,22 @@ namespace contouring {
}
bool FlatSurroundingBox::isTransparent(const surrounding::faces &surrounding, const std::pair<ushort, ushort> &idx) {
return surrounding[idx.first]->get(idx.second).Density < UCHAR_MAX; // MAYBE: materials::transparent
return surrounding[idx.first]->get(idx.second).density() < world::Voxel::DENSITY_MAX; // MAYBE: materials::transparent
}
void FlatSurroundingBox::render(const surrounding::faces &surrounding, std::vector<buffer::VertexData> &vertices) {
const auto center = surrounding[surrounding::CENTER];
vertices.clear();
for (ushort i = 0; i < CHUNK_SIZE; i++) {
if (center->get(i).Density > 0) {
Faces faces = center->get(i).Density < UCHAR_MAX ? Faces::All :
for (ushort i = 0; i < world::CHUNK_SIZE; i++) {
if (center->get(i).density() > 0) {
Faces faces = center->get(i).density() < world::Voxel::DENSITY_MAX ? Faces::All :
(isTransparent(surrounding, surrounding::getNeighborIdx(i, Face::Right)) & Faces::Right) |
(isTransparent(surrounding, surrounding::getNeighborIdx(i, Face::Left)) & Faces::Left) |
(isTransparent(surrounding, surrounding::getNeighborIdx(i, Face::Up)) & Faces::Up) |
(isTransparent(surrounding, surrounding::getNeighborIdx(i, Face::Down)) & Faces::Down) |
(isTransparent(surrounding, surrounding::getNeighborIdx(i, Face::Forward)) & Faces::Forward) |
(isTransparent(surrounding, surrounding::getNeighborIdx(i, Face::Backward)) & Faces::Backward);
box::addCube(vertices, world::Chunk::getPosition(i), center->get(i).Material, faces, glm::vec3(center->get(i).Density * 1.f / UCHAR_MAX));
box::addCube(vertices, glm::fromIdx(i), center->get(i).material(), faces, glm::vec3(center->get(i).density() * 1.f / world::Voxel::DENSITY_MAX));
}
}
}

View File

@ -9,8 +9,6 @@
#include "../render/buffer/ShortIndexed.hpp"
#include <thread>
#define REPORT_BUFFER_SIZE 128
using namespace data;
namespace contouring {
/// Stupid cubes 1:1 contouring
@ -19,30 +17,30 @@ namespace contouring {
FlatSurroundingBox(const std::string&);
virtual ~FlatSurroundingBox();
void update(const camera_pos&) override;
void update(const voxel_pos&, const world::area_map&) override;
void onGui() override;
/// Chunk data change
void onUpdate(const chunk_pos &, const world::chunk_map &, geometry::Faces) override;
void onUpdate(const area_<chunk_pos> &, const chunk_pos &, const world::ChunkContainer &, geometry::Faces) override;
/// Chunk existante ping
/// @note notify for chunks entering view while moving
void onNotify(const chunk_pos &, const world::chunk_map &) override;
void onNotify(const area_<chunk_pos> &, const chunk_pos &, const world::ChunkContainer &) override;
protected:
safe_priority_queue_map<chunk_pos, surrounding::faces, int> loadQueue;
safe_queue<std::pair<chunk_pos, buffer::ShortIndexed::Data>> loadedQueue;
safe_priority_queue_map<area_<chunk_pos>, surrounding::faces, int, area_hash> loadQueue;
safe_queue<std::pair<area_<chunk_pos>, buffer::ShortIndexed::Data>> loadedQueue;
struct report {
circular_buffer<float> count = circular_buffer<float>(REPORT_BUFFER_SIZE, 0); // MAYBE: store int
circular_buffer<float> load = circular_buffer<float>(REPORT_BUFFER_SIZE, 0);
circular_buffer<float> loaded = circular_buffer<float>(REPORT_BUFFER_SIZE, 0);
report_buffer count;
report_buffer load;
report_buffer loaded;
} reports;
bool running = true;
std::vector<std::thread> workers;
void enqueue(const chunk_pos &, const world::chunk_map &);
void enqueue(const area_<chunk_pos> &, const chunk_pos& offset, const world::ChunkContainer &);
private:
static inline bool isTransparent(const surrounding::faces &surrounding, const std::pair<ushort, ushort> &idx);

View File

@ -14,7 +14,7 @@
#include <cstdint>
// stl includes
#include <unordered_map>
#include <robin_hood.h>
#include <vector>
namespace dualmc {
@ -211,7 +211,7 @@ protected:
};
/// Hash map for shared vertex index computations
std::unordered_map<DualPointKey,QuadIndexType,DualPointKeyHash> pointToIndex;
robin_hood::unordered_map<DualPointKey,QuadIndexType,DualPointKeyHash> pointToIndex;
};
// inline function definitions

View File

@ -1,72 +1,79 @@
#include "surrounding.hpp"
#include "../world/Chunk.hpp"
#include "../world/Area.hpp"
#include "../data/math.hpp"
using namespace geometry;
namespace contouring::surrounding {
bool load(faces &out, const chunk_pos &chunkPos, const world::chunk_map &chunks) {
bool load(faces &out, const chunk_pos &chunkPos, const world::ChunkContainer &chunks) {
{
const auto it = chunks.find(chunkPos);
if (it == chunks.end())
const auto chunk = chunks.findInRange(chunkPos);
if (!chunk.has_value())
return false;
out[CENTER] = it->second;
out[CENTER] = chunk.value();
}
for (size_t i = 0; i < CENTER; i++) {
const auto it = chunks.find(chunkPos + g_face_offsets[i]);
if (it == chunks.end())
const auto chunk = chunks.findOrEmpty(chunkPos + g_face_offsets[i]);
if (!chunk.has_value())
return false;
out[i] = it->second;
out[i] = chunk.value();
}
return true;
}
std::pair<ushort, ushort> getNeighborIdx(ushort idx, Face face) {
std::pair<glm::idx, glm::idx> getNeighborIdx(glm::idx idx, Face face) {
switch (face) {
case Face::Forward:
if (idx % CHUNK_LENGTH >= CHUNK_LENGTH - 1)
return {static_cast<int>(Face::Forward), idx - (CHUNK_LENGTH - 1)};
if (idx % glm::IDX_LENGTH >= glm::IDX_LENGTH - 1)
return {static_cast<int>(Face::Forward), idx - (glm::IDX_LENGTH - 1)};
return {CENTER, idx + 1};
case Face::Backward:
if (idx % CHUNK_LENGTH <= 0)
return {static_cast<int>(Face::Backward), idx + (CHUNK_LENGTH - 1)};
if (idx % glm::IDX_LENGTH <= 0)
return {static_cast<int>(Face::Backward), idx + (glm::IDX_LENGTH - 1)};
return {CENTER, idx - 1};
case Face::Up:
if ((idx / CHUNK_LENGTH) % CHUNK_LENGTH >= CHUNK_LENGTH - 1)
return {static_cast<int>(Face::Up), idx - (CHUNK_LENGTH2 - CHUNK_LENGTH)};
return {CENTER, idx + CHUNK_LENGTH};
if ((idx / glm::IDX_LENGTH) % glm::IDX_LENGTH >= glm::IDX_LENGTH - 1)
return {static_cast<int>(Face::Up), idx - (glm::IDX_LENGTH2 - glm::IDX_LENGTH)};
return {CENTER, idx + glm::IDX_LENGTH};
case Face::Down:
if ((idx / CHUNK_LENGTH) % CHUNK_LENGTH <= 0)
return {static_cast<int>(Face::Down), idx + (CHUNK_LENGTH2 - CHUNK_LENGTH)};
return {CENTER, idx - CHUNK_LENGTH};
if ((idx / glm::IDX_LENGTH) % glm::IDX_LENGTH <= 0)
return {static_cast<int>(Face::Down), idx + (glm::IDX_LENGTH2 - glm::IDX_LENGTH)};
return {CENTER, idx - glm::IDX_LENGTH};
case Face::Right:
if (idx / CHUNK_LENGTH2 >= CHUNK_LENGTH - 1)
return {static_cast<int>(Face::Right), idx - (CHUNK_SIZE - CHUNK_LENGTH2)};
return {CENTER, idx + CHUNK_LENGTH2};
if (idx / glm::IDX_LENGTH2 >= glm::IDX_LENGTH - 1)
return {static_cast<int>(Face::Right), idx - (glm::IDX_SIZE - glm::IDX_LENGTH2)};
return {CENTER, idx + glm::IDX_LENGTH2};
case Face::Left:
if (idx / CHUNK_LENGTH2 <= 0)
return {static_cast<int>(Face::Left), idx + (CHUNK_SIZE - CHUNK_LENGTH2)};
return {CENTER, idx - CHUNK_LENGTH2};
if (idx / glm::IDX_LENGTH2 <= 0)
return {static_cast<int>(Face::Left), idx + (glm::IDX_SIZE - glm::IDX_LENGTH2)};
return {CENTER, idx - glm::IDX_LENGTH2};
default:
return {CENTER, idx};
}
}
bool load(corners &out, const chunk_pos &chunkPos, const world::chunk_map &chunks) {
for (size_t i = 0; i < 8; i++) {
const auto it = chunks.find(chunkPos + g_corner_offsets[i]);
if (it == chunks.end())
bool load(corners &out, const chunk_pos &chunkPos, const world::ChunkContainer &chunks) {
{
const auto chunk = chunks.findInRange(chunkPos);
if(!chunk.has_value())
return false;
out[i] = it->second;
out[0] = chunk.value();
}
for (size_t i = 1; i < 8; i++) {
const auto chunk = chunks.findOrEmpty(chunkPos + g_corner_offsets[i]);
if (!chunk.has_value())
return false;
out[i] = chunk.value();
}
return true;
}

View File

@ -10,21 +10,21 @@ namespace contouring::surrounding {
const auto CENTER = 6;
typedef std::array<std::shared_ptr<const world::Chunk>, CENTER+1> faces;
bool load(faces &out, const chunk_pos &chunkPos, const world::chunk_map &chunks);
bool load(faces &out, const chunk_pos &chunkPos, const world::ChunkContainer &chunks);
std::pair<ushort, ushort> getNeighborIdx(ushort idx, geometry::Face face);
typedef std::array<std::shared_ptr<const world::Chunk>, 8> corners;
const glm::ivec3 g_corner_offsets[8] = {
glm::ivec3(0, 0, 0),
glm::ivec3(0, 0, 1),
glm::ivec3(0, 1, 0),
glm::ivec3(0, 1, 1),
glm::ivec3(1, 0, 0),
glm::ivec3(1, 0, 1),
glm::ivec3(1, 1, 0),
glm::ivec3(1, 1, 1),
const chunk_pos g_corner_offsets[8] = {
chunk_pos(0, 0, 0),
chunk_pos(0, 0, 1),
chunk_pos(0, 1, 0),
chunk_pos(0, 1, 1),
chunk_pos(1, 0, 0),
chunk_pos(1, 0, 1),
chunk_pos(1, 1, 0),
chunk_pos(1, 1, 1),
};
bool load(corners &out, const chunk_pos &chunkPos, const world::chunk_map &chunks);
bool load(corners &out, const chunk_pos &chunkPos, const world::ChunkContainer &chunks);
}

View File

@ -3,7 +3,8 @@
#include <glm/gtc/matrix_transform.hpp>
#include "../render/window.hpp"
Camera::Camera(GLFWwindow *window, const InputMap& inputs, const Camera::options& opt): window(window), inputs(inputs), o(opt) {
Camera::Camera(GLFWwindow *window, const InputMap& inputs, const Camera::options& opt): window(window), inputs(inputs),
Position(voxel_pos(0), 1), o(opt){
updateProjection();
}
Camera::~Camera() { }
@ -12,14 +13,7 @@ void Camera::updateProjection() {
ProjectionMatrix = glm::perspective(o.fov, RATIO, o.near, o.far);
}
void Camera::update(bool captureMouse, bool captureKeys) {
// glfwGetTime is called only once, the first time this function is called
static double lastTime = glfwGetTime();
// Compute time difference between current and last frame
double currentTime = glfwGetTime();
float deltaTime = float(currentTime - lastTime);
void Camera::update(bool captureMouse, bool captureKeys, float deltaTime) {
// Get mouse position
if(captureMouse) {
int viewportX, viewportY;
@ -57,39 +51,37 @@ void Camera::update(bool captureMouse, bool captureKeys) {
if(captureKeys) {
// Move forward
if (inputs.isDown(Input::Forward)) {
Position += direction * deltaTime * o.speed;
Position.offset += direction * deltaTime * o.speed;
}
// Move backward
if (inputs.isDown(Input::Backward)) {
Position -= direction * deltaTime * o.speed;
Position.offset -= direction * deltaTime * o.speed;
}
// Strafe right
if (inputs.isDown(Input::Right)) {
Position += right * deltaTime * o.speed;
Position.offset += right * deltaTime * o.speed;
}
// Strafe left
if (inputs.isDown(Input::Left)) {
Position -= right * deltaTime * o.speed;
Position.offset -= right * deltaTime * o.speed;
}
// Move up
if (inputs.isDown(Input::Up)) {
Position += up * deltaTime * o.speed;
Position.offset += up * deltaTime * o.speed;
}
// Move down
if (inputs.isDown(Input::Down)) {
Position -= up * deltaTime * o.speed;
Position.offset -= up * deltaTime * o.speed;
}
Position.center();
}
// MAYBE: only if moved
// MAYBE: save frustum
// Camera matrix
ViewMatrix = glm::lookAt(
Position, // Camera is here
Position + direction, // and looks here : at the same position, plus "direction"
Position.offset, // Camera is here
Position.offset + direction, // and looks here : at the same position, plus "direction"
up // Head is up (set to 0,-1,0 to look upside-down)
);
// For the next frame, the "last time" will be "now"
lastTime = currentTime;
}

View File

@ -7,7 +7,7 @@
#include "../data/glm.hpp"
#include "../data/geometry/Frustum.hpp"
#include "../data/geometry/Ray.hpp"
typedef glm::vec3 camera_pos;
#include "../world/position.h"
/// Moving perspective camera
class Camera {
@ -22,13 +22,9 @@ public:
};
Camera(GLFWwindow*, const InputMap&, const options&);
Camera(Camera &&) = default;
Camera(const Camera &) = default;
Camera &operator=(Camera &&) = default;
Camera &operator=(const Camera &) = default;
~Camera();
void update(bool captureMouse, bool captureKeys);
void update(bool captureMouse, bool captureKeys, float deltaTime);
void setOptions(const options &options) {
o = options;
updateProjection();
@ -40,7 +36,7 @@ public:
constexpr glm::mat4 getViewMatrix() const { return ViewMatrix; }
constexpr glm::mat4 getProjectionMatrix() const { return ProjectionMatrix; }
constexpr camera_pos getPosition() const { return Position; }
camera_pos getPosition() const { return Position; }
constexpr float getDepth() const { return o.far; }
private:
@ -51,7 +47,7 @@ private:
glm::mat4 ProjectionMatrix;
void updateProjection();
camera_pos Position = glm::vec3(0, 0, 5);
camera_pos Position;
float HorizontalAngle = 3.14f;
float VerticalAngle = 0.0f;

View File

@ -1,6 +1,6 @@
#pragma once
#include <unordered_map>
#include <robin_hood.h>
#include <vector>
#include <GL/glew.h>
@ -8,7 +8,7 @@
enum class Input {
Forward, Backward, Left, Right, Up, Down,
Mouse, Debug
Mouse, Debug, Throw
};
enum class Mouse {
Left = GLFW_MOUSE_BUTTON_LEFT,
@ -19,10 +19,6 @@ enum class Mouse {
class InputMap {
public:
InputMap(GLFWwindow*);
InputMap(InputMap &&) = default;
InputMap(const InputMap &) = default;
InputMap &operator=(InputMap &&) = default;
InputMap &operator=(const InputMap &) = default;
~InputMap();
void saveKeys();
@ -41,7 +37,7 @@ public:
private:
GLFWwindow *window;
std::unordered_map<Input, int> Map = {
robin_hood::unordered_map<Input, int> Map = {
{Input::Forward, GLFW_KEY_W},
{Input::Backward, GLFW_KEY_S},
{Input::Left, GLFW_KEY_A},
@ -50,11 +46,12 @@ private:
{Input::Down, GLFW_KEY_LEFT_SHIFT},
{Input::Mouse, GLFW_KEY_E},
{Input::Debug, GLFW_KEY_F3},
{Input::Throw, GLFW_KEY_B}
};
const std::vector<Input> Toggles = {
Input::Mouse, Input::Debug
Input::Mouse, Input::Debug, Input::Throw
};
std::unordered_map<Input, bool> Previous;
robin_hood::unordered_map<Input, bool> Previous;
bool PreviousLeft;
bool PreviousRight;

View File

@ -25,4 +25,8 @@ namespace data {
return buffer[last];
}
};
struct report_buffer: circular_buffer<float> {
report_buffer() : circular_buffer(256, 0) {}
};
}

11
src/data/colors.h Normal file
View File

@ -0,0 +1,11 @@
#define GREY "\033[0;90m"
#define GREEN "\033[0;32m"
#define BROWN "\033[0;33m"
#define BLUE "\033[0;34m"
#define RED "\033[1;31m"
#define YELLOW "\033[1;33m"
#define BOLD "\033[1;1m"
#define BLUE_BOLD "\033[1;34m"
#define END_COLOR "\033[0m"
#define UNDERLINE "\e[4m"
#define END_STYLE "\e[0m"

221
src/data/generational.hpp Normal file
View File

@ -0,0 +1,221 @@
#pragma once
#include <cassert>
#include <vector>
#include <optional>
namespace data::generational {
struct id {
id(size_t index, size_t generation = 0): index(index), generation(generation) { }
id(): id(0) { }
size_t index;
size_t generation;
bool operator==(const id &i) const { return index == i.index && generation == i.generation; }
};
class allocator {
public:
allocator() { }
template <typename C>
allocator(const C& map) {
for(const auto& [key, _]: map) {
if (const auto size = entries.size(); key >= size) {
entries.resize(key + 1);
for (size_t i = size; i < entries.size(); i++) {
entries[i].is_live = false;
freed.push_back(i);
}
}
assert(!entries[key].is_live);
entries[key].is_live = true;
}
}
id alloc() {
if(freed.empty()) {
const auto idx = entries.size();
entries.emplace_back();
return id(idx);
} else {
const auto idx = freed.back();
freed.pop_back();
auto &entry = entries[idx];
assert(!entry.is_live);
entry.is_live = true;
return id(idx, ++entries[idx].generation);
}
}
bool is_live(id id) const {
return id.index < entries.size() &&
entries[id.index].is_live &&
entries[id.index].generation == id.generation;
}
bool free(id id) {
if(!is_live(id))
return false;
entries[id.index].is_live = false;
freed.push_back(id.index);
return true;
}
private:
struct entry {
bool is_live = true;
size_t generation = 0;
};
std::vector<entry> entries;
std::vector<size_t> freed;
};
template<typename T>
class vector {
public:
vector() { }
template<typename C>
vector(const C& map) {
for(const auto& [key, value]: map) {
if (const auto size = entries.size(); key >= size) {
entries.resize(key + 1);
}
entries[key].value = value;
}
for (size_t i = 0; i < entries.size(); i++) {
if(!entries[i].value.has_value())
freed.push_back(i);
}
}
id push(const T& in) {
if(freed.empty()) {
const auto idx = entries.size();
entries.emplace_back(in);
return id(idx);
} else {
const auto idx = freed.back();
freed.pop_back();
auto &entry = entries[idx];
assert(!entry.value.has_value());
entry.value = in;
return id(idx, ++entries[idx].generation);
}
}
template <typename... _Args>
id emplace(_Args &&... __args) {
if(freed.empty()) {
const auto idx = entries.size();
entries.emplace_back(std::forward<_Args>(__args)...);
return id(idx);
} else {
const auto idx = freed.back();
freed.pop_back();
auto &entry = entries[idx];
assert(!entry.value.has_value());
entry.value.emplace(std::forward<_Args>(__args)...);
return id(idx, ++entries[idx].generation);
}
}
bool put(id idx, const T& in) {
if(idx.index >= entries.size())
return false;
if(entries[idx.index].generation != idx.generation || entries[idx.index].value.has_value())
return false;
entries[idx.index].value = in;
return true;
}
T& at(id idx) {
assert(contains(idx));
return entries[idx.index].value.value();
}
bool contains(id idx) const {
return idx.index < entries.size() &&
entries[idx.index].generation == idx.generation &&
entries[idx.index].value.has_value();
}
bool free(id idx) {
if(!contains(idx))
return false;
entries[idx.index].value = std::nullopt;
freed.push_back(idx.index);
return true;
}
template<typename apply>
void iter(apply fn) const {
for (size_t i = 0; i < entries.size(); i++) {
const auto &entry = entries[i];
if(entry.value.has_value()) {
fn(id(i, entry.generation), entry.value.value());
}
}
}
template<typename apply>
void for_each(apply fn) {
for (size_t i = 0; i < entries.size(); i++) {
auto &entry = entries[i];
if(entry.value.has_value()) {
fn(id(i, entry.generation), entry.value.value());
}
}
}
template<typename extractor>
void extract(extractor fn) {
for (size_t i = 0; i < entries.size(); i++) {
auto &entry = entries[i];
if(entry.value.has_value()) {
if(fn(id(i, entry.generation), entry.value.value()))
entry.value = std::nullopt;
}
}
}
template<typename extractor>
void remove(extractor fn) {
for (size_t i = 0; i < entries.size(); i++) {
auto &entry = entries[i];
if(entry.value.has_value()) {
if(fn(id(i, entry.generation), entry.value.value())) {
entry.value = std::nullopt;
freed.push_back(i);
}
}
}
}
size_t size() const {
return std::count_if(entries.begin(), entries.end(),
[](const entry &e) { return e.value.has_value(); });
}
private:
struct entry {
entry(): value(std::nullopt), generation(0) { }
entry(const T &in, size_t gen = 0):
value(in), generation(gen) { }
template <typename... _Args>
entry(_Args &&... __args): generation(0) {
value.emplace(std::forward<_Args>(__args)...);
}
std::optional<T> value;
size_t generation;
};
std::vector<entry> entries;
std::vector<size_t> freed;
};
}
namespace std {
template<>
struct hash<data::generational::id> {
std::size_t operator()(const data::generational::id& i) const noexcept {
return std::hash<size_t>{}(i.index);
}
};
}

View File

@ -1,7 +1,6 @@
#pragma once
#include <glm/glm.hpp>
#include <iostream>
namespace geometry {
/// Axis Aligned Floating Box
@ -11,7 +10,7 @@ namespace geometry {
};
Box(glm::vec3 min, glm::vec3 max): Min(min), Max(max) {
assert(("Min > Max", max.x >= min.x && max.y >= min.y && max.z >= min.z));
assert((max.x >= min.x && max.y >= min.y && max.z >= min.z) && "Min > Max");
}
inline static Box fromCenter(glm::vec3 center, float radius) { return Box(center - radius, center + radius); }
inline static Box fromMin(glm::vec3 min, glm::vec3 size) { return Box(min, min + size); }

View File

@ -1,16 +1,16 @@
#pragma once
#include "../glm.hpp"
#include "../../world/position.h"
/// Math utils
namespace geometry {
enum class Face {
Right, Left, Up, Down, Forward, Backward
};
const glm::ivec3 g_face_offsets[6] = {
glm::ivec3(1,0,0), glm::ivec3(-1,0,0),
glm::ivec3(0,1,0), glm::ivec3(0,-1,0),
glm::ivec3(0,0,1), glm::ivec3(0,0,-1),
const chunk_pos g_face_offsets[6] = {
chunk_pos(1,0,0), chunk_pos(-1,0,0),
chunk_pos(0,1,0), chunk_pos(0,-1,0),
chunk_pos(0,0,1), chunk_pos(0,0,-1),
};
enum class Faces {

View File

@ -0,0 +1,49 @@
#pragma once
#include <glm/glm.hpp>
namespace geometry {
/// Axis Aligned Long Box
struct IBox {
using pos_t = glm::llvec3;
enum class ContainmentType {
Disjoint = false, Intersects, Contains
};
IBox(pos_t min, pos_t max): Min(min), Max(max) {
assert((max.x >= min.x && max.y >= min.y && max.z >= min.z) && "Min > Max");
}
inline static IBox fromCenter(pos_t center, pos_t::value_type radius) { return IBox(center - radius, center + radius); }
inline static IBox fromMin(pos_t min, pos_t size) { return IBox(min, min + size); }
pos_t Min;
pos_t Max;
ContainmentType contains(const IBox& box) const {
//test if all corner is in the same side of a face by just checking min and max
if (box.Max.x < Min.x
|| box.Min.x > Max.x
|| box.Max.y < Min.y
|| box.Min.y > Max.y
|| box.Max.z < Min.z
|| box.Min.z > Max.z)
return ContainmentType::Disjoint;
if (box.Min.x >= Min.x
&& box.Max.x <= Max.x
&& box.Min.y >= Min.y
&& box.Max.y <= Max.y
&& box.Min.z >= Min.z
&& box.Max.z <= Max.z)
return ContainmentType::Contains;
return ContainmentType::Intersects;
}
constexpr bool contains(const pos_t& pos) const {
return pos.x >= Min.x && pos.x <= Max.x &&
pos.y >= Min.y && pos.y <= Max.y &&
pos.z >= Min.z && pos.z <= Max.z;
}
};
}

View File

@ -1,30 +1,30 @@
#pragma once
#include "Box.hpp"
#include "../glm.hpp"
#include "IBox.hpp"
#include "../../world/position.h"
namespace geometry {
/// Raycast with distance
struct Ray {
glm::vec3 from;
camera_pos from;
glm::vec3 dir;
float dist;
// MAYBE: Ray(const glm::mat4& view_matrix) { }
Ray(const glm::vec3& from, const glm::vec3& dir, float dist): from(from), dir(glm::normalize(dir)), dist(dist) { }
Ray(const camera_pos& from, const glm::vec3& dir, float dist): from(from), dir(glm::normalize(dir)), dist(dist) { }
inline Ray operator/(float scale) const noexcept {
return Ray(from / scale, dir, dist / scale);
inline Ray operator*(float scale) const noexcept {
return Ray(from * scale, dir, dist * scale);
}
/// Get path points in integer grid
/// @note not precise enough
inline void grid(std::vector<glm::lvec3>& points) const {
glm::lvec3 current = from + glm::vec3(.5f);
const glm::lvec3 d = dir * dist;
const glm::lvec3 inc = glm::lvec3((d.x < 0) ? -1 : 1, (d.y < 0) ? -1 : 1, (d.z < 0) ? -1 : 1);
const glm::lvec3 size = glm::abs(d);
const glm::lvec3 delta = size << 1ll;
inline void grid(std::vector<glm::llvec3>& points) const {
glm::llvec3 current = from.as_voxel();
const glm::llvec3 d = dir * dist;
const glm::llvec3 inc = glm::llvec3((d.x < 0) ? -1 : 1, (d.y < 0) ? -1 : 1, (d.z < 0) ? -1 : 1);
const glm::llvec3 size = glm::abs(d);
const glm::llvec3 delta = size << 1ll;
if ((size.x >= size.y) && (size.x >= size.z)) {
int err_1 = delta.y - size.x;
@ -83,5 +83,44 @@ namespace geometry {
}
points.push_back(current);
}
IBox::ContainmentType intersect(const IBox& box) const {
const glm::llvec3 start = from.as_voxel();
if(box.contains(start))
return IBox::ContainmentType::Contains;
const auto inv = 1. / glm::dvec3(dir);
glm::f64 tmin = ((inv.x < 0 ? box.Max : box.Min).x - start.x) * inv.x;
glm::f64 tmax = ((inv.x < 0 ? box.Min : box.Max).x - start.x) * inv.x;
glm::f64 tymin = ((inv.y < 0 ? box.Max : box.Min).y - start.y) * inv.y;
glm::f64 tymax = ((inv.y < 0 ? box.Min : box.Max).y - start.y) * inv.y;
if ((tmin > tymax) || (tymin > tmax)){
return IBox::ContainmentType::Disjoint;
}
if (tymin > tmin) {
tmin = tymin;
}
if (tymax < tmax){
tmax = tymax;
}
glm::f64 tzmin = ((inv.z < 0 ? box.Max : box.Min).z - start.z) * inv.z;
glm::f64 tzmax = ((inv.z < 0 ? box.Min : box.Max).z - start.z) * inv.z;
if ((tmin > tzmax) || (tzmin > tmax)){
return IBox::ContainmentType::Disjoint;
}
if (tzmin > tmin){
tmin = tzmin;
}
if (tzmax < tmax){
tmax = tzmax;
}
// this last check is different from the 'ray' case in below references:
// we need to check that the segment is on the span of the line
// that intersects the box
return tmax<0.0f || tmin> 1.0f ? IBox::ContainmentType::Intersects : IBox::ContainmentType::Disjoint;
}
};
}

21
src/data/glm.cpp Normal file
View File

@ -0,0 +1,21 @@
#include "glm.hpp"
#include "math.hpp"
using namespace glm;
ifvec3::ifvec3(const llvec3 &pos, uint density) {
const auto d = IDX_LENGTH2 * density;
raw = divide(pos, glm::uvec3(d));
offset = glm::vec3(rem(pos.x, d), rem(pos.y, d), rem(pos.z, d));
if(density > 1) center();
}
llvec3 ifvec3::raw_as_long() const {
return llvec3(raw) * llvec3(IDX_LENGTH2);
}
llvec3 ifvec3::as_voxel(int density) const {
return raw_as_long() * llvec3(density) + llvec3(offset * vec3(density));
}
void ifvec3::center() {
const auto diff = divide(offset, uvec3(IDX_LENGTH2));
raw += diff;
offset -= diff * static_cast<long>(IDX_LENGTH2);
}

View File

@ -3,8 +3,43 @@
#include <glm/glm.hpp>
namespace glm {
typedef vec<3, long long> lvec3;
typedef vec<4, long long> lvec4;
typedef vec<3, long long> llvec3;
typedef vec<3, long> lvec3;
typedef vec<3, ushort> usvec3;
typedef vec<3, unsigned char> ucvec3;
const auto IDX_LENGTH = 32;
const auto IDX_LENGTH2 = IDX_LENGTH * IDX_LENGTH;
const auto IDX_SIZE = IDX_LENGTH2 * IDX_LENGTH;
using idx = glm::u16;
/// Combination of ivec3 and vec3 as a big and high precision
/// Scale to IDX_LENGTH2
struct ifvec3 {
using raw_t = glm::ivec3;
using offset_t = glm::vec3;
ifvec3(): raw(0), offset(0) { }
ifvec3(const raw_t &raw, const offset_t &offset, bool recenter = true) : raw(raw), offset(offset) {
if(recenter) center();
}
ifvec3(const glm::llvec3 &pos, uint density = 1);
raw_t raw;
offset_t offset;
glm::llvec3 as_voxel(int density = 1) const;
void center();
glm::llvec3 raw_as_long() const;
glm::dvec3 as_double() const;
inline const ifvec3 &operator+=(const offset_t &v) {
offset += v;
center();
return *this;
}
inline ifvec3 operator+(const offset_t& v) const { return ifvec3(raw, offset + v); }
inline ifvec3 operator/(int i) const { return ifvec3(raw / i, offset / (i * 1.f), false); }
inline ifvec3 operator*(int i) const { return ifvec3(raw * i, offset * (i * 1.f)); }
};
}

11
src/data/logger.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "logger.hpp"
#include <chrono>
#include <ctime>
namespace logger {
std::_Put_time<char> now() {
const auto tp = std::chrono::system_clock::now();
const auto timet = std::chrono::system_clock::to_time_t(tp);
return std::put_time(std::localtime(&timet), "%Y-%m-%d %X");
}
}

16
src/data/logger.hpp Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include "colors.h"
#include <iostream>
#include <iomanip>
#define _OUT(expr) {std::ostringstream oss; oss << expr << std::endl; std::cout << oss.str();}
#define LOG(expr) _OUT("[" << logger::now() << "] " << expr)
#define LOG_E(expr) _OUT("[" << RED << logger::now() << END_COLOR << "] " << expr)
#define LOG_W(expr) _OUT("[" << YELLOW << logger::now() << END_COLOR << "] " << expr)
#define LOG_I(expr) _OUT("[" << GREEN << logger::now() << END_COLOR << "] " << expr)
#define LOG_D(expr) _OUT("[" << GREY << logger::now() << END_COLOR << "] " << expr)
namespace logger {
std::_Put_time<char> now();
}

View File

@ -7,9 +7,12 @@ namespace glm {
constexpr ivec3 inline iround(const vec3& p) {
return ivec3(std::round<int>(p.x), std::round<int>(p.y), std::round<int>(p.z));
}
constexpr int inline length2(const ivec3& a) {
constexpr long inline length2(const ivec3& a) {
return a.x * a.x + a.y * a.y + a.z * a.z;
}
constexpr long inline pow2(int v) {
return v * v;
}
constexpr ivec3 inline diff(const ivec3& a, const ivec3& b) {
return glm::abs(glm::abs(a) - glm::abs(b));
}
@ -17,16 +20,35 @@ namespace glm {
constexpr uint inline rem(long long value, uint m) {
return value < 0 ? ((value+1) % (long long)m) + m - 1 : value % (long long)m;
}
constexpr int inline div(long long value, uint m) {
constexpr long inline div(long long value, uint m) {
return value < 0 ? ((value+1) / (long long)m) - 1 : value / (long long)m;
}
constexpr ucvec3 inline modulo(const lvec3& value, const ucvec3& m) {
constexpr ucvec3 inline modulo(const llvec3& value, const ucvec3& m = ucvec3(IDX_LENGTH)) {
return ucvec3(rem(value.x, m.x), rem(value.y, m.y), rem(value.z, m.z));
}
constexpr ivec3 inline divide(const lvec3 &value, const ucvec3 &m) {
return ivec3(div(value.x, m.x), div(value.y, m.y), div(value.z, m.z));
constexpr lvec3 inline divide(const llvec3 &value, const uvec3 &m) {
return lvec3(div(value.x, m.x), div(value.y, m.y), div(value.z, m.z));
}
constexpr std::pair<ivec3, ucvec3> inline split(const lvec3 &value, const ucvec3 &m) {
constexpr lvec3 inline divide(const llvec3 &value, const ucvec3 &m = ucvec3(IDX_LENGTH)) {
return lvec3(div(value.x, m.x), div(value.y, m.y), div(value.z, m.z));
}
constexpr llvec3 inline multiply(const lvec3 &value, const ucvec3 &m = ucvec3(IDX_LENGTH)) {
return llvec3(value) * llvec3(m);
}
constexpr std::pair<lvec3, ucvec3> inline split(const llvec3 &value, const ucvec3 &m = ucvec3(IDX_LENGTH)) {
return {divide(value, m), modulo(value, m)};
}
constexpr ucvec3 inline fromIdx(idx idx) {
assert(idx < IDX_SIZE);
return ucvec3(idx / IDX_LENGTH2, (idx / IDX_LENGTH) % IDX_LENGTH, idx % IDX_LENGTH);
}
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(ucvec3 pos) {
return toIdx(pos.x, pos.y, pos.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

@ -2,7 +2,6 @@
#include <tuple>
#include <vector>
#include <unordered_set>
#include <robin_hood.h>
#include <mutex>
#include <condition_variable>
@ -10,7 +9,7 @@
namespace data {
/// Thread safe queue with unique keys updating priority and value
template <class K, class V, class W>
template <class K, class V, class W, class hash = robin_hood::hash<K>>
class safe_priority_queue_map {
private:
static bool cmpByWeight(const std::pair<K, W> &a, const std::pair<K, W> &b) {
@ -18,7 +17,7 @@ namespace data {
}
std::vector<std::pair<K, W>> heap;
robin_hood::unordered_map<K, V> map;
robin_hood::unordered_map<K, V, hash> map;
std::mutex mutex;
std::condition_variable cv;
@ -59,9 +58,12 @@ namespace data {
return map.size();
}
void notify() {
void notify_all() {
cv.notify_all();
}
void notify_one() {
cv.notify_one();
}
void wait() {
std::unique_lock<std::mutex> lock(mutex);
@ -121,9 +123,12 @@ namespace data {
return set.size();
}
void notify() {
void notify_all() {
cv.notify_all();
}
void notify_one() {
cv.notify_one();
}
void wait() {
std::unique_lock<std::mutex> lock(mutex);

View File

@ -3,7 +3,6 @@
#include <queue>
#include <mutex>
#include <condition_variable>
#include <robin_hood.h>
namespace data {
/// Thread safe queue
@ -48,9 +47,12 @@ namespace data {
return queue.size();
}
void notify() {
void notify_all() {
cv.notify_all();
}
void notify_one() {
cv.notify_one();
}
void wait() {
std::unique_lock<std::mutex> lock(mutex);

View File

@ -7,11 +7,11 @@
namespace data {
/// Thread safe queue discarding duplicate values
template <class T>
template <class T, class hash = robin_hood::hash<T>>
class safe_unique_queue {
private:
std::queue<T> queue;
robin_hood::unordered_flat_set<T> set;
robin_hood::unordered_flat_set<T, hash> set;
std::mutex mutex;
std::condition_variable cv;
@ -62,9 +62,12 @@ namespace data {
return set.size();
}
void notify() {
void notify_all() {
cv.notify_all();
}
void notify_one() {
cv.notify_one();
}
void wait() {
std::unique_lock<std::mutex> lock(mutex);

View File

@ -20,10 +20,11 @@
#include "state.h"
#include "data/math.hpp"
#include <Remotery.h>
#include <Remotery.h> // NOLINT
/// Entry point
int main(int, char *[]){
int main(int /*unused*/, char */*unused*/[]){
LOG("Univerxel");
options options;
state state;
reports reports;
@ -33,7 +34,7 @@ int main(int, char *[]){
return 1;
glClearColor(options.renderer.clear_color.x, options.renderer.clear_color.y, options.renderer.clear_color.z, options.renderer.clear_color.w);
glfwSwapInterval(options.target_fps < MIN_FPS);
glfwSwapInterval(static_cast<int>(options.target_fps < MIN_FPS));
InputMap inputs(window);
Camera camera(window, inputs, options.camera);
@ -73,11 +74,11 @@ int main(int, char *[]){
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
});
#if RMT_ENABLED
Remotery *rmt;
rmt_CreateGlobalInstance(&rmt);
rmt_BindOpenGL();
#if RMT_ENABLED
std::cout << "Profiling !" << std::endl;
LOG("Profiling !");
#endif
world::Universe world = world::Universe(options.world);
@ -88,31 +89,39 @@ int main(int, char *[]){
const double startTime = glfwGetTime();
{ // Update
rmt_ScopedCPUSample(Update, 0);
static double lastTime = glfwGetTime();
const double partTime = glfwGetTime();
const float deltaTime = float(partTime - lastTime);
inputs.toggle(state.capture_mouse, Input::Mouse);
inputs.toggle(options.show_debug_menu, Input::Debug);
camera.update(state.capture_mouse, !UI::isFocus());
camera.update(state.capture_mouse, !UI::isFocus(), deltaTime);
renderer->lookFrom(camera);
state.position = camera.getPosition();
state.look_at = world.raycast(camera.getRay() / options.voxel_size);
if (state.capture_mouse && state.look_at.has_value()) {
if (inputs.isPressing(Mouse::Left))
world.setCube(state.look_at.value().first, world::Voxel{0, 0}, options.tool.radius);
else if (inputs.isPressing(Mouse::Right))
world.setCube(state.look_at.value().first, world::Voxel{UCHAR_MAX, options.tool.material}, options.tool.radius);
state.look_at = world.raycast(camera.getRay() * options.voxel_density);
if (state.capture_mouse) {
if (state.look_at.has_value()) {
if (inputs.isPressing(Mouse::Left))
world.setCube(state.look_at.value().pos, world::Voxel(0), options.tool.radius);
else if (inputs.isPressing(Mouse::Right))
world.setCube(state.look_at.value().pos, world::Voxel(options.tool.material, world::Voxel::DENSITY_MAX), options.tool.radius);
}
if (inputs.isDown(Input::Throw)) {
world.addEntity(entity_id(0), {state.position * options.voxel_density, glm::vec3(10, 0, 0)});
}
}
world.update(state.position / options.voxel_size, reports.world);
world.update((state.position * options.voxel_density).as_voxel(), deltaTime, reports.world);
inputs.saveKeys();
reports.main.update.push((glfwGetTime() - partTime) * 1000);
lastTime = partTime;
}
{
rmt_ScopedCPUSample(UI, 0);
const auto actions = UI::draw(options, state, reports, aimTexture);
if (actions && UI::Actions::FPS) {
glfwSwapInterval(options.target_fps < MIN_FPS);
glfwSwapInterval(static_cast<int>(options.target_fps < MIN_FPS));
}
if (actions && UI::Actions::FullScreen) {
// MAYBE: real fullscreen
@ -153,24 +162,40 @@ int main(int, char *[]){
auto pass = renderer->getPass();
pass.start();
std::vector<std::pair<glm::mat4, buffer::Abstract *const>> models;
reports.main.models_count = 0;
reports.main.tris_count = 0;
std::optional<geometry::Frustum> frustum;
if(options.culling) {
frustum = {camera.getFrustum()};
}
world.getContouring()->getModels(models, frustum, options.voxel_size);
reports.main.models_count = 0;
reports.main.tris_count = 0;
rmt_ScopedOpenGLSample(Render);
for (auto [model, buffer] : models) {
reports.main.models_count++;
reports.main.tris_count += buffer->draw(pass.setup(model));
const auto offset = state.position.raw_as_long();
{ // Chunks
std::vector<std::pair<glm::mat4, buffer::Abstract *const>> models;
world.getContouring()->getModels(models, frustum, offset, options.voxel_density);
rmt_ScopedOpenGLSample(Render);
for (auto [model, buffer] : models) {
reports.main.models_count++;
reports.main.tris_count += buffer->draw(pass.setup(model));
}
}
if(state.look_at.has_value()) {
{ // Entities
std::vector<std::pair<std::vector<glm::mat4>, buffer::Abstract *const>> models;
world.getEntitiesModels(models, frustum, offset, options.voxel_density);
lookProgram->useIt();
lookProgram->start(renderer);
const auto model = glm::scale(glm::translate(glm::scale(glm::mat4(1), glm::vec3(options.voxel_size)), glm::vec3(state.look_at.value().first) - glm::vec3(.5 + options.tool.radius)), glm::vec3(1 + options.tool.radius * 2));
lookBuffer.draw(lookProgram->setup(renderer, model));
for (auto [mats, buffer]: models) {
for(auto model: mats) {
reports.main.models_count++;
reports.main.tris_count += lookBuffer.draw(lookProgram->setup(renderer, model));
}
}
}
if(state.look_at.has_value()) { // Indicator
lookProgram->useIt();
lookProgram->start(renderer);
const auto model = glm::scale(glm::translate(glm::scale(glm::mat4(1), 1.f / glm::vec3(options.voxel_density)), glm::vec3(state.look_at.value().pos.second + state.look_at.value().offset - offset * glm::llvec3(options.voxel_density)) - glm::vec3(.5 + options.tool.radius)), glm::vec3(1 + options.tool.radius * 2));
reports.main.models_count++;
reports.main.tris_count += lookBuffer.draw(lookProgram->setup(renderer, model));
}
renderer->postProcess();

View File

@ -79,8 +79,14 @@ UI::Actions UI::draw(options &options, state &state, const reports &reports, GLu
changeRenderer |= ImGui::Checkbox("PBR", &options.renderer.main.pbr);
ImGui::SameLine();
changeRenderer |= ImGui::Checkbox("Triplanar", &options.renderer.main.triplanar);
changeRenderer |= ImGui::Checkbox("Geometry", &options.renderer.main.geometry);
ImGui::SameLine();
changeRenderer |= ImGui::Checkbox("Blend", &options.renderer.main.blend);
if(options.renderer.main.geometry) {
changeRenderer |= ImGui::Checkbox("Blend", &options.renderer.main.blend);
} else {
ImGui::TextDisabled("Blend");
}
changeRenderer |= ImGui::Checkbox("Fog", &options.renderer.main.fog);
ImGui::SameLine();
@ -107,18 +113,16 @@ UI::Actions UI::draw(options &options, state &state, const reports &reports, GLu
ImGui::PlotHistogram("Loading", reports.world.chunk_load.buffer.get(), reports.world.chunk_load.size, 0, std::to_string(reports.world.chunk_load.current()).c_str(), 0);
ImGui::PlotHistogram("Saving", reports.world.chunk_unload.buffer.get(), reports.world.chunk_unload.size, 0, std::to_string(reports.world.chunk_unload.current()).c_str(), 0);
ImGui::PlotHistogram("Regions", reports.world.region_count.buffer.get(), reports.world.region_count.size, 0, std::to_string(reports.world.region_count.current()).c_str(), 0);
ImGui::PlotHistogram("Entities", reports.world.entity_count.buffer.get(), reports.world.entity_count.size, 0, std::to_string(reports.world.entity_count.current()).c_str(), 0);
ImGui::Separator();
ImGui::Text("Path: %s", options.world.folderPath.c_str());
if (ImGui::SliderInt("Load distance", &options.world.loadDistance, 1, options.world.keepDistance) |
ImGui::SliderInt("Keep distance", &options.world.keepDistance, options.world.loadDistance + 1, 21)) {
actions = actions | Actions::World;
const auto far = std::clamp(options.camera.far, (options.world.loadDistance - 1.5f) * CHUNK_LENGTH * options.voxel_size, (options.world.keepDistance + .5f) * CHUNK_LENGTH * options.voxel_size);
if(far != options.camera.far) {
options.camera.far = far;
actions = actions | Actions::Camera;
}
}
ImGui::SliderFloat("Voxel size", &options.voxel_size, .1, 2);
if(ImGui::SliderInt("Voxel density", &options.voxel_density, 1, CHUNK_LENGTH * REGION_LENGTH)) {
options.voxel_density = pow(2, ceil(log(options.voxel_density) / log(2)));
}
ImGui::End();
}
@ -142,29 +146,41 @@ UI::Actions UI::draw(options &options, state &state, const reports &reports, GLu
ImGui::End();
}
if (options.show_debug_controls) {
ImGui::Begin("Debug: Controls", &options.show_debug_controls, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::Text("Position: (%.3f, %.3f, %.3f)", state.position.x, state.position.y, state.position.z);
ImGui::Separator();
{
bool changePerspective = false;
changePerspective |= ImGui::SliderAngle("FoV", &options.camera.fov, 30, 110);
changePerspective |= ImGui::SliderFloat("Near", &options.camera.near, 0.01, 10);
changePerspective |= ImGui::SliderFloat("Far", &options.camera.far, (options.world.loadDistance - 1.5) * CHUNK_LENGTH * options.voxel_size, (options.world.keepDistance + .5) * CHUNK_LENGTH * options.voxel_size);
changePerspective |= ImGui::SliderFloat("Move speed", &options.camera.speed, 0.1, 50);
changePerspective |= ImGui::SliderInt("Sensibility", &options.camera.sensibility, 1, 100, "%d%%");
if(changePerspective) {
actions = actions | Actions::Camera;
{
const auto farRange = state.contouring->getFarRange();
if (options.show_debug_controls) {
ImGui::Begin("Debug: Controls", &options.show_debug_controls, ImGuiWindowFlags_AlwaysAutoResize);
const auto p = state.position.as_voxel(options.voxel_density);
ImGui::Text("Position: (%lld, %lld, %lld)", p.x, p.y, p.z);
ImGui::Separator();
{
bool changePerspective = false;
changePerspective |= ImGui::SliderAngle("FoV", &options.camera.fov, 30, 110);
changePerspective |= ImGui::SliderFloat("Near", &options.camera.near, 0.01, 10);
changePerspective |= ImGui::SliderFloat("Far", &options.camera.far, farRange.first / options.voxel_density, farRange.second / options.voxel_density);
changePerspective |= ImGui::SliderFloat("Move speed", &options.camera.speed, 0.1, 50);
changePerspective |= ImGui::SliderInt("Sensibility", &options.camera.sensibility, 1, 100, "%d%%");
if(changePerspective) {
actions = actions | Actions::Camera;
}
}
ImGui::End();
}
const auto far = std::clamp(options.camera.far, farRange.first / options.voxel_density, farRange.second / options.voxel_density);
if(far != options.camera.far) {
options.camera.far = far;
actions = actions | Actions::Camera;
}
ImGui::End();
}
if (options.editor_show) {
ImGui::Begin("Editor", &options.editor_show, ImGuiWindowFlags_AlwaysAutoResize);
if (state.look_at.has_value()) {
ImGui::Text("Look at: (%lld, %lld, %lld) (%s, %.1f)", state.look_at.value().first.x, state.look_at.value().first.y, state.look_at.value().first.z, world::materials::textures[state.look_at.value().second.Material].c_str(), state.look_at.value().second.Density * 1. / UCHAR_MAX);
ImGui::Text("(%.3f, %.3f, %.3f)", state.look_at.value().first.x * options.voxel_size, state.look_at.value().first.y * options.voxel_size, state.look_at.value().first.z * options.voxel_size);
const auto &look = state.look_at.value();
ImGui::Text("Look at: (%ld: %lld, %lld, %lld) (%s, %.1f)", look.pos.first.index, look.pos.second.x, look.pos.second.y, look.pos.second.z,
world::materials::textures[look.value.material()].c_str(), look.value.density() * 1. / world::Voxel::DENSITY_MAX);
const auto w_pos = look.pos.second + look.offset;
ImGui::Text("(%.3f, %.3f, %.3f)", w_pos.x * 1. / options.voxel_density, w_pos.y * 1. / options.voxel_density, w_pos.z * 1. / options.voxel_density);
} else {
ImGui::Text("Look at: none");
}

View File

@ -3,9 +3,9 @@
#include <GL/glew.h>
#include <GLFW/glfw3.h>
class options;
class state;
class reports;
struct options;
struct state;
struct reports;
namespace UI {
/// Retro actions to state
enum class Actions {

View File

@ -8,6 +8,7 @@ namespace buffer {
struct params {
/// Bind only vertices positions
bool vertexOnly;
size_t instances = 1;
};
/// Abstract OpenGL Buffer

View File

@ -1,6 +1,7 @@
#include "ShortIndexed.hpp"
#include "vboindexer.hpp"
#include "../../data/logger.hpp"
using namespace buffer;
@ -95,7 +96,7 @@ void ShortIndexed::setData(const ShortIndexed::Data& data) {
IndexSize = data.indices.size();
if(IndexSize != data.indices.size()) {
std::cout << "ShortBuffer overflow: " << data.indices.size() << std::endl;
LOG_E("ShortBuffer overflow: " << data.indices.size());
}
setIndicies(IndexSize * sizeof(GLushort), &data.indices[0]);
setVertices(data.vertices.size() * sizeof(glm::vec3), &data.vertices[0]);

View File

@ -39,13 +39,13 @@ namespace buffer {
ShortIndexed(GLenum shape, const typename ShortIndexed::Data &data);
virtual ~ShortIndexed();
uint draw(params params) override;
private:
void enableAllAttribs();
void disableAllAttribs();
void enableIndex();
uint draw(params params) override;
private:
GLuint IndexBufferID;
GLushort IndexSize = 0;

View File

@ -13,13 +13,16 @@ MainProgram::MainProgram(const MainProgram::options& opts): Program() {
flags.emplace_back("TRIPLANAR");
if (opts.fog)
flags.emplace_back("FOG");
if (opts.blend)
flags.emplace_back("BLEND");
if (opts.geometry) {
flags.emplace_back("GEOMETRY");
if (opts.blend)
flags.emplace_back("BLEND");
}
std::vector<Shader*> shaders;
shaders.push_back(loadShader(GL_VERTEX_SHADER, flags));
shaders.push_back(loadShader(GL_FRAGMENT_SHADER, flags));
if (opts.blend)
if (opts.geometry)
shaders.push_back(loadShader(GL_GEOMETRY_SHADER, flags));
load(shaders);

View File

@ -12,8 +12,10 @@ namespace pass {
bool pbr = true;
/// Triplanar texture mapping
bool triplanar = false;
/// Blend voxel with mixed materials
bool blend = true;
/// Active geometry pass
bool geometry = true;
/// Blend voxel with mixed materials (requires geometry)
bool blend = false;
/// Depth fog
bool fog = true;
};

View File

@ -9,7 +9,7 @@
#define MIN_WIDTH 854
#define MIN_HEIGHT 480
void framebuffer_size_callback(GLFWwindow *window, int width, int height) {
void framebuffer_size_callback(GLFWwindow *, int width, int height) {
glViewport(0, 0, width, height);
}
@ -29,7 +29,9 @@ GLFWwindow* createWindow(int samples) {
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, false); //FIXME: Dev-only: Force floating on i3
#if FIXED_WINDOW
glfwWindowHint(GLFW_RESIZABLE, false); //Note: Dev-only: Force floating on i3
#endif
window = glfwCreateWindow(DEFAULT_WIDTH, DEFAULT_HEIGHT, "Univerxel", NULL, NULL);
if (window == NULL) {

View File

@ -3,7 +3,6 @@
#define UI_MARGIN 5
#define MIN_FPS 24
#define MAX_FPS 240
#define REPORT_BUFFER_SIZE 128
#include <toml.h>
#include <fstream>
@ -17,14 +16,15 @@
#include "contouring/index.hpp"
inline glm::vec4 fromHex(const std::string& str) {
int rgb[3] = {UCHAR_MAX};
std::array<int, 3> rgb = {UCHAR_MAX};
sscanf(str.c_str() + 1, "%02X%02X%02X", (unsigned int *)&rgb[0], (unsigned int *)&rgb[1], (unsigned int *)&rgb[2]);
return glm::vec4(rgb[0] * 1.f / UCHAR_MAX, rgb[1] * 1.f / UCHAR_MAX, rgb[2] * 1.f / UCHAR_MAX, 1);
}
inline std::string toHexa(const glm::vec4& rgb) {
auto out = (char*)malloc(8 * sizeof(char));
sprintf(out, "#%02X%02X%02X", (int)(rgb.x * UCHAR_MAX), (int)(rgb.y * UCHAR_MAX), (int)(rgb.z * UCHAR_MAX));
return std::string(out);
std::ostringstream sstr;
sstr << std::hex << std::setw(2) << std::setfill('0') <<
static_cast<int>(rgb.x * UCHAR_MAX) << static_cast<int>(rgb.y * UCHAR_MAX) << static_cast<int>(rgb.z * UCHAR_MAX) << std::endl;
return sstr.str();
}
/// Savable game options
@ -41,6 +41,7 @@ struct options {
renderer.mipMapLOD = config["render"]["texture_quality"].value_or(renderer.mipMapLOD);
renderer.main.pbr = config["render"]["pbr"].value_or(renderer.main.pbr);
renderer.main.triplanar = config["render"]["triplanar"].value_or(renderer.main.triplanar);
renderer.main.geometry = config["render"]["geometry"].value_or(renderer.main.geometry);
renderer.main.blend = config["render"]["blend"].value_or(renderer.main.blend);
renderer.main.fog = config["render"]["fog"].value_or(renderer.main.fog);
const std::string fog = config["render"]["fog_color"].value_or(std::string{"#000000"});
@ -50,11 +51,11 @@ struct options {
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);
voxel_size = config["world"]["unit_size"].value_or(1.f);
voxel_density = config["world"]["voxel_density"].value_or(1);
culling = config["mesh"]["culling"].value_or(true);
contouring_idx = contouring::idxByName(config["mesh"]["mode"].value_or(std::string("")));
for(const auto name: contouring::names) {
for(const auto& name: contouring::names) {
contouring_data.emplace(name, config["mesh"]["options"][name].value_or(std::string("")));
}
@ -91,6 +92,7 @@ struct options {
{"texture_quality", renderer.mipMapLOD},
{"pbr", renderer.main.pbr},
{"triplanar", renderer.main.triplanar},
{"geometry", renderer.main.geometry},
{"blend", renderer.main.blend},
{"fog", renderer.main.fog},
{"fog_color", toHexa(renderer.clear_color)},
@ -99,7 +101,7 @@ struct options {
config.insert_or_assign("world", toml::table({
{"load_distance", world.loadDistance},
{"keep_distance", world.keepDistance},
{"unit_size", voxel_size},
{"voxel_density", voxel_density},
{"path", world.folderPath}
}));
config.insert_or_assign("mesh", toml::table({
@ -107,9 +109,9 @@ struct options {
{"mode", contouring::names[contouring_idx]},
{"options", toml::table()}
}));
for(auto opt: contouring_data) {
if(!opt.second.empty())
config["mesh"]["options"].as_table()->insert_or_assign(opt.first, opt.second);
for(const auto& [key, val]: contouring_data) {
if(!val.empty())
config["mesh"]["options"].as_table()->insert_or_assign(key, val);
}
config.insert_or_assign("camera", toml::table({
@ -154,7 +156,7 @@ struct options {
bool show_debug_world;
world::Universe::options world;
float voxel_size;
int voxel_density;
bool show_debug_contouring;
bool culling;
@ -180,12 +182,12 @@ struct options {
/// Live state
struct state {
bool capture_mouse = true;
camera_pos position;
std::optional<std::pair<voxel_pos, world::Voxel>> look_at = {};
camera_pos position = camera_pos(voxel_pos(0), 1);
std::optional<world::Universe::ray_target> look_at = {};
std::shared_ptr<contouring::Abstract> contouring;
char console_buffer[256];
std::array<char, 256> console_buffer;
};
/// Readonly metrics
@ -193,11 +195,11 @@ struct reports {
struct main {
size_t tris_count = 0;
size_t models_count = 0;
circular_buffer<float> fps = circular_buffer<float>(REPORT_BUFFER_SIZE, 0);
circular_buffer<float> update = circular_buffer<float>(REPORT_BUFFER_SIZE, 0);
circular_buffer<float> render = circular_buffer<float>(REPORT_BUFFER_SIZE, 0);
circular_buffer<float> swap = circular_buffer<float>(REPORT_BUFFER_SIZE, 0);
circular_buffer<float> wait = circular_buffer<float>(REPORT_BUFFER_SIZE, 0);
report_buffer fps;
report_buffer update;
report_buffer render;
report_buffer swap;
report_buffer wait;
} main;
world::Universe::report world;
};

26
src/world/Area.cpp Normal file
View File

@ -0,0 +1,26 @@
#include "Area.hpp"
#include "Chunk.hpp"
using namespace world;
std::optional<std::shared_ptr<Chunk>> ChunkContainer::findInRange(const chunk_pos &pos) const {
const auto it = find(pos);
return it != end() ? std::make_optional(it->second) : std::nullopt;
}
std::optional<std::shared_ptr<const Chunk>> ChunkContainer::findOrEmpty(const chunk_pos &pos) const {
return inRange(pos) ? findInRange(pos) : std::make_optional(world::EMPTY_CHUNK);
}
std::shared_ptr<Region> Area::getRegion(const std::string& folderPath, const area_<region_pos>& pos) {
{ // Found
const auto shared = regions.lock_shared();
const auto it = shared->find(pos.second);
if(it != shared->end())
return it->second;
}
// Reading
const auto reg = std::make_shared<Region>(folderPath, pos);
const auto unique = regions.lock();
return unique->insert({pos.second, reg}).first->second;
}

75
src/world/Area.hpp Normal file
View File

@ -0,0 +1,75 @@
#pragma once
#include "forward.h"
#include <shared_mutex_guarded.h>
using namespace libguarded;
#include "region/index.hpp"
#include "Generator.hpp"
#include "../data/geometry/IBox.hpp"
namespace world {
/// Chunk map with restricted access
struct ChunkContainer: robin_hood::unordered_map<chunk_pos, std::shared_ptr<Chunk>> {
private:
int radius;
public:
/// Area radius in chunks
constexpr inline int getRadius() const { return radius; }
ChunkContainer(int radius): radius(radius) {
assert(radius > 0 && radius < (1 << 22) / CHUNK_LENGTH);
}
inline bool inRange(const chunk_pos& pos) const {
return glm::abs(pos.x) < radius && glm::abs(pos.y) < radius && glm::abs(pos.z) < radius;
}
std::optional<std::shared_ptr<Chunk>> findInRange(const chunk_pos &pos) const;
std::optional<std::shared_ptr<const Chunk>> findOrEmpty(const chunk_pos &pos) const;
};
/// Area (aka big group of chunks)
struct Area {
public:
using regions_t = robin_hood::unordered_map<region_pos, std::shared_ptr<Region>>;
struct params {
voxel_pos center;
int radius;
int seed = 42;
};
/// radius: size in chunk (length = radius * 2 + 1)
Area(const params& p): center(p.center), chunks(p.radius), generator(p.seed) { }
inline const area_pos &getOffset() const { return center; }
inline geometry::IBox getBounding() const {
const auto c = center.as_voxel();
return geometry::IBox(c - voxel_pos(CHUNK_LENGTH * (chunks.getRadius() - 1)),
c + voxel_pos(CHUNK_LENGTH * chunks.getRadius()));
}
/// Move offset return if chunk_change
bool inline move(const area_pos::offset_t &offset) {
const auto prev = glm::divide(center.as_voxel());
center = center + offset;
return prev != glm::divide(center.as_voxel());
}
inline const ChunkContainer &getChunks() const { return chunks; }
inline ChunkContainer &setChunks() { return chunks; }
std::shared_ptr<Region> getRegion(const std::string& folderPath, const area_<region_pos> &);
shared_guarded<regions_t>::handle getRegions() { return regions.lock(); }
inline Generator &getGenerator() { return generator; }
inline params getParams() const { return params{center.as_voxel(), chunks.getRadius(), generator.getSeed()}; }
private:
area_pos center;
//TODO: rotation
ChunkContainer chunks;
shared_guarded<regions_t> regions;
Generator generator;
};
}

View File

@ -2,76 +2,74 @@
#include "materials.hpp"
#include <algorithm>
#include "../data/math.hpp"
using namespace world;
#define DENSITY 0.f
#define GRANULARITY 30.f
#define RLE 1
constexpr auto DENSITY = 0.f;
constexpr auto GRANULARITY = 30.f;
Chunk::Chunk(const chunk_pos& pos, Generator& rnd) {
const auto [densitySet, materialSet] = rnd.getChunk(pos, CHUNK_LENGTH);
for (size_t i = 0; i < CHUNK_SIZE; i++) {
voxels[i].Density = std::clamp((densitySet[i] + DENSITY) * GRANULARITY, 0.f, 1.f) * UCHAR_MAX;
voxels[i].Material = voxels[i].Density > 0 ? 1 + std::clamp(static_cast<int>(std::lrint((materialSet[i] + 1) / 2 * (materials::count - 2))),
const auto density = std::clamp((densitySet[i] + DENSITY) * GRANULARITY, 0.f, 1.f) * Voxel::DENSITY_MAX;
const auto material = density > 0 ? 1 + std::clamp(static_cast<int>(std::lrint((materialSet[i] + 1) / 2 * (materials::count - 2))),
0, materials::count - 2) : 0; //NOTE: map (approx -1, 1) to (1, mat_max)
voxels[i] = Voxel(material, density);
}
FastNoiseSIMD::FreeNoiseSet(densitySet);
FastNoiseSIMD::FreeNoiseSet(materialSet);
rnd.free(densitySet);
rnd.free(materialSet);
}
Chunk::Chunk(std::istream& str) {
#ifdef RLE
ushort i = 0;
while(!str.eof()) {
ushort count;
Voxel voxel;
str.read(reinterpret_cast<char *>(&count), sizeof(count));
str.read(reinterpret_cast<char *>(&voxel.Density), sizeof(Voxel::Density));
str.read(reinterpret_cast<char *>(&voxel.Material), sizeof(Voxel::Material));
str.peek();
for (; count > 0; count--) {
voxels[i] = voxel;
i++;
#include <iostream>
Chunk::Chunk(std::istream& str, bool rle) {
if(rle) {
ushort i = 0;
while(!str.eof()) {
ushort count;
Voxel voxel;
str.read(reinterpret_cast<char *>(&count), sizeof(count));
str.read(reinterpret_cast<char *>(&voxel), sizeof(voxel));
str.peek();
for (; count > 0; count--) {
voxels[i] = voxel;
i++;
}
}
assert(i == CHUNK_SIZE && "Mismatch data length");
} else {
for(auto& voxel: voxels) {
str.read(reinterpret_cast<char *>(&voxel), sizeof(voxel));
}
}
assert(("Mismatch data length", i == CHUNK_SIZE-1));
#else
for(auto& voxel: voxels) {
str.read(reinterpret_cast<char *>(&voxel.Density), sizeof(Voxel::Density));
str.read(reinterpret_cast<char *>(&voxel.Material), sizeof(Voxel::Material));
}
#endif
}
Chunk::~Chunk() { }
void Chunk::write(std::ostream& str) const {
#ifdef RLE
auto it = voxels.begin();
ushort counter = 1;
Voxel current = *it;
while(true) {
it++;
const auto end = (it == voxels.end());
if(end || current.Density != it->Density || current.Material != it->Material) {
str.write(reinterpret_cast<char *>(&counter), sizeof(counter));
str.write(reinterpret_cast<char *>(&current.Density), sizeof(current.Density));
str.write(reinterpret_cast<char *>(&current.Material), sizeof(current.Material));
if(end)
break;
void Chunk::write(std::ostream& str, bool rle) const {
if (rle) {
const auto *it = voxels.begin();
ushort counter = 1;
Voxel current = *it;
while(true) {
++it;
const auto end = (it == voxels.end());
if(end || current.value != it->value) {
str.write(reinterpret_cast<char *>(&counter), sizeof(counter));
str.write(reinterpret_cast<char *>(&current), sizeof(current));
if(end)
break;
current = *it;
counter = 1;
} else {
counter++;
current = *it;
counter = 1;
} else {
counter++;
}
}
} else {
for(auto current: voxels) {
str.write(reinterpret_cast<char *>(&current), sizeof(current));
}
}
#else
for(auto current: voxels) {
str.write(reinterpret_cast<char *>(&current.Density), sizeof(current.Density));
str.write(reinterpret_cast<char *>(&current.Material), sizeof(current.Material));
}
#endif
}
std::optional<Faces> Chunk::update() {
@ -94,37 +92,37 @@ void Chunk::set(ushort idx, const Voxel& val) {
((!getNeighborIdx(idx, Face::Backward).has_value()) & Faces::Backward));
}
std::optional<ushort> Chunk::getNeighborIdx(ushort idx, Face face) {
switch (face) {
std::optional<chunk_voxel_idx> Chunk::getNeighborIdx(chunk_voxel_idx idx, Face dir) {
switch (dir) {
case Face::Forward:
if (idx % CHUNK_LENGTH >= CHUNK_LENGTH - 1)
if (idx % glm::IDX_LENGTH >= glm::IDX_LENGTH - 1)
return {};
return idx + 1;
case Face::Backward:
if (idx % CHUNK_LENGTH <= 0)
if (idx % glm::IDX_LENGTH <= 0)
return {};
return idx - 1;
case Face::Up:
if ((idx / CHUNK_LENGTH) % CHUNK_LENGTH >= CHUNK_LENGTH - 1)
if ((idx / glm::IDX_LENGTH) % glm::IDX_LENGTH >= glm::IDX_LENGTH - 1)
return {};
return idx + CHUNK_LENGTH;
return idx + glm::IDX_LENGTH;
case Face::Down:
if ((idx / CHUNK_LENGTH) % CHUNK_LENGTH <= 0)
if ((idx / glm::IDX_LENGTH) % glm::IDX_LENGTH <= 0)
return {};
return idx - CHUNK_LENGTH;
return idx - glm::IDX_LENGTH;
case Face::Right:
if (idx / CHUNK_LENGTH2 >= CHUNK_LENGTH - 1)
if (idx / glm::IDX_LENGTH2 >= glm::IDX_LENGTH - 1)
return {};
return idx + CHUNK_LENGTH2;
return idx + glm::IDX_LENGTH2;
case Face::Left:
if (idx / CHUNK_LENGTH2 <= 0)
if (idx / glm::IDX_LENGTH2 <= 0)
return {};
return idx - CHUNK_LENGTH2;
return idx - glm::IDX_LENGTH2;
default:
return {};

View File

@ -1,21 +1,25 @@
#pragma once
#include <memory>
#include <sstream>
#include "Generator.hpp"
#include "Voxel.hpp"
#include "../data/geometry/Faces.hpp"
#include <sstream>
#include "../data/math.hpp"
/// Chunk length
#define CHUNK_LENGTH2 (CHUNK_LENGTH * CHUNK_LENGTH)
#define CHUNK_SIZE (CHUNK_LENGTH2 * CHUNK_LENGTH)
namespace world {
const auto CHUNK_LENGTH2 = CHUNK_LENGTH * CHUNK_LENGTH;
const auto CHUNK_SIZE = CHUNK_LENGTH2 * CHUNK_LENGTH;
constexpr auto RLE = true; //NOTE: only 2.7% gain after zstd
using namespace geometry;
/// World part as linear 3d voxel array
struct Chunk {
class Chunk {
public:
Chunk() {}
Chunk(const chunk_pos& pos, Generator& rnd);
Chunk(std::istream& str);
Chunk(std::istream& str, bool rle = RLE);
~Chunk();
/// Update voxels
@ -29,42 +33,31 @@ namespace world {
modified = true;
}
// Get voxel from index
inline const Voxel& get(ushort idx) const {
inline const Voxel& get(chunk_voxel_idx idx) const {
return voxels[idx];
}
// Get voxel from position
inline const Voxel& getAt(const chunk_voxel_pos& pos) const {
return get(getIdx(pos));
return get(glm::toIdx(pos));
}
// Set voxel from index
void set(ushort idx, const Voxel& val);
void set(chunk_voxel_idx idx, const Voxel& val);
// Set voxel from position
void setAt(const chunk_voxel_pos& pos, const Voxel& val) {
set(getIdx(pos), val);
set(glm::toIdx(pos), val);
}
// Break voxel
Item breakAt(const chunk_voxel_pos& pos, const Voxel& val) {
const auto idx = getIdx(pos);
std::optional<Item> replace(chunk_voxel_idx idx, const Voxel& val) {
const auto res = voxels[idx];
set(idx, val);
return Item{res.Density, res.Material};
return {Item{res.density(), res.material()}}; //TODO: materials break table
}
// Is player modified
inline bool isModified() const { return modified; }
// Write to file.
// Using RLE
void write(std::ostream& str) const;
void write(std::ostream& str, bool rle = RLE) const;
static inline chunk_voxel_pos getPosition(ushort idx) {
return chunk_voxel_pos(idx / CHUNK_LENGTH2, (idx / CHUNK_LENGTH) % CHUNK_LENGTH, idx % CHUNK_LENGTH);
}
static inline ushort getIdx(chunk_voxel_pos pos) {
return getIdx(pos.x, pos.y, pos.z);
}
static inline ushort getIdx(uint x, uint y, uint z) {
return (x * CHUNK_LENGTH + y) * CHUNK_LENGTH + z;
}
static std::optional<ushort> getNeighborIdx(ushort idx, Face dir);
static std::optional<chunk_voxel_idx> getNeighborIdx(chunk_voxel_idx idx, Face dir);
private:
/// Chunk data
@ -76,4 +69,7 @@ namespace world {
/// Modified by player
bool modified = false;
};
/// Chunk full of air
static const std::shared_ptr<const Chunk> EMPTY_CHUNK = std::make_shared<Chunk>();
}

View File

@ -1,6 +1,10 @@
#pragma once
#if HASTY
#include <hastyNoise.h>
#else
#include <FastNoiseSIMD.h>
#endif
#include <tuple>
#include "position.h"
@ -8,34 +12,64 @@ namespace world {
/// Noise generator
class Generator {
public:
Generator(int seed = 42) {
using set = float *;
#if HASTY
using noise = std::unique_ptr<HastyNoise::NoiseSIMD>;
#else
using noise = FastNoiseSIMD *;
#endif
Generator(int seed = 42): seed(seed) {
#if HASTY
printf("double!!!\n");
const size_t fastestSimd = HastyNoise::GetFastestSIMD();
densityNoise = HastyNoise::CreateNoise(seed, fastestSimd);
materialNoise = HastyNoise::CreateNoise(seed * 5, fastestSimd);
materialNoise->SetNoiseType(HastyNoise::NoiseType::Cellular); // NOTE: probably heavy
materialNoise->SetCellularReturnType(HastyNoise::CellularReturnType::Value);
materialNoise->SetCellularDistanceFunction(HastyNoise::CellularDistance::Natural);
materialNoise->SetFrequency(.1);
#else
densityNoise = FastNoiseSIMD::NewFastNoiseSIMD(seed);
materialNoise = FastNoiseSIMD::NewFastNoiseSIMD(seed * 5);
materialNoise->SetNoiseType(FastNoiseSIMD::Cellular); // NOTE: probably heavy
materialNoise->SetCellularReturnType(FastNoiseSIMD::CellValue);
materialNoise->SetCellularDistanceFunction(FastNoiseSIMD::Natural);
materialNoise->SetFrequency(.1);
#endif
}
~Generator() {
#if !HASTY
delete densityNoise;
delete materialNoise;
#endif
}
/// Get block of given size with index pos.
/// @note owning pointers, @see FastNoiseSIMD::FreeNoiseSet
inline std::pair<float*, float*> getChunk(const chunk_pos& pos, int size) {
return {
/// @note owning pointers, call free(set set)
inline std::pair<set, set> getChunk(const chunk_pos& pos, int size) {
auto pair = std::make_pair(
densityNoise->GetNoiseSet(pos.x * size, pos.y * size, pos.z * size, size, size, size),
materialNoise->GetNoiseSet(pos.x * size, pos.y * size, pos.z * size, size, size, size),
};
materialNoise->GetNoiseSet(pos.x * size, pos.y * size, pos.z * size, size, size, size)
);
#if HASTY
return {pair.first.release(), pair.second.release()};
#else
return pair;
#endif
}
inline void freeChunk(std::pair<float*, float*>& set) {
FastNoiseSIMD::FreeNoiseSet(set.first);
FastNoiseSIMD::FreeNoiseSet(set.second);
static inline void free(set set) {
#if HASTY
HastyNoise::SetDeleter()(set);
#else
FastNoiseSIMD::FreeNoiseSet(set);
#endif
}
private:
FastNoiseSIMD *densityNoise;
FastNoiseSIMD *materialNoise;
inline int getSeed() const { return seed; }
private:
int seed;
noise densityNoise;
noise materialNoise;
};
}

View File

@ -1,6 +1,6 @@
#include "Universe.hpp"
#include <Remotery.h>
#include <Remotery.h> // NOLINT
#include <filesystem>
#include "../contouring/Dummy.hpp"
@ -8,7 +8,9 @@
using namespace world;
Universe::Universe(const Universe::options &options): generator(42), regionDict("content/zstd.dict"), contouring(std::make_shared<contouring::Dummy>()) {
const auto AREAS_FILE = "/areas.idx";
Universe::Universe(const Universe::options &options): dicts("content/zstd.dict"), contouring(std::make_shared<contouring::Dummy>()) {
setOptions(options);
folderPath = options.folderPath;
struct vec_istream: std::streambuf {
@ -17,223 +19,361 @@ Universe::Universe(const Universe::options &options): generator(42), regionDict(
}
};
running = true;
std::filesystem::create_directories(folderPath);
// Load workers
std::filesystem::create_directories(folderPath);
{
std::ifstream index(folderPath + AREAS_FILE);
if(index.good()) {
size_t size = 0;
index.read(reinterpret_cast<char *>(&size), sizeof(size));
std::map<size_t, Area::params> tmp;
while(!index.eof()) {
size_t id = UINT32_MAX;
index.read(reinterpret_cast<char *>(&id), sizeof(size_t));
Area::params params{voxel_pos(0), 0};
index.read(reinterpret_cast<char *>(&params.center.x), sizeof(voxel_pos::value_type));
index.read(reinterpret_cast<char *>(&params.center.y), sizeof(voxel_pos::value_type));
index.read(reinterpret_cast<char *>(&params.center.z), sizeof(voxel_pos::value_type));
index.read(reinterpret_cast<char *>(&params.radius), sizeof(int));
index.read(reinterpret_cast<char *>(&params.seed), sizeof(int));
[[maybe_unused]]
auto ok = tmp.emplace(id, params).second;
assert(ok && "Duplicated area");
index.peek();
}
assert(tmp.size() == size && "Corrupted areas index");
far_areas = data::generational::vector<Area::params>(tmp);
LOG_D(far_areas.size() << " areas loaded");
} else {
LOG_E("No index file!!! Probably a new world...");
//TODO: generate universe
far_areas.emplace(Area::params{voxel_pos(0), 1 << 20});
far_areas.emplace(Area::params{voxel_pos(0), 1, 43});
}
index.close();
}
entities.emplace(nullptr, glm::vec3(1), glm::vec3(2));
// Workers
for (size_t i = 0; i < 4; i++) {
loadWorkers.emplace_back([&] {
const auto ctx = regionDict.make_reader();
workers.emplace_back([&] {
const auto read_ctx = dicts.make_reader();
const auto write_ctx = dicts.make_writer();
while (running) {
chunk_pos pos;
loadQueue.wait();
if (loadQueue.pop(pos)) {
if (std::pair<area_<chunk_pos>, std::shared_ptr<Area>> task; loadQueue.pop(task)) {
//MAYBE: loadQueue.take to avoid duplicated work on fast move
rmt_ScopedCPUSample(ProcessLoad, 0);
const region_pos rPos = glm::divide(pos, region_chunk_pos(REGION_LENGTH));
const region_chunk_pos cPos = glm::modulo(pos, region_chunk_pos(REGION_LENGTH));
const auto reg = getRegion(rPos);
const auto &pos = task.first;
const auto rcPos = glm::split(pos.second);
const auto reg = task.second->getRegion(folderPath, std::make_pair(pos.first, rcPos.first));
Region::data data;
if(reg->read(cPos, ctx, data)) {
if(reg->read(rcPos.second, read_ctx, data)) {
rmt_ScopedCPUSample(ProcessRead, 0);
vec_istream idata(data);
std::istream iss(&idata);
loadedQueue.push({pos, std::make_shared<Chunk>(iss)});
} else {
rmt_ScopedCPUSample(ProcessGenerate, 0);
loadedQueue.push({pos, std::make_shared<Chunk>(pos, generator)});
loadedQueue.push({pos, std::make_shared<Chunk>(pos.second, task.second->getGenerator())});
}
}
}
});
}
// Save workers
for (size_t i = 0; i < 2; i++) {
saveWorkers.emplace_back([&] {
const auto ctx = regionDict.make_writer();
while (running) {
robin_hood::pair<chunk_pos, std::shared_ptr<Chunk>> task;
saveQueue.wait();
if (saveQueue.pop(task) && task.second->isModified()) {
} else if(save_task_t task; saveQueue.pop(task)) {
//MAYBE: queue.take to avoid concurent write or duplicated work on fast move
rmt_ScopedCPUSample(ProcessSave, 0);
std::ostringstream out;
task.second->write(out);
const region_pos rPos = glm::divide(task.first, region_chunk_pos(REGION_LENGTH));
const region_chunk_pos cPos = glm::modulo(task.first, region_chunk_pos(REGION_LENGTH));
const auto reg = getRegion(rPos);
reg->write(cPos, ctx, out.str());
if(task.second.second->isModified()) {
std::ostringstream out;
task.second.second->write(out);
const auto rcPos = glm::split(task.second.first);
const auto reg = task.first.second->getRegion(folderPath, std::make_pair(task.first.first, rcPos.first));
reg->write(rcPos.second, write_ctx, out.str());
}
} else {
loadQueue.wait();
}
}
});
}
}
Universe::~Universe() {
contouring = NULL;
contouring = nullptr;
// Save all
for(auto& pair: chunks) {
saveQueue.push(pair);
for(auto& area: areas) {
for(const auto& chunk: area.second->getChunks()) {
saveQueue.emplace(area, chunk);
}
}
loadQueue.notify_all();
if (auto size = saveQueue.size(); size > 0) {
std::cout << std::endl;
LOG_I("Saving " << size << " chunks");
const auto SAVE_CHECK_TIME = 500;
do {
std::cout << "\rSaving... " << size << " " << std::flush;
std::this_thread::sleep_for(std::chrono::microseconds(500));
std::this_thread::sleep_for(std::chrono::microseconds(SAVE_CHECK_TIME));
size = saveQueue.size();
} while (size > 0);
std::cout << std::endl;
}
saveAreas();
running = false;
loadQueue.notify();
saveQueue.notify();
loadQueue.notify_all();
for (auto &worker: loadWorkers) {
if (worker.joinable())
worker.join();
}
for (auto &worker: saveWorkers) {
for (auto &worker: workers) {
if (worker.joinable())
worker.join();
}
LOG_D("Universe disappeared");
}
std::shared_ptr<Region> Universe::getRegion(const region_pos& pos) {
std::shared_lock lock(regionMutex);
const auto it = regionCache.find(pos);
if(it == regionCache.end()) {
lock.unlock();
const auto reg = std::make_shared<Region>(folderPath, pos);
std::unique_lock u_lock(regionMutex);
return regionCache.insert({pos, reg}).first->second;
} else {
return it->second;
// Write areas index (warn: file io)
void Universe::saveAreas() const {
std::ofstream index(folderPath + AREAS_FILE, std::ios::out | std::ios::binary);
if(!index.good()) {
LOG_E("Areas index write error");
return;
}
{
size_t size = areas.size() + far_areas.size();
index.write(reinterpret_cast<char *>(&size), sizeof(size));
}
std::function write = [&](area_id id, Area::params params) {
auto idx = id.index;
index.write(reinterpret_cast<char *>(&idx), sizeof(size_t));
index.write(reinterpret_cast<char *>(&params.center.x), sizeof(voxel_pos::value_type));
index.write(reinterpret_cast<char *>(&params.center.y), sizeof(voxel_pos::value_type));
index.write(reinterpret_cast<char *>(&params.center.z), sizeof(voxel_pos::value_type));
index.write(reinterpret_cast<char *>(&params.radius), sizeof(int));
index.write(reinterpret_cast<char *>(&params.seed), sizeof(int));
};
for(const auto& area: areas) {
write(area.first, area.second->getParams());
}
far_areas.iter(write);
if(!index.good())
LOG_E("Areas index write error");
index.close();
}
void Universe::update(const camera_pos& pos, Universe::report& rep) {
const chunk_pos newPos = glm::divide(pos, chunk_voxel_pos(CHUNK_LENGTH));
void Universe::update(const voxel_pos& pos, float deltaTime, Universe::report& rep) {
rmt_ScopedCPUSample(Universe, 0);
const chunk_pos newPos = glm::divide(pos);
const auto chunkChange = last_pos != newPos;
last_pos = newPos;
rmt_ScopedCPUSample(Universe, 0);
// Update alive chunks
{
rmt_ScopedCPUSample(Update, 0);
auto it = chunks.begin();
while (it != chunks.end()) {
if (glm::length2(last_pos - it->first) > keepDistance * keepDistance) {
saveQueue.push(*it);
it = chunks.erase(it);
if(chunkChange) {
rmt_ScopedCPUSample(Far, 0);
far_areas.extract([&](area_id id, Area::params params){
if (const chunk_pos diff = glm::divide(pos - params.center);
glm::length2(diff) > glm::pow2(loadDistance + params.radius))
return false;
LOG_I("Load area " << id.index);
areas.emplace(id, std::make_shared<Area>(params));
return true;
});
}
{ // Update alive areas
rmt_ScopedCPUSample(World, 0);
size_t chunk_count = 0;
size_t region_count = 0;
const bool queuesEmpty = loadQueue.empty() && saveQueue.empty();
bool allLazy = true;
auto it = areas.begin();
while (it != areas.end()) {
rmt_ScopedCPUSample(Area, 0);
const bool chunkChangeArea = (false && it->first == 1 && it->second->move(glm::vec3(deltaTime))) || chunkChange; // TODO: area.velocity
const chunk_pos diff = glm::divide(pos - it->second->getOffset().as_voxel());
auto &chunks = it->second->setChunks();
if (glm::length2(diff) > glm::pow2(keepDistance + it->second->getChunks().getRadius())) {
auto it_c = chunks.begin();
while(it_c != chunks.end()) {
saveQueue.emplace(*it, *it_c);
it_c = chunks.erase(it_c);
}
LOG_I("Unload area " << it->first.index);
[[maybe_unused]]
auto ok = far_areas.put(it->first, it->second->getParams());
assert(ok);
it = areas.erase(it);
saveAreas();
} else {
if (const auto neighbors = it->second->update()) {
contouring->onUpdate(it->first, chunks, neighbors.value()); //TODO: get update update_type(simple(pos), complex)
} else if (chunkChange) {
contouring->onNotify(it->first, chunks);
bool lazyArea = queuesEmpty;
{ // Update alive chunks
rmt_ScopedCPUSample(Alive, 0);
auto it_c = chunks.begin();
while(it_c != chunks.end()) {
if (glm::length2(diff - it_c->first) > glm::pow2(keepDistance)) {
saveQueue.emplace(*it, *it_c);
lazyArea = false;
it_c = chunks.erase(it_c);
}else {
const area_<chunk_pos> acPos = std::make_pair(it->first, it_c->first);
if (const auto neighbors = it_c->second->update()) {
contouring->onUpdate(acPos, diff, chunks, neighbors.value());
} else if (chunkChangeArea) {
contouring->onNotify(acPos, diff, chunks);
}
++it_c;
chunk_count++;
}
}
}
if (chunkChangeArea) { // Enqueue missing chunks
rmt_ScopedCPUSample(Missing, 0);
//TODO: need dist so no easy sphere fill
for (int x = -loadDistance; x <= loadDistance; x++) {
for (int y = -loadDistance; y <= loadDistance; y++) {
for (int z = -loadDistance; z <= loadDistance; z++) {
const auto dist2 = x * x + y * y + z * z;
if (dist2 <= loadDistance * loadDistance) {
const auto p = diff + chunk_pos(x, y, z);
if (chunks.inRange(p) && chunks.find(p) == chunks.end()) {
loadQueue.push(std::make_pair(it->first, p), it->second, -dist2);
lazyArea = false;
}
}
}}}
}
allLazy &= lazyArea;
if (lazyArea) { // Clear un-used regions
rmt_ScopedCPUSample(Region, 0);
const auto unique = it->second->getRegions(); // MAYBE: shared then unique
region_count += unique->size();
for (auto it_r = unique->begin(); it_r != unique->end(); ++it_r) {
if (glm::length2(diff - glm::lvec3(it_r->first) * glm::lvec3(REGION_LENGTH)) > glm::pow2(keepDistance + REGION_LENGTH * 2)) {
unique->erase(it_r); //FIXME: may wait for os file access (long)
break; //NOTE: save one only max per frame
}
}
}
++it;
}
}
rep.chunk_count.push(chunk_count);
rep.region_count.push(allLazy ? region_count : rep.region_count.current());
}
rep.chunk_load.push(loadQueue.size());
rep.chunk_unload.push(saveQueue.size());
{
rmt_ScopedCPUSample(Contouring, 0);
contouring->update(pos);
contouring->update(pos, areas);
//MAYBE: if(chunkChange) contouring->notify(chunks);
}
// Find missing chunks
if(chunkChange) {
rmt_ScopedCPUSample(ToLoad, 0);
//NOTE: need dist so no easy sphere fill
for (int x = -loadDistance; x <= loadDistance; x++) {
for (int y = -loadDistance; y <= loadDistance; y++) {
for (int z = -loadDistance; z <= loadDistance; z++) {
const auto dist2 = x * x + y * y + z * z;
if (dist2 <= loadDistance * loadDistance) {
const chunk_pos p = last_pos + glm::ivec3(x, y, z);
if (chunks.find(p) == chunks.end()) {
loadQueue.push(p, -dist2);
}
}
}}}
{ // Update entities
rmt_ScopedCPUSample(Entities, 0);
size_t entity_count = 0;
entities.for_each([&](entity_id, Entity &val) {
val.instances.remove([&](entity_id, Entity::Instance &inst) {
entity_count++;
inst.pos += inst.velocity * deltaTime;
return glm::length2(glm::divide(pos - inst.pos.as_voxel())) > glm::pow2(keepDistance);
});
});
rep.entity_count.push(entity_count);
}
rep.chunk_load.push(loadQueue.size());
// Loaded chunks
{
{ // Store loaded chunks
rmt_ScopedCPUSample(Load, 0);
robin_hood::pair<chunk_pos, std::shared_ptr<Chunk>> loaded;
robin_hood::pair<area_<chunk_pos>, std::shared_ptr<Chunk>> loaded;
while (loadedQueue.pop(loaded)) {
chunks.insert(loaded);
contouring->onUpdate(loaded.first, chunks, Faces::All);
}
}
rep.chunk_count.push(chunks.size());
{
rmt_ScopedCPUSample(Region, 0);
std::unique_lock lock(regionMutex);
const auto me = glm::divide(last_pos, glm::ivec3(REGION_LENGTH));
for (auto it = regionCache.begin(); it != regionCache.end();) {
if (glm::length2(it->first - me) > keepDistance) {
std::cout << "rem" << std::endl;
it = regionCache.erase(it);
} else {
it++;
if (const auto it = areas.find(loaded.first.first); it != areas.end()) {
auto &chunks = it->second->setChunks();
chunks.emplace(loaded.first.second, loaded.second);
const chunk_pos diff = glm::divide(pos - it->second->getOffset().as_voxel());
contouring->onUpdate(loaded.first, diff, chunks, Faces::All);
}
}
}
rep.region_count.push(regionCache.size());
}
void Universe::setOptions(const Universe::options& options) {
loadDistance = options.loadDistance;
keepDistance = options.keepDistance;
}
void Universe::setContouring(std::shared_ptr<contouring::Abstract> ct) {
void Universe::setContouring(const std::shared_ptr<contouring::Abstract>& ct) {
contouring = ct;
last_pos = chunk_pos(INT_MAX); // trigger chunkChange on next update
}
std::optional<std::pair<voxel_pos, Voxel>> Universe::raycast(const Ray &ray) const {
std::optional<Universe::ray_target> Universe::raycast(const Ray &ray) const {
//MAYBE: ray + offset to get float precision
std::vector<voxel_pos> points;
ray.grid(points);
std::shared_ptr<Chunk> chunk = NULL;
chunk_pos chunk_pos(INT_MAX);
for(auto point: points) {
const auto pos = glm::divide(point, glm::ivec3(CHUNK_LENGTH));
if(pos != chunk_pos) {
if(const auto& newChunk = at(pos)) {
chunk = newChunk.value();
chunk_pos = pos;
std::optional<Universe::ray_target> target = std::nullopt;
size_t dist = points.size();
for(auto& area: areas) {
if(ray.intersect(area.second->getBounding()) != IBox::ContainmentType::Disjoint) {
const auto &offset = area.second->getOffset().as_voxel();
const auto &chunks = area.second->getChunks();
std::shared_ptr<Chunk> chunk = nullptr;
chunk_pos chunk_vec(INT_MAX);
for (size_t i = 0; i < dist; i++) {
const auto pos = points[i] - offset;
const chunk_pos cPos = glm::divide(pos);
if(cPos != chunk_vec) {
if (const auto it = chunks.find(cPos); it != chunks.end()) {
chunk = it->second;
chunk_vec = cPos;
} else {
chunk = nullptr;
}
}
if(chunk != nullptr) {
const auto voxel = chunk->getAt(glm::modulo(pos));
if(voxel.density() > 0) {
target = {ray_target{{area.first, pos}, voxel, offset}};
dist = i;
i = points.size();
}
}
}
}
if(chunk != NULL) {
const auto voxel = chunk->getAt(glm::modulo(point, glm::uvec3(CHUNK_LENGTH)));
if(voxel.Density > 0)
return {{point, voxel}};
}
}
return target;
}
std::optional<Item> Universe::set(const area_<voxel_pos>& pos, const Voxel& val) {
if(const auto it = areas.find(pos.first); it != areas.end()) {
auto &chunks = it->second->setChunks();
const auto split = glm::splitIdx(pos.second);
if(chunks.inRange(split.first))
if(const auto chunk = chunks.findInRange(split.first))
return {chunk.value()->replace(split.second, val)};
}
return {};
}
std::optional<Item> Universe::set(const voxel_pos& pos, const Voxel& val) {
const auto chunkPos = glm::divide(pos, glm::ivec3(CHUNK_LENGTH));
if(const auto& chunk = at(chunkPos)) {
return {chunk.value()->breakAt(glm::modulo(pos, glm::ivec3(CHUNK_LENGTH)), val)};
} else {
return {};
}
}
ItemList Universe::setCube(const voxel_pos& pos, const Voxel& val, int radius) {
ItemList Universe::setCube(const area_<voxel_pos>& pos, const Voxel& val, int radius) {
ItemList list;
for (int z = -radius; z <= radius; z++) {
for (int y = -radius; y <= radius; y++) {
for (int x = -radius; x <= radius; x++) {
//TODO: list.pop(val)
list.add(set(pos + glm::lvec3(x, y, z), val));
}}}
if(const auto it = areas.find(pos.first); it != areas.end()) {
auto& chunks = it->second->setChunks();
for (int z = -radius; z <= radius; z++) {
for (int y = -radius; y <= radius; y++) {
for (int x = -radius; x <= radius; x++) {
//TODO: list.pop(val)
const auto split = glm::splitIdx(pos.second + voxel_pos(x, y, z));
if(chunks.inRange(split.first))
if(const auto chunk = it->second->setChunks().findInRange(split.first))
list.add(chunk.value()->replace(split.second, val));
}}}
}
return list;
}
entity_instance_id Universe::addEntity(entity_id type, const Entity::Instance &instance) {
return std::make_pair(type, entities.at(type).instances.push(instance));
}
void Universe::getEntitiesModels(std::vector<std::pair<std::vector<glm::mat4>, buffer::Abstract *const>> &buffers, const std::optional<geometry::Frustum> &frustum, const glm::llvec3 &offset, int density) {
entities.iter([&](entity_id, const Entity &entity) {
std::vector<glm::mat4> mats;
entity.instances.iter([&](entity_id, const Entity::Instance &inst) {
const glm::vec3 fPos = (glm::vec3(inst.pos.raw_as_long() - offset * glm::llvec3(density)) + inst.pos.offset) / glm::vec3(density);
if (!frustum.has_value() || frustum.value().contains(geometry::Box::fromMin(fPos, entity.size)))
mats.emplace_back(glm::scale(glm::translate(glm::mat4(1), fPos * (float)density), entity.scale));
});
if(!mats.empty())
buffers.emplace_back(mats, entity.buffer);
});
}

View File

@ -1,25 +1,23 @@
#pragma once
#include <string>
#include <memory>
#include <thread>
#include <shared_mutex>
#include "../data/math.hpp"
#include "../data/safe_queue.hpp"
#include "../data/safe_priority_queue.hpp"
#include "../data/circular_buffer.hpp"
#include "../data/geometry/Ray.hpp"
#include "../data/geometry/Frustum.hpp"
#include "forward.h"
#include "Area.hpp"
#include "Voxel.hpp"
#include "region/index.hpp"
#include "Generator.hpp"
typedef glm::vec3 camera_pos;
#define REPORT_BUFFER_SIZE 128
namespace contouring {
class Abstract;
};
namespace buffer {
class Abstract;
}
using namespace data;
/// Universe data
@ -39,33 +37,58 @@ namespace world {
/// Reports to UI
struct report {
/// Chunks in memory
circular_buffer<float> chunk_count = circular_buffer<float>(REPORT_BUFFER_SIZE, 0); // MAYBE: store int
report_buffer chunk_count;
/// Loaded chunks
circular_buffer<float> chunk_load = circular_buffer<float>(REPORT_BUFFER_SIZE, 0);
report_buffer chunk_load;
/// Saved chunks
circular_buffer<float> chunk_unload = circular_buffer<float>(REPORT_BUFFER_SIZE, 0);
report_buffer chunk_unload;
/// Regions in memory
circular_buffer<float> region_count = circular_buffer<float>(REPORT_BUFFER_SIZE, 0);
report_buffer region_count;
/// Entity instances
report_buffer entity_count;
};
Universe(const options &);
~Universe();
/// Update physics and contouring
void update(const camera_pos& pos, report& rep);
void update(const voxel_pos &pos, float deltaTime, report &rep);
/// Apply new options
void setOptions(const options &);
struct ray_target {
area_<voxel_pos> pos;
Voxel value;
voxel_pos offset;
};
/// Get nearest voxel colliding ray
/// @note ray in world scale
std::optional<std::pair<voxel_pos, Voxel>> raycast(const geometry::Ray &ray) const;
std::optional<ray_target> raycast(const geometry::Ray &ray) const;
/// Set voxel at pos
std::optional<Item> set(const voxel_pos &pos, const Voxel &val);
std::optional<Item> set(const area_<voxel_pos> &pos, const Voxel &val);
/// Set cube of voxel with pos as center
ItemList setCube(const voxel_pos &pos, const Voxel &val, int radius);
/// MAYBE: allow set multi area
ItemList setCube(const area_<voxel_pos> &pos, const Voxel &val, int radius);
/// Entities commun properties
struct Entity {
Entity(buffer::Abstract* buffer, const glm::vec3& size = glm::vec3(1), const glm::vec3& scale = glm::vec3(1)):
buffer(buffer), size(size), scale(scale) { };
buffer::Abstract* buffer;
glm::vec3 size;
glm::vec3 scale;
struct Instance {
glm::ifvec3 pos;
glm::vec3 velocity;
};
data::generational::vector<Instance> instances;
};
/// Instante entity
entity_instance_id addEntity(entity_id type, const Entity::Instance &instance);
void getEntitiesModels(std::vector<std::pair<std::vector<glm::mat4>, buffer::Abstract *const>> &buffers, const std::optional<geometry::Frustum> &frustum, const glm::llvec3 &offset, int density);
/// Change contouring worker
void setContouring(std::shared_ptr<contouring::Abstract>);
void setContouring(const std::shared_ptr<contouring::Abstract>& ct);
/// Get current contouring worker
std::shared_ptr<contouring::Abstract> getContouring() const {
return contouring;
@ -74,34 +97,28 @@ namespace world {
private:
chunk_pos last_pos = chunk_pos(INT_MAX);
/// Data
chunk_map chunks;
/// Alive areas containing chunks
area_map areas;
using area_it_t = robin_hood::pair<area_id, std::shared_ptr<Area>>;
/// Dead areas
data::generational::vector<Area::params> far_areas;
void saveAreas() const;
std::optional<std::shared_ptr<Chunk>> at(const chunk_pos& pos) const {
const auto it = chunks.find(pos);
if(it == chunks.end())
return {};
return {it->second};
}
Generator generator;
data::generational::vector<Entity> entities;
bool running = true;
std::vector<std::thread> loadWorkers;
safe_priority_queue<chunk_pos, int> loadQueue;
safe_queue<robin_hood::pair<chunk_pos, std::shared_ptr<Chunk>>> loadedQueue;
std::vector<std::thread> workers;
safe_priority_queue_map<area_<chunk_pos>, std::shared_ptr<Area>, int, area_hash> loadQueue; //NOTE: consider Area const (getRegion uses mutex)
safe_queue<robin_hood::pair<area_<chunk_pos>, std::shared_ptr<Chunk>>> loadedQueue;
std::vector<std::thread> saveWorkers;
safe_queue<robin_hood::pair<chunk_pos, std::shared_ptr<Chunk>>> saveQueue; //NOTE: consider const Chunk
using save_task_t = std::pair<area_it_t, robin_hood::pair<chunk_pos, std::shared_ptr<Chunk>>>;
data::safe_queue<save_task_t> saveQueue; //NOTE: consider Area and Chunk const
int loadDistance;
int keepDistance;
std::string folderPath;
std::shared_mutex regionMutex; //MAYBE: shared_guard
robin_hood::unordered_map<region_pos, std::shared_ptr<Region>> regionCache;
dict_set regionDict;
std::shared_ptr<Region> getRegion(const region_pos &);
dict_set dicts;
/// Contouring worker
std::shared_ptr<contouring::Abstract> contouring;

View File

@ -5,13 +5,35 @@
namespace world {
/// Universe unit
struct Voxel {
/// Packed value
/// swap(1) + material(12) + density(3)
uint16_t value;
using material_t = uint_fast16_t;
using density_t = uint_fast8_t;
constexpr static const density_t DENSITY_MAX = 0b0111;
Voxel(uint16_t value = 0): value(value) { }
Voxel(material_t material, density_t density, bool swap = false) {
assert(density <= DENSITY_MAX);
assert(material < (1 << 12));
value = (swap & 0b1000'0000'0000'0000) |
((material << 3) & 0b0111'1111'1111'1000) |
(density & DENSITY_MAX);
}
/// Quantity of material
/// FIXME: low density area are cheatty
/// @note v < iso * UCHAR_MAX are useless
unsigned char Density;
constexpr inline density_t density() const {
return value & DENSITY_MAX;
}
/// Material type
/// @see world::materials
unsigned short Material;
constexpr inline material_t material() const {
return (value & 0b0111'1111'1111'1000) >> 3;
}
/// Swap value
/// Use external metadata table
constexpr inline bool swap() const {
return value & 0b1000'0000'0000'0000;
}
};
/// Stock of material
struct Item {
@ -19,7 +41,7 @@ namespace world {
unsigned long long Count;
/// Material type
/// @see world::materials
unsigned short Material;
Voxel::material_t Material;
};
/// List of materials
struct ItemList: std::map<unsigned short, unsigned long long> {

View File

@ -1,9 +1,12 @@
#pragma once
#include "../data/safe_queue.hpp"
#include <robin_hood.h>
#include "position.h"
namespace world {
class Chunk;
typedef robin_hood::unordered_map<chunk_pos, std::shared_ptr<Chunk>> chunk_map;
class Area;
using area_map = robin_hood::unordered_map<area_id, std::shared_ptr<Area>>;
class ChunkContainer;
}

View File

@ -6,6 +6,7 @@
namespace world::materials {
/// Materials count
static const auto count = 9;
static_assert(count < (USHRT_MAX >> 4)); //NOTE: for byte packing see Voxel
/// Materials textures
static const std::array<std::string, count> textures = {{"Air", "Sand", "Dirt", "Stone_path", "Mapl", "Seaside_rock", "Stone_wall", "Rough_rock", "Alien"}};
/// Materials roughness.

View File

@ -1,12 +1,53 @@
/**
* chunk_voxel_pos: u8 (contains u5: CHUNK_LENGTH)
* chunk_voxel_idx: u16
* chunk_pos: i56
* region_chunk_pos: u8 (contains u5: REGION_LENGTH)
* region_chunk_idx: u16
* region_pos: i48 (NOTE: trimmed to i32)
* voxel_pos: i64
*
* camera_offset: f32
* camera_pos: region_pos + camera_offset
*
* known limits:
* - noise: f32 2^24 spaghettification
* - noise: i32 2^32 loop
* - position: i32(chunk_pos)+u5(CHUNK_LENGTH) 2^36 no rendering (NOTE: only with chunk_pos as int)
* - position: i32(region_pos)+u5(REGION_LENGTH)+u5(CHUNK_LENGTH) 2^42 loop
*/
#pragma once
#include <functional>
#include "../data/glm.hpp"
#include "../data/generational.hpp"
#define CHUNK_LENGTH 32
#define REGION_LENGTH 32
const auto CHUNK_LENGTH = glm::IDX_LENGTH;
const auto REGION_LENGTH = glm::IDX_LENGTH;
typedef glm::lvec3 voxel_pos;
typedef glm::ivec3 chunk_pos;
typedef glm::ucvec3 chunk_voxel_pos;
typedef glm::ivec3 region_pos;
typedef glm::ucvec3 region_chunk_pos;
using voxel_pos = glm::llvec3;
using chunk_pos = glm::lvec3;
using chunk_voxel_pos = glm::ucvec3;
using chunk_voxel_idx = glm::u16;
using region_pos = glm::ivec3;
using region_chunk_pos = glm::ucvec3;
using region_chunk_idx = glm::u16;
using area_id = data::generational::id;
template <class pos>
using area_ = std::pair<area_id, pos>;
struct area_hash {
template <typename pos>
std::size_t operator()(area_<pos> const& a) const noexcept {
std::size_t h1 = std::hash<area_id>{}(a.first);
std::size_t h2 = std::hash<pos>{}(a.second);
return h1 ^ (h2 << 1);
}
};
using area_pos = glm::ifvec3;
using entity_id = data::generational::id;
using entity_instance_id = std::pair<entity_id, data::generational::id>;
using camera_pos = glm::ifvec3;

View File

@ -6,9 +6,9 @@ using namespace world;
#define REMOVE_CORRUPTED 1
FileRegion::FileRegion(const std::string &folderPath, const region_pos &pos) {
path = folderPath + '/' + std::to_string(pos.x) + '.' +
std::to_string(pos.y) + '.' + std::to_string(pos.z) + ".map";
FileRegion::FileRegion(const std::string &folderPath, const area_<region_pos> &pos) {
path = folderPath + '/' + std::to_string(pos.first.index) + '.' + std::to_string(pos.second.x) + '.' +
std::to_string(pos.second.y) + '.' + std::to_string(pos.second.z) + ".map";
load();
}
@ -45,14 +45,14 @@ void FileRegion::load() {
// Ignore content
if(!index.insert({pos, std::make_pair(size, file.tellg())}).second) {
std::cout << "Duplicated chunk: " << path << ":" << (int)pos.x << "." << (int)pos.y << "." << (int)pos.z << std::endl;
LOG_E("Duplicated chunk: " << path << ":" << (int)pos.x << "." << (int)pos.y << "." << (int)pos.z);
}
file.ignore(size);
file.peek();
}
if(file.bad()) {
std::cout << "region corrupted read " << path << std::endl;
LOG_E("region corrupted read " << path);
}
assert(index.size() == chunkCount);
@ -73,10 +73,10 @@ bool FileRegion::read(const region_chunk_pos& pos, const read_ctx& ctx, data& ou
out.resize(maxSize);
const auto actualSize = ZSTD_decompress_usingDDict(ctx.ctx, out.data(), out.size(), in->data(), in->size(), ctx.dict);
if(ZSTD_isError(actualSize)) {
std::cout << "Corrupted region chunk: " << path << ":" << (int)pos.x << "." << (int)pos.y << "." << (int)pos.z << " "
<< ZSTD_getErrorName(actualSize) << std::endl;
LOG_E("Corrupted region chunk: " << path << ":" << (int)pos.x << "." << (int)pos.y << "." << (int)pos.z << " "
<< ZSTD_getErrorName(actualSize));
#ifdef REMOVE_CORRUPTED
std::cout << "Removing" << std::endl;
LOG_W("Removing");
index.erase(it);
lock.unlock();
save(std::nullopt);
@ -93,8 +93,8 @@ void FileRegion::write(const region_chunk_pos& pos, const write_ctx& ctx, const
const auto actualSize = ZSTD_compress_usingCDict(ctx.ctx, buffer->data(), buffer->capacity(), in.data(), in.size(), ctx.dict);
if (ZSTD_isError(actualSize)) {
std::cout << "Corrupted chunk save: " << path << ":" << (int)pos.x << "." << (int)pos.y << "." << (int)pos.z << " "
<< ZSTD_getErrorName(actualSize) << std::endl;
LOG_E("Corrupted chunk save: " << path << ":" << (int)pos.x << "." << (int)pos.y << "." << (int)pos.z << " "
<< ZSTD_getErrorName(actualSize));
return;
}
buffer->resize(actualSize);
@ -108,7 +108,7 @@ void FileRegion::save(std::optional<std::pair<region_chunk_pos, std::unique_ptr<
std::ofstream tmpFile(tmpPath, std::ios::out | std::ios::binary);
if (!tmpFile.good()) {
std::cout << "Corrupted region path: " << tmpPath << std::endl;
LOG_E("Corrupted region path: " << tmpPath);
return;
}
@ -165,7 +165,7 @@ void FileRegion::save(std::optional<std::pair<region_chunk_pos, std::unique_ptr<
}
if (!tmpFile.good()) {
std::cout << "region corrupted write " << tmpPath << std::endl;
LOG_E("Region corrupted write " << tmpPath);
tmpFile.close();
return;
}

View File

@ -11,7 +11,7 @@ namespace world {
///Group of chunks saved as a single file only pointer
class FileRegion {
public:
FileRegion(const std::string& folderPath, const region_pos &pos);
FileRegion(const std::string& folderPath, const area_<region_pos> &pos);
~FileRegion();
typedef std::vector<char> data;
@ -27,7 +27,7 @@ namespace world {
std::shared_mutex mutex;
std::ifstream file;
robin_hood::unordered_flat_map<region_chunk_pos, std::pair<ushort, std::streampos>> index;
robin_hood::unordered_map<region_chunk_pos, std::pair<ushort, std::streampos>> index;
void load();
};

View File

@ -5,9 +5,9 @@ using namespace world;
#define REMOVE_CORRUPTED 1
#define LAZYNESS 8
MemoryRegion::MemoryRegion(const std::string &folderPath, const region_pos &pos) {
path = folderPath + '/' + std::to_string(pos.x) + '.' +
std::to_string(pos.y) + '.' + std::to_string(pos.z) + ".map";
MemoryRegion::MemoryRegion(const std::string &folderPath, const area_<region_pos> &pos) {
path = folderPath + '/' + std::to_string(pos.first.index) + '.' + std::to_string(pos.second.x) + '.' +
std::to_string(pos.second.y) + '.' + std::to_string(pos.second.z) + ".map";
load();
}
@ -57,13 +57,13 @@ void MemoryRegion::load() {
data->resize(size);
file.read(data->data(), data->size());
if(!content.insert({pos, data}).second) {
std::cout << "Duplicated chunk: " << path << ":" << (int)pos.x << "." << (int)pos.y << "." << (int)pos.z << std::endl;
LOG_E("Duplicated chunk: " << path << ":" << (int)pos.x << "." << (int)pos.y << "." << (int)pos.z);
}
file.peek();
}
if(file.bad()) {
std::cout << "region corrupted read " << path << std::endl;
LOG_E("Region corrupted read " << path);
}
assert(content.size() == chunkCount);
file.close();
@ -81,10 +81,10 @@ bool MemoryRegion::read(const region_chunk_pos& pos, const read_ctx& ctx, data&
out.resize(maxSize);
const auto actualSize = ZSTD_decompress_usingDDict(ctx.ctx, out.data(), out.size(), in->data(), in->size(), ctx.dict);
if(ZSTD_isError(actualSize)) {
std::cout << "Corrupted region chunk: " << path << ":" << (int)pos.x << "." << (int)pos.y << "." << (int)pos.z << " "
<< ZSTD_getErrorName(actualSize) << std::endl;
LOG_E("Corrupted region chunk: " << path << ":" << (int)pos.x << "." << (int)pos.y << "." << (int)pos.z << " "
<< ZSTD_getErrorName(actualSize));
#ifdef REMOVE_CORRUPTED
std::cout << "Removing" << std::endl;
LOG_W("Removing");
lock.unlock();
{
std::unique_lock ulock(mutex);
@ -104,8 +104,8 @@ void MemoryRegion::write(const region_chunk_pos& pos, const write_ctx& ctx, cons
const auto actualSize = ZSTD_compress_usingCDict(ctx.ctx, buffer->data(), buffer->capacity(), in.data(), in.size(), ctx.dict);
if (ZSTD_isError(actualSize)) {
std::cout << "Corrupted chunk save: " << path << ":" << (int)pos.x << "." << (int)pos.y << "." << (int)pos.z << " "
<< ZSTD_getErrorName(actualSize) << std::endl;
LOG_E("Corrupted chunk save: " << path << ":" << (int)pos.x << "." << (int)pos.y << "." << (int)pos.z << " "
<< ZSTD_getErrorName(actualSize));
return;
}
buffer->resize(actualSize);
@ -134,7 +134,7 @@ void MemoryRegion::save(bool force) {
std::ofstream file(path, std::ios::out | std::ios::binary);
if (!file.good()) {
std::cout << "Corrupted region path: " << path << std::endl;
LOG_E("Corrupted region path: " << path);
return;
}
@ -169,7 +169,7 @@ void MemoryRegion::save(bool force) {
}
if (!file.good()) {
std::cout << "region corrupted write " << path << std::endl;
LOG_E("Region corrupted write " << path);
file.close();
return;
}

View File

@ -12,7 +12,7 @@ namespace world {
///Group of chunks saved as a single file in memory
class MemoryRegion {
public:
MemoryRegion(const std::string& folderPath, const region_pos &pos);
MemoryRegion(const std::string& folderPath, const area_<region_pos> &pos);
~MemoryRegion();
typedef std::vector<char> data;
@ -27,7 +27,7 @@ namespace world {
//TODO: use tickets to remove unused regions
std::shared_mutex mutex;
robin_hood::unordered_flat_map<region_chunk_pos, data*> content;
robin_hood::unordered_map<region_chunk_pos, data*> content;
bool changed = false;
void load();

View File

@ -1,10 +1,10 @@
#pragma once
#include <zstd.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <cassert>
#include "../../data/logger.hpp"
namespace world {
struct read_ctx {
@ -27,7 +27,7 @@ namespace world {
dict_set(const std::string& path) {
std::ifstream is(path, std::ios::in | std::ios::binary | std::ios::ate);
if(!is.good()) {
std::cout << "missing dict " << path << std::endl;
LOG_E("Missing dict " << path);
exit(1);
}
const auto end = is.tellg();

63
src/zstd_sampler.cpp Normal file
View File

@ -0,0 +1,63 @@
/**
* \file zstd_sampler.cpp
* \brief Generate uncompressed chunks
* \author Maelys Bois
* \version 0.0.1
*
* Generate random uncompressed chunks for Zstd dictionary training.
*/
#include "world/Chunk.hpp"
#include <cstdlib>
#include <ctime>
#include <chrono>
#include <fstream>
#include <iostream>
#include <vector>
#include <zstd.h>
#include <zdict.h>
const auto KB = 1000;
const auto COUNT = 100;
const auto SIZE = COUNT * KB;
const auto SAMPLES = 100;
const auto RANGE = 1 << 18;
/// Entry point
int main(int /*unused*/, char * /*unused*/[])
{
std::srand(std::time(nullptr));
world::Generator generator(std::rand());
std::vector<char> samples;
samples.reserve(SIZE * SAMPLES);
std::vector<size_t> sizes;
sizes.reserve(SAMPLES * 10);
std::cout << "Generating..." << std::endl;
std::chrono::nanoseconds gen_time(0);
while(samples.size() < SIZE * SAMPLES) {
const auto start = std::chrono::high_resolution_clock::now();
world::Chunk chunk(chunk_pos(std::rand() % RANGE, std::rand() % RANGE, std::rand() % RANGE), generator);
gen_time += (std::chrono::high_resolution_clock::now() - start);
std::ostringstream oss;
chunk.write(oss);
const auto str = oss.str();
samples.insert(samples.end(), str.begin(), str.end());
sizes.push_back(str.size());
}
std::cout << gen_time.count() / sizes.size() << "ns/chunk" << std::endl;
std::vector<char> dict(SIZE);
std::cout << "Training on " << sizes.size() << " samples..." << std::endl;
const auto actualSize = ZDICT_trainFromBuffer(dict.data(), dict.size(), samples.data(), sizes.data(), sizes.size());
if(ZSTD_isError(actualSize)) {
std::cout << "Error: " << ZSTD_getErrorName(actualSize) << std::endl;
return 1;
}
std::cout << "Dictionary of " << actualSize / KB << "kb" << std::endl;
std::ofstream out("content/zstd.dict");
out.write(dict.data(), actualSize);
out.close();
return 0;
}