Init
This commit is contained in:
134
.drone.yml
Normal file
134
.drone.yml
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: default
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build_image_base
|
||||||
|
pull: never
|
||||||
|
image: bash_n_docker
|
||||||
|
volumes:
|
||||||
|
- name: dockersock
|
||||||
|
path: /var/run/docker.sock
|
||||||
|
- name: docker_config
|
||||||
|
path: /docker_config/config.json
|
||||||
|
commands:
|
||||||
|
- export DOCKER_CONFIG=/docker_config
|
||||||
|
- docker build -f docker/Dockerfile_Base -t spacegame_base .
|
||||||
|
|
||||||
|
- name: build_image_linux
|
||||||
|
pull: never
|
||||||
|
image: bash_n_docker
|
||||||
|
depends_on:
|
||||||
|
- build_image_base
|
||||||
|
volumes:
|
||||||
|
- name: dockersock
|
||||||
|
path: /var/run/docker.sock
|
||||||
|
- name: docker_config
|
||||||
|
path: /docker_config/config.json
|
||||||
|
commands:
|
||||||
|
- export DOCKER_CONFIG=/docker_config
|
||||||
|
- docker build -f docker/Dockerfile_Linux -t spacegame_linux .
|
||||||
|
|
||||||
|
- name: build_image_windows
|
||||||
|
pull: never
|
||||||
|
image: bash_n_docker
|
||||||
|
depends_on:
|
||||||
|
- build_image_base
|
||||||
|
volumes:
|
||||||
|
- name: dockersock
|
||||||
|
path: /var/run/docker.sock
|
||||||
|
- name: docker_config
|
||||||
|
path: /docker_config/config.json
|
||||||
|
commands:
|
||||||
|
- export DOCKER_CONFIG=/docker_config
|
||||||
|
- docker build -f docker/Dockerfile_Windows -t spacegame_windows .
|
||||||
|
|
||||||
|
|
||||||
|
- name: build_game_linux
|
||||||
|
pull: never
|
||||||
|
image: spacegame_linux
|
||||||
|
depends_on:
|
||||||
|
- build_image_linux
|
||||||
|
commands:
|
||||||
|
- >
|
||||||
|
cmake -S . -B build_linux -G Ninja
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE="cmake/clang_toolchain.cmake"
|
||||||
|
-DCMAKE_BUILD_TYPE=Release
|
||||||
|
-DSPACEGAME_BUILD_SHADERS=ON
|
||||||
|
- cmake --build build_linux --verbose -- -l $(($(nproc)+4))
|
||||||
|
|
||||||
|
- name: build_game_windows
|
||||||
|
pull: never
|
||||||
|
image: spacegame_windows
|
||||||
|
depends_on:
|
||||||
|
- build_image_windows
|
||||||
|
commands:
|
||||||
|
- >
|
||||||
|
cmake -S . -B build_windows -G Ninja
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE="cmake/clang_mingw_toolchain.cmake"
|
||||||
|
-DCMAKE_BUILD_TYPE=Release
|
||||||
|
-DSPACEGAME_BUILD_SHADERS=OFF
|
||||||
|
- cmake --build build_windows --verbose -- -l $(($(nproc)+4))
|
||||||
|
|
||||||
|
|
||||||
|
- name: package_game
|
||||||
|
pull: never
|
||||||
|
image: spacegame_linux
|
||||||
|
depends_on:
|
||||||
|
- build_game_linux
|
||||||
|
- build_game_windows
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- tag
|
||||||
|
commands:
|
||||||
|
# We need to fetch the release tag in order to get its message
|
||||||
|
- git fetch origin tag $DRONE_TAG --no-tags
|
||||||
|
# Get short version from tag name and long version from tag message
|
||||||
|
# - VERSION_SHORT=$DRONE_TAG
|
||||||
|
- VERSION_LONG=$(git for-each-ref refs/tags/$DRONE_TAG --format='%(contents)')
|
||||||
|
# Package linux
|
||||||
|
- LINUX_VERSION="$VERSION_LONG.linux"
|
||||||
|
- mkdir spacegame_linux
|
||||||
|
- echo $LINUX_VERSION > spacegame_linux/version.txt
|
||||||
|
- cp build_linux/bin/x86_64/Release/spacegame spacegame_linux/spacegame
|
||||||
|
- cp -r assets spacegame_linux/assets
|
||||||
|
- zip -r spacegame_linux.zip spacegame_linux
|
||||||
|
# Package windows
|
||||||
|
- WINDOWS_VERSION="$VERSION_LONG.windows"
|
||||||
|
- mkdir spacegame_windows
|
||||||
|
- echo $WINDOWS_VERSION > spacegame_windows/version.txt
|
||||||
|
- cp build_windows/bin/x86_64/Release/spacegame.exe spacegame_windows/spacegame.exe
|
||||||
|
- cp -r assets spacegame_windows/assets
|
||||||
|
- zip -r spacegame_windows.zip spacegame_windows
|
||||||
|
|
||||||
|
|
||||||
|
- name: release
|
||||||
|
image: plugins/gitea-release
|
||||||
|
depends_on:
|
||||||
|
- package_game
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- tag
|
||||||
|
settings:
|
||||||
|
api_key:
|
||||||
|
from_secret: api_key
|
||||||
|
base_url: https://git.lph.zone
|
||||||
|
prerelease: true
|
||||||
|
files:
|
||||||
|
- spacegame_linux.zip
|
||||||
|
- spacegame_windows.zip
|
||||||
|
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: dockersock
|
||||||
|
host:
|
||||||
|
path: /var/run/docker.sock
|
||||||
|
|
||||||
|
- name: docker_config
|
||||||
|
host:
|
||||||
|
path: /home/crydsch/.docker/config.json
|
||||||
|
|
||||||
|
|
||||||
|
image_pull_secrets:
|
||||||
|
- dockerconfig
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.vscode
|
||||||
|
build*
|
||||||
|
assets/shaders/*.spv
|
||||||
|
atlas
|
||||||
5
3rdparty/.gitignore
vendored
Normal file
5
3rdparty/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
glfw
|
||||||
|
bx
|
||||||
|
bimg
|
||||||
|
bgfx
|
||||||
|
bgfx.cmake
|
||||||
20
3rdparty/README.md
vendored
Normal file
20
3rdparty/README.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
## emilib: Loose collection of misc C++ libs
|
||||||
|
|
||||||
|
Repo.: https://github.com/emilk/emilib
|
||||||
|
|
||||||
|
|
||||||
|
## stb: single-file public domain libraries for C/C++
|
||||||
|
|
||||||
|
Repo.: https://github.com/nothings/stb
|
||||||
|
|
||||||
|
|
||||||
|
## bgfx for rendering
|
||||||
|
|
||||||
|
Repos.:
|
||||||
|
- bx
|
||||||
|
- bimg
|
||||||
|
- bgfx
|
||||||
|
- bgfx.cmake
|
||||||
|
|
||||||
|
## glfw for window creation
|
||||||
671
3rdparty/emilib/hash_map.hpp
vendored
Normal file
671
3rdparty/emilib/hash_map.hpp
vendored
Normal file
@@ -0,0 +1,671 @@
|
|||||||
|
// By Emil Ernerfeldt 2014-2017
|
||||||
|
// LICENSE:
|
||||||
|
// This software is dual-licensed to the public domain and under the following
|
||||||
|
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||||
|
// publish, and distribute this file as you see fit.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iterator>
|
||||||
|
#include <utility>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
namespace emilib {
|
||||||
|
|
||||||
|
// (Crydsch) fixes for standalone usage
|
||||||
|
#define DCHECK_NE_F(x,y) assert((x) != (y))
|
||||||
|
#define DCHECK_F(x) assert(x)
|
||||||
|
#define DCHECK_EQ_F(x,y) assert((x) == (y))
|
||||||
|
|
||||||
|
/// like std::equal_to but no need to #include <functional>
|
||||||
|
template<typename T>
|
||||||
|
struct HashMapEqualTo
|
||||||
|
{
|
||||||
|
constexpr bool operator()(const T& lhs, const T& rhs) const
|
||||||
|
{
|
||||||
|
return lhs == rhs;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A cache-friendly hash table with open addressing, linear probing and power-of-two capacity
|
||||||
|
template <typename KeyT, typename ValueT, typename HashT = std::hash<KeyT>, typename EqT = HashMapEqualTo<KeyT>>
|
||||||
|
class HashMap
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
using MyType = HashMap<KeyT, ValueT, HashT, EqT>;
|
||||||
|
|
||||||
|
using PairT = std::pair<KeyT, ValueT>;
|
||||||
|
public:
|
||||||
|
using size_type = size_t;
|
||||||
|
using value_type = PairT;
|
||||||
|
using reference = PairT&;
|
||||||
|
using const_reference = const PairT&;
|
||||||
|
|
||||||
|
class iterator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
using difference_type = size_t;
|
||||||
|
using distance_type = size_t;
|
||||||
|
using value_type = std::pair<KeyT, ValueT>;
|
||||||
|
using pointer = value_type*;
|
||||||
|
using reference = value_type&;
|
||||||
|
|
||||||
|
iterator() { }
|
||||||
|
|
||||||
|
iterator(MyType* hash_map, size_t bucket) : _map(hash_map), _bucket(bucket)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator& operator++()
|
||||||
|
{
|
||||||
|
this->goto_next_element();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator operator++(int)
|
||||||
|
{
|
||||||
|
size_t old_index = _bucket;
|
||||||
|
this->goto_next_element();
|
||||||
|
return iterator(_map, old_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
reference operator*() const
|
||||||
|
{
|
||||||
|
return _map->_pairs[_bucket];
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer operator->() const
|
||||||
|
{
|
||||||
|
return _map->_pairs + _bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const iterator& rhs) const
|
||||||
|
{
|
||||||
|
DCHECK_EQ_F(_map, rhs._map);
|
||||||
|
return this->_bucket == rhs._bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const iterator& rhs) const
|
||||||
|
{
|
||||||
|
DCHECK_EQ_F(_map, rhs._map);
|
||||||
|
return this->_bucket != rhs._bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void goto_next_element()
|
||||||
|
{
|
||||||
|
DCHECK_LT_F(_bucket, _map->_num_buckets);
|
||||||
|
do {
|
||||||
|
_bucket++;
|
||||||
|
} while (_bucket < _map->_num_buckets && _map->_states[_bucket] != State::FILLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
//private:
|
||||||
|
// friend class MyType;
|
||||||
|
public:
|
||||||
|
MyType* _map;
|
||||||
|
size_t _bucket;
|
||||||
|
};
|
||||||
|
|
||||||
|
class const_iterator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
using difference_type = size_t;
|
||||||
|
using distance_type = size_t;
|
||||||
|
using value_type = const std::pair<KeyT, ValueT>;
|
||||||
|
using pointer = value_type*;
|
||||||
|
using reference = value_type&;
|
||||||
|
|
||||||
|
const_iterator() { }
|
||||||
|
|
||||||
|
const_iterator(iterator proto) : _map(proto._map), _bucket(proto._bucket)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator(const MyType* hash_map, size_t bucket) : _map(hash_map), _bucket(bucket)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator& operator++()
|
||||||
|
{
|
||||||
|
this->goto_next_element();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator operator++(int)
|
||||||
|
{
|
||||||
|
size_t old_index = _bucket;
|
||||||
|
this->goto_next_element();
|
||||||
|
return const_iterator(_map, old_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
reference operator*() const
|
||||||
|
{
|
||||||
|
return _map->_pairs[_bucket];
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer operator->() const
|
||||||
|
{
|
||||||
|
return _map->_pairs + _bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const const_iterator& rhs) const
|
||||||
|
{
|
||||||
|
DCHECK_EQ_F(_map, rhs._map);
|
||||||
|
return this->_bucket == rhs._bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const const_iterator& rhs) const
|
||||||
|
{
|
||||||
|
DCHECK_EQ_F(_map, rhs._map);
|
||||||
|
return this->_bucket != rhs._bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void goto_next_element()
|
||||||
|
{
|
||||||
|
DCHECK_LT_F(_bucket, _map->_num_buckets);
|
||||||
|
do {
|
||||||
|
_bucket++;
|
||||||
|
} while (_bucket < _map->_num_buckets && _map->_states[_bucket] != State::FILLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
//private:
|
||||||
|
// friend class MyType;
|
||||||
|
public:
|
||||||
|
const MyType* _map;
|
||||||
|
size_t _bucket;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
HashMap() = default;
|
||||||
|
|
||||||
|
HashMap(const HashMap& other)
|
||||||
|
{
|
||||||
|
reserve(other.size());
|
||||||
|
insert(other.cbegin(), other.cend());
|
||||||
|
}
|
||||||
|
|
||||||
|
HashMap(HashMap&& other)
|
||||||
|
{
|
||||||
|
*this = std::move(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
HashMap& operator=(const HashMap& other)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
reserve(other.size());
|
||||||
|
insert(other.cbegin(), other.cend());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator=(HashMap&& other)
|
||||||
|
{
|
||||||
|
this->swap(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
~HashMap()
|
||||||
|
{
|
||||||
|
for (size_t bucket=0; bucket<_num_buckets; ++bucket) {
|
||||||
|
if (_states[bucket] == State::FILLED) {
|
||||||
|
_pairs[bucket].~PairT();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(_states);
|
||||||
|
free(_pairs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void swap(HashMap& other)
|
||||||
|
{
|
||||||
|
std::swap(_hasher, other._hasher);
|
||||||
|
std::swap(_eq, other._eq);
|
||||||
|
std::swap(_states, other._states);
|
||||||
|
std::swap(_pairs, other._pairs);
|
||||||
|
std::swap(_num_buckets, other._num_buckets);
|
||||||
|
std::swap(_num_filled, other._num_filled);
|
||||||
|
std::swap(_max_probe_length, other._max_probe_length);
|
||||||
|
std::swap(_mask, other._mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
|
iterator begin()
|
||||||
|
{
|
||||||
|
size_t bucket = 0;
|
||||||
|
while (bucket<_num_buckets && _states[bucket] != State::FILLED) {
|
||||||
|
++bucket;
|
||||||
|
}
|
||||||
|
return iterator(this, bucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator cbegin() const
|
||||||
|
{
|
||||||
|
size_t bucket = 0;
|
||||||
|
while (bucket<_num_buckets && _states[bucket] != State::FILLED) {
|
||||||
|
++bucket;
|
||||||
|
}
|
||||||
|
return const_iterator(this, bucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator begin() const
|
||||||
|
{
|
||||||
|
return cbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator end()
|
||||||
|
{
|
||||||
|
return iterator(this, _num_buckets);
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator cend() const
|
||||||
|
{
|
||||||
|
return const_iterator(this, _num_buckets);
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator end() const
|
||||||
|
{
|
||||||
|
return cend();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const
|
||||||
|
{
|
||||||
|
return _num_filled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
return _num_filled==0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the number of buckets.
|
||||||
|
size_t bucket_count() const
|
||||||
|
{
|
||||||
|
return _num_buckets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns average number of elements per bucket.
|
||||||
|
float load_factor() const
|
||||||
|
{
|
||||||
|
return static_cast<float>(_num_filled) / static_cast<float>(_num_buckets);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
template<typename KeyLike>
|
||||||
|
iterator find(const KeyLike& key)
|
||||||
|
{
|
||||||
|
auto bucket = this->find_filled_bucket(key);
|
||||||
|
if (bucket == (size_t)-1) {
|
||||||
|
return this->end();
|
||||||
|
}
|
||||||
|
return iterator(this, bucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename KeyLike>
|
||||||
|
const_iterator find(const KeyLike& key) const
|
||||||
|
{
|
||||||
|
auto bucket = this->find_filled_bucket(key);
|
||||||
|
if (bucket == (size_t)-1)
|
||||||
|
{
|
||||||
|
return this->end();
|
||||||
|
}
|
||||||
|
return const_iterator(this, bucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename KeyLike>
|
||||||
|
bool contains(const KeyLike& k) const
|
||||||
|
{
|
||||||
|
return find_filled_bucket(k) != (size_t)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename KeyLike>
|
||||||
|
size_t count(const KeyLike& k) const
|
||||||
|
{
|
||||||
|
return find_filled_bucket(k) != (size_t)-1 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the matching ValueT or nullptr if k isn't found.
|
||||||
|
template<typename KeyLike>
|
||||||
|
ValueT* try_get(const KeyLike& k)
|
||||||
|
{
|
||||||
|
auto bucket = find_filled_bucket(k);
|
||||||
|
if (bucket != (size_t)-1) {
|
||||||
|
return &_pairs[bucket].second;
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Const version of the above
|
||||||
|
template<typename KeyLike>
|
||||||
|
const ValueT* try_get(const KeyLike& k) const
|
||||||
|
{
|
||||||
|
auto bucket = find_filled_bucket(k);
|
||||||
|
if (bucket != (size_t)-1) {
|
||||||
|
return &_pairs[bucket].second;
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function.
|
||||||
|
template<typename KeyLike>
|
||||||
|
const ValueT get_or_return_default(const KeyLike& k) const
|
||||||
|
{
|
||||||
|
const ValueT* ret = try_get(k);
|
||||||
|
if (ret) {
|
||||||
|
return *ret;
|
||||||
|
} else {
|
||||||
|
return ValueT();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------
|
||||||
|
|
||||||
|
/// Returns a pair consisting of an iterator to the inserted element
|
||||||
|
/// (or to the element that prevented the insertion)
|
||||||
|
/// and a bool denoting whether the insertion took place.
|
||||||
|
std::pair<iterator, bool> insert(const KeyT& key, const ValueT& value)
|
||||||
|
{
|
||||||
|
check_expand_need();
|
||||||
|
|
||||||
|
auto bucket = find_or_allocate(key);
|
||||||
|
|
||||||
|
if (_states[bucket] == State::FILLED) {
|
||||||
|
return { iterator(this, bucket), false };
|
||||||
|
} else {
|
||||||
|
_states[bucket] = State::FILLED;
|
||||||
|
new(_pairs + bucket) PairT(key, value);
|
||||||
|
_num_filled++;
|
||||||
|
return { iterator(this, bucket), true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<iterator, bool> insert(const std::pair<KeyT, ValueT>& p)
|
||||||
|
{
|
||||||
|
return insert(p.first, p.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(const_iterator begin, const_iterator end)
|
||||||
|
{
|
||||||
|
// TODO: reserve space exactly once.
|
||||||
|
for (; begin != end; ++begin) {
|
||||||
|
insert(begin->first, begin->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as above, but contains(key) MUST be false
|
||||||
|
void insert_unique(KeyT&& key, ValueT&& value)
|
||||||
|
{
|
||||||
|
DCHECK_F(!contains(key));
|
||||||
|
check_expand_need();
|
||||||
|
auto bucket = find_empty_bucket(key);
|
||||||
|
_states[bucket] = State::FILLED;
|
||||||
|
new(_pairs + bucket) PairT(std::move(key), std::move(value));
|
||||||
|
_num_filled++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert_unique(std::pair<KeyT, ValueT>&& p)
|
||||||
|
{
|
||||||
|
insert_unique(std::move(p.first), std::move(p.second));
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert_or_assign(const KeyT& key, ValueT&& value)
|
||||||
|
{
|
||||||
|
check_expand_need();
|
||||||
|
|
||||||
|
auto bucket = find_or_allocate(key);
|
||||||
|
|
||||||
|
// Check if inserting a new value rather than overwriting an old entry
|
||||||
|
if (_states[bucket] == State::FILLED) {
|
||||||
|
_pairs[bucket].second = value;
|
||||||
|
} else {
|
||||||
|
_states[bucket] = State::FILLED;
|
||||||
|
new(_pairs + bucket) PairT(key, value);
|
||||||
|
_num_filled++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the old value or ValueT() if it didn't exist.
|
||||||
|
ValueT set_get(const KeyT& key, const ValueT& new_value)
|
||||||
|
{
|
||||||
|
check_expand_need();
|
||||||
|
|
||||||
|
auto bucket = find_or_allocate(key);
|
||||||
|
|
||||||
|
// Check if inserting a new value rather than overwriting an old entry
|
||||||
|
if (_states[bucket] == State::FILLED) {
|
||||||
|
ValueT old_value = _pairs[bucket].second;
|
||||||
|
_pairs[bucket] = new_value.second;
|
||||||
|
return old_value;
|
||||||
|
} else {
|
||||||
|
_states[bucket] = State::FILLED;
|
||||||
|
new(_pairs + bucket) PairT(key, new_value);
|
||||||
|
_num_filled++;
|
||||||
|
return ValueT();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like std::map<KeyT,ValueT>::operator[].
|
||||||
|
ValueT& operator[](const KeyT& key)
|
||||||
|
{
|
||||||
|
check_expand_need();
|
||||||
|
|
||||||
|
auto bucket = find_or_allocate(key);
|
||||||
|
|
||||||
|
/* Check if inserting a new value rather than overwriting an old entry */
|
||||||
|
if (_states[bucket] != State::FILLED) {
|
||||||
|
_states[bucket] = State::FILLED;
|
||||||
|
new(_pairs + bucket) PairT(key, ValueT());
|
||||||
|
_num_filled++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _pairs[bucket].second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------
|
||||||
|
|
||||||
|
/// Erase an element from the hash table.
|
||||||
|
/// return false if element was not found
|
||||||
|
bool erase(const KeyT& key)
|
||||||
|
{
|
||||||
|
auto bucket = find_filled_bucket(key);
|
||||||
|
if (bucket != (size_t)-1) {
|
||||||
|
_states[bucket] = State::ACTIVE;
|
||||||
|
_pairs[bucket].~PairT();
|
||||||
|
_num_filled -= 1;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Erase an element using an iterator.
|
||||||
|
/// Returns an iterator to the next element (or end()).
|
||||||
|
iterator erase(iterator it)
|
||||||
|
{
|
||||||
|
DCHECK_EQ_F(it._map, this);
|
||||||
|
DCHECK_LT_F(it._bucket, _num_buckets);
|
||||||
|
_states[it._bucket] = State::ACTIVE;
|
||||||
|
_pairs[it._bucket].~PairT();
|
||||||
|
_num_filled -= 1;
|
||||||
|
return ++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove all elements, keeping full capacity.
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
for (size_t bucket=0; bucket<_num_buckets; ++bucket) {
|
||||||
|
if (_states[bucket] == State::FILLED) {
|
||||||
|
_states[bucket] = State::INACTIVE;
|
||||||
|
_pairs[bucket].~PairT();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_num_filled = 0;
|
||||||
|
_max_probe_length = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make room for this many elements
|
||||||
|
void reserve(size_t num_elems)
|
||||||
|
{
|
||||||
|
size_t required_buckets = num_elems + num_elems/2 + 1;
|
||||||
|
if (required_buckets <= _num_buckets) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t num_buckets = 4;
|
||||||
|
while (num_buckets < required_buckets) { num_buckets *= 2; }
|
||||||
|
|
||||||
|
auto new_states = (State*)malloc(num_buckets * sizeof(State));
|
||||||
|
auto new_pairs = (PairT*)malloc(num_buckets * sizeof(PairT));
|
||||||
|
|
||||||
|
if (!new_states || !new_pairs) {
|
||||||
|
free(new_states);
|
||||||
|
free(new_pairs);
|
||||||
|
// throw std::bad_alloc();
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
//auto old_num_filled = _num_filled;
|
||||||
|
auto old_num_buckets = _num_buckets;
|
||||||
|
auto old_states = _states;
|
||||||
|
auto old_pairs = _pairs;
|
||||||
|
|
||||||
|
_num_filled = 0;
|
||||||
|
_num_buckets = num_buckets;
|
||||||
|
_mask = _num_buckets - 1;
|
||||||
|
_states = new_states;
|
||||||
|
_pairs = new_pairs;
|
||||||
|
|
||||||
|
std::fill_n(_states, num_buckets, State::INACTIVE);
|
||||||
|
|
||||||
|
_max_probe_length = -1;
|
||||||
|
|
||||||
|
for (size_t src_bucket=0; src_bucket<old_num_buckets; src_bucket++) {
|
||||||
|
if (old_states[src_bucket] == State::FILLED) {
|
||||||
|
auto& src_pair = old_pairs[src_bucket];
|
||||||
|
|
||||||
|
auto dst_bucket = find_empty_bucket(src_pair.first);
|
||||||
|
DCHECK_NE_F(dst_bucket, (size_t)-1);
|
||||||
|
DCHECK_NE_F(_states[dst_bucket], State::FILLED);
|
||||||
|
_states[dst_bucket] = State::FILLED;
|
||||||
|
new(_pairs + dst_bucket) PairT(std::move(src_pair));
|
||||||
|
_num_filled += 1;
|
||||||
|
|
||||||
|
src_pair.~PairT();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//DCHECK_EQ_F(old_num_filled, _num_filled);
|
||||||
|
|
||||||
|
free(old_states);
|
||||||
|
free(old_pairs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Can we fit another element?
|
||||||
|
void check_expand_need()
|
||||||
|
{
|
||||||
|
reserve(_num_filled + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the bucket with this key, or return (size_t)-1
|
||||||
|
template<typename KeyLike>
|
||||||
|
size_t find_filled_bucket(const KeyLike& key) const
|
||||||
|
{
|
||||||
|
if (empty()) { return (size_t)-1; } // Optimization
|
||||||
|
|
||||||
|
auto hash_value = _hasher(key);
|
||||||
|
for (int offset=0; offset<=_max_probe_length; ++offset) {
|
||||||
|
auto bucket = (hash_value + offset) & _mask;
|
||||||
|
if (_states[bucket] == State::FILLED) {
|
||||||
|
if (_eq(_pairs[bucket].first, key)) {
|
||||||
|
return bucket;
|
||||||
|
}
|
||||||
|
} else if (_states[bucket] == State::INACTIVE) {
|
||||||
|
return (size_t)-1; // End of the chain!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (size_t)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the bucket with this key, or return a good empty bucket to place the key in.
|
||||||
|
// In the latter case, the bucket is expected to be filled.
|
||||||
|
size_t find_or_allocate(const KeyT& key)
|
||||||
|
{
|
||||||
|
auto hash_value = _hasher(key);
|
||||||
|
size_t hole = (size_t)-1;
|
||||||
|
int offset=0;
|
||||||
|
for (; offset<=_max_probe_length; ++offset) {
|
||||||
|
auto bucket = (hash_value + offset) & _mask;
|
||||||
|
|
||||||
|
if (_states[bucket] == State::FILLED) {
|
||||||
|
if (_eq(_pairs[bucket].first, key)) {
|
||||||
|
return bucket;
|
||||||
|
}
|
||||||
|
} else if (_states[bucket] == State::INACTIVE) {
|
||||||
|
return bucket;
|
||||||
|
} else {
|
||||||
|
// ACTIVE: keep searching
|
||||||
|
if (hole == (size_t)-1) {
|
||||||
|
hole = bucket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No key found - but maybe a hole for it
|
||||||
|
|
||||||
|
assert(offset == _max_probe_length+1);
|
||||||
|
|
||||||
|
if (hole != (size_t)-1) {
|
||||||
|
return hole;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No hole found within _max_probe_length
|
||||||
|
for (; ; ++offset) {
|
||||||
|
auto bucket = (hash_value + offset) & _mask;
|
||||||
|
|
||||||
|
if (_states[bucket] != State::FILLED) {
|
||||||
|
_max_probe_length = offset;
|
||||||
|
return bucket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// key is not in this map. Find a place to put it.
|
||||||
|
size_t find_empty_bucket(const KeyT& key)
|
||||||
|
{
|
||||||
|
auto hash_value = _hasher(key);
|
||||||
|
for (int offset=0; ; ++offset) {
|
||||||
|
auto bucket = (hash_value + offset) & _mask;
|
||||||
|
if (_states[bucket] != State::FILLED) {
|
||||||
|
if (offset > _max_probe_length) {
|
||||||
|
_max_probe_length = offset;
|
||||||
|
}
|
||||||
|
return bucket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class State : uint8_t
|
||||||
|
{
|
||||||
|
INACTIVE, // Never been touched
|
||||||
|
ACTIVE, // Is inside a search-chain, but is empty
|
||||||
|
FILLED // Is set with key/value
|
||||||
|
};
|
||||||
|
|
||||||
|
HashT _hasher;
|
||||||
|
EqT _eq;
|
||||||
|
State* _states = nullptr;
|
||||||
|
PairT* _pairs = nullptr;
|
||||||
|
size_t _num_buckets = 0;
|
||||||
|
size_t _num_filled = 0;
|
||||||
|
int _max_probe_length = -1; // Our longest bucket-brigade is this long. ONLY when we have zero elements is this ever negative (-1).
|
||||||
|
size_t _mask = 0; // _num_buckets minus one
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace emilib
|
||||||
586
3rdparty/emilib/hash_set.hpp
vendored
Normal file
586
3rdparty/emilib/hash_set.hpp
vendored
Normal file
@@ -0,0 +1,586 @@
|
|||||||
|
// By Emil Ernerfeldt 2014-2016
|
||||||
|
// LICENSE:
|
||||||
|
// This software is dual-licensed to the public domain and under the following
|
||||||
|
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||||
|
// publish, and distribute this file as you see fit.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdlib> // malloc
|
||||||
|
#include <iterator>
|
||||||
|
#include <utility>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
namespace emilib {
|
||||||
|
|
||||||
|
/// like std::equal_to but no need to `#include <functional>`
|
||||||
|
template<typename T>
|
||||||
|
struct HashSetEqualTo
|
||||||
|
{
|
||||||
|
constexpr bool operator()(const T& lhs, const T& rhs) const
|
||||||
|
{
|
||||||
|
return lhs == rhs;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A cache-friendly hash set with open addressing, linear probing and power-of-two capacity
|
||||||
|
template <typename KeyT, typename HashT = std::hash<KeyT>, typename EqT = HashSetEqualTo<KeyT>>
|
||||||
|
class HashSet
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
using MyType = HashSet<KeyT, HashT, EqT>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using size_type = size_t;
|
||||||
|
using value_type = KeyT;
|
||||||
|
using reference = KeyT&;
|
||||||
|
using const_reference = const KeyT&;
|
||||||
|
|
||||||
|
class iterator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
using difference_type = size_t;
|
||||||
|
using distance_type = size_t;
|
||||||
|
using value_type = KeyT;
|
||||||
|
using pointer = value_type*;
|
||||||
|
using reference = value_type&;
|
||||||
|
|
||||||
|
iterator() { }
|
||||||
|
|
||||||
|
iterator(MyType* hash_set, size_t bucket) : _set(hash_set), _bucket(bucket)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator& operator++()
|
||||||
|
{
|
||||||
|
this->goto_next_element();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator operator++(int)
|
||||||
|
{
|
||||||
|
size_t old_index = _bucket;
|
||||||
|
this->goto_next_element();
|
||||||
|
return iterator(_set, old_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
reference operator*() const
|
||||||
|
{
|
||||||
|
return _set->_keys[_bucket];
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer operator->() const
|
||||||
|
{
|
||||||
|
return _set->_keys + _bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const iterator& rhs) const
|
||||||
|
{
|
||||||
|
DCHECK_EQ_F(_set, rhs._set);
|
||||||
|
return this->_bucket == rhs._bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const iterator& rhs) const
|
||||||
|
{
|
||||||
|
DCHECK_EQ_F(_set, rhs._set);
|
||||||
|
return this->_bucket != rhs._bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void goto_next_element()
|
||||||
|
{
|
||||||
|
DCHECK_LT_F(_bucket, _set->_num_buckets);
|
||||||
|
do {
|
||||||
|
_bucket++;
|
||||||
|
} while (_bucket < _set->_num_buckets && _set->_states[_bucket] != State::FILLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
//private:
|
||||||
|
// friend class MyType;
|
||||||
|
public:
|
||||||
|
MyType* _set;
|
||||||
|
size_t _bucket;
|
||||||
|
};
|
||||||
|
|
||||||
|
class const_iterator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
using difference_type = size_t;
|
||||||
|
using distance_type = size_t;
|
||||||
|
using value_type = const KeyT;
|
||||||
|
using pointer = value_type*;
|
||||||
|
using reference = value_type&;
|
||||||
|
|
||||||
|
const_iterator() { }
|
||||||
|
|
||||||
|
const_iterator(iterator proto) : _set(proto._set), _bucket(proto._bucket)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator(const MyType* hash_set, size_t bucket) : _set(hash_set), _bucket(bucket)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator& operator++()
|
||||||
|
{
|
||||||
|
this->goto_next_element();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator operator++(int)
|
||||||
|
{
|
||||||
|
size_t old_index = _bucket;
|
||||||
|
this->goto_next_element();
|
||||||
|
return const_iterator(_set, old_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
reference operator*() const
|
||||||
|
{
|
||||||
|
return _set->_keys[_bucket];
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer operator->() const
|
||||||
|
{
|
||||||
|
return _set->_keys + _bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const const_iterator& rhs) const
|
||||||
|
{
|
||||||
|
DCHECK_EQ_F(_set, rhs._set);
|
||||||
|
return this->_bucket == rhs._bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const const_iterator& rhs) const
|
||||||
|
{
|
||||||
|
DCHECK_EQ_F(_set, rhs._set);
|
||||||
|
return this->_bucket != rhs._bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void goto_next_element()
|
||||||
|
{
|
||||||
|
DCHECK_LT_F(_bucket, _set->_num_buckets);
|
||||||
|
do {
|
||||||
|
_bucket++;
|
||||||
|
} while (_bucket < _set->_num_buckets && _set->_states[_bucket] != State::FILLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
//private:
|
||||||
|
// friend class MyType;
|
||||||
|
public:
|
||||||
|
const MyType* _set;
|
||||||
|
size_t _bucket;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
HashSet() = default;
|
||||||
|
|
||||||
|
HashSet(const HashSet& other)
|
||||||
|
{
|
||||||
|
reserve(other.size());
|
||||||
|
insert(other.cbegin(), other.cend());
|
||||||
|
}
|
||||||
|
|
||||||
|
HashSet(HashSet&& other)
|
||||||
|
{
|
||||||
|
*this = std::move(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
HashSet& operator=(const HashSet& other)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
reserve(other.size());
|
||||||
|
insert(other.cbegin(), other.cend());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator=(HashSet&& other)
|
||||||
|
{
|
||||||
|
this->swap(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
~HashSet()
|
||||||
|
{
|
||||||
|
for (size_t bucket=0; bucket<_num_buckets; ++bucket) {
|
||||||
|
if (_states[bucket] == State::FILLED) {
|
||||||
|
_keys[bucket].~KeyT();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(_states);
|
||||||
|
free(_keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
void swap(HashSet& other)
|
||||||
|
{
|
||||||
|
std::swap(_hasher, other._hasher);
|
||||||
|
std::swap(_eq, other._eq);
|
||||||
|
std::swap(_states, other._states);
|
||||||
|
std::swap(_keys, other._keys);
|
||||||
|
std::swap(_num_buckets, other._num_buckets);
|
||||||
|
std::swap(_num_filled, other._num_filled);
|
||||||
|
std::swap(_max_probe_length, other._max_probe_length);
|
||||||
|
std::swap(_mask, other._mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
|
iterator begin()
|
||||||
|
{
|
||||||
|
size_t bucket = 0;
|
||||||
|
while (bucket<_num_buckets && _states[bucket] != State::FILLED) {
|
||||||
|
++bucket;
|
||||||
|
}
|
||||||
|
return iterator(this, bucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator cbegin() const
|
||||||
|
{
|
||||||
|
size_t bucket = 0;
|
||||||
|
while (bucket<_num_buckets && _states[bucket] != State::FILLED) {
|
||||||
|
++bucket;
|
||||||
|
}
|
||||||
|
return const_iterator(this, bucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator begin() const
|
||||||
|
{
|
||||||
|
return cbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator end()
|
||||||
|
{
|
||||||
|
return iterator(this, _num_buckets);
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator cend() const
|
||||||
|
{
|
||||||
|
return const_iterator(this, _num_buckets);
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator end() const
|
||||||
|
{
|
||||||
|
return cend();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const
|
||||||
|
{
|
||||||
|
return _num_filled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
return _num_filled==0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the number of buckets.
|
||||||
|
size_t bucket_count() const
|
||||||
|
{
|
||||||
|
return _num_buckets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns average number of elements per bucket.
|
||||||
|
float load_factor() const
|
||||||
|
{
|
||||||
|
return static_cast<float>(_num_filled) / static_cast<float>(_num_buckets);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
iterator find(const KeyT& key)
|
||||||
|
{
|
||||||
|
auto bucket = this->find_filled_bucket(key);
|
||||||
|
if (bucket == (size_t)-1) {
|
||||||
|
return this->end();
|
||||||
|
}
|
||||||
|
return iterator(this, bucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator find(const KeyT& key) const
|
||||||
|
{
|
||||||
|
auto bucket = this->find_filled_bucket(key);
|
||||||
|
if (bucket == (size_t)-1) {
|
||||||
|
return this->end();
|
||||||
|
}
|
||||||
|
return const_iterator(this, bucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool contains(const KeyT& k) const
|
||||||
|
{
|
||||||
|
return find_filled_bucket(k) != (size_t)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t count(const KeyT& k) const
|
||||||
|
{
|
||||||
|
return find_filled_bucket(k) != (size_t)-1 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------
|
||||||
|
|
||||||
|
/// Insert an element, unless it already exists.
|
||||||
|
/// Returns a pair consisting of an iterator to the inserted element
|
||||||
|
/// (or to the element that prevented the insertion)
|
||||||
|
/// and a bool denoting whether the insertion took place.
|
||||||
|
std::pair<iterator, bool> insert(const KeyT& key)
|
||||||
|
{
|
||||||
|
check_expand_need();
|
||||||
|
|
||||||
|
auto bucket = find_or_allocate(key);
|
||||||
|
|
||||||
|
if (_states[bucket] == State::FILLED) {
|
||||||
|
return { iterator(this, bucket), false };
|
||||||
|
} else {
|
||||||
|
_states[bucket] = State::FILLED;
|
||||||
|
new(_keys + bucket) KeyT(key);
|
||||||
|
_num_filled++;
|
||||||
|
return { iterator(this, bucket), true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert an element, unless it already exists.
|
||||||
|
/// Returns a pair consisting of an iterator to the inserted element
|
||||||
|
/// (or to the element that prevented the insertion)
|
||||||
|
/// and a bool denoting whether the insertion took place.
|
||||||
|
std::pair<iterator, bool> insert(KeyT&& key)
|
||||||
|
{
|
||||||
|
check_expand_need();
|
||||||
|
|
||||||
|
auto bucket = find_or_allocate(key);
|
||||||
|
|
||||||
|
if (_states[bucket] == State::FILLED) {
|
||||||
|
return { iterator(this, bucket), false };
|
||||||
|
} else {
|
||||||
|
_states[bucket] = State::FILLED;
|
||||||
|
new(_keys + bucket) KeyT(std::move(key));
|
||||||
|
_num_filled++;
|
||||||
|
return { iterator(this, bucket), true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
std::pair<iterator, bool> emplace(Args&&... args)
|
||||||
|
{
|
||||||
|
return insert(KeyT(std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(const_iterator begin, const_iterator end)
|
||||||
|
{
|
||||||
|
// TODO: reserve space exactly once.
|
||||||
|
for (; begin != end; ++begin) {
|
||||||
|
insert(*begin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as above, but contains(key) MUST be false
|
||||||
|
void insert_unique(KeyT key)
|
||||||
|
{
|
||||||
|
DCHECK_F(!contains(key));
|
||||||
|
check_expand_need();
|
||||||
|
auto bucket = find_empty_bucket(key);
|
||||||
|
_states[bucket] = State::FILLED;
|
||||||
|
new(_keys + bucket) KeyT(std::move(key));
|
||||||
|
_num_filled++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------
|
||||||
|
|
||||||
|
/// Erase an element from the hash set.
|
||||||
|
/// return false if element was not found.
|
||||||
|
bool erase(const KeyT& key)
|
||||||
|
{
|
||||||
|
auto bucket = find_filled_bucket(key);
|
||||||
|
if (bucket != (size_t)-1) {
|
||||||
|
_states[bucket] = State::ACTIVE;
|
||||||
|
_keys[bucket].~KeyT();
|
||||||
|
_num_filled -= 1;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Erase an element using an iterator.
|
||||||
|
/// Returns an iterator to the next element (or end()).
|
||||||
|
iterator erase(iterator it)
|
||||||
|
{
|
||||||
|
DCHECK_EQ_F(it._set, this);
|
||||||
|
DCHECK_LT_F(it._bucket, _num_buckets);
|
||||||
|
_states[it._bucket] = State::ACTIVE;
|
||||||
|
_keys[it._bucket].~KeyT();
|
||||||
|
_num_filled -= 1;
|
||||||
|
return ++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove all elements, keeping full capacity.
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
for (size_t bucket=0; bucket<_num_buckets; ++bucket) {
|
||||||
|
if (_states[bucket] == State::FILLED) {
|
||||||
|
_states[bucket] = State::INACTIVE;
|
||||||
|
_keys[bucket].~KeyT();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_num_filled = 0;
|
||||||
|
_max_probe_length = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make room for this many elements
|
||||||
|
void reserve(size_t num_elems)
|
||||||
|
{
|
||||||
|
size_t required_buckets = num_elems + num_elems/2 + 1;
|
||||||
|
if (required_buckets <= _num_buckets) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t num_buckets = 4;
|
||||||
|
while (num_buckets < required_buckets) { num_buckets *= 2; }
|
||||||
|
|
||||||
|
auto new_states = (State*)malloc(num_buckets * sizeof(State));
|
||||||
|
auto new_keys = (KeyT*)malloc(num_buckets * sizeof(KeyT));
|
||||||
|
|
||||||
|
if (!new_states || !new_keys) {
|
||||||
|
free(new_states);
|
||||||
|
free(new_keys);
|
||||||
|
// throw std::bad_alloc();
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
// auto old_num_filled = _num_filled;
|
||||||
|
auto old_num_buckets = _num_buckets;
|
||||||
|
auto old_states = _states;
|
||||||
|
auto old_keys = _keys;
|
||||||
|
|
||||||
|
_num_filled = 0;
|
||||||
|
_num_buckets = num_buckets;
|
||||||
|
_mask = _num_buckets - 1;
|
||||||
|
_states = new_states;
|
||||||
|
_keys = new_keys;
|
||||||
|
|
||||||
|
std::fill_n(_states, num_buckets, State::INACTIVE);
|
||||||
|
|
||||||
|
_max_probe_length = -1;
|
||||||
|
|
||||||
|
for (size_t src_bucket=0; src_bucket<old_num_buckets; src_bucket++) {
|
||||||
|
if (old_states[src_bucket] == State::FILLED) {
|
||||||
|
auto& src = old_keys[src_bucket];
|
||||||
|
|
||||||
|
auto dst_bucket = find_empty_bucket(src);
|
||||||
|
DCHECK_NE_F(dst_bucket, (size_t)-1);
|
||||||
|
DCHECK_NE_F(_states[dst_bucket], State::FILLED);
|
||||||
|
_states[dst_bucket] = State::FILLED;
|
||||||
|
new(_keys + dst_bucket) KeyT(std::move(src));
|
||||||
|
_num_filled += 1;
|
||||||
|
|
||||||
|
src.~KeyT();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DCHECK_EQ_F(old_num_filled, _num_filled);
|
||||||
|
|
||||||
|
free(old_states);
|
||||||
|
free(old_keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Can we fit another element?
|
||||||
|
void check_expand_need()
|
||||||
|
{
|
||||||
|
reserve(_num_filled + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the bucket with this key, or return (size_t)-1
|
||||||
|
size_t find_filled_bucket(const KeyT& key) const
|
||||||
|
{
|
||||||
|
if (empty()) { return (size_t)-1; } // Optimization
|
||||||
|
|
||||||
|
auto hash_value = _hasher(key);
|
||||||
|
for (int offset=0; offset<=_max_probe_length; ++offset) {
|
||||||
|
auto bucket = (hash_value + offset) & _mask;
|
||||||
|
if (_states[bucket] == State::FILLED) {
|
||||||
|
if (_eq(_keys[bucket], key)) {
|
||||||
|
return bucket;
|
||||||
|
}
|
||||||
|
} else if (_states[bucket] == State::INACTIVE) {
|
||||||
|
return (size_t)-1; // End of the chain!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (size_t)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the bucket with this key, or return a good empty bucket to place the key in.
|
||||||
|
// In the latter case, the bucket is expected to be filled.
|
||||||
|
size_t find_or_allocate(const KeyT& key)
|
||||||
|
{
|
||||||
|
auto hash_value = _hasher(key);
|
||||||
|
size_t hole = (size_t)-1;
|
||||||
|
int offset=0;
|
||||||
|
for (; offset<=_max_probe_length; ++offset) {
|
||||||
|
auto bucket = (hash_value + offset) & _mask;
|
||||||
|
|
||||||
|
if (_states[bucket] == State::FILLED) {
|
||||||
|
if (_eq(_keys[bucket], key)) {
|
||||||
|
return bucket;
|
||||||
|
}
|
||||||
|
} else if (_states[bucket] == State::INACTIVE) {
|
||||||
|
return bucket;
|
||||||
|
} else {
|
||||||
|
// ACTIVE: keep searching
|
||||||
|
if (hole == (size_t)-1) {
|
||||||
|
hole = bucket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No key found - but maybe a hole for it
|
||||||
|
assert(offset == _max_probe_length+1);
|
||||||
|
|
||||||
|
if (hole != (size_t)-1) {
|
||||||
|
return hole;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No hole found within _max_probe_length
|
||||||
|
for (; ; ++offset) {
|
||||||
|
auto bucket = (hash_value + offset) & _mask;
|
||||||
|
|
||||||
|
if (_states[bucket] != State::FILLED) {
|
||||||
|
_max_probe_length = offset;
|
||||||
|
return bucket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// key is not in this map. Find a place to put it.
|
||||||
|
size_t find_empty_bucket(const KeyT& key)
|
||||||
|
{
|
||||||
|
auto hash_value = _hasher(key);
|
||||||
|
for (int offset=0; ; ++offset) {
|
||||||
|
auto bucket = (hash_value + offset) & _mask;
|
||||||
|
if (_states[bucket] != State::FILLED) {
|
||||||
|
if (offset > _max_probe_length) {
|
||||||
|
_max_probe_length = offset;
|
||||||
|
}
|
||||||
|
return bucket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class State : uint8_t
|
||||||
|
{
|
||||||
|
INACTIVE, // Never been touched
|
||||||
|
ACTIVE, // Is inside a search-chain, but is empty
|
||||||
|
FILLED // Is set with key/value
|
||||||
|
};
|
||||||
|
|
||||||
|
HashT _hasher;
|
||||||
|
EqT _eq;
|
||||||
|
State* _states = nullptr;
|
||||||
|
KeyT* _keys = nullptr;
|
||||||
|
size_t _num_buckets = 0;
|
||||||
|
size_t _num_filled = 0;
|
||||||
|
int _max_probe_length = -1; // Our longest bucket-brigade is this long. ONLY when we have zero elements is this ever negative (-1).
|
||||||
|
size_t _mask = 0; // _num_buckets minus one
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace emilib
|
||||||
7985
3rdparty/stb/stb_image.h
vendored
Normal file
7985
3rdparty/stb/stb_image.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1724
3rdparty/stb/stb_image_write.h
vendored
Normal file
1724
3rdparty/stb/stb_image_write.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
85
CMakeLists.txt
Normal file
85
CMakeLists.txt
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.21)
|
||||||
|
cmake_policy(SET CMP0091 NEW)
|
||||||
|
|
||||||
|
project("spacegame"
|
||||||
|
VERSION 0.2.2
|
||||||
|
DESCRIPTION "Blocky spacegame by crydsch@lph.zone"
|
||||||
|
LANGUAGES CXX
|
||||||
|
)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -ggdb")
|
||||||
|
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O0 -ggdb")
|
||||||
|
|
||||||
|
# Enable compiler warnings
|
||||||
|
set(SPACEGAME_COMPILER_WARNINGS
|
||||||
|
-Wall -Wextra -pedantic
|
||||||
|
-Wcast-align -Wcast-qual -Wformat=2 -Winit-self -Wmissing-declarations
|
||||||
|
-Wmissing-include-dirs -Wredundant-decls -Wswitch-default
|
||||||
|
-Wundef -Wctor-dtor-privacy
|
||||||
|
)
|
||||||
|
|
||||||
|
# Enable compile_commands.json generation for other tools
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
|
||||||
|
|
||||||
|
if(NOT EXISTS /spacegame_deps)
|
||||||
|
message(FATAL_ERROR "'/spacegame_deps' not found!\nYou need to build the required dependecies first.\nSee 'scripts/build_deps.sh'")
|
||||||
|
else()
|
||||||
|
# Search for our pre-built dependencies first
|
||||||
|
set(CMAKE_PREFIX_PATH /spacegame_deps)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
|
# Note: shaderc must be run on the host system
|
||||||
|
option(SPACEGAME_BUILD_SHADERS "Use shaderc to build spirv shaders" OFF)
|
||||||
|
|
||||||
|
|
||||||
|
################ dependencies ################
|
||||||
|
|
||||||
|
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||||
|
find_package(Threads REQUIRED)
|
||||||
|
|
||||||
|
find_package(glfw3 REQUIRED)
|
||||||
|
|
||||||
|
# This finds import libraries: bgfx::bgfx, bgfx::bx and bgfx::bimg
|
||||||
|
# Linking with these will bring: include dirs, compiler options, compiler defs, link libraries
|
||||||
|
find_package(bgfx REQUIRED)
|
||||||
|
# bgfx::bgfx wrongly links with: OpenGL,GLX and others
|
||||||
|
# So we link to libbgfx.a only
|
||||||
|
find_library(BGFX NAMES libbgfx.a REQUIRED)
|
||||||
|
|
||||||
|
if(SPACEGAME_BUILD_SHADERS)
|
||||||
|
find_program(SHADERC shaderc REQUIRED)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
################## sources ###################
|
||||||
|
|
||||||
|
if(SPACEGAME_BUILD_SHADERS)
|
||||||
|
add_subdirectory(shaders)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Game executable is here:
|
||||||
|
add_subdirectory(src)
|
||||||
|
|
||||||
|
################# debugging ##################
|
||||||
|
|
||||||
|
# A useful trick is to append "-LAH" to the cmake command
|
||||||
|
# of libraries to see their available options.
|
||||||
|
#
|
||||||
|
# Uncomment this section to have cmake print all variables.
|
||||||
|
# This is useful to find the actual variable names of the libraries in use.
|
||||||
|
# Such as, is it ${SMTH_INCLUDE_DIR} or ${SMTH_INCLUDE_DIRS}?
|
||||||
|
# get_cmake_property(_variableNames VARIABLES)
|
||||||
|
# list (SORT _variableNames)
|
||||||
|
# foreach (_variableName ${_variableNames})
|
||||||
|
# message(STATUS "${_variableName}=${${_variableName}}")
|
||||||
|
# endforeach()
|
||||||
|
#
|
||||||
|
# List imported targets from 'find_package(..)'
|
||||||
|
# get_property(importTargets DIRECTORY "${CMAKE_SOURCE_DIR}" PROPERTY IMPORTED_TARGETS)
|
||||||
|
# message("Import Targets: ${importTargets}")
|
||||||
|
#
|
||||||
|
# Build this target to test generator expressions
|
||||||
|
# add_custom_target(debug_gen_expr COMMAND ${CMAKE_COMMAND} -E echo "$<IF:$<BOOL:${SPACEGAME_BUILD_SHADERS}>,true_string,false_string>")
|
||||||
14
README.md
Normal file
14
README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Spacegame
|
||||||
|
|
||||||
|
My little toy voxel engine born from an unconventional use of the rendering pipeline.
|
||||||
|
An idea that just kept working until I had a small prototype.
|
||||||
|
Might become a real game one day when it grows up.
|
||||||
|
|
||||||
|
## Experiements here include
|
||||||
|
|
||||||
|
- CMake + ninja as a build system
|
||||||
|
- The entire llvm compiler suite + cross compiling linux->windows
|
||||||
|
- A little Orthodox C++
|
||||||
|
- Developing my own camera and input system
|
||||||
|
- Vulkan via bgfx: compute shader to generate geometry on the GPU + rendering the result.
|
||||||
|
- Automated release builds with docker + CI
|
||||||
BIN
assets/textures/colors.png
Normal file
BIN
assets/textures/colors.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 300 B |
18
cmake/README.md
Normal file
18
cmake/README.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
# Toolchain
|
||||||
|
To use the same toolchain as the CI in vscode
|
||||||
|
place the file 'cmake-kits.json' into your '.vscode' directory.
|
||||||
|
Then select the 'Spacegame LLVM Kit' in the cmake-tools extension.
|
||||||
|
Restarting vscode may be necessary.
|
||||||
|
|
||||||
|
# Debugging
|
||||||
|
To enable debugging add this to your .vscode/settings.json
|
||||||
|
```
|
||||||
|
"cmake.debugConfig": {
|
||||||
|
"MIMode": "gdb",
|
||||||
|
"miDebuggerPath": "/usr/bin/gdb"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Usefull links
|
||||||
|
- [LLVM Debian/Ubuntu packages](https://apt.llvm.org/)
|
||||||
34
cmake/clang_mingw_toolchain.cmake
Normal file
34
cmake/clang_mingw_toolchain.cmake
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# This toolchain configuration file can be used cross-compile from linux to windows
|
||||||
|
# Ref.: https://github.com/glfw/glfw/blob/master/CMake/x86_64-w64-mingw32-clang.cmake
|
||||||
|
|
||||||
|
MESSAGE("Cross-Compiling with toolchain file: ${CMAKE_TOOLCHAIN_FILE}")
|
||||||
|
|
||||||
|
# llvm-mingw-x86_64 is expected to be avilable under this directory
|
||||||
|
set(MINGW_DIRECTORY /llvm-mingw-x86_64)
|
||||||
|
|
||||||
|
# Define the environment for cross-compiling with 64-bit MinGW-w64 Clang
|
||||||
|
set(CMAKE_SYSTEM_NAME Windows)
|
||||||
|
SET(CMAKE_SYSTEM_VERSION 1)
|
||||||
|
set(CMAKE_C_COMPILER ${MINGW_DIRECTORY}/bin/x86_64-w64-mingw32-clang)
|
||||||
|
set(CMAKE_CXX_COMPILER ${MINGW_DIRECTORY}/bin/x86_64-w64-mingw32-clang++)
|
||||||
|
set(CMAKE_RC_COMPILER ${MINGW_DIRECTORY}/bin/x86_64-w64-mingw32-windres)
|
||||||
|
set(CMAKE_RANLIB ${MINGW_DIRECTORY}/bin/x86_64-w64-mingw32-ranlib)
|
||||||
|
set(CMAKE_AR ${MINGW_DIRECTORY}/bin/x86_64-w64-mingw32-ar)
|
||||||
|
set(CMAKE_STRIP ${MINGW_DIRECTORY}/bin/x86_64-w64-mingw32-strip)
|
||||||
|
|
||||||
|
# Configure the behaviour of the find commands
|
||||||
|
set(CMAKE_FIND_ROOT_PATH
|
||||||
|
${MINGW_DIRECTORY}/x86_64-w64-mingw32
|
||||||
|
${MINGW_DIRECTORY}
|
||||||
|
/spacegame_deps)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
|
|
||||||
|
# Note: For windows we supply all libraries in use directly through mingw => Full static linking!
|
||||||
|
set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++")
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld -static")
|
||||||
|
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld -static")
|
||||||
|
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld -static")
|
||||||
|
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
|
||||||
|
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL TRUE)
|
||||||
23
cmake/clang_toolchain.cmake
Normal file
23
cmake/clang_toolchain.cmake
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# This toolchain configuration file can be used for 'normal' linux native compilation
|
||||||
|
|
||||||
|
MESSAGE("Compiling with toolchain file: ${CMAKE_TOOLCHAIN_FILE}")
|
||||||
|
|
||||||
|
# Define the environment for compiling with 64-bit clang
|
||||||
|
# Note: Ensure the proper links / aliases are set
|
||||||
|
# ex. /usr/bin/clang -> /usr/bin/clang-18
|
||||||
|
set(CMAKE_C_COMPILER /usr/bin/clang )
|
||||||
|
set(CMAKE_CXX_COMPILER /usr/bin/clang++ )
|
||||||
|
set(CMAKE_RC_COMPILER /usr/bin/llvm-windres)
|
||||||
|
set(CMAKE_RANLIB /usr/bin/llvm-ranlib )
|
||||||
|
set(CMAKE_AR /usr/bin/llvm-ar )
|
||||||
|
set(CMAKE_STRIP /usr/bin/llvm-strip )
|
||||||
|
|
||||||
|
# Note: We can static link with libc++, but not entirely since we depend on dynamic
|
||||||
|
# libraries such as x11,glx,etc...
|
||||||
|
# Note: Despite its name '-static-libstdc++' the option causes libc++ to be linked statically
|
||||||
|
set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++" )
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld -static-libstdc++")
|
||||||
|
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld -static-libstdc++")
|
||||||
|
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld -static-libstdc++")
|
||||||
|
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
|
||||||
|
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL TRUE)
|
||||||
10
cmake/cmake-kits.json
Normal file
10
cmake/cmake-kits.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Spacegame LLVM Kit",
|
||||||
|
"toolchainFile": "cmake/clang_toolchain.cmake",
|
||||||
|
"cmakeSettings":
|
||||||
|
{
|
||||||
|
"SPACEGAME_BUILD_SHADERS": "ON"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
37
docker/Dockerfile_Base
Normal file
37
docker/Dockerfile_Base
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Ensure `nvidia-smi` & `vulkaninfo` run correctly on the host system.
|
||||||
|
# Run with `sudo docker run -it --rm --gpus all <tag>`.
|
||||||
|
# Check `nvidia-smi` & `vulkaninfo` run correctly inside the container.
|
||||||
|
# may need to install:
|
||||||
|
# libnvidia-gl-525-server \
|
||||||
|
# vulkan-tools
|
||||||
|
# for debugging maybe: gdb
|
||||||
|
|
||||||
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
|
# Non interactive mode
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
COPY docker/rebuild_from_base.stamp docker/rebuild_from_base.stamp
|
||||||
|
|
||||||
|
# Dependencies & Tools
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
cmake git micro jq \
|
||||||
|
pkg-config curl wget zip \
|
||||||
|
ca-certificates xz-utils \
|
||||||
|
software-properties-common \
|
||||||
|
cppcheck valgrind \
|
||||||
|
`# glfw dependecies for x11 ` \
|
||||||
|
xorg-dev \
|
||||||
|
`# glfw dependecies for wayland ` \
|
||||||
|
libwayland-dev libxkbcommon-dev wayland-protocols extra-cmake-modules \
|
||||||
|
`# bgfx dependecies ` \
|
||||||
|
libgl1-mesa-dev x11proto-core-dev libx11-dev && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
|
`# custom ninja ` \
|
||||||
|
wget -O /usr/bin/ninja https://git.lph.zone/crydsch/ninja/releases/download/latest.proc_loadavg/ninja && \
|
||||||
|
chmod +x /usr/bin/ninja && \
|
||||||
|
ninja --version && \
|
||||||
|
`# disable git detachedHead warning ` \
|
||||||
|
git config --global advice.detachedHead false
|
||||||
32
docker/Dockerfile_Linux
Normal file
32
docker/Dockerfile_Linux
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
FROM spacegame_base
|
||||||
|
|
||||||
|
COPY docker/rebuild_from_llvm.stamp docker/rebuild_from_llvm.stamp
|
||||||
|
|
||||||
|
# LLVM
|
||||||
|
# RUN `# llvm-16 ` \
|
||||||
|
# wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc && \
|
||||||
|
# add-apt-repository deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-16 main && \
|
||||||
|
# apt-get update && \
|
||||||
|
# apt-get install -y --no-install-recommends \
|
||||||
|
# clang-16 clang-tools-16 \
|
||||||
|
# lldb-16 lld-16 \
|
||||||
|
# clang-tidy-16 clang-format-16 \
|
||||||
|
# libc++-16-dev libc++abi-16-dev && \
|
||||||
|
# apt-get clean && \
|
||||||
|
# rm -rf /var/lib/apt/lists/*
|
||||||
|
COPY scripts/setup_llvm_links.sh scripts/setup_llvm_links.sh
|
||||||
|
RUN `# llvm stable via convenience script` \
|
||||||
|
wget https://apt.llvm.org/llvm.sh && \
|
||||||
|
chmod +x llvm.sh && \
|
||||||
|
./llvm.sh all && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
|
`# setup required links` \
|
||||||
|
scripts/setup_llvm_links.sh
|
||||||
|
|
||||||
|
# Build 3rdparty deps
|
||||||
|
COPY 3rdparty 3rdparty
|
||||||
|
COPY cmake/clang_toolchain.cmake cmake/clang_toolchain.cmake
|
||||||
|
COPY scripts/build_deps.sh scripts/build_deps.sh
|
||||||
|
RUN scripts/build_deps.sh "cmake/clang_toolchain.cmake" && \
|
||||||
|
rm -rf 3rdparty/glfw 3rdparty/bx 3rdparty/bimg 3rdparty/bgfx 3rdparty/bgfx.cmake
|
||||||
24
docker/Dockerfile_Windows
Normal file
24
docker/Dockerfile_Windows
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
FROM spacegame_base
|
||||||
|
|
||||||
|
COPY docker/rebuild_from_llvm.stamp docker/rebuild_from_llvm.stamp
|
||||||
|
|
||||||
|
# LLVM
|
||||||
|
RUN `# llvm mingw ` \
|
||||||
|
curl -L \
|
||||||
|
-H "Accept: application/vnd.github+json" \
|
||||||
|
-H "Authorization: Bearer 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" \
|
||||||
|
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||||
|
https://api.github.com/repos/mstorsjo/llvm-mingw/releases/latest | jq '.assets[] | select( .name | test("ucrt-ubuntu-20.04-x86_64") ).browser_download_url' | xargs wget -O llvm-mingw-x86_64.tar.xz && \
|
||||||
|
mkdir llvm-mingw-x86_64 && \
|
||||||
|
tar xvf llvm-mingw-x86_64.tar.xz --directory llvm-mingw-x86_64 --strip-components=1 && \
|
||||||
|
rm -rf llvm-mingw-x86_64.tar.xz \
|
||||||
|
llvm-mingw-x86_64/aarch64-w64-mingw32 \
|
||||||
|
llvm-mingw-x86_64/armv7-w64-mingw32 \
|
||||||
|
llvm-mingw-x86_64/i686-w64-mingw32
|
||||||
|
|
||||||
|
# Build 3rdparty deps
|
||||||
|
COPY 3rdparty 3rdparty
|
||||||
|
COPY cmake/clang_mingw_toolchain.cmake cmake/clang_mingw_toolchain.cmake
|
||||||
|
COPY scripts/build_deps.sh scripts/build_deps.sh
|
||||||
|
RUN scripts/build_deps.sh "cmake/clang_mingw_toolchain.cmake" && \
|
||||||
|
rm -rf 3rdparty/glfw 3rdparty/bx 3rdparty/bimg 3rdparty/bgfx 3rdparty/bgfx.cmake
|
||||||
0
docker/rebuild_from_base.stamp
Normal file
0
docker/rebuild_from_base.stamp
Normal file
1
docker/rebuild_from_llvm.stamp
Normal file
1
docker/rebuild_from_llvm.stamp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
16
scripts/clean_deps_n_build.sh
Executable file
16
scripts/clean_deps_n_build.sh
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$EUID" -ne 0 ]
|
||||||
|
then echo "This script must be run as root"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||||
|
|
||||||
|
pushd "$SCRIPT_DIR"/..
|
||||||
|
|
||||||
|
rm -rf /spacegame_deps build* assets/shaders/*.spv 3rdparty/glfw 3rdparty/bx 3rdparty/bimg 3rdparty/bgfx 3rdparty/bgfx.cmake
|
||||||
|
|
||||||
|
popd
|
||||||
53
scripts/release_game.sh
Executable file
53
scripts/release_game.sh
Executable file
@@ -0,0 +1,53 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Construct base version: '1.2.3-123+20230527T195630.811d543'
|
||||||
|
# Platform specific versions may add extra info: '1.2.3-123+20230527T195630.811d543.linux.x11'
|
||||||
|
|
||||||
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||||
|
|
||||||
|
pushd "$SCRIPT_DIR"/.. 2>&1 1>/dev/null
|
||||||
|
|
||||||
|
# Fetch all tags from remote (if something changed)
|
||||||
|
git fetch --tags
|
||||||
|
|
||||||
|
# Get current version from latest tag
|
||||||
|
VERSION_SHORT=$(git describe --tags --abbrev=0)
|
||||||
|
|
||||||
|
echo "Current Version: $VERSION_SHORT"
|
||||||
|
|
||||||
|
# Advance by one revision
|
||||||
|
# check if revision present
|
||||||
|
if [[ $VERSION_SHORT != *"-"* ]]; then
|
||||||
|
VERSION_SHORT="$VERSION_SHORT-0"
|
||||||
|
fi
|
||||||
|
# seperate base version from revision
|
||||||
|
BASE_VERSION=$(echo $VERSION_SHORT | sed -E 's~^(.*[.].*[.].*)-([0-9]+)~\1~I')
|
||||||
|
REVISION=$(echo $VERSION_SHORT | sed -E 's~^(.*[.].*[.].*)-([0-9]+)~\2~I')
|
||||||
|
# +1
|
||||||
|
REVISION=$(($REVISION+1)) || $(echo 1)
|
||||||
|
# Assemble new version
|
||||||
|
VERSION_SHORT="$BASE_VERSION-$REVISION"
|
||||||
|
|
||||||
|
read -p "Enter New Version [$VERSION_SHORT]: " VERSION_SHORT_INPUT
|
||||||
|
VERSION_SHORT=${VERSION_SHORT_INPUT:-$VERSION_SHORT}
|
||||||
|
|
||||||
|
# Collect extra build information
|
||||||
|
# CMAKE_VERSION=$(grep CMAKE_PROJECT_VERSION:STATIC build/CMakeCache.txt | cut -d "=" -f2)
|
||||||
|
TIME=$(date -u +"%Y%m%dT%H%M%S")
|
||||||
|
COMMIT=$(git rev-parse HEAD)
|
||||||
|
|
||||||
|
VERSION_LONG="$VERSION_SHORT+$TIME.$COMMIT"
|
||||||
|
|
||||||
|
# Push any outstanding commits
|
||||||
|
git commit --allow-empty -m "Release $VERSION_SHORT" -m "[CI SKIP]"
|
||||||
|
git push
|
||||||
|
|
||||||
|
# Create annotated tag named named after short version and annotated with long version
|
||||||
|
git tag -a -m "$VERSION_LONG" "$VERSION_SHORT"
|
||||||
|
|
||||||
|
# push the new tag
|
||||||
|
git push origin "$VERSION_SHORT"
|
||||||
|
|
||||||
|
popd 2>&1 1>/dev/null
|
||||||
58
scripts/setup_llvm_links.sh
Executable file
58
scripts/setup_llvm_links.sh
Executable file
@@ -0,0 +1,58 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This cript creates default links to a set of llvm binaries
|
||||||
|
# ex. /usr/bin/clang -> /usr/bin/clang-18
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$EUID" -ne 0 ]
|
||||||
|
then echo "This script must be run as root"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Detect clang version
|
||||||
|
# Command Breakdown:
|
||||||
|
# Get all installed packages
|
||||||
|
# | Detect clang version
|
||||||
|
# | | Sort available versions
|
||||||
|
# | | | Pick highest version
|
||||||
|
# | | | | Remove "clang-" to extract the version only
|
||||||
|
# v v v v v
|
||||||
|
VER=$(dpkg --list | grep -o "clang-[0-9][0-9]*" | sort -r | head -n 1 | sed "s#clang-##")
|
||||||
|
#echo Detected Tool-Version: $VER
|
||||||
|
|
||||||
|
# Ensure all tools are installed
|
||||||
|
TOOLS=(
|
||||||
|
clang
|
||||||
|
clang++
|
||||||
|
llvm-windres
|
||||||
|
llvm-ranlib
|
||||||
|
llvm-ar
|
||||||
|
llvm-strip
|
||||||
|
)
|
||||||
|
|
||||||
|
for TOOL in "${TOOLS[@]}"
|
||||||
|
do
|
||||||
|
|
||||||
|
set +e
|
||||||
|
OUTPUT=$($TOOL-$VER --version 2>&1)
|
||||||
|
EXIT_CODE=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# on error (ex. no ground truth file) just print nothing
|
||||||
|
if [ $EXIT_CODE -ne 0 ]
|
||||||
|
then
|
||||||
|
echo $TOOL-$VER must be installed!
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
#echo Found $TOOL-$VER
|
||||||
|
|
||||||
|
# Get full path
|
||||||
|
TOOL_PATH=$(which $TOOL-$VER)
|
||||||
|
|
||||||
|
# Create links
|
||||||
|
ln -sf $TOOL_PATH /usr/bin/$TOOL
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
106
shaders/CMakeLists.txt
Normal file
106
shaders/CMakeLists.txt
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
|
||||||
|
# Ref.: https://stackoverflow.com/questions/67040258/cmake-compile-glsl-shaders
|
||||||
|
# Ref.: https://cmake.org/cmake/help/latest/command/add_custom_command.html
|
||||||
|
|
||||||
|
set(SHADER_SOURCE_DIR ${PROJECT_SOURCE_DIR}/shaders)
|
||||||
|
set(SHADER_BIN_DIR ${PROJECT_SOURCE_DIR}/assets/shaders)
|
||||||
|
file(GLOB VERTEX_SHADERS ${SHADER_SOURCE_DIR}/vs_*.sc)
|
||||||
|
file(GLOB FRAGMENT_SHADERS ${SHADER_SOURCE_DIR}/fs_*.sc)
|
||||||
|
file(GLOB COMPUTE_SHADERS ${SHADER_SOURCE_DIR}/cs_*.sc)
|
||||||
|
|
||||||
|
# vertex shaders
|
||||||
|
foreach(SHADER_IN IN LISTS VERTEX_SHADERS)
|
||||||
|
cmake_path(GET SHADER_IN FILENAME SHADER_NAME) # ex. SHADER_NAME==vs_cubes.sc
|
||||||
|
|
||||||
|
# output file
|
||||||
|
string(REPLACE ".sc" ".spv" SHADER_OUT ${SHADER_NAME})
|
||||||
|
set(SHADER_OUT ${SHADER_BIN_DIR}/${SHADER_OUT})
|
||||||
|
|
||||||
|
# varying file
|
||||||
|
string(REPLACE "vs" "varying" SHADER_VARYING ${SHADER_IN})
|
||||||
|
|
||||||
|
# message("SHADER_IN: " ${SHADER_IN} " - SHADER_VARYING: " ${SHADER_VARYING} " - SHADER_OUT: " ${SHADER_OUT})
|
||||||
|
|
||||||
|
_bgfx_shaderc_parse(CLI
|
||||||
|
FILE ${SHADER_IN}
|
||||||
|
OUTPUT ${SHADER_OUT}
|
||||||
|
VERTEX
|
||||||
|
LINUX
|
||||||
|
PROFILE spirv
|
||||||
|
VARYINGDEF ${SHADER_VARYING}
|
||||||
|
INCLUDES ${SHADER_BIN_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${SHADER_OUT}
|
||||||
|
COMMAND ${SHADERC} ${CLI}
|
||||||
|
DEPENDS ${SHADER_IN}
|
||||||
|
COMMENT "Compiling shader ${SHADER_NAME}"
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND SPV_SHADERS ${SHADER_OUT})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# fragment shaders
|
||||||
|
foreach(SHADER_IN IN LISTS FRAGMENT_SHADERS)
|
||||||
|
cmake_path(GET SHADER_IN FILENAME SHADER_NAME) # ex. SHADER_NAME==vs_cubes.sc
|
||||||
|
|
||||||
|
# output file
|
||||||
|
string(REPLACE ".sc" ".spv" SHADER_OUT ${SHADER_NAME})
|
||||||
|
set(SHADER_OUT ${SHADER_BIN_DIR}/${SHADER_OUT})
|
||||||
|
|
||||||
|
# varying file
|
||||||
|
string(REPLACE "fs" "varying" SHADER_VARYING ${SHADER_IN})
|
||||||
|
|
||||||
|
# message("SHADER_IN: " ${SHADER_IN} " - SHADER_VARYING: " ${SHADER_VARYING} " - SHADER_OUT: " ${SHADER_OUT})
|
||||||
|
|
||||||
|
_bgfx_shaderc_parse(CLI
|
||||||
|
FILE ${SHADER_IN}
|
||||||
|
OUTPUT ${SHADER_OUT}
|
||||||
|
FRAGMENT
|
||||||
|
LINUX
|
||||||
|
PROFILE spirv
|
||||||
|
VARYINGDEF ${SHADER_VARYING}
|
||||||
|
INCLUDES ${SHADER_BIN_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${SHADER_OUT}
|
||||||
|
COMMAND ${SHADERC} ${CLI}
|
||||||
|
DEPENDS ${SHADER_IN}
|
||||||
|
COMMENT "Compiling shader ${SHADER_NAME}"
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND SPV_SHADERS ${SHADER_OUT})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# compute shaders
|
||||||
|
foreach(SHADER_IN IN LISTS COMPUTE_SHADERS)
|
||||||
|
cmake_path(GET SHADER_IN FILENAME SHADER_NAME) # ex. SHADER_NAME==vs_cubes.sc
|
||||||
|
|
||||||
|
# output file
|
||||||
|
string(REPLACE ".sc" ".spv" SHADER_OUT ${SHADER_NAME})
|
||||||
|
set(SHADER_OUT ${SHADER_BIN_DIR}/${SHADER_OUT})
|
||||||
|
|
||||||
|
# message("SHADER_IN: " ${SHADER_IN} " - "SHADER_OUT: " ${SHADER_OUT})
|
||||||
|
|
||||||
|
_bgfx_shaderc_parse(CLI
|
||||||
|
FILE ${SHADER_IN}
|
||||||
|
OUTPUT ${SHADER_OUT}
|
||||||
|
COMPUTE
|
||||||
|
LINUX
|
||||||
|
PROFILE spirv
|
||||||
|
INCLUDES ${SHADER_BIN_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${SHADER_OUT}
|
||||||
|
COMMAND ${SHADERC} ${CLI}
|
||||||
|
DEPENDS ${SHADER_IN}
|
||||||
|
COMMENT "Compiling shader ${SHADER_NAME}"
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND SPV_SHADERS ${SHADER_OUT})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
add_custom_target(shaders ALL DEPENDS ${SPV_SHADERS})
|
||||||
327
shaders/bgfx_compute.sh
Normal file
327
shaders/bgfx_compute.sh
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011-2022 Branimir Karadzic. All rights reserved.
|
||||||
|
* License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef BGFX_COMPUTE_H_HEADER_GUARD
|
||||||
|
#define BGFX_COMPUTE_H_HEADER_GUARD
|
||||||
|
|
||||||
|
#include "bgfx_shader.sh"
|
||||||
|
|
||||||
|
#ifndef __cplusplus
|
||||||
|
|
||||||
|
#if BGFX_SHADER_LANGUAGE_HLSL > 0 && BGFX_SHADER_LANGUAGE_HLSL < 400
|
||||||
|
# error "Compute is not supported!"
|
||||||
|
#endif // BGFX_SHADER_LANGUAGE_HLSL
|
||||||
|
|
||||||
|
#if BGFX_SHADER_LANGUAGE_METAL || BGFX_SHADER_LANGUAGE_SPIRV
|
||||||
|
# define FORMAT(_format) [[spv::format_ ## _format]]
|
||||||
|
# define WRITEONLY [[spv::nonreadable]]
|
||||||
|
#else
|
||||||
|
# define FORMAT(_format)
|
||||||
|
# define WRITEONLY
|
||||||
|
#endif // BGFX_SHADER_LANGUAGE_METAL || BGFX_SHADER_LANGUAGE_SPIRV
|
||||||
|
|
||||||
|
#if BGFX_SHADER_LANGUAGE_GLSL
|
||||||
|
|
||||||
|
#define SHARED shared
|
||||||
|
|
||||||
|
#define __IMAGE_XX(_name, _format, _reg, _image, _access) \
|
||||||
|
layout(_format, binding=_reg) _access uniform highp _image _name
|
||||||
|
|
||||||
|
#define readwrite
|
||||||
|
#define IMAGE2D_RO( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image2D, readonly)
|
||||||
|
#define UIMAGE2D_RO(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage2D, readonly)
|
||||||
|
#define IMAGE2D_WR( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image2D, writeonly)
|
||||||
|
#define UIMAGE2D_WR(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage2D, writeonly)
|
||||||
|
#define IMAGE2D_RW( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image2D, readwrite)
|
||||||
|
#define UIMAGE2D_RW(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage2D, readwrite)
|
||||||
|
|
||||||
|
#define IMAGE2D_ARRAY_RO( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image2DArray, readonly)
|
||||||
|
#define UIMAGE2D_ARRAY_RO(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage2DArray, readonly)
|
||||||
|
#define IMAGE2D_ARRAY_WR( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image2DArray, writeonly)
|
||||||
|
#define UIMAGE2D_ARRAY_WR(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage2DArray, writeonly)
|
||||||
|
#define IMAGE2D_ARRAY_RW( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image2DArray, readwrite)
|
||||||
|
#define UIMAGE2D_ARRAY_RW(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage2DArray, readwrite)
|
||||||
|
|
||||||
|
#define IMAGE3D_RO( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image3D, readonly)
|
||||||
|
#define UIMAGE3D_RO(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage3D, readonly)
|
||||||
|
#define IMAGE3D_WR( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image3D, writeonly)
|
||||||
|
#define UIMAGE3D_WR(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage3D, writeonly)
|
||||||
|
#define IMAGE3D_RW( _name, _format, _reg) __IMAGE_XX(_name, _format, _reg, image3D, readwrite)
|
||||||
|
#define UIMAGE3D_RW(_name, _format, _reg) __IMAGE_XX(_name, _format, _reg, uimage3D, readwrite)
|
||||||
|
|
||||||
|
#define __BUFFER_XX(_name, _type, _reg, _access) \
|
||||||
|
layout(std430, binding=_reg) _access buffer _name ## Buffer \
|
||||||
|
{ \
|
||||||
|
_type _name[]; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define BUFFER_RO(_name, _type, _reg) __BUFFER_XX(_name, _type, _reg, readonly)
|
||||||
|
#define BUFFER_RW(_name, _type, _reg) __BUFFER_XX(_name, _type, _reg, readwrite)
|
||||||
|
#define BUFFER_WR(_name, _type, _reg) __BUFFER_XX(_name, _type, _reg, writeonly)
|
||||||
|
|
||||||
|
#define NUM_THREADS(_x, _y, _z) layout (local_size_x = _x, local_size_y = _y, local_size_z = _z) in;
|
||||||
|
|
||||||
|
#define atomicFetchAndAdd(_mem, _data, _original) _original = atomicAdd(_mem, _data)
|
||||||
|
#define atomicFetchAndAnd(_mem, _data, _original) _original = atomicAnd(_mem, _data)
|
||||||
|
#define atomicFetchAndMax(_mem, _data, _original) _original = atomicMax(_mem, _data)
|
||||||
|
#define atomicFetchAndMin(_mem, _data, _original) _original = atomicMin(_mem, _data)
|
||||||
|
#define atomicFetchAndOr(_mem, _data, _original) _original = atomicOr(_mem, _data)
|
||||||
|
#define atomicFetchAndXor(_mem, _data, _original) _original = atomicXor(_mem, _data)
|
||||||
|
#define atomicFetchAndExchange(_mem, _data, _original) _original = atomicExchange(_mem, _data)
|
||||||
|
#define atomicFetchCompareExchange(_mem, _compare, _data, _original) _original = atomicCompSwap(_mem,_compare, _data)
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define SHARED groupshared
|
||||||
|
|
||||||
|
#define COMP_r32ui uint
|
||||||
|
#define COMP_rg32ui uint2
|
||||||
|
#define COMP_rgba32ui uint4
|
||||||
|
#define COMP_r32f float
|
||||||
|
#define COMP_r16f float
|
||||||
|
#define COMP_rg16f float2
|
||||||
|
#define COMP_rgba16f float4
|
||||||
|
#if BGFX_SHADER_LANGUAGE_HLSL
|
||||||
|
# define COMP_rgba8 unorm float4
|
||||||
|
# define COMP_rg8 unorm float2
|
||||||
|
# define COMP_r8 unorm float
|
||||||
|
#else
|
||||||
|
# define COMP_rgba8 float4
|
||||||
|
# define COMP_rg8 float2
|
||||||
|
# define COMP_r8 float
|
||||||
|
#endif // BGFX_SHADER_LANGUAGE_HLSL
|
||||||
|
#define COMP_rgba32f float4
|
||||||
|
|
||||||
|
#define IMAGE2D_RO( _name, _format, _reg) \
|
||||||
|
FORMAT(_format) Texture2D<COMP_ ## _format> _name : REGISTER(t, _reg); \
|
||||||
|
|
||||||
|
#define UIMAGE2D_RO(_name, _format, _reg) IMAGE2D_RO(_name, _format, _reg)
|
||||||
|
|
||||||
|
#define IMAGE2D_WR( _name, _format, _reg) \
|
||||||
|
WRITEONLY FORMAT(_format) RWTexture2D<COMP_ ## _format> _name : REGISTER(u, _reg); \
|
||||||
|
|
||||||
|
#define UIMAGE2D_WR(_name, _format, _reg) IMAGE2D_WR(_name, _format, _reg)
|
||||||
|
|
||||||
|
#define IMAGE2D_RW( _name, _format, _reg) \
|
||||||
|
FORMAT(_format) RWTexture2D<COMP_ ## _format> _name : REGISTER(u, _reg); \
|
||||||
|
|
||||||
|
#define UIMAGE2D_RW(_name, _format, _reg) IMAGE2D_RW(_name, _format, _reg)
|
||||||
|
|
||||||
|
#define IMAGE2D_ARRAY_RO(_name, _format, _reg) \
|
||||||
|
FORMAT(_format) Texture2DArray<COMP_ ## _format> _name : REGISTER(t, _reg); \
|
||||||
|
|
||||||
|
#define UIMAGE2D_ARRAY_RO(_name, _format, _reg) IMAGE2D_ARRAY_RO(_name, _format, _reg)
|
||||||
|
|
||||||
|
#define IMAGE2D_ARRAY_WR( _name, _format, _reg) \
|
||||||
|
WRITEONLY FORMAT(_format) RWTexture2DArray<COMP_ ## _format> _name : REGISTER(u, _reg); \
|
||||||
|
|
||||||
|
#define UIMAGE2D_ARRAY_WR(_name, _format, _reg) IMAGE2D_ARRAY_WR(_name, _format, _reg)
|
||||||
|
|
||||||
|
#define IMAGE2D_ARRAY_RW(_name, _format, _reg) \
|
||||||
|
FORMAT(_format) RWTexture2DArray<COMP_ ## _format> _name : REGISTER(u, _reg); \
|
||||||
|
|
||||||
|
#define UIMAGE2D_ARRAY_RW(_name, _format, _reg) IMAGE2D_ARRAY_RW(_name, _format, _reg)
|
||||||
|
|
||||||
|
#define IMAGE3D_RO( _name, _format, _reg) \
|
||||||
|
FORMAT(_format) Texture3D<COMP_ ## _format> _name : REGISTER(t, _reg);
|
||||||
|
|
||||||
|
#define UIMAGE3D_RO(_name, _format, _reg) IMAGE3D_RO(_name, _format, _reg)
|
||||||
|
|
||||||
|
#define IMAGE3D_WR( _name, _format, _reg) \
|
||||||
|
WRITEONLY FORMAT(_format) RWTexture3D<COMP_ ## _format> _name : REGISTER(u, _reg);
|
||||||
|
|
||||||
|
#define UIMAGE3D_WR(_name, _format, _reg) IMAGE3D_RW(_name, _format, _reg)
|
||||||
|
|
||||||
|
#define IMAGE3D_RW( _name, _format, _reg) \
|
||||||
|
FORMAT(_format) RWTexture3D<COMP_ ## _format> _name : REGISTER(u, _reg); \
|
||||||
|
|
||||||
|
#define UIMAGE3D_RW(_name, _format, _reg) IMAGE3D_RW(_name, _format, _reg)
|
||||||
|
|
||||||
|
#if BGFX_SHADER_LANGUAGE_METAL || BGFX_SHADER_LANGUAGE_SPIRV
|
||||||
|
#define BUFFER_RO(_name, _struct, _reg) StructuredBuffer<_struct> _name : REGISTER(t, _reg)
|
||||||
|
#define BUFFER_RW(_name, _struct, _reg) RWStructuredBuffer <_struct> _name : REGISTER(u, _reg)
|
||||||
|
#define BUFFER_WR(_name, _struct, _reg) BUFFER_RW(_name, _struct, _reg)
|
||||||
|
#else
|
||||||
|
#define BUFFER_RO(_name, _struct, _reg) Buffer<_struct> _name : REGISTER(t, _reg)
|
||||||
|
#define BUFFER_RW(_name, _struct, _reg) RWBuffer<_struct> _name : REGISTER(u, _reg)
|
||||||
|
#define BUFFER_WR(_name, _struct, _reg) BUFFER_RW(_name, _struct, _reg)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define NUM_THREADS(_x, _y, _z) [numthreads(_x, _y, _z)]
|
||||||
|
|
||||||
|
#define __IMAGE_IMPL_A(_format, _storeComponents, _type, _loadComponents) \
|
||||||
|
_type imageLoad(Texture2D<_format> _image, ivec2 _uv) \
|
||||||
|
{ \
|
||||||
|
return _image[_uv]._loadComponents; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
ivec2 imageSize(Texture2D<_format> _image) \
|
||||||
|
{ \
|
||||||
|
uvec2 result; \
|
||||||
|
_image.GetDimensions(result.x, result.y); \
|
||||||
|
return ivec2(result); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
_type imageLoad(RWTexture2D<_format> _image, ivec2 _uv) \
|
||||||
|
{ \
|
||||||
|
return _image[_uv]._loadComponents; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
void imageStore(RWTexture2D<_format> _image, ivec2 _uv, _type _value) \
|
||||||
|
{ \
|
||||||
|
_image[_uv] = _value._storeComponents; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
ivec2 imageSize(RWTexture2D<_format> _image) \
|
||||||
|
{ \
|
||||||
|
uvec2 result; \
|
||||||
|
_image.GetDimensions(result.x, result.y); \
|
||||||
|
return ivec2(result); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
_type imageLoad(Texture2DArray<_format> _image, ivec3 _uvw) \
|
||||||
|
{ \
|
||||||
|
return _image[_uvw]._loadComponents; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
ivec3 imageSize(Texture2DArray<_format> _image) \
|
||||||
|
{ \
|
||||||
|
uvec3 result; \
|
||||||
|
_image.GetDimensions(result.x, result.y, result.z); \
|
||||||
|
return ivec3(result); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
_type imageLoad(RWTexture2DArray<_format> _image, ivec3 _uvw) \
|
||||||
|
{ \
|
||||||
|
return _image[_uvw]._loadComponents; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
void imageStore(RWTexture2DArray<_format> _image, ivec3 _uvw, _type _value) \
|
||||||
|
{ \
|
||||||
|
_image[_uvw] = _value._storeComponents; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
ivec3 imageSize(RWTexture2DArray<_format> _image) \
|
||||||
|
{ \
|
||||||
|
uvec3 result; \
|
||||||
|
_image.GetDimensions(result.x, result.y, result.z); \
|
||||||
|
return ivec3(result); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
_type imageLoad(Texture3D<_format> _image, ivec3 _uvw) \
|
||||||
|
{ \
|
||||||
|
return _image[_uvw]._loadComponents; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
ivec3 imageSize(Texture3D<_format> _image) \
|
||||||
|
{ \
|
||||||
|
uvec3 result; \
|
||||||
|
_image.GetDimensions(result.x, result.y, result.z); \
|
||||||
|
return ivec3(result); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
_type imageLoad(RWTexture3D<_format> _image, ivec3 _uvw) \
|
||||||
|
{ \
|
||||||
|
return _image[_uvw]._loadComponents; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
void imageStore(RWTexture3D<_format> _image, ivec3 _uvw, _type _value) \
|
||||||
|
{ \
|
||||||
|
_image[_uvw] = _value._storeComponents; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
ivec3 imageSize(RWTexture3D<_format> _image) \
|
||||||
|
{ \
|
||||||
|
uvec3 result; \
|
||||||
|
_image.GetDimensions(result.x, result.y, result.z); \
|
||||||
|
return ivec3(result); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define __IMAGE_IMPL_ATOMIC(_format, _storeComponents, _type, _loadComponents) \
|
||||||
|
\
|
||||||
|
void imageAtomicAdd(RWTexture2D<_format> _image, ivec2 _uv, _type _value) \
|
||||||
|
{ \
|
||||||
|
InterlockedAdd(_image[_uv], _value._storeComponents); \
|
||||||
|
} \
|
||||||
|
|
||||||
|
|
||||||
|
__IMAGE_IMPL_A(float, x, vec4, xxxx)
|
||||||
|
__IMAGE_IMPL_A(float2, xy, vec4, xyyy)
|
||||||
|
__IMAGE_IMPL_A(float4, xyzw, vec4, xyzw)
|
||||||
|
|
||||||
|
__IMAGE_IMPL_A(uint, x, uvec4, xxxx)
|
||||||
|
__IMAGE_IMPL_A(uint2, xy, uvec4, xyyy)
|
||||||
|
__IMAGE_IMPL_A(uint4, xyzw, uvec4, xyzw)
|
||||||
|
|
||||||
|
#if BGFX_SHADER_LANGUAGE_HLSL
|
||||||
|
__IMAGE_IMPL_A(unorm float, x, vec4, xxxx)
|
||||||
|
__IMAGE_IMPL_A(unorm float2, xy, vec4, xyyy)
|
||||||
|
__IMAGE_IMPL_A(unorm float4, xyzw, vec4, xyzw)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
__IMAGE_IMPL_ATOMIC(uint, x, uvec4, xxxx)
|
||||||
|
|
||||||
|
|
||||||
|
#define atomicAdd(_mem, _data) InterlockedAdd(_mem, _data)
|
||||||
|
#define atomicAnd(_mem, _data) InterlockedAnd(_mem, _data)
|
||||||
|
#define atomicMax(_mem, _data) InterlockedMax(_mem, _data)
|
||||||
|
#define atomicMin(_mem, _data) InterlockedMin(_mem, _data)
|
||||||
|
#define atomicOr(_mem, _data) InterlockedOr(_mem, _data)
|
||||||
|
#define atomicXor(_mem, _data) InterlockedXor(_mem, _data)
|
||||||
|
#define atomicFetchAndAdd(_mem, _data, _original) InterlockedAdd(_mem, _data, _original)
|
||||||
|
#define atomicFetchAndAnd(_mem, _data, _original) InterlockedAnd(_mem, _data, _original)
|
||||||
|
#define atomicFetchAndMax(_mem, _data, _original) InterlockedMax(_mem, _data, _original)
|
||||||
|
#define atomicFetchAndMin(_mem, _data, _original) InterlockedMin(_mem, _data, _original)
|
||||||
|
#define atomicFetchAndOr(_mem, _data, _original) InterlockedOr(_mem, _data, _original)
|
||||||
|
#define atomicFetchAndXor(_mem, _data, _original) InterlockedXor(_mem, _data, _original)
|
||||||
|
#define atomicFetchAndExchange(_mem, _data, _original) InterlockedExchange(_mem, _data, _original)
|
||||||
|
#define atomicFetchCompareExchange(_mem, _compare, _data, _original) InterlockedCompareExchange(_mem,_compare, _data, _original)
|
||||||
|
|
||||||
|
// InterlockedCompareStore
|
||||||
|
|
||||||
|
#define barrier() GroupMemoryBarrierWithGroupSync()
|
||||||
|
#define memoryBarrier() GroupMemoryBarrierWithGroupSync()
|
||||||
|
#define memoryBarrierAtomicCounter() GroupMemoryBarrierWithGroupSync()
|
||||||
|
#define memoryBarrierBuffer() AllMemoryBarrierWithGroupSync()
|
||||||
|
#define memoryBarrierImage() GroupMemoryBarrierWithGroupSync()
|
||||||
|
#define memoryBarrierShared() GroupMemoryBarrierWithGroupSync()
|
||||||
|
#define groupMemoryBarrier() GroupMemoryBarrierWithGroupSync()
|
||||||
|
|
||||||
|
#endif // BGFX_SHADER_LANGUAGE_GLSL
|
||||||
|
|
||||||
|
#define dispatchIndirect( \
|
||||||
|
_buffer \
|
||||||
|
, _offset \
|
||||||
|
, _numX \
|
||||||
|
, _numY \
|
||||||
|
, _numZ \
|
||||||
|
) \
|
||||||
|
_buffer[(_offset)*2+0] = uvec4(_numX, _numY, _numZ, 0u)
|
||||||
|
|
||||||
|
#define drawIndirect( \
|
||||||
|
_buffer \
|
||||||
|
, _offset \
|
||||||
|
, _numVertices \
|
||||||
|
, _numInstances \
|
||||||
|
, _startVertex \
|
||||||
|
, _startInstance \
|
||||||
|
) \
|
||||||
|
_buffer[(_offset)*2+0] = uvec4(_numVertices, _numInstances, _startVertex, _startInstance)
|
||||||
|
|
||||||
|
#define drawIndexedIndirect( \
|
||||||
|
_buffer \
|
||||||
|
, _offset \
|
||||||
|
, _numIndices \
|
||||||
|
, _numInstances \
|
||||||
|
, _startIndex \
|
||||||
|
, _startVertex \
|
||||||
|
, _startInstance \
|
||||||
|
) \
|
||||||
|
_buffer[(_offset)*2+0] = uvec4(_numIndices, _numInstances, _startIndex, _startVertex); \
|
||||||
|
_buffer[(_offset)*2+1] = uvec4(_startInstance, 0u, 0u, 0u)
|
||||||
|
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
#endif // BGFX_COMPUTE_H_HEADER_GUARD
|
||||||
698
shaders/bgfx_shader.sh
Normal file
698
shaders/bgfx_shader.sh
Normal file
@@ -0,0 +1,698 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011-2022 Branimir Karadzic. All rights reserved.
|
||||||
|
* License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef BGFX_SHADER_H_HEADER_GUARD
|
||||||
|
#define BGFX_SHADER_H_HEADER_GUARD
|
||||||
|
|
||||||
|
#if !defined(BGFX_CONFIG_MAX_BONES)
|
||||||
|
# define BGFX_CONFIG_MAX_BONES 32
|
||||||
|
#endif // !defined(BGFX_CONFIG_MAX_BONES)
|
||||||
|
|
||||||
|
#ifndef __cplusplus
|
||||||
|
|
||||||
|
#if BGFX_SHADER_LANGUAGE_HLSL > 300
|
||||||
|
# define BRANCH [branch]
|
||||||
|
# define LOOP [loop]
|
||||||
|
# define UNROLL [unroll]
|
||||||
|
#else
|
||||||
|
# define BRANCH
|
||||||
|
# define LOOP
|
||||||
|
# define UNROLL
|
||||||
|
#endif // BGFX_SHADER_LANGUAGE_HLSL > 300
|
||||||
|
|
||||||
|
#if (BGFX_SHADER_LANGUAGE_HLSL > 300 || BGFX_SHADER_LANGUAGE_METAL || BGFX_SHADER_LANGUAGE_SPIRV) && BGFX_SHADER_TYPE_FRAGMENT
|
||||||
|
# define EARLY_DEPTH_STENCIL [earlydepthstencil]
|
||||||
|
#else
|
||||||
|
# define EARLY_DEPTH_STENCIL
|
||||||
|
#endif // BGFX_SHADER_LANGUAGE_HLSL > 300 && BGFX_SHADER_TYPE_FRAGMENT
|
||||||
|
|
||||||
|
#if BGFX_SHADER_LANGUAGE_GLSL
|
||||||
|
# define ARRAY_BEGIN(_type, _name, _count) _type _name[_count] = _type[](
|
||||||
|
# define ARRAY_END() )
|
||||||
|
#else
|
||||||
|
# define ARRAY_BEGIN(_type, _name, _count) _type _name[_count] = {
|
||||||
|
# define ARRAY_END() }
|
||||||
|
#endif // BGFX_SHADER_LANGUAGE_GLSL
|
||||||
|
|
||||||
|
#if BGFX_SHADER_LANGUAGE_HLSL \
|
||||||
|
|| BGFX_SHADER_LANGUAGE_PSSL \
|
||||||
|
|| BGFX_SHADER_LANGUAGE_SPIRV \
|
||||||
|
|| BGFX_SHADER_LANGUAGE_METAL
|
||||||
|
# define CONST(_x) static const _x
|
||||||
|
# define dFdx(_x) ddx(_x)
|
||||||
|
# define dFdy(_y) ddy(-(_y))
|
||||||
|
# define inversesqrt(_x) rsqrt(_x)
|
||||||
|
# define fract(_x) frac(_x)
|
||||||
|
|
||||||
|
# define bvec2 bool2
|
||||||
|
# define bvec3 bool3
|
||||||
|
# define bvec4 bool4
|
||||||
|
|
||||||
|
// To be able to patch the uav registers on the DXBC SPDB Chunk (D3D11 renderer) the whitespaces around
|
||||||
|
// '_type[_reg]' are necessary. This only affects shaders with debug info (i.e., those that have the SPDB Chunk).
|
||||||
|
# if BGFX_SHADER_LANGUAGE_HLSL > 400 || BGFX_SHADER_LANGUAGE_PSSL || BGFX_SHADER_LANGUAGE_SPIRV || BGFX_SHADER_LANGUAGE_METAL
|
||||||
|
# define REGISTER(_type, _reg) register( _type[_reg] )
|
||||||
|
# else
|
||||||
|
# define REGISTER(_type, _reg) register(_type ## _reg)
|
||||||
|
# endif // BGFX_SHADER_LANGUAGE_HLSL
|
||||||
|
|
||||||
|
# if BGFX_SHADER_LANGUAGE_HLSL > 300 || BGFX_SHADER_LANGUAGE_PSSL || BGFX_SHADER_LANGUAGE_SPIRV || BGFX_SHADER_LANGUAGE_METAL
|
||||||
|
# if BGFX_SHADER_LANGUAGE_HLSL > 400 || BGFX_SHADER_LANGUAGE_PSSL || BGFX_SHADER_LANGUAGE_SPIRV || BGFX_SHADER_LANGUAGE_METAL
|
||||||
|
# define dFdxCoarse(_x) ddx_coarse(_x)
|
||||||
|
# define dFdxFine(_x) ddx_fine(_x)
|
||||||
|
# define dFdyCoarse(_y) ddy_coarse(-(_y))
|
||||||
|
# define dFdyFine(_y) ddy_fine(-(_y))
|
||||||
|
# endif // BGFX_SHADER_LANGUAGE_HLSL > 400
|
||||||
|
|
||||||
|
# if BGFX_SHADER_LANGUAGE_HLSL || BGFX_SHADER_LANGUAGE_SPIRV || BGFX_SHADER_LANGUAGE_METAL
|
||||||
|
float intBitsToFloat(int _x) { return asfloat(_x); }
|
||||||
|
vec2 intBitsToFloat(uint2 _x) { return asfloat(_x); }
|
||||||
|
vec3 intBitsToFloat(uint3 _x) { return asfloat(_x); }
|
||||||
|
vec4 intBitsToFloat(uint4 _x) { return asfloat(_x); }
|
||||||
|
# endif // BGFX_SHADER_LANGUAGE_HLSL || BGFX_SHADER_LANGUAGE_SPIRV || BGFX_SHADER_LANGUAGE_METAL
|
||||||
|
|
||||||
|
float uintBitsToFloat(uint _x) { return asfloat(_x); }
|
||||||
|
vec2 uintBitsToFloat(uint2 _x) { return asfloat(_x); }
|
||||||
|
vec3 uintBitsToFloat(uint3 _x) { return asfloat(_x); }
|
||||||
|
vec4 uintBitsToFloat(uint4 _x) { return asfloat(_x); }
|
||||||
|
|
||||||
|
uint floatBitsToUint(float _x) { return asuint(_x); }
|
||||||
|
uvec2 floatBitsToUint(vec2 _x) { return asuint(_x); }
|
||||||
|
uvec3 floatBitsToUint(vec3 _x) { return asuint(_x); }
|
||||||
|
uvec4 floatBitsToUint(vec4 _x) { return asuint(_x); }
|
||||||
|
|
||||||
|
int floatBitsToInt(float _x) { return asint(_x); }
|
||||||
|
ivec2 floatBitsToInt(vec2 _x) { return asint(_x); }
|
||||||
|
ivec3 floatBitsToInt(vec3 _x) { return asint(_x); }
|
||||||
|
ivec4 floatBitsToInt(vec4 _x) { return asint(_x); }
|
||||||
|
|
||||||
|
uint bitfieldReverse(uint _x) { return reversebits(_x); }
|
||||||
|
uint2 bitfieldReverse(uint2 _x) { return reversebits(_x); }
|
||||||
|
uint3 bitfieldReverse(uint3 _x) { return reversebits(_x); }
|
||||||
|
uint4 bitfieldReverse(uint4 _x) { return reversebits(_x); }
|
||||||
|
|
||||||
|
# if !BGFX_SHADER_LANGUAGE_SPIRV
|
||||||
|
uint packHalf2x16(vec2 _x)
|
||||||
|
{
|
||||||
|
return (f32tof16(_x.y)<<16) | f32tof16(_x.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 unpackHalf2x16(uint _x)
|
||||||
|
{
|
||||||
|
return vec2(f16tof32(_x & 0xffff), f16tof32(_x >> 16) );
|
||||||
|
}
|
||||||
|
# endif // !BGFX_SHADER_LANGUAGE_SPIRV
|
||||||
|
|
||||||
|
struct BgfxSampler2D
|
||||||
|
{
|
||||||
|
SamplerState m_sampler;
|
||||||
|
Texture2D m_texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BgfxISampler2D
|
||||||
|
{
|
||||||
|
Texture2D<ivec4> m_texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BgfxUSampler2D
|
||||||
|
{
|
||||||
|
Texture2D<uvec4> m_texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BgfxSampler2DArray
|
||||||
|
{
|
||||||
|
SamplerState m_sampler;
|
||||||
|
Texture2DArray m_texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BgfxSampler2DShadow
|
||||||
|
{
|
||||||
|
SamplerComparisonState m_sampler;
|
||||||
|
Texture2D m_texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BgfxSampler2DArrayShadow
|
||||||
|
{
|
||||||
|
SamplerComparisonState m_sampler;
|
||||||
|
Texture2DArray m_texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BgfxSampler3D
|
||||||
|
{
|
||||||
|
SamplerState m_sampler;
|
||||||
|
Texture3D m_texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BgfxISampler3D
|
||||||
|
{
|
||||||
|
Texture3D<ivec4> m_texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BgfxUSampler3D
|
||||||
|
{
|
||||||
|
Texture3D<uvec4> m_texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BgfxSamplerCube
|
||||||
|
{
|
||||||
|
SamplerState m_sampler;
|
||||||
|
TextureCube m_texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BgfxSamplerCubeShadow
|
||||||
|
{
|
||||||
|
SamplerComparisonState m_sampler;
|
||||||
|
TextureCube m_texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BgfxSampler2DMS
|
||||||
|
{
|
||||||
|
Texture2DMS<vec4> m_texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
vec4 bgfxTexture2D(BgfxSampler2D _sampler, vec2 _coord)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.Sample(_sampler.m_sampler, _coord);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTexture2DBias(BgfxSampler2D _sampler, vec2 _coord, float _bias)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.SampleBias(_sampler.m_sampler, _coord, _bias);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTexture2DLod(BgfxSampler2D _sampler, vec2 _coord, float _level)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.SampleLevel(_sampler.m_sampler, _coord, _level);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTexture2DLodOffset(BgfxSampler2D _sampler, vec2 _coord, float _level, ivec2 _offset)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.SampleLevel(_sampler.m_sampler, _coord, _level, _offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTexture2DProj(BgfxSampler2D _sampler, vec3 _coord)
|
||||||
|
{
|
||||||
|
vec2 coord = _coord.xy * rcp(_coord.z);
|
||||||
|
return _sampler.m_texture.Sample(_sampler.m_sampler, coord);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTexture2DProj(BgfxSampler2D _sampler, vec4 _coord)
|
||||||
|
{
|
||||||
|
vec2 coord = _coord.xy * rcp(_coord.w);
|
||||||
|
return _sampler.m_texture.Sample(_sampler.m_sampler, coord);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTexture2DGrad(BgfxSampler2D _sampler, vec2 _coord, vec2 _dPdx, vec2 _dPdy)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.SampleGrad(_sampler.m_sampler, _coord, _dPdx, _dPdy);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTexture2DArray(BgfxSampler2DArray _sampler, vec3 _coord)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.Sample(_sampler.m_sampler, _coord);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTexture2DArrayLod(BgfxSampler2DArray _sampler, vec3 _coord, float _lod)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.SampleLevel(_sampler.m_sampler, _coord, _lod);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTexture2DArrayLodOffset(BgfxSampler2DArray _sampler, vec3 _coord, float _level, ivec2 _offset)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.SampleLevel(_sampler.m_sampler, _coord, _level, _offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
float bgfxShadow2D(BgfxSampler2DShadow _sampler, vec3 _coord)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.SampleCmpLevelZero(_sampler.m_sampler, _coord.xy, _coord.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
float bgfxShadow2DProj(BgfxSampler2DShadow _sampler, vec4 _coord)
|
||||||
|
{
|
||||||
|
vec3 coord = _coord.xyz * rcp(_coord.w);
|
||||||
|
return _sampler.m_texture.SampleCmpLevelZero(_sampler.m_sampler, coord.xy, coord.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxShadow2DArray(BgfxSampler2DArrayShadow _sampler, vec4 _coord)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.SampleCmpLevelZero(_sampler.m_sampler, _coord.xyz, _coord.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTexture3D(BgfxSampler3D _sampler, vec3 _coord)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.Sample(_sampler.m_sampler, _coord);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTexture3DLod(BgfxSampler3D _sampler, vec3 _coord, float _level)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.SampleLevel(_sampler.m_sampler, _coord, _level);
|
||||||
|
}
|
||||||
|
|
||||||
|
ivec4 bgfxTexture3D(BgfxISampler3D _sampler, vec3 _coord)
|
||||||
|
{
|
||||||
|
uvec3 size;
|
||||||
|
_sampler.m_texture.GetDimensions(size.x, size.y, size.z);
|
||||||
|
return _sampler.m_texture.Load(ivec4(_coord * size, 0) );
|
||||||
|
}
|
||||||
|
|
||||||
|
uvec4 bgfxTexture3D(BgfxUSampler3D _sampler, vec3 _coord)
|
||||||
|
{
|
||||||
|
uvec3 size;
|
||||||
|
_sampler.m_texture.GetDimensions(size.x, size.y, size.z);
|
||||||
|
return _sampler.m_texture.Load(ivec4(_coord * size, 0) );
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTextureCube(BgfxSamplerCube _sampler, vec3 _coord)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.Sample(_sampler.m_sampler, _coord);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTextureCubeBias(BgfxSamplerCube _sampler, vec3 _coord, float _bias)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.SampleBias(_sampler.m_sampler, _coord, _bias);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTextureCubeLod(BgfxSamplerCube _sampler, vec3 _coord, float _level)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.SampleLevel(_sampler.m_sampler, _coord, _level);
|
||||||
|
}
|
||||||
|
|
||||||
|
float bgfxShadowCube(BgfxSamplerCubeShadow _sampler, vec4 _coord)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.SampleCmpLevelZero(_sampler.m_sampler, _coord.xyz, _coord.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTexelFetch(BgfxSampler2D _sampler, ivec2 _coord, int _lod)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.Load(ivec3(_coord, _lod) );
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTexelFetchOffset(BgfxSampler2D _sampler, ivec2 _coord, int _lod, ivec2 _offset)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.Load(ivec3(_coord, _lod), _offset );
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 bgfxTextureSize(BgfxSampler2D _sampler, int _lod)
|
||||||
|
{
|
||||||
|
vec2 result;
|
||||||
|
_sampler.m_texture.GetDimensions(result.x, result.y);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 bgfxTextureSize(BgfxISampler2D _sampler, int _lod)
|
||||||
|
{
|
||||||
|
vec2 result;
|
||||||
|
_sampler.m_texture.GetDimensions(result.x, result.y);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 bgfxTextureSize(BgfxUSampler2D _sampler, int _lod)
|
||||||
|
{
|
||||||
|
vec2 result;
|
||||||
|
_sampler.m_texture.GetDimensions(result.x, result.y);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTextureGather0(BgfxSampler2D _sampler, vec2 _coord)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.GatherRed(_sampler.m_sampler, _coord);
|
||||||
|
}
|
||||||
|
vec4 bgfxTextureGather1(BgfxSampler2D _sampler, vec2 _coord)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.GatherGreen(_sampler.m_sampler, _coord);
|
||||||
|
}
|
||||||
|
vec4 bgfxTextureGather2(BgfxSampler2D _sampler, vec2 _coord)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.GatherBlue(_sampler.m_sampler, _coord);
|
||||||
|
}
|
||||||
|
vec4 bgfxTextureGather3(BgfxSampler2D _sampler, vec2 _coord)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.GatherAlpha(_sampler.m_sampler, _coord);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTextureGatherOffset0(BgfxSampler2D _sampler, vec2 _coord, ivec2 _offset)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.GatherRed(_sampler.m_sampler, _coord, _offset);
|
||||||
|
}
|
||||||
|
vec4 bgfxTextureGatherOffset1(BgfxSampler2D _sampler, vec2 _coord, ivec2 _offset)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.GatherGreen(_sampler.m_sampler, _coord, _offset);
|
||||||
|
}
|
||||||
|
vec4 bgfxTextureGatherOffset2(BgfxSampler2D _sampler, vec2 _coord, ivec2 _offset)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.GatherBlue(_sampler.m_sampler, _coord, _offset);
|
||||||
|
}
|
||||||
|
vec4 bgfxTextureGatherOffset3(BgfxSampler2D _sampler, vec2 _coord, ivec2 _offset)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.GatherAlpha(_sampler.m_sampler, _coord, _offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTextureGather0(BgfxSampler2DArray _sampler, vec3 _coord)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.GatherRed(_sampler.m_sampler, _coord);
|
||||||
|
}
|
||||||
|
vec4 bgfxTextureGather1(BgfxSampler2DArray _sampler, vec3 _coord)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.GatherGreen(_sampler.m_sampler, _coord);
|
||||||
|
}
|
||||||
|
vec4 bgfxTextureGather2(BgfxSampler2DArray _sampler, vec3 _coord)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.GatherBlue(_sampler.m_sampler, _coord);
|
||||||
|
}
|
||||||
|
vec4 bgfxTextureGather3(BgfxSampler2DArray _sampler, vec3 _coord)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.GatherAlpha(_sampler.m_sampler, _coord);
|
||||||
|
}
|
||||||
|
|
||||||
|
ivec4 bgfxTexelFetch(BgfxISampler2D _sampler, ivec2 _coord, int _lod)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.Load(ivec3(_coord, _lod) );
|
||||||
|
}
|
||||||
|
|
||||||
|
uvec4 bgfxTexelFetch(BgfxUSampler2D _sampler, ivec2 _coord, int _lod)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.Load(ivec3(_coord, _lod) );
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTexelFetch(BgfxSampler2DMS _sampler, ivec2 _coord, int _sampleIdx)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.Load(_coord, _sampleIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTexelFetch(BgfxSampler2DArray _sampler, ivec3 _coord, int _lod)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.Load(ivec4(_coord, _lod) );
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTexelFetch(BgfxSampler3D _sampler, ivec3 _coord, int _lod)
|
||||||
|
{
|
||||||
|
return _sampler.m_texture.Load(ivec4(_coord, _lod) );
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 bgfxTextureSize(BgfxSampler3D _sampler, int _lod)
|
||||||
|
{
|
||||||
|
vec3 result;
|
||||||
|
_sampler.m_texture.GetDimensions(result.x, result.y, result.z);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
# define SAMPLER2D(_name, _reg) \
|
||||||
|
uniform SamplerState _name ## Sampler : REGISTER(s, _reg); \
|
||||||
|
uniform Texture2D _name ## Texture : REGISTER(t, _reg); \
|
||||||
|
static BgfxSampler2D _name = { _name ## Sampler, _name ## Texture }
|
||||||
|
# define ISAMPLER2D(_name, _reg) \
|
||||||
|
uniform Texture2D<ivec4> _name ## Texture : REGISTER(t, _reg); \
|
||||||
|
static BgfxISampler2D _name = { _name ## Texture }
|
||||||
|
# define USAMPLER2D(_name, _reg) \
|
||||||
|
uniform Texture2D<uvec4> _name ## Texture : REGISTER(t, _reg); \
|
||||||
|
static BgfxUSampler2D _name = { _name ## Texture }
|
||||||
|
# define sampler2D BgfxSampler2D
|
||||||
|
# define texture2D(_sampler, _coord) bgfxTexture2D(_sampler, _coord)
|
||||||
|
# define texture2DBias(_sampler, _coord, _bias) bgfxTexture2DBias(_sampler, _coord, _bias)
|
||||||
|
# define texture2DLod(_sampler, _coord, _level) bgfxTexture2DLod(_sampler, _coord, _level)
|
||||||
|
# define texture2DLodOffset(_sampler, _coord, _level, _offset) bgfxTexture2DLodOffset(_sampler, _coord, _level, _offset)
|
||||||
|
# define texture2DProj(_sampler, _coord) bgfxTexture2DProj(_sampler, _coord)
|
||||||
|
# define texture2DGrad(_sampler, _coord, _dPdx, _dPdy) bgfxTexture2DGrad(_sampler, _coord, _dPdx, _dPdy)
|
||||||
|
|
||||||
|
# define SAMPLER2DARRAY(_name, _reg) \
|
||||||
|
uniform SamplerState _name ## Sampler : REGISTER(s, _reg); \
|
||||||
|
uniform Texture2DArray _name ## Texture : REGISTER(t, _reg); \
|
||||||
|
static BgfxSampler2DArray _name = { _name ## Sampler, _name ## Texture }
|
||||||
|
# define sampler2DArray BgfxSampler2DArray
|
||||||
|
# define texture2DArray(_sampler, _coord) bgfxTexture2DArray(_sampler, _coord)
|
||||||
|
# define texture2DArrayLod(_sampler, _coord, _lod) bgfxTexture2DArrayLod(_sampler, _coord, _lod)
|
||||||
|
# define texture2DArrayLodOffset(_sampler, _coord, _level, _offset) bgfxTexture2DArrayLodOffset(_sampler, _coord, _level, _offset)
|
||||||
|
|
||||||
|
# define SAMPLER2DMS(_name, _reg) \
|
||||||
|
uniform Texture2DMS<vec4> _name ## Texture : REGISTER(t, _reg); \
|
||||||
|
static BgfxSampler2DMS _name = { _name ## Texture }
|
||||||
|
# define sampler2DMS BgfxSampler2DMS
|
||||||
|
|
||||||
|
# define SAMPLER2DSHADOW(_name, _reg) \
|
||||||
|
uniform SamplerComparisonState _name ## SamplerComparison : REGISTER(s, _reg); \
|
||||||
|
uniform Texture2D _name ## Texture : REGISTER(t, _reg); \
|
||||||
|
static BgfxSampler2DShadow _name = { _name ## SamplerComparison, _name ## Texture }
|
||||||
|
# define sampler2DShadow BgfxSampler2DShadow
|
||||||
|
# define shadow2D(_sampler, _coord) bgfxShadow2D(_sampler, _coord)
|
||||||
|
# define shadow2DProj(_sampler, _coord) bgfxShadow2DProj(_sampler, _coord)
|
||||||
|
|
||||||
|
# define SAMPLER2DARRAYSHADOW(_name, _reg) \
|
||||||
|
uniform SamplerComparisonState _name ## SamplerComparison : REGISTER(s, _reg); \
|
||||||
|
uniform Texture2DArray _name ## Texture : REGISTER(t, _reg); \
|
||||||
|
static BgfxSampler2DArrayShadow _name = { _name ## SamplerComparison, _name ## Texture }
|
||||||
|
# define sampler2DArrayShadow BgfxSampler2DArrayShadow
|
||||||
|
# define shadow2DArray(_sampler, _coord) bgfxShadow2DArray(_sampler, _coord)
|
||||||
|
|
||||||
|
# define SAMPLER3D(_name, _reg) \
|
||||||
|
uniform SamplerState _name ## Sampler : REGISTER(s, _reg); \
|
||||||
|
uniform Texture3D _name ## Texture : REGISTER(t, _reg); \
|
||||||
|
static BgfxSampler3D _name = { _name ## Sampler, _name ## Texture }
|
||||||
|
# define ISAMPLER3D(_name, _reg) \
|
||||||
|
uniform Texture3D<ivec4> _name ## Texture : REGISTER(t, _reg); \
|
||||||
|
static BgfxISampler3D _name = { _name ## Texture }
|
||||||
|
# define USAMPLER3D(_name, _reg) \
|
||||||
|
uniform Texture3D<uvec4> _name ## Texture : REGISTER(t, _reg); \
|
||||||
|
static BgfxUSampler3D _name = { _name ## Texture }
|
||||||
|
# define sampler3D BgfxSampler3D
|
||||||
|
# define texture3D(_sampler, _coord) bgfxTexture3D(_sampler, _coord)
|
||||||
|
# define texture3DLod(_sampler, _coord, _level) bgfxTexture3DLod(_sampler, _coord, _level)
|
||||||
|
|
||||||
|
# define SAMPLERCUBE(_name, _reg) \
|
||||||
|
uniform SamplerState _name ## Sampler : REGISTER(s, _reg); \
|
||||||
|
uniform TextureCube _name ## Texture : REGISTER(t, _reg); \
|
||||||
|
static BgfxSamplerCube _name = { _name ## Sampler, _name ## Texture }
|
||||||
|
# define samplerCube BgfxSamplerCube
|
||||||
|
# define textureCube(_sampler, _coord) bgfxTextureCube(_sampler, _coord)
|
||||||
|
# define textureCubeBias(_sampler, _coord, _bias) bgfxTextureCubeBias(_sampler, _coord, _bias)
|
||||||
|
# define textureCubeLod(_sampler, _coord, _level) bgfxTextureCubeLod(_sampler, _coord, _level)
|
||||||
|
|
||||||
|
# define SAMPLERCUBESHADOW(_name, _reg) \
|
||||||
|
uniform SamplerComparisonState _name ## SamplerComparison : REGISTER(s, _reg); \
|
||||||
|
uniform TextureCube _name ## Texture : REGISTER(t, _reg); \
|
||||||
|
static BgfxSamplerCubeShadow _name = { _name ## SamplerComparison, _name ## Texture }
|
||||||
|
# define samplerCubeShadow BgfxSamplerCubeShadow
|
||||||
|
# define shadowCube(_sampler, _coord) bgfxShadowCube(_sampler, _coord)
|
||||||
|
|
||||||
|
# define texelFetch(_sampler, _coord, _lod) bgfxTexelFetch(_sampler, _coord, _lod)
|
||||||
|
# define texelFetchOffset(_sampler, _coord, _lod, _offset) bgfxTexelFetchOffset(_sampler, _coord, _lod, _offset)
|
||||||
|
# define textureSize(_sampler, _lod) bgfxTextureSize(_sampler, _lod)
|
||||||
|
# define textureGather(_sampler, _coord, _comp) bgfxTextureGather ## _comp(_sampler, _coord)
|
||||||
|
# define textureGatherOffset(_sampler, _coord, _offset, _comp) bgfxTextureGatherOffset ## _comp(_sampler, _coord, _offset)
|
||||||
|
# else
|
||||||
|
|
||||||
|
# define sampler2DShadow sampler2D
|
||||||
|
|
||||||
|
vec4 bgfxTexture2DProj(sampler2D _sampler, vec3 _coord)
|
||||||
|
{
|
||||||
|
return tex2Dproj(_sampler, vec4(_coord.xy, 0.0, _coord.z) );
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 bgfxTexture2DProj(sampler2D _sampler, vec4 _coord)
|
||||||
|
{
|
||||||
|
return tex2Dproj(_sampler, _coord);
|
||||||
|
}
|
||||||
|
|
||||||
|
float bgfxShadow2D(sampler2DShadow _sampler, vec3 _coord)
|
||||||
|
{
|
||||||
|
#if 0
|
||||||
|
float occluder = tex2D(_sampler, _coord.xy).x;
|
||||||
|
return step(_coord.z, occluder);
|
||||||
|
#else
|
||||||
|
return tex2Dproj(_sampler, vec4(_coord.xy, _coord.z, 1.0) ).x;
|
||||||
|
#endif // 0
|
||||||
|
}
|
||||||
|
|
||||||
|
float bgfxShadow2DProj(sampler2DShadow _sampler, vec4 _coord)
|
||||||
|
{
|
||||||
|
#if 0
|
||||||
|
vec3 coord = _coord.xyz * rcp(_coord.w);
|
||||||
|
float occluder = tex2D(_sampler, coord.xy).x;
|
||||||
|
return step(coord.z, occluder);
|
||||||
|
#else
|
||||||
|
return tex2Dproj(_sampler, _coord).x;
|
||||||
|
#endif // 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# define SAMPLER2D(_name, _reg) uniform sampler2D _name : REGISTER(s, _reg)
|
||||||
|
# define SAMPLER2DMS(_name, _reg) uniform sampler2DMS _name : REGISTER(s, _reg)
|
||||||
|
# define texture2D(_sampler, _coord) tex2D(_sampler, _coord)
|
||||||
|
# define texture2DProj(_sampler, _coord) bgfxTexture2DProj(_sampler, _coord)
|
||||||
|
|
||||||
|
# define SAMPLER2DARRAY(_name, _reg) SAMPLER2D(_name, _reg)
|
||||||
|
# define texture2DArray(_sampler, _coord) texture2D(_sampler, (_coord).xy)
|
||||||
|
# define texture2DArrayLod(_sampler, _coord, _lod) texture2DLod(_sampler, _coord, _lod)
|
||||||
|
|
||||||
|
# define SAMPLER2DSHADOW(_name, _reg) uniform sampler2DShadow _name : REGISTER(s, _reg)
|
||||||
|
# define shadow2D(_sampler, _coord) bgfxShadow2D(_sampler, _coord)
|
||||||
|
# define shadow2DProj(_sampler, _coord) bgfxShadow2DProj(_sampler, _coord)
|
||||||
|
|
||||||
|
# define SAMPLER3D(_name, _reg) uniform sampler3D _name : REGISTER(s, _reg)
|
||||||
|
# define texture3D(_sampler, _coord) tex3D(_sampler, _coord)
|
||||||
|
|
||||||
|
# define SAMPLERCUBE(_name, _reg) uniform samplerCUBE _name : REGISTER(s, _reg)
|
||||||
|
# define textureCube(_sampler, _coord) texCUBE(_sampler, _coord)
|
||||||
|
|
||||||
|
# define texture2DLod(_sampler, _coord, _level) tex2Dlod(_sampler, vec4( (_coord).xy, 0.0, _level) )
|
||||||
|
# define texture2DGrad(_sampler, _coord, _dPdx, _dPdy) tex2Dgrad(_sampler, _coord, _dPdx, _dPdy)
|
||||||
|
# define texture3DLod(_sampler, _coord, _level) tex3Dlod(_sampler, vec4( (_coord).xyz, _level) )
|
||||||
|
# define textureCubeLod(_sampler, _coord, _level) texCUBElod(_sampler, vec4( (_coord).xyz, _level) )
|
||||||
|
|
||||||
|
# endif // BGFX_SHADER_LANGUAGE_HLSL > 300
|
||||||
|
|
||||||
|
vec3 instMul(vec3 _vec, mat3 _mtx) { return mul(_mtx, _vec); }
|
||||||
|
vec3 instMul(mat3 _mtx, vec3 _vec) { return mul(_vec, _mtx); }
|
||||||
|
vec4 instMul(vec4 _vec, mat4 _mtx) { return mul(_mtx, _vec); }
|
||||||
|
vec4 instMul(mat4 _mtx, vec4 _vec) { return mul(_vec, _mtx); }
|
||||||
|
|
||||||
|
bvec2 lessThan(vec2 _a, vec2 _b) { return _a < _b; }
|
||||||
|
bvec3 lessThan(vec3 _a, vec3 _b) { return _a < _b; }
|
||||||
|
bvec4 lessThan(vec4 _a, vec4 _b) { return _a < _b; }
|
||||||
|
|
||||||
|
bvec2 lessThanEqual(vec2 _a, vec2 _b) { return _a <= _b; }
|
||||||
|
bvec3 lessThanEqual(vec3 _a, vec3 _b) { return _a <= _b; }
|
||||||
|
bvec4 lessThanEqual(vec4 _a, vec4 _b) { return _a <= _b; }
|
||||||
|
|
||||||
|
bvec2 greaterThan(vec2 _a, vec2 _b) { return _a > _b; }
|
||||||
|
bvec3 greaterThan(vec3 _a, vec3 _b) { return _a > _b; }
|
||||||
|
bvec4 greaterThan(vec4 _a, vec4 _b) { return _a > _b; }
|
||||||
|
|
||||||
|
bvec2 greaterThanEqual(vec2 _a, vec2 _b) { return _a >= _b; }
|
||||||
|
bvec3 greaterThanEqual(vec3 _a, vec3 _b) { return _a >= _b; }
|
||||||
|
bvec4 greaterThanEqual(vec4 _a, vec4 _b) { return _a >= _b; }
|
||||||
|
|
||||||
|
bvec2 notEqual(vec2 _a, vec2 _b) { return _a != _b; }
|
||||||
|
bvec3 notEqual(vec3 _a, vec3 _b) { return _a != _b; }
|
||||||
|
bvec4 notEqual(vec4 _a, vec4 _b) { return _a != _b; }
|
||||||
|
|
||||||
|
bvec2 equal(vec2 _a, vec2 _b) { return _a == _b; }
|
||||||
|
bvec3 equal(vec3 _a, vec3 _b) { return _a == _b; }
|
||||||
|
bvec4 equal(vec4 _a, vec4 _b) { return _a == _b; }
|
||||||
|
|
||||||
|
float mix(float _a, float _b, float _t) { return lerp(_a, _b, _t); }
|
||||||
|
vec2 mix(vec2 _a, vec2 _b, vec2 _t) { return lerp(_a, _b, _t); }
|
||||||
|
vec3 mix(vec3 _a, vec3 _b, vec3 _t) { return lerp(_a, _b, _t); }
|
||||||
|
vec4 mix(vec4 _a, vec4 _b, vec4 _t) { return lerp(_a, _b, _t); }
|
||||||
|
|
||||||
|
float mod(float _a, float _b) { return _a - _b * floor(_a / _b); }
|
||||||
|
vec2 mod(vec2 _a, vec2 _b) { return _a - _b * floor(_a / _b); }
|
||||||
|
vec3 mod(vec3 _a, vec3 _b) { return _a - _b * floor(_a / _b); }
|
||||||
|
vec4 mod(vec4 _a, vec4 _b) { return _a - _b * floor(_a / _b); }
|
||||||
|
|
||||||
|
#else
|
||||||
|
# define CONST(_x) const _x
|
||||||
|
# define atan2(_x, _y) atan(_x, _y)
|
||||||
|
# define mul(_a, _b) ( (_a) * (_b) )
|
||||||
|
# define saturate(_x) clamp(_x, 0.0, 1.0)
|
||||||
|
# define SAMPLER2D(_name, _reg) uniform sampler2D _name
|
||||||
|
# define SAMPLER2DMS(_name, _reg) uniform sampler2DMS _name
|
||||||
|
# define SAMPLER3D(_name, _reg) uniform sampler3D _name
|
||||||
|
# define SAMPLERCUBE(_name, _reg) uniform samplerCube _name
|
||||||
|
# define SAMPLER2DSHADOW(_name, _reg) uniform sampler2DShadow _name
|
||||||
|
|
||||||
|
# define SAMPLER2DARRAY(_name, _reg) uniform sampler2DArray _name
|
||||||
|
# define SAMPLER2DMSARRAY(_name, _reg) uniform sampler2DMSArray _name
|
||||||
|
# define SAMPLERCUBEARRAY(_name, _reg) uniform samplerCubeArray _name
|
||||||
|
# define SAMPLER2DARRAYSHADOW(_name, _reg) uniform sampler2DArrayShadow _name
|
||||||
|
|
||||||
|
# define ISAMPLER2D(_name, _reg) uniform isampler2D _name
|
||||||
|
# define USAMPLER2D(_name, _reg) uniform usampler2D _name
|
||||||
|
# define ISAMPLER3D(_name, _reg) uniform isampler3D _name
|
||||||
|
# define USAMPLER3D(_name, _reg) uniform usampler3D _name
|
||||||
|
|
||||||
|
# define texture2DBias(_sampler, _coord, _bias) texture2D(_sampler, _coord, _bias)
|
||||||
|
# define textureCubeBias(_sampler, _coord, _bias) textureCube(_sampler, _coord, _bias)
|
||||||
|
|
||||||
|
# if BGFX_SHADER_LANGUAGE_GLSL >= 130
|
||||||
|
# define texture2D(_sampler, _coord) texture(_sampler, _coord)
|
||||||
|
# define texture2DArray(_sampler, _coord) texture(_sampler, _coord)
|
||||||
|
# define texture3D(_sampler, _coord) texture(_sampler, _coord)
|
||||||
|
# define textureCube(_sampler, _coord) texture(_sampler, _coord)
|
||||||
|
# define texture2DLod(_sampler, _coord, _lod) textureLod(_sampler, _coord, _lod)
|
||||||
|
# define texture2DLodOffset(_sampler, _coord, _lod, _offset) textureLodOffset(_sampler, _coord, _lod, _offset)
|
||||||
|
# endif // BGFX_SHADER_LANGUAGE_GLSL >= 130
|
||||||
|
|
||||||
|
vec3 instMul(vec3 _vec, mat3 _mtx) { return mul(_vec, _mtx); }
|
||||||
|
vec3 instMul(mat3 _mtx, vec3 _vec) { return mul(_mtx, _vec); }
|
||||||
|
vec4 instMul(vec4 _vec, mat4 _mtx) { return mul(_vec, _mtx); }
|
||||||
|
vec4 instMul(mat4 _mtx, vec4 _vec) { return mul(_mtx, _vec); }
|
||||||
|
|
||||||
|
float rcp(float _a) { return 1.0/_a; }
|
||||||
|
vec2 rcp(vec2 _a) { return vec2(1.0)/_a; }
|
||||||
|
vec3 rcp(vec3 _a) { return vec3(1.0)/_a; }
|
||||||
|
vec4 rcp(vec4 _a) { return vec4(1.0)/_a; }
|
||||||
|
#endif // BGFX_SHADER_LANGUAGE_*
|
||||||
|
|
||||||
|
vec2 vec2_splat(float _x) { return vec2(_x, _x); }
|
||||||
|
vec3 vec3_splat(float _x) { return vec3(_x, _x, _x); }
|
||||||
|
vec4 vec4_splat(float _x) { return vec4(_x, _x, _x, _x); }
|
||||||
|
|
||||||
|
#if BGFX_SHADER_LANGUAGE_GLSL >= 130 || BGFX_SHADER_LANGUAGE_HLSL || BGFX_SHADER_LANGUAGE_PSSL || BGFX_SHADER_LANGUAGE_SPIRV || BGFX_SHADER_LANGUAGE_METAL
|
||||||
|
uvec2 uvec2_splat(uint _x) { return uvec2(_x, _x); }
|
||||||
|
uvec3 uvec3_splat(uint _x) { return uvec3(_x, _x, _x); }
|
||||||
|
uvec4 uvec4_splat(uint _x) { return uvec4(_x, _x, _x, _x); }
|
||||||
|
#endif // BGFX_SHADER_LANGUAGE_GLSL >= 130 || BGFX_SHADER_LANGUAGE_HLSL || BGFX_SHADER_LANGUAGE_PSSL || BGFX_SHADER_LANGUAGE_SPIRV || BGFX_SHADER_LANGUAGE_METAL
|
||||||
|
|
||||||
|
mat4 mtxFromRows(vec4 _0, vec4 _1, vec4 _2, vec4 _3)
|
||||||
|
{
|
||||||
|
#if BGFX_SHADER_LANGUAGE_GLSL
|
||||||
|
return transpose(mat4(_0, _1, _2, _3) );
|
||||||
|
#else
|
||||||
|
return mat4(_0, _1, _2, _3);
|
||||||
|
#endif // BGFX_SHADER_LANGUAGE_GLSL
|
||||||
|
}
|
||||||
|
mat4 mtxFromCols(vec4 _0, vec4 _1, vec4 _2, vec4 _3)
|
||||||
|
{
|
||||||
|
#if BGFX_SHADER_LANGUAGE_GLSL
|
||||||
|
return mat4(_0, _1, _2, _3);
|
||||||
|
#else
|
||||||
|
return transpose(mat4(_0, _1, _2, _3) );
|
||||||
|
#endif // BGFX_SHADER_LANGUAGE_GLSL
|
||||||
|
}
|
||||||
|
mat3 mtxFromRows(vec3 _0, vec3 _1, vec3 _2)
|
||||||
|
{
|
||||||
|
#if BGFX_SHADER_LANGUAGE_GLSL
|
||||||
|
return transpose(mat3(_0, _1, _2) );
|
||||||
|
#else
|
||||||
|
return mat3(_0, _1, _2);
|
||||||
|
#endif // BGFX_SHADER_LANGUAGE_GLSL
|
||||||
|
}
|
||||||
|
mat3 mtxFromCols(vec3 _0, vec3 _1, vec3 _2)
|
||||||
|
{
|
||||||
|
#if BGFX_SHADER_LANGUAGE_GLSL
|
||||||
|
return mat3(_0, _1, _2);
|
||||||
|
#else
|
||||||
|
return transpose(mat3(_0, _1, _2) );
|
||||||
|
#endif // BGFX_SHADER_LANGUAGE_GLSL
|
||||||
|
}
|
||||||
|
|
||||||
|
#if BGFX_SHADER_LANGUAGE_GLSL
|
||||||
|
#define mtxFromRows3(_0, _1, _2) transpose(mat3(_0, _1, _2) )
|
||||||
|
#define mtxFromRows4(_0, _1, _2, _3) transpose(mat4(_0, _1, _2, _3) )
|
||||||
|
#define mtxFromCols3(_0, _1, _2) mat3(_0, _1, _2)
|
||||||
|
#define mtxFromCols4(_0, _1, _2, _3) mat4(_0, _1, _2, _3)
|
||||||
|
#else
|
||||||
|
#define mtxFromRows3(_0, _1, _2) mat3(_0, _1, _2)
|
||||||
|
#define mtxFromRows4(_0, _1, _2, _3) mat4(_0, _1, _2, _3)
|
||||||
|
#define mtxFromCols3(_0, _1, _2) transpose(mat3(_0, _1, _2) )
|
||||||
|
#define mtxFromCols4(_0, _1, _2, _3) transpose(mat4(_0, _1, _2, _3) )
|
||||||
|
#endif // BGFX_SHADER_LANGUAGE_GLSL
|
||||||
|
|
||||||
|
uniform vec4 u_viewRect;
|
||||||
|
uniform vec4 u_viewTexel;
|
||||||
|
uniform mat4 u_view;
|
||||||
|
uniform mat4 u_invView;
|
||||||
|
uniform mat4 u_proj;
|
||||||
|
uniform mat4 u_invProj;
|
||||||
|
uniform mat4 u_viewProj;
|
||||||
|
uniform mat4 u_invViewProj;
|
||||||
|
uniform mat4 u_model[BGFX_CONFIG_MAX_BONES];
|
||||||
|
uniform mat4 u_modelView;
|
||||||
|
uniform mat4 u_modelViewProj;
|
||||||
|
uniform vec4 u_alphaRef4;
|
||||||
|
#define u_alphaRef u_alphaRef4.x
|
||||||
|
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
#endif // BGFX_SHADER_H_HEADER_GUARD
|
||||||
103
shaders/cs_cubes.sc
Normal file
103
shaders/cs_cubes.sc
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
|
||||||
|
#include "orientations.sh"
|
||||||
|
#include "bgfx_compute.sh"
|
||||||
|
|
||||||
|
// INPUT
|
||||||
|
// grids (matrices)
|
||||||
|
BUFFER_RO(grids, vec4, 0);
|
||||||
|
// chunks (grid_id, offset_x, _y, _z)
|
||||||
|
BUFFER_RO(chunks, vec4, 1);
|
||||||
|
// blocks (chunk_id, transform [pos in chunk, rotation], idx_buf_offset, num_indices)
|
||||||
|
BUFFER_RO(blocks, vec4, 2);
|
||||||
|
// block selection (visible blocks post-culling)
|
||||||
|
BUFFER_RO(block_selection, float, 3);
|
||||||
|
|
||||||
|
// OUTPUT
|
||||||
|
// indirect draw calls
|
||||||
|
BUFFER_WR(indirectBuffer, uvec4, 4);
|
||||||
|
// matrices for each instance
|
||||||
|
BUFFER_WR(instanceBuffer, vec4, 5);
|
||||||
|
|
||||||
|
uniform vec4 u_cubes_compute_params;
|
||||||
|
|
||||||
|
// Use 64*1*1 local threads
|
||||||
|
NUM_THREADS(64, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
int tId = int(gl_GlobalInvocationID.x);
|
||||||
|
int numDrawItems = int(u_cubes_compute_params.w);
|
||||||
|
|
||||||
|
int numToDrawPerThread = numDrawItems/64 + 1;
|
||||||
|
|
||||||
|
int idxStart = tId*numToDrawPerThread;
|
||||||
|
int idxMax = min(numDrawItems, (tId+1)*numToDrawPerThread);
|
||||||
|
|
||||||
|
for (int k = idxStart; k < idxMax; k++) {
|
||||||
|
uint block_id = block_selection[k];
|
||||||
|
|
||||||
|
// get block data
|
||||||
|
uint b_chunk_id = blocks[block_id].x;
|
||||||
|
uint b_transform = blocks[block_id].y;
|
||||||
|
uint b_index_buf_offset = blocks[block_id].z;
|
||||||
|
uint b_num_indices = blocks[block_id].w;
|
||||||
|
|
||||||
|
// get chunk data
|
||||||
|
uint c_grid_id = chunks[b_chunk_id].x;
|
||||||
|
vec3 c_offset = chunks[b_chunk_id].yzw;
|
||||||
|
|
||||||
|
// get grid data
|
||||||
|
mat4 g_mtx = mtxFromRows(grids[c_grid_id*4 + 0], grids[c_grid_id*4 + 1], grids[c_grid_id*4 + 2], grids[c_grid_id*4 + 3]);
|
||||||
|
|
||||||
|
// calc block offset
|
||||||
|
// b_transform == [off_x | off_y | off_z | orientation]
|
||||||
|
uint b_orientation = b_transform & 0x1F; // 5 bit
|
||||||
|
vec3 b_offset;
|
||||||
|
b_offset.x = (b_transform >> 11) & 0x7; // 3 bit
|
||||||
|
b_offset.y = (b_transform >> 8) & 0x7; // 3 bit
|
||||||
|
b_offset.z = (b_transform >> 5) & 0x7; // 3 bit
|
||||||
|
|
||||||
|
b_offset = b_offset + (c_offset * 8);
|
||||||
|
|
||||||
|
// rotate block -> offset block -> apply g_mtx
|
||||||
|
|
||||||
|
// apply orientation
|
||||||
|
mat3 b_mtx_orientation = orientations[b_orientation];
|
||||||
|
|
||||||
|
// apply offset
|
||||||
|
mat4 b_mtx = mat4(b_mtx_orientation); // Note: glsl matrices are colum major
|
||||||
|
//mat4 b_mtx;
|
||||||
|
//b_mtx[0] = vec4(1,0,0,0);
|
||||||
|
//b_mtx[1] = vec4(0,1,0,0);
|
||||||
|
//b_mtx[2] = vec4(0,0,1,0);
|
||||||
|
b_mtx[3] = vec4(b_offset, 1);
|
||||||
|
|
||||||
|
// apply g_mtx
|
||||||
|
mat4 mtx_model = mul(b_mtx, g_mtx);
|
||||||
|
|
||||||
|
instanceBuffer[k*4+0] = mtx_model[0];
|
||||||
|
instanceBuffer[k*4+1] = mtx_model[1];
|
||||||
|
instanceBuffer[k*4+2] = mtx_model[2];
|
||||||
|
instanceBuffer[k*4+3] = mtx_model[3];
|
||||||
|
|
||||||
|
|
||||||
|
// Fill indirect buffer
|
||||||
|
|
||||||
|
drawIndexedIndirect(
|
||||||
|
// Target location params:
|
||||||
|
indirectBuffer, // target buffer
|
||||||
|
k, // index in buffer
|
||||||
|
// Draw call params:
|
||||||
|
b_num_indices, // number of indices for this draw call
|
||||||
|
1u, // number of instances for this draw call. You can disable this draw call by setting to zero
|
||||||
|
b_index_buf_offset, // offset in the index buffer
|
||||||
|
0, // offset in the vertex buffer. Note that you can use this to "re-index" sub-meshes - all indices in this draw will be decremented by this amount
|
||||||
|
k // offset in the instance buffer. If you are drawing more than 1 instance per call see "gpu driven rendering" for how to handle
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
78
shaders/fs_cubes.sc
Normal file
78
shaders/fs_cubes.sc
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
$input tex_coord, world_pos
|
||||||
|
|
||||||
|
#include "bgfx_shader.sh"
|
||||||
|
#include "shaderlib.sh"
|
||||||
|
|
||||||
|
//layout(early_fragment_tests) in;
|
||||||
|
|
||||||
|
SAMPLER2D(texture_atlas_sampler, 0);
|
||||||
|
|
||||||
|
uniform vec4 u_cubes_compute_params;
|
||||||
|
|
||||||
|
// Ref: https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_reflection_model
|
||||||
|
|
||||||
|
const float lightPower = 40.0;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
|
||||||
|
// vec4 col = texture2D(texture_atlas_sampler, tex_coord);
|
||||||
|
|
||||||
|
// // if(col.r + col.g + col.b == 0.0) discard; // playing with transparency
|
||||||
|
|
||||||
|
|
||||||
|
// vec3 normal = normalize(cross(dFdy(world_pos), dFdx(world_pos))); // screen space surface normal
|
||||||
|
// //col = vec4(normal, 1.0f);
|
||||||
|
|
||||||
|
// const float ambient = 0.2;
|
||||||
|
// const vec3 light_pos = vec3(-5.0, 5.0, -5.0);
|
||||||
|
// const vec3 light_col = vec3(1.0, 1.0, 1.0);
|
||||||
|
// vec3 light_dir = light_pos - world_pos;
|
||||||
|
// float squared_distance = light_dir.x*light_dir.x + light_dir.y*light_dir.y + light_dir.z*light_dir.z;
|
||||||
|
// light_dir = normalize(light_dir);
|
||||||
|
|
||||||
|
// vec4 diffuse = vec4(max(dot(light_dir, normal), 0) * light_col, 1.0);
|
||||||
|
// col = col * ambient + col * diffuse;
|
||||||
|
|
||||||
|
// // Note: if gl_FrontFacing is making problems
|
||||||
|
// // => https://stackoverflow.com/questions/24375171/is-there-a-reliable-alternative-to-gl-frontfacing-in-a-fragment-shader
|
||||||
|
// //if (gl_FrontFacing) {
|
||||||
|
// // col = vec4(1.0,0.0,0.0,1.0);
|
||||||
|
// //}else{
|
||||||
|
// // col = vec4(0.0,1.0,0.0,1.0);
|
||||||
|
// //}
|
||||||
|
|
||||||
|
|
||||||
|
// Calc id_coords (in id_atlas, for id sampling)
|
||||||
|
// in [0;ID_SIZE], is basically int
|
||||||
|
// id_coord = floor(id_coord);
|
||||||
|
// id_coord = id_coord +
|
||||||
|
// TODO use modf and combine floor/fract
|
||||||
|
// ! flooring may not be needed anyway
|
||||||
|
// just sample id_atlas with tex_coord
|
||||||
|
|
||||||
|
// Texturing rework V2
|
||||||
|
const uint IA_WIDTH = 2*8; // id's per row
|
||||||
|
const uint IA_HEIGHT = 2*8; // id's per column
|
||||||
|
const float ID_STRIDE_X = 1.0f / IA_WIDTH;
|
||||||
|
const float ID_STRIDE_Y = 1.0f / IA_HEIGHT;
|
||||||
|
|
||||||
|
// Calc tex_coords (in tex_atlas, for texture sampling)
|
||||||
|
// in [0;1] in local texture space
|
||||||
|
tex_coord = tex_coord * vec2(IA_WIDTH, IA_HEIGHT);
|
||||||
|
tex_coord = fract(tex_coord);
|
||||||
|
|
||||||
|
// vec4 col;
|
||||||
|
// if (tex_coord.x < 0.5)
|
||||||
|
// {
|
||||||
|
// col = vec4(1.0, 0.0, 0.0, 1.0);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// col = vec4(0.0, 1.0, 0.0, 1.0);
|
||||||
|
// }
|
||||||
|
|
||||||
|
vec4 col = vec4(tex_coord, 0.0, 1.0);
|
||||||
|
|
||||||
|
gl_FragColor = col;
|
||||||
|
}
|
||||||
9
shaders/fs_lines.sc
Normal file
9
shaders/fs_lines.sc
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#include "bgfx_shader.sh"
|
||||||
|
#include "shaderlib.sh"
|
||||||
|
|
||||||
|
uniform vec4 u_line_color;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_FragColor = u_line_color;
|
||||||
|
}
|
||||||
27
shaders/orientations.sh
Normal file
27
shaders/orientations.sh
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
const mat3 orientations[24] =
|
||||||
|
{
|
||||||
|
{{1.000000, 0.000000, 0.000000}, {0.000000, 1.000000, 0.000000}, {0.000000, 0.000000, 1.000000}},
|
||||||
|
{{0.000000, 1.000000, 0.000000}, {-1.000000, 0.000000, 0.000000}, {0.000000, 0.000000, 1.000000}},
|
||||||
|
{{-1.000000, 0.000000, 0.000000}, {0.000000, -1.000000, 0.000000}, {0.000000, 0.000000, 1.000000}},
|
||||||
|
{{0.000000, -1.000000, 0.000000}, {1.000000, 0.000000, 0.000000}, {0.000000, 0.000000, 1.000000}},
|
||||||
|
{{0.000000, 0.000000, -1.000000}, {0.000000, 1.000000, 0.000000}, {1.000000, 0.000000, 0.000000}},
|
||||||
|
{{0.000000, 1.000000, 0.000000}, {0.000000, 0.000000, 1.000000}, {1.000000, 0.000000, 0.000000}},
|
||||||
|
{{0.000000, 0.000000, 1.000000}, {0.000000, -1.000000, 0.000000}, {1.000000, 0.000000, 0.000000}},
|
||||||
|
{{0.000000, -1.000000, 0.000000}, {0.000000, 0.000000, -1.000000}, {1.000000, 0.000000, 0.000000}},
|
||||||
|
{{-1.000000, 0.000000, 0.000000}, {0.000000, 1.000000, 0.000000}, {0.000000, 0.000000, -1.000000}},
|
||||||
|
{{0.000000, 1.000000, 0.000000}, {1.000000, 0.000000, 0.000000}, {0.000000, 0.000000, -1.000000}},
|
||||||
|
{{1.000000, 0.000000, 0.000000}, {0.000000, -1.000000, 0.000000}, {0.000000, 0.000000, -1.000000}},
|
||||||
|
{{0.000000, -1.000000, 0.000000}, {-1.000000, 0.000000, 0.000000}, {0.000000, 0.000000, -1.000000}},
|
||||||
|
{{0.000000, 0.000000, 1.000000}, {0.000000, 1.000000, 0.000000}, {-1.000000, 0.000000, 0.000000}},
|
||||||
|
{{0.000000, 1.000000, 0.000000}, {0.000000, 0.000000, -1.000000}, {-1.000000, 0.000000, 0.000000}},
|
||||||
|
{{0.000000, 0.000000, -1.000000}, {0.000000, -1.000000, 0.000000}, {-1.000000, 0.000000, 0.000000}},
|
||||||
|
{{0.000000, -1.000000, 0.000000}, {0.000000, 0.000000, 1.000000}, {-1.000000, 0.000000, 0.000000}},
|
||||||
|
{{1.000000, 0.000000, 0.000000}, {0.000000, 0.000000, 1.000000}, {0.000000, -1.000000, 0.000000}},
|
||||||
|
{{0.000000, 0.000000, 1.000000}, {-1.000000, 0.000000, 0.000000}, {0.000000, -1.000000, 0.000000}},
|
||||||
|
{{-1.000000, 0.000000, 0.000000}, {0.000000, 0.000000, -1.000000}, {0.000000, -1.000000, 0.000000}},
|
||||||
|
{{0.000000, 0.000000, -1.000000}, {1.000000, 0.000000, 0.000000}, {0.000000, -1.000000, 0.000000}},
|
||||||
|
{{1.000000, 0.000000, 0.000000}, {0.000000, 0.000000, -1.000000}, {0.000000, 1.000000, 0.000000}},
|
||||||
|
{{0.000000, 0.000000, -1.000000}, {-1.000000, 0.000000, 0.000000}, {0.000000, 1.000000, 0.000000}},
|
||||||
|
{{-1.000000, 0.000000, 0.000000}, {0.000000, 0.000000, 1.000000}, {0.000000, 1.000000, 0.000000}},
|
||||||
|
{{0.000000, 0.000000, 1.000000}, {1.000000, 0.000000, 0.000000}, {0.000000, 1.000000, 0.000000}},
|
||||||
|
};
|
||||||
431
shaders/shaderlib.sh
Normal file
431
shaders/shaderlib.sh
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011-2022 Branimir Karadzic. All rights reserved.
|
||||||
|
* License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __SHADERLIB_SH__
|
||||||
|
#define __SHADERLIB_SH__
|
||||||
|
|
||||||
|
vec4 encodeRE8(float _r)
|
||||||
|
{
|
||||||
|
float exponent = ceil(log2(_r) );
|
||||||
|
return vec4(_r / exp2(exponent)
|
||||||
|
, 0.0
|
||||||
|
, 0.0
|
||||||
|
, (exponent + 128.0) / 255.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
float decodeRE8(vec4 _re8)
|
||||||
|
{
|
||||||
|
float exponent = _re8.w * 255.0 - 128.0;
|
||||||
|
return _re8.x * exp2(exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 encodeRGBE8(vec3 _rgb)
|
||||||
|
{
|
||||||
|
vec4 rgbe8;
|
||||||
|
float maxComponent = max(max(_rgb.x, _rgb.y), _rgb.z);
|
||||||
|
float exponent = ceil(log2(maxComponent) );
|
||||||
|
rgbe8.xyz = _rgb / exp2(exponent);
|
||||||
|
rgbe8.w = (exponent + 128.0) / 255.0;
|
||||||
|
return rgbe8;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 decodeRGBE8(vec4 _rgbe8)
|
||||||
|
{
|
||||||
|
float exponent = _rgbe8.w * 255.0 - 128.0;
|
||||||
|
vec3 rgb = _rgbe8.xyz * exp2(exponent);
|
||||||
|
return rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 encodeNormalUint(vec3 _normal)
|
||||||
|
{
|
||||||
|
return _normal * 0.5 + 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 decodeNormalUint(vec3 _encodedNormal)
|
||||||
|
{
|
||||||
|
return _encodedNormal * 2.0 - 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 encodeNormalSphereMap(vec3 _normal)
|
||||||
|
{
|
||||||
|
return normalize(_normal.xy) * sqrt(_normal.z * 0.5 + 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 decodeNormalSphereMap(vec2 _encodedNormal)
|
||||||
|
{
|
||||||
|
float zz = dot(_encodedNormal, _encodedNormal) * 2.0 - 1.0;
|
||||||
|
return vec3(normalize(_encodedNormal.xy) * sqrt(1.0 - zz*zz), zz);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 octahedronWrap(vec2 _val)
|
||||||
|
{
|
||||||
|
// Reference(s):
|
||||||
|
// - Octahedron normal vector encoding
|
||||||
|
// https://web.archive.org/web/20191027010600/https://knarkowicz.wordpress.com/2014/04/16/octahedron-normal-vector-encoding/comment-page-1/
|
||||||
|
return (1.0 - abs(_val.yx) )
|
||||||
|
* mix(vec2_splat(-1.0), vec2_splat(1.0), vec2(greaterThanEqual(_val.xy, vec2_splat(0.0) ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 encodeNormalOctahedron(vec3 _normal)
|
||||||
|
{
|
||||||
|
_normal /= abs(_normal.x) + abs(_normal.y) + abs(_normal.z);
|
||||||
|
_normal.xy = _normal.z >= 0.0 ? _normal.xy : octahedronWrap(_normal.xy);
|
||||||
|
_normal.xy = _normal.xy * 0.5 + 0.5;
|
||||||
|
return _normal.xy;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 decodeNormalOctahedron(vec2 _encodedNormal)
|
||||||
|
{
|
||||||
|
_encodedNormal = _encodedNormal * 2.0 - 1.0;
|
||||||
|
|
||||||
|
vec3 normal;
|
||||||
|
normal.z = 1.0 - abs(_encodedNormal.x) - abs(_encodedNormal.y);
|
||||||
|
normal.xy = normal.z >= 0.0 ? _encodedNormal.xy : octahedronWrap(_encodedNormal.xy);
|
||||||
|
return normalize(normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 convertRGB2XYZ(vec3 _rgb)
|
||||||
|
{
|
||||||
|
// Reference(s):
|
||||||
|
// - RGB/XYZ Matrices
|
||||||
|
// https://web.archive.org/web/20191027010220/http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
|
||||||
|
vec3 xyz;
|
||||||
|
xyz.x = dot(vec3(0.4124564, 0.3575761, 0.1804375), _rgb);
|
||||||
|
xyz.y = dot(vec3(0.2126729, 0.7151522, 0.0721750), _rgb);
|
||||||
|
xyz.z = dot(vec3(0.0193339, 0.1191920, 0.9503041), _rgb);
|
||||||
|
return xyz;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 convertXYZ2RGB(vec3 _xyz)
|
||||||
|
{
|
||||||
|
vec3 rgb;
|
||||||
|
rgb.x = dot(vec3( 3.2404542, -1.5371385, -0.4985314), _xyz);
|
||||||
|
rgb.y = dot(vec3(-0.9692660, 1.8760108, 0.0415560), _xyz);
|
||||||
|
rgb.z = dot(vec3( 0.0556434, -0.2040259, 1.0572252), _xyz);
|
||||||
|
return rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 convertXYZ2Yxy(vec3 _xyz)
|
||||||
|
{
|
||||||
|
// Reference(s):
|
||||||
|
// - XYZ to xyY
|
||||||
|
// https://web.archive.org/web/20191027010144/http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_xyY.html
|
||||||
|
float inv = 1.0/dot(_xyz, vec3(1.0, 1.0, 1.0) );
|
||||||
|
return vec3(_xyz.y, _xyz.x*inv, _xyz.y*inv);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 convertYxy2XYZ(vec3 _Yxy)
|
||||||
|
{
|
||||||
|
// Reference(s):
|
||||||
|
// - xyY to XYZ
|
||||||
|
// https://web.archive.org/web/20191027010036/http://www.brucelindbloom.com/index.html?Eqn_xyY_to_XYZ.html
|
||||||
|
vec3 xyz;
|
||||||
|
xyz.x = _Yxy.x*_Yxy.y/_Yxy.z;
|
||||||
|
xyz.y = _Yxy.x;
|
||||||
|
xyz.z = _Yxy.x*(1.0 - _Yxy.y - _Yxy.z)/_Yxy.z;
|
||||||
|
return xyz;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 convertRGB2Yxy(vec3 _rgb)
|
||||||
|
{
|
||||||
|
return convertXYZ2Yxy(convertRGB2XYZ(_rgb) );
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 convertYxy2RGB(vec3 _Yxy)
|
||||||
|
{
|
||||||
|
return convertXYZ2RGB(convertYxy2XYZ(_Yxy) );
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 convertRGB2Yuv(vec3 _rgb)
|
||||||
|
{
|
||||||
|
vec3 yuv;
|
||||||
|
yuv.x = dot(_rgb, vec3(0.299, 0.587, 0.114) );
|
||||||
|
yuv.y = (_rgb.x - yuv.x)*0.713 + 0.5;
|
||||||
|
yuv.z = (_rgb.z - yuv.x)*0.564 + 0.5;
|
||||||
|
return yuv;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 convertYuv2RGB(vec3 _yuv)
|
||||||
|
{
|
||||||
|
vec3 rgb;
|
||||||
|
rgb.x = _yuv.x + 1.403*(_yuv.y-0.5);
|
||||||
|
rgb.y = _yuv.x - 0.344*(_yuv.y-0.5) - 0.714*(_yuv.z-0.5);
|
||||||
|
rgb.z = _yuv.x + 1.773*(_yuv.z-0.5);
|
||||||
|
return rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 convertRGB2YIQ(vec3 _rgb)
|
||||||
|
{
|
||||||
|
vec3 yiq;
|
||||||
|
yiq.x = dot(vec3(0.299, 0.587, 0.114 ), _rgb);
|
||||||
|
yiq.y = dot(vec3(0.595716, -0.274453, -0.321263), _rgb);
|
||||||
|
yiq.z = dot(vec3(0.211456, -0.522591, 0.311135), _rgb);
|
||||||
|
return yiq;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 convertYIQ2RGB(vec3 _yiq)
|
||||||
|
{
|
||||||
|
vec3 rgb;
|
||||||
|
rgb.x = dot(vec3(1.0, 0.9563, 0.6210), _yiq);
|
||||||
|
rgb.y = dot(vec3(1.0, -0.2721, -0.6474), _yiq);
|
||||||
|
rgb.z = dot(vec3(1.0, -1.1070, 1.7046), _yiq);
|
||||||
|
return rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 toLinear(vec3 _rgb)
|
||||||
|
{
|
||||||
|
return pow(abs(_rgb), vec3_splat(2.2) );
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 toLinear(vec4 _rgba)
|
||||||
|
{
|
||||||
|
return vec4(toLinear(_rgba.xyz), _rgba.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 toLinearAccurate(vec3 _rgb)
|
||||||
|
{
|
||||||
|
vec3 lo = _rgb / 12.92;
|
||||||
|
vec3 hi = pow( (_rgb + 0.055) / 1.055, vec3_splat(2.4) );
|
||||||
|
vec3 rgb = mix(hi, lo, vec3(lessThanEqual(_rgb, vec3_splat(0.04045) ) ) );
|
||||||
|
return rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 toLinearAccurate(vec4 _rgba)
|
||||||
|
{
|
||||||
|
return vec4(toLinearAccurate(_rgba.xyz), _rgba.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
float toGamma(float _r)
|
||||||
|
{
|
||||||
|
return pow(abs(_r), 1.0/2.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 toGamma(vec3 _rgb)
|
||||||
|
{
|
||||||
|
return pow(abs(_rgb), vec3_splat(1.0/2.2) );
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 toGamma(vec4 _rgba)
|
||||||
|
{
|
||||||
|
return vec4(toGamma(_rgba.xyz), _rgba.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 toGammaAccurate(vec3 _rgb)
|
||||||
|
{
|
||||||
|
vec3 lo = _rgb * 12.92;
|
||||||
|
vec3 hi = pow(abs(_rgb), vec3_splat(1.0/2.4) ) * 1.055 - 0.055;
|
||||||
|
vec3 rgb = mix(hi, lo, vec3(lessThanEqual(_rgb, vec3_splat(0.0031308) ) ) );
|
||||||
|
return rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 toGammaAccurate(vec4 _rgba)
|
||||||
|
{
|
||||||
|
return vec4(toGammaAccurate(_rgba.xyz), _rgba.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 toReinhard(vec3 _rgb)
|
||||||
|
{
|
||||||
|
return toGamma(_rgb/(_rgb+vec3_splat(1.0) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 toReinhard(vec4 _rgba)
|
||||||
|
{
|
||||||
|
return vec4(toReinhard(_rgba.xyz), _rgba.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 toFilmic(vec3 _rgb)
|
||||||
|
{
|
||||||
|
_rgb = max(vec3_splat(0.0), _rgb - 0.004);
|
||||||
|
_rgb = (_rgb*(6.2*_rgb + 0.5) ) / (_rgb*(6.2*_rgb + 1.7) + 0.06);
|
||||||
|
return _rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 toFilmic(vec4 _rgba)
|
||||||
|
{
|
||||||
|
return vec4(toFilmic(_rgba.xyz), _rgba.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 toAcesFilmic(vec3 _rgb)
|
||||||
|
{
|
||||||
|
// Reference(s):
|
||||||
|
// - ACES Filmic Tone Mapping Curve
|
||||||
|
// https://web.archive.org/web/20191027010704/https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/
|
||||||
|
float aa = 2.51f;
|
||||||
|
float bb = 0.03f;
|
||||||
|
float cc = 2.43f;
|
||||||
|
float dd = 0.59f;
|
||||||
|
float ee = 0.14f;
|
||||||
|
return saturate( (_rgb*(aa*_rgb + bb) )/(_rgb*(cc*_rgb + dd) + ee) );
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 toAcesFilmic(vec4 _rgba)
|
||||||
|
{
|
||||||
|
return vec4(toAcesFilmic(_rgba.xyz), _rgba.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 luma(vec3 _rgb)
|
||||||
|
{
|
||||||
|
float yy = dot(vec3(0.2126729, 0.7151522, 0.0721750), _rgb);
|
||||||
|
return vec3_splat(yy);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 luma(vec4 _rgba)
|
||||||
|
{
|
||||||
|
return vec4(luma(_rgba.xyz), _rgba.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 conSatBri(vec3 _rgb, vec3 _csb)
|
||||||
|
{
|
||||||
|
vec3 rgb = _rgb * _csb.z;
|
||||||
|
rgb = mix(luma(rgb), rgb, _csb.y);
|
||||||
|
rgb = mix(vec3_splat(0.5), rgb, _csb.x);
|
||||||
|
return rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 conSatBri(vec4 _rgba, vec3 _csb)
|
||||||
|
{
|
||||||
|
return vec4(conSatBri(_rgba.xyz, _csb), _rgba.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 posterize(vec3 _rgb, float _numColors)
|
||||||
|
{
|
||||||
|
return floor(_rgb*_numColors) / _numColors;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 posterize(vec4 _rgba, float _numColors)
|
||||||
|
{
|
||||||
|
return vec4(posterize(_rgba.xyz, _numColors), _rgba.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 sepia(vec3 _rgb)
|
||||||
|
{
|
||||||
|
vec3 color;
|
||||||
|
color.x = dot(_rgb, vec3(0.393, 0.769, 0.189) );
|
||||||
|
color.y = dot(_rgb, vec3(0.349, 0.686, 0.168) );
|
||||||
|
color.z = dot(_rgb, vec3(0.272, 0.534, 0.131) );
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 sepia(vec4 _rgba)
|
||||||
|
{
|
||||||
|
return vec4(sepia(_rgba.xyz), _rgba.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 blendOverlay(vec3 _base, vec3 _blend)
|
||||||
|
{
|
||||||
|
vec3 lt = 2.0 * _base * _blend;
|
||||||
|
vec3 gte = 1.0 - 2.0 * (1.0 - _base) * (1.0 - _blend);
|
||||||
|
return mix(lt, gte, step(vec3_splat(0.5), _base) );
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 blendOverlay(vec4 _base, vec4 _blend)
|
||||||
|
{
|
||||||
|
return vec4(blendOverlay(_base.xyz, _blend.xyz), _base.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 adjustHue(vec3 _rgb, float _hue)
|
||||||
|
{
|
||||||
|
vec3 yiq = convertRGB2YIQ(_rgb);
|
||||||
|
float angle = _hue + atan2(yiq.z, yiq.y);
|
||||||
|
float len = length(yiq.yz);
|
||||||
|
return convertYIQ2RGB(vec3(yiq.x, len*cos(angle), len*sin(angle) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 packFloatToRgba(float _value)
|
||||||
|
{
|
||||||
|
const vec4 shift = vec4(256 * 256 * 256, 256 * 256, 256, 1.0);
|
||||||
|
const vec4 mask = vec4(0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);
|
||||||
|
vec4 comp = fract(_value * shift);
|
||||||
|
comp -= comp.xxyz * mask;
|
||||||
|
return comp;
|
||||||
|
}
|
||||||
|
|
||||||
|
float unpackRgbaToFloat(vec4 _rgba)
|
||||||
|
{
|
||||||
|
const vec4 shift = vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0);
|
||||||
|
return dot(_rgba, shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 packHalfFloat(float _value)
|
||||||
|
{
|
||||||
|
const vec2 shift = vec2(256, 1.0);
|
||||||
|
const vec2 mask = vec2(0, 1.0 / 256.0);
|
||||||
|
vec2 comp = fract(_value * shift);
|
||||||
|
comp -= comp.xx * mask;
|
||||||
|
return comp;
|
||||||
|
}
|
||||||
|
|
||||||
|
float unpackHalfFloat(vec2 _rg)
|
||||||
|
{
|
||||||
|
const vec2 shift = vec2(1.0 / 256.0, 1.0);
|
||||||
|
return dot(_rg, shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
float random(vec2 _uv)
|
||||||
|
{
|
||||||
|
return fract(sin(dot(_uv.xy, vec2(12.9898, 78.233) ) ) * 43758.5453);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 fixCubeLookup(vec3 _v, float _lod, float _topLevelCubeSize)
|
||||||
|
{
|
||||||
|
// Reference(s):
|
||||||
|
// - Seamless cube-map filtering
|
||||||
|
// https://web.archive.org/web/20190411181934/http://the-witness.net/news/2012/02/seamless-cube-map-filtering/
|
||||||
|
float ax = abs(_v.x);
|
||||||
|
float ay = abs(_v.y);
|
||||||
|
float az = abs(_v.z);
|
||||||
|
float vmax = max(max(ax, ay), az);
|
||||||
|
float scale = 1.0 - exp2(_lod) / _topLevelCubeSize;
|
||||||
|
if (ax != vmax) { _v.x *= scale; }
|
||||||
|
if (ay != vmax) { _v.y *= scale; }
|
||||||
|
if (az != vmax) { _v.z *= scale; }
|
||||||
|
return _v;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 texture2DBc5(sampler2D _sampler, vec2 _uv)
|
||||||
|
{
|
||||||
|
#if BGFX_SHADER_LANGUAGE_HLSL && BGFX_SHADER_LANGUAGE_HLSL <= 300
|
||||||
|
return texture2D(_sampler, _uv).yx;
|
||||||
|
#else
|
||||||
|
return texture2D(_sampler, _uv).xy;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
mat3 cofactor(mat4 _m)
|
||||||
|
{
|
||||||
|
// Reference:
|
||||||
|
// Cofactor of matrix. Use to transform normals. The code assumes the last column of _m is [0,0,0,1].
|
||||||
|
// https://www.shadertoy.com/view/3s33zj
|
||||||
|
// https://github.com/graphitemaster/normals_revisited
|
||||||
|
return mat3(
|
||||||
|
_m[1][1]*_m[2][2]-_m[1][2]*_m[2][1],
|
||||||
|
_m[1][2]*_m[2][0]-_m[1][0]*_m[2][2],
|
||||||
|
_m[1][0]*_m[2][1]-_m[1][1]*_m[2][0],
|
||||||
|
_m[0][2]*_m[2][1]-_m[0][1]*_m[2][2],
|
||||||
|
_m[0][0]*_m[2][2]-_m[0][2]*_m[2][0],
|
||||||
|
_m[0][1]*_m[2][0]-_m[0][0]*_m[2][1],
|
||||||
|
_m[0][1]*_m[1][2]-_m[0][2]*_m[1][1],
|
||||||
|
_m[0][2]*_m[1][0]-_m[0][0]*_m[1][2],
|
||||||
|
_m[0][0]*_m[1][1]-_m[0][1]*_m[1][0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
float toClipSpaceDepth(float _depthTextureZ)
|
||||||
|
{
|
||||||
|
#if BGFX_SHADER_LANGUAGE_GLSL
|
||||||
|
return _depthTextureZ * 2.0 - 1.0;
|
||||||
|
#else
|
||||||
|
return _depthTextureZ;
|
||||||
|
#endif // BGFX_SHADER_LANGUAGE_GLSL
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 clipToWorld(mat4 _invViewProj, vec3 _clipPos)
|
||||||
|
{
|
||||||
|
vec4 wpos = mul(_invViewProj, vec4(_clipPos, 1.0) );
|
||||||
|
return wpos.xyz / wpos.w;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __SHADERLIB_SH__
|
||||||
6
shaders/varying_cubes.sc
Normal file
6
shaders/varying_cubes.sc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
vec2 tex_coord : TEXCOORD0 = vec2(0.0, 0.0);
|
||||||
|
vec3 world_pos : TEXCOORD1 = vec3(0.0, 0.0, 0.0);
|
||||||
|
vec4 i_data0 : TEXCOORD7;
|
||||||
|
vec4 i_data1 : TEXCOORD6;
|
||||||
|
vec4 i_data2 : TEXCOORD5;
|
||||||
|
vec4 i_data3 : TEXCOORD4;
|
||||||
1
shaders/varying_lines.sc
Normal file
1
shaders/varying_lines.sc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
vec3 a_position : POSITION;
|
||||||
732
shaders/verts.sh
Normal file
732
shaders/verts.sh
Normal file
@@ -0,0 +1,732 @@
|
|||||||
|
const vec3 verts[729] =
|
||||||
|
{
|
||||||
|
{-0.500000, -0.500000, -0.500000},
|
||||||
|
{-0.375000, -0.500000, -0.500000},
|
||||||
|
{-0.250000, -0.500000, -0.500000},
|
||||||
|
{-0.125000, -0.500000, -0.500000},
|
||||||
|
{0.000000, -0.500000, -0.500000},
|
||||||
|
{0.125000, -0.500000, -0.500000},
|
||||||
|
{0.250000, -0.500000, -0.500000},
|
||||||
|
{0.375000, -0.500000, -0.500000},
|
||||||
|
{0.500000, -0.500000, -0.500000},
|
||||||
|
{-0.500000, -0.375000, -0.500000},
|
||||||
|
{-0.375000, -0.375000, -0.500000},
|
||||||
|
{-0.250000, -0.375000, -0.500000},
|
||||||
|
{-0.125000, -0.375000, -0.500000},
|
||||||
|
{0.000000, -0.375000, -0.500000},
|
||||||
|
{0.125000, -0.375000, -0.500000},
|
||||||
|
{0.250000, -0.375000, -0.500000},
|
||||||
|
{0.375000, -0.375000, -0.500000},
|
||||||
|
{0.500000, -0.375000, -0.500000},
|
||||||
|
{-0.500000, -0.250000, -0.500000},
|
||||||
|
{-0.375000, -0.250000, -0.500000},
|
||||||
|
{-0.250000, -0.250000, -0.500000},
|
||||||
|
{-0.125000, -0.250000, -0.500000},
|
||||||
|
{0.000000, -0.250000, -0.500000},
|
||||||
|
{0.125000, -0.250000, -0.500000},
|
||||||
|
{0.250000, -0.250000, -0.500000},
|
||||||
|
{0.375000, -0.250000, -0.500000},
|
||||||
|
{0.500000, -0.250000, -0.500000},
|
||||||
|
{-0.500000, -0.125000, -0.500000},
|
||||||
|
{-0.375000, -0.125000, -0.500000},
|
||||||
|
{-0.250000, -0.125000, -0.500000},
|
||||||
|
{-0.125000, -0.125000, -0.500000},
|
||||||
|
{0.000000, -0.125000, -0.500000},
|
||||||
|
{0.125000, -0.125000, -0.500000},
|
||||||
|
{0.250000, -0.125000, -0.500000},
|
||||||
|
{0.375000, -0.125000, -0.500000},
|
||||||
|
{0.500000, -0.125000, -0.500000},
|
||||||
|
{-0.500000, 0.000000, -0.500000},
|
||||||
|
{-0.375000, 0.000000, -0.500000},
|
||||||
|
{-0.250000, 0.000000, -0.500000},
|
||||||
|
{-0.125000, 0.000000, -0.500000},
|
||||||
|
{0.000000, 0.000000, -0.500000},
|
||||||
|
{0.125000, 0.000000, -0.500000},
|
||||||
|
{0.250000, 0.000000, -0.500000},
|
||||||
|
{0.375000, 0.000000, -0.500000},
|
||||||
|
{0.500000, 0.000000, -0.500000},
|
||||||
|
{-0.500000, 0.125000, -0.500000},
|
||||||
|
{-0.375000, 0.125000, -0.500000},
|
||||||
|
{-0.250000, 0.125000, -0.500000},
|
||||||
|
{-0.125000, 0.125000, -0.500000},
|
||||||
|
{0.000000, 0.125000, -0.500000},
|
||||||
|
{0.125000, 0.125000, -0.500000},
|
||||||
|
{0.250000, 0.125000, -0.500000},
|
||||||
|
{0.375000, 0.125000, -0.500000},
|
||||||
|
{0.500000, 0.125000, -0.500000},
|
||||||
|
{-0.500000, 0.250000, -0.500000},
|
||||||
|
{-0.375000, 0.250000, -0.500000},
|
||||||
|
{-0.250000, 0.250000, -0.500000},
|
||||||
|
{-0.125000, 0.250000, -0.500000},
|
||||||
|
{0.000000, 0.250000, -0.500000},
|
||||||
|
{0.125000, 0.250000, -0.500000},
|
||||||
|
{0.250000, 0.250000, -0.500000},
|
||||||
|
{0.375000, 0.250000, -0.500000},
|
||||||
|
{0.500000, 0.250000, -0.500000},
|
||||||
|
{-0.500000, 0.375000, -0.500000},
|
||||||
|
{-0.375000, 0.375000, -0.500000},
|
||||||
|
{-0.250000, 0.375000, -0.500000},
|
||||||
|
{-0.125000, 0.375000, -0.500000},
|
||||||
|
{0.000000, 0.375000, -0.500000},
|
||||||
|
{0.125000, 0.375000, -0.500000},
|
||||||
|
{0.250000, 0.375000, -0.500000},
|
||||||
|
{0.375000, 0.375000, -0.500000},
|
||||||
|
{0.500000, 0.375000, -0.500000},
|
||||||
|
{-0.500000, 0.500000, -0.500000},
|
||||||
|
{-0.375000, 0.500000, -0.500000},
|
||||||
|
{-0.250000, 0.500000, -0.500000},
|
||||||
|
{-0.125000, 0.500000, -0.500000},
|
||||||
|
{0.000000, 0.500000, -0.500000},
|
||||||
|
{0.125000, 0.500000, -0.500000},
|
||||||
|
{0.250000, 0.500000, -0.500000},
|
||||||
|
{0.375000, 0.500000, -0.500000},
|
||||||
|
{0.500000, 0.500000, -0.500000},
|
||||||
|
{-0.500000, -0.500000, -0.375000},
|
||||||
|
{-0.375000, -0.500000, -0.375000},
|
||||||
|
{-0.250000, -0.500000, -0.375000},
|
||||||
|
{-0.125000, -0.500000, -0.375000},
|
||||||
|
{0.000000, -0.500000, -0.375000},
|
||||||
|
{0.125000, -0.500000, -0.375000},
|
||||||
|
{0.250000, -0.500000, -0.375000},
|
||||||
|
{0.375000, -0.500000, -0.375000},
|
||||||
|
{0.500000, -0.500000, -0.375000},
|
||||||
|
{-0.500000, -0.375000, -0.375000},
|
||||||
|
{-0.375000, -0.375000, -0.375000},
|
||||||
|
{-0.250000, -0.375000, -0.375000},
|
||||||
|
{-0.125000, -0.375000, -0.375000},
|
||||||
|
{0.000000, -0.375000, -0.375000},
|
||||||
|
{0.125000, -0.375000, -0.375000},
|
||||||
|
{0.250000, -0.375000, -0.375000},
|
||||||
|
{0.375000, -0.375000, -0.375000},
|
||||||
|
{0.500000, -0.375000, -0.375000},
|
||||||
|
{-0.500000, -0.250000, -0.375000},
|
||||||
|
{-0.375000, -0.250000, -0.375000},
|
||||||
|
{-0.250000, -0.250000, -0.375000},
|
||||||
|
{-0.125000, -0.250000, -0.375000},
|
||||||
|
{0.000000, -0.250000, -0.375000},
|
||||||
|
{0.125000, -0.250000, -0.375000},
|
||||||
|
{0.250000, -0.250000, -0.375000},
|
||||||
|
{0.375000, -0.250000, -0.375000},
|
||||||
|
{0.500000, -0.250000, -0.375000},
|
||||||
|
{-0.500000, -0.125000, -0.375000},
|
||||||
|
{-0.375000, -0.125000, -0.375000},
|
||||||
|
{-0.250000, -0.125000, -0.375000},
|
||||||
|
{-0.125000, -0.125000, -0.375000},
|
||||||
|
{0.000000, -0.125000, -0.375000},
|
||||||
|
{0.125000, -0.125000, -0.375000},
|
||||||
|
{0.250000, -0.125000, -0.375000},
|
||||||
|
{0.375000, -0.125000, -0.375000},
|
||||||
|
{0.500000, -0.125000, -0.375000},
|
||||||
|
{-0.500000, 0.000000, -0.375000},
|
||||||
|
{-0.375000, 0.000000, -0.375000},
|
||||||
|
{-0.250000, 0.000000, -0.375000},
|
||||||
|
{-0.125000, 0.000000, -0.375000},
|
||||||
|
{0.000000, 0.000000, -0.375000},
|
||||||
|
{0.125000, 0.000000, -0.375000},
|
||||||
|
{0.250000, 0.000000, -0.375000},
|
||||||
|
{0.375000, 0.000000, -0.375000},
|
||||||
|
{0.500000, 0.000000, -0.375000},
|
||||||
|
{-0.500000, 0.125000, -0.375000},
|
||||||
|
{-0.375000, 0.125000, -0.375000},
|
||||||
|
{-0.250000, 0.125000, -0.375000},
|
||||||
|
{-0.125000, 0.125000, -0.375000},
|
||||||
|
{0.000000, 0.125000, -0.375000},
|
||||||
|
{0.125000, 0.125000, -0.375000},
|
||||||
|
{0.250000, 0.125000, -0.375000},
|
||||||
|
{0.375000, 0.125000, -0.375000},
|
||||||
|
{0.500000, 0.125000, -0.375000},
|
||||||
|
{-0.500000, 0.250000, -0.375000},
|
||||||
|
{-0.375000, 0.250000, -0.375000},
|
||||||
|
{-0.250000, 0.250000, -0.375000},
|
||||||
|
{-0.125000, 0.250000, -0.375000},
|
||||||
|
{0.000000, 0.250000, -0.375000},
|
||||||
|
{0.125000, 0.250000, -0.375000},
|
||||||
|
{0.250000, 0.250000, -0.375000},
|
||||||
|
{0.375000, 0.250000, -0.375000},
|
||||||
|
{0.500000, 0.250000, -0.375000},
|
||||||
|
{-0.500000, 0.375000, -0.375000},
|
||||||
|
{-0.375000, 0.375000, -0.375000},
|
||||||
|
{-0.250000, 0.375000, -0.375000},
|
||||||
|
{-0.125000, 0.375000, -0.375000},
|
||||||
|
{0.000000, 0.375000, -0.375000},
|
||||||
|
{0.125000, 0.375000, -0.375000},
|
||||||
|
{0.250000, 0.375000, -0.375000},
|
||||||
|
{0.375000, 0.375000, -0.375000},
|
||||||
|
{0.500000, 0.375000, -0.375000},
|
||||||
|
{-0.500000, 0.500000, -0.375000},
|
||||||
|
{-0.375000, 0.500000, -0.375000},
|
||||||
|
{-0.250000, 0.500000, -0.375000},
|
||||||
|
{-0.125000, 0.500000, -0.375000},
|
||||||
|
{0.000000, 0.500000, -0.375000},
|
||||||
|
{0.125000, 0.500000, -0.375000},
|
||||||
|
{0.250000, 0.500000, -0.375000},
|
||||||
|
{0.375000, 0.500000, -0.375000},
|
||||||
|
{0.500000, 0.500000, -0.375000},
|
||||||
|
{-0.500000, -0.500000, -0.250000},
|
||||||
|
{-0.375000, -0.500000, -0.250000},
|
||||||
|
{-0.250000, -0.500000, -0.250000},
|
||||||
|
{-0.125000, -0.500000, -0.250000},
|
||||||
|
{0.000000, -0.500000, -0.250000},
|
||||||
|
{0.125000, -0.500000, -0.250000},
|
||||||
|
{0.250000, -0.500000, -0.250000},
|
||||||
|
{0.375000, -0.500000, -0.250000},
|
||||||
|
{0.500000, -0.500000, -0.250000},
|
||||||
|
{-0.500000, -0.375000, -0.250000},
|
||||||
|
{-0.375000, -0.375000, -0.250000},
|
||||||
|
{-0.250000, -0.375000, -0.250000},
|
||||||
|
{-0.125000, -0.375000, -0.250000},
|
||||||
|
{0.000000, -0.375000, -0.250000},
|
||||||
|
{0.125000, -0.375000, -0.250000},
|
||||||
|
{0.250000, -0.375000, -0.250000},
|
||||||
|
{0.375000, -0.375000, -0.250000},
|
||||||
|
{0.500000, -0.375000, -0.250000},
|
||||||
|
{-0.500000, -0.250000, -0.250000},
|
||||||
|
{-0.375000, -0.250000, -0.250000},
|
||||||
|
{-0.250000, -0.250000, -0.250000},
|
||||||
|
{-0.125000, -0.250000, -0.250000},
|
||||||
|
{0.000000, -0.250000, -0.250000},
|
||||||
|
{0.125000, -0.250000, -0.250000},
|
||||||
|
{0.250000, -0.250000, -0.250000},
|
||||||
|
{0.375000, -0.250000, -0.250000},
|
||||||
|
{0.500000, -0.250000, -0.250000},
|
||||||
|
{-0.500000, -0.125000, -0.250000},
|
||||||
|
{-0.375000, -0.125000, -0.250000},
|
||||||
|
{-0.250000, -0.125000, -0.250000},
|
||||||
|
{-0.125000, -0.125000, -0.250000},
|
||||||
|
{0.000000, -0.125000, -0.250000},
|
||||||
|
{0.125000, -0.125000, -0.250000},
|
||||||
|
{0.250000, -0.125000, -0.250000},
|
||||||
|
{0.375000, -0.125000, -0.250000},
|
||||||
|
{0.500000, -0.125000, -0.250000},
|
||||||
|
{-0.500000, 0.000000, -0.250000},
|
||||||
|
{-0.375000, 0.000000, -0.250000},
|
||||||
|
{-0.250000, 0.000000, -0.250000},
|
||||||
|
{-0.125000, 0.000000, -0.250000},
|
||||||
|
{0.000000, 0.000000, -0.250000},
|
||||||
|
{0.125000, 0.000000, -0.250000},
|
||||||
|
{0.250000, 0.000000, -0.250000},
|
||||||
|
{0.375000, 0.000000, -0.250000},
|
||||||
|
{0.500000, 0.000000, -0.250000},
|
||||||
|
{-0.500000, 0.125000, -0.250000},
|
||||||
|
{-0.375000, 0.125000, -0.250000},
|
||||||
|
{-0.250000, 0.125000, -0.250000},
|
||||||
|
{-0.125000, 0.125000, -0.250000},
|
||||||
|
{0.000000, 0.125000, -0.250000},
|
||||||
|
{0.125000, 0.125000, -0.250000},
|
||||||
|
{0.250000, 0.125000, -0.250000},
|
||||||
|
{0.375000, 0.125000, -0.250000},
|
||||||
|
{0.500000, 0.125000, -0.250000},
|
||||||
|
{-0.500000, 0.250000, -0.250000},
|
||||||
|
{-0.375000, 0.250000, -0.250000},
|
||||||
|
{-0.250000, 0.250000, -0.250000},
|
||||||
|
{-0.125000, 0.250000, -0.250000},
|
||||||
|
{0.000000, 0.250000, -0.250000},
|
||||||
|
{0.125000, 0.250000, -0.250000},
|
||||||
|
{0.250000, 0.250000, -0.250000},
|
||||||
|
{0.375000, 0.250000, -0.250000},
|
||||||
|
{0.500000, 0.250000, -0.250000},
|
||||||
|
{-0.500000, 0.375000, -0.250000},
|
||||||
|
{-0.375000, 0.375000, -0.250000},
|
||||||
|
{-0.250000, 0.375000, -0.250000},
|
||||||
|
{-0.125000, 0.375000, -0.250000},
|
||||||
|
{0.000000, 0.375000, -0.250000},
|
||||||
|
{0.125000, 0.375000, -0.250000},
|
||||||
|
{0.250000, 0.375000, -0.250000},
|
||||||
|
{0.375000, 0.375000, -0.250000},
|
||||||
|
{0.500000, 0.375000, -0.250000},
|
||||||
|
{-0.500000, 0.500000, -0.250000},
|
||||||
|
{-0.375000, 0.500000, -0.250000},
|
||||||
|
{-0.250000, 0.500000, -0.250000},
|
||||||
|
{-0.125000, 0.500000, -0.250000},
|
||||||
|
{0.000000, 0.500000, -0.250000},
|
||||||
|
{0.125000, 0.500000, -0.250000},
|
||||||
|
{0.250000, 0.500000, -0.250000},
|
||||||
|
{0.375000, 0.500000, -0.250000},
|
||||||
|
{0.500000, 0.500000, -0.250000},
|
||||||
|
{-0.500000, -0.500000, -0.125000},
|
||||||
|
{-0.375000, -0.500000, -0.125000},
|
||||||
|
{-0.250000, -0.500000, -0.125000},
|
||||||
|
{-0.125000, -0.500000, -0.125000},
|
||||||
|
{0.000000, -0.500000, -0.125000},
|
||||||
|
{0.125000, -0.500000, -0.125000},
|
||||||
|
{0.250000, -0.500000, -0.125000},
|
||||||
|
{0.375000, -0.500000, -0.125000},
|
||||||
|
{0.500000, -0.500000, -0.125000},
|
||||||
|
{-0.500000, -0.375000, -0.125000},
|
||||||
|
{-0.375000, -0.375000, -0.125000},
|
||||||
|
{-0.250000, -0.375000, -0.125000},
|
||||||
|
{-0.125000, -0.375000, -0.125000},
|
||||||
|
{0.000000, -0.375000, -0.125000},
|
||||||
|
{0.125000, -0.375000, -0.125000},
|
||||||
|
{0.250000, -0.375000, -0.125000},
|
||||||
|
{0.375000, -0.375000, -0.125000},
|
||||||
|
{0.500000, -0.375000, -0.125000},
|
||||||
|
{-0.500000, -0.250000, -0.125000},
|
||||||
|
{-0.375000, -0.250000, -0.125000},
|
||||||
|
{-0.250000, -0.250000, -0.125000},
|
||||||
|
{-0.125000, -0.250000, -0.125000},
|
||||||
|
{0.000000, -0.250000, -0.125000},
|
||||||
|
{0.125000, -0.250000, -0.125000},
|
||||||
|
{0.250000, -0.250000, -0.125000},
|
||||||
|
{0.375000, -0.250000, -0.125000},
|
||||||
|
{0.500000, -0.250000, -0.125000},
|
||||||
|
{-0.500000, -0.125000, -0.125000},
|
||||||
|
{-0.375000, -0.125000, -0.125000},
|
||||||
|
{-0.250000, -0.125000, -0.125000},
|
||||||
|
{-0.125000, -0.125000, -0.125000},
|
||||||
|
{0.000000, -0.125000, -0.125000},
|
||||||
|
{0.125000, -0.125000, -0.125000},
|
||||||
|
{0.250000, -0.125000, -0.125000},
|
||||||
|
{0.375000, -0.125000, -0.125000},
|
||||||
|
{0.500000, -0.125000, -0.125000},
|
||||||
|
{-0.500000, 0.000000, -0.125000},
|
||||||
|
{-0.375000, 0.000000, -0.125000},
|
||||||
|
{-0.250000, 0.000000, -0.125000},
|
||||||
|
{-0.125000, 0.000000, -0.125000},
|
||||||
|
{0.000000, 0.000000, -0.125000},
|
||||||
|
{0.125000, 0.000000, -0.125000},
|
||||||
|
{0.250000, 0.000000, -0.125000},
|
||||||
|
{0.375000, 0.000000, -0.125000},
|
||||||
|
{0.500000, 0.000000, -0.125000},
|
||||||
|
{-0.500000, 0.125000, -0.125000},
|
||||||
|
{-0.375000, 0.125000, -0.125000},
|
||||||
|
{-0.250000, 0.125000, -0.125000},
|
||||||
|
{-0.125000, 0.125000, -0.125000},
|
||||||
|
{0.000000, 0.125000, -0.125000},
|
||||||
|
{0.125000, 0.125000, -0.125000},
|
||||||
|
{0.250000, 0.125000, -0.125000},
|
||||||
|
{0.375000, 0.125000, -0.125000},
|
||||||
|
{0.500000, 0.125000, -0.125000},
|
||||||
|
{-0.500000, 0.250000, -0.125000},
|
||||||
|
{-0.375000, 0.250000, -0.125000},
|
||||||
|
{-0.250000, 0.250000, -0.125000},
|
||||||
|
{-0.125000, 0.250000, -0.125000},
|
||||||
|
{0.000000, 0.250000, -0.125000},
|
||||||
|
{0.125000, 0.250000, -0.125000},
|
||||||
|
{0.250000, 0.250000, -0.125000},
|
||||||
|
{0.375000, 0.250000, -0.125000},
|
||||||
|
{0.500000, 0.250000, -0.125000},
|
||||||
|
{-0.500000, 0.375000, -0.125000},
|
||||||
|
{-0.375000, 0.375000, -0.125000},
|
||||||
|
{-0.250000, 0.375000, -0.125000},
|
||||||
|
{-0.125000, 0.375000, -0.125000},
|
||||||
|
{0.000000, 0.375000, -0.125000},
|
||||||
|
{0.125000, 0.375000, -0.125000},
|
||||||
|
{0.250000, 0.375000, -0.125000},
|
||||||
|
{0.375000, 0.375000, -0.125000},
|
||||||
|
{0.500000, 0.375000, -0.125000},
|
||||||
|
{-0.500000, 0.500000, -0.125000},
|
||||||
|
{-0.375000, 0.500000, -0.125000},
|
||||||
|
{-0.250000, 0.500000, -0.125000},
|
||||||
|
{-0.125000, 0.500000, -0.125000},
|
||||||
|
{0.000000, 0.500000, -0.125000},
|
||||||
|
{0.125000, 0.500000, -0.125000},
|
||||||
|
{0.250000, 0.500000, -0.125000},
|
||||||
|
{0.375000, 0.500000, -0.125000},
|
||||||
|
{0.500000, 0.500000, -0.125000},
|
||||||
|
{-0.500000, -0.500000, 0.000000},
|
||||||
|
{-0.375000, -0.500000, 0.000000},
|
||||||
|
{-0.250000, -0.500000, 0.000000},
|
||||||
|
{-0.125000, -0.500000, 0.000000},
|
||||||
|
{0.000000, -0.500000, 0.000000},
|
||||||
|
{0.125000, -0.500000, 0.000000},
|
||||||
|
{0.250000, -0.500000, 0.000000},
|
||||||
|
{0.375000, -0.500000, 0.000000},
|
||||||
|
{0.500000, -0.500000, 0.000000},
|
||||||
|
{-0.500000, -0.375000, 0.000000},
|
||||||
|
{-0.375000, -0.375000, 0.000000},
|
||||||
|
{-0.250000, -0.375000, 0.000000},
|
||||||
|
{-0.125000, -0.375000, 0.000000},
|
||||||
|
{0.000000, -0.375000, 0.000000},
|
||||||
|
{0.125000, -0.375000, 0.000000},
|
||||||
|
{0.250000, -0.375000, 0.000000},
|
||||||
|
{0.375000, -0.375000, 0.000000},
|
||||||
|
{0.500000, -0.375000, 0.000000},
|
||||||
|
{-0.500000, -0.250000, 0.000000},
|
||||||
|
{-0.375000, -0.250000, 0.000000},
|
||||||
|
{-0.250000, -0.250000, 0.000000},
|
||||||
|
{-0.125000, -0.250000, 0.000000},
|
||||||
|
{0.000000, -0.250000, 0.000000},
|
||||||
|
{0.125000, -0.250000, 0.000000},
|
||||||
|
{0.250000, -0.250000, 0.000000},
|
||||||
|
{0.375000, -0.250000, 0.000000},
|
||||||
|
{0.500000, -0.250000, 0.000000},
|
||||||
|
{-0.500000, -0.125000, 0.000000},
|
||||||
|
{-0.375000, -0.125000, 0.000000},
|
||||||
|
{-0.250000, -0.125000, 0.000000},
|
||||||
|
{-0.125000, -0.125000, 0.000000},
|
||||||
|
{0.000000, -0.125000, 0.000000},
|
||||||
|
{0.125000, -0.125000, 0.000000},
|
||||||
|
{0.250000, -0.125000, 0.000000},
|
||||||
|
{0.375000, -0.125000, 0.000000},
|
||||||
|
{0.500000, -0.125000, 0.000000},
|
||||||
|
{-0.500000, 0.000000, 0.000000},
|
||||||
|
{-0.375000, 0.000000, 0.000000},
|
||||||
|
{-0.250000, 0.000000, 0.000000},
|
||||||
|
{-0.125000, 0.000000, 0.000000},
|
||||||
|
{0.000000, 0.000000, 0.000000},
|
||||||
|
{0.125000, 0.000000, 0.000000},
|
||||||
|
{0.250000, 0.000000, 0.000000},
|
||||||
|
{0.375000, 0.000000, 0.000000},
|
||||||
|
{0.500000, 0.000000, 0.000000},
|
||||||
|
{-0.500000, 0.125000, 0.000000},
|
||||||
|
{-0.375000, 0.125000, 0.000000},
|
||||||
|
{-0.250000, 0.125000, 0.000000},
|
||||||
|
{-0.125000, 0.125000, 0.000000},
|
||||||
|
{0.000000, 0.125000, 0.000000},
|
||||||
|
{0.125000, 0.125000, 0.000000},
|
||||||
|
{0.250000, 0.125000, 0.000000},
|
||||||
|
{0.375000, 0.125000, 0.000000},
|
||||||
|
{0.500000, 0.125000, 0.000000},
|
||||||
|
{-0.500000, 0.250000, 0.000000},
|
||||||
|
{-0.375000, 0.250000, 0.000000},
|
||||||
|
{-0.250000, 0.250000, 0.000000},
|
||||||
|
{-0.125000, 0.250000, 0.000000},
|
||||||
|
{0.000000, 0.250000, 0.000000},
|
||||||
|
{0.125000, 0.250000, 0.000000},
|
||||||
|
{0.250000, 0.250000, 0.000000},
|
||||||
|
{0.375000, 0.250000, 0.000000},
|
||||||
|
{0.500000, 0.250000, 0.000000},
|
||||||
|
{-0.500000, 0.375000, 0.000000},
|
||||||
|
{-0.375000, 0.375000, 0.000000},
|
||||||
|
{-0.250000, 0.375000, 0.000000},
|
||||||
|
{-0.125000, 0.375000, 0.000000},
|
||||||
|
{0.000000, 0.375000, 0.000000},
|
||||||
|
{0.125000, 0.375000, 0.000000},
|
||||||
|
{0.250000, 0.375000, 0.000000},
|
||||||
|
{0.375000, 0.375000, 0.000000},
|
||||||
|
{0.500000, 0.375000, 0.000000},
|
||||||
|
{-0.500000, 0.500000, 0.000000},
|
||||||
|
{-0.375000, 0.500000, 0.000000},
|
||||||
|
{-0.250000, 0.500000, 0.000000},
|
||||||
|
{-0.125000, 0.500000, 0.000000},
|
||||||
|
{0.000000, 0.500000, 0.000000},
|
||||||
|
{0.125000, 0.500000, 0.000000},
|
||||||
|
{0.250000, 0.500000, 0.000000},
|
||||||
|
{0.375000, 0.500000, 0.000000},
|
||||||
|
{0.500000, 0.500000, 0.000000},
|
||||||
|
{-0.500000, -0.500000, 0.125000},
|
||||||
|
{-0.375000, -0.500000, 0.125000},
|
||||||
|
{-0.250000, -0.500000, 0.125000},
|
||||||
|
{-0.125000, -0.500000, 0.125000},
|
||||||
|
{0.000000, -0.500000, 0.125000},
|
||||||
|
{0.125000, -0.500000, 0.125000},
|
||||||
|
{0.250000, -0.500000, 0.125000},
|
||||||
|
{0.375000, -0.500000, 0.125000},
|
||||||
|
{0.500000, -0.500000, 0.125000},
|
||||||
|
{-0.500000, -0.375000, 0.125000},
|
||||||
|
{-0.375000, -0.375000, 0.125000},
|
||||||
|
{-0.250000, -0.375000, 0.125000},
|
||||||
|
{-0.125000, -0.375000, 0.125000},
|
||||||
|
{0.000000, -0.375000, 0.125000},
|
||||||
|
{0.125000, -0.375000, 0.125000},
|
||||||
|
{0.250000, -0.375000, 0.125000},
|
||||||
|
{0.375000, -0.375000, 0.125000},
|
||||||
|
{0.500000, -0.375000, 0.125000},
|
||||||
|
{-0.500000, -0.250000, 0.125000},
|
||||||
|
{-0.375000, -0.250000, 0.125000},
|
||||||
|
{-0.250000, -0.250000, 0.125000},
|
||||||
|
{-0.125000, -0.250000, 0.125000},
|
||||||
|
{0.000000, -0.250000, 0.125000},
|
||||||
|
{0.125000, -0.250000, 0.125000},
|
||||||
|
{0.250000, -0.250000, 0.125000},
|
||||||
|
{0.375000, -0.250000, 0.125000},
|
||||||
|
{0.500000, -0.250000, 0.125000},
|
||||||
|
{-0.500000, -0.125000, 0.125000},
|
||||||
|
{-0.375000, -0.125000, 0.125000},
|
||||||
|
{-0.250000, -0.125000, 0.125000},
|
||||||
|
{-0.125000, -0.125000, 0.125000},
|
||||||
|
{0.000000, -0.125000, 0.125000},
|
||||||
|
{0.125000, -0.125000, 0.125000},
|
||||||
|
{0.250000, -0.125000, 0.125000},
|
||||||
|
{0.375000, -0.125000, 0.125000},
|
||||||
|
{0.500000, -0.125000, 0.125000},
|
||||||
|
{-0.500000, 0.000000, 0.125000},
|
||||||
|
{-0.375000, 0.000000, 0.125000},
|
||||||
|
{-0.250000, 0.000000, 0.125000},
|
||||||
|
{-0.125000, 0.000000, 0.125000},
|
||||||
|
{0.000000, 0.000000, 0.125000},
|
||||||
|
{0.125000, 0.000000, 0.125000},
|
||||||
|
{0.250000, 0.000000, 0.125000},
|
||||||
|
{0.375000, 0.000000, 0.125000},
|
||||||
|
{0.500000, 0.000000, 0.125000},
|
||||||
|
{-0.500000, 0.125000, 0.125000},
|
||||||
|
{-0.375000, 0.125000, 0.125000},
|
||||||
|
{-0.250000, 0.125000, 0.125000},
|
||||||
|
{-0.125000, 0.125000, 0.125000},
|
||||||
|
{0.000000, 0.125000, 0.125000},
|
||||||
|
{0.125000, 0.125000, 0.125000},
|
||||||
|
{0.250000, 0.125000, 0.125000},
|
||||||
|
{0.375000, 0.125000, 0.125000},
|
||||||
|
{0.500000, 0.125000, 0.125000},
|
||||||
|
{-0.500000, 0.250000, 0.125000},
|
||||||
|
{-0.375000, 0.250000, 0.125000},
|
||||||
|
{-0.250000, 0.250000, 0.125000},
|
||||||
|
{-0.125000, 0.250000, 0.125000},
|
||||||
|
{0.000000, 0.250000, 0.125000},
|
||||||
|
{0.125000, 0.250000, 0.125000},
|
||||||
|
{0.250000, 0.250000, 0.125000},
|
||||||
|
{0.375000, 0.250000, 0.125000},
|
||||||
|
{0.500000, 0.250000, 0.125000},
|
||||||
|
{-0.500000, 0.375000, 0.125000},
|
||||||
|
{-0.375000, 0.375000, 0.125000},
|
||||||
|
{-0.250000, 0.375000, 0.125000},
|
||||||
|
{-0.125000, 0.375000, 0.125000},
|
||||||
|
{0.000000, 0.375000, 0.125000},
|
||||||
|
{0.125000, 0.375000, 0.125000},
|
||||||
|
{0.250000, 0.375000, 0.125000},
|
||||||
|
{0.375000, 0.375000, 0.125000},
|
||||||
|
{0.500000, 0.375000, 0.125000},
|
||||||
|
{-0.500000, 0.500000, 0.125000},
|
||||||
|
{-0.375000, 0.500000, 0.125000},
|
||||||
|
{-0.250000, 0.500000, 0.125000},
|
||||||
|
{-0.125000, 0.500000, 0.125000},
|
||||||
|
{0.000000, 0.500000, 0.125000},
|
||||||
|
{0.125000, 0.500000, 0.125000},
|
||||||
|
{0.250000, 0.500000, 0.125000},
|
||||||
|
{0.375000, 0.500000, 0.125000},
|
||||||
|
{0.500000, 0.500000, 0.125000},
|
||||||
|
{-0.500000, -0.500000, 0.250000},
|
||||||
|
{-0.375000, -0.500000, 0.250000},
|
||||||
|
{-0.250000, -0.500000, 0.250000},
|
||||||
|
{-0.125000, -0.500000, 0.250000},
|
||||||
|
{0.000000, -0.500000, 0.250000},
|
||||||
|
{0.125000, -0.500000, 0.250000},
|
||||||
|
{0.250000, -0.500000, 0.250000},
|
||||||
|
{0.375000, -0.500000, 0.250000},
|
||||||
|
{0.500000, -0.500000, 0.250000},
|
||||||
|
{-0.500000, -0.375000, 0.250000},
|
||||||
|
{-0.375000, -0.375000, 0.250000},
|
||||||
|
{-0.250000, -0.375000, 0.250000},
|
||||||
|
{-0.125000, -0.375000, 0.250000},
|
||||||
|
{0.000000, -0.375000, 0.250000},
|
||||||
|
{0.125000, -0.375000, 0.250000},
|
||||||
|
{0.250000, -0.375000, 0.250000},
|
||||||
|
{0.375000, -0.375000, 0.250000},
|
||||||
|
{0.500000, -0.375000, 0.250000},
|
||||||
|
{-0.500000, -0.250000, 0.250000},
|
||||||
|
{-0.375000, -0.250000, 0.250000},
|
||||||
|
{-0.250000, -0.250000, 0.250000},
|
||||||
|
{-0.125000, -0.250000, 0.250000},
|
||||||
|
{0.000000, -0.250000, 0.250000},
|
||||||
|
{0.125000, -0.250000, 0.250000},
|
||||||
|
{0.250000, -0.250000, 0.250000},
|
||||||
|
{0.375000, -0.250000, 0.250000},
|
||||||
|
{0.500000, -0.250000, 0.250000},
|
||||||
|
{-0.500000, -0.125000, 0.250000},
|
||||||
|
{-0.375000, -0.125000, 0.250000},
|
||||||
|
{-0.250000, -0.125000, 0.250000},
|
||||||
|
{-0.125000, -0.125000, 0.250000},
|
||||||
|
{0.000000, -0.125000, 0.250000},
|
||||||
|
{0.125000, -0.125000, 0.250000},
|
||||||
|
{0.250000, -0.125000, 0.250000},
|
||||||
|
{0.375000, -0.125000, 0.250000},
|
||||||
|
{0.500000, -0.125000, 0.250000},
|
||||||
|
{-0.500000, 0.000000, 0.250000},
|
||||||
|
{-0.375000, 0.000000, 0.250000},
|
||||||
|
{-0.250000, 0.000000, 0.250000},
|
||||||
|
{-0.125000, 0.000000, 0.250000},
|
||||||
|
{0.000000, 0.000000, 0.250000},
|
||||||
|
{0.125000, 0.000000, 0.250000},
|
||||||
|
{0.250000, 0.000000, 0.250000},
|
||||||
|
{0.375000, 0.000000, 0.250000},
|
||||||
|
{0.500000, 0.000000, 0.250000},
|
||||||
|
{-0.500000, 0.125000, 0.250000},
|
||||||
|
{-0.375000, 0.125000, 0.250000},
|
||||||
|
{-0.250000, 0.125000, 0.250000},
|
||||||
|
{-0.125000, 0.125000, 0.250000},
|
||||||
|
{0.000000, 0.125000, 0.250000},
|
||||||
|
{0.125000, 0.125000, 0.250000},
|
||||||
|
{0.250000, 0.125000, 0.250000},
|
||||||
|
{0.375000, 0.125000, 0.250000},
|
||||||
|
{0.500000, 0.125000, 0.250000},
|
||||||
|
{-0.500000, 0.250000, 0.250000},
|
||||||
|
{-0.375000, 0.250000, 0.250000},
|
||||||
|
{-0.250000, 0.250000, 0.250000},
|
||||||
|
{-0.125000, 0.250000, 0.250000},
|
||||||
|
{0.000000, 0.250000, 0.250000},
|
||||||
|
{0.125000, 0.250000, 0.250000},
|
||||||
|
{0.250000, 0.250000, 0.250000},
|
||||||
|
{0.375000, 0.250000, 0.250000},
|
||||||
|
{0.500000, 0.250000, 0.250000},
|
||||||
|
{-0.500000, 0.375000, 0.250000},
|
||||||
|
{-0.375000, 0.375000, 0.250000},
|
||||||
|
{-0.250000, 0.375000, 0.250000},
|
||||||
|
{-0.125000, 0.375000, 0.250000},
|
||||||
|
{0.000000, 0.375000, 0.250000},
|
||||||
|
{0.125000, 0.375000, 0.250000},
|
||||||
|
{0.250000, 0.375000, 0.250000},
|
||||||
|
{0.375000, 0.375000, 0.250000},
|
||||||
|
{0.500000, 0.375000, 0.250000},
|
||||||
|
{-0.500000, 0.500000, 0.250000},
|
||||||
|
{-0.375000, 0.500000, 0.250000},
|
||||||
|
{-0.250000, 0.500000, 0.250000},
|
||||||
|
{-0.125000, 0.500000, 0.250000},
|
||||||
|
{0.000000, 0.500000, 0.250000},
|
||||||
|
{0.125000, 0.500000, 0.250000},
|
||||||
|
{0.250000, 0.500000, 0.250000},
|
||||||
|
{0.375000, 0.500000, 0.250000},
|
||||||
|
{0.500000, 0.500000, 0.250000},
|
||||||
|
{-0.500000, -0.500000, 0.375000},
|
||||||
|
{-0.375000, -0.500000, 0.375000},
|
||||||
|
{-0.250000, -0.500000, 0.375000},
|
||||||
|
{-0.125000, -0.500000, 0.375000},
|
||||||
|
{0.000000, -0.500000, 0.375000},
|
||||||
|
{0.125000, -0.500000, 0.375000},
|
||||||
|
{0.250000, -0.500000, 0.375000},
|
||||||
|
{0.375000, -0.500000, 0.375000},
|
||||||
|
{0.500000, -0.500000, 0.375000},
|
||||||
|
{-0.500000, -0.375000, 0.375000},
|
||||||
|
{-0.375000, -0.375000, 0.375000},
|
||||||
|
{-0.250000, -0.375000, 0.375000},
|
||||||
|
{-0.125000, -0.375000, 0.375000},
|
||||||
|
{0.000000, -0.375000, 0.375000},
|
||||||
|
{0.125000, -0.375000, 0.375000},
|
||||||
|
{0.250000, -0.375000, 0.375000},
|
||||||
|
{0.375000, -0.375000, 0.375000},
|
||||||
|
{0.500000, -0.375000, 0.375000},
|
||||||
|
{-0.500000, -0.250000, 0.375000},
|
||||||
|
{-0.375000, -0.250000, 0.375000},
|
||||||
|
{-0.250000, -0.250000, 0.375000},
|
||||||
|
{-0.125000, -0.250000, 0.375000},
|
||||||
|
{0.000000, -0.250000, 0.375000},
|
||||||
|
{0.125000, -0.250000, 0.375000},
|
||||||
|
{0.250000, -0.250000, 0.375000},
|
||||||
|
{0.375000, -0.250000, 0.375000},
|
||||||
|
{0.500000, -0.250000, 0.375000},
|
||||||
|
{-0.500000, -0.125000, 0.375000},
|
||||||
|
{-0.375000, -0.125000, 0.375000},
|
||||||
|
{-0.250000, -0.125000, 0.375000},
|
||||||
|
{-0.125000, -0.125000, 0.375000},
|
||||||
|
{0.000000, -0.125000, 0.375000},
|
||||||
|
{0.125000, -0.125000, 0.375000},
|
||||||
|
{0.250000, -0.125000, 0.375000},
|
||||||
|
{0.375000, -0.125000, 0.375000},
|
||||||
|
{0.500000, -0.125000, 0.375000},
|
||||||
|
{-0.500000, 0.000000, 0.375000},
|
||||||
|
{-0.375000, 0.000000, 0.375000},
|
||||||
|
{-0.250000, 0.000000, 0.375000},
|
||||||
|
{-0.125000, 0.000000, 0.375000},
|
||||||
|
{0.000000, 0.000000, 0.375000},
|
||||||
|
{0.125000, 0.000000, 0.375000},
|
||||||
|
{0.250000, 0.000000, 0.375000},
|
||||||
|
{0.375000, 0.000000, 0.375000},
|
||||||
|
{0.500000, 0.000000, 0.375000},
|
||||||
|
{-0.500000, 0.125000, 0.375000},
|
||||||
|
{-0.375000, 0.125000, 0.375000},
|
||||||
|
{-0.250000, 0.125000, 0.375000},
|
||||||
|
{-0.125000, 0.125000, 0.375000},
|
||||||
|
{0.000000, 0.125000, 0.375000},
|
||||||
|
{0.125000, 0.125000, 0.375000},
|
||||||
|
{0.250000, 0.125000, 0.375000},
|
||||||
|
{0.375000, 0.125000, 0.375000},
|
||||||
|
{0.500000, 0.125000, 0.375000},
|
||||||
|
{-0.500000, 0.250000, 0.375000},
|
||||||
|
{-0.375000, 0.250000, 0.375000},
|
||||||
|
{-0.250000, 0.250000, 0.375000},
|
||||||
|
{-0.125000, 0.250000, 0.375000},
|
||||||
|
{0.000000, 0.250000, 0.375000},
|
||||||
|
{0.125000, 0.250000, 0.375000},
|
||||||
|
{0.250000, 0.250000, 0.375000},
|
||||||
|
{0.375000, 0.250000, 0.375000},
|
||||||
|
{0.500000, 0.250000, 0.375000},
|
||||||
|
{-0.500000, 0.375000, 0.375000},
|
||||||
|
{-0.375000, 0.375000, 0.375000},
|
||||||
|
{-0.250000, 0.375000, 0.375000},
|
||||||
|
{-0.125000, 0.375000, 0.375000},
|
||||||
|
{0.000000, 0.375000, 0.375000},
|
||||||
|
{0.125000, 0.375000, 0.375000},
|
||||||
|
{0.250000, 0.375000, 0.375000},
|
||||||
|
{0.375000, 0.375000, 0.375000},
|
||||||
|
{0.500000, 0.375000, 0.375000},
|
||||||
|
{-0.500000, 0.500000, 0.375000},
|
||||||
|
{-0.375000, 0.500000, 0.375000},
|
||||||
|
{-0.250000, 0.500000, 0.375000},
|
||||||
|
{-0.125000, 0.500000, 0.375000},
|
||||||
|
{0.000000, 0.500000, 0.375000},
|
||||||
|
{0.125000, 0.500000, 0.375000},
|
||||||
|
{0.250000, 0.500000, 0.375000},
|
||||||
|
{0.375000, 0.500000, 0.375000},
|
||||||
|
{0.500000, 0.500000, 0.375000},
|
||||||
|
{-0.500000, -0.500000, 0.500000},
|
||||||
|
{-0.375000, -0.500000, 0.500000},
|
||||||
|
{-0.250000, -0.500000, 0.500000},
|
||||||
|
{-0.125000, -0.500000, 0.500000},
|
||||||
|
{0.000000, -0.500000, 0.500000},
|
||||||
|
{0.125000, -0.500000, 0.500000},
|
||||||
|
{0.250000, -0.500000, 0.500000},
|
||||||
|
{0.375000, -0.500000, 0.500000},
|
||||||
|
{0.500000, -0.500000, 0.500000},
|
||||||
|
{-0.500000, -0.375000, 0.500000},
|
||||||
|
{-0.375000, -0.375000, 0.500000},
|
||||||
|
{-0.250000, -0.375000, 0.500000},
|
||||||
|
{-0.125000, -0.375000, 0.500000},
|
||||||
|
{0.000000, -0.375000, 0.500000},
|
||||||
|
{0.125000, -0.375000, 0.500000},
|
||||||
|
{0.250000, -0.375000, 0.500000},
|
||||||
|
{0.375000, -0.375000, 0.500000},
|
||||||
|
{0.500000, -0.375000, 0.500000},
|
||||||
|
{-0.500000, -0.250000, 0.500000},
|
||||||
|
{-0.375000, -0.250000, 0.500000},
|
||||||
|
{-0.250000, -0.250000, 0.500000},
|
||||||
|
{-0.125000, -0.250000, 0.500000},
|
||||||
|
{0.000000, -0.250000, 0.500000},
|
||||||
|
{0.125000, -0.250000, 0.500000},
|
||||||
|
{0.250000, -0.250000, 0.500000},
|
||||||
|
{0.375000, -0.250000, 0.500000},
|
||||||
|
{0.500000, -0.250000, 0.500000},
|
||||||
|
{-0.500000, -0.125000, 0.500000},
|
||||||
|
{-0.375000, -0.125000, 0.500000},
|
||||||
|
{-0.250000, -0.125000, 0.500000},
|
||||||
|
{-0.125000, -0.125000, 0.500000},
|
||||||
|
{0.000000, -0.125000, 0.500000},
|
||||||
|
{0.125000, -0.125000, 0.500000},
|
||||||
|
{0.250000, -0.125000, 0.500000},
|
||||||
|
{0.375000, -0.125000, 0.500000},
|
||||||
|
{0.500000, -0.125000, 0.500000},
|
||||||
|
{-0.500000, 0.000000, 0.500000},
|
||||||
|
{-0.375000, 0.000000, 0.500000},
|
||||||
|
{-0.250000, 0.000000, 0.500000},
|
||||||
|
{-0.125000, 0.000000, 0.500000},
|
||||||
|
{0.000000, 0.000000, 0.500000},
|
||||||
|
{0.125000, 0.000000, 0.500000},
|
||||||
|
{0.250000, 0.000000, 0.500000},
|
||||||
|
{0.375000, 0.000000, 0.500000},
|
||||||
|
{0.500000, 0.000000, 0.500000},
|
||||||
|
{-0.500000, 0.125000, 0.500000},
|
||||||
|
{-0.375000, 0.125000, 0.500000},
|
||||||
|
{-0.250000, 0.125000, 0.500000},
|
||||||
|
{-0.125000, 0.125000, 0.500000},
|
||||||
|
{0.000000, 0.125000, 0.500000},
|
||||||
|
{0.125000, 0.125000, 0.500000},
|
||||||
|
{0.250000, 0.125000, 0.500000},
|
||||||
|
{0.375000, 0.125000, 0.500000},
|
||||||
|
{0.500000, 0.125000, 0.500000},
|
||||||
|
{-0.500000, 0.250000, 0.500000},
|
||||||
|
{-0.375000, 0.250000, 0.500000},
|
||||||
|
{-0.250000, 0.250000, 0.500000},
|
||||||
|
{-0.125000, 0.250000, 0.500000},
|
||||||
|
{0.000000, 0.250000, 0.500000},
|
||||||
|
{0.125000, 0.250000, 0.500000},
|
||||||
|
{0.250000, 0.250000, 0.500000},
|
||||||
|
{0.375000, 0.250000, 0.500000},
|
||||||
|
{0.500000, 0.250000, 0.500000},
|
||||||
|
{-0.500000, 0.375000, 0.500000},
|
||||||
|
{-0.375000, 0.375000, 0.500000},
|
||||||
|
{-0.250000, 0.375000, 0.500000},
|
||||||
|
{-0.125000, 0.375000, 0.500000},
|
||||||
|
{0.000000, 0.375000, 0.500000},
|
||||||
|
{0.125000, 0.375000, 0.500000},
|
||||||
|
{0.250000, 0.375000, 0.500000},
|
||||||
|
{0.375000, 0.375000, 0.500000},
|
||||||
|
{0.500000, 0.375000, 0.500000},
|
||||||
|
{-0.500000, 0.500000, 0.500000},
|
||||||
|
{-0.375000, 0.500000, 0.500000},
|
||||||
|
{-0.250000, 0.500000, 0.500000},
|
||||||
|
{-0.125000, 0.500000, 0.500000},
|
||||||
|
{0.000000, 0.500000, 0.500000},
|
||||||
|
{0.125000, 0.500000, 0.500000},
|
||||||
|
{0.250000, 0.500000, 0.500000},
|
||||||
|
{0.375000, 0.500000, 0.500000},
|
||||||
|
{0.500000, 0.500000, 0.500000},
|
||||||
|
};
|
||||||
97
shaders/vs_cubes.sc
Normal file
97
shaders/vs_cubes.sc
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
$input i_data0, i_data1, i_data2, i_data3
|
||||||
|
$output tex_coord, world_pos
|
||||||
|
|
||||||
|
#include "verts.sh"
|
||||||
|
#include "bgfx_shader.sh"
|
||||||
|
#include "shaderlib.sh"
|
||||||
|
|
||||||
|
// ATTENTION must match config.h
|
||||||
|
// VertexID Layout for texturing (not used)
|
||||||
|
// Bits: 19 | 3 | 10
|
||||||
|
// Data: texture index | texture corner | vertex index
|
||||||
|
|
||||||
|
const uint vertex_index_bits = 10;
|
||||||
|
const uint vertex_index_mask = (1 << vertex_index_bits) - 1;
|
||||||
|
const uint texture_corner_bits = 3;
|
||||||
|
const uint texture_corner_mask = (1 << texture_corner_bits) - 1;
|
||||||
|
const uint texture_index_bits = 19;
|
||||||
|
const uint texture_index_mask = (1 << texture_index_bits) - 1;
|
||||||
|
|
||||||
|
const uint TA_WIDTH = 2; // patches in atlas
|
||||||
|
const uint TA_HEIGHT = 2; // patches in atlas
|
||||||
|
const uint TA_MAIN_PATCH_SIZE = 8; // textures per patch
|
||||||
|
const uint TA_TEXTURE_SIZE = 4; // texels per texture
|
||||||
|
const uint TA_TEXTURES_PER_ROW = TA_WIDTH * TA_MAIN_PATCH_SIZE;
|
||||||
|
const uint TA_TEXTURES_PER_COL = TA_HEIGHT * TA_MAIN_PATCH_SIZE;
|
||||||
|
|
||||||
|
const float texel_stride_x = 1.0f / (TA_WIDTH * TA_MAIN_PATCH_SIZE * TA_TEXTURE_SIZE);
|
||||||
|
const float texel_stride_y = 1.0f / (TA_HEIGHT * TA_MAIN_PATCH_SIZE * TA_TEXTURE_SIZE);
|
||||||
|
const float texture_stride_x = 1.0f / TA_TEXTURES_PER_ROW;
|
||||||
|
const float texture_stride_y = 1.0f / TA_TEXTURES_PER_ROW;
|
||||||
|
|
||||||
|
const vec2 corner_offsets[8] =
|
||||||
|
{
|
||||||
|
{ 0.000000, 0.000000}, // TopLeft
|
||||||
|
{ 0.000000, 2*texel_stride_y}, // CenterLeft
|
||||||
|
{ 0.000000, 4*texel_stride_y}, // BotLeft
|
||||||
|
{ 2*texel_stride_x, 4*texel_stride_y}, // CenterBot
|
||||||
|
{ 4*texel_stride_x, 4*texel_stride_y}, // BotRight
|
||||||
|
{ 4*texel_stride_x, 2*texel_stride_y}, // CenterRight
|
||||||
|
{ 4*texel_stride_x, 0.000000}, // TopRight
|
||||||
|
{ 2*texel_stride_x, 0.000000}, // CenterTop
|
||||||
|
};
|
||||||
|
|
||||||
|
// Texturing rework V2
|
||||||
|
const uint IA_WIDTH = 2*8; // id's per row
|
||||||
|
const uint IA_HEIGHT = 2*8; // id's per column
|
||||||
|
const float ID_STRIDE_X = 1.0f / IA_WIDTH;
|
||||||
|
const float ID_STRIDE_Y = 1.0f / IA_HEIGHT;
|
||||||
|
|
||||||
|
const vec2 ID_CORNER_OFFSETS[8] =
|
||||||
|
{
|
||||||
|
{ 0.0, 0.0}, // TopLeft
|
||||||
|
{ 0.0, 0.5*ID_STRIDE_Y}, // CenterLeft
|
||||||
|
{ 0.0, ID_STRIDE_Y}, // BotLeft
|
||||||
|
{ 0.5*ID_STRIDE_X, ID_STRIDE_Y}, // CenterBot
|
||||||
|
{ ID_STRIDE_X, ID_STRIDE_Y}, // BotRight
|
||||||
|
{ ID_STRIDE_X, 0.5*ID_STRIDE_Y}, // CenterRight
|
||||||
|
{ ID_STRIDE_X, 0.0}, // TopRight
|
||||||
|
{ 0.5*ID_STRIDE_X, 0.0}, // CenterTop
|
||||||
|
};
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
uint index = uint(gl_VertexID); // gl_VertexIndex ??
|
||||||
|
|
||||||
|
// Calculate vertex coordinates
|
||||||
|
uint vert_index = index & vertex_index_mask;
|
||||||
|
mat4 model_mtx = mtxFromCols(i_data0, i_data1, i_data2, i_data3);
|
||||||
|
vec4 worldPos = mul(model_mtx, vec4(verts[vert_index], 1.0));
|
||||||
|
gl_Position = mul(u_viewProj, worldPos);
|
||||||
|
|
||||||
|
world_pos = (worldPos.xyz); // pass world pos to fs
|
||||||
|
|
||||||
|
// // Calculate texture coordinates
|
||||||
|
// uint texture_corner = (index >> vertex_index_bits) & texture_corner_mask;
|
||||||
|
// uint texture_index = (index >> (texture_corner_bits + vertex_index_bits)) & texture_index_mask;
|
||||||
|
|
||||||
|
// uint texture_x = texture_index % TA_TEXTURES_PER_ROW;
|
||||||
|
// uint texture_y = texture_index / TA_TEXTURES_PER_ROW;
|
||||||
|
|
||||||
|
// tex_coord = vec2(texture_x * texture_stride_x, texture_y * texture_stride_y);
|
||||||
|
// tex_coord = tex_coord + corner_offsets[texture_corner];
|
||||||
|
|
||||||
|
// Texturing rework V2
|
||||||
|
// split index bits
|
||||||
|
uint texture_corner = (index >> vertex_index_bits) & texture_corner_mask;
|
||||||
|
uint id_index = (index >> (texture_corner_bits + vertex_index_bits)) & texture_index_mask;
|
||||||
|
|
||||||
|
// Calc id_coords (in id_atlas, for sampling [tex_id,light_id])
|
||||||
|
// in [0;ID_ATLAS_SIZE]
|
||||||
|
uint id_x = id_index % IA_WIDTH;
|
||||||
|
uint id_y = id_index / IA_HEIGHT;
|
||||||
|
|
||||||
|
// TODO rename tex_coord->id_coord
|
||||||
|
tex_coord = vec2(id_x * ID_STRIDE_X, id_y * ID_STRIDE_Y);
|
||||||
|
tex_coord = tex_coord + ID_CORNER_OFFSETS[texture_corner];
|
||||||
|
}
|
||||||
9
shaders/vs_lines.sc
Normal file
9
shaders/vs_lines.sc
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
$input a_position
|
||||||
|
|
||||||
|
#include "bgfx_shader.sh"
|
||||||
|
#include "shaderlib.sh"
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0) );
|
||||||
|
}
|
||||||
74
src/CMakeLists.txt
Normal file
74
src/CMakeLists.txt
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
|
||||||
|
add_executable(spacegame
|
||||||
|
"factory.cpp"
|
||||||
|
"gameloop.cpp"
|
||||||
|
"graphics.cpp"
|
||||||
|
"lib/camera.cpp"
|
||||||
|
"lib/input.cpp"
|
||||||
|
"lib/stb_image.cpp"
|
||||||
|
"main.cpp"
|
||||||
|
"net/debug_server.cpp"
|
||||||
|
"net/network.cpp"
|
||||||
|
"renderer.cpp"
|
||||||
|
"space_input.cpp"
|
||||||
|
"space_math.cpp"
|
||||||
|
"util.cpp"
|
||||||
|
"world.cpp"
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
if(SPACEGAME_BUILD_SHADERS)
|
||||||
|
add_dependencies(spacegame shaders)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set_target_properties(spacegame PROPERTIES
|
||||||
|
OUTPUT_NAME spacegame
|
||||||
|
CXX_STANDARD 20
|
||||||
|
CXX_STANDARD_REQUIRED YES
|
||||||
|
CXX_EXTENSIONS NO
|
||||||
|
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/x86_64/$<CONFIG>
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/x86_64/$<CONFIG>
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/x86_64/$<CONFIG>
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(spacegame PRIVATE
|
||||||
|
$<$<CONFIG:Debug>:_DEBUG>
|
||||||
|
)
|
||||||
|
|
||||||
|
# -m64 - Build for a 64-bit machine
|
||||||
|
# -fno-exceptions - Disable exceptions (throw exception; => abort())
|
||||||
|
# -fno-rtti - Disable runtime type information (no reflection)
|
||||||
|
target_compile_options(spacegame PRIVATE
|
||||||
|
-m64
|
||||||
|
-fno-exceptions
|
||||||
|
-fno-rtti
|
||||||
|
# ${SPACEGAME_COMPILER_WARNINGS}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(spacegame PRIVATE
|
||||||
|
${PROJECT_SOURCE_DIR}/src
|
||||||
|
${PROJECT_SOURCE_DIR}/src/data
|
||||||
|
${PROJECT_SOURCE_DIR}/src/lib
|
||||||
|
${PROJECT_SOURCE_DIR}/src/net
|
||||||
|
${PROJECT_SOURCE_DIR}/3rdparty/stb
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(spacegame PRIVATE
|
||||||
|
glfw
|
||||||
|
bgfx::bx
|
||||||
|
bgfx::bimg
|
||||||
|
${BGFX}
|
||||||
|
Threads::Threads
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
################## release ###################
|
||||||
|
|
||||||
|
# Strip binary for release builds
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
|
||||||
|
add_custom_command(TARGET spacegame POST_BUILD
|
||||||
|
COMMAND echo "Stripping game executable"
|
||||||
|
COMMAND ${CMAKE_STRIP} --strip-all $<TARGET_FILE:spacegame>
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
endif()
|
||||||
72
src/config.h
Normal file
72
src/config.h
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/// Constants are used in many places
|
||||||
|
/// For ease of use we collect them all here
|
||||||
|
|
||||||
|
// TODO find reasonable numbers for these (take real use case measurements)
|
||||||
|
// how often do buffers need to grow? Are the initial values to small? too big?
|
||||||
|
|
||||||
|
namespace config
|
||||||
|
{
|
||||||
|
constexpr uint32_t MINIMAL_WINDOW_WIDTH = 800;
|
||||||
|
constexpr uint32_t MINIMAL_WINDOW_HEIGHT = 450;
|
||||||
|
|
||||||
|
enum LogLevel {
|
||||||
|
LOG_TRACE,
|
||||||
|
LOG_DEBUG,
|
||||||
|
LOG_INFO,
|
||||||
|
LOG_WARNING,
|
||||||
|
LOG_ERROR,
|
||||||
|
};
|
||||||
|
#ifdef NDEBUG
|
||||||
|
// in release mode
|
||||||
|
static LogLevel LOG_LEVEL = LOG_INFO;
|
||||||
|
#else
|
||||||
|
// in debug mode
|
||||||
|
static LogLevel LOG_LEVEL = LOG_DEBUG;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
constexpr uint32_t INITIAL_NUM_GRIDS = 64;
|
||||||
|
constexpr uint32_t INITIAL_NUM_CHUNKS = 1024;
|
||||||
|
constexpr uint32_t INITIAL_NUM_BLOCKS = 1024 * 8; // all blocks in view distance at a time
|
||||||
|
constexpr uint32_t INITIAL_NUM_COMPONENTS = 16 * 24; // components are generated in all 24 orientation
|
||||||
|
constexpr uint32_t INITIAL_NUM_COMPONENT_TEXTURES = INITIAL_NUM_COMPONENTS * 6;
|
||||||
|
constexpr uint32_t INITIAL_NUM_BLOCK_MODELS = 256;
|
||||||
|
constexpr uint32_t AVERAGE_BLOCK_MODEL_SIZE = 64; // average number of indices in a block model
|
||||||
|
constexpr uint32_t INITIAL_BLOCK_MODEL_BUFFER_SIZE = INITIAL_NUM_BLOCK_MODELS * AVERAGE_BLOCK_MODEL_SIZE; // number of uint32_t indices
|
||||||
|
constexpr uint32_t INITIAL_BLOCK_SELECTION = 1024; // per frame visible and rendered blocks
|
||||||
|
constexpr uint32_t INITIAL_LINE_BUFFER_SIZE = INITIAL_BLOCK_MODEL_BUFFER_SIZE * 2; // *2 since a line consists of 2 vertices
|
||||||
|
|
||||||
|
/* Texture atlas */
|
||||||
|
// Vulkan spec 1.3 defines a minimum of 4096 for device cap maxImageDimension2D
|
||||||
|
// TODO actually query device caps
|
||||||
|
|
||||||
|
// ATTENTION must match shader
|
||||||
|
const uint32_t TA_WIDTH = 2; // patches in atlas
|
||||||
|
const uint32_t TA_HEIGHT = 2; // patches in atlas
|
||||||
|
const uint32_t TA_MAIN_PATCH_SIZE = 8; // textures per patch
|
||||||
|
const uint32_t TA_TEXTURE_SIZE = 4; // texel width per texture (aka how many texels per component)
|
||||||
|
constexpr uint32_t TA_INITIAL_NUMBER_PATCHES = 512; // TODO find reasonable default
|
||||||
|
|
||||||
|
const uint32_t TA_TEXTURES_PER_ROW = TA_WIDTH * TA_MAIN_PATCH_SIZE; // PERF should be power of 2 // per texture row
|
||||||
|
const uint32_t TA_TEXELS_PER_ROW = TA_TEXTURES_PER_ROW * TA_TEXTURE_SIZE; // per texel row
|
||||||
|
const uint32_t TA_TEXELS_PER_TEXTURE = TA_TEXTURE_SIZE * TA_TEXTURE_SIZE;
|
||||||
|
const uint32_t TA_TOTAL_TEXTURES = TA_WIDTH * TA_HEIGHT * TA_MAIN_PATCH_SIZE * TA_MAIN_PATCH_SIZE;
|
||||||
|
const uint32_t TA_TOTAL_TEXELS = TA_TOTAL_TEXTURES * TA_TEXELS_PER_TEXTURE;
|
||||||
|
|
||||||
|
// at least 8, since a full block must fit (and blocks have 8x8 components)
|
||||||
|
static_assert(TA_MAIN_PATCH_SIZE >= 8);
|
||||||
|
// shader can only address 2^19 textures
|
||||||
|
static_assert(TA_WIDTH * TA_HEIGHT * TA_MAIN_PATCH_SIZE * TA_MAIN_PATCH_SIZE < (1 << 19));
|
||||||
|
|
||||||
|
static uint64_t DEBUG_EXIT_AFTER_FRAME = 0; // set to 0 to disable
|
||||||
|
static bool DEBUG_SAVE_TEXTURE_ATLAS_TO_PNG = false;
|
||||||
|
static bool DEBUG_TRACE_LOG_FOREVER_PATCHES = true;
|
||||||
|
static bool DEBUG_TRACE_LOG_FIRST_FIT_BUFFER = false;
|
||||||
|
static bool DEBUG_TRACE_LOG_LINEAR_BUFFER = false;
|
||||||
|
static bool DEBUG_TRACE_LOG_SLOT_BUFFER = false;
|
||||||
|
static bool DEBUG_TRACE_LOG_SLOT_LIST = false;
|
||||||
|
|
||||||
|
}
|
||||||
37
src/data/chunk_storage.h
Normal file
37
src/data/chunk_storage.h
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*#pragma once
|
||||||
|
|
||||||
|
#include "data/offset_vector.h"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
class ChunkStorage
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
OffsetVector<OffsetVector<OffsetVector<uint32_t>>> data;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ChunkStorage() = default;
|
||||||
|
~ChunkStorage() = default;
|
||||||
|
|
||||||
|
void insert(const uint32_t _x, const uint32_t _y, const uint32_t _z, const uint32_t _element)
|
||||||
|
{
|
||||||
|
logErr("ChunkStorage.insert is not implemented\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t at(const uint32_t _x, const uint32_t _y, const uint32_t _z)
|
||||||
|
{
|
||||||
|
logErr("ChunkStorage.at is not implemented\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(const uint32_t _x, const uint32_t _y, const uint32_t _z)
|
||||||
|
{
|
||||||
|
logErr("ChunkStorage.remove is not implemented\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void clean()
|
||||||
|
{
|
||||||
|
logErr("ChunkStorage.clean is not implemented\n");
|
||||||
|
// TODO shrink all offset vectors
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*/
|
||||||
59
src/data/chunk_storage_old.h
Normal file
59
src/data/chunk_storage_old.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "data/offset_vector.h"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
class ChunkStorage
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
OffsetVector<OffsetVector<OffsetVector<uint32_t>>> data;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ChunkStorage() = default;
|
||||||
|
~ChunkStorage() = default;
|
||||||
|
|
||||||
|
void insert(const uint32_t _x, const uint32_t _y, const uint32_t _z, const uint32_t _element)
|
||||||
|
{
|
||||||
|
data.reserve_at(_x).reserve_at(_y).reserve_at(_z) = _element;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t at(const uint32_t _x, const uint32_t _y, const uint32_t _z)
|
||||||
|
{
|
||||||
|
return data.get_at(_x).get_at(_y).get_at(_z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(const uint32_t _x, const uint32_t _y, const uint32_t _z)
|
||||||
|
{
|
||||||
|
auto& x = data;
|
||||||
|
auto& y = x.get_at(_x);
|
||||||
|
auto& z = y.get_at(_y);
|
||||||
|
|
||||||
|
z.remove_at(_z);
|
||||||
|
if (z.size() == 0)
|
||||||
|
{
|
||||||
|
y.remove_at(_y);
|
||||||
|
if (y.size() == 0)
|
||||||
|
{
|
||||||
|
x.remove_at(_x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void clean()
|
||||||
|
{
|
||||||
|
logErr("ChunkStorage.clean is not implemented\n");
|
||||||
|
// TODO shrink all offset vectors
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// has to be some kind of 3D array
|
||||||
|
// only stores uint32_t
|
||||||
|
|
||||||
|
// chunks need to be accessible by coordinates (x,y,z)
|
||||||
|
// should leave slots temporarily available
|
||||||
|
// only grow, do not shrink on remove
|
||||||
|
// only shrink on occasional clean()
|
||||||
|
// has to support negative offsets
|
||||||
|
|
||||||
202
src/data/first_fit_buffer.h
Normal file
202
src/data/first_fit_buffer.h
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <cstring>
|
||||||
|
#include "util.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
/// Provides first fit memory allocation
|
||||||
|
/// On demand defragmentation (if no more memory available)
|
||||||
|
/// Auto growing (if no more memory, even after defragmentation)
|
||||||
|
/// Auto updating of block references
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
class FirstFitBuffer
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
const uint32_t OFFSET_FREE_MARKER = UINT32_C(1 << 31);
|
||||||
|
const uint32_t DEFRAGMENTATION_THRESHOLD = UINT32_MAX; // UINT32_C(1); // TODO find reasonable number, percentage?
|
||||||
|
std::vector<T> buffer;
|
||||||
|
std::vector<uint32_t> offsets; // holds all offsets, incl. the next free one
|
||||||
|
uint32_t num_free_elements; // removed sections that can be reclaimed by defragmentation
|
||||||
|
|
||||||
|
uint32_t modified_from; // inclusive
|
||||||
|
uint32_t modified_to; // !! exclusive
|
||||||
|
|
||||||
|
void defrag()
|
||||||
|
{
|
||||||
|
if (config::DEBUG_TRACE_LOG_FIRST_FIT_BUFFER)
|
||||||
|
logTrace("first_fit_buffer running defragmentation\n");
|
||||||
|
std::vector<uint32_t> ref_mapping(offsets.size(), UINT32_MAX);
|
||||||
|
|
||||||
|
uint32_t src_offset = 0;
|
||||||
|
uint32_t dst_offset = 0;
|
||||||
|
uint32_t num_elements = 0;
|
||||||
|
|
||||||
|
uint32_t offset = 0;
|
||||||
|
uint32_t next_offset = 0;
|
||||||
|
uint32_t section_size = 0;
|
||||||
|
|
||||||
|
uint32_t last_ref = 0;
|
||||||
|
|
||||||
|
// walk offsets
|
||||||
|
// on normal offset, increase num elements to copy
|
||||||
|
// on free, actually copy data
|
||||||
|
|
||||||
|
for (uint32_t ref = 0; ref < offsets.size() - 1; ref++)
|
||||||
|
{
|
||||||
|
offset = offsets[ref] & ~OFFSET_FREE_MARKER;
|
||||||
|
next_offset = offsets[(size_t)ref + 1] & ~OFFSET_FREE_MARKER;
|
||||||
|
|
||||||
|
if (offsets[ref] & OFFSET_FREE_MARKER)
|
||||||
|
{ // we hit a free section => copy anything up to this point
|
||||||
|
std::memmove(
|
||||||
|
(void*)(buffer.data() + dst_offset),
|
||||||
|
(void*)(buffer.data() + src_offset),
|
||||||
|
sizeof(T) * num_elements);
|
||||||
|
|
||||||
|
src_offset = next_offset;
|
||||||
|
dst_offset += num_elements;
|
||||||
|
num_elements = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // is an in-use section => just add its size for next copy
|
||||||
|
section_size = next_offset - offset; // num elements at this offset
|
||||||
|
num_elements += section_size;
|
||||||
|
|
||||||
|
ref_mapping[ref /*orig ref*/] = last_ref /*new ref*/;
|
||||||
|
|
||||||
|
// update offsets
|
||||||
|
offsets[last_ref] = offset - (src_offset - dst_offset);
|
||||||
|
last_ref++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy last section (if any)
|
||||||
|
std::memmove(
|
||||||
|
(void*)(buffer.data() + dst_offset),
|
||||||
|
(void*)(buffer.data() + src_offset),
|
||||||
|
sizeof(T) * num_elements);
|
||||||
|
|
||||||
|
// shrink offsets
|
||||||
|
offsets[last_ref] = offsets[(size_t)last_ref - 1] + section_size;
|
||||||
|
last_ref++;
|
||||||
|
offsets.resize(last_ref);
|
||||||
|
|
||||||
|
num_free_elements = 0;
|
||||||
|
|
||||||
|
modified_from = 0;
|
||||||
|
modified_to = offsets.back();
|
||||||
|
|
||||||
|
// At this point things have changed:
|
||||||
|
// buffer: data is now at difference offsets (though size remains the same)
|
||||||
|
// offsets: the ref into the offsets have changed (=> mapping)
|
||||||
|
|
||||||
|
// TODO update
|
||||||
|
// walk all world_blocks
|
||||||
|
// update their referenced blocks offsets
|
||||||
|
// update their gfx_ref with the mapping
|
||||||
|
logErr("first_fit_buffer defragmentation: mapping update not implemented\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
FirstFitBuffer(const uint32_t _initial_size) :
|
||||||
|
buffer(_initial_size), offsets(0), num_free_elements(0),
|
||||||
|
modified_from(UINT32_MAX), modified_to(0)
|
||||||
|
{
|
||||||
|
// add first free offset
|
||||||
|
offsets.push_back(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
~FirstFitBuffer() = default;
|
||||||
|
|
||||||
|
// Returns reference NOT offset into buffer
|
||||||
|
uint32_t add(const T* _data, const uint32_t _num_elements)
|
||||||
|
{
|
||||||
|
uint32_t offset = offsets.back();
|
||||||
|
if (offset + _num_elements > (uint32_t)buffer.size())
|
||||||
|
{ // not enough space
|
||||||
|
|
||||||
|
// defragmentation worth it?
|
||||||
|
uint32_t total_free_elements = num_free_elements + ((uint32_t)buffer.size() - offsets.back());
|
||||||
|
if (total_free_elements >= _num_elements &&
|
||||||
|
total_free_elements >= DEFRAGMENTATION_THRESHOLD)
|
||||||
|
{
|
||||||
|
defrag();
|
||||||
|
// we know that there is enough space now
|
||||||
|
return add(_data, _num_elements); // tail recursive call
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // defrag not sufficient or not worth it => grow the buffer
|
||||||
|
if (config::DEBUG_TRACE_LOG_FIRST_FIT_BUFFER)
|
||||||
|
logTrace("first_fit_buffer resizing from %d to %d\n", buffer.size(), buffer.size() * 2);
|
||||||
|
// notify caller about change in size
|
||||||
|
modified_from = (uint32_t)buffer.size();
|
||||||
|
buffer.resize(2 * buffer.size());
|
||||||
|
modified_to = (uint32_t)buffer.size();
|
||||||
|
// we know that there is enough space now
|
||||||
|
return add(_data, _num_elements); // tail recursive call
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy data
|
||||||
|
std::memcpy(buffer.data() + offset, _data, sizeof(T) * _num_elements);
|
||||||
|
|
||||||
|
modified_from = std::min(modified_from, offset);
|
||||||
|
|
||||||
|
// add next offset
|
||||||
|
offsets.push_back(offset + _num_elements);
|
||||||
|
|
||||||
|
modified_to = std::max(modified_to, offsets.back());
|
||||||
|
|
||||||
|
return (uint32_t)offsets.size() - 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attention: new data MUST be of equal _num_elements as original
|
||||||
|
// Returns given _ref
|
||||||
|
uint32_t update(const uint32_t _ref, const T* _data)
|
||||||
|
{
|
||||||
|
uint32_t offset, num_elements;
|
||||||
|
get_offset(_ref, offset, num_elements);
|
||||||
|
|
||||||
|
// copy data
|
||||||
|
std::memcpy(buffer.data() + offset, _data, sizeof(T) * num_elements);
|
||||||
|
|
||||||
|
modified_from = std::min(modified_from, offset);
|
||||||
|
modified_to = std::max(modified_to, offset + num_elements);
|
||||||
|
|
||||||
|
return _ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(const uint32_t _ref)
|
||||||
|
{
|
||||||
|
num_free_elements += ((offsets[(size_t)_ref + 1] & ~OFFSET_FREE_MARKER) - offsets[_ref]);
|
||||||
|
// just mark offset as free
|
||||||
|
offsets[_ref] |= OFFSET_FREE_MARKER;
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_offset(const uint32_t _ref, uint32_t& _out_offset, uint32_t& _out_num_elements)
|
||||||
|
{
|
||||||
|
_out_offset = offsets[_ref];
|
||||||
|
_out_num_elements = offsets[(size_t)_ref + 1] - _out_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wasModified(uint32_t& _out_offset, uint32_t& _out_num_elements)
|
||||||
|
{
|
||||||
|
_out_offset = modified_from;
|
||||||
|
_out_num_elements = modified_to - modified_from;
|
||||||
|
return modified_to > modified_from;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearModified()
|
||||||
|
{
|
||||||
|
modified_from = UINT32_MAX;
|
||||||
|
modified_to = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* data()
|
||||||
|
{
|
||||||
|
return buffer.data();
|
||||||
|
}
|
||||||
|
};
|
||||||
93
src/data/linear_buffer.h
Normal file
93
src/data/linear_buffer.h
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
class LinearBuffer // aka transient buffer
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::vector<T> buffer;
|
||||||
|
uint16_t num_elements; // bgfx only allows up to 16-bit number of elements
|
||||||
|
uint16_t modified_size;
|
||||||
|
bool modified;
|
||||||
|
bool resized;
|
||||||
|
|
||||||
|
public:
|
||||||
|
LinearBuffer(const uint32_t _initial_size) :
|
||||||
|
buffer(_initial_size), num_elements(0), modified_size(0), modified(false), resized(false)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
~LinearBuffer() = default;
|
||||||
|
|
||||||
|
// TODO support multi-threading
|
||||||
|
// Reserves a number of elements, that MUST be filled (use update(..))
|
||||||
|
// Returns an offset to be passed to update(..)
|
||||||
|
uint32_t reserve(uint32_t _num_slots)
|
||||||
|
{
|
||||||
|
modified = true;
|
||||||
|
uint32_t offset = num_elements;
|
||||||
|
|
||||||
|
// sanity check
|
||||||
|
if ((uint32_t)num_elements + _num_slots > UINT16_MAX)
|
||||||
|
{
|
||||||
|
logErr("linear_buffer elements is greater than uint16. This will now break things.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
num_elements += _num_slots;
|
||||||
|
|
||||||
|
if (num_elements > buffer.size())
|
||||||
|
{ // need to grow
|
||||||
|
if (config::DEBUG_TRACE_LOG_LINEAR_BUFFER)
|
||||||
|
logTrace("linear_buffer resizing from %d to %d\n", buffer.size(), 2 * buffer.size());
|
||||||
|
buffer.resize(2 * buffer.size());
|
||||||
|
// notify caller
|
||||||
|
modified_size = (uint32_t)buffer.size();
|
||||||
|
resized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(uint32_t _offset, const T& _element)
|
||||||
|
{
|
||||||
|
modified = true;
|
||||||
|
buffer[_offset] = _element;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds an element at the end of the buffer
|
||||||
|
void add(const T& _element)
|
||||||
|
{
|
||||||
|
update(reserve(1), _element);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t get_num_elements()
|
||||||
|
{
|
||||||
|
return num_elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wasModified(uint32_t& _out_offset, uint32_t& _out_num_elements, bool& _out_resized)
|
||||||
|
{
|
||||||
|
_out_offset = 0;
|
||||||
|
_out_num_elements = std::max(num_elements, modified_size);
|
||||||
|
_out_resized = resized;
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* data()
|
||||||
|
{
|
||||||
|
return buffer.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clears entire buffer
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
modified = false;
|
||||||
|
resized = false;
|
||||||
|
modified_size = 0;
|
||||||
|
num_elements = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
158
src/data/offset_vector.h
Normal file
158
src/data/offset_vector.h
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
/*#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <cassert>
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
// A dynamic container, allowing negative indices and wrap around
|
||||||
|
// Note: indices alway centered around 0 (ie. min_index=5 max_index=8 still allocates [0;8])
|
||||||
|
// Attention: The internal storage capacity will be at least [min_index; 0] + [0; max_index]
|
||||||
|
|
||||||
|
template<typename Ty>
|
||||||
|
class OffsetVector
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::vector<Ty> data;
|
||||||
|
int32_t min_index; // smallest in-use index
|
||||||
|
int32_t max_index;
|
||||||
|
Ty invalid;
|
||||||
|
|
||||||
|
// Calculates a wrap around index into data
|
||||||
|
int32_t wrap_index(int32_t _index)
|
||||||
|
{
|
||||||
|
//return (_index + data.size()) % data.size();
|
||||||
|
return (_index + (int32_t)data.size()) & ((int32_t)data.size() - 1); // equal since size is always power of 2
|
||||||
|
}
|
||||||
|
|
||||||
|
void grow(uint32_t _required)
|
||||||
|
{
|
||||||
|
uint32_t size = (uint32_t)data.size();
|
||||||
|
size = (size == 0) ? 1 : size;
|
||||||
|
|
||||||
|
uint32_t old_size = size;
|
||||||
|
uint32_t old_wrap_min_index = wrap_index(min_index); // needs to be done before resize
|
||||||
|
|
||||||
|
while (size < _required)
|
||||||
|
size *= 2;
|
||||||
|
|
||||||
|
data.resize(size);
|
||||||
|
|
||||||
|
// move data
|
||||||
|
// Note: elements from [0;max_index] always stay in the same location
|
||||||
|
// only elements from [min_index;data.size()-1] may need to be moved
|
||||||
|
|
||||||
|
if (min_index < 0)
|
||||||
|
{
|
||||||
|
// we swap new "empty" elements from the back with the old ones that need moving
|
||||||
|
// back to front in order not to copy overlapping memory regions
|
||||||
|
// we use std::swap because std::move is not guaranteed to leave the old element in a
|
||||||
|
// valid state (in the case its a vector or so)
|
||||||
|
|
||||||
|
uint32_t dst = (uint32_t)data.size();
|
||||||
|
for (uint32_t src = old_size - 1; src >= old_wrap_min_index; --src)
|
||||||
|
{
|
||||||
|
--dst;
|
||||||
|
std::swap(data[src], data[dst]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// When deleting an element, its slot is set to _invalid.
|
||||||
|
// This can be a default constructed object.
|
||||||
|
OffsetVector(Ty _invalid = Ty()) : invalid(_invalid), min_index(0), max_index(0), data(0, _invalid) {};
|
||||||
|
~OffsetVector() = default;
|
||||||
|
|
||||||
|
// reserves the give index
|
||||||
|
// Returns reference to the slot
|
||||||
|
Ty& reserve_at(const int32_t _index)
|
||||||
|
{
|
||||||
|
if (data.size() == 0)
|
||||||
|
{
|
||||||
|
grow(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_index > max_index)
|
||||||
|
{ // need to expand in positive direction
|
||||||
|
int32_t free = (int32_t)data.size() - (max_index - min_index + 1);
|
||||||
|
|
||||||
|
if (_index > max_index + free)
|
||||||
|
{ // need to grow
|
||||||
|
grow(_index - min_index + 1);
|
||||||
|
}
|
||||||
|
max_index = _index; // advance max_index
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_index < min_index)
|
||||||
|
{ // need to expand in negative direction
|
||||||
|
int32_t free = (int32_t)data.size() - (max_index - min_index + 1);
|
||||||
|
|
||||||
|
if (_index < min_index - free)
|
||||||
|
{ // need to grow
|
||||||
|
grow(max_index - _index + 1);
|
||||||
|
}
|
||||||
|
min_index = _index; // advance min_index
|
||||||
|
}
|
||||||
|
|
||||||
|
return data[wrap_index(_index)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience function. Should probably use reserve_at
|
||||||
|
// May overwrite an existing element
|
||||||
|
void insert_at(const int32_t _index, const Ty _element)
|
||||||
|
{
|
||||||
|
reserve_at(_index) = _element;
|
||||||
|
}
|
||||||
|
|
||||||
|
// May return a "invalid" aka not inserted element
|
||||||
|
Ty& get_at(const int32_t _index)
|
||||||
|
{
|
||||||
|
assert(_index >= min_index);
|
||||||
|
assert(_index <= max_index);
|
||||||
|
return data[wrap_index(_index)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrites the element at _index with the _invalid
|
||||||
|
void remove_at(const int32_t _index)
|
||||||
|
{
|
||||||
|
assert(_index >= min_index);
|
||||||
|
assert(_index <= max_index);
|
||||||
|
// Note: we do not erase from vector, we just set min/max_index, and reset the element to _invalid
|
||||||
|
data[wrap_index(_index)] = invalid;
|
||||||
|
|
||||||
|
if (_index == min_index)
|
||||||
|
{ // we have to increase min_index
|
||||||
|
while (min_index < 0 && data[wrap_index(min_index)] == invalid)
|
||||||
|
{
|
||||||
|
++min_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_index == max_index)
|
||||||
|
{ // we have to decrease max_index
|
||||||
|
while (max_index > 0 && data[wrap_index(max_index)] == invalid)
|
||||||
|
{
|
||||||
|
--max_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t size()
|
||||||
|
{
|
||||||
|
//return (min_index == max_index && data[0] == invalid) ? 0 : max_index - min_index + 1;
|
||||||
|
|
||||||
|
if (min_index == max_index)
|
||||||
|
{
|
||||||
|
assert(min_index == 0);
|
||||||
|
return (data[0] != invalid) ? 1 : 0;
|
||||||
|
}
|
||||||
|
return max_index - min_index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void shrink_to_fit()
|
||||||
|
{ // TODO
|
||||||
|
logErr("offset_vector.shrink_to_fit not implemented\n");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*/
|
||||||
83
src/data/queue.h
Normal file
83
src/data/queue.h
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
// simple single-threaded queue
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
class Queue
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::vector<T> buffer;
|
||||||
|
size_t next_push; // points to next free slot to be pushed
|
||||||
|
size_t next_pop; // points to next data slot to be popped
|
||||||
|
size_t size_mask;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Queue(size_t _initial_size = 2) :
|
||||||
|
buffer(0), next_push(0), next_pop(0), size_mask(1)
|
||||||
|
{
|
||||||
|
// Enforce size is power of 2
|
||||||
|
if (_initial_size < 2) { _initial_size = 2; }
|
||||||
|
size_t size = 2;
|
||||||
|
while (_initial_size > size)
|
||||||
|
{
|
||||||
|
size <<= 1;
|
||||||
|
size_mask = (size_mask << 1) | 0b1;
|
||||||
|
}
|
||||||
|
buffer.resize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
~Queue() = default;
|
||||||
|
|
||||||
|
inline size_t wrap(size_t idx) {
|
||||||
|
return idx & size_mask;
|
||||||
|
//return idx % buffer.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void push(const T _t)
|
||||||
|
{
|
||||||
|
buffer[next_push] = _t;
|
||||||
|
|
||||||
|
// check if theres is space available
|
||||||
|
if (wrap(next_push + 1) == next_pop)
|
||||||
|
{ // no more space => need to grow
|
||||||
|
buffer.resize(buffer.size() << 1);
|
||||||
|
size_mask = (size_mask << 1) | 0b1;
|
||||||
|
// move potential wrapped data at the end
|
||||||
|
if (next_pop > next_push)
|
||||||
|
{
|
||||||
|
size_t src = (buffer.size() >> 1) - 1; // old size -1
|
||||||
|
size_t dst = buffer.size() - 1;
|
||||||
|
while (src >= next_pop)
|
||||||
|
{
|
||||||
|
std::swap(buffer[dst], buffer[src]);
|
||||||
|
--dst;
|
||||||
|
--src;
|
||||||
|
}
|
||||||
|
next_pop = dst + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// advance next_push
|
||||||
|
next_push = wrap(next_push + 1);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// advance next_push
|
||||||
|
next_push = wrap(next_push + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true if at least one element is available
|
||||||
|
bool can_pop() {
|
||||||
|
return next_pop != next_push;
|
||||||
|
}
|
||||||
|
|
||||||
|
T pop()
|
||||||
|
{
|
||||||
|
T t = buffer[next_pop];
|
||||||
|
next_pop = wrap(next_pop + 1);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
};
|
||||||
120
src/data/slot_buffer.h
Normal file
120
src/data/slot_buffer.h
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
#include "util.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
// TODO specialization if T==int (or atleast sizeof(T) >= sizeof(int))
|
||||||
|
// store freelist in-place in the buffer itself
|
||||||
|
// free points to 0 (aka invalid) => use net_free/offset to find next free slot
|
||||||
|
// on remove(5), point 5 to where ever free points, point free to 5
|
||||||
|
// on add, just take the first value from free (or next_free if free==0)
|
||||||
|
|
||||||
|
|
||||||
|
// Auto growing buffer
|
||||||
|
// returns slot reference that stays valid until element is removed
|
||||||
|
// empty slots are reused
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
class SlotBuffer
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::vector<T> buffer;
|
||||||
|
uint32_t offset; // points to next free slot
|
||||||
|
std::vector<int32_t> free;
|
||||||
|
uint32_t modified_from; // inclusive
|
||||||
|
uint32_t modified_to; // inclusive
|
||||||
|
|
||||||
|
public:
|
||||||
|
SlotBuffer(const uint32_t _initial_size) :
|
||||||
|
buffer(_initial_size), offset(0), free(0),
|
||||||
|
modified_from(UINT32_MAX), modified_to(0)
|
||||||
|
{
|
||||||
|
if (_initial_size == 0 || _initial_size == UINT32_MAX) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~SlotBuffer() = default;
|
||||||
|
|
||||||
|
// Reserves a slot. Data MUST be initialized!
|
||||||
|
// Returns slot in _out_ref.
|
||||||
|
// Returns reference to element.
|
||||||
|
T& add(uint32_t& _out_slot)
|
||||||
|
{
|
||||||
|
uint32_t slot = 0;
|
||||||
|
if (!free.empty())
|
||||||
|
{ // reuse slot
|
||||||
|
slot = free.back();
|
||||||
|
free.pop_back();
|
||||||
|
}
|
||||||
|
else if (offset < buffer.size())
|
||||||
|
{ // use new slot
|
||||||
|
slot = offset;
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // no free slot => need to grow
|
||||||
|
if (config::DEBUG_TRACE_LOG_SLOT_BUFFER)
|
||||||
|
logTrace("slot_buffer resizing from %d to %d\n", buffer.size(), 2 * buffer.size());
|
||||||
|
// notify caller about change in size
|
||||||
|
modified_from = (uint32_t)buffer.size();
|
||||||
|
|
||||||
|
buffer.resize(2 * buffer.size());
|
||||||
|
|
||||||
|
modified_to = (uint32_t)buffer.size() - 1;
|
||||||
|
|
||||||
|
// now there is enough space
|
||||||
|
slot = offset;
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
modified_from = std::min(modified_from, slot);
|
||||||
|
modified_to = std::max(modified_to, slot);
|
||||||
|
|
||||||
|
_out_slot = slot;
|
||||||
|
return buffer[slot];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provides direct access to the element in _slot
|
||||||
|
// Does NOT perform bound checks.
|
||||||
|
// Element will considered modified.
|
||||||
|
inline T& at(const uint32_t _slot)
|
||||||
|
{
|
||||||
|
modified_from = std::min(modified_from, _slot);
|
||||||
|
modified_to = std::max(modified_to, _slot);
|
||||||
|
|
||||||
|
return buffer[_slot];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provides direct read-only access to the element in _slot
|
||||||
|
// Does NOT perform bound checks.
|
||||||
|
inline const T& at_const(const uint32_t _slot)
|
||||||
|
{
|
||||||
|
return buffer[_slot];
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(const uint32_t _slot)
|
||||||
|
{
|
||||||
|
free.push_back(_slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wasModified(uint32_t& _out_offset, uint32_t& _out_num_elements)
|
||||||
|
{
|
||||||
|
_out_offset = modified_from;
|
||||||
|
_out_num_elements = modified_to - modified_from + 1;
|
||||||
|
return modified_to >= modified_from;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearModified()
|
||||||
|
{
|
||||||
|
modified_from = UINT32_MAX;
|
||||||
|
modified_to = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* data()
|
||||||
|
{
|
||||||
|
return buffer.data();
|
||||||
|
}
|
||||||
|
};
|
||||||
196
src/data/slot_list.h
Normal file
196
src/data/slot_list.h
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
#include "util.h"
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
// Auto growing buffer
|
||||||
|
// returns slot reference that remains valid until element is removed
|
||||||
|
// removed slots are reused
|
||||||
|
// supports element iteration (in random order)
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
class SlotList
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
typedef struct header {
|
||||||
|
uint32_t prev;
|
||||||
|
uint32_t next;
|
||||||
|
} Header;
|
||||||
|
|
||||||
|
// contains links for every used slot to the next used slot
|
||||||
|
// or from a free slot to the next free slot
|
||||||
|
std::vector<Header> header_buffer;
|
||||||
|
// contains the used data
|
||||||
|
std::vector<T> data_buffer;
|
||||||
|
// points to the next available/free slot
|
||||||
|
uint32_t next_free;
|
||||||
|
uint32_t modified_from; // inclusive
|
||||||
|
uint32_t modified_to; // inclusive
|
||||||
|
|
||||||
|
const uint32_t first_free_slot = 0;
|
||||||
|
const uint32_t first_used_slot = 1;
|
||||||
|
|
||||||
|
// removes element at _slot from its current list (free or used)
|
||||||
|
void list_remove(const uint32_t _slot) {
|
||||||
|
assert(_slot != first_free_slot);
|
||||||
|
assert(_slot != first_used_slot);
|
||||||
|
assert(_slot != UINT32_MAX);
|
||||||
|
header_buffer[header_buffer[_slot].prev].next = header_buffer[_slot].next;
|
||||||
|
header_buffer[header_buffer[_slot].next].prev = header_buffer[_slot].prev;
|
||||||
|
}
|
||||||
|
// adds element at _slot to free list
|
||||||
|
void list_add_free(const uint32_t _slot) {
|
||||||
|
assert(_slot != first_free_slot);
|
||||||
|
assert(_slot != first_used_slot);
|
||||||
|
assert(_slot != UINT32_MAX);
|
||||||
|
header_buffer[_slot].next = header_buffer[first_free_slot].next;
|
||||||
|
header_buffer[_slot].prev = first_free_slot;
|
||||||
|
header_buffer[header_buffer[_slot].next].prev = _slot;
|
||||||
|
header_buffer[first_free_slot].next = _slot;
|
||||||
|
}
|
||||||
|
// adds element at _slot to used list
|
||||||
|
void list_add_used(const uint32_t _slot) {
|
||||||
|
assert(_slot != first_free_slot);
|
||||||
|
assert(_slot != first_used_slot);
|
||||||
|
assert(_slot != UINT32_MAX);
|
||||||
|
header_buffer[_slot].next = header_buffer[first_used_slot].next;
|
||||||
|
header_buffer[_slot].prev = first_used_slot;
|
||||||
|
header_buffer[header_buffer[_slot].next].prev = _slot;
|
||||||
|
header_buffer[first_used_slot].next = _slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
SlotList(const uint32_t _initial_size) :
|
||||||
|
header_buffer(_initial_size), data_buffer(_initial_size),
|
||||||
|
next_free(2), modified_from(UINT32_MAX), modified_to(0)
|
||||||
|
{
|
||||||
|
if (_initial_size < 4 || _initial_size == UINT32_MAX) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
header_buffer[first_free_slot].prev = first_free_slot;
|
||||||
|
header_buffer[first_free_slot].next = first_free_slot;
|
||||||
|
header_buffer[first_used_slot].prev = first_used_slot;
|
||||||
|
header_buffer[first_used_slot].next = first_used_slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
~SlotList() = default;
|
||||||
|
|
||||||
|
// Reserves a slot. Data is NOT automatically initialized!
|
||||||
|
// Returns slot in _out_ref.
|
||||||
|
// Returns reference to element.
|
||||||
|
// Element will considered modified.
|
||||||
|
T& add(uint32_t& _out_slot)
|
||||||
|
{
|
||||||
|
uint32_t slot = 0;
|
||||||
|
if (header_buffer[first_free_slot].next != first_free_slot)
|
||||||
|
{ // reuse slot
|
||||||
|
slot = header_buffer[first_free_slot].next;
|
||||||
|
// remove from free list
|
||||||
|
list_remove(slot);
|
||||||
|
}
|
||||||
|
else if (next_free < data_buffer.size())
|
||||||
|
{ // use new slot
|
||||||
|
slot = next_free;
|
||||||
|
next_free++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // no free slot => need to grow
|
||||||
|
if (config::DEBUG_TRACE_LOG_SLOT_LIST)
|
||||||
|
logTrace("slot_list resizing from %d to %d\n", data_buffer.size(), 2 * data_buffer.size());
|
||||||
|
// notify caller about change in size
|
||||||
|
modified_from = (uint32_t)data_buffer.size();
|
||||||
|
|
||||||
|
header_buffer.resize(2 * data_buffer.size());
|
||||||
|
data_buffer.resize(2 * data_buffer.size());
|
||||||
|
|
||||||
|
modified_to = (uint32_t)data_buffer.size() - 1;
|
||||||
|
|
||||||
|
// now there are free slots
|
||||||
|
slot = next_free;
|
||||||
|
next_free++;
|
||||||
|
}
|
||||||
|
|
||||||
|
modified_from = std::min(modified_from, slot);
|
||||||
|
modified_to = std::max(modified_to, slot);
|
||||||
|
|
||||||
|
// add to used list
|
||||||
|
list_add_used(slot);
|
||||||
|
|
||||||
|
_out_slot = slot;
|
||||||
|
return data_buffer[slot];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provides direct access to the element in _slot
|
||||||
|
// Does NOT perform bound checks.
|
||||||
|
// Element will considered modified.
|
||||||
|
inline T& at(const uint32_t _slot)
|
||||||
|
{
|
||||||
|
modified_from = std::min(modified_from, _slot);
|
||||||
|
modified_to = std::max(modified_to, _slot);
|
||||||
|
|
||||||
|
return data_buffer[_slot];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provides direct read-only access to the element in _slot
|
||||||
|
// Does NOT perform bound checks.
|
||||||
|
inline const T& at_const(const uint32_t _slot) const
|
||||||
|
{
|
||||||
|
return data_buffer[_slot];
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(const uint32_t _slot)
|
||||||
|
{
|
||||||
|
// remove from used list
|
||||||
|
list_remove(_slot);
|
||||||
|
// add to free list
|
||||||
|
list_add_free(_slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if there are no elements in the container
|
||||||
|
inline bool empty() const {
|
||||||
|
return header_buffer[first_used_slot].next == first_used_slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns slot of a random element
|
||||||
|
// Behaviour is undefined, if container is empty.
|
||||||
|
// Note: Use this function together with next() to iterate all elements
|
||||||
|
inline uint32_t first() const {
|
||||||
|
return header_buffer[first_used_slot].next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if element at _slot has a next element
|
||||||
|
inline bool has_next(const uint32_t _slot) const {
|
||||||
|
return header_buffer[_slot].next != first_used_slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns slot of the next element
|
||||||
|
// Behaviour is undefined, if _slot is invalid or 'has_next(_slot) == false'.
|
||||||
|
// Note: Use this function together with first() to iterate all elements
|
||||||
|
inline uint32_t next(const uint32_t _slot) const {
|
||||||
|
return header_buffer[_slot].next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns interval of internal data buffer which was modified (if any)
|
||||||
|
bool wasModified(uint32_t& _out_offset, uint32_t& _out_num_elements) const
|
||||||
|
{
|
||||||
|
_out_offset = modified_from;
|
||||||
|
_out_num_elements = modified_to - modified_from + 1;
|
||||||
|
return modified_to >= modified_from;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clears internal modified interval
|
||||||
|
void clearModified()
|
||||||
|
{
|
||||||
|
modified_from = UINT32_MAX;
|
||||||
|
modified_to = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns direct access to internal data buffer
|
||||||
|
T* data()
|
||||||
|
{
|
||||||
|
return data_buffer.data();
|
||||||
|
}
|
||||||
|
};
|
||||||
101
src/data/slot_queue.h
Normal file
101
src/data/slot_queue.h
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
// Auto growing, slot-based, thread safe queue
|
||||||
|
// Attention: Thread-safety applies only for 1 producer and 1 consumer thread!
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
class SlotQueue
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::vector<T> buffer;
|
||||||
|
size_t next_push; // points to next free slot to be pushed
|
||||||
|
size_t next_pop; // points to next data slot to be popped
|
||||||
|
size_t size_mask;
|
||||||
|
std::mutex mutex;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SlotQueue(size_t _initial_size = 2) :
|
||||||
|
buffer(0), next_push(0), next_pop(0), size_mask(1)
|
||||||
|
{
|
||||||
|
// Enforce size is power of 2
|
||||||
|
if (_initial_size < 2) { _initial_size = 2; }
|
||||||
|
size_t size = 2;
|
||||||
|
while (_initial_size > size)
|
||||||
|
{
|
||||||
|
size <<= 1;
|
||||||
|
size_mask = (size_mask << 1) | 0b1;
|
||||||
|
}
|
||||||
|
buffer.resize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
~SlotQueue() = default;
|
||||||
|
|
||||||
|
inline size_t wrap(size_t idx) {
|
||||||
|
return idx & size_mask;
|
||||||
|
//return idx % buffer.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a pointer to the next element at the end of the queue, to be produced
|
||||||
|
// Returned pointer is never NULL
|
||||||
|
T* begin_push()
|
||||||
|
{
|
||||||
|
return &buffer[next_push];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publishes last (with begin_push()) produced element
|
||||||
|
void end_push()
|
||||||
|
{
|
||||||
|
// check if theres is space available
|
||||||
|
if (wrap(next_push + 1) == next_pop)
|
||||||
|
{ // no more space => need to grow
|
||||||
|
mutex.lock();
|
||||||
|
buffer.resize(buffer.size() << 1);
|
||||||
|
size_mask = (size_mask << 1) | 0b1;
|
||||||
|
// move potential wrapped data at the end
|
||||||
|
if (next_pop > next_push)
|
||||||
|
{
|
||||||
|
size_t src = (buffer.size() >> 1) - 1; // old size -1
|
||||||
|
size_t dst = buffer.size() - 1;
|
||||||
|
while (src >= next_pop)
|
||||||
|
{
|
||||||
|
std::swap(buffer[dst], buffer[src]);
|
||||||
|
--dst;
|
||||||
|
--src;
|
||||||
|
}
|
||||||
|
next_pop = dst + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// advance next_push
|
||||||
|
next_push = wrap(next_push + 1);
|
||||||
|
|
||||||
|
mutex.unlock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// advance next_push
|
||||||
|
mutex.lock();
|
||||||
|
next_push = wrap(next_push + 1);
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns pointer to the element at the top of the queue, to be consumed
|
||||||
|
// Returns NULL if there is no element in the queue
|
||||||
|
// Can be used to just "peek" at the top element
|
||||||
|
T* begin_pop()
|
||||||
|
{
|
||||||
|
return (next_pop != next_push) ? &buffer[next_pop] : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Releases last (with begin_pop()) consumed element
|
||||||
|
// MUST only be called after a valid (non NULL) 'begin_pop()'
|
||||||
|
void end_pop()
|
||||||
|
{
|
||||||
|
mutex.lock();
|
||||||
|
next_pop = wrap(next_pop + 1);
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
||||||
|
};
|
||||||
63
src/data/texel_buffer.h
Normal file
63
src/data/texel_buffer.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
#include "util.h"
|
||||||
|
#include "../space_math.h"
|
||||||
|
#include <bx/bx.h>
|
||||||
|
|
||||||
|
class TexelBuffer
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::vector<Texel> buffer;
|
||||||
|
uint32_t modified_from; // inclusive
|
||||||
|
uint32_t modified_to; // inclusive
|
||||||
|
|
||||||
|
public:
|
||||||
|
TexelBuffer(const uint32_t _size) :
|
||||||
|
buffer(_size), modified_from(UINT32_MAX), modified_to(0)
|
||||||
|
{
|
||||||
|
if (_size == 0) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~TexelBuffer() = default;
|
||||||
|
|
||||||
|
// copies 4x4 texels from _data into the buffer at _texel_id
|
||||||
|
void update_texels(const uint32_t _texel_id, const Texel *_data) {
|
||||||
|
bx::memCopy(buffer.data() + _texel_id,
|
||||||
|
_data,
|
||||||
|
4 * sizeof(Texel));
|
||||||
|
bx::memCopy(buffer.data() + _texel_id + config::TA_TEXELS_PER_ROW,
|
||||||
|
_data + 4,
|
||||||
|
4 * sizeof(Texel));
|
||||||
|
bx::memCopy(buffer.data() + _texel_id + config::TA_TEXELS_PER_ROW * 2,
|
||||||
|
_data + 8,
|
||||||
|
4 * sizeof(Texel));
|
||||||
|
bx::memCopy(buffer.data() + _texel_id + config::TA_TEXELS_PER_ROW * 3,
|
||||||
|
_data + 12,
|
||||||
|
4 * sizeof(Texel));
|
||||||
|
|
||||||
|
modified_from = std::min(modified_from, _texel_id);
|
||||||
|
modified_to = std::max(modified_to, _texel_id + config::TA_TEXELS_PER_ROW * 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wasModified(uint32_t& _out_offset, uint32_t& _out_num_elements)
|
||||||
|
{
|
||||||
|
_out_offset = modified_from;
|
||||||
|
_out_num_elements = modified_to - modified_from + 1;
|
||||||
|
return modified_to >= modified_from;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearModified()
|
||||||
|
{
|
||||||
|
modified_from = UINT32_MAX;
|
||||||
|
modified_to = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Texel* data()
|
||||||
|
{
|
||||||
|
return buffer.data();
|
||||||
|
}
|
||||||
|
};
|
||||||
463
src/data/texture_atlas.h
Normal file
463
src/data/texture_atlas.h
Normal file
@@ -0,0 +1,463 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
#include "util.h"
|
||||||
|
#include "assert.h"
|
||||||
|
#include "slot_list.h"
|
||||||
|
#include "../space_math.h"
|
||||||
|
#include "texel_buffer.h"
|
||||||
|
#include "../config.h"
|
||||||
|
#include "stb_image.h"
|
||||||
|
#include "stb_image_write.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "../3rdparty/emilib/hash_map.hpp"
|
||||||
|
|
||||||
|
// Manages texturing data in a combined atlas
|
||||||
|
// all textures are stored in patches
|
||||||
|
// a patch is a region in the atlas
|
||||||
|
// a patch consists of 8x8 or smaller textures
|
||||||
|
// a texture consists of 4x4 texels
|
||||||
|
// each texture is addressed by a texture id
|
||||||
|
|
||||||
|
// TODO add normal mapping
|
||||||
|
|
||||||
|
// TODO OPT: Do not update entire rows of the atlas, but more localized blocks
|
||||||
|
// Ex.: Track modified main textures (sorted list), then when modified() is called,
|
||||||
|
// combine them into rectangular block.
|
||||||
|
|
||||||
|
// TODO consider
|
||||||
|
// Sidenote: if we de-couple the actual tex_atlas and the id_atlas
|
||||||
|
// we can generate patches in id_atlas only, without loading onto gpu right away
|
||||||
|
// then later on-demand copy texture into tex_atlas (and thereby onto gpu)
|
||||||
|
|
||||||
|
// Note to future self: TextureAtlas 2.0
|
||||||
|
// If we ever want to upgrade this implementation we should try to do so by
|
||||||
|
// considering triangular textures instead of quad textures
|
||||||
|
// Large trigs waste precious atlas space
|
||||||
|
// Would need to consider double-trigs
|
||||||
|
// => Maybe keep current api and detect empty patches, then
|
||||||
|
// just increase patch ref count
|
||||||
|
// and add sub-patches to pool of free patches (ensure freeing works!)
|
||||||
|
// This would imply proper patch-parenting
|
||||||
|
// Add parent patch_id to each patch and replace main patch logic with this
|
||||||
|
//
|
||||||
|
// Note: This also breaks our hashing, except we no actually consider the trig shape somehow
|
||||||
|
// Maybe combine this sub-texture deduplication?
|
||||||
|
|
||||||
|
// Helper classes for the texture deduplication
|
||||||
|
struct TextureHash {
|
||||||
|
const uint16_t width = 0;
|
||||||
|
const uint16_t height = 0;
|
||||||
|
const uint32_t stride = 0; // way too big, but alignment
|
||||||
|
const uint32_t* texture_ids = nullptr;
|
||||||
|
|
||||||
|
TextureHash() = default;
|
||||||
|
// stride describes how to index into texture_ids
|
||||||
|
// aka should be '8' for temporary 8x8 arrays and config::TA_TEXTURES_PER_ROW
|
||||||
|
TextureHash(const uint16_t _width, const uint16_t _height, const uint32_t _stride, const uint32_t* _texture_ids) : width(_width), height(_height), stride(_stride), texture_ids(_texture_ids) {};
|
||||||
|
|
||||||
|
bool operator==(const TextureHash &_o) const {
|
||||||
|
if (width != _o.width || height != _o.height)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool equal = true;
|
||||||
|
for (int y = 0; y < height; y++)
|
||||||
|
for (int x = 0; x < width; x++)
|
||||||
|
{
|
||||||
|
equal &= (texture_ids[x + y * stride] == _o.texture_ids[x + y * stride]);
|
||||||
|
}
|
||||||
|
return equal;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const int TextureHashSize = sizeof(TextureHash);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct std::hash<TextureHash>
|
||||||
|
{
|
||||||
|
size_t operator()(const TextureHash &_v) const noexcept
|
||||||
|
{
|
||||||
|
std::size_t h=0;
|
||||||
|
hash_combine(h, _v.width, _v.height);
|
||||||
|
for (int y = 0; y < _v.height; y++)
|
||||||
|
for (int x = 0; x < _v.width; x++)
|
||||||
|
hash_combine(h, _v.texture_ids[x + y * _v.stride]);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class TextureAtlas
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
typedef struct patch {
|
||||||
|
uint32_t refCount : 8 = 0; // steal some bits from texture_id for alignment reasons and since we don't need that many
|
||||||
|
uint32_t texture_id : 24 = 0; // offset to textures in atlas // TODO maybe store x&y offsets instead
|
||||||
|
uint16_t width = 0; // number of textures
|
||||||
|
uint16_t height = 0; // number of textures
|
||||||
|
} Patch;
|
||||||
|
static_assert(sizeof(Patch) == 8);
|
||||||
|
static_assert(config::TA_TOTAL_TEXTURES < (1 << 24));
|
||||||
|
|
||||||
|
// main patch == full 8x8 patch
|
||||||
|
typedef struct main_patch {
|
||||||
|
uint32_t used_count; // can be merged when ==0
|
||||||
|
std::vector<uint32_t> free_patches; // refs to patches
|
||||||
|
} MainPatch;
|
||||||
|
|
||||||
|
SlotList<Patch> patches = SlotList<Patch>(config::TA_INITIAL_NUMBER_PATCHES);
|
||||||
|
|
||||||
|
// "second" atlas with texture_ids (corresponding to the loaded texels) for deduplication
|
||||||
|
uint32_t textures[config::TA_TOTAL_TEXTURES] = {};
|
||||||
|
emilib::HashMap<TextureHash, uint32_t /*patch_id*/> texture_lookup;
|
||||||
|
|
||||||
|
// free patches sorted by size
|
||||||
|
// bins[0] = 1x1
|
||||||
|
// bins[1] = 1x2
|
||||||
|
// bins[2] = 1x3
|
||||||
|
// ...
|
||||||
|
// bins[TA_MAIN_PATCH_SIZE] = 2x1
|
||||||
|
std::vector<uint32_t> bins[config::TA_MAIN_PATCH_SIZE * config::TA_MAIN_PATCH_SIZE]; // holds refs to patches
|
||||||
|
|
||||||
|
// hold refs to sub patches for merging purposes
|
||||||
|
MainPatch main_patches[config::TA_WIDTH * config::TA_HEIGHT];
|
||||||
|
|
||||||
|
TexelBuffer texels = TexelBuffer(config::TA_TOTAL_TEXELS);
|
||||||
|
|
||||||
|
uint32_t main_patch_id_2_texture_id(uint32_t _id) {
|
||||||
|
const uint32_t x = _id % config::TA_WIDTH;
|
||||||
|
const uint32_t y = _id / config::TA_WIDTH;
|
||||||
|
return (y * config::TA_MAIN_PATCH_SIZE * config::TA_TEXTURES_PER_ROW) + (x * config::TA_MAIN_PATCH_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t texture_id_2_main_patch_id(uint32_t _id) {
|
||||||
|
uint32_t x = _id % config::TA_TEXTURES_PER_ROW; // texture offsets
|
||||||
|
uint32_t y = _id / config::TA_TEXTURES_PER_ROW;
|
||||||
|
x /= config::TA_MAIN_PATCH_SIZE; // main patch offsets
|
||||||
|
y /= config::TA_MAIN_PATCH_SIZE;
|
||||||
|
return y * config::TA_WIDTH + x;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t texture_id_2_texel_id(uint32_t _id) {
|
||||||
|
uint32_t x = _id % config::TA_TEXTURES_PER_ROW; // texture offsets
|
||||||
|
uint32_t y = _id / config::TA_TEXTURES_PER_ROW;
|
||||||
|
return (y * config::TA_TEXTURES_PER_ROW * config::TA_TEXELS_PER_TEXTURE) +
|
||||||
|
(x * config::TA_TEXTURE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saves the current texture atlas to a rgba8 texture (.png)
|
||||||
|
// Starts enumerating at 0 and incrementing on subsequent calls
|
||||||
|
// Resets suffix on startup
|
||||||
|
// Overwrites existing files
|
||||||
|
uint32_t counter = 0;
|
||||||
|
void save_texture_atlas_to_png()
|
||||||
|
{
|
||||||
|
std::string filename = "atlas";
|
||||||
|
std::filesystem::create_directories(filename);
|
||||||
|
filename += "/";
|
||||||
|
filename += std::to_string(counter);
|
||||||
|
filename += ".png";
|
||||||
|
|
||||||
|
const int width = config::TA_TEXELS_PER_ROW;
|
||||||
|
const int height = config::TA_HEIGHT * config::TA_MAIN_PATCH_SIZE * config::TA_TEXTURE_SIZE;
|
||||||
|
stbi_write_png(filename.c_str(), width, height, 4 /* channels (RGBA) */, texels.data(), config::TA_TEXELS_PER_ROW * sizeof(Texel));
|
||||||
|
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
TextureAtlas() = default;
|
||||||
|
~TextureAtlas() = default;
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
texture_lookup.reserve(config::TA_INITIAL_NUMBER_PATCHES);
|
||||||
|
|
||||||
|
// create main patches
|
||||||
|
for (int i = (config::TA_WIDTH * config::TA_HEIGHT) - 1; i >= 0; i--) {
|
||||||
|
uint32_t patch_id;
|
||||||
|
Patch &patch = patches.add(patch_id);
|
||||||
|
|
||||||
|
patch.texture_id = main_patch_id_2_texture_id(i);
|
||||||
|
patch.width = config::TA_MAIN_PATCH_SIZE;
|
||||||
|
patch.height = config::TA_MAIN_PATCH_SIZE;
|
||||||
|
|
||||||
|
// add to bins
|
||||||
|
bins[(config::TA_MAIN_PATCH_SIZE * config::TA_MAIN_PATCH_SIZE) - 1].push_back(patch_id);
|
||||||
|
|
||||||
|
// add to main patches
|
||||||
|
main_patches[i].used_count = 0;
|
||||||
|
main_patches[i].free_patches.push_back(patch_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private: // TODO move implementations into atlas.cpp and fix this
|
||||||
|
|
||||||
|
// finds fitting patch in atlas
|
||||||
|
// Supports deduplication
|
||||||
|
// returns patch_id
|
||||||
|
uint32_t get_new_patch(const uint8_t _width, const uint8_t _height) {
|
||||||
|
assert(_width > 0);
|
||||||
|
assert(_height > 0);
|
||||||
|
assert(_width <= 8);
|
||||||
|
assert(_height <= 8);
|
||||||
|
|
||||||
|
// search bins
|
||||||
|
uint32_t bin = (_width-1) * 8 + (_height-1);
|
||||||
|
while (bin < 64 && bins[bin].size() == 0) {
|
||||||
|
bin++; // try next bigger one
|
||||||
|
}
|
||||||
|
if (bin == 64) {
|
||||||
|
logErr("TextureAtlas overflow\n");
|
||||||
|
std::abort(); // Atlas overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove from bins
|
||||||
|
uint32_t patch_id = bins[bin].back();
|
||||||
|
bins[bin].pop_back();
|
||||||
|
|
||||||
|
Patch &patch = patches.at(patch_id);
|
||||||
|
|
||||||
|
// remove from main patch
|
||||||
|
MainPatch &main_patch = main_patches[texture_id_2_main_patch_id(patch.texture_id)];
|
||||||
|
main_patch.used_count++;
|
||||||
|
auto it = std::find(main_patch.free_patches.begin(), main_patch.free_patches.end(), patch_id);
|
||||||
|
assert(it != main_patch.free_patches.end());
|
||||||
|
main_patch.free_patches.erase(it);
|
||||||
|
|
||||||
|
// cut it up
|
||||||
|
// [X|ooo]
|
||||||
|
// [-----]
|
||||||
|
// [ooooo]
|
||||||
|
// [ooooo]
|
||||||
|
// [ooooo]
|
||||||
|
// & add any extra back into bins
|
||||||
|
|
||||||
|
// extra to the right
|
||||||
|
if (_width < patch.width) {
|
||||||
|
uint32_t right_patch_id;
|
||||||
|
Patch& right_patch = patches.add(right_patch_id);
|
||||||
|
|
||||||
|
right_patch.width = patch.width - _width;
|
||||||
|
assert(right_patch.width > 0 && right_patch.width < config::TA_MAIN_PATCH_SIZE);
|
||||||
|
right_patch.height = _height;
|
||||||
|
right_patch.texture_id = patch.texture_id + _width;
|
||||||
|
|
||||||
|
// add to bins
|
||||||
|
const uint32_t bin = ((right_patch.width-1) * config::TA_MAIN_PATCH_SIZE) + (right_patch.height-1);
|
||||||
|
bins[bin].push_back(right_patch_id);
|
||||||
|
|
||||||
|
// add to main patch
|
||||||
|
main_patch.free_patches.push_back(right_patch_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// extra below
|
||||||
|
if (_height < patch.height) {
|
||||||
|
uint32_t bottom_patch_id;
|
||||||
|
Patch& bottom_patch = patches.add(bottom_patch_id);
|
||||||
|
|
||||||
|
bottom_patch.width = patch.width;
|
||||||
|
bottom_patch.height = patch.height - _height;
|
||||||
|
assert(bottom_patch.height > 0 && bottom_patch.height < config::TA_MAIN_PATCH_SIZE);
|
||||||
|
bottom_patch.texture_id = patch.texture_id + (_height * config::TA_TEXTURES_PER_ROW);
|
||||||
|
|
||||||
|
// add to bins
|
||||||
|
const uint32_t bin = ((bottom_patch.width-1) * config::TA_MAIN_PATCH_SIZE) + (bottom_patch.height-1);
|
||||||
|
bins[bin].push_back(bottom_patch_id);
|
||||||
|
|
||||||
|
// add to main patch
|
||||||
|
main_patch.free_patches.push_back(bottom_patch_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
patch.refCount = 1;
|
||||||
|
patch.width = _width;
|
||||||
|
patch.height = _height;
|
||||||
|
|
||||||
|
return patch_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copies a texture into a patch
|
||||||
|
// _data MUST point to valid memory (depending on configured texture size)
|
||||||
|
// Note: always copies a full texture (aka TA_TEXTURE_SIZE*TA_TEXTURE_SIZE individual texels)
|
||||||
|
// Returns texture_id for gfx::vertex buffer generation
|
||||||
|
uint32_t update_texture(const Patch &patch, const uint8_t _offset_x, const uint8_t _offset_y, const uint32_t* _component_texture_ids) {
|
||||||
|
const Texel* data = gfx::get_component_texture(_component_texture_ids[_offset_x + _offset_y * 8]);
|
||||||
|
const uint32_t texture_id = patch.texture_id + _offset_y * config::TA_TEXTURES_PER_ROW + _offset_x;
|
||||||
|
|
||||||
|
assert(_offset_x < patch.width);
|
||||||
|
assert(_offset_y < patch.height);
|
||||||
|
|
||||||
|
// load texture_ids into "second" atlas for deduplication
|
||||||
|
textures[texture_id] = _component_texture_ids[_offset_x + _offset_y * 8];
|
||||||
|
|
||||||
|
// load texels into atlas
|
||||||
|
const uint32_t texel_id = texture_id_2_texel_id(texture_id);
|
||||||
|
texels.update_texels(texel_id, data);
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
if (config::DEBUG_SAVE_TEXTURE_ATLAS_TO_PNG)
|
||||||
|
save_texture_atlas_to_png();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return texture_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true on success, false otherwise
|
||||||
|
// _component_texture_ids expected to be inside an array of 8*8
|
||||||
|
// it is guaranteed not to access other _component_texture_ids as confined per _width and _height though
|
||||||
|
bool deduplicate_patch(const TextureHash& _key, uint32_t& _out_patch_id)
|
||||||
|
{
|
||||||
|
auto it = texture_lookup.find(_key);
|
||||||
|
if (it != texture_lookup.end())
|
||||||
|
{
|
||||||
|
_out_patch_id = it->second;
|
||||||
|
|
||||||
|
// increase ref count, but do not overflow
|
||||||
|
// once we hit the limit, we consider
|
||||||
|
// the patch promoted to an forever patch
|
||||||
|
Patch& patch = patches.at(_out_patch_id);
|
||||||
|
if (patch.refCount < 255)
|
||||||
|
patch.refCount++;
|
||||||
|
|
||||||
|
if (config::DEBUG_TRACE_LOG_FOREVER_PATCHES && patch.refCount == 255)
|
||||||
|
logTrace("[TextureAtlas] Patch %d hit a refcount of 255 and was promoted to a forever patch.\n", _out_patch_id);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public: // TODO move implementations into atlas.cpp and fix this
|
||||||
|
|
||||||
|
// Finds fitting patch in atlas
|
||||||
|
// Supports deduplication
|
||||||
|
// returns patch_id
|
||||||
|
// _component_texture_ids expected to be inside an array of 8*8
|
||||||
|
// it is guaranteed not to access other _component_texture_ids as confined per _width and _height though
|
||||||
|
uint32_t add_patch(const uint8_t _width, const uint8_t _height, const uint32_t* _component_texture_ids) {
|
||||||
|
assert(_width > 0);
|
||||||
|
assert(_height > 0);
|
||||||
|
assert(_width <= 8);
|
||||||
|
assert(_height <= 8);
|
||||||
|
|
||||||
|
uint32_t patch_id = 0;
|
||||||
|
|
||||||
|
// try to de-duplicate the given texture
|
||||||
|
if (deduplicate_patch(TextureHash(_width, _height, 8, _component_texture_ids), patch_id))
|
||||||
|
return patch_id;
|
||||||
|
|
||||||
|
// else get new patch and copy data
|
||||||
|
patch_id = get_new_patch(_width, _height);
|
||||||
|
const Patch &patch = patches.at_const(patch_id);
|
||||||
|
|
||||||
|
// 8*8 texels
|
||||||
|
for (int x = 0; x < _width; x++)
|
||||||
|
for (int y = 0; y < _height; y++)
|
||||||
|
update_texture(patch, x, y, _component_texture_ids);
|
||||||
|
|
||||||
|
// add this patch the texture_lookup
|
||||||
|
texture_lookup.insert_unique(std::move(TextureHash(_width, _height, config::TA_TEXTURES_PER_ROW, &textures[patch.texture_id])), std::move(patch_id));
|
||||||
|
|
||||||
|
return patch_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns 8 texture ids (for vertex buffer generation) of given patch
|
||||||
|
void get_patch_texture_ids(const uint32_t _patch_id, uint32_t* _out_texture_ids)
|
||||||
|
{
|
||||||
|
Patch &patch = patches.at(_patch_id);
|
||||||
|
|
||||||
|
_out_texture_ids[TexCorner::TopLeft] = patch.texture_id;
|
||||||
|
|
||||||
|
// TexCorner for sampling is = (trigSize%2==0) ? TL : CL;
|
||||||
|
_out_texture_ids[TexCorner::CenterLeft] = patch.texture_id + ((patch.height / (uint32_t)2) * config::TA_TEXTURES_PER_ROW);
|
||||||
|
|
||||||
|
_out_texture_ids[TexCorner::BotLeft] = patch.texture_id + ((patch.height - 1) * config::TA_TEXTURES_PER_ROW);
|
||||||
|
|
||||||
|
// TexCorner for sampling is = (trigSize%2==0) ? BL : CB;
|
||||||
|
_out_texture_ids[TexCorner::CenterBot] = _out_texture_ids[TexCorner::BotLeft] + (patch.width / (uint32_t)2);
|
||||||
|
|
||||||
|
_out_texture_ids[TexCorner::BotRight] = _out_texture_ids[TexCorner::BotLeft] + (patch.width - 1);
|
||||||
|
|
||||||
|
// TexCorner for sampling is = (trigSize%2==0) ? TR : CR;
|
||||||
|
_out_texture_ids[TexCorner::CenterRight] = _out_texture_ids[TexCorner::CenterLeft] + (patch.width - 1);
|
||||||
|
|
||||||
|
_out_texture_ids[TexCorner::TopRight] = patch.texture_id + (patch.width - 1);
|
||||||
|
|
||||||
|
// TexCorner for sampling is = (trigSize%2==0) ? TL : CT;
|
||||||
|
_out_texture_ids[TexCorner::CenterTop] = patch.texture_id + (patch.width / (uint32_t)2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// frees this patch's space in the atlas
|
||||||
|
void remove_patch(const uint32_t _patch_id) {
|
||||||
|
Patch &patch = patches.at(_patch_id);
|
||||||
|
|
||||||
|
// decrement patch ref count
|
||||||
|
// but do not demote forever patches
|
||||||
|
if (patch.refCount < 255)
|
||||||
|
patch.refCount--;
|
||||||
|
if (patch.refCount > 0)
|
||||||
|
return; // do no (yet) remove it
|
||||||
|
|
||||||
|
// remove it from the texture_lookup
|
||||||
|
bool ok = texture_lookup.erase(TextureHash(patch.width, patch.height, config::TA_TEXTURES_PER_ROW, &textures[patch.texture_id]));
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
// decrement main patch used counter
|
||||||
|
const uint32_t main_patch_id = texture_id_2_main_patch_id(patch.texture_id);
|
||||||
|
MainPatch &main_patch = main_patches[main_patch_id];
|
||||||
|
assert(main_patch.used_count > 0);
|
||||||
|
main_patch.used_count--;
|
||||||
|
|
||||||
|
// merge main patch?
|
||||||
|
if (main_patch.used_count == 0) {
|
||||||
|
logDebug("[TextureAtlas], merging main patch #%d\n", main_patch_id);
|
||||||
|
// remove sibling patches
|
||||||
|
for (uint32_t id : main_patch.free_patches) {
|
||||||
|
const Patch &patch = patches.at_const(id);
|
||||||
|
const uint32_t bin = ((patch.width-1) * config::TA_MAIN_PATCH_SIZE) + (patch.height-1);
|
||||||
|
auto it = std::find(bins[bin].begin(), bins[bin].end(), id);
|
||||||
|
assert(it != bins[bin].end());
|
||||||
|
bins[bin].erase(it);
|
||||||
|
|
||||||
|
patches.remove(id);
|
||||||
|
}
|
||||||
|
main_patch.free_patches.clear();
|
||||||
|
|
||||||
|
// add main patch (reuse this patch)
|
||||||
|
patch.texture_id = main_patch_id;
|
||||||
|
patch.width = config::TA_MAIN_PATCH_SIZE;
|
||||||
|
patch.height = config::TA_MAIN_PATCH_SIZE;
|
||||||
|
main_patch.free_patches.push_back(_patch_id);
|
||||||
|
|
||||||
|
// add to bins
|
||||||
|
bins[(config::TA_MAIN_PATCH_SIZE * config::TA_MAIN_PATCH_SIZE) - 1].push_back(_patch_id);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, make patch available again
|
||||||
|
// add to bins
|
||||||
|
const uint32_t bin = ((patch.width-1) * config::TA_MAIN_PATCH_SIZE) + (patch.height-1);
|
||||||
|
bins[bin].push_back(_patch_id);
|
||||||
|
|
||||||
|
// add to main patch
|
||||||
|
main_patch.free_patches.push_back(_patch_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool wasModified(uint32_t& _out_offset, uint32_t& _out_num_elements)
|
||||||
|
{
|
||||||
|
return texels.wasModified(_out_offset, _out_num_elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearModified()
|
||||||
|
{
|
||||||
|
texels.clearModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
Texel* data()
|
||||||
|
{
|
||||||
|
return texels.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
1484
src/factory.cpp
Normal file
1484
src/factory.cpp
Normal file
File diff suppressed because it is too large
Load Diff
27
src/factory.h
Normal file
27
src/factory.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "space_math.h"
|
||||||
|
#include "graphics.h"
|
||||||
|
#include "world.h"
|
||||||
|
|
||||||
|
namespace factory
|
||||||
|
{
|
||||||
|
// Run the amazing block welding algorithm
|
||||||
|
// Returns true on success, false otherwise
|
||||||
|
bool weld_block(BlockModel& _model);
|
||||||
|
|
||||||
|
// Returns pointer to generated data
|
||||||
|
// Data array is always 24 elements long
|
||||||
|
// Attention: Data ist only valid until next call
|
||||||
|
GPULineVertex *generate_AABB_outline(const AABB &_aabb);
|
||||||
|
|
||||||
|
// Returns pointer to generated data
|
||||||
|
// Attention: Data ist only valid until next call
|
||||||
|
uint32_t *generate_debug_full_block_1_model(); // 36 elements
|
||||||
|
uint32_t *generate_debug_full_block_2_model(); // 36 elements
|
||||||
|
uint32_t *generate_debug_full_block_3_model(); // 36 elements
|
||||||
|
uint32_t *generate_debug_pyramid_block_model(); // 18 elements
|
||||||
|
|
||||||
|
void generate_block_outline();
|
||||||
|
|
||||||
|
}
|
||||||
448
src/gameloop.cpp
Normal file
448
src/gameloop.cpp
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
#include "gameloop.h"
|
||||||
|
#include <GLFW/glfw3.h>
|
||||||
|
#include <cfloat>
|
||||||
|
|
||||||
|
#include "graphics.h"
|
||||||
|
#include "world.h"
|
||||||
|
#include "renderer.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "factory.h"
|
||||||
|
#include "lib/camera.h"
|
||||||
|
#include "space_input.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
std::atomic_bool game_shutdown_requested = false;
|
||||||
|
// std::atomic_flag frame_ready_flag = ATOMIC_FLAG_INIT; // frame was rendered - ready for new data
|
||||||
|
// std::atomic_flag input_ready_flag = ATOMIC_FLAG_INIT; // input was updated - new states can be read from the input system
|
||||||
|
// std::atomic_flag bgfx_ready_flag = ATOMIC_FLAG_INIT; // bgfx init is done
|
||||||
|
std::atomic_bool frame_ready_flag; // frame was rendered - ready for new data
|
||||||
|
std::atomic_bool input_ready_flag; // input was updated - new states can be read from the input system
|
||||||
|
std::atomic_bool bgfx_ready_flag; // bgfx init is done
|
||||||
|
SlotQueue<Event> event_queue(4);
|
||||||
|
uint64_t frameCounter = 0;
|
||||||
|
uint32_t resetFlags = BGFX_RESET_VSYNC;
|
||||||
|
Camera camera;
|
||||||
|
extern uint32_t window_width; // defined in main.cpp
|
||||||
|
extern uint32_t window_height; // we only read the value here
|
||||||
|
|
||||||
|
void init(bgfx::PlatformData *_platformData)
|
||||||
|
{
|
||||||
|
// Initialize bgfx using the native window handle
|
||||||
|
bgfx::Init init;
|
||||||
|
init.type = bgfx::RendererType::Vulkan;
|
||||||
|
init.platformData = *_platformData;
|
||||||
|
init.resolution.width = window_width;
|
||||||
|
init.resolution.height = window_height;
|
||||||
|
init.resolution.reset = resetFlags;
|
||||||
|
if (!bgfx::init(init))
|
||||||
|
DIE("Could not initialize bgfx\n");
|
||||||
|
|
||||||
|
// Signal main thread that initialization is done
|
||||||
|
// bgfx_ready_flag.test_and_set();
|
||||||
|
bgfx_ready_flag = true;
|
||||||
|
|
||||||
|
bgfx::setViewClear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x443355FF);
|
||||||
|
bgfx::setViewRect(0, 0, 0, bgfx::BackbufferRatio::Equal);
|
||||||
|
|
||||||
|
// Render initial frame#0
|
||||||
|
flag_wait(&input_ready_flag);
|
||||||
|
bgfx::touch(0);
|
||||||
|
bgfx::frame();
|
||||||
|
|
||||||
|
// Perform startup checks and abort if necessary
|
||||||
|
startup_checks();
|
||||||
|
|
||||||
|
// Initialize systems
|
||||||
|
gfx::init();
|
||||||
|
world::init();
|
||||||
|
|
||||||
|
// Camera
|
||||||
|
camera = camera_init();
|
||||||
|
camera.target_position = {0, 1, -10};
|
||||||
|
camera.mode = CAMERA_MODE_FIRST_PERSON;
|
||||||
|
camera.minPitch = -bx::kPiHalf;
|
||||||
|
camera.maxPitch = bx::kPiHalf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void debug_populate_world()
|
||||||
|
{
|
||||||
|
// Add some debug things to the world
|
||||||
|
|
||||||
|
// add models
|
||||||
|
// BlockModel (Single Debug Component)
|
||||||
|
uint32_t m0, m1, m2, m3;
|
||||||
|
|
||||||
|
m0 = world::add_block_model();
|
||||||
|
uint32_t comps[512] = {};
|
||||||
|
comps[0] = 24; // debug component cube
|
||||||
|
world::set_block_model_components(m0, comps);
|
||||||
|
|
||||||
|
m1 = world::add_block_model();
|
||||||
|
bx::memSet(comps, 0x0, 512*sizeof(int32_t));
|
||||||
|
comps[1] = 24; // debug component cube
|
||||||
|
comps[2] = 24; // debug component cube
|
||||||
|
world::set_block_model_components(m1, comps);
|
||||||
|
|
||||||
|
// TODO add different models
|
||||||
|
m2 = m3 = m0;
|
||||||
|
|
||||||
|
// add grids
|
||||||
|
uint32_t g0, g1;
|
||||||
|
g0 = world::add_grid(Vec3(0.0f), Quat::unit());
|
||||||
|
// Vec3 g1_pos = Vec3(-5.0f, -5.0f, -5.0f);
|
||||||
|
Vec3 g1_pos = Vec3(0.0f, -0.5f, -7.0f);
|
||||||
|
// g1 = world::add_grid(g1_pos, bx::fromEuler(Vec3(bx::kPiQuarter)));
|
||||||
|
g1 = world::add_grid(g1_pos, Quat::unit());
|
||||||
|
|
||||||
|
// add chunks
|
||||||
|
uint32_t c0, c1, c2, c3;
|
||||||
|
c0 = world::add_chunk(g0, 0, 0, 0);
|
||||||
|
c1 = world::add_chunk(g0, 1, 0, 0);
|
||||||
|
c2 = world::add_chunk(g1, 0, 0, 0);
|
||||||
|
c3 = world::add_chunk(g0, 1, 1, 0);
|
||||||
|
|
||||||
|
// add a few blocks
|
||||||
|
uint32_t b0, b1, b2, b3, b4;
|
||||||
|
b0 = world::add_block(c0, block_transform_pack(7, 2, 1, 0), m0, m0);
|
||||||
|
b1 = world::add_block(c1, block_transform_pack(0, 2, 1, 0), m1, m1);
|
||||||
|
b2 = world::add_block(c2, block_transform_pack(0, 0, 0, 0), m1, m1);
|
||||||
|
b3 = world::add_block(c2, block_transform_pack(1, 0, 0, 0), m2, m2);
|
||||||
|
|
||||||
|
b4 = world::add_block(c0, block_transform_pack(0, 0, 0, 0), m2, m2);
|
||||||
|
b4 = world::add_block(c0, block_transform_pack(0, 2, 0, 0), m1, m1);
|
||||||
|
b4 = world::add_block(c0, block_transform_pack(0, 4, 0, 0), m1, m1);
|
||||||
|
b4 = world::add_block(c0, block_transform_pack(0, 6, 0, 0), m1, m1);
|
||||||
|
b4 = world::add_block(c0, block_transform_pack(0, 7, 0, 0), m2, m2);
|
||||||
|
b4 = world::add_block(c0, block_transform_pack(0, 0, 7, 0), m2, m2);
|
||||||
|
b4 = world::add_block(c0, block_transform_pack(7, 0, 0, 0), m2, m3);
|
||||||
|
|
||||||
|
// setup view into world
|
||||||
|
camera_look_at(&camera, bx::sub(Vec3(0.0f), camera_eye(&camera)), Vec3(0.0f, 1.0f, 0.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool debug_animate = true;
|
||||||
|
void debug_animate_world()
|
||||||
|
{
|
||||||
|
if (debug_animate)
|
||||||
|
{
|
||||||
|
const float increment = bx::toRad(0.2f);
|
||||||
|
|
||||||
|
for (uint32_t grid_id : world::get_grid_list())
|
||||||
|
{
|
||||||
|
if (grid_id == 2)
|
||||||
|
{
|
||||||
|
const Grid &grid = world::get_grid(grid_id);
|
||||||
|
Vec3 pos = grid.position;
|
||||||
|
pos.x = pos.x * bx::cos(increment) - pos.z * bx::sin(increment);
|
||||||
|
pos.z = pos.x * bx::sin(increment) + pos.z * bx::cos(increment);
|
||||||
|
world::update_grid_position(grid_id, pos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_animate = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_event_queue()
|
||||||
|
{
|
||||||
|
// Handle main thread events
|
||||||
|
for (Event *ev = event_queue.begin_pop(); ev != NULL; event_queue.end_pop(), ev = event_queue.begin_pop())
|
||||||
|
{
|
||||||
|
switch (ev->type)
|
||||||
|
{
|
||||||
|
case Resize:
|
||||||
|
bgfx::reset(window_width, window_height, resetFlags);
|
||||||
|
bgfx::setViewRect(0, 0, 0, bgfx::BackbufferRatio::Equal);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
logWarn("main: Encountered unknown event type => ignoring\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t runGameloopThread(bx::Thread *_thread_self, void *_platformData)
|
||||||
|
{
|
||||||
|
using input::Axis;
|
||||||
|
using input::Button;
|
||||||
|
|
||||||
|
init((bgfx::PlatformData *)_platformData);
|
||||||
|
|
||||||
|
debug_populate_world();
|
||||||
|
|
||||||
|
uint16_t num_block_selection = 0;
|
||||||
|
|
||||||
|
while (!game_shutdown_requested)
|
||||||
|
{
|
||||||
|
|
||||||
|
flag_wait(&input_ready_flag); // wait for input updates to be written to input system
|
||||||
|
|
||||||
|
// input::detect_input();
|
||||||
|
|
||||||
|
camera_move(&camera, Vec3(
|
||||||
|
input::axis_value_is(Axis::MoveForward),
|
||||||
|
input::axis_value_is(Axis::MoveUp),
|
||||||
|
input::axis_value_is(Axis::MoveRight)));
|
||||||
|
|
||||||
|
camera_rotate(&camera, Vec3(
|
||||||
|
input::axis_value_is(Axis::CameraPitch),
|
||||||
|
input::axis_value_is(Axis::CameraYaw),
|
||||||
|
0.0f));
|
||||||
|
|
||||||
|
if (input::button_pressed(Button::LookAtOrigin))
|
||||||
|
{
|
||||||
|
const Vec3 dir = cm_normalizeVec3(cm_negate(camera_eye(&camera)));
|
||||||
|
camera_look_at(&camera, dir, CAMERA_WORLD_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
game_shutdown_requested = input::button_released(Button::GameShouldExit);
|
||||||
|
|
||||||
|
// At this point we want to make sure that the frame actually got rendered, before we update world data.
|
||||||
|
// So we wait for the rendering thread to signal this.
|
||||||
|
// TODO use bgfx::update(, releaseFunction) to determine when updating of individual buffer is allowed
|
||||||
|
// instead of this global flag. Maybe even allow per buffer granularity.
|
||||||
|
flag_wait(&frame_ready_flag); // wait for last frame to be rendered before preparing new one (aka writing to buffers)
|
||||||
|
|
||||||
|
// logTrace(mfc, "API-THREAD: frame #%d\n", frameCounter);
|
||||||
|
|
||||||
|
// Handle inputs etc.
|
||||||
|
handle_event_queue();
|
||||||
|
|
||||||
|
bgfx::dbgTextClear();
|
||||||
|
|
||||||
|
// This dummy draw call is here to make sure that view 0 is cleared if no other draw calls are submitted to view 0.
|
||||||
|
bgfx::touch(0);
|
||||||
|
|
||||||
|
// Enable stats or debug text.
|
||||||
|
bgfx::setDebug(input::button_is_down(Button::ShowStats) ? BGFX_DEBUG_STATS : BGFX_DEBUG_TEXT);
|
||||||
|
|
||||||
|
float view[16];
|
||||||
|
camera_view_matrix(&camera, view);
|
||||||
|
|
||||||
|
float proj[16];
|
||||||
|
bx::mtxProj(proj, 60.0f, float(window_width) / float(window_height), 0.1f, 100.0f, bgfx::getCaps()->homogeneousDepth);
|
||||||
|
bgfx::setViewTransform(0, view, proj);
|
||||||
|
|
||||||
|
debug_animate_world(); // Move things around
|
||||||
|
|
||||||
|
// Update Game State
|
||||||
|
world::update();
|
||||||
|
num_block_selection = gfx::get_num_block_selection(); // Note: gfx::update() clears this to 0
|
||||||
|
gfx::update(); // upload new data onto GPU
|
||||||
|
|
||||||
|
// generate block instance matrices
|
||||||
|
renderer::dispatch_cubes_cs(camera_eye(&camera), num_block_selection);
|
||||||
|
// draw the prepared indirect buffer
|
||||||
|
renderer::draw_cubes(num_block_selection);
|
||||||
|
|
||||||
|
// Block picking
|
||||||
|
Vec4 red(1.0, 0.0, 0.0, 1.0);
|
||||||
|
Vec4 green(0.0, 1.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
Ray ray = {
|
||||||
|
.position = camera_eye(&camera),
|
||||||
|
.direction = camera_forward(&camera)};
|
||||||
|
// InvRay inv_ray = InverseRay(ray);
|
||||||
|
|
||||||
|
float grid_mtx[16];
|
||||||
|
float chunk_mtx[16];
|
||||||
|
float block_mtx[16];
|
||||||
|
float inverse_mtx[16];
|
||||||
|
|
||||||
|
// iterate all grids
|
||||||
|
// TODO optimize with manual ray position offset instead of inv_matrices
|
||||||
|
// maybe even sorted testing? (a grid further away than the closest block is irrelevant?)
|
||||||
|
// maybe algorithmic solution? walk ray through 3D space and query specific chunks/blocks?
|
||||||
|
const std::vector<uint32_t> &grids = world::get_grid_list();
|
||||||
|
float grid_distance = FLT_MAX, chunk_distance = FLT_MAX, block_distance = FLT_MAX;
|
||||||
|
float closest_grid_distance = FLT_MAX, closest_chunk_distance = FLT_MAX, closest_block_distance = FLT_MAX;
|
||||||
|
uint32_t closest_grid_id = 0, closest_chunk_id = 0, closest_block_id = 0;
|
||||||
|
for (uint32_t grid_id : grids)
|
||||||
|
{
|
||||||
|
const Grid &grid = world::get_grid(grid_id);
|
||||||
|
world::get_grid_transform_mtx(grid_id, grid_mtx);
|
||||||
|
bx::mtxInverse(inverse_mtx, grid_mtx);
|
||||||
|
if (CheckCollisionRayOrientedBox(ray, grid.aabb, inverse_mtx, grid_distance))
|
||||||
|
{ // hit grid aabb => check its chunks
|
||||||
|
|
||||||
|
if (closest_chunk_id == 0 && grid_distance < closest_grid_distance)
|
||||||
|
{
|
||||||
|
closest_grid_distance = grid_distance;
|
||||||
|
closest_grid_id = grid_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t chunk_id : grid.chunk_id_list)
|
||||||
|
{
|
||||||
|
world::get_chunk_transform_mtx(chunk_id, chunk_mtx);
|
||||||
|
bx::mtxInverse(inverse_mtx, chunk_mtx);
|
||||||
|
if (CheckCollisionRayOrientedBox(ray, CHUNK_AABB, inverse_mtx, chunk_distance))
|
||||||
|
{ // hit chunk aabb => check its blocks
|
||||||
|
|
||||||
|
if (closest_block_id == 0 && chunk_distance < closest_chunk_distance)
|
||||||
|
{
|
||||||
|
closest_grid_distance = grid_distance;
|
||||||
|
closest_grid_id = grid_id;
|
||||||
|
closest_chunk_distance = chunk_distance;
|
||||||
|
closest_chunk_id = chunk_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Chunk &chunk = world::get_chunk(chunk_id);
|
||||||
|
for (uint32_t block_id : chunk.block_ids)
|
||||||
|
{
|
||||||
|
world::get_block_transform_mtx(block_id, block_mtx);
|
||||||
|
bx::mtxInverse(inverse_mtx, block_mtx);
|
||||||
|
if (CheckCollisionRayOrientedBox(ray, BLOCK_AABB, inverse_mtx, block_distance) && block_distance < closest_block_distance)
|
||||||
|
{ // hit block aabb
|
||||||
|
closest_grid_distance = grid_distance;
|
||||||
|
closest_grid_id = grid_id;
|
||||||
|
closest_chunk_distance = chunk_distance;
|
||||||
|
closest_chunk_id = chunk_id;
|
||||||
|
closest_block_id = block_id;
|
||||||
|
closest_block_distance = block_distance;
|
||||||
|
|
||||||
|
// TODO perform geometry collision check aka ray-triangle-checks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw outlines + info text
|
||||||
|
Vec3 eye = camera_eye(&camera);
|
||||||
|
Vec3 forward = camera_forward(&camera);
|
||||||
|
Vec3 point;
|
||||||
|
if (closest_grid_id > 0)
|
||||||
|
{
|
||||||
|
const Vec4 grid_color = Vec4(114.0f / 255.0f, 159.0f / 255.0f, 207.0f / 255.0f, 1.0f);
|
||||||
|
world::get_grid_transform_mtx(closest_grid_id, grid_mtx);
|
||||||
|
renderer::draw_lines(gfx::get_aabb_outline(world::get_grid(closest_grid_id).aabb), grid_color, grid_mtx);
|
||||||
|
|
||||||
|
point = bx::add(eye, bx::mul(forward, closest_grid_distance));
|
||||||
|
bgfx::dbgTextPrintf(0, 0, 0x09, "Grid @ %5.2f, %5.2f, %5.2f dist=%5.2f", point.x, point.y, point.z, closest_grid_distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closest_chunk_id > 0)
|
||||||
|
{
|
||||||
|
const Vec4 chunk_color = Vec4(52.0f / 255.0f, 226.0f / 255.0f, 226.0f / 255.0f, 1.0f);
|
||||||
|
world::get_chunk_transform_mtx(closest_chunk_id, chunk_mtx);
|
||||||
|
renderer::draw_lines(gfx::get_chunk_aabb_outline(), chunk_color, chunk_mtx);
|
||||||
|
|
||||||
|
point = bx::add(eye, bx::mul(forward, closest_chunk_distance));
|
||||||
|
bgfx::dbgTextPrintf(0, 1, 0x0B, "Chunk @ %5.2f, %5.2f, %5.2f dist=%5.2f", point.x, point.y, point.z, closest_chunk_distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closest_block_id > 0)
|
||||||
|
{
|
||||||
|
const Vec4 block_color = Vec4(196.0f / 255.0f, 160.0f / 255.0f, 0.0f, 1.0f);
|
||||||
|
world::get_block_transform_mtx(closest_block_id, block_mtx);
|
||||||
|
renderer::draw_lines(gfx::get_block_aabb_outline(), block_color, block_mtx);
|
||||||
|
|
||||||
|
point = bx::add(eye, bx::mul(forward, closest_block_distance));
|
||||||
|
bgfx::dbgTextPrintf(0, 2, 0x06, "Block @ %5.2f, %5.2f, %5.2f dist=%5.2f", point.x, point.y, point.z, closest_block_distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate Block
|
||||||
|
if (closest_block_id > 0 && input::button_pressed(Button::RotateBlockInc))
|
||||||
|
{
|
||||||
|
world::rotate_block(closest_block_id, true);
|
||||||
|
}
|
||||||
|
else if (closest_block_id > 0 && input::button_pressed(Button::RotateBlockDec))
|
||||||
|
{
|
||||||
|
world::rotate_block(closest_block_id, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove Block
|
||||||
|
else if (closest_block_id > 0 && input::button_pressed(Button::RemoveBlock))
|
||||||
|
{ // there is a block in sight
|
||||||
|
world::remove_block(closest_block_id); // TODO maybe only stop from being selected for rendering and wait for server ack/refuse
|
||||||
|
// maybe remove from the world, but keep around? PS do not decrement model counter -> gpu unload? world puts it on sideline(just slotbuffer thing with linear search)
|
||||||
|
// Note: We do not remove chunks or grids. Not even when empty. The server will tell us when to.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place Block
|
||||||
|
else if (closest_block_id > 0)
|
||||||
|
{
|
||||||
|
// Calc new block direction
|
||||||
|
Vec3 hitpoint = bx::add(eye, bx::mul(forward, closest_block_distance));
|
||||||
|
Vec3 block_center = world::get_block_world_position(closest_block_id);
|
||||||
|
|
||||||
|
Vec3 dir = bx::sub(hitpoint, block_center);
|
||||||
|
dir = bx::mul(dir, 2.001f); // extend 0.5 coordinates to <1.0
|
||||||
|
int8_t x = (int8_t)dir.x; // These are now either -1, 0 or 1
|
||||||
|
int8_t y = (int8_t)dir.y; // We only allow one direction through
|
||||||
|
int8_t z = (int8_t)dir.z;
|
||||||
|
assert(x == -1 || x == 0 || x == 1);
|
||||||
|
assert(y == -1 || y == 0 || y == 1);
|
||||||
|
assert(z == -1 || z == 0 || z == 1);
|
||||||
|
if (x != 0)
|
||||||
|
{
|
||||||
|
y = z = 0;
|
||||||
|
}
|
||||||
|
if (y != 0)
|
||||||
|
{
|
||||||
|
x = z = 0;
|
||||||
|
}
|
||||||
|
if (z != 0)
|
||||||
|
{
|
||||||
|
x = y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get selected block and chunk coordinates in chunk
|
||||||
|
const Block &block = world::get_block(closest_block_id);
|
||||||
|
const Chunk &chunk = world::get_chunk(block.parent_ref);
|
||||||
|
|
||||||
|
uint16_t block_offset_x, block_offset_y, block_offset_z, block_orientation;
|
||||||
|
block_transform_unpack(block.transform, block_offset_x, block_offset_y, block_offset_z, block_orientation);
|
||||||
|
|
||||||
|
uint16_t chunk_offset_x = chunk.parent_offset_x << 3; // <<3 == *8
|
||||||
|
uint16_t chunk_offset_y = chunk.parent_offset_y << 3;
|
||||||
|
uint16_t chunk_offset_z = chunk.parent_offset_z << 3;
|
||||||
|
|
||||||
|
// Get new block offset
|
||||||
|
block_offset_x += x;
|
||||||
|
block_offset_y += y;
|
||||||
|
block_offset_z += z;
|
||||||
|
|
||||||
|
if (block_offset_x >= 0 && block_offset_x < 8 &&
|
||||||
|
block_offset_y >= 0 && block_offset_y < 8 &&
|
||||||
|
block_offset_z >= 0 && block_offset_z < 8)
|
||||||
|
{ // new block is still in same chunk
|
||||||
|
|
||||||
|
if (input::button_pressed(Button::PlaceBlock))
|
||||||
|
{ // place it
|
||||||
|
uint16_t new_block_transform = block_transform_pack(block_offset_x, block_offset_y, block_offset_z, 0);
|
||||||
|
if (chunk.block_ids[block_transform_to_chunk_index(new_block_transform)] == 0)
|
||||||
|
{ // no block yet
|
||||||
|
world::add_block(closest_chunk_id, block_transform_pack(block_offset_x, block_offset_y, block_offset_z, 0), 1, 1); // TODO do not hardcode block model
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // render outline
|
||||||
|
const Vec4 block_color = Vec4(0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, 1.0f);
|
||||||
|
world::get_block_transform_mtx(block_offset_x, block_offset_y, block_offset_z, 0, block.parent_ref, block_mtx);
|
||||||
|
renderer::draw_lines(gfx::get_block_aabb_outline(), block_color, block_mtx);
|
||||||
|
}
|
||||||
|
} // TODO new block in neighboring chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crosshair
|
||||||
|
renderer::draw_crosshair(&camera);
|
||||||
|
|
||||||
|
// Advance to next frame. Main thread will be kicked to process submitted rendering primitives.
|
||||||
|
bgfx::frame();
|
||||||
|
|
||||||
|
frameCounter++;
|
||||||
|
|
||||||
|
if (config::DEBUG_EXIT_AFTER_FRAME > 0 && frameCounter > config::DEBUG_EXIT_AFTER_FRAME)
|
||||||
|
game_shutdown_requested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release resources
|
||||||
|
gfx::destroy();
|
||||||
|
|
||||||
|
bgfx::shutdown();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
73
src/gameloop.h
Normal file
73
src/gameloop.h
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <bx/bx.h>
|
||||||
|
#include <bx/thread.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <atomic>
|
||||||
|
#include "data/slot_queue.h"
|
||||||
|
|
||||||
|
typedef enum event_type
|
||||||
|
{
|
||||||
|
Exit, // Application should shutdown
|
||||||
|
Key, // Keyboard input
|
||||||
|
MouseButton, // Mouse buttons
|
||||||
|
MouseCursor, // Mouse movement input
|
||||||
|
MouseScroll, // Mouse scrolling
|
||||||
|
Resize, // Window resized
|
||||||
|
Joystick, // Joystick connected/disconnected
|
||||||
|
} EventType;
|
||||||
|
|
||||||
|
typedef struct event
|
||||||
|
{
|
||||||
|
EventType type;
|
||||||
|
|
||||||
|
// struct exit_event {};
|
||||||
|
// struct resize_event {};
|
||||||
|
struct key_event
|
||||||
|
{
|
||||||
|
int keycode;
|
||||||
|
int action;
|
||||||
|
};
|
||||||
|
struct mouse_button_event
|
||||||
|
{
|
||||||
|
int button;
|
||||||
|
int action;
|
||||||
|
};
|
||||||
|
struct cursor_event
|
||||||
|
{
|
||||||
|
float xpos;
|
||||||
|
float ypos;
|
||||||
|
};
|
||||||
|
struct scroll_event
|
||||||
|
{
|
||||||
|
float xoffset;
|
||||||
|
float yoffset;
|
||||||
|
};
|
||||||
|
struct joystick_event
|
||||||
|
{
|
||||||
|
int id;
|
||||||
|
int event;
|
||||||
|
};
|
||||||
|
|
||||||
|
union // purposeful anonymous union, as struct are named
|
||||||
|
{
|
||||||
|
struct key_event key;
|
||||||
|
struct mouse_button_event mouse_button;
|
||||||
|
struct cursor_event cursor;
|
||||||
|
struct scroll_event scroll;
|
||||||
|
struct joystick_event joystick;
|
||||||
|
};
|
||||||
|
} Event;
|
||||||
|
constexpr size_t event_size = sizeof(Event);
|
||||||
|
|
||||||
|
extern std::atomic_bool game_shutdown_requested;
|
||||||
|
// extern std::atomic_flag frame_ready_flag; // frame was rendered - ready for new data
|
||||||
|
// extern std::atomic_flag input_ready_flag; // input for next frame is ready
|
||||||
|
// extern std::atomic_flag bgfx_ready_flag; // bgfx init is done
|
||||||
|
extern std::atomic_bool frame_ready_flag; // frame was rendered - ready for new data
|
||||||
|
extern std::atomic_bool input_ready_flag; // input for next frame is ready
|
||||||
|
extern std::atomic_bool bgfx_ready_flag; // bgfx init is done
|
||||||
|
extern SlotQueue<Event> event_queue;
|
||||||
|
extern uint64_t frameCounter;
|
||||||
|
|
||||||
|
int32_t runGameloopThread(bx::Thread *_thread_self, void *_platformData /*userData*/);
|
||||||
453
src/graphics.cpp
Normal file
453
src/graphics.cpp
Normal file
@@ -0,0 +1,453 @@
|
|||||||
|
#include "graphics.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <bx/bx.h>
|
||||||
|
#include "data/slot_buffer.h"
|
||||||
|
#include "data/first_fit_buffer.h"
|
||||||
|
#include "data/linear_buffer.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include <cassert>
|
||||||
|
#include "factory.h"
|
||||||
|
#include "world.h"
|
||||||
|
#include "data/texture_atlas.h"
|
||||||
|
|
||||||
|
// Static variables
|
||||||
|
bgfx::VertexLayout GPUGrid::layout;
|
||||||
|
bgfx::VertexLayout GPUChunk::layout;
|
||||||
|
bgfx::VertexLayout GPUBlock::layout;
|
||||||
|
bgfx::VertexLayout GPUBlockSelection::layout;
|
||||||
|
bgfx::VertexLayout GPURenderInstance::layout;
|
||||||
|
bgfx::VertexLayout GPUDummyVertex::layout;
|
||||||
|
bgfx::VertexLayout GPULineVertex::layout;
|
||||||
|
|
||||||
|
namespace gfx
|
||||||
|
{
|
||||||
|
|
||||||
|
/* Graphics data */
|
||||||
|
// grids
|
||||||
|
SlotBuffer<GPUGrid> cpu_grids(config::INITIAL_NUM_GRIDS);
|
||||||
|
bgfx::DynamicVertexBufferHandle gpu_grids = BGFX_INVALID_HANDLE;
|
||||||
|
|
||||||
|
// chunks
|
||||||
|
SlotBuffer<GPUChunk> cpu_chunks(config::INITIAL_NUM_CHUNKS);
|
||||||
|
bgfx::DynamicVertexBufferHandle gpu_chunks = BGFX_INVALID_HANDLE;
|
||||||
|
|
||||||
|
// blocks (pre culling)
|
||||||
|
SlotBuffer<GPUBlock> cpu_blocks(config::INITIAL_NUM_BLOCKS);
|
||||||
|
bgfx::DynamicVertexBufferHandle gpu_blocks = BGFX_INVALID_HANDLE;
|
||||||
|
|
||||||
|
// block selection (post culling)
|
||||||
|
LinearBuffer<float> cpu_block_selection(config::INITIAL_BLOCK_SELECTION); // TODO should be uint32_t => use bgfx packing/conversion
|
||||||
|
bgfx::DynamicVertexBufferHandle gpu_block_selection = BGFX_INVALID_HANDLE;
|
||||||
|
|
||||||
|
// block models (index buffer with block render data)
|
||||||
|
FirstFitBuffer<uint32_t> cpu_block_models(config::INITIAL_BLOCK_MODEL_BUFFER_SIZE);
|
||||||
|
bgfx::DynamicIndexBufferHandle gpu_block_models = BGFX_INVALID_HANDLE;
|
||||||
|
|
||||||
|
// dummy vertex buffer
|
||||||
|
bgfx::VertexBufferHandle gpu_dummy_vertex_buffer = BGFX_INVALID_HANDLE;
|
||||||
|
|
||||||
|
// the indirect buffer holds the indirect draw calls generated in the compute shader
|
||||||
|
bgfx::IndirectBufferHandle gpu_indirect_buffer = BGFX_INVALID_HANDLE;
|
||||||
|
|
||||||
|
// per instance data (matrices) generated by compute shader
|
||||||
|
bgfx::DynamicVertexBufferHandle gpu_instance_buffer = BGFX_INVALID_HANDLE;
|
||||||
|
|
||||||
|
// line buffer
|
||||||
|
FirstFitBuffer<GPULineVertex> cpu_lines(config::INITIAL_LINE_BUFFER_SIZE);
|
||||||
|
bgfx::DynamicVertexBufferHandle gpu_line_buffer = BGFX_INVALID_HANDLE;
|
||||||
|
|
||||||
|
// uniforms
|
||||||
|
bgfx::UniformHandle texture_atlas_sampler = BGFX_INVALID_HANDLE;
|
||||||
|
bgfx::UniformHandle u_cubes_compute_params = BGFX_INVALID_HANDLE;
|
||||||
|
bgfx::UniformHandle u_line_color = BGFX_INVALID_HANDLE;
|
||||||
|
|
||||||
|
// shader
|
||||||
|
bgfx::ProgramHandle cubes_shader = BGFX_INVALID_HANDLE;
|
||||||
|
bgfx::ProgramHandle cubes_compute_shader = BGFX_INVALID_HANDLE;
|
||||||
|
bgfx::ProgramHandle lines_shader = BGFX_INVALID_HANDLE;
|
||||||
|
|
||||||
|
// textures
|
||||||
|
SlotBuffer<CompTexture> cpu_component_textures(config::INITIAL_NUM_COMPONENT_TEXTURES);
|
||||||
|
TextureAtlas cpu_texture_atlas = TextureAtlas();
|
||||||
|
bgfx::TextureHandle gpu_texture_atlas = BGFX_INVALID_HANDLE;
|
||||||
|
|
||||||
|
// lines
|
||||||
|
uint32_t aabb_outline = 0; // non static
|
||||||
|
uint32_t chunk_aabb_outline = 0;
|
||||||
|
uint32_t block_aabb_outline = 0;
|
||||||
|
|
||||||
|
/* Internal functions */
|
||||||
|
|
||||||
|
static bgfx::ShaderHandle loadShader(const char *filePath)
|
||||||
|
{
|
||||||
|
FILE *file = fopen(filePath, "rb");
|
||||||
|
if (!file)
|
||||||
|
DIE("loadShader: fopen(%s) failed\n", filePath);
|
||||||
|
|
||||||
|
fseek(file, 0, SEEK_END);
|
||||||
|
long fileSize = ftell(file);
|
||||||
|
fseek(file, 0, SEEK_SET);
|
||||||
|
|
||||||
|
const bgfx::Memory *mem = bgfx::alloc(fileSize + 1);
|
||||||
|
if (!mem)
|
||||||
|
DIE("loadShader: bgfx::alloc() failed\n");
|
||||||
|
|
||||||
|
fread(mem->data, 1, fileSize, file);
|
||||||
|
mem->data[mem->size - 1] = '\0';
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
return bgfx::createShader(mem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public functions */
|
||||||
|
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
GPUGrid::init();
|
||||||
|
GPUChunk::init();
|
||||||
|
GPUBlock::init();
|
||||||
|
GPUBlockSelection::init();
|
||||||
|
GPURenderInstance::init();
|
||||||
|
GPUDummyVertex::init();
|
||||||
|
GPULineVertex::init();
|
||||||
|
|
||||||
|
// gpu buffer
|
||||||
|
gpu_grids = bgfx::createDynamicVertexBuffer(config::INITIAL_NUM_GRIDS, GPUGrid::layout, BGFX_BUFFER_COMPUTE_READ | BGFX_BUFFER_ALLOW_RESIZE);
|
||||||
|
|
||||||
|
gpu_chunks = bgfx::createDynamicVertexBuffer(config::INITIAL_NUM_CHUNKS, GPUChunk::layout, BGFX_BUFFER_COMPUTE_READ | BGFX_BUFFER_ALLOW_RESIZE);
|
||||||
|
|
||||||
|
gpu_blocks = bgfx::createDynamicVertexBuffer(config::INITIAL_NUM_BLOCKS, GPUBlock::layout, BGFX_BUFFER_COMPUTE_READ | BGFX_BUFFER_ALLOW_RESIZE);
|
||||||
|
|
||||||
|
gpu_block_selection = bgfx::createDynamicVertexBuffer(config::INITIAL_BLOCK_SELECTION, GPUBlockSelection::layout, BGFX_BUFFER_COMPUTE_READ | BGFX_BUFFER_ALLOW_RESIZE);
|
||||||
|
|
||||||
|
gpu_block_models = bgfx::createDynamicIndexBuffer(config::INITIAL_BLOCK_MODEL_BUFFER_SIZE, BGFX_BUFFER_INDEX32 | BGFX_BUFFER_ALLOW_RESIZE);
|
||||||
|
|
||||||
|
GPUDummyVertex dummy_vertex = {0};
|
||||||
|
gpu_dummy_vertex_buffer = bgfx::createVertexBuffer(bgfx::copy(&dummy_vertex, sizeof(float)), GPUDummyVertex::layout, BGFX_BUFFER_NONE);
|
||||||
|
|
||||||
|
gpu_indirect_buffer = bgfx::createIndirectBuffer(config::INITIAL_BLOCK_SELECTION);
|
||||||
|
|
||||||
|
gpu_instance_buffer = bgfx::createDynamicVertexBuffer(config::INITIAL_BLOCK_SELECTION, GPURenderInstance::layout, BGFX_BUFFER_COMPUTE_WRITE);
|
||||||
|
|
||||||
|
gpu_line_buffer = bgfx::createDynamicVertexBuffer(config::INITIAL_LINE_BUFFER_SIZE, GPULineVertex::layout, BGFX_BUFFER_ALLOW_RESIZE);
|
||||||
|
|
||||||
|
// uniforms
|
||||||
|
texture_atlas_sampler = bgfx::createUniform("texture_atlas_sampler", bgfx::UniformType::Sampler);
|
||||||
|
u_cubes_compute_params = bgfx::createUniform("u_cubes_compute_params", bgfx::UniformType::Vec4);
|
||||||
|
u_line_color = bgfx::createUniform("u_line_color", bgfx::UniformType::Vec4);
|
||||||
|
|
||||||
|
// shader
|
||||||
|
cubes_shader = bgfx::createProgram(
|
||||||
|
loadShader("./assets/shaders/vs_cubes.spv"),
|
||||||
|
loadShader("./assets/shaders/fs_cubes.spv"), true);
|
||||||
|
|
||||||
|
cubes_compute_shader = bgfx::createProgram(
|
||||||
|
loadShader("./assets/shaders/cs_cubes.spv"), true);
|
||||||
|
|
||||||
|
lines_shader = bgfx::createProgram(
|
||||||
|
loadShader("./assets/shaders/vs_lines.spv"),
|
||||||
|
loadShader("./assets/shaders/fs_lines.spv"), true);
|
||||||
|
|
||||||
|
// textures
|
||||||
|
cpu_texture_atlas.init();
|
||||||
|
gpu_texture_atlas = bgfx::createTexture2D(
|
||||||
|
config::TA_WIDTH * config::TA_MAIN_PATCH_SIZE * config::TA_TEXTURE_SIZE,
|
||||||
|
config::TA_HEIGHT * config::TA_MAIN_PATCH_SIZE * config::TA_TEXTURE_SIZE,
|
||||||
|
false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_NONE | BGFX_SAMPLER_POINT, NULL);
|
||||||
|
|
||||||
|
// cpu buffer index 0 is invalid for all purposes => we push an empty element
|
||||||
|
uint32_t ref;
|
||||||
|
cpu_grids.add(ref);
|
||||||
|
cpu_grids.clearModified();
|
||||||
|
assert(ref == 0);
|
||||||
|
cpu_chunks.add(ref);
|
||||||
|
cpu_chunks.clearModified();
|
||||||
|
assert(ref == 0);
|
||||||
|
cpu_blocks.add(ref);
|
||||||
|
cpu_blocks.clearModified();
|
||||||
|
assert(ref == 0);
|
||||||
|
ref = cpu_block_models.add(NULL, 0);
|
||||||
|
cpu_block_models.clearModified();
|
||||||
|
assert(ref == 0);
|
||||||
|
// Push debug texture into invalid index 0
|
||||||
|
const Texel O = {204, 131, 67, 255}; // Orange
|
||||||
|
const Texel W = {255, 255, 255, 255};
|
||||||
|
const Texel B = {0, 0, 0, 255};
|
||||||
|
Texel texture[16] = {
|
||||||
|
W, O, O, W,
|
||||||
|
O, W, W, O,
|
||||||
|
W, O, O, W,
|
||||||
|
O, W, W, O,
|
||||||
|
};
|
||||||
|
uint32_t debug_comp_tex_id = add_component_textures(texture);
|
||||||
|
assert(debug_comp_tex_id == 0);
|
||||||
|
// add a first debug texture to the atlas
|
||||||
|
// This also reserves the invalid texture_index 0
|
||||||
|
cpu_texture_atlas.add_patch(1, 1, &debug_comp_tex_id);
|
||||||
|
|
||||||
|
// load default AABB outlines
|
||||||
|
aabb_outline = add_line(factory::generate_AABB_outline(BLOCK_AABB), 24); // use block aabb initially
|
||||||
|
chunk_aabb_outline = add_line(factory::generate_AABB_outline(CHUNK_AABB), 24);
|
||||||
|
block_aabb_outline = add_line(factory::generate_AABB_outline(BLOCK_AABB), 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call every frame!
|
||||||
|
// Updates outstanding changes from CPU -> GPU
|
||||||
|
void update()
|
||||||
|
{
|
||||||
|
uint32_t offset = 0, num_elements = 0;
|
||||||
|
bool resized = false;
|
||||||
|
|
||||||
|
if (cpu_grids.wasModified(offset, num_elements))
|
||||||
|
{
|
||||||
|
bgfx::update(gpu_grids, offset, bgfx::makeRef(cpu_grids.data() + offset, sizeof(GPUGrid) * num_elements));
|
||||||
|
cpu_grids.clearModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cpu_chunks.wasModified(offset, num_elements))
|
||||||
|
{
|
||||||
|
bgfx::update(gpu_chunks, offset, bgfx::makeRef(cpu_chunks.data() + offset, sizeof(GPUChunk) * num_elements));
|
||||||
|
cpu_chunks.clearModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cpu_blocks.wasModified(offset, num_elements))
|
||||||
|
{
|
||||||
|
bgfx::update(gpu_blocks, offset, bgfx::makeRef(cpu_blocks.data() + offset, sizeof(GPUBlock) * num_elements));
|
||||||
|
cpu_blocks.clearModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cpu_block_selection.wasModified(offset, num_elements, resized))
|
||||||
|
{
|
||||||
|
bgfx::update(gpu_block_selection, offset, bgfx::makeRef(cpu_block_selection.data() + offset, sizeof(float) * num_elements));
|
||||||
|
cpu_block_selection.clear();
|
||||||
|
|
||||||
|
if (resized)
|
||||||
|
{ // This means our indirect and instance buffers need to be resized too
|
||||||
|
bgfx::destroy(gpu_indirect_buffer);
|
||||||
|
gpu_indirect_buffer = bgfx::createIndirectBuffer(num_elements);
|
||||||
|
|
||||||
|
bgfx::destroy(gpu_instance_buffer);
|
||||||
|
gpu_instance_buffer = bgfx::createDynamicVertexBuffer(num_elements, GPURenderInstance::layout, BGFX_BUFFER_COMPUTE_WRITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cpu_block_models.wasModified(offset, num_elements))
|
||||||
|
{
|
||||||
|
bgfx::update(gpu_block_models, offset, bgfx::makeRef(cpu_block_models.data() + offset, sizeof(uint32_t) * num_elements));
|
||||||
|
cpu_block_models.clearModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cpu_lines.wasModified(offset, num_elements))
|
||||||
|
{
|
||||||
|
bgfx::update(gpu_line_buffer, offset, bgfx::makeRef(cpu_lines.data() + offset, sizeof(GPULineVertex) * num_elements));
|
||||||
|
cpu_lines.clearModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cpu_texture_atlas.wasModified(offset, num_elements))
|
||||||
|
{
|
||||||
|
// offset == texel_id
|
||||||
|
// we need to update entire rows, since the texel data is linear
|
||||||
|
const uint16_t offset_y_begin = offset / config::TA_TEXELS_PER_ROW;
|
||||||
|
const uint16_t offset_y_end = ((offset + num_elements) / config::TA_TEXELS_PER_ROW) + 1;
|
||||||
|
const uint16_t num_rows = offset_y_end + offset_y_begin;
|
||||||
|
bgfx::updateTexture2D(gpu_texture_atlas, 0, 0, 0, offset_y_begin, config::TA_TEXELS_PER_ROW, num_rows,
|
||||||
|
bgfx::makeRef(cpu_texture_atlas.data() + (offset_y_begin * config::TA_TEXELS_PER_ROW), num_rows * config::TA_TEXELS_PER_ROW * sizeof(Texel)));
|
||||||
|
cpu_texture_atlas.clearModified();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy()
|
||||||
|
{
|
||||||
|
bgfx::destroy(gpu_grids);
|
||||||
|
gpu_grids = BGFX_INVALID_HANDLE;
|
||||||
|
bgfx::destroy(gpu_chunks);
|
||||||
|
gpu_chunks = BGFX_INVALID_HANDLE;
|
||||||
|
bgfx::destroy(gpu_blocks);
|
||||||
|
gpu_blocks = BGFX_INVALID_HANDLE;
|
||||||
|
bgfx::destroy(gpu_block_selection);
|
||||||
|
gpu_block_selection = BGFX_INVALID_HANDLE;
|
||||||
|
bgfx::destroy(gpu_block_models);
|
||||||
|
gpu_block_models = BGFX_INVALID_HANDLE;
|
||||||
|
bgfx::destroy(gpu_dummy_vertex_buffer);
|
||||||
|
gpu_dummy_vertex_buffer = BGFX_INVALID_HANDLE;
|
||||||
|
bgfx::destroy(gpu_indirect_buffer);
|
||||||
|
gpu_indirect_buffer = BGFX_INVALID_HANDLE;
|
||||||
|
bgfx::destroy(gpu_instance_buffer);
|
||||||
|
gpu_instance_buffer = BGFX_INVALID_HANDLE;
|
||||||
|
bgfx::destroy(gpu_line_buffer);
|
||||||
|
gpu_line_buffer = BGFX_INVALID_HANDLE;
|
||||||
|
|
||||||
|
bgfx::destroy(texture_atlas_sampler);
|
||||||
|
texture_atlas_sampler = BGFX_INVALID_HANDLE;
|
||||||
|
bgfx::destroy(u_cubes_compute_params);
|
||||||
|
u_cubes_compute_params = BGFX_INVALID_HANDLE;
|
||||||
|
bgfx::destroy(u_line_color);
|
||||||
|
u_line_color = BGFX_INVALID_HANDLE;
|
||||||
|
|
||||||
|
bgfx::destroy(cubes_shader);
|
||||||
|
cubes_shader = BGFX_INVALID_HANDLE;
|
||||||
|
bgfx::destroy(cubes_compute_shader);
|
||||||
|
cubes_compute_shader = BGFX_INVALID_HANDLE;
|
||||||
|
bgfx::destroy(lines_shader);
|
||||||
|
lines_shader = BGFX_INVALID_HANDLE;
|
||||||
|
|
||||||
|
bgfx::destroy(gpu_texture_atlas);
|
||||||
|
gpu_texture_atlas = BGFX_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
GPUGrid &add_grid(uint32_t &_out_ref)
|
||||||
|
{
|
||||||
|
return cpu_grids.add(_out_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
GPUGrid &update_grid(const uint32_t _ref)
|
||||||
|
{
|
||||||
|
return cpu_grids.at(_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_grid(const uint32_t _ref)
|
||||||
|
{
|
||||||
|
cpu_grids.remove(_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
GPUChunk &add_chunk(uint32_t &_out_ref)
|
||||||
|
{
|
||||||
|
return cpu_chunks.add(_out_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_chunk(const uint32_t _ref)
|
||||||
|
{
|
||||||
|
cpu_chunks.remove(_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
GPUBlock &add_block(uint32_t &_out_ref)
|
||||||
|
{
|
||||||
|
return cpu_blocks.add(_out_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
GPUBlock &update_block(const uint32_t _ref)
|
||||||
|
{
|
||||||
|
return cpu_blocks.at(_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_block(const uint32_t _ref)
|
||||||
|
{
|
||||||
|
cpu_blocks.remove(_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_block_selection(const uint32_t _ref)
|
||||||
|
{
|
||||||
|
cpu_block_selection.add((float)_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t get_num_block_selection()
|
||||||
|
{
|
||||||
|
return cpu_block_selection.get_num_elements();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t add_block_model(const uint32_t *_data, const uint32_t _num_elements)
|
||||||
|
{
|
||||||
|
return cpu_block_models.add(_data, _num_elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_block_model(const uint32_t _ref)
|
||||||
|
{
|
||||||
|
cpu_block_models.remove(_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_block_model_offset(const uint32_t _ref, uint32_t &_out_offset, uint32_t &_out_num_elements)
|
||||||
|
{
|
||||||
|
cpu_block_models.get_offset(_ref, _out_offset, _out_num_elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A line always consists of 2 vertices.
|
||||||
|
uint32_t add_line(const GPULineVertex *_data, const uint32_t _num_elements)
|
||||||
|
{
|
||||||
|
assert(_num_elements % 2 == 0);
|
||||||
|
return cpu_lines.add(_data, _num_elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t update_line(const uint32_t _ref, const GPULineVertex *_data)
|
||||||
|
{
|
||||||
|
return cpu_lines.update(_ref, _data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_line(const uint32_t _ref)
|
||||||
|
{
|
||||||
|
cpu_lines.remove(_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_line_offset(const uint32_t _ref, uint32_t &_out_offset, uint32_t &_out_num_elements)
|
||||||
|
{
|
||||||
|
cpu_lines.get_offset(_ref, _out_offset, _out_num_elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
uint32_t add_component_textures(const Texel *_textures)
|
||||||
|
{
|
||||||
|
uint32_t ref;
|
||||||
|
CompTexture& tex = cpu_component_textures.add(ref);
|
||||||
|
bx::memCopy(tex.data, _textures, config::TA_TEXELS_PER_TEXTURE * sizeof(Texel));
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_component_textures(uint32_t _ref)
|
||||||
|
{
|
||||||
|
cpu_component_textures.remove(_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Texel *get_component_texture(const uint32_t _ref)
|
||||||
|
{
|
||||||
|
return cpu_component_textures.at_const(_ref).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
uint32_t add_patch(const uint8_t _width, const uint8_t _height, const uint32_t *_component_texture_ids)
|
||||||
|
{
|
||||||
|
return cpu_texture_atlas.add_patch(_width, _height, _component_texture_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_patch(const uint8_t _patch_id)
|
||||||
|
{
|
||||||
|
cpu_texture_atlas.remove_patch(_patch_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_patch_texture_ids(const uint8_t _patch_id, uint32_t *_out_texture_ids)
|
||||||
|
{
|
||||||
|
cpu_texture_atlas.get_patch_texture_ids(_patch_id, _out_texture_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
bgfx::DynamicVertexBufferHandle get_grid_buffer() { return gpu_grids; }
|
||||||
|
bgfx::DynamicVertexBufferHandle get_chunk_buffer() { return gpu_chunks; }
|
||||||
|
bgfx::DynamicVertexBufferHandle get_block_buffer() { return gpu_blocks; }
|
||||||
|
bgfx::DynamicVertexBufferHandle get_block_selection_buffer() { return gpu_block_selection; }
|
||||||
|
bgfx::DynamicIndexBufferHandle get_block_model_buffer() { return gpu_block_models; }
|
||||||
|
bgfx::VertexBufferHandle get_dummy_vertex_buffer() { return gpu_dummy_vertex_buffer; }
|
||||||
|
bgfx::IndirectBufferHandle get_indirect_buffer() { return gpu_indirect_buffer; }
|
||||||
|
bgfx::DynamicVertexBufferHandle get_instance_buffer() { return gpu_instance_buffer; }
|
||||||
|
bgfx::DynamicVertexBufferHandle get_line_buffer() { return gpu_line_buffer; }
|
||||||
|
|
||||||
|
bgfx::UniformHandle get_uniform_texture_atlas_sampler() { return texture_atlas_sampler; }
|
||||||
|
bgfx::UniformHandle get_uniform_cubes_compute_params() { return u_cubes_compute_params; }
|
||||||
|
bgfx::UniformHandle get_uniform_line_color() { return u_line_color; }
|
||||||
|
|
||||||
|
bgfx::ProgramHandle get_cubes_shader() { return cubes_shader; }
|
||||||
|
bgfx::ProgramHandle get_cubes_compute_shader() { return cubes_compute_shader; }
|
||||||
|
bgfx::ProgramHandle get_lines_shader() { return lines_shader; }
|
||||||
|
|
||||||
|
bgfx::TextureHandle get_texture_atlas() { return gpu_texture_atlas; }
|
||||||
|
|
||||||
|
uint32_t get_aabb_outline(const AABB &_aabb)
|
||||||
|
{
|
||||||
|
// Note: update is possible since, an aabb outline always has 24 elements
|
||||||
|
return update_line(aabb_outline, factory::generate_AABB_outline(_aabb));
|
||||||
|
}
|
||||||
|
uint32_t get_last_aabb_outline() { return aabb_outline; }
|
||||||
|
uint32_t get_chunk_aabb_outline() { return chunk_aabb_outline; }
|
||||||
|
uint32_t get_block_aabb_outline() { return block_aabb_outline; }
|
||||||
|
|
||||||
|
}
|
||||||
239
src/graphics.h
Normal file
239
src/graphics.h
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <bgfx/bgfx.h>
|
||||||
|
#include "space_math.h"
|
||||||
|
|
||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
/* The gfx namespace manages all GPU resources */
|
||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
// ------------ \\
|
||||||
|
// DATA STRUCTS \\
|
||||||
|
// ------------ \\
|
||||||
|
|
||||||
|
// Per grid data for rendering
|
||||||
|
typedef struct gpu_grid
|
||||||
|
{
|
||||||
|
float m_mtx[16];
|
||||||
|
|
||||||
|
static void init()
|
||||||
|
{
|
||||||
|
layout
|
||||||
|
.begin()
|
||||||
|
.add(bgfx::Attrib::TexCoord0, 4, bgfx::AttribType::Float)
|
||||||
|
.add(bgfx::Attrib::TexCoord1, 4, bgfx::AttribType::Float)
|
||||||
|
.add(bgfx::Attrib::TexCoord2, 4, bgfx::AttribType::Float)
|
||||||
|
.add(bgfx::Attrib::TexCoord3, 4, bgfx::AttribType::Float)
|
||||||
|
.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
static bgfx::VertexLayout layout;
|
||||||
|
} GPUGrid;
|
||||||
|
|
||||||
|
// Per chunk data for rendering
|
||||||
|
typedef struct gpu_chunk
|
||||||
|
{
|
||||||
|
float grid_id;
|
||||||
|
float grid_offset_x;
|
||||||
|
float grid_offset_y;
|
||||||
|
float grid_offset_z;
|
||||||
|
|
||||||
|
static void init()
|
||||||
|
{
|
||||||
|
layout
|
||||||
|
.begin()
|
||||||
|
.add(bgfx::Attrib::TexCoord0, 4, bgfx::AttribType::Float)
|
||||||
|
.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
static bgfx::VertexLayout layout;
|
||||||
|
} GPUChunk;
|
||||||
|
|
||||||
|
// Per block data for rendering (all blocks ready for rendering aka in render distance)
|
||||||
|
// This points to a block_blueprint in the index buffer
|
||||||
|
// It represents a single block instance (pre culling)
|
||||||
|
typedef struct gpu_block
|
||||||
|
{
|
||||||
|
float chunk_id;
|
||||||
|
float transform; // position in chunk and rotation
|
||||||
|
float index_buf_offset;
|
||||||
|
float num_indices;
|
||||||
|
|
||||||
|
static void init()
|
||||||
|
{
|
||||||
|
layout
|
||||||
|
.begin()
|
||||||
|
.add(bgfx::Attrib::TexCoord0, 4, bgfx::AttribType::Float)
|
||||||
|
.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
static bgfx::VertexLayout layout;
|
||||||
|
} GPUBlock;
|
||||||
|
|
||||||
|
// Helper class
|
||||||
|
struct CompTexture
|
||||||
|
{
|
||||||
|
Texel data[4*4];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Post culling selection
|
||||||
|
// This is referencing a GPUBlock
|
||||||
|
typedef struct gpu_block_selection
|
||||||
|
{
|
||||||
|
float index;
|
||||||
|
|
||||||
|
static void init()
|
||||||
|
{
|
||||||
|
layout
|
||||||
|
.begin()
|
||||||
|
.add(bgfx::Attrib::TexCoord0, 1, bgfx::AttribType::Float)
|
||||||
|
.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
static bgfx::VertexLayout layout;
|
||||||
|
} GPUBlockSelection;
|
||||||
|
|
||||||
|
// Per instance data, generated by computer shader
|
||||||
|
// Required for indirect drawing
|
||||||
|
// This is just the blocks world matrix (position+orientation in the world)
|
||||||
|
typedef struct gpu_render_instance
|
||||||
|
{
|
||||||
|
float m_mtx[16];
|
||||||
|
|
||||||
|
static void init()
|
||||||
|
{
|
||||||
|
layout
|
||||||
|
.begin()
|
||||||
|
.add(bgfx::Attrib::TexCoord0, 4, bgfx::AttribType::Float)
|
||||||
|
.add(bgfx::Attrib::TexCoord1, 4, bgfx::AttribType::Float)
|
||||||
|
.add(bgfx::Attrib::TexCoord2, 4, bgfx::AttribType::Float)
|
||||||
|
.add(bgfx::Attrib::TexCoord3, 4, bgfx::AttribType::Float)
|
||||||
|
.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
static bgfx::VertexLayout layout;
|
||||||
|
} GPURenderInstance;
|
||||||
|
|
||||||
|
// Dummy vertex
|
||||||
|
typedef struct gpu_dummy_vertex
|
||||||
|
{
|
||||||
|
float data;
|
||||||
|
|
||||||
|
static void init()
|
||||||
|
{
|
||||||
|
layout
|
||||||
|
.begin()
|
||||||
|
.add(bgfx::Attrib::TexCoord0, 1, bgfx::AttribType::Float)
|
||||||
|
.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
static bgfx::VertexLayout layout;
|
||||||
|
} GPUDummyVertex;
|
||||||
|
|
||||||
|
// A line between two points
|
||||||
|
typedef struct gpu_line_vertex
|
||||||
|
{
|
||||||
|
Vec3 position;
|
||||||
|
|
||||||
|
static void init()
|
||||||
|
{
|
||||||
|
layout
|
||||||
|
.begin()
|
||||||
|
.add(bgfx::Attrib::TexCoord0, 3, bgfx::AttribType::Float)
|
||||||
|
.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
static bgfx::VertexLayout layout;
|
||||||
|
} GPULineVertex;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace gfx
|
||||||
|
{
|
||||||
|
|
||||||
|
// --------- \\
|
||||||
|
// FUNCTIONS \\
|
||||||
|
// --------- \\
|
||||||
|
|
||||||
|
void init();
|
||||||
|
void update();
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
// Adds grid data for the gpu
|
||||||
|
// Returns reference for updating/removing
|
||||||
|
GPUGrid &add_grid(uint32_t &_out_ref);
|
||||||
|
GPUGrid &update_grid(const uint32_t _ref);
|
||||||
|
void remove_grid(const uint32_t _ref);
|
||||||
|
|
||||||
|
// Adds chunk data for the gpu
|
||||||
|
// Returns reference for updating/removing
|
||||||
|
GPUChunk &add_chunk(uint32_t &_out_ref);
|
||||||
|
void remove_chunk(const uint32_t _ref);
|
||||||
|
|
||||||
|
// Adds block data for the gpu
|
||||||
|
// Returns reference for updating/removing
|
||||||
|
GPUBlock &add_block(uint32_t &_out_ref);
|
||||||
|
GPUBlock &update_block(const uint32_t _ref);
|
||||||
|
void remove_block(const uint32_t _ref);
|
||||||
|
|
||||||
|
void add_block_selection(const uint32_t _ref);
|
||||||
|
uint16_t get_num_block_selection();
|
||||||
|
|
||||||
|
// Adds block model data (index buffer) for the gpu
|
||||||
|
// Returns reference for updating/removing
|
||||||
|
uint32_t add_block_model(const uint32_t *_data, const uint32_t _num_elements);
|
||||||
|
void remove_block_model(const uint32_t _ref);
|
||||||
|
void get_block_model_offset(const uint32_t _ref, uint32_t &_out_offset, uint32_t &_out_num_elements);
|
||||||
|
|
||||||
|
// Adds line data (line segments) for the gpu
|
||||||
|
// Returns reference for updating/removing
|
||||||
|
uint32_t add_line(const GPULineVertex *_data, const uint32_t _num_elements);
|
||||||
|
// Attention: new _data MUST have the exact same _num_elements as the original
|
||||||
|
uint32_t update_line(const uint32_t _ref, const GPULineVertex *_data);
|
||||||
|
void remove_line(const uint32_t _ref);
|
||||||
|
void get_line_offset(const uint32_t _ref, uint32_t &_out_offset, uint32_t &_out_num_elements);
|
||||||
|
|
||||||
|
// Adds 8*8 texels to the internal buffer
|
||||||
|
// Returns id to the textures
|
||||||
|
uint32_t add_component_textures(const Color* textures);
|
||||||
|
void remove_component_textures(uint32_t _ref);
|
||||||
|
const Texel* get_component_texture(uint32_t _ref);
|
||||||
|
|
||||||
|
// Adds a patch to the atlas with size _width*_height
|
||||||
|
// Deduplicates the _component_texture_ids if possible, otherwise copies the data
|
||||||
|
uint32_t add_patch(const uint8_t _width, const uint8_t _height, const uint32_t* _component_texture_ids);
|
||||||
|
// Copies _data into the given patch
|
||||||
|
// Returns texture id for vertex buffer generation
|
||||||
|
// uint32_t update_patch_texture(const uint32_t _patch_id, const uint8_t _offset_x, const uint8_t _offset_y, const uint32_t _component_texture_id);
|
||||||
|
void remove_patch(const uint8_t _patch_id);
|
||||||
|
// Returns 4 texture ids (for vertex buffer generation) of given patch
|
||||||
|
void get_patch_texture_ids(const uint8_t _patch_id, uint32_t* _out_texture_ids);
|
||||||
|
|
||||||
|
|
||||||
|
bgfx::DynamicVertexBufferHandle get_grid_buffer();
|
||||||
|
bgfx::DynamicVertexBufferHandle get_chunk_buffer();
|
||||||
|
bgfx::DynamicVertexBufferHandle get_block_buffer();
|
||||||
|
bgfx::DynamicVertexBufferHandle get_block_selection_buffer();
|
||||||
|
bgfx::DynamicIndexBufferHandle get_block_model_buffer();
|
||||||
|
bgfx::VertexBufferHandle get_dummy_vertex_buffer();
|
||||||
|
bgfx::IndirectBufferHandle get_indirect_buffer();
|
||||||
|
bgfx::DynamicVertexBufferHandle get_instance_buffer();
|
||||||
|
bgfx::DynamicVertexBufferHandle get_line_buffer();
|
||||||
|
|
||||||
|
bgfx::UniformHandle get_uniform_texture_atlas_sampler();
|
||||||
|
bgfx::UniformHandle get_uniform_cubes_compute_params();
|
||||||
|
bgfx::UniformHandle get_uniform_line_color();
|
||||||
|
|
||||||
|
bgfx::ProgramHandle get_cubes_shader();
|
||||||
|
bgfx::ProgramHandle get_cubes_compute_shader();
|
||||||
|
bgfx::ProgramHandle get_lines_shader();
|
||||||
|
|
||||||
|
bgfx::TextureHandle get_texture_atlas();
|
||||||
|
|
||||||
|
uint32_t get_aabb_outline(const AABB &_aabb);
|
||||||
|
// Returns last aabb outline generated by "get_aabb_outline(const AABB& _aabb)"
|
||||||
|
uint32_t get_last_aabb_outline();
|
||||||
|
uint32_t get_chunk_aabb_outline();
|
||||||
|
uint32_t get_block_aabb_outline();
|
||||||
|
|
||||||
|
} // namespace gfx
|
||||||
4
src/lib/camera.cpp
Normal file
4
src/lib/camera.cpp
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#define CAMERA_IMPLEMENTATION
|
||||||
|
#include "camera.h"
|
||||||
|
|
||||||
|
/// This file only exists as a translation unit for the library implementation
|
||||||
446
src/lib/camera.h
Normal file
446
src/lib/camera.h
Normal file
@@ -0,0 +1,446 @@
|
|||||||
|
/*
|
||||||
|
* INFO:
|
||||||
|
*
|
||||||
|
* C/C++ (single) header quaternion based 3D camera system
|
||||||
|
* for games and other 3D graphics applications.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* FEATURES:
|
||||||
|
*
|
||||||
|
* - Quaternion based
|
||||||
|
* This naturally avoids gimbal lock and enables smooth interpolation (ex. for cinematic camera movement)
|
||||||
|
* - Precise manipulation
|
||||||
|
* A call of 'camera_rotate(&camera, {45 * DEG_TO_RAD, 0, 0});' will rotate exactly 45 degrees.
|
||||||
|
* - Engine agnostic
|
||||||
|
* No matter your input system, the camera is updated in angles
|
||||||
|
* - Different camera modes to control the cameras behaviour
|
||||||
|
* - Allows custom configuration
|
||||||
|
* - Can be changed at runtime
|
||||||
|
* - See CAMERA_MODE_* defines for more
|
||||||
|
* - Pre-defined modes: CAMERA_MODE_FREE, CAMERA_MODE_FIRST_PERSON, CAMERA_MODE_THIRD_PERSON, CAMERA_MODE_ORBITAL
|
||||||
|
* - Supports seamless camera mode transitions (ex. first person -> third person)
|
||||||
|
* - Full access to all camera state data - nothing is hidden
|
||||||
|
* You can access and manipulate the entire camera state at any time!
|
||||||
|
* - Supports angle clamping
|
||||||
|
* Restrict the angles your camera is allowed to work in
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* USAGE:
|
||||||
|
*
|
||||||
|
* Simply add 'camera.h' and one 'camera_math.h' to your project and '#include "camera.h"' it wherever.
|
||||||
|
*
|
||||||
|
* ONE (and only ONE) source file must hold the implementation
|
||||||
|
* by using '#define CAMERA_IMPLEMENTATION' before including it with '#include "camera.h"'.
|
||||||
|
*
|
||||||
|
* Camera code is math heavy. For all required math functions we provide a separate 'camera_math.h' file.
|
||||||
|
* To get started you can use the 'camera_math_default.h'.
|
||||||
|
* Simply add it to your project and rename it to 'camera_math.h'.
|
||||||
|
*
|
||||||
|
* But your engine probably brings its own math library.
|
||||||
|
* In order to reduce the duplication of math-code you can create your own
|
||||||
|
* 'camera_math.h' implementing the required functions with the math library of your choice.
|
||||||
|
* This also allows you to pass and receive arguments without the need to convert them.
|
||||||
|
* See 'camera_math_bx.h' as an example.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* ANGLE CLAMPING:
|
||||||
|
*
|
||||||
|
* If angle clamping is activated, the corresponding limits must be set in the camera struct.
|
||||||
|
* All limits are expected in radians and min* must be smaller than max*.
|
||||||
|
* They are expected in the range [-pi; pi], with 0 representing no rotation.
|
||||||
|
* The camera rotations are restricting in WORLD space.
|
||||||
|
* This means if pitch AND yaw are clamped, this essentially creates a "window" the camera is not allowed to rotate out of.
|
||||||
|
*
|
||||||
|
* Example for clamping pitch:
|
||||||
|
* 1. Activate pitch clamping: 'camera.mode |= CAMERA_MODE_CLAMP_PITCH;'
|
||||||
|
* 2. Set limits: 'camera.maxPitch = -pi / 2.0f; // aka restrict to 90 deg upwards'
|
||||||
|
* 'camera.minPitch = pi / 2.0f; // aka restrict to 90 deg downwards'
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* GENERAL NOTES:
|
||||||
|
*
|
||||||
|
* ALL camera struct members can be safely manipulated at any time.
|
||||||
|
*
|
||||||
|
* To change to a right-handed coordinate system simply change CAMERA_WORLD_FORWARD to (0.0f, 0.0f, -1.0f)
|
||||||
|
*
|
||||||
|
* CameraQuaternions are well suited to represent 3D orientation, but they do not accumulate
|
||||||
|
* many rotations in differing axes well and thus require occasional re-normalization.
|
||||||
|
* To accommodate for this and for general performance reasons changes are accumulated and the
|
||||||
|
* orientation quaternion is only updated when the view matrix is requested (once per frame).
|
||||||
|
*
|
||||||
|
* Query functions only return the correct value AFTER pending changes have been applied. (i.e. calling camera_view_matrix(..))
|
||||||
|
* Example:
|
||||||
|
* 1. camera_move(..) // Changes NOT yet applied
|
||||||
|
* 2. camera_eye_position(..) // Returned state does NOT yet include previous move
|
||||||
|
* 3. camera_view_matrix(..) // Changes are now applied
|
||||||
|
* 4. camera_eye_position(..) // Returned state DOES now include previous move
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* LICENSE:
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022 Crydsch Cube
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef CAMERA_HEADER_GUARD
|
||||||
|
#define CAMERA_HEADER_GUARD
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "camera_math_bx.h"
|
||||||
|
|
||||||
|
/* World and mode defines */
|
||||||
|
|
||||||
|
#define CAMERA_WORLD_FORWARD CameraVec3(0.0f, 0.0f, 1.0f)
|
||||||
|
#define CAMERA_WORLD_UP CameraVec3(0.0f, 1.0f, 0.0f)
|
||||||
|
#define CAMERA_WORLD_RIGHT CameraVec3(1.0f, 0.0f, 0.0f)
|
||||||
|
|
||||||
|
// Camera mode configuration flags
|
||||||
|
// Can be combined with bitwise OR
|
||||||
|
#define CAMERA_MODE_DISABLE_ROLL UINT32_C(0x00000001) // Disables the roll axis
|
||||||
|
#define CAMERA_MODE_MOVE_IN_WORLDPLANE UINT32_C(0x00000002) // Projects movement onto world plane
|
||||||
|
#define CAMERA_MODE_CLAMP_PITCH_ANGLE UINT32_C(0x00000004) // Limits the pitch angle. Typically used in first/third person to prevent overrotation (i.e. somersaults).
|
||||||
|
#define CAMERA_MODE_CLAMP_YAW_ANGLE UINT32_C(0x00000008) // Limits the yaw angle.
|
||||||
|
#define CAMERA_MODE_CLAMP_ROLL_ANGLE UINT32_C(0x00000010) // Limits the roll angle.
|
||||||
|
|
||||||
|
// Free float camera mode (no restrictions applied)
|
||||||
|
#define CAMERA_MODE_FREE (0)
|
||||||
|
|
||||||
|
// First person camera mode
|
||||||
|
// Note: Set camera.minPitch = -pi/2 and camera.maxPitch = pi/2
|
||||||
|
#define CAMERA_MODE_FIRST_PERSON (0 \
|
||||||
|
| CAMERA_MODE_DISABLE_ROLL \
|
||||||
|
| CAMERA_MODE_MOVE_IN_WORLDPLANE \
|
||||||
|
| CAMERA_MODE_CLAMP_PITCH_ANGLE \
|
||||||
|
)
|
||||||
|
|
||||||
|
// Third person camera mode
|
||||||
|
// Note: Set camera.minPitch = -pi/2 and camera.maxPitch = pi/2
|
||||||
|
// Note: Use a target_distance > 0
|
||||||
|
#define CAMERA_MODE_THIRD_PERSON CAMERA_MODE_FIRST_PERSON
|
||||||
|
|
||||||
|
// Orbital camera mode (orbit around some target)
|
||||||
|
// Useful for inspecting models.
|
||||||
|
// Note: Set camera.minPitch = -pi/2 and camera.maxPitch = pi/2
|
||||||
|
#define CAMERA_MODE_ORBITAL (0 \
|
||||||
|
| CAMERA_MODE_DISABLE_ROLL \
|
||||||
|
| CAMERA_MODE_CLAMP_PITCH_ANGLE \
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
/* Camera struct */
|
||||||
|
|
||||||
|
typedef struct camera {
|
||||||
|
CameraVec3 target_position; // The target point, the camera is looking at. Aka camera eye position if camera.target_distance == 0.
|
||||||
|
float target_distance; // Camera distance from eye to target. Note: negative values create zoom-like behaviour.
|
||||||
|
CameraQuat orientation; // Camera rotation in 3D.
|
||||||
|
uint32_t mode; // Controls camera behaviour. See CAMERA_MODE_* defines.
|
||||||
|
|
||||||
|
// Temporary accumulator. Cleared on camera_view_matrix(..).
|
||||||
|
CameraVec3 movement_accumulator;
|
||||||
|
CameraVec3 rotation_accumulator;
|
||||||
|
// Angle clamping limits. See "Angle Clamping" for further information.
|
||||||
|
float minPitch;
|
||||||
|
float maxPitch;
|
||||||
|
float minYaw;
|
||||||
|
float maxYaw;
|
||||||
|
float minRoll;
|
||||||
|
float maxRoll;
|
||||||
|
} Camera;
|
||||||
|
|
||||||
|
|
||||||
|
/* Function declarations */
|
||||||
|
|
||||||
|
// Initialize/Reset the camera struct.
|
||||||
|
extern Camera camera_init();
|
||||||
|
|
||||||
|
// Returns the cameras current forward direction (normalized)
|
||||||
|
extern CameraVec3 camera_forward(const Camera* _cam);
|
||||||
|
|
||||||
|
// Returns the cameras current up direction (normalized)
|
||||||
|
extern CameraVec3 camera_up(const Camera* _cam);
|
||||||
|
|
||||||
|
// Returns the cameras current right direction (normalized)
|
||||||
|
extern CameraVec3 camera_right(const Camera* _cam);
|
||||||
|
|
||||||
|
// Returns the cameras current eye position
|
||||||
|
extern CameraVec3 camera_eye(const Camera* _cam);
|
||||||
|
|
||||||
|
// Move the camera in its relative orientation
|
||||||
|
// _offset == (forward, up, right)
|
||||||
|
extern void camera_move(Camera* _cam, const CameraVec3 _offset);
|
||||||
|
|
||||||
|
// Rotate the camera view
|
||||||
|
// _angles = (pitch, yaw, roll)
|
||||||
|
// pitch == "Look Up/Down" yaw == "Look Left/Right" roll == "Turn head Left/Right"
|
||||||
|
// Note: angles are expected in radians
|
||||||
|
extern void camera_rotate(Camera* _cam, const CameraVec3 _angles);
|
||||||
|
|
||||||
|
// Rotate the camera to look into the direction _forward
|
||||||
|
// This only changes the camera.orientation, it will still face its camera.target_position!
|
||||||
|
// Note: _forward and _up are expected to be normalized
|
||||||
|
extern void camera_look_at(Camera* _cam, CameraVec3 _forward, CameraVec3 _up);
|
||||||
|
|
||||||
|
// Update the camera and generate a view matrix
|
||||||
|
// Note: _out_matrix is expected to be a float[16]
|
||||||
|
extern void camera_view_matrix(Camera* _cam, float* _out_matrix);
|
||||||
|
|
||||||
|
|
||||||
|
#endif // !CAMERA_HEADER_GUARD
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef CAMERA_IMPLEMENTATION
|
||||||
|
|
||||||
|
extern Camera camera_init()
|
||||||
|
{
|
||||||
|
static Camera cam = {
|
||||||
|
.target_position = cm_init_vec3(0.0f, 0.0f, 0.0f),
|
||||||
|
.target_distance = 0.0f,
|
||||||
|
.orientation = cm_init_quat(0.0f, 0.0f, 0.0, 0.0f),
|
||||||
|
.mode = CAMERA_MODE_FREE,
|
||||||
|
|
||||||
|
.movement_accumulator = cm_init_vec3(0.0f, 0.0f, 0.0f),
|
||||||
|
.rotation_accumulator = cm_init_vec3(0.0f, 0.0f, 0.0f),
|
||||||
|
|
||||||
|
.minPitch = 0.0f,
|
||||||
|
.maxPitch = 0.0f,
|
||||||
|
.minYaw = 0.0f,
|
||||||
|
.maxYaw = 0.0f,
|
||||||
|
.minRoll = 0.0f,
|
||||||
|
.maxRoll = 0.0f,
|
||||||
|
};
|
||||||
|
|
||||||
|
return cam;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern CameraVec3 camera_forward(const Camera* _cam)
|
||||||
|
{
|
||||||
|
return cm_mul(CAMERA_WORLD_FORWARD, cm_invert(_cam->orientation));
|
||||||
|
};
|
||||||
|
|
||||||
|
extern CameraVec3 camera_up(const Camera* _cam)
|
||||||
|
{
|
||||||
|
return cm_mul(CAMERA_WORLD_UP, cm_invert(_cam->orientation));
|
||||||
|
};
|
||||||
|
|
||||||
|
extern CameraVec3 camera_right(const Camera* _cam)
|
||||||
|
{
|
||||||
|
return cm_mul(CAMERA_WORLD_RIGHT, cm_invert(_cam->orientation));
|
||||||
|
};
|
||||||
|
|
||||||
|
extern CameraVec3 camera_eye(const Camera* _cam)
|
||||||
|
{
|
||||||
|
return cm_add(_cam->target_position, cm_scale(camera_forward(_cam), -_cam->target_distance));
|
||||||
|
};
|
||||||
|
|
||||||
|
extern void camera_move(Camera* _cam, const CameraVec3 _offset)
|
||||||
|
{
|
||||||
|
_cam->movement_accumulator = cm_add(_cam->movement_accumulator, _offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void camera_rotate(Camera* _cam, const CameraVec3 _angles)
|
||||||
|
{
|
||||||
|
_cam->rotation_accumulator = cm_add(_cam->rotation_accumulator, _angles);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void camera_look_at(Camera* _cam, CameraVec3 _forward, CameraVec3 _up)
|
||||||
|
{
|
||||||
|
// Based on typical vector to matrix to quaternion approach
|
||||||
|
|
||||||
|
// Get orthogonal basis vectors
|
||||||
|
const CameraVec3 right = cm_normalizeVec3(cm_cross(_up, _forward));
|
||||||
|
_up = cm_cross(_forward, right);
|
||||||
|
|
||||||
|
// Convert to Quaternion
|
||||||
|
// Ref.: https://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/
|
||||||
|
|
||||||
|
const float m0 = right.x;
|
||||||
|
const float m1 = right.y;
|
||||||
|
const float m2 = right.z;
|
||||||
|
|
||||||
|
const float m4 = _up.x;
|
||||||
|
const float m5 = _up.y;
|
||||||
|
const float m6 = _up.z;
|
||||||
|
|
||||||
|
const float m8 = _forward.x;
|
||||||
|
const float m9 = _forward.y;
|
||||||
|
const float m10 = _forward.z;
|
||||||
|
|
||||||
|
float trace = m0 + m5 + m10;
|
||||||
|
if (trace > 0) {
|
||||||
|
float s = 0.5f / cm_sqrt(trace + 1.0f);
|
||||||
|
_cam->orientation.w = 0.25f / s;
|
||||||
|
_cam->orientation.x = (m6 - m9) * s;
|
||||||
|
_cam->orientation.y = (m8 - m2) * s;
|
||||||
|
_cam->orientation.z = (m1 - m4) * s;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (m0 > m5 && m0 > m10) {
|
||||||
|
float s = 2.0f * cm_sqrt(1.0f + m0 - m5 - m10);
|
||||||
|
_cam->orientation.w = (m6 - m9) / s;
|
||||||
|
_cam->orientation.x = 0.25f * s;
|
||||||
|
_cam->orientation.y = (m4 + m1) / s;
|
||||||
|
_cam->orientation.z = (m8 + m2) / s;
|
||||||
|
}
|
||||||
|
else if (m5 > m10) {
|
||||||
|
float s = 2.0f * cm_sqrt(1.0f + m5 - m0 - m10);
|
||||||
|
_cam->orientation.w = (m8 - m2) / s;
|
||||||
|
_cam->orientation.x = (m4 + m1) / s;
|
||||||
|
_cam->orientation.y = 0.25f * s;
|
||||||
|
_cam->orientation.z = (m9 + m6) / s;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
float s = 2.0f * cm_sqrt(1.0f + m10 - m0 - m5);
|
||||||
|
_cam->orientation.w = (m1 - m4) / s;
|
||||||
|
_cam->orientation.x = (m8 + m2) / s;
|
||||||
|
_cam->orientation.y = (m9 + m6) / s;
|
||||||
|
_cam->orientation.z = 0.25f * s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void camera_view_matrix(Camera* _cam, float* _out_matrix)
|
||||||
|
{
|
||||||
|
/* Clamp angles */
|
||||||
|
|
||||||
|
if (_cam->mode & (CAMERA_MODE_CLAMP_PITCH_ANGLE | CAMERA_MODE_CLAMP_YAW_ANGLE | CAMERA_MODE_CLAMP_ROLL_ANGLE))
|
||||||
|
{
|
||||||
|
const CameraVec3 angles = cm_toEuler(_cam->orientation);
|
||||||
|
|
||||||
|
if (_cam->mode & CAMERA_MODE_CLAMP_PITCH_ANGLE)
|
||||||
|
{
|
||||||
|
_cam->rotation_accumulator.x = cm_max(_cam->minPitch - angles.x, _cam->rotation_accumulator.x);
|
||||||
|
_cam->rotation_accumulator.x = cm_min(_cam->maxPitch - angles.x, _cam->rotation_accumulator.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_cam->mode & CAMERA_MODE_CLAMP_YAW_ANGLE)
|
||||||
|
{
|
||||||
|
_cam->rotation_accumulator.y = cm_max(_cam->minYaw - angles.y, _cam->rotation_accumulator.y);
|
||||||
|
_cam->rotation_accumulator.y = cm_min(_cam->maxYaw - angles.y, _cam->rotation_accumulator.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_cam->mode & CAMERA_MODE_CLAMP_ROLL_ANGLE)
|
||||||
|
{
|
||||||
|
_cam->rotation_accumulator.z = cm_max(_cam->minRoll - angles.z, _cam->rotation_accumulator.z);
|
||||||
|
_cam->rotation_accumulator.z = cm_min(_cam->maxRoll - angles.z, _cam->rotation_accumulator.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Update orientation */
|
||||||
|
|
||||||
|
const CameraQuat pitch = cm_fromAxisAngle(CAMERA_WORLD_RIGHT, _cam->rotation_accumulator.x);
|
||||||
|
const CameraQuat yaw = cm_fromAxisAngle(CAMERA_WORLD_UP, _cam->rotation_accumulator.y);
|
||||||
|
|
||||||
|
if (_cam->mode & CAMERA_MODE_DISABLE_ROLL)
|
||||||
|
{
|
||||||
|
// Note: The multiplication order is important, not to induce roll from pitch+yaw
|
||||||
|
_cam->orientation = cm_mulQuat(_cam->orientation, pitch);
|
||||||
|
_cam->orientation = cm_mulQuat(yaw, _cam->orientation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const CameraQuat roll = cm_fromAxisAngle(CAMERA_WORLD_FORWARD, _cam->rotation_accumulator.z);
|
||||||
|
|
||||||
|
_cam->orientation = cm_mulQuat(_cam->orientation, pitch);
|
||||||
|
_cam->orientation = cm_mulQuat(_cam->orientation, yaw);
|
||||||
|
_cam->orientation = cm_mulQuat(_cam->orientation, roll);
|
||||||
|
}
|
||||||
|
|
||||||
|
_cam->orientation = cm_normalizeQuat(_cam->orientation); // Re-Normalize orientation quaternion
|
||||||
|
|
||||||
|
// Reset accumulator
|
||||||
|
_cam->rotation_accumulator.x = 0.0f;
|
||||||
|
_cam->rotation_accumulator.y = 0.0f;
|
||||||
|
_cam->rotation_accumulator.z = 0.0f;
|
||||||
|
|
||||||
|
|
||||||
|
/* Update target_position */
|
||||||
|
|
||||||
|
CameraVec3 forward = camera_forward(_cam);
|
||||||
|
CameraVec3 up = camera_up(_cam);
|
||||||
|
CameraVec3 right = camera_right(_cam);
|
||||||
|
|
||||||
|
if (_cam->mode & CAMERA_MODE_MOVE_IN_WORLDPLANE)
|
||||||
|
{
|
||||||
|
const float epsilon = 0.0001f; // Avoid floating point errors
|
||||||
|
|
||||||
|
if (forward.y > 1.0f - epsilon) // Note: forward is normalized, so checking .y is sufficient
|
||||||
|
{ // Special case: Looking straight up
|
||||||
|
forward = cm_negate(up);
|
||||||
|
}
|
||||||
|
else if (forward.y < -1.0f + epsilon)
|
||||||
|
{ // Special case: Looking straight down
|
||||||
|
forward = up;
|
||||||
|
}
|
||||||
|
else if (right.y > 1.0f - epsilon)
|
||||||
|
{
|
||||||
|
right = up;
|
||||||
|
}
|
||||||
|
else if (right.y < -1.0f + epsilon)
|
||||||
|
{
|
||||||
|
right = cm_negate(up);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project the forward and right into the world plane
|
||||||
|
forward.y = 0;
|
||||||
|
forward = cm_normalizeVec3(forward);
|
||||||
|
|
||||||
|
right.y = 0;
|
||||||
|
right = cm_normalizeVec3(right);
|
||||||
|
|
||||||
|
up = CAMERA_WORLD_UP;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale by desired distance
|
||||||
|
forward = cm_scale(forward, _cam->movement_accumulator.x);
|
||||||
|
up = cm_scale(up, _cam->movement_accumulator.y);
|
||||||
|
right = cm_scale(right, _cam->movement_accumulator.z);
|
||||||
|
|
||||||
|
// Apply changes to target_position
|
||||||
|
_cam->target_position = cm_add(_cam->target_position, forward);
|
||||||
|
_cam->target_position = cm_add(_cam->target_position, up);
|
||||||
|
_cam->target_position = cm_add(_cam->target_position, right);
|
||||||
|
|
||||||
|
// Reset accumulator
|
||||||
|
_cam->movement_accumulator.x = 0.0f;
|
||||||
|
_cam->movement_accumulator.y = 0.0f;
|
||||||
|
_cam->movement_accumulator.z = 0.0f;
|
||||||
|
|
||||||
|
|
||||||
|
/* Generate view matrix */
|
||||||
|
|
||||||
|
// Get rotation matrix
|
||||||
|
cm_matrixFromQuat(_out_matrix, _cam->orientation);
|
||||||
|
|
||||||
|
// Add translation
|
||||||
|
CameraVec3 translation = cm_negate(camera_eye(_cam));
|
||||||
|
|
||||||
|
translation = cm_mul(translation, _cam->orientation);
|
||||||
|
|
||||||
|
_out_matrix[12] = translation.x;
|
||||||
|
_out_matrix[13] = translation.y;
|
||||||
|
_out_matrix[14] = translation.z;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CAMERA_IMPLEMENTATION
|
||||||
107
src/lib/camera_math_bx.h
Normal file
107
src/lib/camera_math_bx.h
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* INFO:
|
||||||
|
*
|
||||||
|
* This file provides an interface to math functions for camera.h
|
||||||
|
*
|
||||||
|
* Specifically interfacing with bx/math.h
|
||||||
|
* Ref.: https://github.com/bkaradzic/bgfx
|
||||||
|
* Ref.: https://github.com/bkaradzic/bx
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* LICENSE:
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022 Crydsch Cube
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <space_math.h>
|
||||||
|
|
||||||
|
#define CameraVec3 Vec3
|
||||||
|
#define CameraQuat Quat
|
||||||
|
|
||||||
|
static inline CameraVec3 cm_init_vec3(float _x, float _y, float _z) {
|
||||||
|
return bx::Vec3(_x, _y, _z);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline CameraQuat cm_init_quat(float _x, float _y, float _z, float _w) {
|
||||||
|
return bx::Quaternion(_x, _y, _z, _w);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline CameraVec3 cm_mul(CameraVec3 _a, CameraQuat _b) {
|
||||||
|
return bx::mul(_a, _b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline CameraQuat cm_invert(CameraQuat _a) {
|
||||||
|
return bx::invert(_a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline CameraVec3 cm_add(CameraVec3 _a, CameraVec3 _b) {
|
||||||
|
return bx::add(_a, _b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline CameraVec3 cm_scale(CameraVec3 _a, float _b) {
|
||||||
|
return bx::mul(_a, _b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline CameraVec3 cm_cross(CameraVec3 _a, CameraVec3 _b) {
|
||||||
|
return bx::cross(_a, _b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline float cm_min(float _a, float _b) {
|
||||||
|
return bx::min(_a, _b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline float cm_max(float _a, float _b) {
|
||||||
|
return bx::max(_a, _b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline float cm_sqrt(float _a) {
|
||||||
|
return bx::sqrt(_a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline CameraVec3 cm_toEuler(CameraQuat _a) {
|
||||||
|
return bx::toEuler(_a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline CameraQuat cm_fromAxisAngle(CameraVec3 _a, float _b) {
|
||||||
|
return bx::fromAxisAngle(_a, _b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline CameraQuat cm_mulQuat(CameraQuat _a, CameraQuat _b) {
|
||||||
|
return bx::mul(_a, _b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline CameraQuat cm_normalizeQuat(CameraQuat _a) {
|
||||||
|
return bx::normalize(_a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline CameraVec3 cm_normalizeVec3(CameraVec3 _a) {
|
||||||
|
return bx::normalize(_a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline CameraVec3 cm_negate(CameraVec3 _a) {
|
||||||
|
return bx::neg(_a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void cm_matrixFromQuat(float* _a, CameraQuat _b) {
|
||||||
|
return bx::mtxFromQuaternion(_a, _b);
|
||||||
|
}
|
||||||
641
src/lib/input.cpp
Normal file
641
src/lib/input.cpp
Normal file
@@ -0,0 +1,641 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cfloat>
|
||||||
|
|
||||||
|
#include "input.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "space_math.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* buttons and axes keep state (need to be reset manually)
|
||||||
|
* axes are considered normalized by default
|
||||||
|
*
|
||||||
|
* Simple input handling system, with a fire-and-forget design.
|
||||||
|
* You configure it once, and it will handle the rest.
|
||||||
|
* It provides virtual inputs, so you can just poll for the state of your 'throw grenade button' in your gameloop!
|
||||||
|
* Any number of real buttons (keyboard, mouse, controller) can map to this virtual button.
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - buttons and axes
|
||||||
|
* - Supports input sensitivity (via an input multiplier)
|
||||||
|
* - mappings between
|
||||||
|
* - buttons -> buttons
|
||||||
|
* - buttons -> axes (ex. keyboard input -> camera rotation)
|
||||||
|
* - axes -> buttons (ex. controller input -> fixed speed movement)
|
||||||
|
* - axes -> axes
|
||||||
|
* - multiple mappings to the same virtual input
|
||||||
|
* - 'w' or 'controller_axis_1' -> 'move_forward_axis'
|
||||||
|
* - combination mappings (only active if both real inputs are active)
|
||||||
|
* - 'shift' and 'w' -> 'running_button'
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace input {
|
||||||
|
|
||||||
|
// TODO remove_mapping(..) and set active=false
|
||||||
|
|
||||||
|
using Device = struct device {
|
||||||
|
uint16_t real_buttons_offset;
|
||||||
|
uint16_t real_axes_offset;
|
||||||
|
std::string name;
|
||||||
|
bool connected;
|
||||||
|
|
||||||
|
device(const uint16_t _real_buttons_offset, const uint16_t _real_axes_offset, std::string _name) {
|
||||||
|
real_buttons_offset = _real_buttons_offset;
|
||||||
|
real_axes_offset = _real_axes_offset;
|
||||||
|
name = std::move(_name);
|
||||||
|
connected = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using VirtualButton = struct virtual_button {
|
||||||
|
std::vector<uint16_t> real_inputs;
|
||||||
|
std::vector<float> multiplier;
|
||||||
|
};
|
||||||
|
|
||||||
|
using VirtualAxis = struct virtual_axis {
|
||||||
|
std::vector<uint16_t> real_inputs;
|
||||||
|
std::vector<float> multiplier;
|
||||||
|
float min;
|
||||||
|
float max;
|
||||||
|
|
||||||
|
virtual_axis(const float _min, const float _max) {
|
||||||
|
min = _min;
|
||||||
|
max = _max;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint16_t BUTTON_BIT = 0x1 << 15; // virtual input is button (instead of axis)
|
||||||
|
const uint16_t COMBINE_BIT = 0x1 << 14; // combine next input with logical 'and' (instead of 'or')
|
||||||
|
const uint16_t ID_MASK = COMBINE_BIT - 0x1; // bit mask to extract id
|
||||||
|
|
||||||
|
const float DEADZONE = 0.1f; // per axis deadzone
|
||||||
|
|
||||||
|
std::vector<Device> devices = { { 0, 0, "" }}; // Includes first dummy device
|
||||||
|
std::vector<DID> active_devices; // is online and has at least one mapping
|
||||||
|
std::vector<uint8_t> real_buttons_is = { };
|
||||||
|
std::vector<uint8_t> real_buttons_was = { };
|
||||||
|
std::vector<float> real_axes_is = { };
|
||||||
|
std::vector<float> real_axes_was = { };
|
||||||
|
std::vector<VirtualButton> virtual_buttons;
|
||||||
|
std::vector<VirtualAxis> virtual_axes;
|
||||||
|
|
||||||
|
inline uint16_t SET_BUTTON_BIT(uint16_t _id)
|
||||||
|
{
|
||||||
|
return _id | BUTTON_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool GET_BUTTON_BIT(uint16_t _id)
|
||||||
|
{
|
||||||
|
return _id & BUTTON_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint16_t SET_COMBINE_BIT(uint16_t _id)
|
||||||
|
{
|
||||||
|
return _id | COMBINE_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool GET_COMBINE_BIT(uint16_t _id)
|
||||||
|
{
|
||||||
|
return _id & COMBINE_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint16_t GET_ID(uint16_t _id)
|
||||||
|
{
|
||||||
|
return _id & ID_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float apply_deadzone(float _value) {
|
||||||
|
return std::abs(_value) < DEADZONE ? 0.0f : _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update()
|
||||||
|
{
|
||||||
|
// Copy state
|
||||||
|
real_buttons_was = real_buttons_is;
|
||||||
|
real_axes_was = real_axes_is;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t get_device_num_buttons(const DID _device)
|
||||||
|
{
|
||||||
|
assert(_device < devices.size() - 1);
|
||||||
|
|
||||||
|
Device& device = devices[_device];
|
||||||
|
Device& device_next = devices[_device + 1];
|
||||||
|
|
||||||
|
return device_next.real_buttons_offset - device.real_buttons_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t get_device_num_axes(const DID _device)
|
||||||
|
{
|
||||||
|
assert(_device < devices.size() - 1);
|
||||||
|
|
||||||
|
Device& device = devices[_device];
|
||||||
|
Device& device_next = devices[_device + 1];
|
||||||
|
|
||||||
|
return device_next.real_axes_offset - device.real_axes_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [internal]
|
||||||
|
bool device_has_mappings(const DID _device) {
|
||||||
|
assert(_device < devices.size() - 1);
|
||||||
|
|
||||||
|
// get range of absolute button & axis indices for this device
|
||||||
|
Device& device = devices[_device];
|
||||||
|
uint16_t btn_range_start = device.real_buttons_offset;
|
||||||
|
uint16_t btn_range_end = device.real_buttons_offset + get_device_num_buttons(_device) - 1;
|
||||||
|
uint16_t axis_range_start = device.real_axes_offset;
|
||||||
|
uint16_t axis_range_end = device.real_axes_offset + get_device_num_axes(_device) - 1;
|
||||||
|
|
||||||
|
// iterate all virtual buttons & axes
|
||||||
|
// check if any of their mappings refer to the set of device indices
|
||||||
|
for (uint16_t vbid = 0; vbid < virtual_buttons.size(); ++vbid) {
|
||||||
|
VirtualButton& btn = virtual_buttons[vbid];
|
||||||
|
|
||||||
|
for (uint16_t rid : btn.real_inputs) {
|
||||||
|
bool is_button = GET_BUTTON_BIT(rid);
|
||||||
|
uint16_t index = GET_ID(rid);
|
||||||
|
|
||||||
|
if ((is_button && between(index, btn_range_start, btn_range_end)) ||
|
||||||
|
(!is_button && between(index, axis_range_start, axis_range_end)))
|
||||||
|
{ // Found valid mapping!
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t vid = 0; vid < virtual_axes.size(); ++vid) {
|
||||||
|
VirtualAxis& axis = virtual_axes[vid];
|
||||||
|
|
||||||
|
for (uint16_t rid : axis.real_inputs) {
|
||||||
|
bool is_button = GET_BUTTON_BIT(rid);
|
||||||
|
uint16_t index = GET_ID(rid);
|
||||||
|
|
||||||
|
if ((is_button && between(index, btn_range_start, btn_range_end)) ||
|
||||||
|
(!is_button && between(index, axis_range_start, axis_range_end)))
|
||||||
|
{ // Found valid mapping!
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_device_active(const DID _device) {
|
||||||
|
return std::find(active_devices.begin(), active_devices.end(), _device) != active_devices.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add GUID to device
|
||||||
|
DID add_device(const uint16_t _num_buttons, const uint16_t _num_axes, std::string _name)
|
||||||
|
{
|
||||||
|
// Try to re-identify a disconnected device
|
||||||
|
for (uint16_t id = 0; id < devices.size(); ++id) {
|
||||||
|
Device& device = devices[id];
|
||||||
|
if (!device.connected &&
|
||||||
|
get_device_num_buttons(id) == _num_buttons &&
|
||||||
|
get_device_num_axes(id) == _num_axes &&
|
||||||
|
device.name == _name)
|
||||||
|
{
|
||||||
|
// Found it!
|
||||||
|
logInfo("Re-identified input device ID: %d Buttons: %d Axes: %d Name: %s\n", id, _num_buttons, _num_axes, _name.c_str());
|
||||||
|
device.connected = true;
|
||||||
|
|
||||||
|
// Check if this device has mappings
|
||||||
|
assert(std::find(active_devices.begin(), active_devices.end(), id) == active_devices.end());
|
||||||
|
if (device_has_mappings(id)) {
|
||||||
|
active_devices.emplace_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new device
|
||||||
|
DID id = (uint16_t)devices.size() - 1;
|
||||||
|
Device& device = devices[id];
|
||||||
|
|
||||||
|
// We now have the current dummy device
|
||||||
|
// Its offsets mark the current position on the bitset/vector
|
||||||
|
|
||||||
|
// Add new buttons
|
||||||
|
assert(real_buttons_is.size() == device.real_buttons_offset);
|
||||||
|
real_buttons_is.resize(device.real_buttons_offset + _num_buttons);
|
||||||
|
real_buttons_was.resize(device.real_buttons_offset + _num_buttons);
|
||||||
|
|
||||||
|
// Add new axes
|
||||||
|
assert(real_axes_is.size() == device.real_axes_offset);
|
||||||
|
real_axes_is.resize(device.real_axes_offset + _num_axes);
|
||||||
|
real_axes_was.resize(device.real_axes_offset + _num_axes);
|
||||||
|
|
||||||
|
logInfo("New input device ID: %d Buttons: %d Axes: %d Name: %s\n", id, _num_buttons, _num_axes, _name.c_str());
|
||||||
|
|
||||||
|
// Set device info
|
||||||
|
// no need to set offsets, they are already correct!
|
||||||
|
device.name = std::move(_name);
|
||||||
|
|
||||||
|
// Push new dummy device (marking new end)
|
||||||
|
devices.emplace_back(real_buttons_is.size(), real_axes_is.size(), "");
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_device(const DID _device) {
|
||||||
|
reset_device(_device);
|
||||||
|
Device& device = devices[_device];
|
||||||
|
device.connected = false;
|
||||||
|
|
||||||
|
// find and remove from active devices
|
||||||
|
auto it = std::find(active_devices.begin(), active_devices.end(), _device);
|
||||||
|
if (it != active_devices.end()) {
|
||||||
|
active_devices.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset_device_buttons(const DID _device)
|
||||||
|
{
|
||||||
|
assert(_device < devices.size() - 1);
|
||||||
|
|
||||||
|
Device& device = devices[_device];
|
||||||
|
Device& device_next = devices[_device + 1];
|
||||||
|
|
||||||
|
// Reset all buttons
|
||||||
|
for (uint16_t i = device.real_buttons_offset; i < device_next.real_buttons_offset; ++i) {
|
||||||
|
real_buttons_is[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset_device_axes(const DID _device)
|
||||||
|
{
|
||||||
|
assert(_device < devices.size() - 1);
|
||||||
|
|
||||||
|
Device& device = devices[_device];
|
||||||
|
Device& device_next = devices[_device + 1];
|
||||||
|
|
||||||
|
// Reset all axes
|
||||||
|
for (uint16_t i = device.real_axes_offset; i < device_next.real_axes_offset; ++i) {
|
||||||
|
real_axes_is[i] = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset_device(const DID _device)
|
||||||
|
{
|
||||||
|
reset_device_buttons(_device);
|
||||||
|
reset_device_axes(_device);
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_buttons(const DID _device, const uint16_t _start_index, const uint8_t* _new_states, const uint16_t _num_states)
|
||||||
|
{
|
||||||
|
assert(_device < devices.size() - 1);
|
||||||
|
assert(_new_states != nullptr);
|
||||||
|
assert(_start_index + _num_states <= get_device_num_buttons(_device));
|
||||||
|
|
||||||
|
Device& device = devices[_device];
|
||||||
|
std::memcpy(real_buttons_is.data() + device.real_buttons_offset + _start_index, _new_states, _num_states * sizeof(uint8_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_button(const DID _device, const uint16_t _index, const uint8_t _new_state)
|
||||||
|
{
|
||||||
|
update_buttons(_device, _index, &_new_state, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool accumulate>
|
||||||
|
void update_axes(const DID _device, const uint16_t _start_index, const float* _new_states, const uint16_t _num_states)
|
||||||
|
{
|
||||||
|
assert(_device < devices.size() - 1);
|
||||||
|
assert(_new_states != nullptr);
|
||||||
|
assert(_start_index + _num_states <= get_device_num_axes(_device));
|
||||||
|
|
||||||
|
Device& device = devices[_device];
|
||||||
|
float* dst = real_axes_is.data() + device.real_axes_offset + _start_index;
|
||||||
|
|
||||||
|
if constexpr (accumulate) {
|
||||||
|
for (uint16_t i = 0; i < _num_states; ++i) {
|
||||||
|
dst[i] += _new_states[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::memcpy(dst, _new_states, _num_states * sizeof(float));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_axes(const DID _device, const uint16_t _start_index, const float* _new_states, const uint16_t _num_states)
|
||||||
|
{
|
||||||
|
update_axes<false>(_device, _start_index, _new_states, _num_states);
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_axes_acc(const DID _device, const uint16_t _start_index, const float* _new_states, const uint16_t _num_states)
|
||||||
|
{
|
||||||
|
update_axes<true>(_device, _start_index, _new_states, _num_states);
|
||||||
|
}
|
||||||
|
|
||||||
|
VBID add_virtual_button()
|
||||||
|
{
|
||||||
|
VBID vbid = (uint16_t)virtual_buttons.size();
|
||||||
|
virtual_buttons.emplace_back();
|
||||||
|
return vbid;
|
||||||
|
}
|
||||||
|
|
||||||
|
VAID add_virtual_axis(const float _min_value, const float _max_value)
|
||||||
|
{
|
||||||
|
VAID id = (uint16_t)virtual_axes.size();
|
||||||
|
virtual_axes.emplace_back(_min_value, _max_value);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_mapping(const VBID _button)
|
||||||
|
{
|
||||||
|
assert(_button < virtual_buttons.size());
|
||||||
|
|
||||||
|
VirtualButton& v_button = virtual_buttons[_button];
|
||||||
|
v_button.real_inputs.clear();
|
||||||
|
v_button.multiplier.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_mapping(const VAID _axis)
|
||||||
|
{
|
||||||
|
assert(_axis < virtual_buttons.size());
|
||||||
|
|
||||||
|
// virtual axis
|
||||||
|
VirtualAxis& v_axis = virtual_axes[_axis];
|
||||||
|
v_axis.real_inputs.clear();
|
||||||
|
v_axis.multiplier.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_button(const DID _device, const uint16_t _index, const VBID _button, const bool _combine_with_next)
|
||||||
|
{
|
||||||
|
assert(_device < devices.size() - 1);
|
||||||
|
assert(_index < get_device_num_buttons(_device));
|
||||||
|
assert(_button < virtual_buttons.size());
|
||||||
|
|
||||||
|
// get internal index of this button
|
||||||
|
Device& device = devices[_device];
|
||||||
|
uint16_t internal_id = device.real_buttons_offset + _index;
|
||||||
|
|
||||||
|
internal_id = SET_BUTTON_BIT(internal_id);
|
||||||
|
if (_combine_with_next) {
|
||||||
|
internal_id = SET_COMBINE_BIT(internal_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// virtual button
|
||||||
|
VirtualButton& v_button = virtual_buttons[_button];
|
||||||
|
v_button.real_inputs.emplace_back(internal_id);
|
||||||
|
v_button.multiplier.emplace_back(0.0f); // Button to Button mappings do not use multiplier
|
||||||
|
|
||||||
|
if (device.connected) {
|
||||||
|
// we added a mapping => mark device as active (if not already)
|
||||||
|
auto it = std::find(active_devices.begin(), active_devices.end(), _device);
|
||||||
|
if (it == active_devices.end()) {
|
||||||
|
active_devices.emplace_back(_device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_button(const DID _device, const uint16_t _index, const VAID _axis, const float _multiplier, const bool _combine_with_next)
|
||||||
|
{
|
||||||
|
assert(_device < devices.size() - 1);
|
||||||
|
assert(_index < get_device_num_buttons(_device));
|
||||||
|
assert(_axis < virtual_axes.size());
|
||||||
|
|
||||||
|
// get internal index of this button
|
||||||
|
Device& device = devices[_device];
|
||||||
|
uint16_t internal_id = device.real_buttons_offset + _index;
|
||||||
|
|
||||||
|
internal_id = SET_BUTTON_BIT(internal_id);
|
||||||
|
if (_combine_with_next) {
|
||||||
|
internal_id = SET_COMBINE_BIT(internal_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// virtual axis
|
||||||
|
VirtualAxis& v_axis = virtual_axes[_axis];
|
||||||
|
v_axis.real_inputs.emplace_back(internal_id);
|
||||||
|
v_axis.multiplier.emplace_back(_multiplier);
|
||||||
|
|
||||||
|
if (device.connected) {
|
||||||
|
// we added a mapping => mark device as active (if not already)
|
||||||
|
auto it = std::find(active_devices.begin(), active_devices.end(), _device);
|
||||||
|
if (it == active_devices.end()) {
|
||||||
|
active_devices.emplace_back(_device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_axis(const DID _device, const uint16_t _index, const VBID _button, const float _multiplier, const bool _combine_with_next)
|
||||||
|
{
|
||||||
|
assert(_device < devices.size() - 1);
|
||||||
|
assert(_index < get_device_num_axes(_device));
|
||||||
|
assert(_button < virtual_buttons.size());
|
||||||
|
|
||||||
|
// get internal index of this button
|
||||||
|
Device& device = devices[_device];
|
||||||
|
uint16_t internal_id = device.real_axes_offset + _index;
|
||||||
|
|
||||||
|
if (_combine_with_next) {
|
||||||
|
internal_id = SET_COMBINE_BIT(internal_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// virtual button
|
||||||
|
VirtualButton& v_button = virtual_buttons[_button];
|
||||||
|
v_button.real_inputs.emplace_back(internal_id);
|
||||||
|
v_button.multiplier.emplace_back(_multiplier);
|
||||||
|
|
||||||
|
if (device.connected) {
|
||||||
|
// we added a mapping => mark device as active (if not already)
|
||||||
|
auto it = std::find(active_devices.begin(), active_devices.end(), _device);
|
||||||
|
if (it == active_devices.end()) {
|
||||||
|
active_devices.emplace_back(_device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_axis(const DID _device, const uint16_t _index, const VAID _axis, const float _multiplier, const bool _combine_with_next)
|
||||||
|
{
|
||||||
|
assert(_device < devices.size() - 1);
|
||||||
|
assert(_index < get_device_num_axes(_device));
|
||||||
|
assert(_axis < virtual_axes.size());
|
||||||
|
|
||||||
|
// get internal index of this button
|
||||||
|
Device& device = devices[_device];
|
||||||
|
uint16_t internal_id = device.real_axes_offset + _index;
|
||||||
|
|
||||||
|
if (_combine_with_next) {
|
||||||
|
internal_id = SET_COMBINE_BIT(internal_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// virtual axis
|
||||||
|
VirtualAxis& v_axis = virtual_axes[_axis];
|
||||||
|
v_axis.real_inputs.emplace_back(internal_id);
|
||||||
|
v_axis.multiplier.emplace_back(_multiplier);
|
||||||
|
|
||||||
|
if (device.connected) {
|
||||||
|
// we added a mapping => mark device as active (if not already)
|
||||||
|
auto it = std::find(active_devices.begin(), active_devices.end(), _device);
|
||||||
|
if (it == active_devices.end()) {
|
||||||
|
active_devices.emplace_back(_device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [internal]
|
||||||
|
template<bool in_current_frame>
|
||||||
|
bool button_down(const VBID _button) {
|
||||||
|
VirtualButton& v_button = virtual_buttons[_button];
|
||||||
|
|
||||||
|
bool down = false;
|
||||||
|
bool combination_group_down = true;
|
||||||
|
for (int i = 0; i < v_button.real_inputs.size(); ++i) {
|
||||||
|
uint16_t r_input = v_button.real_inputs[i];
|
||||||
|
|
||||||
|
bool is_button = GET_BUTTON_BIT(r_input);
|
||||||
|
bool is_combination = GET_COMBINE_BIT(r_input); // we have to AND it with the next one
|
||||||
|
uint16_t index = GET_ID(r_input);
|
||||||
|
|
||||||
|
bool tmp;
|
||||||
|
if (is_button) {
|
||||||
|
if constexpr (in_current_frame) {
|
||||||
|
tmp = real_buttons_is[index] > 0; // v_button.multiplier[i] ignored here
|
||||||
|
} else {
|
||||||
|
tmp = real_buttons_was[index] > 0; // v_button.multiplier[i] ignored here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if constexpr (in_current_frame) {
|
||||||
|
tmp = (apply_deadzone(real_axes_is[index]) * v_button.multiplier[i]) > 0.5f;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tmp = (apply_deadzone(real_axes_was[index]) * v_button.multiplier[i]) > 0.5f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
combination_group_down &= tmp;
|
||||||
|
|
||||||
|
if (!is_combination) {
|
||||||
|
// last combination group ended
|
||||||
|
down |= combination_group_down;
|
||||||
|
// reset combination group state
|
||||||
|
combination_group_down = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return down;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool button_is_down(const VBID _button) {
|
||||||
|
return button_down<true>(_button);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool button_was_down(const VBID _button) {
|
||||||
|
return button_down<false>(_button);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool button_pressed(const VBID _button)
|
||||||
|
{
|
||||||
|
return button_is_down(_button) && !button_was_down(_button);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool button_released(const VBID _button)
|
||||||
|
{
|
||||||
|
return button_was_down(_button) && !button_is_down(_button);
|
||||||
|
}
|
||||||
|
|
||||||
|
// [internal]
|
||||||
|
template<bool in_current_frame>
|
||||||
|
float axis_value(const VAID _axis)
|
||||||
|
{
|
||||||
|
assert(_axis < virtual_axes.size());
|
||||||
|
|
||||||
|
VirtualAxis& v_axis = virtual_axes[_axis];
|
||||||
|
|
||||||
|
float value = 0.0f;
|
||||||
|
float combination_group_value = FLT_MAX;
|
||||||
|
for (int i = 0; i < v_axis.real_inputs.size(); ++i) {
|
||||||
|
uint16_t r_input = v_axis.real_inputs[i];
|
||||||
|
|
||||||
|
bool is_button = GET_BUTTON_BIT(r_input);
|
||||||
|
bool is_combination = GET_COMBINE_BIT(r_input); // we have to AND it with the next one
|
||||||
|
uint16_t index = GET_ID(r_input);
|
||||||
|
|
||||||
|
float tmp;
|
||||||
|
if (is_button) {
|
||||||
|
if constexpr (in_current_frame) {
|
||||||
|
tmp = real_buttons_is[index] * v_axis.multiplier[i];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tmp = real_buttons_was[index] * v_axis.multiplier[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if constexpr (in_current_frame) {
|
||||||
|
tmp = apply_deadzone(real_axes_is[index]) * v_axis.multiplier[i];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tmp = apply_deadzone(real_axes_was[index]) * v_axis.multiplier[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
combination_group_value = std::min(combination_group_value, tmp);
|
||||||
|
|
||||||
|
if (!is_combination) {
|
||||||
|
// last combination group ended
|
||||||
|
value += combination_group_value;
|
||||||
|
// reset combination group state
|
||||||
|
combination_group_value = FLT_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::clamp(value, v_axis.min, v_axis.max);
|
||||||
|
}
|
||||||
|
|
||||||
|
float axis_value_is(const VAID _axis)
|
||||||
|
{
|
||||||
|
return axis_value<true>(_axis);
|
||||||
|
}
|
||||||
|
|
||||||
|
float axis_value_was(const VAID _axis)
|
||||||
|
{
|
||||||
|
return axis_value<false>(_axis);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<DID>& get_active_devices() {
|
||||||
|
return active_devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
void detect_input_callback(std::function<void()> _callback) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void detect_input() {
|
||||||
|
// return did, is_button, index, value,
|
||||||
|
|
||||||
|
for (uint16_t did = 0; did < devices.size() - 1; ++did) {
|
||||||
|
Device& device = devices[did];
|
||||||
|
|
||||||
|
uint16_t num_buttons = get_device_num_buttons(did);
|
||||||
|
for (uint16_t btn_index = 0; btn_index < num_buttons; ++btn_index) {
|
||||||
|
uint8_t btn_is = real_buttons_is[device.real_buttons_offset + btn_index];
|
||||||
|
uint8_t btn_was = real_buttons_was[device.real_buttons_offset + btn_index];
|
||||||
|
if (btn_is != btn_was)
|
||||||
|
{ // change detected
|
||||||
|
logDebug("Device input detected: ID: %d Index: %d Is_Button: %d Value: %f\n", did, btn_index, 1, btn_is ? 1.0f : 0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t num_axes = get_device_num_axes(did);
|
||||||
|
for (uint16_t axis_index = 0; axis_index < num_axes; ++axis_index) {
|
||||||
|
float axis_is = apply_deadzone(real_axes_is[device.real_axes_offset + axis_index]);
|
||||||
|
float axis_was = apply_deadzone(real_axes_was[device.real_axes_offset + axis_index]);
|
||||||
|
if (axis_is != axis_was)
|
||||||
|
{ // change detected
|
||||||
|
logDebug("Device input detected: ID: %d Index: %d Is_Button: %d Value: %f\n", did, axis_index, 0, axis_is);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
172
src/lib/input.h
Normal file
172
src/lib/input.h
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cfloat>
|
||||||
|
|
||||||
|
namespace input {
|
||||||
|
|
||||||
|
// An ID used to identify devices
|
||||||
|
// Basically just a uint16_t - You can convert freely to and from it
|
||||||
|
// This type exists to aid correct usage
|
||||||
|
// Note: Returned ids are guaranteed to start from 0
|
||||||
|
using DID = struct device_id {
|
||||||
|
uint16_t id;
|
||||||
|
|
||||||
|
// default constructor
|
||||||
|
constexpr device_id() : id(0) {}
|
||||||
|
|
||||||
|
// implicit constructor from uint16_t
|
||||||
|
constexpr device_id(const uint16_t _id) : id(_id) {}
|
||||||
|
// implicit conversion to uint16_t
|
||||||
|
constexpr operator uint16_t() const { return id; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// An ID used to identify virtual buttons
|
||||||
|
// Basically just a uint16_t - You can convert freely to and from it
|
||||||
|
// This type exists to aid correct usage
|
||||||
|
// Note: Returned ids are guaranteed to start from 0
|
||||||
|
using VBID = struct virtual_button_id {
|
||||||
|
uint16_t id;
|
||||||
|
|
||||||
|
// implicit constructor from uint16_t
|
||||||
|
constexpr virtual_button_id(const uint16_t _id) : id(_id) {}
|
||||||
|
// implicit conversion to uint16_t
|
||||||
|
constexpr operator uint16_t() const { return id; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// An ID used to identify virtual axes
|
||||||
|
// Basically just a uint16_t - You can convert freely to and from it
|
||||||
|
// This type exists to aid correct usage
|
||||||
|
// Note: Returned ids are guaranteed to start from 0
|
||||||
|
using VAID = struct virtual_axis_id {
|
||||||
|
uint16_t id;
|
||||||
|
|
||||||
|
// implicit constructor from uint16_t
|
||||||
|
constexpr virtual_axis_id(const uint16_t _id) : id(_id) {}
|
||||||
|
// implicit conversion to uint16_t
|
||||||
|
constexpr operator uint16_t() const { return id; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Advance the internal state to the next frame.
|
||||||
|
// Should be called every frame BEFORE new input is updated
|
||||||
|
void update();
|
||||||
|
|
||||||
|
// Tries to re-identify an offline device with the same number
|
||||||
|
// of buttons, axes and matching name. Otherwise adds a new device
|
||||||
|
// Returns the ID of the device
|
||||||
|
// Note: Buttons and axes are indexed from 0 to num_<buttons/axes> - 1
|
||||||
|
DID add_device(const uint16_t _num_buttons, const uint16_t _num_axes, std::string _name);
|
||||||
|
|
||||||
|
// Marks it as offline
|
||||||
|
// The internal state is reset, but not actually removed
|
||||||
|
// A later call to 'add_device(..)' with the same parameters,
|
||||||
|
// may re-identify this device (and return its original DID)
|
||||||
|
void remove_device(const DID _device);
|
||||||
|
|
||||||
|
// Resets all buttons (to the state 'up') of the given device
|
||||||
|
void reset_device_buttons(const DID _device);
|
||||||
|
|
||||||
|
// Resets all axes (to 0.0f) of the given device
|
||||||
|
void reset_device_axes(const DID _device);
|
||||||
|
|
||||||
|
// Resets all buttons and all axes of the given device
|
||||||
|
void reset_device(const DID _device);
|
||||||
|
|
||||||
|
// Returns whether the device is active (connected and has at least one mapping)
|
||||||
|
bool is_device_active(const DID _device);
|
||||||
|
|
||||||
|
// Returns list of all active devices (devices that are connected and have at least one mapping)
|
||||||
|
// Useful to check which devices need to be updated
|
||||||
|
const std::vector<DID>& get_active_devices();
|
||||||
|
|
||||||
|
// Update multiple buttons of this device at once
|
||||||
|
// _start_index is the index of the first button to update
|
||||||
|
// _new_states must hold at least _num_states
|
||||||
|
void update_buttons(const DID _device, const uint16_t _start_index, const uint8_t* _new_states, const uint16_t _num_states);
|
||||||
|
|
||||||
|
// Update a single buttons state
|
||||||
|
void update_button(const DID _device, const uint16_t _index, const uint8_t _new_state);
|
||||||
|
|
||||||
|
// Update multiple axes of this device at once (overwriting the state)
|
||||||
|
// _start_index is the index of the first axis to update
|
||||||
|
// _new_states must hold at least _num_states
|
||||||
|
void update_axes(const DID _device, const uint16_t _start_index, const float* _new_states, const uint16_t _num_states);
|
||||||
|
|
||||||
|
// Update multiple axes of this device at once (accumulating to the state)
|
||||||
|
// _start_index is the index of the first axis to update
|
||||||
|
// _new_states must hold at least _num_states
|
||||||
|
void update_axes_acc(const DID _device, const uint16_t _start_index, const float* _new_states, const uint16_t _num_states);
|
||||||
|
|
||||||
|
// Update a real input buttons state (overwriting the state)
|
||||||
|
inline void update_axis(const DID _device, const uint16_t _index, const float _new_state)
|
||||||
|
{
|
||||||
|
update_axes(_device, _index, &_new_state, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update a real input buttons state (accumulating to the state)
|
||||||
|
inline void update_axis_acc(const DID _device, const uint16_t _index, const float _new_state)
|
||||||
|
{
|
||||||
|
update_axes_acc(_device, _index, &_new_state, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a virtual button
|
||||||
|
VBID add_virtual_button();
|
||||||
|
|
||||||
|
// Add a virtual axis
|
||||||
|
// Its range will be confined to [_min_value; _max_value]
|
||||||
|
// Use defaults for unrestricted values or
|
||||||
|
// set [-1;1] for a normalized axis or
|
||||||
|
// typically you want set these to gameplay restriction ex.: max_walk_speed, max_camera_speed.
|
||||||
|
VAID add_virtual_axis(const float _min_value = -FLT_MAX, const float _max_value = FLT_MAX);
|
||||||
|
|
||||||
|
// Remove all mappings from this virtual button
|
||||||
|
void clear_mapping(const VBID _button);
|
||||||
|
|
||||||
|
// Remove all mappings from this virtual axis
|
||||||
|
void clear_mapping(const VAID _axis);
|
||||||
|
|
||||||
|
// Map a real button to a virtual button
|
||||||
|
// if _combine_with_next is true, this mapping will be combined with the next mapping applied to this virtual button
|
||||||
|
// Note: The last mapping in a combination group must have _combine_with_next == false
|
||||||
|
void map_button(const DID _device, const uint16_t _index, const VBID _button, const bool _combine_with_next = false);
|
||||||
|
|
||||||
|
// Map a real button to a virtual axis
|
||||||
|
// if _combine_with_next is true, this mapping will be combined with the next mapping applied to this virtual axis
|
||||||
|
// Note: The last mapping in a combination group must have _combine_with_next == false
|
||||||
|
void map_button(const DID _device, const uint16_t _index, const VAID _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false);
|
||||||
|
|
||||||
|
// Map a real axis to a virtual button
|
||||||
|
// if _combine_with_next is true, this mapping will be combined with the next mapping applied to this virtual button
|
||||||
|
// Note: The last mapping in a combination group must have _combine_with_next == false
|
||||||
|
void map_axis(const DID _device, const uint16_t _index, const VBID _button, const float _multiplier = 1.0f, const bool _combine_with_next = false);
|
||||||
|
|
||||||
|
// Map a real axis to a virtual axis
|
||||||
|
// if _combine_with_next is true, this mapping will be combined with the next mapping applied to this virtual axis
|
||||||
|
// Note: The last mapping in a combination group must have _combine_with_next == false
|
||||||
|
void map_axis(const DID _device, const uint16_t _index, const VAID _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false);
|
||||||
|
|
||||||
|
// Poll whether the virtual button is down in the current frame
|
||||||
|
bool button_is_down(const VBID _button);
|
||||||
|
|
||||||
|
// Poll whether the virtual button was down in the previous frame
|
||||||
|
bool button_was_down(const VBID _button);
|
||||||
|
|
||||||
|
// Poll whether the virtual button was pressed from the previous frame to the current frame
|
||||||
|
// This is a rising edge detection aka is_down && !was_down
|
||||||
|
bool button_pressed(const VBID _button);
|
||||||
|
|
||||||
|
// Poll whether the virtual button was released from the previous frame to the current frame
|
||||||
|
// This is a falling edge detection aka was_down && !is_down
|
||||||
|
bool button_released(const VBID _button);
|
||||||
|
|
||||||
|
// Poll axis value in the current frame
|
||||||
|
float axis_value_is(const VAID _axis);
|
||||||
|
|
||||||
|
// Poll axis value in the previous frame
|
||||||
|
float axis_value_was(const VAID _axis);
|
||||||
|
|
||||||
|
void detect_input();
|
||||||
|
}
|
||||||
8
src/lib/stb_image.cpp
Normal file
8
src/lib/stb_image.cpp
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
|
#pragma warning( disable : 6262 26451)
|
||||||
|
#include "stb_image.h"
|
||||||
|
#include "stb_image_write.h"
|
||||||
|
#pragma warning( default : 6262 26451)
|
||||||
|
|
||||||
|
/// This file only exists as a translation unit for the library implementation
|
||||||
392
src/main.cpp
Normal file
392
src/main.cpp
Normal file
@@ -0,0 +1,392 @@
|
|||||||
|
#include <bx/platform.h>
|
||||||
|
|
||||||
|
#define GLFW_INCLUDE_NONE
|
||||||
|
#include <GLFW/glfw3.h>
|
||||||
|
|
||||||
|
#if BX_PLATFORM_LINUX || BX_PLATFORM_BSD
|
||||||
|
#define GLFW_EXPOSE_NATIVE_WAYLAND
|
||||||
|
#define GLFW_EXPOSE_NATIVE_X11
|
||||||
|
#elif BX_PLATFORM_WINDOWS
|
||||||
|
#define GLFW_EXPOSE_NATIVE_WIN32
|
||||||
|
#elif BX_PLATFORM_OSX
|
||||||
|
#define GLFW_EXPOSE_NATIVE_COCOA
|
||||||
|
#endif
|
||||||
|
#include <GLFW/glfw3native.h>
|
||||||
|
|
||||||
|
#include <bx/thread.h>
|
||||||
|
#include <bx/timer.h>
|
||||||
|
#include <bgfx/bgfx.h>
|
||||||
|
#include <bgfx/platform.h>
|
||||||
|
#include <thread>
|
||||||
|
#include <bx/thread.h>
|
||||||
|
#include <bx/timer.h>
|
||||||
|
#include <bgfx/bgfx.h>
|
||||||
|
#include <bgfx/platform.h>
|
||||||
|
|
||||||
|
#include "test.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "gameloop.h"
|
||||||
|
#include "space_input.h"
|
||||||
|
|
||||||
|
uint32_t window_width = config::MINIMAL_WINDOW_WIDTH;
|
||||||
|
uint32_t window_height = config::MINIMAL_WINDOW_HEIGHT;
|
||||||
|
bool cursor_entered_window = false;
|
||||||
|
GLFWgamepadstate gamepad_state;
|
||||||
|
|
||||||
|
static void glfw_error_callback(int error, const char *description)
|
||||||
|
{
|
||||||
|
logErr("GLFW error %d: %s\n", error, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void glfw_window_size_callback(GLFWwindow *_window, int _window_width, int _window_height)
|
||||||
|
{
|
||||||
|
window_width = _window_width;
|
||||||
|
window_height = _window_height;
|
||||||
|
// Send window resize event to the game thread (to update the bgfx framebuffer size)
|
||||||
|
Event *ev = event_queue.begin_push();
|
||||||
|
ev->type = Resize;
|
||||||
|
event_queue.end_push();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void glfw_window_focus_callback(GLFWwindow *_window, int _focused)
|
||||||
|
{
|
||||||
|
if (_focused)
|
||||||
|
{
|
||||||
|
glfwSetCursorPos(_window, 0, 0); // Avoid camera jump
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void glfw_key_callback(GLFWwindow *_window, int _key, int _scancode, int _action, int _mods)
|
||||||
|
{
|
||||||
|
if (_action == GLFW_REPEAT)
|
||||||
|
return; // We ignore key repeats
|
||||||
|
input::update_button_keycode(_scancode, _action == GLFW_PRESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void glfw_mouse_button_callback(GLFWwindow *_window, int _button, int _action, int _mods)
|
||||||
|
{
|
||||||
|
input::update_button(input::Device::Mouse, _button, _action == GLFW_PRESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void glfw_cursor_callback(GLFWwindow *_window, double _xpos, double _ypos)
|
||||||
|
{
|
||||||
|
// Avoid camera jump on (re-)entering the window
|
||||||
|
// ex. alt-tabbing
|
||||||
|
if (cursor_entered_window)
|
||||||
|
{
|
||||||
|
cursor_entered_window = false;
|
||||||
|
_xpos = 0.0; // Ignore first cursor movement
|
||||||
|
_ypos = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float pos[2] = {(float)_xpos, (float)_ypos};
|
||||||
|
// input::update_axis_acc(Device::Mouse, input::MOUSE_CURSOR_X_AXIS_INDEX, pos[0]);
|
||||||
|
// input::update_axis_acc(Device::Mouse, input::MOUSE_CURSOR_Y_AXIS_INDEX, pos[1]);
|
||||||
|
// Same as above
|
||||||
|
input::update_axes_acc(input::Device::Mouse, input::MouseAxis::CursorX, &pos[0], 2);
|
||||||
|
|
||||||
|
// reset internal mouse position to (0,0)
|
||||||
|
// this gives us relative coordinates every frame
|
||||||
|
glfwSetCursorPos(_window, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void glfw_cursor_enter_callback(GLFWwindow *_window, int _entered)
|
||||||
|
{
|
||||||
|
cursor_entered_window = _entered;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void glfw_scroll_callback(GLFWwindow *_window, double _xoffset, double _yoffset)
|
||||||
|
{
|
||||||
|
const float offset[2] = {(float)_xoffset, (float)_yoffset};
|
||||||
|
input::update_axes_acc(input::Device::Mouse, input::MouseAxis::ScrollX, &offset[0], 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void glfw_joystick_callback(int _jid, int _event)
|
||||||
|
{
|
||||||
|
if (_event == GLFW_CONNECTED)
|
||||||
|
{
|
||||||
|
// get number of axes & buttons
|
||||||
|
int button_count = 0, axes_count = 0;
|
||||||
|
const uint8_t *buttons = glfwGetJoystickButtons(_jid, &button_count);
|
||||||
|
const float *axes = glfwGetJoystickAxes(_jid, &axes_count);
|
||||||
|
|
||||||
|
// get name
|
||||||
|
const char *name = glfwGetJoystickName(_jid);
|
||||||
|
|
||||||
|
if (axes == nullptr || buttons == nullptr || name == nullptr)
|
||||||
|
{
|
||||||
|
// Something is wrong with this joystick
|
||||||
|
// Maybe it was disconnected => do nothing
|
||||||
|
logWarn("Joystick connected, but cannot get data. (disconnected again?)\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// tries to re-identify it or adds it as new
|
||||||
|
input::DID id = input::add_joystick(_jid, button_count, axes_count, std::string(name) + " (RAW)");
|
||||||
|
|
||||||
|
// Note: We do not update the state here, polling happens elsewhere
|
||||||
|
|
||||||
|
// is xbox mapping available?
|
||||||
|
if (glfwJoystickIsGamepad(_jid))
|
||||||
|
{
|
||||||
|
// get input state
|
||||||
|
int state_valid = glfwGetGamepadState(_jid, &gamepad_state);
|
||||||
|
|
||||||
|
if (state_valid == GLFW_FALSE)
|
||||||
|
{
|
||||||
|
// Something is wrong with this joystick
|
||||||
|
// Maybe it was disconnected => do nothing
|
||||||
|
logWarn("Joystick (JID: %d) has xbox mappings, but cannot get data. (disconnected again?)\n", _jid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to re-identify it or adds it as new
|
||||||
|
id = input::add_gamepad(_jid, std::string(name) + " (XBOX)");
|
||||||
|
|
||||||
|
// Note: We do not update the state here, polling happens elsewhere
|
||||||
|
|
||||||
|
// If no mappings yet => set default mappings
|
||||||
|
if (!input::is_joystick_active(_jid) &&
|
||||||
|
!input::is_gamepad_active(_jid))
|
||||||
|
{
|
||||||
|
input::set_gamepad_default_mappings(_jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_event == GLFW_DISCONNECTED)
|
||||||
|
{
|
||||||
|
input::remove_joystick(_jid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logWarn("Received unknown joystick event\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_events(GLFWwindow *_window)
|
||||||
|
{
|
||||||
|
using input::Axis;
|
||||||
|
using input::Button;
|
||||||
|
using input::Device;
|
||||||
|
|
||||||
|
game_shutdown_requested = glfwWindowShouldClose(_window);
|
||||||
|
|
||||||
|
// Advance input system to next frame
|
||||||
|
input::update();
|
||||||
|
|
||||||
|
// Reset mouse axes
|
||||||
|
input::reset_device_axes(Device::Mouse);
|
||||||
|
|
||||||
|
glfwPollEvents();
|
||||||
|
|
||||||
|
// Poll joystick input
|
||||||
|
for (uint16_t jid : input::get_active_joysticks())
|
||||||
|
{
|
||||||
|
int button_count = 0, axes_count = 0;
|
||||||
|
const uint8_t *buttons = glfwGetJoystickButtons(jid, &button_count);
|
||||||
|
const float *axes = glfwGetJoystickAxes(jid, &axes_count);
|
||||||
|
|
||||||
|
if (axes == nullptr || buttons == nullptr)
|
||||||
|
{
|
||||||
|
// Something is wrong with this joystick
|
||||||
|
// Maybe it was disconnected => do nothing
|
||||||
|
logWarn("Joystick (JID: %d) error: Cannot get data (RAW). (disconnected?)\n", jid);
|
||||||
|
input::remove_joystick(jid);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
input::update_joystick(jid, buttons, button_count, axes, axes_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t jid : input::get_active_gamepads())
|
||||||
|
{
|
||||||
|
// get input state
|
||||||
|
int state_valid = glfwGetGamepadState(jid, &gamepad_state);
|
||||||
|
|
||||||
|
if (state_valid == GLFW_FALSE)
|
||||||
|
{
|
||||||
|
// Something is wrong with this joystick
|
||||||
|
// Maybe it was disconnected => do nothing
|
||||||
|
logWarn("Joystick (JID: %d) error: Cannot get data (XBOX). (disconnected?)\n", jid);
|
||||||
|
input::remove_joystick(jid);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
input::update_gamepad(jid, gamepad_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
logInfo("Starting spacegame :)\n");
|
||||||
|
|
||||||
|
// generate_vertices("C:/Users/Crydsch/Desktop/spacegame v5/shaders/verts.sh");
|
||||||
|
// generate_glsl_orientation_matrices("/home/crydsch/spacegame/shaders/orientations.sh");
|
||||||
|
// return 0;
|
||||||
|
|
||||||
|
// Run some debugging checks
|
||||||
|
#if not defined(NDEBUG)
|
||||||
|
if (!test())
|
||||||
|
exit(1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Fix for differences in debugging environment vs deploy environment
|
||||||
|
find_correct_working_directory();
|
||||||
|
|
||||||
|
logDebug("GLFW_PLATFORM_WAYLAND supported = %d\n", glfwPlatformSupported(GLFW_PLATFORM_WAYLAND));
|
||||||
|
logDebug("GLFW_PLATFORM_X11 supported = %d\n", glfwPlatformSupported(GLFW_PLATFORM_X11));
|
||||||
|
logDebug("GLFW_PLATFORM_WIN32 supported = %d\n", glfwPlatformSupported(GLFW_PLATFORM_WIN32));
|
||||||
|
|
||||||
|
// Create a GLFW window
|
||||||
|
#if BX_PLATFORM_LINUX || BX_PLATFORM_BSD
|
||||||
|
bool enable_wayland = should_use_wayland();
|
||||||
|
if (enable_wayland)
|
||||||
|
glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_WAYLAND);
|
||||||
|
else
|
||||||
|
glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11);
|
||||||
|
|
||||||
|
#elif BX_PLATFORM_WINDOWS
|
||||||
|
glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_WIN32);
|
||||||
|
#elif BX_PLATFORM_OSX
|
||||||
|
glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_COCOA);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
glfwSetErrorCallback(glfw_error_callback);
|
||||||
|
if (!glfwInit())
|
||||||
|
DIE("Could not initialize GLFW\n");
|
||||||
|
|
||||||
|
int platform = glfwGetPlatform();
|
||||||
|
if (platform == GLFW_PLATFORM_WAYLAND)
|
||||||
|
logInfo("GLFW running on wayland\n");
|
||||||
|
else if (platform == GLFW_PLATFORM_X11)
|
||||||
|
logInfo("GLFW running on x11\n");
|
||||||
|
else if (platform == GLFW_PLATFORM_WIN32)
|
||||||
|
logInfo("GLFW running on win32\n");
|
||||||
|
else if (platform == GLFW_PLATFORM_COCOA)
|
||||||
|
logInfo("GLFW running on cocoa\n");
|
||||||
|
else
|
||||||
|
logInfo("GLFW running on unknown platform\n");
|
||||||
|
|
||||||
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||||
|
|
||||||
|
GLFWwindow *window = glfwCreateWindow(window_width, window_height, "spacegame", nullptr, nullptr);
|
||||||
|
if (!window)
|
||||||
|
DIE("Could not create GLFW window\n");
|
||||||
|
|
||||||
|
// Initialize input system
|
||||||
|
// Before we set glfw callbacks
|
||||||
|
input::init();
|
||||||
|
|
||||||
|
glfwSetWindowSizeLimits(window, config::MINIMAL_WINDOW_WIDTH, config::MINIMAL_WINDOW_HEIGHT, GLFW_DONT_CARE, GLFW_DONT_CARE);
|
||||||
|
glfwSetWindowSizeCallback(window, glfw_window_size_callback);
|
||||||
|
glfwSetWindowFocusCallback(window, glfw_window_focus_callback);
|
||||||
|
glfwSetCursorPos(window, 0, 0); // once before cursor callbacks
|
||||||
|
glfwSetCursorEnterCallback(window, glfw_cursor_enter_callback);
|
||||||
|
glfwSetKeyCallback(window, glfw_key_callback);
|
||||||
|
glfwSetMouseButtonCallback(window, glfw_mouse_button_callback);
|
||||||
|
glfwSetCursorPosCallback(window, glfw_cursor_callback);
|
||||||
|
glfwSetCursorPos(window, 0, 0); // once after cursor callbacks
|
||||||
|
glfwSetScrollCallback(window, glfw_scroll_callback);
|
||||||
|
glfwSetJoystickCallback(glfw_joystick_callback);
|
||||||
|
|
||||||
|
// Detect (already connected) joysticks
|
||||||
|
for (int i = 0; i < GLFW_JOYSTICK_LAST + 1; ++i)
|
||||||
|
{
|
||||||
|
if (glfwJoystickPresent(i))
|
||||||
|
{
|
||||||
|
glfw_joystick_callback(i, GLFW_CONNECTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup cursor
|
||||||
|
#if NDEBUG
|
||||||
|
// Catch mouse (Only when not debugging; it will not release the mouse on a breakpoint..)
|
||||||
|
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
||||||
|
#endif
|
||||||
|
if (glfwRawMouseMotionSupported())
|
||||||
|
{
|
||||||
|
glfwSetInputMode(window, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call bgfx::renderFrame before bgfx::init to signal to bgfx not to create a render thread
|
||||||
|
// We do the threading ourselves
|
||||||
|
bgfx::renderFrame();
|
||||||
|
|
||||||
|
bgfx::PlatformData platformData;
|
||||||
|
#if BX_PLATFORM_LINUX || BX_PLATFORM_BSD
|
||||||
|
if (enable_wayland)
|
||||||
|
{
|
||||||
|
platformData.type = bgfx::NativeWindowHandleType::Wayland;
|
||||||
|
platformData.nwh = (void *)(uintptr_t)glfwGetWaylandWindow(window);
|
||||||
|
platformData.ndt = glfwGetWaylandDisplay();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
platformData.type = bgfx::NativeWindowHandleType::Default;
|
||||||
|
platformData.nwh = (void *)(uintptr_t)glfwGetX11Window(window);
|
||||||
|
platformData.ndt = glfwGetX11Display();
|
||||||
|
}
|
||||||
|
#elif BX_PLATFORM_OSX
|
||||||
|
platformData.nwh = glfwGetCocoaWindow(window);
|
||||||
|
#elif BX_PLATFORM_WINDOWS
|
||||||
|
platformData.nwh = glfwGetWin32Window(window);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Create a thread to call the bgfx API from (except bgfx::renderFrame)
|
||||||
|
bx::Thread gameloopThread;
|
||||||
|
gameloopThread.init(runGameloopThread, &platformData);
|
||||||
|
|
||||||
|
// Wait until bgfx is initialized
|
||||||
|
// this includes an initial dummy frame
|
||||||
|
// while (!bgfx_ready_flag.test()) {
|
||||||
|
while (!bgfx_ready_flag)
|
||||||
|
{
|
||||||
|
// Must be called repeatedly until bgfx is fully initialized
|
||||||
|
bgfx::renderFrame(100);
|
||||||
|
};
|
||||||
|
|
||||||
|
int64_t time_to_seconds = bx::getHPFrequency();
|
||||||
|
int64_t time_to_millis = time_to_seconds / 1000;
|
||||||
|
int64_t time_frame_start = 0;
|
||||||
|
// should be 16.666 for 60fps
|
||||||
|
// by slightly under estimating it, we might run slightly above 60fps, which is fine
|
||||||
|
int64_t frame_time_target_millis = 15; // todo is float neceed here?
|
||||||
|
|
||||||
|
// Main loop
|
||||||
|
while (!game_shutdown_requested)
|
||||||
|
{
|
||||||
|
// Handle glfw events
|
||||||
|
handle_events(window);
|
||||||
|
flag_signal(&input_ready_flag); // input is ready to be used
|
||||||
|
|
||||||
|
time_frame_start = bx::getHPCounter();
|
||||||
|
|
||||||
|
// Block until call to bgfx::frame, then
|
||||||
|
// Process submitted rendering primitives
|
||||||
|
bgfx::renderFrame();
|
||||||
|
flag_signal(&frame_ready_flag); // signal last frame was rendered
|
||||||
|
|
||||||
|
int64_t now = bx::getHPCounter();
|
||||||
|
int64_t frame_time_diff = (now - time_frame_start);
|
||||||
|
int64_t sleep = frame_time_target_millis - (frame_time_diff / time_to_millis);
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(sleep));
|
||||||
|
|
||||||
|
// now = bx::getHPCounter();
|
||||||
|
// frame_time_diff = (now - time_frame_start);
|
||||||
|
// logDebug("FPS: %lf\n", 1000.0 / ((double)frame_time_diff / (double)time_to_millis));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for bgfx to shut down
|
||||||
|
while (bgfx::RenderFrame::NoContext != bgfx::renderFrame())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
gameloopThread.shutdown(); // join game thread
|
||||||
|
glfwDestroyWindow(window);
|
||||||
|
glfwTerminate();
|
||||||
|
|
||||||
|
logInfo("Stopped spacegame gracefully. Till next time :)\n");
|
||||||
|
|
||||||
|
return gameloopThread.getExitCode();
|
||||||
|
}
|
||||||
20
src/net/debug_server.cpp
Normal file
20
src/net/debug_server.cpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#include "debug_server.h"
|
||||||
|
|
||||||
|
namespace debug_server {
|
||||||
|
|
||||||
|
void join(/*player id*/)
|
||||||
|
{
|
||||||
|
// TODO check player not already connected
|
||||||
|
|
||||||
|
|
||||||
|
// TODO register player joined
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void simulate_server()
|
||||||
|
{
|
||||||
|
// TODO check registered event
|
||||||
|
// send updates to client
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/net/debug_server.h
Normal file
17
src/net/debug_server.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
namespace debug_server {
|
||||||
|
|
||||||
|
// Requests to join a server
|
||||||
|
// Server will answer with surrounding grids
|
||||||
|
void join(/*player id*/);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// will "answer" requests
|
||||||
|
void simulate_server();
|
||||||
|
}
|
||||||
382
src/net/network.cpp
Normal file
382
src/net/network.cpp
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
//#include "network.h"
|
||||||
|
//#include "config.h"
|
||||||
|
//#include "util.h"
|
||||||
|
//#include <vector>
|
||||||
|
//#include "world.h"
|
||||||
|
//#include <unordered_map>
|
||||||
|
//#include "debug_server.h"
|
||||||
|
//
|
||||||
|
//// This system manages all network communication including:
|
||||||
|
//// Sending/Receiving client requests
|
||||||
|
//// Sending/Receiving server updates
|
||||||
|
//// Server <-> Client id translation
|
||||||
|
//
|
||||||
|
//// Request buffers
|
||||||
|
//std::unordered_map<uint32_t, ChunkRequest> chunk_requests; // chunk_PRid -> where on grid
|
||||||
|
//std::unordered_map<uint32_t, std::vector<BlockModelRequest>> block_model_requests;
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//// ID mappings remote <-> local
|
||||||
|
//// Note: All ids here are pure!
|
||||||
|
//std::vector<uint32_t> RtoL_grid_ids(config::INITIAL_NUM_GRIDS, 0x0);
|
||||||
|
//std::vector<uint32_t> LtoR_grid_ids(config::INITIAL_NUM_GRIDS, 0x0);
|
||||||
|
//std::vector<uint32_t> RtoL_chunk_ids(config::INITIAL_NUM_CHUNKS, 0x0);
|
||||||
|
//std::vector<uint32_t> LtoR_chunk_ids(config::INITIAL_NUM_CHUNKS, 0x0);
|
||||||
|
//std::vector<uint32_t> RtoL_block_ids(config::INITIAL_NUM_BLOCKS, 0x0);
|
||||||
|
//std::vector<uint32_t> LtoR_block_ids(config::INITIAL_NUM_BLOCKS, 0x0);
|
||||||
|
//std::vector<uint32_t> RtoL_block_model_ids(config::INITIAL_NUM_BLOCK_MODELS, 0x0);
|
||||||
|
//std::vector<uint32_t> LtoR_block_model_ids(config::INITIAL_NUM_BLOCK_MODELS, 0x0);
|
||||||
|
//
|
||||||
|
//namespace net {
|
||||||
|
//
|
||||||
|
// // id translation functions
|
||||||
|
// // Pass one id and get reference to the translated one
|
||||||
|
// uint32_t& translate_grid_id_RtoL(const uint32_t _PRid)
|
||||||
|
// {
|
||||||
|
// while (_PRid >= RtoL_grid_ids.size())
|
||||||
|
// { // need to grow array
|
||||||
|
// RtoL_grid_ids.resize(2 * RtoL_grid_ids.size(), 0);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return RtoL_grid_ids[_PRid];
|
||||||
|
// }
|
||||||
|
// uint32_t& translate_grid_id_LtoR(const uint32_t _PLid)
|
||||||
|
// {
|
||||||
|
// while (_PLid >= LtoR_grid_ids.size())
|
||||||
|
// { // need to grow array
|
||||||
|
// LtoR_grid_ids.resize(2 * LtoR_grid_ids.size(), 0);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return LtoR_grid_ids[_PLid];
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// uint32_t& translate_chunk_id_RtoL(const uint32_t _PRid)
|
||||||
|
// {
|
||||||
|
// while (_PRid >= RtoL_chunk_ids.size())
|
||||||
|
// { // need to grow array
|
||||||
|
// RtoL_chunk_ids.resize(2 * RtoL_chunk_ids.size(), 0);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return RtoL_chunk_ids[_PRid];
|
||||||
|
// }
|
||||||
|
// uint32_t& translate_chunk_id_LtoR(const uint32_t _PLid)
|
||||||
|
// {
|
||||||
|
// while (_PLid >= LtoR_chunk_ids.size())
|
||||||
|
// { // need to grow array
|
||||||
|
// LtoR_chunk_ids.resize(2 * LtoR_chunk_ids.size(), 0);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return LtoR_chunk_ids[_PLid];
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// uint32_t& translate_block_id_RtoL(const uint32_t _PRid)
|
||||||
|
// {
|
||||||
|
// while (_PRid >= RtoL_block_ids.size())
|
||||||
|
// { // need to grow array
|
||||||
|
// RtoL_block_ids.resize(2 * RtoL_block_ids.size(), 0);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return RtoL_block_ids[_PRid];
|
||||||
|
// }
|
||||||
|
// uint32_t& translate_block_id_LtoR(const uint32_t _PLid)
|
||||||
|
// {
|
||||||
|
// while (_PLid >= LtoR_block_ids.size())
|
||||||
|
// { // need to grow array
|
||||||
|
// LtoR_block_ids.resize(2 * LtoR_block_ids.size(), 0);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return LtoR_block_ids[_PLid];
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// uint32_t& translate_block_model_id_RtoL(const uint32_t _PRid)
|
||||||
|
// {
|
||||||
|
// while (_PRid >= RtoL_block_model_ids.size())
|
||||||
|
// { // need to grow array
|
||||||
|
// RtoL_block_model_ids.resize(2 * RtoL_block_model_ids.size(), 0);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return RtoL_block_model_ids[_PRid];
|
||||||
|
// }
|
||||||
|
// uint32_t& translate_block_model_id_LtoR(const uint32_t _PLid)
|
||||||
|
// {
|
||||||
|
// while (_PLid >= LtoR_block_model_ids.size())
|
||||||
|
// { // need to grow array
|
||||||
|
// LtoR_block_model_ids.resize(2 * LtoR_block_model_ids.size(), 0);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return LtoR_block_model_ids[_PLid];
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void join()
|
||||||
|
// {
|
||||||
|
// debug_server::join();
|
||||||
|
//
|
||||||
|
// // TODO send_CtoS_join
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void recv_StoC_grid_update(StoC_grid_update _update)
|
||||||
|
// {
|
||||||
|
// uint32_t& grid_PLid = translate_grid_id_RtoL(_update.id);
|
||||||
|
//
|
||||||
|
// if (grid_PLid == 0)
|
||||||
|
// { // id does not exist => new grid
|
||||||
|
// if (_update.optional != 0b111)
|
||||||
|
// { // new grid MUST have all optional parameters
|
||||||
|
// logWarn("recv_StoC_grid_update received update for unknown grid. => ignoring it\n");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// grid_PLid = world::add_grid();
|
||||||
|
// translate_grid_id_LtoR(grid_PLid) = _update.id;
|
||||||
|
//
|
||||||
|
// world::update_grid_transform(grid_PLid, _update.position, _update.orientation);
|
||||||
|
//
|
||||||
|
// for (uint32_t x = 0; x < 8; x++)
|
||||||
|
// for (uint32_t y = 0; y < 8; y++)
|
||||||
|
// for (uint32_t z = 0; z < 8; z++)
|
||||||
|
// world::update_grid_chunk(grid_PLid, x, y, z, _update.chunks_ids[x][y][z]);
|
||||||
|
//
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// // else id is valid grid
|
||||||
|
//
|
||||||
|
// // determine type of update
|
||||||
|
//
|
||||||
|
// if (_update.optional == 0b000)
|
||||||
|
// { // just id => grid got removed
|
||||||
|
//
|
||||||
|
// // TODO remove chunk id mappings
|
||||||
|
// // world::iterate_grid_chunks
|
||||||
|
// // RtoL_chunk_ids =0 ...
|
||||||
|
//
|
||||||
|
// world::remove_grid(grid_PLid);
|
||||||
|
//
|
||||||
|
// // remove id mapping
|
||||||
|
// translate_chunk_id_LtoR(grid_PLid) = 0;
|
||||||
|
// grid_PLid = 0;
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (_update.optional & 0b110)
|
||||||
|
// { // combined transform update
|
||||||
|
// world::update_grid_transform(grid_PLid, _update.position, _update.orientation);
|
||||||
|
// }
|
||||||
|
// else if (_update.optional & 0b100)
|
||||||
|
// { // position only
|
||||||
|
// world::update_grid_position(grid_PLid, _update.position);
|
||||||
|
// }
|
||||||
|
// else if (_update.optional & 0b010)
|
||||||
|
// { // orientation only
|
||||||
|
// world::update_grid_orientation(grid_PLid, _update.orientation);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (_update.optional & 0b001)
|
||||||
|
// { // new chunk ids
|
||||||
|
// for (uint32_t x = 0; x < 8; x++)
|
||||||
|
// for (uint32_t y = 0; y < 8; y++)
|
||||||
|
// for (uint32_t z = 0; z < 8; z++)
|
||||||
|
// {
|
||||||
|
// uint32_t chunk_id = _update.chunks_ids[x][y][z]; // TODO rem when updating only valid ids
|
||||||
|
// if (chunk_id == 0) continue; // We only add new chunks, removal is done via chunk updates
|
||||||
|
//
|
||||||
|
// world::update_grid_chunk(grid_PLid, x, y, z, to_remote_id(chunk_id));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void subscribe_chunk(const uint32_t _PRid, ChunkRequest _req)
|
||||||
|
// {
|
||||||
|
// chunk_requests.insert(std::pair(_PRid, _req));
|
||||||
|
// send_CtoS_subscribe_chunk(_PRid);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void unsubscribe_requested_chunk(const uint32_t _PRid)
|
||||||
|
// {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void unsubscribe_local_chunk(const uint32_t _PRid, ChunkRequest _req)
|
||||||
|
// {
|
||||||
|
// auto req_it = chunk_requests.find(_PRid);
|
||||||
|
// if (req_it == chunk_requests.end())
|
||||||
|
// { // request is still pending => remove it
|
||||||
|
// chunk_requests.erase(req_it);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// chunk_requests.insert(req_it, std::pair(_PRid, _req));
|
||||||
|
// }
|
||||||
|
// send_CtoS_unsubscribe_chunk(_PRid);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // instruct server to send chunk information (including blocks) AND keep us up-to-date
|
||||||
|
// void send_CtoS_subscribe_chunk(const uint32_t _PRid)
|
||||||
|
// {
|
||||||
|
// // TODO send request
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void send_CtoS_unsubscribe_chunk(const uint32_t _PRid)
|
||||||
|
// { // instruct server to stop sending chunk updates
|
||||||
|
// // TODO send request
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void recv_StoC_chunk_update(StoC_chunk_update _update)
|
||||||
|
// {
|
||||||
|
// // #1 DO WE KNOW THIS CHUNK?
|
||||||
|
// // translate id
|
||||||
|
// uint32_t& chunk_PLid = translate_chunk_id_RtoL(_update.id);
|
||||||
|
//
|
||||||
|
// // #1 NO
|
||||||
|
// if (chunk_PLid == 0)
|
||||||
|
// { // might be new chunk
|
||||||
|
// // #2 DID WE REQUEST IT?
|
||||||
|
// auto req_it = chunk_requests.find(_update.id);
|
||||||
|
// // #2 NO
|
||||||
|
// if (req_it == chunk_requests.end())
|
||||||
|
// { // this chunk was not requested! (or unsubscribed before we received this update)
|
||||||
|
// logTrace("recv_StoC_chunk_update received a non requested chunk => ignoring it\n");
|
||||||
|
// send_CtoS_unsubscribe_chunk(_update.id); // prevent further updates
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// // #2 YES
|
||||||
|
// ChunkRequest req = req_it->second;
|
||||||
|
// chunk_requests.erase(req_it);
|
||||||
|
//
|
||||||
|
// // fail-fast - does update indicate removed chunk?
|
||||||
|
// if (!_update.block_ids_exist)
|
||||||
|
// { // chunk does not/no longer exists on server
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // add new chunk AND set id mapping
|
||||||
|
// chunk_PLid = world::add_chunk();
|
||||||
|
// translate_chunk_id_LtoR(chunk_PLid) = _update.id;
|
||||||
|
//
|
||||||
|
// // update parent
|
||||||
|
// world::update_grid_chunk(req.grid_id, req.offset_x, req.offset_y, req.offset_z, chunk_PLid);
|
||||||
|
//
|
||||||
|
// // Note:
|
||||||
|
// }
|
||||||
|
// // #1 YES
|
||||||
|
// // else chunk_PLid is known chunk
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// // determine type of update
|
||||||
|
// if (!_update.block_ids_exist)
|
||||||
|
// { // just id => chunk got removed
|
||||||
|
// // update parent grid (set chunk id to original remote id)
|
||||||
|
// world::update_grid_chunk(req.grid_id, req.offset_x, req.offset_y, req.offset_z, to_remote_id(_update.id));
|
||||||
|
//
|
||||||
|
// world::remove_chunk(chunk_PLid);
|
||||||
|
//
|
||||||
|
// // remove id mapping
|
||||||
|
// translate_chunk_id_LtoR(chunk_PLid) = 0;
|
||||||
|
// chunk_PLid = 0;
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// { // got new block ids
|
||||||
|
// for (uint32_t x = 0; x < 8; x++)
|
||||||
|
// for (uint32_t y = 0; y < 8; y++)
|
||||||
|
// for (uint32_t z = 0; z < 8; z++)
|
||||||
|
// {
|
||||||
|
// uint32_t block_PRid = _update.block_ids[x][y][z];
|
||||||
|
// uint32_t& block_PLid = translate_block_id_RtoL(block_PRid);
|
||||||
|
//
|
||||||
|
// if (block_PLid == 0) continue; // TODO rem when handling only valid updates
|
||||||
|
// // Note: block removal is done through block updates, not chunk updates
|
||||||
|
//
|
||||||
|
// block_PLid = world::add_block();
|
||||||
|
// world::update_chunk_block(chunk_PLid, x, y, z, block_PLid);
|
||||||
|
//
|
||||||
|
// // init block data
|
||||||
|
// auto& block_update = _update.block_data[x][y][z];
|
||||||
|
//
|
||||||
|
// world::update_block_rotation(block_PLid, block_update.rotation);
|
||||||
|
// world::update_block_base_model(block_PLid, to_remote_id(block_update.base_model_id));
|
||||||
|
// world::update_block_curr_model(block_PLid, to_remote_id(block_update.curr_model_id));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void recv_StoC_block_update(StoC_block_update _update)
|
||||||
|
// { // aka existing block got removed or changed (model, health, ...)
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void request_block_model(const uint32_t _PRid, const uint32_t _requesting_block_PLid, const bool _request_base_model /* or curr_model?*/)
|
||||||
|
// {
|
||||||
|
// BlockModelRequest req = { .block_id = _requesting_block_PLid, .base_model = _request_base_model };
|
||||||
|
//
|
||||||
|
// auto req_it = block_model_requests.find(_PRid);
|
||||||
|
// if (req_it == block_model_requests.end())
|
||||||
|
// { // new request
|
||||||
|
// block_model_requests.insert(std::pair(_PRid, std::vector<BlockModelRequest>(1, req)));
|
||||||
|
// send_CtoS_get_block_model(_PRid);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// { // additional request => just enqueue requesting block
|
||||||
|
// std::vector<BlockModelRequest>& reqs = req_it->second;
|
||||||
|
// reqs.push_back(req);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void send_CtoS_get_block_model(const uint32_t _PRid)
|
||||||
|
// {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void recv_StoC_block_model_update(StoC_block_model_update _update)
|
||||||
|
// {
|
||||||
|
// // translate id
|
||||||
|
// uint32_t& block_model_PLid = translate_block_model_id_RtoL(_update.id);
|
||||||
|
//
|
||||||
|
// if (block_model_PLid == 0)
|
||||||
|
// { // new block model
|
||||||
|
// auto req_it = block_model_requests.find(_update.id);
|
||||||
|
// if (req_it == block_model_requests.end())
|
||||||
|
// { // this model was not requested!
|
||||||
|
// logWarn("recv_StoC_block_model_update received a non requested block model => ignoring it\n");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// // else was requested
|
||||||
|
// std::vector<BlockModelRequest> reqs = req_it->second;
|
||||||
|
// block_model_requests.erase(req_it);
|
||||||
|
//
|
||||||
|
// if (!_update.data_exists)
|
||||||
|
// { // model does not/no longer exist on server
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // add model and id mapping
|
||||||
|
// block_model_PLid = world::add_block_model();
|
||||||
|
// translate_block_model_id_LtoR(block_model_PLid) = _update.id;
|
||||||
|
//
|
||||||
|
// // init model data
|
||||||
|
// world::update_block_model_data(block_model_PLid, _update.data, _update.num_data_elements);
|
||||||
|
//
|
||||||
|
// // update requesting blocks
|
||||||
|
// for (BlockModelRequest req : reqs)
|
||||||
|
// {
|
||||||
|
// if (req.base_model)
|
||||||
|
// {
|
||||||
|
// world::update_block_base_model(req.block_id, to_local_id(block_model_PLid));
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// world::update_block_curr_model(req.block_id, to_local_id(block_model_PLid));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// // else block_model_PLid was already known
|
||||||
|
// logWarn("recv_StoC_block_model_update received update for already known block model => ignoring it\n");
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//}
|
||||||
83
src/net/network.h
Normal file
83
src/net/network.h
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
//#pragma once
|
||||||
|
//
|
||||||
|
//#include "space_math.h"
|
||||||
|
//
|
||||||
|
//typedef struct chunk_request {
|
||||||
|
// uint32_t grid_id;
|
||||||
|
// uint32_t offset_x;
|
||||||
|
// uint32_t offset_y;
|
||||||
|
// uint32_t offset_z;
|
||||||
|
//} ChunkRequest;
|
||||||
|
//
|
||||||
|
//typedef struct block_model_request {
|
||||||
|
// uint32_t block_id;
|
||||||
|
// bool base_model; // else was curr_model
|
||||||
|
//} BlockModelRequest;
|
||||||
|
//
|
||||||
|
//struct StoC_grid_update {
|
||||||
|
// // id
|
||||||
|
// uint32_t id;
|
||||||
|
// // 0b000 == grid removed
|
||||||
|
// // 0b1xx == includes position
|
||||||
|
// // 0bx1x == includes orientation
|
||||||
|
// // 0bxx1 == includes chunk ids
|
||||||
|
// uint8_t optional; // bitmap
|
||||||
|
// // opt: position
|
||||||
|
// Vec3 position;
|
||||||
|
// // opt: orientation
|
||||||
|
// Quat orientation;
|
||||||
|
// // opt: chunk_ids
|
||||||
|
// uint32_t chunks_ids[8][8][8]; // TODO better chunk_storage
|
||||||
|
//};
|
||||||
|
//
|
||||||
|
//struct CtoS_subscribe_chunk {
|
||||||
|
// // id
|
||||||
|
// uint32_t id;
|
||||||
|
//};
|
||||||
|
//
|
||||||
|
//struct CtoS_unsubscribe_chunk {
|
||||||
|
// // id
|
||||||
|
// uint32_t id;
|
||||||
|
//};
|
||||||
|
//
|
||||||
|
//struct StoC_chunk_update {
|
||||||
|
// // chunk id
|
||||||
|
// uint32_t id;
|
||||||
|
// // block_ids
|
||||||
|
// bool block_ids_exist; // TODO
|
||||||
|
// uint32_t block_ids[8][8][8];
|
||||||
|
//};
|
||||||
|
//
|
||||||
|
//struct StoC_chunk_block_data {
|
||||||
|
// uint8_t rotation;
|
||||||
|
// uint32_t base_model_id;
|
||||||
|
// uint32_t curr_model_id;
|
||||||
|
//};
|
||||||
|
//
|
||||||
|
//struct StoC_block_update {
|
||||||
|
// // block id
|
||||||
|
// uint32_t id;
|
||||||
|
// bool curr_model_id_exists;
|
||||||
|
// uint32_t curr_model_id; // may be ==base_model_id OR damaged variant
|
||||||
|
//};
|
||||||
|
//
|
||||||
|
//struct StoC_block_model_update {
|
||||||
|
// // id
|
||||||
|
// uint32_t id;
|
||||||
|
// bool data_exists;
|
||||||
|
// uint32_t num_data_elements;
|
||||||
|
// uint32_t* data;
|
||||||
|
//};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//namespace net {
|
||||||
|
//
|
||||||
|
// void join();
|
||||||
|
//
|
||||||
|
// void subscribe_chunk(const uint32_t _PRid, ChunkRequest _req);
|
||||||
|
//
|
||||||
|
// void unsubscribe_chunk(const uint32_t _PRid, ChunkRequest _req);
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//}
|
||||||
97
src/renderer.cpp
Normal file
97
src/renderer.cpp
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
#include "renderer.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace renderer
|
||||||
|
{
|
||||||
|
|
||||||
|
void dispatch_cubes_cs(const Vec3 _camera_eye, const uint16_t _num_block_selection)
|
||||||
|
{
|
||||||
|
// uniform data
|
||||||
|
float ud[4] = {_camera_eye.x, _camera_eye.y, _camera_eye.z, float(_num_block_selection)};
|
||||||
|
bgfx::setUniform(gfx::get_uniform_cubes_compute_params(), ud);
|
||||||
|
|
||||||
|
bgfx::setBuffer(0, gfx::get_grid_buffer(), bgfx::Access::Read);
|
||||||
|
bgfx::setBuffer(1, gfx::get_chunk_buffer(), bgfx::Access::Read);
|
||||||
|
bgfx::setBuffer(2, gfx::get_block_buffer(), bgfx::Access::Read);
|
||||||
|
bgfx::setBuffer(3, gfx::get_block_selection_buffer(), bgfx::Access::Read);
|
||||||
|
bgfx::setBuffer(4, gfx::get_indirect_buffer(), bgfx::Access::Write);
|
||||||
|
bgfx::setBuffer(5, gfx::get_instance_buffer(), bgfx::Access::Write);
|
||||||
|
|
||||||
|
// Dispatch the call. We are using 64 local threads on the GPU to process the object list
|
||||||
|
// So lets dispatch ceil(numToDraw/64) workgroups of 64 local threads
|
||||||
|
// TODO investigate this number of threads...
|
||||||
|
bgfx::dispatch(0, gfx::get_cubes_compute_shader(), 64 /*uint32_t(num_objects / 64 + 1)*/, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_cubes(const uint16_t _num_block_selection)
|
||||||
|
{
|
||||||
|
bgfx::setVertexBuffer(0, gfx::get_dummy_vertex_buffer()); // Some GPUs/drivers require a vertex buffer, even if we do not use it.
|
||||||
|
bgfx::setIndexBuffer(gfx::get_block_model_buffer());
|
||||||
|
bgfx::setTexture(0, gfx::get_uniform_texture_atlas_sampler(), gfx::get_texture_atlas(), BGFX_SAMPLER_POINT);
|
||||||
|
bgfx::setInstanceDataBuffer(gfx::get_instance_buffer(), 0, _num_block_selection);
|
||||||
|
|
||||||
|
bgfx::setState(0 | BGFX_STATE_WRITE_RGB
|
||||||
|
//| BGFX_STATE_WRITE_A
|
||||||
|
//| BGFX_STATE_BLEND_ALPHA
|
||||||
|
| BGFX_STATE_WRITE_Z | BGFX_STATE_DEPTH_TEST_LESS | BGFX_STATE_CULL_CW | BGFX_STATE_MSAA);
|
||||||
|
|
||||||
|
bgfx::submit(0, gfx::get_cubes_shader(), gfx::get_indirect_buffer(), 0, _num_block_selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_lines(const uint32_t _ref, const Vec4 _color, const float *_transform_mtx)
|
||||||
|
{
|
||||||
|
/// Note: This uses bgfx line primitives.
|
||||||
|
/// These might not be supported on every platform.
|
||||||
|
/// Alternative 1: Generate lines as tiny quads ourselves.
|
||||||
|
/// Allows for line thickness. Maximum control.
|
||||||
|
/// Requires "line normal" estimation.
|
||||||
|
/// Cannot do fancy things like caps.
|
||||||
|
/// Alternative 2: Use par_shapes.h to generate 3D cylindrical shapes.
|
||||||
|
/// Allows for line thickness. Maximum control.
|
||||||
|
/// Requires library and generating structures.
|
||||||
|
/// May be very expensive.
|
||||||
|
/// Alternative 3: Use par_streamlines.h to generate 2D lines.
|
||||||
|
/// Allows for a variety of line renderings (caps).
|
||||||
|
/// Requires library and transforming 3D data into 2D.
|
||||||
|
/// Requires solving depth issues as lines are 2D.
|
||||||
|
|
||||||
|
uint32_t line_offset = 0, line_num_elems = 0;
|
||||||
|
gfx::get_line_offset(_ref, line_offset, line_num_elems);
|
||||||
|
|
||||||
|
bgfx::setVertexBuffer(0, gfx::get_line_buffer(), line_offset, line_num_elems);
|
||||||
|
|
||||||
|
bgfx::setUniform(gfx::get_uniform_line_color(), &_color);
|
||||||
|
bgfx::setTransform(_transform_mtx);
|
||||||
|
|
||||||
|
bgfx::setState(0 | BGFX_STATE_WRITE_RGB | BGFX_STATE_DEPTH_TEST_LEQUAL | BGFX_STATE_MSAA | BGFX_STATE_PT_LINES | BGFX_STATE_LINEAA);
|
||||||
|
|
||||||
|
bgfx::submit(0, gfx::get_lines_shader());
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t crosshair_ref = 0; // gfx_line
|
||||||
|
float crosshair_mtx[16];
|
||||||
|
void draw_crosshair(const Camera *_cam)
|
||||||
|
{
|
||||||
|
vec3 eye = camera_eye(_cam);
|
||||||
|
vec3 forward = camera_forward(_cam);
|
||||||
|
assert(bx::isEqual(bx::length(forward), 1.0f, 0.001f));
|
||||||
|
Vec3 up = camera_up(_cam);
|
||||||
|
assert(bx::isEqual(bx::length(up), 1.0f, 0.001f));
|
||||||
|
Vec3 right = camera_right(_cam);
|
||||||
|
assert(bx::isEqual(bx::length(right), 1.0f, 0.001f));
|
||||||
|
eye = bx::add(eye, bx::mul(forward, 0.2f)); // in front of camera
|
||||||
|
GPULineVertex crosshair[4];
|
||||||
|
crosshair[0].position = bx::add(eye, bx::mul(up, -0.004f)); // down
|
||||||
|
crosshair[1].position = bx::add(eye, bx::mul(up, 0.004f)); // up
|
||||||
|
crosshair[2].position = bx::add(eye, bx::mul(right, -0.004f)); // left
|
||||||
|
crosshair[3].position = bx::add(eye, bx::mul(right, 0.004f)); // right
|
||||||
|
if (crosshair_ref == 0)
|
||||||
|
{ // create
|
||||||
|
crosshair_ref = gfx::add_line(crosshair, 4);
|
||||||
|
bx::mtxIdentity(crosshair_mtx);
|
||||||
|
}
|
||||||
|
gfx::update_line(crosshair_ref, crosshair);
|
||||||
|
renderer::draw_lines(crosshair_ref, {1.0f, 1.0f, 1.0f, 1.0f}, crosshair_mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
34
src/renderer.h
Normal file
34
src/renderer.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "graphics.h"
|
||||||
|
#include "space_math.h"
|
||||||
|
#include "lib/camera.h"
|
||||||
|
|
||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
/* The renderer namespace performs draw calls */
|
||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
namespace renderer
|
||||||
|
{
|
||||||
|
|
||||||
|
void dispatch_cubes_cs(const Vec3 _camera_eye, const uint16_t _num_block_selection);
|
||||||
|
|
||||||
|
void draw_cubes(const uint16_t _num_block_selection);
|
||||||
|
|
||||||
|
// Draws lines referenced by _ref from the line buffer
|
||||||
|
void draw_lines(const uint32_t _ref, const Vec4 _color, const float *_transform_mtx);
|
||||||
|
|
||||||
|
void draw_crosshair(const Camera *_cam);
|
||||||
|
|
||||||
|
// void draw_outline(uint32_t _block_id, Vec3 _color) // block_id?
|
||||||
|
//{
|
||||||
|
// get block model -> outline offset/size in line buffer
|
||||||
|
// set color uniform
|
||||||
|
// set mvp uniform (grid+block transform)
|
||||||
|
// set vertex buffer
|
||||||
|
// set render state
|
||||||
|
//
|
||||||
|
// submit line shader call
|
||||||
|
//}
|
||||||
|
|
||||||
|
}
|
||||||
512
src/space_input.cpp
Normal file
512
src/space_input.cpp
Normal file
@@ -0,0 +1,512 @@
|
|||||||
|
|
||||||
|
#include <GLFW/glfw3.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <cfloat>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "space_input.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
// Note: We extend the input namespace with spacegame specific stuff
|
||||||
|
namespace input
|
||||||
|
{
|
||||||
|
|
||||||
|
using Joystick = struct joystick
|
||||||
|
{
|
||||||
|
DID raw;
|
||||||
|
DID xbox;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint16_t max_keyboard_keys = 128;
|
||||||
|
const float cursor_input_sensitivity = 0.002f; // TODO frame time based // maybe extend input system with global multiplier updated every frame?
|
||||||
|
const float max_camera_move_speed = 0.05f; // TODO frame time based
|
||||||
|
const float button_camera_move_speed = max_camera_move_speed; // TODO frame time based
|
||||||
|
const float button_camera_turn_speed = 0.03f; // TODO frame time based
|
||||||
|
|
||||||
|
std::unordered_map<int, uint16_t> keycode_map; // maps platform specific keycodes to key indices for the input system
|
||||||
|
uint16_t next_key_index = 0;
|
||||||
|
Joystick joystick_map[GLFW_JOYSTICK_LAST + 1] = {}; // maps glfw joystick ids to indices for the input system
|
||||||
|
std::vector<int> active_joysticks; // holds jids
|
||||||
|
std::vector<int> active_gamepads; // holds jids
|
||||||
|
std::vector<int> active_gamepads_tmp; // holds temporary jids for detection minigame
|
||||||
|
bool detect_minigame = false;
|
||||||
|
|
||||||
|
// convenience functions
|
||||||
|
inline void add_device(const Device _device, const uint16_t _num_buttons, const uint16_t _num_axes, std::string _name)
|
||||||
|
{
|
||||||
|
DID did = add_device(_num_buttons, _num_axes, _name);
|
||||||
|
assert(did == _device);
|
||||||
|
}
|
||||||
|
inline void add_button(const Button _button)
|
||||||
|
{
|
||||||
|
VBID vbid = add_virtual_button();
|
||||||
|
assert(vbid == _button);
|
||||||
|
}
|
||||||
|
inline void add_axis(const Axis _axis, const float _min_value = -FLT_MAX, const float _max_value = FLT_MAX)
|
||||||
|
{
|
||||||
|
VAID vaid = add_virtual_axis(_min_value, _max_value);
|
||||||
|
assert(vaid == _axis);
|
||||||
|
}
|
||||||
|
inline void map_button(const Device _device, const uint16_t _index, const Button _button, const bool _combine_with_next = false)
|
||||||
|
{
|
||||||
|
map_button((DID)_device, _index, (VBID)_button, _combine_with_next);
|
||||||
|
};
|
||||||
|
inline void map_button(const Device _device, const uint16_t _index, const Axis _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false)
|
||||||
|
{
|
||||||
|
map_button((DID)_device, _index, (VAID)_axis, _multiplier, _combine_with_next);
|
||||||
|
};
|
||||||
|
inline void map_axis(const Device _device, const uint16_t _index, const Button _button, const float _multiplier = 1.0f, const bool _combine_with_next = false)
|
||||||
|
{
|
||||||
|
map_axis((DID)_device, _index, (VBID)_button, _multiplier, _combine_with_next);
|
||||||
|
};
|
||||||
|
inline void map_axis(const Device _device, const uint16_t _index, const Axis _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false)
|
||||||
|
{
|
||||||
|
map_axis((DID)_device, _index, (VAID)_axis, _multiplier, _combine_with_next);
|
||||||
|
};
|
||||||
|
// Map a glfw named key
|
||||||
|
inline void map_key(const int _keyname /*GLFW_KEY_E*/, const Button _button, const bool _combine_with_next = false)
|
||||||
|
{
|
||||||
|
int keycode = glfwGetKeyScancode(_keyname);
|
||||||
|
if (keycode < 0)
|
||||||
|
{
|
||||||
|
assert(false); // Does this key not exist on the current platform?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t index = 0;
|
||||||
|
auto it = keycode_map.find(keycode);
|
||||||
|
if (it == keycode_map.end())
|
||||||
|
{
|
||||||
|
// key is not mapped yet
|
||||||
|
|
||||||
|
if (next_key_index >= max_keyboard_keys)
|
||||||
|
{
|
||||||
|
assert(false); // cannot map more keys! Need to increase max_keyboard_keys
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = next_key_index++;
|
||||||
|
keycode_map[keycode] = index;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// key is already mapped
|
||||||
|
index = it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
map_button(Device::Keyboard, index, _button, _combine_with_next);
|
||||||
|
}
|
||||||
|
inline void map_key(const int _keyname /*GLFW_KEY_E*/, const Axis _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false)
|
||||||
|
{
|
||||||
|
int keycode = glfwGetKeyScancode(_keyname);
|
||||||
|
if (keycode < 0)
|
||||||
|
{
|
||||||
|
assert(false); // Does this key not exist on the current platform?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t index = 0;
|
||||||
|
auto it = keycode_map.find(keycode);
|
||||||
|
if (it == keycode_map.end())
|
||||||
|
{
|
||||||
|
// key is not mapped yet
|
||||||
|
|
||||||
|
if (next_key_index >= max_keyboard_keys)
|
||||||
|
{
|
||||||
|
assert(false); // cannot map more keys! Need to increase max_keyboard_keys
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = next_key_index++;
|
||||||
|
keycode_map[keycode] = index;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// key is already mapped
|
||||||
|
index = it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
map_button(Device::Keyboard, index, _axis, _multiplier, _combine_with_next);
|
||||||
|
}
|
||||||
|
// ex. for gamepad: _index == GLFW_GAMEPAD_BUTTON_A
|
||||||
|
inline void set_joystick_active(const int _jid)
|
||||||
|
{
|
||||||
|
assert(joystick_map[_jid].raw != 0);
|
||||||
|
auto it = std::find(active_joysticks.begin(), active_joysticks.end(), _jid);
|
||||||
|
if (it == active_joysticks.end())
|
||||||
|
{
|
||||||
|
active_joysticks.emplace_back(_jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline void set_gamepad_active(const int _jid)
|
||||||
|
{
|
||||||
|
assert(joystick_map[_jid].xbox != 0);
|
||||||
|
auto it = std::find(active_gamepads.begin(), active_gamepads.end(), _jid);
|
||||||
|
if (it == active_gamepads.end())
|
||||||
|
{
|
||||||
|
active_gamepads.emplace_back(_jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline void map_joystick_button(const int _jid, const uint16_t _index, const Button _button, const bool _combine_with_next = false)
|
||||||
|
{
|
||||||
|
DID did = joystick_map[_jid].raw;
|
||||||
|
assert(did != 0);
|
||||||
|
|
||||||
|
map_button(did, _index, (VBID)_button, _combine_with_next);
|
||||||
|
|
||||||
|
if (is_device_active(did))
|
||||||
|
{
|
||||||
|
set_joystick_active(_jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline void map_joystick_button(const int _jid, const uint16_t _index, const Axis _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false)
|
||||||
|
{
|
||||||
|
DID did = joystick_map[_jid].raw;
|
||||||
|
assert(did != 0);
|
||||||
|
|
||||||
|
map_button(did, _index, (VAID)_axis, _multiplier, _combine_with_next);
|
||||||
|
|
||||||
|
if (is_device_active(did))
|
||||||
|
{
|
||||||
|
set_joystick_active(_jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline void map_joystick_axis(const int _jid, const uint16_t _index, const Button _button, const float _multiplier = 1.0f, const bool _combine_with_next = false)
|
||||||
|
{
|
||||||
|
DID did = joystick_map[_jid].raw;
|
||||||
|
assert(did != 0);
|
||||||
|
|
||||||
|
map_axis(did, _index, (VBID)_button, _multiplier, _combine_with_next);
|
||||||
|
|
||||||
|
if (is_device_active(did))
|
||||||
|
{
|
||||||
|
set_joystick_active(_jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline void map_joystick_axis(const int _jid, const uint16_t _index, const Axis _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false)
|
||||||
|
{
|
||||||
|
DID did = joystick_map[_jid].raw;
|
||||||
|
assert(did != 0);
|
||||||
|
|
||||||
|
map_axis(did, _index, (VAID)_axis, _multiplier, _combine_with_next);
|
||||||
|
|
||||||
|
if (is_device_active(did))
|
||||||
|
{
|
||||||
|
set_joystick_active(_jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline void map_gamepad_button(const int _jid, const uint16_t _index, const Button _button, const bool _combine_with_next = false)
|
||||||
|
{
|
||||||
|
DID did = joystick_map[_jid].xbox;
|
||||||
|
assert(did != 0);
|
||||||
|
|
||||||
|
map_button(did, _index, (VBID)_button, _combine_with_next);
|
||||||
|
|
||||||
|
if (is_device_active(did))
|
||||||
|
{
|
||||||
|
set_gamepad_active(_jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline void map_gamepad_button(const int _jid, const uint16_t _index, const Axis _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false)
|
||||||
|
{
|
||||||
|
DID did = joystick_map[_jid].xbox;
|
||||||
|
assert(did != 0);
|
||||||
|
|
||||||
|
map_button(did, _index, (VAID)_axis, _multiplier, _combine_with_next);
|
||||||
|
|
||||||
|
if (is_device_active(did))
|
||||||
|
{
|
||||||
|
set_gamepad_active(_jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline void map_gamepad_axis(const int _jid, const uint16_t _index, const Button _button, const float _multiplier = 1.0f, const bool _combine_with_next = false)
|
||||||
|
{
|
||||||
|
DID did = joystick_map[_jid].xbox;
|
||||||
|
assert(did != 0);
|
||||||
|
|
||||||
|
map_axis(did, _index, (VBID)_button, _multiplier, _combine_with_next);
|
||||||
|
|
||||||
|
if (is_device_active(did))
|
||||||
|
{
|
||||||
|
set_gamepad_active(_jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline void map_gamepad_axis(const int _jid, const uint16_t _index, const Axis _axis, const float _multiplier = 1.0f, const bool _combine_with_next = false)
|
||||||
|
{
|
||||||
|
DID did = joystick_map[_jid].xbox;
|
||||||
|
assert(did != 0);
|
||||||
|
|
||||||
|
map_axis(did, _index, (VAID)_axis, _multiplier, _combine_with_next);
|
||||||
|
|
||||||
|
if (is_device_active(did))
|
||||||
|
{
|
||||||
|
set_gamepad_active(_jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
// Add devices
|
||||||
|
add_device(Device::Keyboard, max_keyboard_keys, 0, "Keyboard");
|
||||||
|
add_device(Device::Mouse, GLFW_MOUSE_BUTTON_LAST + 1, MouseAxis::Count, "Mouse");
|
||||||
|
|
||||||
|
// Add virtual buttons
|
||||||
|
for (int i = 0; i < Button::Count; ++i)
|
||||||
|
{
|
||||||
|
add_button((Button)i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add virtual Axes
|
||||||
|
add_axis(Axis::MoveForward, -max_camera_move_speed, max_camera_move_speed);
|
||||||
|
add_axis(Axis::MoveRight, -max_camera_move_speed, max_camera_move_speed);
|
||||||
|
add_axis(Axis::MoveUp, -max_camera_move_speed, max_camera_move_speed);
|
||||||
|
add_axis(Axis::CameraPitch); // Camera rotation is unrestricted
|
||||||
|
add_axis(Axis::CameraYaw); // Camera rotation is unrestricted
|
||||||
|
add_axis(Axis::CameraRoll); // Camera rotation is unrestricted
|
||||||
|
|
||||||
|
// Add default mapping
|
||||||
|
map_key(GLFW_KEY_F1, Button::ShowStats);
|
||||||
|
map_key(GLFW_KEY_F2, Button::LookAtOrigin);
|
||||||
|
map_key(GLFW_KEY_ESCAPE, Button::GameShouldExit);
|
||||||
|
|
||||||
|
map_key(GLFW_KEY_W, Axis::MoveForward, max_camera_move_speed);
|
||||||
|
map_key(GLFW_KEY_S, Axis::MoveForward, -max_camera_move_speed);
|
||||||
|
map_key(GLFW_KEY_D, Axis::MoveRight, max_camera_move_speed);
|
||||||
|
map_key(GLFW_KEY_A, Axis::MoveRight, -max_camera_move_speed);
|
||||||
|
map_key(GLFW_KEY_SPACE, Axis::MoveUp, max_camera_move_speed);
|
||||||
|
map_key(GLFW_KEY_LEFT_CONTROL, Axis::MoveUp, -max_camera_move_speed);
|
||||||
|
|
||||||
|
map_axis(Device::Mouse, MouseAxis::CursorX, Axis::CameraYaw, cursor_input_sensitivity);
|
||||||
|
map_axis(Device::Mouse, MouseAxis::CursorY, Axis::CameraPitch, cursor_input_sensitivity);
|
||||||
|
map_key(GLFW_KEY_LEFT, Axis::CameraYaw, -button_camera_turn_speed);
|
||||||
|
map_key(GLFW_KEY_RIGHT, Axis::CameraYaw, button_camera_turn_speed);
|
||||||
|
map_key(GLFW_KEY_UP, Axis::CameraPitch, -button_camera_turn_speed);
|
||||||
|
map_key(GLFW_KEY_DOWN, Axis::CameraPitch, button_camera_turn_speed);
|
||||||
|
|
||||||
|
map_button(Device::Mouse, GLFW_MOUSE_BUTTON_RIGHT, Button::PlaceBlock);
|
||||||
|
map_button(Device::Mouse, GLFW_MOUSE_BUTTON_LEFT, Button::RemoveBlock);
|
||||||
|
|
||||||
|
map_key(GLFW_KEY_E, Button::RotateBlockInc);
|
||||||
|
map_key(GLFW_KEY_Q, Button::RotateBlockDec);
|
||||||
|
map_axis(Device::Mouse, MouseAxis::ScrollY, Button::RotateBlockInc);
|
||||||
|
map_axis(Device::Mouse, MouseAxis::ScrollY, Button::RotateBlockDec, -1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_gamepad_default_mappings(const int _jid)
|
||||||
|
{
|
||||||
|
assert(joystick_map[_jid].xbox != 0); // is gamepad
|
||||||
|
|
||||||
|
map_gamepad_axis(_jid, GLFW_GAMEPAD_AXIS_LEFT_X, Axis::MoveRight, 0.08f);
|
||||||
|
map_gamepad_axis(_jid, GLFW_GAMEPAD_AXIS_LEFT_Y, Axis::MoveForward, 0.08f * -1.0f);
|
||||||
|
map_gamepad_axis(_jid, GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, Axis::MoveUp, 0.08f);
|
||||||
|
map_gamepad_axis(_jid, GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, Axis::MoveUp, 0.08f * -1.0f);
|
||||||
|
map_gamepad_axis(_jid, GLFW_GAMEPAD_AXIS_RIGHT_X, Axis::CameraYaw, 0.03f);
|
||||||
|
map_gamepad_axis(_jid, GLFW_GAMEPAD_AXIS_RIGHT_Y, Axis::CameraPitch, 0.03f);
|
||||||
|
map_gamepad_button(_jid, GLFW_GAMEPAD_BUTTON_LEFT_BUMPER, Button::PlaceBlock);
|
||||||
|
map_gamepad_button(_jid, GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER, Button::RemoveBlock);
|
||||||
|
map_gamepad_button(_jid, GLFW_GAMEPAD_BUTTON_START, Button::GameShouldExit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_button_keycode(const int _keycode, const uint8_t _new_state)
|
||||||
|
{
|
||||||
|
auto it = keycode_map.find(_keycode);
|
||||||
|
if (it == keycode_map.end())
|
||||||
|
{
|
||||||
|
return; // ignore unmapped key
|
||||||
|
}
|
||||||
|
|
||||||
|
update_button(Device::Keyboard, it->second, _new_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
DID add_joystick(const int _jid, const uint16_t _num_buttons, const uint16_t _num_axes, std::string _name)
|
||||||
|
{
|
||||||
|
logInfo("Joystick connected JID: %d\n", _jid);
|
||||||
|
// add device (may re-identify it and returns the correct id)
|
||||||
|
DID id = input::add_device(_num_buttons, _num_axes, _name);
|
||||||
|
assert(id != 0);
|
||||||
|
// update id mapping
|
||||||
|
joystick_map[_jid].raw = id;
|
||||||
|
|
||||||
|
if (is_device_active(id))
|
||||||
|
{
|
||||||
|
active_joysticks.emplace_back(_jid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
DID add_gamepad(const int _jid, std::string _name)
|
||||||
|
{
|
||||||
|
logInfo("Joystick (JID: %d) has xbox mappings\n", _jid);
|
||||||
|
// add device (may re-identify it and returns the correct id)
|
||||||
|
DID id = input::add_device(GLFW_GAMEPAD_BUTTON_LAST + 1, GLFW_GAMEPAD_AXIS_LAST + 1, _name);
|
||||||
|
assert(id != 0);
|
||||||
|
// update id mapping
|
||||||
|
joystick_map[_jid].xbox = id;
|
||||||
|
|
||||||
|
if (is_device_active(id))
|
||||||
|
{
|
||||||
|
active_gamepads.emplace_back(_jid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_joystick(const int _jid)
|
||||||
|
{
|
||||||
|
DID id = joystick_map[_jid].raw;
|
||||||
|
if (id > 0)
|
||||||
|
{
|
||||||
|
logInfo("Joystick (JID: %d) disconnected\n", _jid);
|
||||||
|
joystick_map[_jid].raw = 0; // reset mapping
|
||||||
|
remove_device(id);
|
||||||
|
|
||||||
|
// find and remove from active joysticks
|
||||||
|
auto it = std::find(active_joysticks.begin(), active_joysticks.end(), _jid);
|
||||||
|
if (it != active_joysticks.end())
|
||||||
|
{
|
||||||
|
active_joysticks.erase(it);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
id = joystick_map[_jid].xbox;
|
||||||
|
if (id > 0)
|
||||||
|
{
|
||||||
|
joystick_map[_jid].xbox = 0; // reset mapping
|
||||||
|
remove_device(id);
|
||||||
|
|
||||||
|
// find and remove from active gamepads
|
||||||
|
auto it = std::find(active_gamepads.begin(), active_gamepads.end(), _jid);
|
||||||
|
if (it != active_gamepads.end())
|
||||||
|
{
|
||||||
|
active_gamepads.erase(it);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_joystick(const int _jid, const uint8_t *_buttons, const uint16_t _button_count, const float *_axes, const uint16_t _axes_count)
|
||||||
|
{
|
||||||
|
DID did = joystick_map[_jid].raw;
|
||||||
|
assert(did != 0);
|
||||||
|
input::update_buttons(did, 0, _buttons, _button_count);
|
||||||
|
input::update_axes(did, 0, _axes, _axes_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_gamepad(const int _jid, const GLFWgamepadstate &_state)
|
||||||
|
{
|
||||||
|
DID did = joystick_map[_jid].xbox;
|
||||||
|
assert(did != 0);
|
||||||
|
input::update_buttons(did, 0, _state.buttons, GLFW_GAMEPAD_BUTTON_LAST + 1);
|
||||||
|
input::update_axes(did, 0, _state.axes, GLFW_GAMEPAD_AXIS_LAST + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_joystick_active(const int _jid)
|
||||||
|
{
|
||||||
|
assert(joystick_map[_jid].raw != 0); // is joystick
|
||||||
|
return std::find(active_joysticks.begin(), active_joysticks.end(), _jid) != active_joysticks.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_gamepad_active(const int _jid)
|
||||||
|
{
|
||||||
|
assert(joystick_map[_jid].xbox != 0); // is joystick
|
||||||
|
return std::find(active_gamepads.begin(), active_gamepads.end(), _jid) != active_gamepads.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<int> &get_active_joysticks()
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
#ifndef NDEBUG
|
||||||
|
auto ad = get_active_devices();
|
||||||
|
// All entries in active_joysticks are also active devices
|
||||||
|
for (auto jid : active_joysticks)
|
||||||
|
{
|
||||||
|
assert(joystick_map[jid].raw != 0);
|
||||||
|
assert(std::find(ad.begin(), ad.end(), joystick_map[jid].raw) != ad.end());
|
||||||
|
}
|
||||||
|
// All joysticks in active_devices are also active joysticks
|
||||||
|
for (auto d : ad)
|
||||||
|
{
|
||||||
|
for (int jid = 0; jid < GLFW_JOYSTICK_LAST + 1; ++jid)
|
||||||
|
{
|
||||||
|
if (d != 0 && d == joystick_map[jid].raw)
|
||||||
|
{ // d is joystick
|
||||||
|
assert(std::find(ad.begin(), ad.end(), jid) != ad.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // NDEBUG
|
||||||
|
|
||||||
|
if (detect_minigame)
|
||||||
|
{ // return all (connected) joysticks
|
||||||
|
active_gamepads_tmp.clear();
|
||||||
|
for (int jid = 0; jid < GLFW_JOYSTICK_LAST + 1; ++jid)
|
||||||
|
{
|
||||||
|
if (joystick_map[jid].raw != 0)
|
||||||
|
{
|
||||||
|
active_gamepads_tmp.emplace_back(jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return active_gamepads_tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return active_joysticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<int> &get_active_gamepads()
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
#ifndef NDEBUG
|
||||||
|
auto ad = get_active_devices();
|
||||||
|
// All entries in active_joysticks are also active devices
|
||||||
|
for (auto jid : active_gamepads)
|
||||||
|
{
|
||||||
|
assert(joystick_map[jid].xbox != 0);
|
||||||
|
assert(std::find(ad.begin(), ad.end(), joystick_map[jid].xbox) != ad.end());
|
||||||
|
}
|
||||||
|
// All joysticks in active_devices are also active joysticks
|
||||||
|
for (auto d : ad)
|
||||||
|
{
|
||||||
|
for (int jid = 0; jid < GLFW_JOYSTICK_LAST + 1; ++jid)
|
||||||
|
{
|
||||||
|
if (d != 0 && d == joystick_map[jid].xbox)
|
||||||
|
{ // d is joystick
|
||||||
|
assert(std::find(ad.begin(), ad.end(), jid) != ad.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // NDEBUG
|
||||||
|
|
||||||
|
if (detect_minigame)
|
||||||
|
{ // return all (connected) gamepads
|
||||||
|
active_gamepads_tmp.clear();
|
||||||
|
for (int jid = 0; jid < GLFW_JOYSTICK_LAST + 1; ++jid)
|
||||||
|
{
|
||||||
|
if (joystick_map[jid].xbox != 0)
|
||||||
|
{
|
||||||
|
active_gamepads_tmp.emplace_back(jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return active_gamepads_tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return active_gamepads;
|
||||||
|
}
|
||||||
|
|
||||||
|
void detect_minigame_start()
|
||||||
|
{
|
||||||
|
detect_minigame = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void detect_minigame_frame()
|
||||||
|
{
|
||||||
|
// must set active joysticks/gamepad to all connected
|
||||||
|
// or input will not be polled
|
||||||
|
// reset after detection is done
|
||||||
|
}
|
||||||
|
|
||||||
|
void detect_minigame_stop()
|
||||||
|
{
|
||||||
|
detect_minigame = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
109
src/space_input.h
Normal file
109
src/space_input.h
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "lib/input.h"
|
||||||
|
|
||||||
|
// Note: We extend the input namespace with spacegame specific stuff
|
||||||
|
namespace input
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace device_ns
|
||||||
|
{
|
||||||
|
enum device : uint16_t
|
||||||
|
{
|
||||||
|
Keyboard,
|
||||||
|
Mouse
|
||||||
|
};
|
||||||
|
}
|
||||||
|
using Device = device_ns::device;
|
||||||
|
|
||||||
|
namespace button_ns
|
||||||
|
{
|
||||||
|
enum button : uint16_t
|
||||||
|
{
|
||||||
|
// Debug & Internal actions
|
||||||
|
ShowStats,
|
||||||
|
LookAtOrigin,
|
||||||
|
GameShouldExit, // On window closed or menu exit
|
||||||
|
|
||||||
|
// Gameplay actions
|
||||||
|
PlaceBlock,
|
||||||
|
RemoveBlock,
|
||||||
|
RotateBlockDec,
|
||||||
|
RotateBlockInc,
|
||||||
|
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
}
|
||||||
|
using Button = button_ns::button;
|
||||||
|
|
||||||
|
namespace axis_ns
|
||||||
|
{
|
||||||
|
enum axis : uint16_t
|
||||||
|
{
|
||||||
|
MoveForward,
|
||||||
|
MoveRight,
|
||||||
|
MoveUp,
|
||||||
|
CameraPitch,
|
||||||
|
CameraYaw,
|
||||||
|
CameraRoll,
|
||||||
|
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
}
|
||||||
|
using Axis = axis_ns::axis;
|
||||||
|
|
||||||
|
namespace mouse_axis_ns
|
||||||
|
{
|
||||||
|
enum mouse_axis : uint16_t
|
||||||
|
{
|
||||||
|
CursorX,
|
||||||
|
CursorY,
|
||||||
|
ScrollX,
|
||||||
|
ScrollY,
|
||||||
|
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
}
|
||||||
|
using MouseAxis = mouse_axis_ns::mouse_axis;
|
||||||
|
|
||||||
|
// Initialize input system
|
||||||
|
// Set default mapping
|
||||||
|
void init();
|
||||||
|
|
||||||
|
// Update the button state of a keyboard keycode
|
||||||
|
void update_button_keycode(const int _keycode, const uint8_t _new_state);
|
||||||
|
|
||||||
|
DID add_joystick(const int jid, const uint16_t _num_buttons, const uint16_t _num_axes, std::string _name);
|
||||||
|
|
||||||
|
// Adds a joystick as a gamepad
|
||||||
|
// Note: Use the same _jid to associate it with its raw counterpart
|
||||||
|
// Note: Automatically removed the corresponding joystick is removed
|
||||||
|
DID add_gamepad(const int jid, std::string _name);
|
||||||
|
|
||||||
|
void set_gamepad_default_mappings(const int _jid);
|
||||||
|
|
||||||
|
// Mark joystick as disconnected
|
||||||
|
void remove_joystick(const int _jid);
|
||||||
|
|
||||||
|
// Update joystick state (RAW)
|
||||||
|
void update_joystick(const int _jid, const uint8_t *_buttons, const uint16_t _button_count, const float *_axes, const uint16_t _axes_count);
|
||||||
|
|
||||||
|
// Update gamepad state (XBOX)
|
||||||
|
void update_gamepad(const int _jid, const GLFWgamepadstate &_state);
|
||||||
|
|
||||||
|
// Returns whether the given joystick is active (connected and at least one mapping)
|
||||||
|
bool is_joystick_active(const int _jid);
|
||||||
|
|
||||||
|
// Returns whether the given gamepad is active (connected and at least one mapping)
|
||||||
|
bool is_gamepad_active(const int _jid);
|
||||||
|
|
||||||
|
// Returns list of active joysticks (connected and at least one mapping)
|
||||||
|
// Useful to check which devices need to be updated
|
||||||
|
const std::vector<int> &get_active_joysticks();
|
||||||
|
|
||||||
|
// Returns list of active gamepads (connected and at least one mapping)
|
||||||
|
// Useful to check which devices need to be updated
|
||||||
|
const std::vector<int> &get_active_gamepads();
|
||||||
|
}
|
||||||
448
src/space_math.cpp
Normal file
448
src/space_math.cpp
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
#include "space_math.h"
|
||||||
|
|
||||||
|
constexpr Vec3 dir_to_normal_table[Direction::Count] = {
|
||||||
|
// PosX
|
||||||
|
Vec3(1.0f, 0.0f, 0.0f),
|
||||||
|
// NegX
|
||||||
|
Vec3(-1.0f, 0.0f, 0.0f),
|
||||||
|
// PosY
|
||||||
|
Vec3(0.0f, 1.0f, 0.0f),
|
||||||
|
// NegY
|
||||||
|
Vec3(0.0f, -1.0f, 0.0f),
|
||||||
|
// PosZ
|
||||||
|
Vec3(0.0f, 0.0f, 1.0f),
|
||||||
|
// NegZ
|
||||||
|
Vec3(0.0f, 0.0f, -1.0f),
|
||||||
|
};
|
||||||
|
|
||||||
|
Vec3 dir_to_normal(Direction _dir)
|
||||||
|
{
|
||||||
|
return dir_to_normal_table[_dir];
|
||||||
|
}
|
||||||
|
|
||||||
|
void transform_mtx(const Vec3 &_position, const Quat &_orientation, float *_out_mtx)
|
||||||
|
{
|
||||||
|
bx::mtxFromQuaternion(_out_mtx, _orientation);
|
||||||
|
bx::store(&_out_mtx[12], _position);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr InvRay InverseRay(Ray &_ray)
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
_ray.position,
|
||||||
|
{1.0f / _ray.direction.x,
|
||||||
|
1.0f / _ray.direction.y,
|
||||||
|
1.0f / _ray.direction.z}};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheckCollisionRayBox(const InvRay &_ray, const AABB &_box, float &_out_distance)
|
||||||
|
{
|
||||||
|
static float t[8];
|
||||||
|
|
||||||
|
t[0] = (_box.min.x - _ray.position.x) * _ray.inverse_direction.x;
|
||||||
|
t[1] = (_box.max.x - _ray.position.x) * _ray.inverse_direction.x;
|
||||||
|
t[2] = (_box.min.y - _ray.position.y) * _ray.inverse_direction.y;
|
||||||
|
t[3] = (_box.max.y - _ray.position.y) * _ray.inverse_direction.y;
|
||||||
|
t[4] = (_box.min.z - _ray.position.z) * _ray.inverse_direction.z;
|
||||||
|
t[5] = (_box.max.z - _ray.position.z) * _ray.inverse_direction.z;
|
||||||
|
t[6] = bx::max(bx::max(bx::min(t[0], t[1]), bx::min(t[2], t[3])), bx::min(t[4], t[5]));
|
||||||
|
t[7] = bx::min(bx::min(bx::max(t[0], t[1]), bx::max(t[2], t[3])), bx::max(t[4], t[5]));
|
||||||
|
|
||||||
|
_out_distance = t[6];
|
||||||
|
return !(t[7] < 0 || t[6] > t[7]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheckCollisionRayOrientedBox(const Ray &_ray, const AABB &_box, const float *_inverse_model_matrix, float &_distance)
|
||||||
|
{
|
||||||
|
// We transform the ray "back" to the models origin where the axis aligned bounding box is valid
|
||||||
|
bx::Vec3 ray_origin = _ray.position;
|
||||||
|
bx::Vec3 ray_lookat = bx::add(_ray.position, _ray.direction);
|
||||||
|
|
||||||
|
Ray new_ray = {
|
||||||
|
.position = bx::mul(ray_origin, _inverse_model_matrix),
|
||||||
|
.direction = bx::mul(ray_lookat, _inverse_model_matrix),
|
||||||
|
};
|
||||||
|
new_ray.direction = bx::normalize(bx::sub(new_ray.direction, new_ray.position));
|
||||||
|
|
||||||
|
// now do normal AABB
|
||||||
|
return CheckCollisionRayBox(InverseRay(new_ray), _box, _distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO check if it reports same results as matrix version
|
||||||
|
bool CheckCollisionRayOrientedBox(Ray _ray, const AABB &_box, const bx::Vec3 &_model_position, const bx::Quaternion &_model_orientation, float &_distance)
|
||||||
|
{
|
||||||
|
// We transform the ray "back" to the models origin where the axis aligned bounding box is valid
|
||||||
|
_ray.position = bx::sub(_ray.position, _model_position);
|
||||||
|
_ray.position = bx::mul(_ray.position, bx::invert(_model_orientation));
|
||||||
|
_ray.direction = bx::mul(_ray.direction, bx::invert(_model_orientation));
|
||||||
|
|
||||||
|
// now do normal AABB check
|
||||||
|
return CheckCollisionRayBox(InverseRay(_ray), _box, _distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check ray <-> AABB collision
|
||||||
|
// Optimized version with box.min==(0,0,0)
|
||||||
|
bool CheckCollisionRayBoxOrigin(const InvRay &_ray, const bx::Vec3 &_box_max, float &_distance)
|
||||||
|
{
|
||||||
|
static float t[8];
|
||||||
|
|
||||||
|
t[0] = (0.0f - _ray.position.x) * _ray.inverse_direction.x;
|
||||||
|
t[1] = (_box_max.x - _ray.position.x) * _ray.inverse_direction.x;
|
||||||
|
t[2] = (0.0f - _ray.position.y) * _ray.inverse_direction.y;
|
||||||
|
t[3] = (_box_max.y - _ray.position.y) * _ray.inverse_direction.y;
|
||||||
|
t[4] = (0.0f - _ray.position.z) * _ray.inverse_direction.z;
|
||||||
|
t[5] = (_box_max.z - _ray.position.z) * _ray.inverse_direction.z;
|
||||||
|
t[6] = bx::max(bx::max(bx::min(t[0], t[1]), bx::min(t[2], t[3])), bx::min(t[4], t[5]));
|
||||||
|
t[7] = bx::min(bx::min(bx::max(t[0], t[1]), bx::max(t[2], t[3])), bx::max(t[4], t[5]));
|
||||||
|
|
||||||
|
_distance = t[6];
|
||||||
|
return !(t[7] < 0 || t[6] > t[7]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void combineAABB(AABB &_a, const AABB &_b)
|
||||||
|
{
|
||||||
|
_a.min.x = bx::min(_a.min.x, _b.min.x);
|
||||||
|
_a.min.y = bx::min(_a.min.y, _b.min.y);
|
||||||
|
_a.min.z = bx::min(_a.min.z, _b.min.z);
|
||||||
|
_a.max.x = bx::max(_a.max.x, _b.max.x);
|
||||||
|
_a.max.y = bx::max(_a.max.y, _b.max.y);
|
||||||
|
_a.max.z = bx::max(_a.max.z, _b.max.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
float angle_between(const Vec3 _v0, const Vec3 _v1, const Vec3 _axis)
|
||||||
|
{
|
||||||
|
// Ref.: https://math.stackexchange.com/questions/878785/how-to-find-an-angle-in-range0-360-between-2-vectors
|
||||||
|
const float dot = bx::dot(_v0, _v1);
|
||||||
|
const float det = bx::dot(_axis, bx::cross(_v0, _v1));
|
||||||
|
return bx::atan2(det, dot);
|
||||||
|
}
|
||||||
|
|
||||||
|
float angle_between_faces(const BlockFace _f0, const BlockFace _f1, const Vec3 _common_edge)
|
||||||
|
{
|
||||||
|
// Note: The angle from face0 to face1 is the same as from neg(normal0) to normal1
|
||||||
|
// We use the common edge as the rotational axis
|
||||||
|
// const Vec3 n0 = toVec3(negate(_f0.normal));
|
||||||
|
// const Vec3 n1 = toVec3(_f1.normal);
|
||||||
|
|
||||||
|
// return angle_between(n0, n1, bx::normalize(_common_edge));
|
||||||
|
assert(false);
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t reverse_bits(uint64_t b)
|
||||||
|
{
|
||||||
|
b = (b & 0xAAAAAAAAAAAAAAAA) >> 1 | (b & 0x5555555555555555) << 1;
|
||||||
|
b = (b & 0xCCCCCCCCCCCCCCCC) >> 2 | (b & 0x3333333333333333) << 2;
|
||||||
|
b = (b & 0xF0F0F0F0F0F0F0F0) >> 4 | (b & 0x0F0F0F0F0F0F0F0F) << 4;
|
||||||
|
b = (b & 0xFF00FF00FF00FF00) >> 8 | (b & 0xFF00FF00FF00FF00) << 8;
|
||||||
|
b = (b & 0xFFFF0000FFFF0000) >> 16 | (b & 0x0000FFFF0000FFFF) << 16;
|
||||||
|
b = b >> 32 | b << 32;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t reverse_bits_in_bytes(uint64_t b)
|
||||||
|
{
|
||||||
|
b = (b & 0xAAAAAAAAAAAAAAAA) >> 1 | (b & 0x5555555555555555) << 1;
|
||||||
|
b = (b & 0xCCCCCCCCCCCCCCCC) >> 2 | (b & 0x3333333333333333) << 2;
|
||||||
|
b = (b & 0xF0F0F0F0F0F0F0F0) >> 4 | (b & 0x0F0F0F0F0F0F0F0F) << 4;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO return result in 'uint8_t[4] _out_res'
|
||||||
|
ComponentVertices get_component_face_vertices(FaceType _face, Direction _view_dir)
|
||||||
|
{
|
||||||
|
ComponentVertices res = {8, 8, 8, 8}; // init all as invalid
|
||||||
|
|
||||||
|
// TODO OPT try
|
||||||
|
// use Quad verts then rotate values (bytes in a uint32?)
|
||||||
|
// by (_face-2) positions to the left
|
||||||
|
// and set vert[3] = 8
|
||||||
|
switch (_face)
|
||||||
|
{
|
||||||
|
case FaceType::None :
|
||||||
|
return res;
|
||||||
|
|
||||||
|
case FaceType::Quad :
|
||||||
|
res = {2,0,1,3};
|
||||||
|
break;
|
||||||
|
case FaceType::TrigBL :
|
||||||
|
res = {2,0,1,8};
|
||||||
|
break;
|
||||||
|
case FaceType::TrigBR :
|
||||||
|
res = {0,1,3,8};
|
||||||
|
break;
|
||||||
|
case FaceType::TrigTR :
|
||||||
|
res = {1,3,2,8};
|
||||||
|
break;
|
||||||
|
case FaceType::TrigTL :
|
||||||
|
res = {3,2,0,8};
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct normalized orientation based on _view_dir
|
||||||
|
Orientation ori;
|
||||||
|
|
||||||
|
switch (_view_dir)
|
||||||
|
{
|
||||||
|
case Direction::PosX:
|
||||||
|
ori = Orientation(_view_dir, Direction::PosY);
|
||||||
|
break;
|
||||||
|
case Direction::NegX:
|
||||||
|
ori = Orientation(_view_dir, Direction::PosY);
|
||||||
|
break;
|
||||||
|
case Direction::PosY:
|
||||||
|
ori = Orientation(_view_dir, Direction::PosZ);
|
||||||
|
break;
|
||||||
|
case Direction::NegY:
|
||||||
|
ori = Orientation(_view_dir, Direction::PosZ);
|
||||||
|
break;
|
||||||
|
case Direction::PosZ:
|
||||||
|
ori = Orientation(_view_dir, Direction::PosY);
|
||||||
|
break;
|
||||||
|
case Direction::NegZ:
|
||||||
|
ori = Orientation(_view_dir, Direction::PosY);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct rot matrix from ori
|
||||||
|
I8RotMat3x3 mat(ori);
|
||||||
|
|
||||||
|
// rotate via matrix
|
||||||
|
res[0] = mat.rotateComponentVertex(res[0]);
|
||||||
|
res[1] = mat.rotateComponentVertex(res[1]);
|
||||||
|
res[2] = mat.rotateComponentVertex(res[2]);
|
||||||
|
if (_face == FaceType::Quad)
|
||||||
|
res[3] = mat.rotateComponentVertex(res[3]);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct component_face component_face::NONE =
|
||||||
|
{
|
||||||
|
.normal = FaceNormal(0, 1, 0), // irrelevant, but valid
|
||||||
|
.tex_apex = TexCorner::Count, // irrelevant
|
||||||
|
.vertices = {0, 0, 0, 0}, // irrelevant, and marking it as invalid
|
||||||
|
.texture_id = 0, // irrelevant
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/* Integer Math */
|
||||||
|
|
||||||
|
void mulMtxVec3(int8_t *_result, const int8_t *_mat, const int8_t *_vec)
|
||||||
|
{
|
||||||
|
_result[0] = _vec[0] * _mat[0] + _vec[1] * _mat[3] + _vec[2] * _mat[6];
|
||||||
|
_result[1] = _vec[0] * _mat[1] + _vec[1] * _mat[4] + _vec[2] * _mat[7];
|
||||||
|
_result[2] = _vec[0] * _mat[2] + _vec[1] * _mat[5] + _vec[2] * _mat[8];
|
||||||
|
}
|
||||||
|
|
||||||
|
void mulMtxMtx(int8_t *_result, const int8_t *_a, const int8_t *_b)
|
||||||
|
{
|
||||||
|
mulMtxVec3(&_result[ 0], &_a[ 0], _b);
|
||||||
|
mulMtxVec3(&_result[ 3], &_a[ 3], _b);
|
||||||
|
mulMtxVec3(&_result[ 6], &_a[ 6], _b);
|
||||||
|
}
|
||||||
|
|
||||||
|
i8rotmat3x3::i8rotmat3x3(const Orientation _ori)
|
||||||
|
{
|
||||||
|
Direction forward, up;
|
||||||
|
_ori.to_dirs(forward, up);
|
||||||
|
Direction right = rotate(forward, up);
|
||||||
|
|
||||||
|
I8Vec3 x(right);
|
||||||
|
I8Vec3 y(up);
|
||||||
|
I8Vec3 z(forward);
|
||||||
|
|
||||||
|
m[0] = x.x;
|
||||||
|
m[1] = x.y;
|
||||||
|
m[2] = x.z;
|
||||||
|
m[3] = y.x;
|
||||||
|
m[4] = y.y;
|
||||||
|
m[5] = y.z;
|
||||||
|
m[6] = z.x;
|
||||||
|
m[7] = z.y;
|
||||||
|
m[8] = z.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
I8Vec3 i8rotmat3x3::mul(const I8Vec3 &_vec)
|
||||||
|
{
|
||||||
|
I8Vec3 res;
|
||||||
|
mulMtxVec3(&res.x, m, &_vec.x);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
i8rotmat3x3 i8rotmat3x3::mul(const i8rotmat3x3 &_m)
|
||||||
|
{
|
||||||
|
i8rotmat3x3 res;
|
||||||
|
mulMtxMtx(res.m, m, _m.m);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
i8rotmat3x3 i8rotmat3x3::transpose()
|
||||||
|
{
|
||||||
|
// [0] [3] [6] [0] [1] [2]
|
||||||
|
// [1] [4] [7] => [3] [4] [5]
|
||||||
|
// [2] [5] [8] [6] [7] [8]
|
||||||
|
i8rotmat3x3 res;
|
||||||
|
res.m[0] = m[0];
|
||||||
|
res.m[1] = m[3];
|
||||||
|
res.m[2] = m[6];
|
||||||
|
res.m[3] = m[1];
|
||||||
|
res.m[4] = m[4];
|
||||||
|
res.m[5] = m[7];
|
||||||
|
res.m[6] = m[2];
|
||||||
|
res.m[7] = m[5];
|
||||||
|
res.m[8] = m[8];
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// _vert MUST be in [0;7]
|
||||||
|
uint8_t i8rotmat3x3::rotateComponentVertex(uint8_t _vert)
|
||||||
|
{
|
||||||
|
assert(_vert >= 0 && _vert <= 7);
|
||||||
|
|
||||||
|
I8Vec3 vec(_vert);
|
||||||
|
|
||||||
|
vec = mul(vec);
|
||||||
|
|
||||||
|
assert(vec.x >= -1 && vec.x <= 1 &&
|
||||||
|
vec.y >= -1 && vec.y <= 1 &&
|
||||||
|
vec.z >= -1 && vec.z <= 1);
|
||||||
|
|
||||||
|
return vec.toCompVert();
|
||||||
|
}
|
||||||
|
|
||||||
|
FaceNormal i8rotmat3x3::rotateFaceNormal(const FaceNormal _normal)
|
||||||
|
{
|
||||||
|
return FaceNormal(mul(_normal));
|
||||||
|
}
|
||||||
|
|
||||||
|
Direction i8rotmat3x3::rotateDirection(Direction _dir)
|
||||||
|
{
|
||||||
|
return mul(I8Vec3(_dir)).toDirection();
|
||||||
|
}
|
||||||
|
|
||||||
|
FaceType component_face::faceType(Direction* _out_view_dir) const
|
||||||
|
{
|
||||||
|
using namespace direction_ns;
|
||||||
|
// Faces are interpreted in normalized orientation
|
||||||
|
// up== PosY when forward== {PosX,NegX,PosZ,NegZ}
|
||||||
|
// and up== PosZ when forward== {PosY,NegY}
|
||||||
|
|
||||||
|
// vertices => type + direction
|
||||||
|
|
||||||
|
if (_out_view_dir)
|
||||||
|
*_out_view_dir = Direction::Count;
|
||||||
|
|
||||||
|
if (!isValid())
|
||||||
|
return FaceType::None;
|
||||||
|
|
||||||
|
// ! The triangle apex determines the type !
|
||||||
|
|
||||||
|
// determine direction
|
||||||
|
|
||||||
|
// vertices are 3 bits representing z,y,x respectively
|
||||||
|
// & will leave only the common 1 bit => aka the direction
|
||||||
|
// if x=1,y=1 or z=1, if the common bit is 0 however
|
||||||
|
// we need to negate first
|
||||||
|
// => copy the 3 bits over and to everything in one go
|
||||||
|
uint8_t bits = vertices[0] & vertices[1] & vertices[2];
|
||||||
|
uint8_t bits2 = (~vertices[0] & ~vertices[1] & ~vertices[2]) & 0b111;
|
||||||
|
bits |= (bits2 << 3);
|
||||||
|
|
||||||
|
// Now the bits are:
|
||||||
|
// [0|0|PosZ|PosY|PosX|NegZ|NegY|NegX]
|
||||||
|
Direction dir;
|
||||||
|
Direction up;
|
||||||
|
switch (bits)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
dir = NegX;
|
||||||
|
up = PosY;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
dir = NegY;
|
||||||
|
up = PosZ;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
dir = NegZ;
|
||||||
|
up = PosY;
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
dir = PosX;
|
||||||
|
up = PosY;
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
dir = PosY;
|
||||||
|
up = PosZ;
|
||||||
|
break;
|
||||||
|
case 32:
|
||||||
|
dir = PosZ;
|
||||||
|
up = PosY;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_out_view_dir)
|
||||||
|
*_out_view_dir = dir;
|
||||||
|
|
||||||
|
if (isQuad())
|
||||||
|
return FaceType::Quad;
|
||||||
|
|
||||||
|
// Alternatively we could maybe?
|
||||||
|
// Shuffle bits to be
|
||||||
|
// [0|0|NegZ|PosZ|NegY|PosY|NegX|PosX]
|
||||||
|
// and just
|
||||||
|
// Direction dir = (Direction)bx::countTrailingZeros(bits);
|
||||||
|
|
||||||
|
// rotate apex in reverse
|
||||||
|
I8RotMat3x3 mat(Orientation(dir, up));
|
||||||
|
mat = mat.transpose();
|
||||||
|
|
||||||
|
uint8_t vert = mat.rotateComponentVertex(vertices[1]);
|
||||||
|
|
||||||
|
// match with "front" vertices
|
||||||
|
switch (vert)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return FaceType::TrigBL;
|
||||||
|
case 1:
|
||||||
|
return FaceType::TrigBR;
|
||||||
|
case 2:
|
||||||
|
return FaceType::TrigTL;
|
||||||
|
case 3:
|
||||||
|
return FaceType::TrigTR;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FaceType::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Direction i8vec3::toDirection()
|
||||||
|
{
|
||||||
|
assert(x >= -1 && x <= 1 &&
|
||||||
|
y >= -1 && y <= 1 &&
|
||||||
|
z >= -1 && z <= 1 &&
|
||||||
|
(x + y + z != 0));
|
||||||
|
return (Direction)((x == -1) +
|
||||||
|
(y == 1) * 2 + (y == -1) * 3 +
|
||||||
|
(z == 1) * 4 + (z == -1) * 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t face_normal::dims()
|
||||||
|
{
|
||||||
|
uint8_t res = 0;
|
||||||
|
res += x != 0;
|
||||||
|
res += y != 0;
|
||||||
|
res += z != 0;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
821
src/space_math.h
Normal file
821
src/space_math.h
Normal file
@@ -0,0 +1,821 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <bx/math.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <utility>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
|
// Dummy defines for broken intellisense
|
||||||
|
// #define size_t (unsigned long int)
|
||||||
|
|
||||||
|
// Note: bx structs behave a bit strange (ex. deleted default constructor)
|
||||||
|
// For ease of use we provide our own equivalents with implicit casting
|
||||||
|
// to and from the corresponding bx types.
|
||||||
|
|
||||||
|
// TODO move all lookup tables into .cpp without constexpr
|
||||||
|
// => constexpr copies it into each translation unit!
|
||||||
|
|
||||||
|
typedef struct vec3
|
||||||
|
{
|
||||||
|
float x, y, z;
|
||||||
|
|
||||||
|
vec3() = default;
|
||||||
|
~vec3() = default;
|
||||||
|
|
||||||
|
constexpr vec3(const float _v) : x(_v), y(_v), z(_v) {};
|
||||||
|
|
||||||
|
constexpr vec3(const float _x, const float _y, const float _z)
|
||||||
|
: x(_x), y(_y), z(_z) {}
|
||||||
|
|
||||||
|
// User-defined implicit constructor from bx::Vec3
|
||||||
|
constexpr vec3(const bx::Vec3 &_v) : x(_v.x), y(_v.y), z(_v.z) {}
|
||||||
|
|
||||||
|
// User-defined implicit conversion to bx::Vec3
|
||||||
|
constexpr operator bx::Vec3() const { return bx::Vec3(x, y, z); }
|
||||||
|
} Vec3;
|
||||||
|
|
||||||
|
typedef struct orientation Orientation;
|
||||||
|
typedef struct face_normal FaceNormal;
|
||||||
|
|
||||||
|
typedef struct vec4
|
||||||
|
{
|
||||||
|
float x, y, z, w;
|
||||||
|
|
||||||
|
vec4() = default;
|
||||||
|
~vec4() = default;
|
||||||
|
|
||||||
|
constexpr vec4(const float _v) : x(_v), y(_v), z(_v), w(_v) {};
|
||||||
|
|
||||||
|
constexpr vec4(const float _x, const float _y, const float _z, const float _w)
|
||||||
|
: x(_x), y(_y), z(_z), w(_w) {}
|
||||||
|
} Vec4;
|
||||||
|
|
||||||
|
typedef struct quat
|
||||||
|
{
|
||||||
|
float x, y, z, w;
|
||||||
|
|
||||||
|
quat() = default;
|
||||||
|
~quat() = default;
|
||||||
|
|
||||||
|
constexpr quat(const float _x, const float _y, const float _z, const float _w)
|
||||||
|
: x(_x), y(_y), z(_z), w(_w) {}
|
||||||
|
|
||||||
|
// User-defined implicit constructor from bx::Quaternion
|
||||||
|
constexpr quat(const bx::Quaternion &_v) : x(_v.x), y(_v.y), z(_v.z), w(_v.w) {}
|
||||||
|
|
||||||
|
// User-defined implicit conversion to bx::Quaternion
|
||||||
|
constexpr operator bx::Quaternion() const { return bx::Quaternion(x, y, z, w); }
|
||||||
|
|
||||||
|
static constexpr quat unit()
|
||||||
|
{
|
||||||
|
return quat(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
} Quat;
|
||||||
|
|
||||||
|
typedef struct color
|
||||||
|
{
|
||||||
|
uint8_t r, g, b, a;
|
||||||
|
|
||||||
|
color() : r(0), g(0), b(0), a(1) {}
|
||||||
|
color(uint8_t _r, uint8_t _g, uint8_t _b, uint8_t _a) : r(_r), g(_g), b(_b), a(_a) {}
|
||||||
|
} Color;
|
||||||
|
|
||||||
|
typedef Color Texel;
|
||||||
|
|
||||||
|
typedef struct ray
|
||||||
|
{
|
||||||
|
Vec3 position;
|
||||||
|
Vec3 direction;
|
||||||
|
} Ray;
|
||||||
|
|
||||||
|
typedef struct inv_ray
|
||||||
|
{
|
||||||
|
Vec3 position;
|
||||||
|
Vec3 inverse_direction;
|
||||||
|
} InvRay;
|
||||||
|
|
||||||
|
typedef struct aabb
|
||||||
|
{
|
||||||
|
Vec3 min;
|
||||||
|
Vec3 max;
|
||||||
|
|
||||||
|
aabb() = default;
|
||||||
|
~aabb() = default;
|
||||||
|
|
||||||
|
constexpr aabb(const Vec3 _min, const Vec3 _max) : min(_min), max(_max) {};
|
||||||
|
} AABB;
|
||||||
|
|
||||||
|
namespace direction_ns
|
||||||
|
{
|
||||||
|
enum direction : uint8_t
|
||||||
|
{
|
||||||
|
// The order MUST stay like this
|
||||||
|
// Other systems rely on this exact order
|
||||||
|
// e.g. FaceNormals
|
||||||
|
PosX,
|
||||||
|
NegX,
|
||||||
|
PosY,
|
||||||
|
NegY,
|
||||||
|
PosZ,
|
||||||
|
NegZ,
|
||||||
|
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
}
|
||||||
|
using Direction = direction_ns::direction;
|
||||||
|
|
||||||
|
constexpr Direction negate(const Direction _dir)
|
||||||
|
{
|
||||||
|
// +1 if even or -1 if odd
|
||||||
|
return (Direction)((uint8_t)_dir ^ 0x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO OPT bitpack or padding
|
||||||
|
typedef struct i8vec3
|
||||||
|
{
|
||||||
|
int8_t x, y, z;
|
||||||
|
|
||||||
|
constexpr i8vec3() : x(0), y(0), z(0) {}
|
||||||
|
|
||||||
|
constexpr i8vec3(const int8_t _x, const int8_t _y, const int8_t _z) : x(_x), y(_y), z(_z)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr i8vec3(Direction _dir)
|
||||||
|
{
|
||||||
|
using namespace direction_ns;
|
||||||
|
x = (_dir > 1) ? 0 : (_dir == PosX) ? 1 : -1;
|
||||||
|
z = (_dir < 4) ? 0 : (_dir == PosZ) ? 1 : -1;
|
||||||
|
// x==z only if x==z==0
|
||||||
|
y = (x != z) ? 0 : (_dir == PosY) ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr i8vec3(uint8_t _comp_vert) {
|
||||||
|
x = ((_comp_vert & 0b001) == 0) ? -1 : 1;
|
||||||
|
y = ((_comp_vert & 0b010) == 0) ? -1 : 1;
|
||||||
|
z = ((_comp_vert & 0b100) == 0) ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
i8vec3 negate()
|
||||||
|
{
|
||||||
|
return i8vec3(-x, -y, -z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be a valid conversion (no safety checks)
|
||||||
|
Direction toDirection();
|
||||||
|
|
||||||
|
uint8_t toCompVert() {
|
||||||
|
return (x == 1) + (y == 1) * 2 + (z == 1) * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto operator<=>(const i8vec3&) const = default;
|
||||||
|
} I8Vec3;
|
||||||
|
static_assert(sizeof(I8Vec3) == 3);
|
||||||
|
|
||||||
|
// A integer based rotation matrix
|
||||||
|
// Values should only be in [-1;1] and only encode valid rotations
|
||||||
|
typedef struct i8rotmat3x3
|
||||||
|
{
|
||||||
|
// [0] [3] [6]
|
||||||
|
// [1] [4] [7]
|
||||||
|
// [2] [5] [8]
|
||||||
|
int8_t m[9] = {};
|
||||||
|
|
||||||
|
i8rotmat3x3() {}
|
||||||
|
|
||||||
|
i8rotmat3x3(const Orientation _ori);
|
||||||
|
|
||||||
|
// consider the static version of this method: mulMtxVec3
|
||||||
|
I8Vec3 mul(const I8Vec3& _vec);
|
||||||
|
// consider the static version of this method: mulMtxMtx
|
||||||
|
i8rotmat3x3 mul(const i8rotmat3x3& _vec);
|
||||||
|
|
||||||
|
// Note: For a rotation matrix the inverse IS the transpose
|
||||||
|
i8rotmat3x3 transpose();
|
||||||
|
|
||||||
|
uint8_t rotateComponentVertex(uint8_t _vert);
|
||||||
|
|
||||||
|
FaceNormal rotateFaceNormal(const FaceNormal _normal);
|
||||||
|
|
||||||
|
Direction rotateDirection(Direction _dir);
|
||||||
|
|
||||||
|
// we want real, but integer based matrix multiplication
|
||||||
|
// specifically for rotations
|
||||||
|
// convert to and from orientations
|
||||||
|
// ori -> directions(up, forward) -> to vectors
|
||||||
|
// match base vectors with directions -> ori from dirs
|
||||||
|
// must be able to rotate
|
||||||
|
// - component vertices
|
||||||
|
// - face normals (and therefore directions)
|
||||||
|
|
||||||
|
} I8RotMat3x3;
|
||||||
|
|
||||||
|
// TODO OBB ?
|
||||||
|
// but with vec3 & quat - right? <.<
|
||||||
|
|
||||||
|
// reverses all bits i.e. bit#0 is swapped with bit#63, bit#1 with bit#62, ...
|
||||||
|
uint64_t reverse_bits(uint64_t b);
|
||||||
|
|
||||||
|
// reverse all bits in each byte i.e. bit#0 is swapped with bit#7, bit#1 with bit6,...
|
||||||
|
uint64_t reverse_bits_in_bytes(uint64_t b);
|
||||||
|
|
||||||
|
constexpr Direction rotate_dir[7 * 7] =
|
||||||
|
{
|
||||||
|
// Manually defined direction rotation
|
||||||
|
// PosX*6+PosX
|
||||||
|
Direction::PosX,
|
||||||
|
// PosX*6+NegX
|
||||||
|
Direction::PosX,
|
||||||
|
// PosX*6+PosY
|
||||||
|
Direction::NegZ,
|
||||||
|
// PosX*6+NegY
|
||||||
|
Direction::PosZ,
|
||||||
|
// PosX*6+PosZ
|
||||||
|
Direction::PosY,
|
||||||
|
// PosX*6+NegZ
|
||||||
|
Direction::NegY,
|
||||||
|
// PosX*6+Count
|
||||||
|
Direction::Count,
|
||||||
|
|
||||||
|
// NegX*6+PosX
|
||||||
|
Direction::NegX,
|
||||||
|
// NegX*6+NegX
|
||||||
|
Direction::NegX,
|
||||||
|
// NegX*6+PosY
|
||||||
|
Direction::PosZ,
|
||||||
|
// NegX*6+NegY
|
||||||
|
Direction::NegZ,
|
||||||
|
// NegX*6+PosZ
|
||||||
|
Direction::NegY,
|
||||||
|
// NegX*6+NegZ
|
||||||
|
Direction::PosY,
|
||||||
|
// NegX*6+Count
|
||||||
|
Direction::Count,
|
||||||
|
|
||||||
|
// PosY*6+PosX
|
||||||
|
Direction::PosZ,
|
||||||
|
// PosY*6+NegX
|
||||||
|
Direction::NegZ,
|
||||||
|
// PosY*6+PosY
|
||||||
|
Direction::PosY,
|
||||||
|
// PosY*6+NegY
|
||||||
|
Direction::PosY,
|
||||||
|
// PosY*6+PosZ
|
||||||
|
Direction::NegX,
|
||||||
|
// PosY*6+NegZ
|
||||||
|
Direction::PosX,
|
||||||
|
// PosY*6+Count
|
||||||
|
Direction::Count,
|
||||||
|
|
||||||
|
// NegY*6+PosX
|
||||||
|
Direction::NegZ,
|
||||||
|
// NegY*6+NegX
|
||||||
|
Direction::PosZ,
|
||||||
|
// NegY*6+PosY
|
||||||
|
Direction::NegY,
|
||||||
|
// NegY*6+NegY
|
||||||
|
Direction::NegY,
|
||||||
|
// NegY*6+PosZ
|
||||||
|
Direction::PosX,
|
||||||
|
// NegY*6+NegZ
|
||||||
|
Direction::NegX,
|
||||||
|
// NegY*6+Count
|
||||||
|
Direction::Count,
|
||||||
|
|
||||||
|
// PosZ*6+PosX
|
||||||
|
Direction::NegY,
|
||||||
|
// PosZ*6+NegX
|
||||||
|
Direction::PosY,
|
||||||
|
// PosZ*6+PosY
|
||||||
|
Direction::PosX,
|
||||||
|
// PosZ*6+NegY
|
||||||
|
Direction::NegX,
|
||||||
|
// PosZ*6+PosZ
|
||||||
|
Direction::PosZ,
|
||||||
|
// PosZ*6+NegZ
|
||||||
|
Direction::PosZ,
|
||||||
|
// PosZ*6+Count
|
||||||
|
Direction::Count,
|
||||||
|
|
||||||
|
// NegZ*6+PosX
|
||||||
|
Direction::PosY,
|
||||||
|
// NegZ*6+NegX
|
||||||
|
Direction::NegY,
|
||||||
|
// NegZ*6+PosY
|
||||||
|
Direction::NegX,
|
||||||
|
// NegZ*6+NegY
|
||||||
|
Direction::PosX,
|
||||||
|
// NegZ*6+PosZ
|
||||||
|
Direction::NegZ,
|
||||||
|
// NegZ*6+NegZ
|
||||||
|
Direction::NegZ,
|
||||||
|
// NegZ*6+Count
|
||||||
|
Direction::Count,
|
||||||
|
|
||||||
|
// Count*6+PosX
|
||||||
|
Direction::Count,
|
||||||
|
// Count*6+NegX
|
||||||
|
Direction::Count,
|
||||||
|
// Count*6+PosY
|
||||||
|
Direction::Count,
|
||||||
|
// Count*6+NegY
|
||||||
|
Direction::Count,
|
||||||
|
// Count*6+PosZ
|
||||||
|
Direction::Count,
|
||||||
|
// Count*6+NegZ
|
||||||
|
Direction::Count,
|
||||||
|
// Count*6+Count
|
||||||
|
Direction::Count,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rotates _dir by 90deg around _axis according to the left-claw-rule
|
||||||
|
// Supports Direction::Count as input and always returns Count then
|
||||||
|
constexpr Direction rotate(Direction _dir, Direction _axis)
|
||||||
|
{
|
||||||
|
return rotate_dir[_dir * 7 + _axis];
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 dir_to_normal(Direction _dir);
|
||||||
|
|
||||||
|
constexpr uint8_t pack_dirs(const Direction _forward, const Direction _up)
|
||||||
|
{
|
||||||
|
// each dir is 3 bit wide
|
||||||
|
return (_forward << 3) | _up;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void unpack_dirs(const uint8_t _packed, Direction &_out_forward, Direction &_out_up)
|
||||||
|
{
|
||||||
|
// each dir is 3 bit wide
|
||||||
|
_out_forward = (Direction)(_packed >> 3);
|
||||||
|
_out_up = (Direction)(_packed & 0b111);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr uint8_t orientation_2_packed_dirs[24] =
|
||||||
|
{
|
||||||
|
// Manually defined orientation
|
||||||
|
// The default orientation is forward==PosZ with up==PosY
|
||||||
|
// We then rotate up according to the left-claw-rule (around forward)
|
||||||
|
// We then rotate forward according to the left-claw-rule (around up)
|
||||||
|
pack_dirs(Direction::PosZ, Direction::PosY), // 0
|
||||||
|
pack_dirs(Direction::PosZ, Direction::NegX), // 1
|
||||||
|
pack_dirs(Direction::PosZ, Direction::NegY), // 2
|
||||||
|
pack_dirs(Direction::PosZ, Direction::PosX), // 3
|
||||||
|
|
||||||
|
pack_dirs(Direction::PosX, Direction::PosY), // 4
|
||||||
|
pack_dirs(Direction::PosX, Direction::PosZ), // 5
|
||||||
|
pack_dirs(Direction::PosX, Direction::NegY), // 6
|
||||||
|
pack_dirs(Direction::PosX, Direction::NegZ), // 7
|
||||||
|
|
||||||
|
pack_dirs(Direction::NegZ, Direction::PosY), // 8
|
||||||
|
pack_dirs(Direction::NegZ, Direction::PosX), // 9
|
||||||
|
pack_dirs(Direction::NegZ, Direction::NegY), // 10
|
||||||
|
pack_dirs(Direction::NegZ, Direction::NegX), // 11
|
||||||
|
|
||||||
|
pack_dirs(Direction::NegX, Direction::PosY), // 12
|
||||||
|
pack_dirs(Direction::NegX, Direction::NegZ), // 13
|
||||||
|
pack_dirs(Direction::NegX, Direction::NegY), // 14
|
||||||
|
pack_dirs(Direction::NegX, Direction::PosZ), // 15
|
||||||
|
|
||||||
|
// For forward==NegY we default to up==PosZ
|
||||||
|
pack_dirs(Direction::NegY, Direction::PosZ), // 16
|
||||||
|
pack_dirs(Direction::NegY, Direction::NegX), // 17
|
||||||
|
pack_dirs(Direction::NegY, Direction::NegZ), // 18
|
||||||
|
pack_dirs(Direction::NegY, Direction::PosX), // 19
|
||||||
|
|
||||||
|
// For forward==PosY we default to up==NegZ
|
||||||
|
pack_dirs(Direction::PosY, Direction::NegZ), // 20
|
||||||
|
pack_dirs(Direction::PosY, Direction::NegX), // 21
|
||||||
|
pack_dirs(Direction::PosY, Direction::PosZ), // 22
|
||||||
|
pack_dirs(Direction::PosY, Direction::PosX), // 23
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note packed dirs MUST be valid as orientation
|
||||||
|
// aka dirs must be perpendicular
|
||||||
|
constexpr uint8_t packed_dirs_2_orientation[44] =
|
||||||
|
{ // Reverse of ori_to_packed_dirs
|
||||||
|
UINT8_MAX,
|
||||||
|
UINT8_MAX,
|
||||||
|
4,
|
||||||
|
6,
|
||||||
|
5,
|
||||||
|
7,
|
||||||
|
UINT8_MAX,
|
||||||
|
UINT8_MAX,
|
||||||
|
UINT8_MAX,
|
||||||
|
UINT8_MAX,
|
||||||
|
12, // 10th element
|
||||||
|
14,
|
||||||
|
15,
|
||||||
|
13,
|
||||||
|
UINT8_MAX,
|
||||||
|
UINT8_MAX,
|
||||||
|
23,
|
||||||
|
21,
|
||||||
|
UINT8_MAX,
|
||||||
|
UINT8_MAX,
|
||||||
|
22, // 20th element
|
||||||
|
20,
|
||||||
|
UINT8_MAX,
|
||||||
|
UINT8_MAX,
|
||||||
|
19,
|
||||||
|
17,
|
||||||
|
UINT8_MAX,
|
||||||
|
UINT8_MAX,
|
||||||
|
16,
|
||||||
|
18,
|
||||||
|
UINT8_MAX, // 30th element
|
||||||
|
UINT8_MAX,
|
||||||
|
3,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
UINT8_MAX,
|
||||||
|
UINT8_MAX,
|
||||||
|
UINT8_MAX,
|
||||||
|
UINT8_MAX,
|
||||||
|
9, // 40th element
|
||||||
|
11,
|
||||||
|
8,
|
||||||
|
10};
|
||||||
|
|
||||||
|
typedef struct orientation
|
||||||
|
{
|
||||||
|
uint8_t data;
|
||||||
|
|
||||||
|
orientation() : data(packed_dirs_2_orientation[pack_dirs(Direction::PosZ, Direction::PosY)]) {};
|
||||||
|
~orientation() = default;
|
||||||
|
|
||||||
|
constexpr orientation(uint8_t _data) : data(_data) {};
|
||||||
|
|
||||||
|
constexpr orientation(const Direction _forward, const Direction _up)
|
||||||
|
: data(packed_dirs_2_orientation[pack_dirs(_forward, _up)]) {};
|
||||||
|
|
||||||
|
constexpr void to_dirs(Direction &_out_forward, Direction &_out_up) const
|
||||||
|
{
|
||||||
|
unpack_dirs(orientation_2_packed_dirs[data], _out_forward, _out_up);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void to_matrix(float *_out_mtx)
|
||||||
|
{
|
||||||
|
Direction forward;
|
||||||
|
Direction up;
|
||||||
|
to_dirs(forward, up);
|
||||||
|
Vec3 forward_vec = dir_to_normal(forward);
|
||||||
|
Vec3 up_vec = dir_to_normal(up);
|
||||||
|
Vec3 right_vec = bx::cross(up_vec, forward_vec);
|
||||||
|
// default orientation: forward = PosZ & up = PosY
|
||||||
|
// should result in identity matrix
|
||||||
|
// => mtx = (right, up, forward)
|
||||||
|
// Note: GLSL matrices are column major
|
||||||
|
_out_mtx[0] = right_vec.x == -0.0f ? 0.0f : right_vec.x;
|
||||||
|
_out_mtx[1] = right_vec.y == -0.0f ? 0.0f : right_vec.y;
|
||||||
|
_out_mtx[2] = right_vec.z == -0.0f ? 0.0f : right_vec.z;
|
||||||
|
_out_mtx[3] = up_vec.x == -0.0f ? 0.0f : up_vec.x;
|
||||||
|
_out_mtx[4] = up_vec.y == -0.0f ? 0.0f : up_vec.y;
|
||||||
|
_out_mtx[5] = up_vec.z == -0.0f ? 0.0f : up_vec.z;
|
||||||
|
_out_mtx[6] = forward_vec.x == -0.0f ? 0.0f : forward_vec.x;
|
||||||
|
_out_mtx[7] = forward_vec.y == -0.0f ? 0.0f : forward_vec.y;
|
||||||
|
_out_mtx[8] = forward_vec.z == -0.0f ? 0.0f : forward_vec.z;
|
||||||
|
}
|
||||||
|
} Orientation;
|
||||||
|
|
||||||
|
// Ensure default orientation equals integer 0
|
||||||
|
constexpr auto check_default_orientation = []() constexpr -> bool
|
||||||
|
{
|
||||||
|
Orientation ori(Direction::PosZ, Direction::PosY);
|
||||||
|
return ori.data == 0;
|
||||||
|
};
|
||||||
|
static_assert(check_default_orientation());
|
||||||
|
|
||||||
|
// Rotates _ori by 90deg around _axis according to the left-claw-rule
|
||||||
|
constexpr Orientation rotate(const Orientation _ori, const Direction _axis)
|
||||||
|
{
|
||||||
|
Direction forward;
|
||||||
|
Direction up;
|
||||||
|
_ori.to_dirs(forward, up);
|
||||||
|
|
||||||
|
forward = rotate(forward, _axis);
|
||||||
|
up = rotate(up, _axis);
|
||||||
|
|
||||||
|
return Orientation(forward, up);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
typedef struct face_normal : public I8Vec3
|
||||||
|
{
|
||||||
|
// default constructor, creates invalid face normal
|
||||||
|
face_normal() : I8Vec3() {};
|
||||||
|
|
||||||
|
face_normal(I8Vec3 _v) : I8Vec3(_v.x, _v.y, _v.z)
|
||||||
|
{
|
||||||
|
assert((_v.x >= -1 && _v.x <= 1) &&
|
||||||
|
(_v.y >= -1 && _v.y <= 1) &&
|
||||||
|
(_v.z >= -1 && _v.z <= 1) &&
|
||||||
|
!(_v.x == 0 && _v.y == 0 && _v.z == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameter MUST be in [-1;1]
|
||||||
|
// At least one MUST NOT be 0
|
||||||
|
face_normal(int8_t _x, int8_t _y, int8_t _z) :
|
||||||
|
I8Vec3(_x, _y, _z)
|
||||||
|
{
|
||||||
|
assert((_x >= -1 && _x <= 1) &&
|
||||||
|
(_y >= -1 && _y <= 1) &&
|
||||||
|
(_z >= -1 && _z <= 1) &&
|
||||||
|
!(_x == 0 && _y == 0 && _z == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience constructor
|
||||||
|
face_normal(Direction _dir) : face_normal(I8Vec3(_dir)) {}
|
||||||
|
|
||||||
|
// returns the dimensionality (e.g. dims({0,1,0})==1 ; dims({-1,1,1})==3)
|
||||||
|
uint8_t dims();
|
||||||
|
|
||||||
|
} FaceNormal;
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Work around X.h shenanigan macros
|
||||||
|
#ifdef None
|
||||||
|
#define OldNone None
|
||||||
|
#define PlsRedefineNone 1
|
||||||
|
#undef None
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace component_face_type_ns
|
||||||
|
{
|
||||||
|
enum component_face_type : uint8_t
|
||||||
|
{
|
||||||
|
// TODO this would be much nicer if the trigs corresponded with
|
||||||
|
// their comp vertices (apex)
|
||||||
|
// 0 == BL, 1 == BR, 2 == TL, 3 == TR
|
||||||
|
None,
|
||||||
|
Quad,
|
||||||
|
TrigBL,
|
||||||
|
TrigBR,
|
||||||
|
TrigTR,
|
||||||
|
TrigTL,
|
||||||
|
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
}
|
||||||
|
using FaceType = component_face_type_ns::component_face_type;
|
||||||
|
|
||||||
|
// Work around X.h shenanigan macros
|
||||||
|
#ifdef PlsRedefineNone
|
||||||
|
#define None OldNone
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static_assert(FaceType::None == 0); // necessary for light flooding algorithm
|
||||||
|
static_assert(FaceType::Quad == 1); // see "flooding_table"
|
||||||
|
static_assert(FaceType::TrigBL == 2);
|
||||||
|
static_assert(FaceType::TrigBR == 3);
|
||||||
|
static_assert(FaceType::TrigTR == 4);
|
||||||
|
static_assert(FaceType::TrigTL == 5);
|
||||||
|
static_assert(FaceType::Count == 6);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
typedef struct face_edge
|
||||||
|
{
|
||||||
|
uint16_t v0;
|
||||||
|
uint16_t v1;
|
||||||
|
|
||||||
|
face_edge() = default;
|
||||||
|
face_edge(uint16_t _v0, uint16_t _v1) : v0(_v0), v1(_v1) {};
|
||||||
|
|
||||||
|
bool operator==(const face_edge &) const = default;
|
||||||
|
} FaceEdge;
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct std::hash<FaceEdge>
|
||||||
|
{
|
||||||
|
size_t operator()(const FaceEdge &_v) const noexcept
|
||||||
|
{
|
||||||
|
uint32_t val = (((uint32_t)_v.v0) << 16) | (((uint32_t)_v.v1));
|
||||||
|
return std::hash<std::uint32_t>{}(val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct u8_vec3
|
||||||
|
{
|
||||||
|
uint8_t x;
|
||||||
|
uint8_t y;
|
||||||
|
uint8_t z;
|
||||||
|
|
||||||
|
u8_vec3() = default;
|
||||||
|
u8_vec3(const uint8_t _x, const uint8_t _y, const uint8_t _z) : x(_x), y(_y), z(_z) {};
|
||||||
|
|
||||||
|
bool operator==(const u8_vec3 &) const = default;
|
||||||
|
} U8Vec3;
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct std::hash<U8Vec3>
|
||||||
|
{
|
||||||
|
size_t operator()(const U8Vec3 &_v) const noexcept
|
||||||
|
{
|
||||||
|
uint32_t val = (((uint32_t)_v.x) << 16) | (((uint32_t)_v.y) << 8) | (((uint32_t)_v.z));
|
||||||
|
return std::hash<std::uint32_t>{}(val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
namespace texture_corner_ns
|
||||||
|
{
|
||||||
|
enum corner : uint32_t
|
||||||
|
{
|
||||||
|
TopLeft,
|
||||||
|
CenterLeft,
|
||||||
|
BotLeft,
|
||||||
|
CenterBot,
|
||||||
|
BotRight,
|
||||||
|
CenterRight,
|
||||||
|
TopRight,
|
||||||
|
CenterTop,
|
||||||
|
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
}
|
||||||
|
using TexCorner = texture_corner_ns::corner;
|
||||||
|
|
||||||
|
// Convenience struct (Basically an array in a trench-coat)
|
||||||
|
struct ComponentVertices {
|
||||||
|
uint8_t data[4];
|
||||||
|
|
||||||
|
uint8_t& operator[](int _index)
|
||||||
|
{
|
||||||
|
return data[_index];
|
||||||
|
}
|
||||||
|
const uint8_t& operator[] (int _index) const
|
||||||
|
{
|
||||||
|
return data[_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const ComponentVertices& _o) const
|
||||||
|
{
|
||||||
|
return data[0]==_o[0] && data[1]==_o[1] && data[2]==_o[2] && data[3]==_o[3];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convenience struct (Basically an array in a trench-coat)
|
||||||
|
struct BlockVertices {
|
||||||
|
uint16_t data[4];
|
||||||
|
|
||||||
|
uint16_t& operator[](int _index)
|
||||||
|
{
|
||||||
|
return data[_index];
|
||||||
|
}
|
||||||
|
const uint16_t& operator[] (int _index) const
|
||||||
|
{
|
||||||
|
return data[_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const BlockVertices& _o) const
|
||||||
|
{
|
||||||
|
return data[0]==_o[0] && data[1]==_o[1] && data[2]==_o[2] && data[3]==_o[3];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO move to world.h?
|
||||||
|
typedef struct component_face
|
||||||
|
{
|
||||||
|
FaceNormal normal;
|
||||||
|
TexCorner tex_apex; // defines how the triangle/quad samples its texture. In [0;7]
|
||||||
|
// vertices[1] corresponds with this tex-corners
|
||||||
|
ComponentVertices vertices; // counter-clockwise winding // vertices[1] MUST be triangle apex // TODO OPT if we find a way to rotate FaceType's we can get rid of these
|
||||||
|
uint32_t texture_id; // gfx::add_component_textures
|
||||||
|
|
||||||
|
bool isValid() const { return vertices[0] != vertices[1]; }
|
||||||
|
bool isQuad() const { return vertices[3] < 8; }
|
||||||
|
|
||||||
|
// Returns the face type as seen in normalized direction, based on the vertices
|
||||||
|
// Inner faces not supported for now
|
||||||
|
// Also returns the view direction if non-nullptr
|
||||||
|
FaceType faceType(Direction* _out_view_dir = nullptr) const;
|
||||||
|
|
||||||
|
// Convenience initializer for non-existant/empty faces
|
||||||
|
static const struct component_face NONE;
|
||||||
|
} ComponentFace;
|
||||||
|
constexpr size_t component_face_size = sizeof(ComponentFace);
|
||||||
|
|
||||||
|
|
||||||
|
// A face in block coordinates
|
||||||
|
// used for block welding
|
||||||
|
typedef struct block_face
|
||||||
|
{
|
||||||
|
const uint16_t INVALID_VERT = 1024; // anything >728 will do, maybe this value can be optimized
|
||||||
|
|
||||||
|
uint8_t comp_coords[3]; // coordinates of origination component in block
|
||||||
|
TexCorner tex_apex;
|
||||||
|
|
||||||
|
// block space vertex ids [0;728]
|
||||||
|
// vert[3] may be any value, but is only valid of isQuad()==true
|
||||||
|
BlockVertices vertices; // counter-clockwise winding // vertices[1] MUST be triangle apex
|
||||||
|
|
||||||
|
FaceType faceType;
|
||||||
|
FaceNormal normal;
|
||||||
|
uint32_t comp_texture_id;
|
||||||
|
|
||||||
|
bool visited; // for surface walk
|
||||||
|
bool valid; // for optimization (only valid faces get into the vertex buffer)
|
||||||
|
uint32_t texture_ids[TexCorner::Count]; // one per TexCorner // for optimization
|
||||||
|
|
||||||
|
// Constructor from ComponentFace
|
||||||
|
explicit block_face(const BlockVertices& block_verts, uint8_t _off_x, uint8_t _off_y, uint8_t _off_z,
|
||||||
|
TexCorner _tex_apex, FaceType _faceType, FaceNormal _normal, uint32_t _comp_texture_id) :
|
||||||
|
comp_coords{_off_x, _off_y, _off_z},
|
||||||
|
tex_apex(_tex_apex), vertices(block_verts),
|
||||||
|
faceType(_faceType), normal(_normal), comp_texture_id(_comp_texture_id),
|
||||||
|
visited(false), valid(true), texture_ids{0,0,0,0}
|
||||||
|
{};
|
||||||
|
|
||||||
|
// Default constructor, invalid face
|
||||||
|
explicit block_face() :
|
||||||
|
comp_coords{8, 8, 8},
|
||||||
|
tex_apex(TexCorner::Count),
|
||||||
|
vertices{INVALID_VERT, INVALID_VERT, INVALID_VERT, INVALID_VERT},
|
||||||
|
faceType(FaceType::None),
|
||||||
|
normal(), visited(false), valid(false), texture_ids{0,0,0,0} {};
|
||||||
|
|
||||||
|
constexpr bool isQuad() const { return faceType == FaceType::Quad; };
|
||||||
|
|
||||||
|
static constexpr uint16_t comp_vertex_2_block_vertex(const uint8_t _v, const uint8_t _off_x, const uint8_t _off_y, const uint8_t _off_z)
|
||||||
|
{
|
||||||
|
// place comp in block at origin
|
||||||
|
// 0 0b000 -> 0
|
||||||
|
// 1 0b001 -> 1 +1
|
||||||
|
// 2 0b010 -> 9 +9
|
||||||
|
// 3 0b011 -> 10 +9+1
|
||||||
|
// 4 0b100 -> 81 +81
|
||||||
|
// 5 0b101 -> 82 +81 +1
|
||||||
|
// 6 0b110 -> 90 +81+9
|
||||||
|
// 7 0b111 -> 91 +81+9+1
|
||||||
|
|
||||||
|
// then shift inside block by offset
|
||||||
|
return (((_v & 0b001) > 0) + _off_x) +
|
||||||
|
(((_v & 0b010) > 0) + _off_y) * 9 +
|
||||||
|
(((_v & 0b100) > 0) + _off_z) * 9 * 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
} BlockFace;
|
||||||
|
const int BlockFaceSize = sizeof(BlockFace);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Returns the (up to 4) vertices in [0;8] of the component
|
||||||
|
// Note: vertex == 8 <=> invalid <=> no vertex
|
||||||
|
// => vert[4] != 8 <=> FaceType::Quad
|
||||||
|
// => vert[1] is the apex
|
||||||
|
ComponentVertices get_component_face_vertices(FaceType _face, Direction _view_dir);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Returns the angle between _v0 and _v1 around _axis in radians [-pi;pi]
|
||||||
|
// All input expected to be unit vectors
|
||||||
|
// Note: Rotation direction is determined by right claw rule
|
||||||
|
// Returns values in [-pi;pi]
|
||||||
|
float angle_between(const Vec3 _v0, const Vec3 _v1, const Vec3 _axis);
|
||||||
|
|
||||||
|
// Note: rotation follows right claw rule along _common_edge
|
||||||
|
float angle_between_faces(const BlockFace _f0, const BlockFace _f1, const Vec3 _common_edge);
|
||||||
|
|
||||||
|
void transform_mtx(const Vec3 &_position, const Quat &_orientation, float *_out_mtx);
|
||||||
|
|
||||||
|
// Inverse ray (used as pre-calculation for collision checks)
|
||||||
|
inline constexpr InvRay InverseRay(Ray &_ray);
|
||||||
|
|
||||||
|
// Check ray <-> AABB collision
|
||||||
|
bool CheckCollisionRayBox(const InvRay &_ray, const AABB &_box, float &_out_distance);
|
||||||
|
|
||||||
|
// Check ray <-> OBB collision
|
||||||
|
bool CheckCollisionRayOrientedBox(const Ray &_ray, const AABB &_box, const float *_inverse_model_matrix, float &_distance);
|
||||||
|
|
||||||
|
void combineAABB(AABB &_a, const AABB &_b);
|
||||||
|
|
||||||
|
// Return wether _value lies between _start and _end (both inclusive)
|
||||||
|
template <typename T>
|
||||||
|
inline bool between(T _value, T _start, T _end)
|
||||||
|
{
|
||||||
|
return _value >= _start && _value <= _end;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/* Integer Math */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// _result and _vec should be I8Vec3 and _mat should be I8RotMat3x3
|
||||||
|
void mulMtxVec3(int8_t* _result, const int8_t* _mat, const int8_t* _vec);
|
||||||
|
|
||||||
|
// All parameters should be I8RotMat3x3
|
||||||
|
void mulMtxMtx(int8_t* _result, const int8_t* _a, const int8_t* _b);
|
||||||
1217
src/test.h
Normal file
1217
src/test.h
Normal file
File diff suppressed because it is too large
Load Diff
271
src/util.cpp
Normal file
271
src/util.cpp
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
#include "util.h"
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <cstring>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <bgfx/bgfx.h>
|
||||||
|
#include "config.h"
|
||||||
|
#include "space_math.h"
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include "world.h"
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
// TODO logging output should go to a file as well!
|
||||||
|
void logImpl(FILE *stream, const char *pre, const char *fmt, va_list args)
|
||||||
|
{
|
||||||
|
fprintf(stream, "%s", pre);
|
||||||
|
vfprintf(stream, fmt, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void logTrace(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
if (config::LOG_LEVEL <= config::LOG_TRACE)
|
||||||
|
{
|
||||||
|
va_list valist;
|
||||||
|
va_start(valist, fmt);
|
||||||
|
logImpl(stderr, "[TRACE] ", fmt, valist);
|
||||||
|
va_end(valist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void logDebug(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
if (config::LOG_LEVEL <= config::LOG_DEBUG)
|
||||||
|
{
|
||||||
|
va_list valist;
|
||||||
|
va_start(valist, fmt);
|
||||||
|
logImpl(stderr, "[DEBUG] ", fmt, valist);
|
||||||
|
va_end(valist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void logInfo(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
if (config::LOG_LEVEL <= config::LOG_INFO)
|
||||||
|
{
|
||||||
|
va_list valist;
|
||||||
|
va_start(valist, fmt);
|
||||||
|
logImpl(stdout, "[INFO] ", fmt, valist);
|
||||||
|
va_end(valist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void logWarn(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
if (config::LOG_LEVEL <= config::LOG_WARNING)
|
||||||
|
{
|
||||||
|
va_list valist;
|
||||||
|
va_start(valist, fmt);
|
||||||
|
logImpl(stderr, "[WARN] ", fmt, valist);
|
||||||
|
va_end(valist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void logErr(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
if (config::LOG_LEVEL <= config::LOG_ERROR)
|
||||||
|
{
|
||||||
|
va_list valist;
|
||||||
|
va_start(valist, fmt);
|
||||||
|
logImpl(stderr, "[ERROR] ", fmt, valist);
|
||||||
|
va_end(valist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void DIE(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
fflush(stdout); // better safe than sorry
|
||||||
|
|
||||||
|
va_list valist;
|
||||||
|
va_start(valist, fmt);
|
||||||
|
logImpl(stderr, "[FATAL] ", fmt, valist);
|
||||||
|
va_end(valist);
|
||||||
|
|
||||||
|
fflush(stderr); // better safe than sorry
|
||||||
|
|
||||||
|
std::abort(); // abort to show error
|
||||||
|
}
|
||||||
|
|
||||||
|
void generate_vertices(const char *filePath)
|
||||||
|
{
|
||||||
|
FILE *file = fopen(filePath, "w+"); // open for writing & truncate if exists
|
||||||
|
if (!file)
|
||||||
|
DIE("generateVertices: fopen(%s) failed\n", filePath);
|
||||||
|
|
||||||
|
fprintf(file, "const vec3 verts[729] = \n{\n");
|
||||||
|
|
||||||
|
// generate vert coordinates
|
||||||
|
// order: x -> y -> z (chosen arbitrarily..)
|
||||||
|
|
||||||
|
const float dist = 1.0f / 8.0f; // normalized distance between vertices
|
||||||
|
const float off = 0.5f; // offset to center unit cube around (0,0,0)
|
||||||
|
for (int z = 0; z < 9; z++)
|
||||||
|
{
|
||||||
|
float nz = z * dist - off;
|
||||||
|
for (int y = 0; y < 9; y++)
|
||||||
|
{
|
||||||
|
float ny = y * dist - off;
|
||||||
|
for (int x = 0; x < 9; x++)
|
||||||
|
{
|
||||||
|
float nx = x * dist - off;
|
||||||
|
fprintf(file, "{%f, %f, %f},\n", nx, ny, nz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(file, "};\n");
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates all 24 block/component orientations as glsl matrices
|
||||||
|
// Outputs a glsl lookup table (orientation_id -> matrix)
|
||||||
|
void generate_glsl_orientation_matrices(const char *_filePath)
|
||||||
|
{
|
||||||
|
FILE *file = fopen(_filePath, "w+"); // open for writing & truncate if exists
|
||||||
|
if (!file)
|
||||||
|
DIE("generateVertices: fopen(%s) failed\n", _filePath);
|
||||||
|
|
||||||
|
fprintf(file, "const mat3 orientations[24] = \n{\n");
|
||||||
|
|
||||||
|
float mtx[9];
|
||||||
|
for (uint8_t i = 0; i < 24; i++)
|
||||||
|
{
|
||||||
|
Orientation ori(i);
|
||||||
|
ori.to_matrix(mtx);
|
||||||
|
|
||||||
|
fprintf(file, "{{%f, %f, %f}, {%f, %f, %f}, {%f, %f, %f}},\n",
|
||||||
|
mtx[0], mtx[1], mtx[2],
|
||||||
|
mtx[3], mtx[4], mtx[5],
|
||||||
|
mtx[6], mtx[7], mtx[8]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(file, "};\n");
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void find_correct_working_directory()
|
||||||
|
{
|
||||||
|
// When debugging we want to change our current working directory
|
||||||
|
// to the repo root to simulate deployment environment
|
||||||
|
|
||||||
|
auto path = std::filesystem::current_path();
|
||||||
|
|
||||||
|
// Note: We check for the correct path, by searching for the shaders directory
|
||||||
|
if (std::filesystem::exists(path / "assets"))
|
||||||
|
{ // We're already set
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search in upwards
|
||||||
|
while (path != path.root_path())
|
||||||
|
{
|
||||||
|
path = path.parent_path();
|
||||||
|
|
||||||
|
if (std::filesystem::exists(path / "assets"))
|
||||||
|
{
|
||||||
|
logInfo("Correcting cwd to %s\n", path.string().c_str());
|
||||||
|
std::filesystem::current_path(path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DIE("Could not find correct working directory. I can't find my assets :(\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool should_use_wayland()
|
||||||
|
{
|
||||||
|
// Check user override
|
||||||
|
if (getenv("SPACEGAME_USE_WAYLAND"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (getenv("SPACEGAME_USE_X11"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to detect if wayland is supported
|
||||||
|
char *xdg_session_type = getenv("XDG_SESSION_TYPE");
|
||||||
|
if (xdg_session_type == NULL)
|
||||||
|
{ // Fallback
|
||||||
|
return getenv("WAYLAND_DISPLAY") != NULL;
|
||||||
|
}
|
||||||
|
return std::strcmp(xdg_session_type, "wayland") == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo add to config?
|
||||||
|
void startup_checks()
|
||||||
|
{
|
||||||
|
uint64_t caps = bgfx::getCaps()->supported;
|
||||||
|
|
||||||
|
if (!(BGFX_CAPS_COMPUTE & caps)) // vulkan core 1.0
|
||||||
|
DIE("compute shaders not supported!\n");
|
||||||
|
if (!(BGFX_CAPS_DRAW_INDIRECT & caps)) // vulkan core 1.0
|
||||||
|
DIE("draw indirect not supported!\n");
|
||||||
|
if (!(BGFX_CAPS_INSTANCING & caps))
|
||||||
|
DIE("instancing not supported!\n");
|
||||||
|
if (!(BGFX_CAPS_INDEX32 & caps)) // vulkan core 1.0 (but max value may be limited)
|
||||||
|
DIE("index32 not supported!\n");
|
||||||
|
// if (!(BGFX_CAPS_TEXTURE_2D_ARRAY & caps)) // vulkan core 1.0
|
||||||
|
// DIE("texture2DArrays not supported!\n");
|
||||||
|
|
||||||
|
bgfx::Caps::Limits limits = bgfx::getCaps()->limits;
|
||||||
|
|
||||||
|
// vulkan property maxDrawIndirectCount
|
||||||
|
// typically 2^32 -1
|
||||||
|
// TODO add check
|
||||||
|
|
||||||
|
// vulkan property fullDrawIndexUint32 must be supported
|
||||||
|
// or maxDrawIndexedIndexValue must be 2^32-1
|
||||||
|
// otherwise 32-bit indices only support values up to 2^24-1
|
||||||
|
|
||||||
|
// config::MAX_TEXTURE_LAYERS = limits.maxTextureLayers;
|
||||||
|
// if (config::MAX_TEXTURE_LAYERS < config::INITIAL_TEXTURE_LAYERS)
|
||||||
|
// DIE("limit maxTextureLayers is too small (require <=%d; was %d)",
|
||||||
|
// config::INITIAL_TEXTURE_LAYERS, config::MAX_TEXTURE_LAYERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// void flag_wait(std::atomic_flag* _flag) {
|
||||||
|
// std::atomic_flag_wait(_flag, false); // expect flag==false, wait until ==true
|
||||||
|
// std::atomic_flag_clear(_flag); // reset to ==false
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void flag_signal(std::atomic_flag* _flag) {
|
||||||
|
// while (std::atomic_flag_test_and_set(_flag)) {}; // set flag==true (spin if not yet ==false)
|
||||||
|
// std::atomic_flag_notify_one(_flag); // wake up potentially waiting thread
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void flag_wait(std::atomic_flag* _flag) {
|
||||||
|
// std::atomic_flag_wait(_flag, false); // expect flag==false, wait until ==true
|
||||||
|
// std::atomic_flag_clear(_flag); // reset to ==false
|
||||||
|
// std::atomic_flag_notify_one(_flag); // wake up potentially wating thread
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void flag_signal(std::atomic_flag* _flag) {
|
||||||
|
// while (std::atomic_flag_test_and_set(_flag)) { // set flag==true (if still true wait until cleared)
|
||||||
|
// std::atomic_flag_wait(_flag, true); // expect flag==true, wait until ==false
|
||||||
|
// };
|
||||||
|
// std::atomic_flag_notify_one(_flag); // wake up potentially waiting thread
|
||||||
|
// }
|
||||||
|
|
||||||
|
void flag_wait(std::atomic_bool *_flag)
|
||||||
|
{
|
||||||
|
std::atomic_wait(_flag, false); // expect flag==false, wait until ==true
|
||||||
|
_flag->store(false); // reset to ==false
|
||||||
|
std::atomic_notify_one(_flag); // wake up potentially wating thread
|
||||||
|
}
|
||||||
|
|
||||||
|
void flag_signal(std::atomic_bool *_flag)
|
||||||
|
{
|
||||||
|
bool expect = false;
|
||||||
|
while (!_flag->compare_exchange_weak(expect, true))
|
||||||
|
{ // set flag==true (if still true wait until cleared)
|
||||||
|
expect = false; // reset expected value
|
||||||
|
std::atomic_wait(_flag, true); // expect flag==true, wait until ==false
|
||||||
|
};
|
||||||
|
std::atomic_notify_one(_flag); // wake up potentially waiting thread
|
||||||
|
}
|
||||||
74
src/util.h
Normal file
74
src/util.h
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <atomic>
|
||||||
|
#include <bx/bx.h>
|
||||||
|
#include <bx/timer.h>
|
||||||
|
|
||||||
|
void logTrace(const char *fmt, ...);
|
||||||
|
void logDebug(const char *fmt, ...);
|
||||||
|
void logInfo(const char *fmt, ...);
|
||||||
|
void logWarn(const char *fmt, ...);
|
||||||
|
void logErr(const char *fmt, ...);
|
||||||
|
|
||||||
|
[[noreturn]] void DIE(const char *fmt, ...);
|
||||||
|
|
||||||
|
// generate vertex buffer as shader constant
|
||||||
|
// generate_vertices("C:/Users/Crydsch/Desktop/spacegame v5/shaders/verts.sh");
|
||||||
|
void generate_vertices(const char *filePath);
|
||||||
|
|
||||||
|
void generate_glsl_orientation_matrices(const char *_filePath);
|
||||||
|
|
||||||
|
void find_correct_working_directory();
|
||||||
|
|
||||||
|
// Returns true if wayland should be used instead of x11
|
||||||
|
// Only makes sense on *nix systems
|
||||||
|
bool should_use_wayland();
|
||||||
|
|
||||||
|
// performs startup checks ensuring hardware capabilities and integrity
|
||||||
|
void startup_checks();
|
||||||
|
|
||||||
|
// void flag_wait(std::atomic_flag* _flag); TODO maybe re-activate once glibc supports it -.-
|
||||||
|
// void flag_signal(std::atomic_flag* _flag);
|
||||||
|
|
||||||
|
void flag_wait(std::atomic_bool *_flag);
|
||||||
|
void flag_signal(std::atomic_bool *_flag);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline void freeContainer(T &p_container)
|
||||||
|
{
|
||||||
|
T empty{};
|
||||||
|
std::swap(p_container, empty);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ref.: https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x
|
||||||
|
// ref.: https://www.boost.org/doc/libs/1_84_0/libs/container_hash/doc/html/hash.html#combine
|
||||||
|
// TODO update to this https://www.boost.org/doc/libs/1_86_0/libs/container_hash/doc/html/hash.html#notes_hash_combine
|
||||||
|
// aka https://github.com/boostorg/container_hash/blob/89e5b98f6bc05841a21069d76cc5adcbee62b9cc/include/boost/container_hash/detail/hash_mix.hpp
|
||||||
|
// and https://github.com/boostorg/container_hash/blob/89e5b98f6bc05841a21069d76cc5adcbee62b9cc/include/boost/container_hash/hash.hpp#L469
|
||||||
|
template <class T>
|
||||||
|
inline void hash_combine(std::size_t& seed, const T& v)
|
||||||
|
{
|
||||||
|
std::hash<T> hasher;
|
||||||
|
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
|
||||||
|
}
|
||||||
|
template <typename T, typename... Rest>
|
||||||
|
inline void hash_combine(size_t &seed, const T &v, Rest... rest)
|
||||||
|
{
|
||||||
|
std::hash<T> hasher;
|
||||||
|
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||||||
|
hash_combine(seed, rest...);
|
||||||
|
}
|
||||||
|
// use like:
|
||||||
|
// std::size_t h=0;
|
||||||
|
// hash_combine(h, obj1, obj2, obj3);
|
||||||
|
|
||||||
|
#define BENCH_START \
|
||||||
|
int64_t bench_time_to_seconds = bx::getHPFrequency(); \
|
||||||
|
int64_t bench_time_to_millis = bench_time_to_seconds / 1000; \
|
||||||
|
int64_t bench_time_start = bx::getHPCounter();
|
||||||
|
|
||||||
|
#define BENCH_STOP \
|
||||||
|
int64_t bench_time_stop = bx::getHPCounter(); \
|
||||||
|
int64_t bench_time_diff = (bench_time_stop - bench_time_start); \
|
||||||
|
logInfo("BENCHMARK: %f ms\n", ((float)bench_time_diff / (float)bench_time_to_millis));
|
||||||
1070
src/world.cpp
Normal file
1070
src/world.cpp
Normal file
File diff suppressed because it is too large
Load Diff
196
src/world.h
Normal file
196
src/world.h
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <cassert>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <array>
|
||||||
|
#include "space_math.h"
|
||||||
|
// #include "graphics.h"
|
||||||
|
|
||||||
|
/* * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
/* The world namespace manages all CPU data */
|
||||||
|
/* * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
///
|
||||||
|
/// All things are referenced per their id.
|
||||||
|
/// Some ids have special meanings:
|
||||||
|
/// 0: is invalid for all purposes. Allows efficient initialization.
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
|
||||||
|
// Returns the packed transform of a block (offset in chunk + orientation)
|
||||||
|
// [off_x | off_y | off_z | orientation]
|
||||||
|
// TODO should use Orientation struct!
|
||||||
|
inline uint16_t block_transform_pack(const uint16_t off_x, const uint16_t off_y, const uint16_t off_z, const uint16_t orientation)
|
||||||
|
{
|
||||||
|
uint16_t res = 0;
|
||||||
|
res |= off_x;
|
||||||
|
res <<= 3;
|
||||||
|
res |= off_y;
|
||||||
|
res <<= 3;
|
||||||
|
res |= off_z;
|
||||||
|
res <<= 5;
|
||||||
|
res |= orientation;
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
// TODO should use Orientation struct!
|
||||||
|
inline void block_transform_unpack(const uint16_t _transform, uint16_t &_out_off_x, uint16_t &_out_off_y, uint16_t &_out_off_z, uint16_t &_out_orientation)
|
||||||
|
{
|
||||||
|
_out_orientation = _transform & 0x1F; // 5 bit
|
||||||
|
_out_off_z = (_transform >> 5) & 0x7; // 3 bit
|
||||||
|
_out_off_y = (_transform >> 8) & 0x7; // 3 bit
|
||||||
|
_out_off_x = (_transform >> 11) & 0x7; // 3 bit
|
||||||
|
}
|
||||||
|
inline uint16_t block_transform_to_chunk_index(const uint16_t _transform)
|
||||||
|
{
|
||||||
|
return (_transform >> 5); // just remove orientation
|
||||||
|
}
|
||||||
|
inline void list_add_sorted(std::vector<uint32_t> &_list, const uint32_t _val)
|
||||||
|
{
|
||||||
|
auto it = std::upper_bound(_list.begin(), _list.end(), _val);
|
||||||
|
assert(it == _list.end() || *it != _val); // value should not yet exist in the list
|
||||||
|
_list.insert(it, _val);
|
||||||
|
}
|
||||||
|
inline void list_remove_sorted(std::vector<uint32_t> &_list, const uint32_t _val)
|
||||||
|
{
|
||||||
|
auto it = std::lower_bound(_list.begin(), _list.end(), _val);
|
||||||
|
assert(it != _list.end()); // value must exist in the list
|
||||||
|
assert(*it == _val); // value must exist in the list
|
||||||
|
_list.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct grid
|
||||||
|
{
|
||||||
|
Vec3 position;
|
||||||
|
Quat orientation;
|
||||||
|
std::unordered_map<uint64_t, uint32_t> chunk_ids; // Note: if hashmap too slow try other (ex. emilib/hashmap)
|
||||||
|
std::vector<uint32_t> chunk_id_list; // kept sorted
|
||||||
|
AABB aabb;
|
||||||
|
bool aabb_dirty; // TODO uint_8 state ?
|
||||||
|
uint32_t gfx_ref; // => GPUGrid
|
||||||
|
} Grid;
|
||||||
|
|
||||||
|
typedef struct chunk
|
||||||
|
{
|
||||||
|
uint32_t parent_ref; // => Grid
|
||||||
|
int16_t parent_offset_x;
|
||||||
|
int16_t parent_offset_y;
|
||||||
|
int16_t parent_offset_z;
|
||||||
|
uint32_t block_ids[512]; // 512 = 8*8*8
|
||||||
|
// TODO block_list with only valid block_ids ?
|
||||||
|
uint32_t gfx_ref; // => GPUChunk
|
||||||
|
} Chunk;
|
||||||
|
|
||||||
|
constexpr AABB CHUNK_AABB = {bx::sub(Vec3(0.0f), Vec3(0.5f)), bx::sub(Vec3(8.0f), Vec3(0.5f))}; // A chunk consists of 8*8*8 blocks
|
||||||
|
|
||||||
|
typedef struct block
|
||||||
|
{
|
||||||
|
/* engine internals */
|
||||||
|
uint32_t parent_ref; // => Chunk
|
||||||
|
uint16_t transform; // offset + orientation in parent chunk
|
||||||
|
|
||||||
|
uint32_t base_model_ref; // => BlockModel // blueprint OR freshly built
|
||||||
|
uint32_t curr_model_ref; // => BlockModel // may be ==base_model_id OR damaged variant
|
||||||
|
|
||||||
|
uint32_t gfx_ref; // => GPUBlock
|
||||||
|
|
||||||
|
/* gameplay attributes */
|
||||||
|
// uint32_t health; // TODO
|
||||||
|
} Block;
|
||||||
|
|
||||||
|
constexpr AABB BLOCK_AABB = {{-0.5f, -0.5f, -0.5f}, {0.5f, 0.5f, 0.5f}}; // A block has a side length of 1
|
||||||
|
|
||||||
|
// instance of a component in a block model
|
||||||
|
// Note: Components are read-only
|
||||||
|
// We generate all 24 orientations once during loading. They are fixed from then on.
|
||||||
|
typedef struct component
|
||||||
|
{
|
||||||
|
ComponentFace faces[8];
|
||||||
|
|
||||||
|
static Orientation getOrientation(const uint32_t _id);
|
||||||
|
static uint32_t rotate(const uint32_t _id, const bool _inc);
|
||||||
|
static uint32_t rotate(const uint32_t _id, const Direction _axis);
|
||||||
|
} Component;
|
||||||
|
|
||||||
|
// Block Model
|
||||||
|
// What we render for a block
|
||||||
|
// Depending on its state we might render it differently
|
||||||
|
// Blueprint => generate & render scaffolding
|
||||||
|
// During construction => generate & render
|
||||||
|
// Fully intact => render base model
|
||||||
|
// Damaged => generate & render "sub"-model
|
||||||
|
// Note: self glow is encoded in the texture data
|
||||||
|
// Note: emittance is handled by lighting
|
||||||
|
typedef struct block_model
|
||||||
|
{
|
||||||
|
// ref_counts aka how many blocks refer to this model
|
||||||
|
uint32_t ram_ref_count; // if ==0 model can be ram unloaded (on cleanup run)
|
||||||
|
uint32_t gpu_ref_count; // if ==0 model can be gpu unloaded
|
||||||
|
|
||||||
|
uint32_t component_ids[512]; // 512 = 8*8*8
|
||||||
|
|
||||||
|
uint32_t gfx_ref; // => GPUBlockModel
|
||||||
|
std::vector<uint32_t> patch_refs; // => GPUTextureAtlasPatch
|
||||||
|
// TODO free patches on gpu_unload?
|
||||||
|
uint32_t outline_ref; // => GPULine
|
||||||
|
|
||||||
|
uint32_t flags;
|
||||||
|
static const uint32_t FLAG_HAS_COMPONENTS = 0x1;
|
||||||
|
|
||||||
|
// TODO rename to
|
||||||
|
// has_cpu_data & has_gpu_data
|
||||||
|
// is_ram_loaded & is_gpu_loaded
|
||||||
|
bool has_components() { return (flags & FLAG_HAS_COMPONENTS) > 0; }
|
||||||
|
} BlockModel;
|
||||||
|
|
||||||
|
namespace world
|
||||||
|
{
|
||||||
|
|
||||||
|
void init();
|
||||||
|
// Call every frame! Call BEFORE gfx::update()
|
||||||
|
void update();
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
uint32_t add_grid(const Vec3 _position, const Quat _orientation);
|
||||||
|
void update_grid_position(const uint32_t _id, const Vec3 _new_position);
|
||||||
|
void update_grid_orientation(const uint32_t _id, const Quat _new_orientation);
|
||||||
|
void update_grid_transform(const uint32_t _id, const Vec3 _new_position, const Quat _new_orientation);
|
||||||
|
void remove_grid(const uint32_t _id);
|
||||||
|
const Grid &get_grid(const uint32_t _id);
|
||||||
|
const std::vector<uint32_t> &get_grid_list();
|
||||||
|
|
||||||
|
uint32_t add_chunk(const uint32_t _parent_grid_id, const int16_t _parent_offset_x, const int16_t _parent_offset_y, const int16_t _parent_offset_z);
|
||||||
|
void remove_chunk(const uint32_t _id);
|
||||||
|
const Chunk &get_chunk(const uint32_t _id);
|
||||||
|
|
||||||
|
uint32_t add_block(const uint32_t _parent_chunk_id, const uint16_t _transform, const uint32_t _base_model_id, const uint32_t _curr_model_id);
|
||||||
|
void update_block_curr_model(const uint32_t _id, const uint32_t _curr_model_id);
|
||||||
|
void rotate_block(const uint32_t _id, const bool _inc);
|
||||||
|
void rotate_block(const uint32_t _id, const Direction _dir);
|
||||||
|
void remove_block(const uint32_t _id);
|
||||||
|
const Block &get_block(const uint32_t _id);
|
||||||
|
|
||||||
|
uint32_t add_block_model();
|
||||||
|
// Note: models are immutable (cannot be changed after setting it once)
|
||||||
|
void set_block_model_components(const uint32_t _id, const uint32_t _component_ids[512]);
|
||||||
|
// Note: models are automatically unloaded when no longer referenced by any blocks
|
||||||
|
const BlockModel &get_block_model(const uint32_t _id);
|
||||||
|
|
||||||
|
// The faces are given in Direction order and the last two are internal faces
|
||||||
|
// e.g. faces[Direction::PosX]
|
||||||
|
// Faces are interpreted in normalized orientation
|
||||||
|
// up== PosY when forward== {PosX,NegX,PosZ,NegZ}
|
||||||
|
// and up== PosZ when forward== {PosY,NegY}
|
||||||
|
uint32_t add_component(const ComponentFace _faces[8]);
|
||||||
|
const Component &get_component(const uint32_t _id);
|
||||||
|
|
||||||
|
|
||||||
|
void get_grid_transform_mtx(const uint32_t _id, float *_out_mtx);
|
||||||
|
void get_chunk_transform_mtx(const uint32_t _id, float *_out_mtx);
|
||||||
|
void get_block_transform_mtx(uint16_t _block_offset_x, uint16_t _block_offset_y, uint16_t _block_offset_z, uint16_t _block_orientation, const uint32_t _chunk_id, float *_out_mtx);
|
||||||
|
void get_block_transform_mtx(const uint32_t _id, float *_out_mtx);
|
||||||
|
Vec3 get_block_world_position(const uint32_t _id);
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user