Set up build/packaging infra for web builds #1
|
|
@ -1,4 +1,4 @@
|
||||||
cmake_minimum_required(VERSION 3.8)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(psemek)
|
project(psemek)
|
||||||
|
|
||||||
cmake_policy(SET CMP0077 NEW)
|
cmake_policy(SET CMP0077 NEW)
|
||||||
|
|
@ -45,8 +45,6 @@ option(PSEMEK_LEGACY_UI "Use legacy UI library" OFF)
|
||||||
|
|
||||||
message(STATUS "Using backend ${PSEMEK_BACKEND}")
|
message(STATUS "Using backend ${PSEMEK_BACKEND}")
|
||||||
|
|
||||||
option(PSEMEK_BT_LOG "Add logging for behavior tree actions" OFF)
|
|
||||||
|
|
||||||
if(PSEMEK_BACKEND STREQUAL "ANDROID")
|
if(PSEMEK_BACKEND STREQUAL "ANDROID")
|
||||||
set(PSEMEK_GL_API gles32)
|
set(PSEMEK_GL_API gles32)
|
||||||
set(PSEMEK_GL_LIBRARIES GLESv3 EGL)
|
set(PSEMEK_GL_LIBRARIES GLESv3 EGL)
|
||||||
|
|
@ -78,7 +76,7 @@ if(NOT DEFINED PSEMEK_GRAPHICS_API)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(PSEMEK_GRAPHICS_API STREQUAL WEBGPU)
|
if(PSEMEK_GRAPHICS_API STREQUAL WEBGPU)
|
||||||
find_package(wgpu-native REQUIRED)
|
find_package(wgpu-native 27.0.2.0 REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
message(STATUS "Using graphics API ${PSEMEK_GRAPHICS_API}")
|
message(STATUS "Using graphics API ${PSEMEK_GRAPHICS_API}")
|
||||||
|
|
|
||||||
29
LICENSE.txt
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
MIT NON-AI NON-NFT License
|
||||||
|
|
||||||
|
Copyright (c) 2025, Nikita Lisitsa
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of the 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.
|
||||||
|
|
||||||
|
In addition, the following restrictions apply:
|
||||||
|
|
||||||
|
1. The Software, any modifications made to it, any artifacts or media generated by it, and any parts thereof, including but not limited to images,
|
||||||
|
video, and code snippets, may not be used for the purpose of training or improving machine learning algorithms, or be included in any dataset used
|
||||||
|
for training or improving machine learning algorithms, including but not limited to artificial intelligence, natural language processing, large
|
||||||
|
language models, or data mining. This condition applies to any derivatives, modifications, or updates based on the Software code. Any usage of the
|
||||||
|
Software in an AI-training dataset is considered a breach of this License.
|
||||||
|
|
||||||
|
2. The Software, any modifications made to it, any artifacts or media generated by it, and any parts thereof, including but not limited to images,
|
||||||
|
video, and code snippets, may not be owned via a blockchain-backed ownership token, or as a means of generating blockchain-backed ownership tokens,
|
||||||
|
including but not limited to non-fungible tokens. Any usage of the Software to generate non-fungible tokens is considered a breach of this License.
|
||||||
|
|
||||||
|
3. Any person or organization found to be in violation of these restrictions will be subject to legal action and may be held liable for any damages
|
||||||
|
resulting from such use.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
@ -3,16 +3,27 @@ if(wgpu-native_FOUND)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Don't search for include files - these are bundled with psemek-wgpu lib
|
# Don't search for include files - these are bundled with psemek-wgpu lib
|
||||||
find_library(wgpu-native_LIBRARIES NAMES libwgpu_native.a wgpu_native.dll wgpu_native PATHS "${WGPU_NATIVE_ROOT}")
|
find_library(wgpu-native_LIBRARY NAMES libwgpu_native.a wgpu_native.dll wgpu_native PATHS "${WGPU_NATIVE_ROOT}" "${WGPU_NATIVE_ROOT}/lib")
|
||||||
|
find_file(wgpu-native_VERSION_FILE wgpu-native-git-tag PATHS "${WGPU_NATIVE_ROOT}/wgpu-native-meta")
|
||||||
|
|
||||||
|
if(EXISTS "${wgpu-native_VERSION_FILE}")
|
||||||
|
file(READ "${wgpu-native_VERSION_FILE}" wgpu-native_VERSION_NOT_STRIPPED)
|
||||||
|
string(STRIP "${wgpu-native_VERSION_NOT_STRIPPED}" wgpu-native_VERSION_STRIPPED)
|
||||||
|
string(SUBSTRING "${wgpu-native_VERSION_STRIPPED}" 1 -1 wgpu-native_VERSION)
|
||||||
|
endif()
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
include(FindPackageHandleStandardArgs)
|
||||||
find_package_handle_standard_args(wgpu-native DEFAULT_MSG wgpu-native_LIBRARIES)
|
find_package_handle_standard_args(wgpu-native
|
||||||
|
REQUIRED_VARS wgpu-native_LIBRARY wgpu-native_VERSION_FILE
|
||||||
|
VERSION_VAR wgpu-native_VERSION
|
||||||
|
)
|
||||||
|
|
||||||
if(wgpu-native_FOUND AND NOT TARGET wgpu-native)
|
if(wgpu-native_FOUND AND NOT TARGET wgpu-native)
|
||||||
|
set(wgpu-native_LIBRARIES ${wgpu-native_LIBRARY})
|
||||||
add_library(wgpu-native STATIC IMPORTED)
|
add_library(wgpu-native STATIC IMPORTED)
|
||||||
set_target_properties(wgpu-native PROPERTIES
|
set_target_properties(wgpu-native PROPERTIES
|
||||||
IMPORTED_LOCATION "${wgpu-native_LIBRARIES}"
|
IMPORTED_LOCATION "${wgpu-native_LIBRARIES}"
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
mark_as_advanced(wgpu-native_LIBRARIES)
|
mark_as_advanced(wgpu-native_LIBRARY wgpu-native_VERSION_FILE)
|
||||||
|
|
|
||||||
BIN
examples/biomes.png
Normal file
|
Before Width: | Height: | Size: 248 B After Width: | Height: | Size: 248 B |
|
|
@ -223,7 +223,7 @@ struct cloud_app
|
||||||
random::generator rng(random::device{});
|
random::generator rng(random::device{});
|
||||||
random::uniform_sphere_vector_distribution<float, 3> d;
|
random::uniform_sphere_vector_distribution<float, 3> d;
|
||||||
|
|
||||||
std::vector<util::array<math::vector<float, 3>, 3>> grad(4);
|
std::vector<util::ndarray<math::vector<float, 3>, 3>> grad(4);
|
||||||
std::vector<float> weights(grad.size());
|
std::vector<float> weights(grad.size());
|
||||||
float weight_sum = 0.f;
|
float weight_sum = 0.f;
|
||||||
|
|
||||||
|
|
@ -247,7 +247,7 @@ struct cloud_app
|
||||||
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89062
|
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89062
|
||||||
math::gradient<float> g(std::make_pair(0.2f, 0.f), math::easing_type::quadratic_out, std::pair{0.3f, max_density});
|
math::gradient<float> g(std::make_pair(0.2f, 0.f), math::easing_type::quadratic_out, std::pair{0.3f, max_density});
|
||||||
|
|
||||||
util::array<std::uint8_t, 3> cloud_data({cloud_size[0], cloud_size[1], cloud_size[2]});
|
util::ndarray<std::uint8_t, 3> cloud_data({cloud_size[0], cloud_size[1], cloud_size[2]});
|
||||||
|
|
||||||
for (std::size_t z = 0; z < cloud_data.depth(); ++z)
|
for (std::size_t z = 0; z < cloud_data.depth(); ++z)
|
||||||
{
|
{
|
||||||
|
|
@ -328,7 +328,7 @@ struct cloud_app
|
||||||
return math::lerp(v0, v1, t[2]) / 255.f * max_density;
|
return math::lerp(v0, v1, t[2]) / 255.f * max_density;
|
||||||
};
|
};
|
||||||
|
|
||||||
util::array<math::vector<float, 4>, 3> cloud_shadow_f(cloud_data.dims());
|
util::ndarray<math::vector<float, 4>, 3> cloud_shadow_f(cloud_data.dims());
|
||||||
|
|
||||||
dirs.resize(32);
|
dirs.resize(32);
|
||||||
|
|
||||||
|
|
@ -448,7 +448,7 @@ struct cloud_app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
util::array<math::vector<std::uint8_t, 4>, 3> cloud_shadow(cloud_data.dims());
|
util::ndarray<math::vector<std::uint8_t, 4>, 3> cloud_shadow(cloud_data.dims());
|
||||||
|
|
||||||
for (auto const & idx : cloud_shadow.indices())
|
for (auto const & idx : cloud_shadow.indices())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
#include <psemek/app/application_base.hpp>
|
#include <psemek/app/application_base.hpp>
|
||||||
#include <psemek/app/default_application_factory.hpp>
|
#include <psemek/app/default_application_factory.hpp>
|
||||||
#include <psemek/log/log.hpp>
|
#include <psemek/log/log.hpp>
|
||||||
#include <psemek/util/array.hpp>
|
#include <psemek/util/ndarray.hpp>
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ using namespace psemek;
|
||||||
|
|
||||||
std::vector<std::vector<math::point<int, 2>>> fibonacci_cycles(int n)
|
std::vector<std::vector<math::point<int, 2>>> fibonacci_cycles(int n)
|
||||||
{
|
{
|
||||||
util::array<int, 2> cycle({n, n}, -1);
|
util::ndarray<int, 2> cycle({n, n}, -1);
|
||||||
|
|
||||||
std::vector<std::vector<math::point<int, 2>>> result;
|
std::vector<std::vector<math::point<int, 2>>> result;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ candle_renderer::candle_renderer()
|
||||||
random::generator rng;
|
random::generator rng;
|
||||||
random::uniform_sphere_vector_distribution<float, 2> d;
|
random::uniform_sphere_vector_distribution<float, 2> d;
|
||||||
|
|
||||||
util::array<math::vector<float, 2>, 2> grad({16, 16});
|
util::ndarray<math::vector<float, 2>, 2> grad({16, 16});
|
||||||
|
|
||||||
for (auto & v : grad) v = d(rng);
|
for (auto & v : grad) v = d(rng);
|
||||||
pcg::perlin<float, 2> perlinx(grad, pcg::seamless);
|
pcg::perlin<float, 2> perlinx(grad, pcg::seamless);
|
||||||
|
|
|
||||||
BIN
examples/heightmap_seed_0.png
Normal file
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
BIN
examples/heightmap_seed_1.png
Normal file
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
BIN
examples/heightmap_seed_2.png
Normal file
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
BIN
examples/heightmap_seed_3.png
Normal file
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
441
examples/particle_life_2d.cpp
Normal file
|
|
@ -0,0 +1,441 @@
|
||||||
|
#include <psemek/app/application_base.hpp>
|
||||||
|
#include <psemek/app/default_application_factory.hpp>
|
||||||
|
#include <psemek/gfx/painter.hpp>
|
||||||
|
#include <psemek/gfx/gl.hpp>
|
||||||
|
#include <psemek/math/camera.hpp>
|
||||||
|
#include <psemek/math/contains.hpp>
|
||||||
|
#include <psemek/cg/kdtree.hpp>
|
||||||
|
#include <psemek/random/device.hpp>
|
||||||
|
#include <psemek/random/generator.hpp>
|
||||||
|
#include <psemek/random/uniform.hpp>
|
||||||
|
#include <psemek/random/uniform_box.hpp>
|
||||||
|
#include <psemek/util/clock.hpp>
|
||||||
|
#include <psemek/util/ndarray.hpp>
|
||||||
|
#include <psemek/log/log.hpp>
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
using namespace psemek;
|
||||||
|
|
||||||
|
struct particle
|
||||||
|
{
|
||||||
|
math::point<float, 2> position;
|
||||||
|
math::vector<float, 2> velocity;
|
||||||
|
float mass;
|
||||||
|
float radius;
|
||||||
|
int type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct particle_life_2d_app
|
||||||
|
: app::application_base
|
||||||
|
{
|
||||||
|
particle_life_2d_app(options const &, context const &)
|
||||||
|
{
|
||||||
|
float S = 5.f;
|
||||||
|
float W = 160.f * S;
|
||||||
|
float H = 90.f * S;
|
||||||
|
area_ = {{{-W, W}, {-H, H}}};
|
||||||
|
|
||||||
|
types_ = 10;
|
||||||
|
|
||||||
|
auto seed = random::device{}();
|
||||||
|
|
||||||
|
// Explosions
|
||||||
|
// types_ = 6;
|
||||||
|
// seed = 3940378904;
|
||||||
|
|
||||||
|
// Cells
|
||||||
|
// types_ = 8;
|
||||||
|
// seed = 3071915692;
|
||||||
|
|
||||||
|
// Cells v2
|
||||||
|
// types_ = 6;
|
||||||
|
// seed = 3337663839;
|
||||||
|
|
||||||
|
// Cool cells!
|
||||||
|
types_ = 10;
|
||||||
|
seed = 388834085;
|
||||||
|
|
||||||
|
// One cell, use central gravity
|
||||||
|
// types_ = 10;
|
||||||
|
// seed = 175098945;
|
||||||
|
|
||||||
|
// Mitochondria cell
|
||||||
|
// types_ = 10;
|
||||||
|
// seed = 1763331406;
|
||||||
|
|
||||||
|
// Just fun
|
||||||
|
// types_ = 10;
|
||||||
|
// seed = 1676103531;
|
||||||
|
|
||||||
|
// Another cell
|
||||||
|
// types_ = 10;
|
||||||
|
// seed = 2396049785;
|
||||||
|
|
||||||
|
log::info() << "Seed: " << seed;
|
||||||
|
|
||||||
|
random::generator rng{seed, 0};
|
||||||
|
|
||||||
|
random::uniform_distribution<int> dcolor(63, 255);
|
||||||
|
random::uniform_distribution<int> dtype(0, types_ - 1);
|
||||||
|
random::uniform_box_point_distribution<float, 2> darea(area_);
|
||||||
|
|
||||||
|
for (int i = 0; i < types_; ++i)
|
||||||
|
colors_.push_back({dcolor(rng), dcolor(rng), dcolor(rng), 255});
|
||||||
|
|
||||||
|
for (int i = 0; i < 4 * 1024; ++i)
|
||||||
|
{
|
||||||
|
particles_.push_back({
|
||||||
|
.position = darea(rng),
|
||||||
|
.velocity = {0.f, 0.f},
|
||||||
|
.mass = 1.f,
|
||||||
|
.radius = 1.f,
|
||||||
|
.type = dtype(rng),
|
||||||
|
// .type = random::uniform_from(rng, {4, 6}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dforce = [](auto & rng)
|
||||||
|
{
|
||||||
|
return random::uniform(rng, 20.f, 100.f) * (random::uniform<bool>(rng) ? 1.f : -1.f);
|
||||||
|
};
|
||||||
|
|
||||||
|
random::uniform_distribution<float> ddistance(3.f, 20.f);
|
||||||
|
|
||||||
|
force_constants_.resize({types_, types_});
|
||||||
|
force_distance_.resize({types_, types_});
|
||||||
|
collision_distance_.resize({types_, types_});
|
||||||
|
|
||||||
|
for (int i = 0; i < types_; ++i)
|
||||||
|
for (int j = 0; j < types_; ++j)
|
||||||
|
force_constants_(i, j) = dforce(rng);
|
||||||
|
|
||||||
|
for (int i = 0; i < types_; ++i)
|
||||||
|
for (int j = 0; j < types_; ++j)
|
||||||
|
force_distance_(i, j) = ddistance(rng);
|
||||||
|
|
||||||
|
for (int i = 0; i < types_; ++i)
|
||||||
|
for (int j = 0; j < types_; ++j)
|
||||||
|
collision_distance_(i, j) = random::uniform(rng, 0.125f, 0.75f) * force_distance_(i, j);
|
||||||
|
|
||||||
|
// for (int i = 0; i < types_; ++i)
|
||||||
|
// for (int j = 0; j < types_; ++j)
|
||||||
|
// force_constants_(i, j) = -100.f * ((i == j) ? 1.f : 2.f);
|
||||||
|
|
||||||
|
max_force_distance_.resize(types_, 2.f);
|
||||||
|
for (int i = 0; i < types_; ++i)
|
||||||
|
for (int j = 0; j < types_; ++j)
|
||||||
|
math::make_max(max_force_distance_[i], force_distance_(i, j));
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_event(app::mouse_wheel_event const & event) override
|
||||||
|
{
|
||||||
|
app::application_base::on_event(event);
|
||||||
|
|
||||||
|
scale_target_ *= std::pow(1.25f, -event.delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_event(app::key_event const & event) override
|
||||||
|
{
|
||||||
|
app::application_base::on_event(event);
|
||||||
|
|
||||||
|
if (event.down && event.key == app::keycode::SPACE)
|
||||||
|
paused_ ^= true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update() override
|
||||||
|
{
|
||||||
|
float const dt = 0.02f;
|
||||||
|
|
||||||
|
scale_ += (scale_target_ - scale_) * (- std::expm1(- 20.f * dt));
|
||||||
|
|
||||||
|
auto visible_area = area_;
|
||||||
|
visible_area = math::expand(visible_area, 0.5f * (scale_ - 1.f) * visible_area.dimensions());
|
||||||
|
|
||||||
|
float window_aspect_ratio = state().size[0] * 1.f / state().size[1];
|
||||||
|
float area_aspect_ratio = visible_area[0].length() / visible_area[1].length();
|
||||||
|
|
||||||
|
if (area_aspect_ratio < window_aspect_ratio)
|
||||||
|
{
|
||||||
|
view_area_[1] = visible_area[1];
|
||||||
|
view_area_[0] = math::expand(math::interval<float>::singleton(visible_area[0].center()), visible_area[1].length() * window_aspect_ratio * 0.5f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
view_area_[0] = visible_area[0];
|
||||||
|
view_area_[1] = math::expand(math::interval<float>::singleton(visible_area[1].center()), visible_area[0].length() / window_aspect_ratio * 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!paused_)
|
||||||
|
for (int step = 0; step < 5; ++step)
|
||||||
|
{
|
||||||
|
cg::kdtree<float, 2, int> kdtree;
|
||||||
|
|
||||||
|
for (int i = 0; i < particles_.size(); ++i)
|
||||||
|
kdtree.insert({particles_[i].position, i});
|
||||||
|
|
||||||
|
float const collision_strength = 200.f;
|
||||||
|
|
||||||
|
mouseover_particle_ = std::nullopt;
|
||||||
|
if (state().mouse_button_down.contains(app::mouse_button::left))
|
||||||
|
{
|
||||||
|
math::point<float, 2> m;
|
||||||
|
m[0] = math::lerp(view_area_[0], state().mouse[0] * 1.f / state().size[0]);
|
||||||
|
m[1] = math::lerp(view_area_[1], 1.f - state().mouse[1] * 1.f / state().size[1]);
|
||||||
|
mouseover_particle_ = kdtree.closest(m).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma omp parallel for
|
||||||
|
for (int i = 0; i < particles_.size(); ++i)
|
||||||
|
{
|
||||||
|
// ======== kd-tree based algorithm ========
|
||||||
|
|
||||||
|
auto & pi = particles_[i];
|
||||||
|
auto f = math::vector{0.f, 0.f};
|
||||||
|
|
||||||
|
kdtree.closer_than_map(pi.position, max_force_distance_[pi.type], [&](auto const & value){
|
||||||
|
auto j = value.data;
|
||||||
|
|
||||||
|
if (i == j)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto const & pj = particles_[j];
|
||||||
|
|
||||||
|
auto r = pj.position - pi.position;
|
||||||
|
float l = math::length(r);
|
||||||
|
auto n = r / l;
|
||||||
|
|
||||||
|
// ==== 1/R forces ====
|
||||||
|
// float lg = std::max(0.5f, l);
|
||||||
|
// auto g = n / (lg * lg);
|
||||||
|
|
||||||
|
// if (l < force_distance_(pi.type, pj.type))
|
||||||
|
// f += force_constants_(pi.type, pj.type) * g;
|
||||||
|
|
||||||
|
// float collision_distance = pi.radius + pj.radius;
|
||||||
|
|
||||||
|
// if (l < collision_distance)
|
||||||
|
// {
|
||||||
|
// float d = std::max(0.25f, l / collision_distance);
|
||||||
|
|
||||||
|
// f -= collision_strength * n * (1.f / (d * d) - 1.f);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ==== Linear forces ====
|
||||||
|
if (l < force_distance_(pi.type, pj.type))
|
||||||
|
f += force_constants_(pi.type, pj.type) * n * (1.f - l / force_distance_(pi.type, pj.type));
|
||||||
|
|
||||||
|
if (l < collision_distance_(pi.type, pj.type))
|
||||||
|
f -= 10.f * std::abs(force_constants_(pi.type, pj.type)) * n * (1.f - l / collision_distance_(pi.type, pj.type));
|
||||||
|
|
||||||
|
(void)collision_strength;
|
||||||
|
});
|
||||||
|
|
||||||
|
pi.velocity += f * dt / pi.mass;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ======== Naive algorithm v2 ========
|
||||||
|
|
||||||
|
// auto & pi = particles_[i];
|
||||||
|
// auto f = math::vector{0.f, 0.f};
|
||||||
|
|
||||||
|
// for (int j = 0; j < particles_.size(); ++j)
|
||||||
|
// {
|
||||||
|
// if (i == j)
|
||||||
|
// continue;
|
||||||
|
|
||||||
|
// auto const & pj = particles_[j];
|
||||||
|
|
||||||
|
// auto r = pj.position - pi.position;
|
||||||
|
// float l = math::length(r);
|
||||||
|
// auto n = r / l;
|
||||||
|
// auto g = n / (l * l);
|
||||||
|
|
||||||
|
// if (l < force_distance_(pi.type, pj.type))
|
||||||
|
// f += force_constants_(pi.type, pj.type) * g;
|
||||||
|
|
||||||
|
// if (l < pi.radius + pj.radius)
|
||||||
|
// {
|
||||||
|
// float d = l / (pi.radius + pj.radius);
|
||||||
|
|
||||||
|
// f -= collision_strength * n * (1.f / (d * d) - 1.f);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pi.velocity += f * dt / pi.mass;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ======== Naive algorithm ========
|
||||||
|
|
||||||
|
// for (int j = i + 1; j < particles_.size(); ++j)
|
||||||
|
// {
|
||||||
|
// auto & pi = particles_[i];
|
||||||
|
// auto & pj = particles_[j];
|
||||||
|
|
||||||
|
// auto fi = math::vector{0.f, 0.f};
|
||||||
|
// auto fj = math::vector{0.f, 0.f};
|
||||||
|
|
||||||
|
// auto r = pj.position - pi.position;
|
||||||
|
// float l = math::length(r);
|
||||||
|
// auto n = r / l;
|
||||||
|
// auto g = n / (l * l);
|
||||||
|
|
||||||
|
// if (l < force_distance_(pi.type, pj.type))
|
||||||
|
// fi += force_constants_(pi.type, pj.type) * g;
|
||||||
|
|
||||||
|
// if (l < force_distance_(pj.type, pi.type))
|
||||||
|
// fj -= force_constants_(pj.type, pi.type) * g;
|
||||||
|
|
||||||
|
// if (l < pi.radius + pj.radius)
|
||||||
|
// {
|
||||||
|
// float d = l / (pi.radius + pj.radius);
|
||||||
|
|
||||||
|
// auto f = collision_strength * n * (1.f / (d * d) - 1.f);
|
||||||
|
// fi -= f;
|
||||||
|
// fj += f;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pi.velocity += fi * dt / pi.mass;
|
||||||
|
// pj.velocity += fj * dt / pj.mass;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
float const friction = 10.f;
|
||||||
|
float const friction_factor = std::exp(- friction * dt);
|
||||||
|
float const wall_distance = 0.f;
|
||||||
|
float const wall_force = 10.f;
|
||||||
|
float const gravity = 0.f;
|
||||||
|
float const center_gravity = 0.f;
|
||||||
|
float const line_gravity = 0.f;
|
||||||
|
float const circle_gravity = 0.f;
|
||||||
|
float const circle_radius = 100.f;
|
||||||
|
bool const circle_wall = false;
|
||||||
|
float const circle_wall_radius = 100.f;
|
||||||
|
|
||||||
|
for (auto & p : particles_)
|
||||||
|
{
|
||||||
|
auto r = p.position - p.position.zero();
|
||||||
|
|
||||||
|
p.velocity[1] -= gravity * dt;
|
||||||
|
p.velocity[1] -= line_gravity * p.position[1] * dt;
|
||||||
|
p.velocity -= center_gravity * r * dt;
|
||||||
|
p.velocity += circle_gravity * r / math::length(r) * (circle_radius - math::length(r)) * dt;
|
||||||
|
p.velocity *= friction_factor;
|
||||||
|
p.position += p.velocity * dt;
|
||||||
|
|
||||||
|
for (int d : {0, 1})
|
||||||
|
{
|
||||||
|
// if (p.position[d] < area_[d].min + p.radius)
|
||||||
|
// {
|
||||||
|
// p.position[d] = area_[d].min + p.radius;
|
||||||
|
// p.velocity[d] *= -1.f;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (p.position[d] > area_[d].max - p.radius)
|
||||||
|
// {
|
||||||
|
// p.position[d] = area_[d].max - p.radius;
|
||||||
|
// p.velocity[d] *= -1.f;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (circle_wall)
|
||||||
|
{
|
||||||
|
auto r = p.position - p.position.zero();
|
||||||
|
auto l = math::length(r);
|
||||||
|
if (l > circle_wall_radius)
|
||||||
|
{
|
||||||
|
p.velocity -= wall_force * r / l * (l - circle_wall_radius) * dt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (p.position[d] < area_[d].min + wall_distance)
|
||||||
|
{
|
||||||
|
p.velocity[d] += wall_force * (area_[d].min + wall_distance - p.position[d]) * dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p.position[d] > area_[d].max - wall_distance)
|
||||||
|
{
|
||||||
|
p.velocity[d] -= wall_force * (p.position[d] - (area_[d].max - wall_distance)) * dt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void present() override
|
||||||
|
{
|
||||||
|
gl::Viewport(0, 0, state().size[0], state().size[1]);
|
||||||
|
|
||||||
|
gl::ClearColor(0.02f, 0.02f, 0.02f, 1.f);
|
||||||
|
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
for (auto const & p : particles_)
|
||||||
|
{
|
||||||
|
auto c0 = colors_[p.type];
|
||||||
|
c0[3] = 15;
|
||||||
|
|
||||||
|
auto c1 = colors_[p.type];
|
||||||
|
c1[3] = 0;
|
||||||
|
|
||||||
|
painter_.circle(p.position, p.radius * 8.f, c0, c1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto const & p : particles_)
|
||||||
|
painter_.circle(p.position, p.radius, colors_[p.type]);
|
||||||
|
|
||||||
|
painter_.render(math::orthographic_camera{view_area_}.transform());
|
||||||
|
|
||||||
|
if (mouseover_particle_)
|
||||||
|
{
|
||||||
|
int type = particles_[*mouseover_particle_].type;
|
||||||
|
painter_.text({state().size[0] / 2.f, state().size[1] - 20.f}, std::format("Species #{}", type), {
|
||||||
|
.scale = {2.f, 2.f},
|
||||||
|
.y = gfx::painter::y_align::bottom,
|
||||||
|
.c = colors_[type],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
painter_.render(math::window_camera{state().size[0], state().size[1]}.transform());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
gfx::painter painter_;
|
||||||
|
|
||||||
|
util::clock<std::chrono::duration<float>, std::chrono::high_resolution_clock> clock_;
|
||||||
|
|
||||||
|
math::box<float, 2> area_;
|
||||||
|
math::box<float, 2> view_area_;
|
||||||
|
float scale_ = 1.f;
|
||||||
|
float scale_target_ = 1.f;
|
||||||
|
|
||||||
|
int types_;
|
||||||
|
std::vector<gfx::color_rgba> colors_;
|
||||||
|
util::ndarray<float, 2> force_constants_;
|
||||||
|
util::ndarray<float, 2> force_distance_;
|
||||||
|
util::ndarray<float, 2> collision_distance_;
|
||||||
|
std::vector<float> max_force_distance_;
|
||||||
|
|
||||||
|
std::vector<particle> particles_;
|
||||||
|
|
||||||
|
std::optional<int> mouseover_particle_;
|
||||||
|
|
||||||
|
bool paused_ = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace psemek::app
|
||||||
|
{
|
||||||
|
|
||||||
|
std::unique_ptr<application::factory> make_application_factory()
|
||||||
|
{
|
||||||
|
return default_application_factory<particle_life_2d_app>({.name = "Particle Life 2D"});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -507,7 +507,7 @@ void physics_demo_app::update()
|
||||||
|
|
||||||
std::vector<util::fixed_vector<std::array<int, 2>, 8>> ball_cells(model.points.size());
|
std::vector<util::fixed_vector<std::array<int, 2>, 8>> ball_cells(model.points.size());
|
||||||
|
|
||||||
util::array<util::fixed_vector<std::uint32_t, 16>, 2> cells({std::ceil(view_region[0].length() / cell_size), std::ceil(view_region[1].length() / cell_size)});
|
util::ndarray<util::fixed_vector<std::uint32_t, 16>, 2> cells({std::ceil(view_region[0].length() / cell_size), std::ceil(view_region[1].length() / cell_size)});
|
||||||
|
|
||||||
float dx = view_region[0].length() / cells.width();
|
float dx = view_region[0].length() / cells.width();
|
||||||
float dy = view_region[1].length() / cells.height();
|
float dy = view_region[1].length() / cells.height();
|
||||||
|
|
|
||||||
|
|
@ -751,7 +751,9 @@ private:
|
||||||
|
|
||||||
std::vector<creature> evaluated_creatures(population_.size());
|
std::vector<creature> evaluated_creatures(population_.size());
|
||||||
|
|
||||||
|
#ifdef _OPENMP
|
||||||
#pragma omp parallel for
|
#pragma omp parallel for
|
||||||
|
#endif
|
||||||
for (int i = 0; i < population_.size(); ++i)
|
for (int i = 0; i < population_.size(); ++i)
|
||||||
{
|
{
|
||||||
auto & creature = population_[i];
|
auto & creature = population_[i];
|
||||||
|
|
|
||||||
|
|
@ -648,7 +648,7 @@ srtm_app::srtm_app(options const &, context const & context)
|
||||||
selected_mesh.setup<math::point<float, 3>>();
|
selected_mesh.setup<math::point<float, 3>>();
|
||||||
|
|
||||||
{
|
{
|
||||||
util::array<gfx::color_rgb, 1> colors({16});
|
util::ndarray<gfx::color_rgb, 1> colors({16});
|
||||||
|
|
||||||
auto * c = colors.data();
|
auto * c = colors.data();
|
||||||
|
|
||||||
|
|
@ -674,7 +674,7 @@ srtm_app::srtm_app(options const &, context const & context)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
util::array<gfx::color_rgb, 1> colors({5});
|
util::ndarray<gfx::color_rgb, 1> colors({5});
|
||||||
|
|
||||||
colors(0) = {0, 63, 127};
|
colors(0) = {0, 63, 127};
|
||||||
colors(1) = {0, 0, 127};
|
colors(1) = {0, 0, 127};
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@
|
||||||
#include <psemek/app/default_application_factory.hpp>
|
#include <psemek/app/default_application_factory.hpp>
|
||||||
#include <psemek/gfx/painter.hpp>
|
#include <psemek/gfx/painter.hpp>
|
||||||
#include <psemek/math/orthographic.hpp>
|
#include <psemek/math/orthographic.hpp>
|
||||||
|
#include <psemek/math/camera.hpp>
|
||||||
#include <psemek/log/log.hpp>
|
#include <psemek/log/log.hpp>
|
||||||
#include <psemek/util/array.hpp>
|
#include <psemek/util/ndarray.hpp>
|
||||||
#include <psemek/random/device.hpp>
|
#include <psemek/random/device.hpp>
|
||||||
#include <psemek/random/generator.hpp>
|
#include <psemek/random/generator.hpp>
|
||||||
#include <psemek/random/uniform.hpp>
|
#include <psemek/random/uniform.hpp>
|
||||||
|
|
@ -31,31 +32,40 @@ struct water_2d_app
|
||||||
|
|
||||||
random::generator rng{random::device{}};
|
random::generator rng{random::device{}};
|
||||||
|
|
||||||
int const N = 128;
|
int const N = 256;
|
||||||
|
|
||||||
float time = 0.f;
|
float time = 0.f;
|
||||||
|
|
||||||
util::array<float, 2> bed;
|
util::ndarray<float, 2> bed;
|
||||||
util::array<float, 2> water;
|
util::ndarray<float, 2> water;
|
||||||
util::array<float, 2> flowx;
|
util::ndarray<float, 2> flowx;
|
||||||
util::array<float, 2> flowy;
|
util::ndarray<float, 2> flowy;
|
||||||
util::array<math::vector<float, 2>, 2> velocity;
|
util::ndarray<math::vector<float, 2>, 2> velocity;
|
||||||
util::array<float, 2> sediment;
|
util::ndarray<float, 2> sediment;
|
||||||
util::array<float, 2> new_sediment;
|
util::ndarray<float, 2> new_sediment;
|
||||||
|
|
||||||
gfx::painter painter;
|
gfx::painter painter;
|
||||||
|
|
||||||
bool paused = true;
|
bool paused = true;
|
||||||
bool show_water = true;
|
bool show_water = true;
|
||||||
bool show_velocity = false;
|
bool show_velocity = false;
|
||||||
|
bool show_particles = false;
|
||||||
bool erosion_on = false;
|
bool erosion_on = false;
|
||||||
bool rain_on = false;
|
bool rain_on = false;
|
||||||
|
|
||||||
|
struct particle
|
||||||
|
{
|
||||||
|
math::point<float, 2> position;
|
||||||
|
int lifetime;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<particle> particles;
|
||||||
};
|
};
|
||||||
|
|
||||||
water_2d_app::water_2d_app(options const &, context const & ctx)
|
water_2d_app::water_2d_app(options const &, context const & ctx)
|
||||||
{
|
{
|
||||||
(void)ctx;
|
(void)ctx;
|
||||||
ctx.vsync(true);
|
// ctx.vsync(true);
|
||||||
|
|
||||||
simulation_area[0] = {-1.f, 1.f};
|
simulation_area[0] = {-1.f, 1.f};
|
||||||
simulation_area[1] = {-1.f, 1.f};
|
simulation_area[1] = {-1.f, 1.f};
|
||||||
|
|
@ -73,7 +83,7 @@ water_2d_app::water_2d_app(options const &, context const & ctx)
|
||||||
// water(N / 2, N / 2) = 100.f;
|
// water(N / 2, N / 2) = 100.f;
|
||||||
|
|
||||||
random::uniform_sphere_vector_distribution<float, 2> d;
|
random::uniform_sphere_vector_distribution<float, 2> d;
|
||||||
util::array<math::vector<float, 2>, 2> grads({9, 9});
|
util::ndarray<math::vector<float, 2>, 2> grads({9, 9});
|
||||||
for (auto & v : grads)
|
for (auto & v : grads)
|
||||||
v = d(rng);
|
v = d(rng);
|
||||||
|
|
||||||
|
|
@ -86,8 +96,8 @@ water_2d_app::water_2d_app(options const &, context const & ctx)
|
||||||
// if (x == N / 2 && (y != N / 4 && y != 3 * N / 4))
|
// if (x == N / 2 && (y != N / 4 && y != 3 * N / 4))
|
||||||
// bed(x, y) = 100.f;
|
// bed(x, y) = 100.f;
|
||||||
|
|
||||||
float tx = (x + 0.5f) / N;
|
[[maybe_unused]] float tx = (x + 0.5f) / N;
|
||||||
float ty = (y + 0.5f) / N;
|
[[maybe_unused]] float ty = (y + 0.5f) / N;
|
||||||
|
|
||||||
float n = 0.f;
|
float n = 0.f;
|
||||||
|
|
||||||
|
|
@ -106,7 +116,9 @@ water_2d_app::water_2d_app(options const &, context const & ctx)
|
||||||
// n = 4.f * std::abs(2.f * noise(tx, ty) - 1.f) * std::pow(4.f * tx * (1.f - tx), 2.f);
|
// n = 4.f * std::abs(2.f * noise(tx, ty) - 1.f) * std::pow(4.f * tx * (1.f - tx), 2.f);
|
||||||
|
|
||||||
// River
|
// River
|
||||||
n = std::min(4.f, math::lerp(10.f, 20.f, noise(tx, ty)) * std::abs(ty - math::lerp({0.4f, 0.6f}, noise(tx, 0.f))));
|
// n = std::min(4.f, math::lerp(10.f, 20.f, noise(tx, ty)) * std::abs(ty - math::lerp({0.4f, 0.6f}, noise(tx, 0.f))));
|
||||||
|
|
||||||
|
// n = 0.f;
|
||||||
|
|
||||||
// Canyon
|
// Canyon
|
||||||
// n = 10.f * math::clamp(10.f * math::lerp(-1.f, 1.f, noise(tx, ty)), {0.f, 1.f});
|
// n = 10.f * math::clamp(10.f * math::lerp(-1.f, 1.f, noise(tx, ty)), {0.f, 1.f});
|
||||||
|
|
@ -128,13 +140,17 @@ water_2d_app::water_2d_app(options const &, context const & ctx)
|
||||||
|
|
||||||
// if (x == 0)
|
// if (x == 0)
|
||||||
// water(x, y) = 100.f - bed(x, y);
|
// water(x, y) = 100.f - bed(x, y);
|
||||||
|
|
||||||
|
water(x, y) = std::max(0.f, 1.f - bed(x, y)) * (0.5f + 0.5f * noise(tx, ty));
|
||||||
|
|
||||||
|
flowx(x, y) = (2.f * ty - 1.f) * water(x, y) * 10.f / N;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// water(N / 2, N / 2) = 10000.f;
|
// water(N / 2, N / 2) = 10000.f;
|
||||||
}
|
}
|
||||||
|
|
||||||
float const dt = 0.002f;
|
float const dt = 0.001f;
|
||||||
|
|
||||||
void water_2d_app::update()
|
void water_2d_app::update()
|
||||||
{
|
{
|
||||||
|
|
@ -144,11 +160,15 @@ void water_2d_app::update()
|
||||||
float const dx = simulation_area[0].length() / N;
|
float const dx = simulation_area[0].length() / N;
|
||||||
float const dy = simulation_area[1].length() / N;
|
float const dy = simulation_area[1].length() / N;
|
||||||
float const g = 10.f;
|
float const g = 10.f;
|
||||||
float const friction = std::pow(0.5f, dt);
|
float const friction = std::pow(1.f, dt);
|
||||||
float const viscosity = 0.f;
|
float const viscosity = 0.f;
|
||||||
float const sediment_capacity = 0.1f;
|
float const sediment_capacity = 0.1f;
|
||||||
float const erosion = 1.f;
|
float const erosion = 1.f;
|
||||||
float const deposition = 10.f;
|
float const deposition = 10.f;
|
||||||
|
float const coriolis = 0.01f;
|
||||||
|
int const max_particles = 16 * 1024;
|
||||||
|
int const particles_per_frame = 16;
|
||||||
|
int const max_particle_lifetime = max_particles / particles_per_frame;
|
||||||
|
|
||||||
// Init boundary flows
|
// Init boundary flows
|
||||||
for (int x = 0; x < N; ++x)
|
for (int x = 0; x < N; ++x)
|
||||||
|
|
@ -160,13 +180,16 @@ void water_2d_app::update()
|
||||||
// if (x < N / 4) flowy(x, N) = -3.f / N;
|
// if (x < N / 4) flowy(x, N) = -3.f / N;
|
||||||
|
|
||||||
// flowy(x, 0) = 5.f * std::sin(10.f * time) / N;
|
// flowy(x, 0) = 5.f * std::sin(10.f * time) / N;
|
||||||
|
|
||||||
|
flowy(x, 0) = 0.f;
|
||||||
|
flowy(x, N) = 0.f;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int y = 0; y < N; ++y)
|
for (int y = 0; y < N; ++y)
|
||||||
{
|
{
|
||||||
// flowx(0, y) = 5.f * std::sin(10.f * time) / N;
|
// flowx(0, y) = 5.f * std::sin(10.f * time) / N;
|
||||||
|
|
||||||
// flowx(0, y) = 1.f / N;
|
// flowx(0, y) = 10.f / N;
|
||||||
// flowx(N, y) = 1.f / N;
|
// flowx(N, y) = 1.f / N;
|
||||||
|
|
||||||
// flowx(0, y) = 10.f * ((y + 0.5f) / N) / N;
|
// flowx(0, y) = 10.f * ((y + 0.5f) / N) / N;
|
||||||
|
|
@ -175,12 +198,12 @@ void water_2d_app::update()
|
||||||
// if (y < N / 4) flowx(0, y) = 3.f / N;
|
// if (y < N / 4) flowx(0, y) = 3.f / N;
|
||||||
// if (y >= 3 * N / 4) flowx(N, y) = -3.f / N;
|
// if (y >= 3 * N / 4) flowx(N, y) = -3.f / N;
|
||||||
|
|
||||||
if (bed(0, y) < 0.75f)
|
// if (bed(0, y) < 0.75f)
|
||||||
flowx(0, y) = 4.f / N;
|
// flowx(0, y) = 4.f / N;
|
||||||
// flowx(0, y) = 5.f * math::sqr(std::max(0.f, std::sin(15.f * time))) / N;
|
// flowx(0, y) = 5.f * math::sqr(std::max(0.f, std::sin(15.f * time))) / N;
|
||||||
|
|
||||||
if (bed(N - 1, y) < 0.75f)
|
// if (bed(N - 1, y) < 0.75f)
|
||||||
flowx(N, y) = 5.f / N;
|
// flowx(N, y) = 5.f / N;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rain :)
|
// Rain :)
|
||||||
|
|
@ -195,9 +218,13 @@ void water_2d_app::update()
|
||||||
|
|
||||||
// Update X flows
|
// Update X flows
|
||||||
for (int y = 0; y < N; ++y)
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
for (int x = 1; x < N; ++x)
|
for (int x = 1; x < N; ++x)
|
||||||
flowx(x, y) = friction * flowx(x, y) + g * dt * (water(x - 1, y) + bed(x - 1, y) - water(x, y) - bed(x, y));
|
flowx(x, y) = friction * flowx(x, y) + g * dt * (water(x - 1, y) + bed(x - 1, y) - water(x, y) - bed(x, y));
|
||||||
|
|
||||||
|
flowx(0, y) = friction * flowx(0, y) + g * dt * (water(N - 1, y) + bed(N - 1, y) - water(0, y) - bed(0, y));
|
||||||
|
}
|
||||||
|
|
||||||
// Update Y flows
|
// Update Y flows
|
||||||
for (int y = 1; y < N; ++y)
|
for (int y = 1; y < N; ++y)
|
||||||
for (int x = 0; x < N; ++x)
|
for (int x = 0; x < N; ++x)
|
||||||
|
|
@ -236,7 +263,7 @@ void water_2d_app::update()
|
||||||
{
|
{
|
||||||
float outflow = 0.f;
|
float outflow = 0.f;
|
||||||
outflow += std::max(0.f, - flowx(x , y ));
|
outflow += std::max(0.f, - flowx(x , y ));
|
||||||
outflow += std::max(0.f, flowx(x + 1, y ));
|
outflow += std::max(0.f, flowx((x + 1) % N, y ));
|
||||||
outflow += std::max(0.f, - flowy(x , y ));
|
outflow += std::max(0.f, - flowy(x , y ));
|
||||||
outflow += std::max(0.f, flowy(x , y + 1));
|
outflow += std::max(0.f, flowy(x , y + 1));
|
||||||
|
|
||||||
|
|
@ -247,7 +274,7 @@ void water_2d_app::update()
|
||||||
float scale = std::min(1.f, max_outflow / outflow);
|
float scale = std::min(1.f, max_outflow / outflow);
|
||||||
|
|
||||||
if (flowx(x, y) < 0.f) flowx(x, y) *= scale;
|
if (flowx(x, y) < 0.f) flowx(x, y) *= scale;
|
||||||
if (flowx(x + 1, y) > 0.f) flowx(x + 1, y) *= scale;
|
if (flowx((x + 1) % N, y) > 0.f) flowx((x + 1) % N, y) *= scale;
|
||||||
|
|
||||||
if (flowy(x, y) < 0.f) flowy(x, y) *= scale;
|
if (flowy(x, y) < 0.f) flowy(x, y) *= scale;
|
||||||
if (flowy(x, y + 1) > 0.f) flowy(x, y + 1) *= scale;
|
if (flowy(x, y + 1) > 0.f) flowy(x, y + 1) *= scale;
|
||||||
|
|
@ -262,7 +289,7 @@ void water_2d_app::update()
|
||||||
{
|
{
|
||||||
float w_old = water(x, y);
|
float w_old = water(x, y);
|
||||||
|
|
||||||
water(x, y) += dt / dx / dy * (flowx(x, y) + flowy(x, y) - flowx(x + 1, y) - flowy(x, y + 1));
|
water(x, y) += dt / dx / dy * (flowx(x, y) + flowy(x, y) - flowx((x + 1) % N, y) - flowy(x, y + 1));
|
||||||
|
|
||||||
float w_avg = (w_old + water(x, y)) / 2.f;
|
float w_avg = (w_old + water(x, y)) / 2.f;
|
||||||
|
|
||||||
|
|
@ -270,7 +297,7 @@ void water_2d_app::update()
|
||||||
|
|
||||||
if (w_avg > 0.f)
|
if (w_avg > 0.f)
|
||||||
{
|
{
|
||||||
v[0] = (flowx(x, y) + flowx(x + 1, y)) / 2.f / dx / w_avg;
|
v[0] = (flowx(x, y) + flowx((x + 1) % N, y)) / 2.f / dx / w_avg;
|
||||||
v[1] = (flowy(x, y) + flowy(x, y + 1)) / 2.f / dy / w_avg;
|
v[1] = (flowy(x, y) + flowy(x, y + 1)) / 2.f / dy / w_avg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,6 +305,20 @@ void water_2d_app::update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add coriolis force
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
auto n = math::ort(velocity(x, y)) * coriolis * dt / 2.f * std::sin(float(math::pi) / 2.f * ((y + 0.5f) * 2.f / N - 1.f));
|
||||||
|
|
||||||
|
flowx(x, y) += n[0];
|
||||||
|
flowx((x + 1) % N, y) += n[0];
|
||||||
|
flowy(x, y) += n[1];
|
||||||
|
flowy(x, y + 1) += n[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (erosion_on)
|
if (erosion_on)
|
||||||
{
|
{
|
||||||
// Erosion-deposition
|
// Erosion-deposition
|
||||||
|
|
@ -332,6 +373,36 @@ void water_2d_app::update()
|
||||||
std::swap(sediment, new_sediment);
|
std::swap(sediment, new_sediment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init particles
|
||||||
|
for (int i = 0; i < particles_per_frame; ++i)
|
||||||
|
if (particles.size() < max_particles)
|
||||||
|
{
|
||||||
|
math::point pos{random::uniform<float>(rng, 0.f, N), random::uniform<float>(rng, 0.f, N)};
|
||||||
|
if (water(std::floor(pos[0]), std::floor(pos[1])) > 1e-3f)
|
||||||
|
particles.push_back({pos, 0});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update particles
|
||||||
|
for (auto & p : particles)
|
||||||
|
{
|
||||||
|
p.position += velocity(std::min<int>(N - 1, std::floor(p.position[0])), std::min<int>(N - 1, std::floor(p.position[1]))) * (N * dt);
|
||||||
|
p.position[0] = math::fmod(p.position[0], float(N));
|
||||||
|
p.position[1] = math::clamp(p.position[1], {0.f, N});
|
||||||
|
p.lifetime += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy particles
|
||||||
|
for (int i = 0; i < particles.size();)
|
||||||
|
{
|
||||||
|
if (particles[i].lifetime >= max_particle_lifetime)
|
||||||
|
{
|
||||||
|
std::swap(particles[i], particles.back());
|
||||||
|
particles.pop_back();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
time += dt;
|
time += dt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -371,8 +442,8 @@ void water_2d_app::present()
|
||||||
float w = 1.f - std::exp(- 1.f * water(x, y));
|
float w = 1.f - std::exp(- 1.f * water(x, y));
|
||||||
float s = 1.f - std::exp(- 1.f * sediment(x, y));
|
float s = 1.f - std::exp(- 1.f * sediment(x, y));
|
||||||
|
|
||||||
if (water(x, y) > 1e-3f)
|
// if (water(x, y) > 1e-3f)
|
||||||
w = 0.5f + 0.5f * w;
|
// w = 0.5f + 0.5f * w;
|
||||||
|
|
||||||
auto bed_color = gfx::to_coloru8(gfx::color_4f{0.96f, 0.72f, 0.53f, b});
|
auto bed_color = gfx::to_coloru8(gfx::color_4f{0.96f, 0.72f, 0.53f, b});
|
||||||
// auto bed_color = gfx::to_coloru8(gfx::color_4f{0.3f, 0.5f, 0.1f, b});
|
// auto bed_color = gfx::to_coloru8(gfx::color_4f{0.3f, 0.5f, 0.1f, b});
|
||||||
|
|
@ -398,20 +469,33 @@ void water_2d_app::present()
|
||||||
{
|
{
|
||||||
auto center = simulation_area.corner((x + 0.5f) * invN, (y + 0.5f) * invN);
|
auto center = simulation_area.corner((x + 0.5f) * invN, (y + 0.5f) * invN);
|
||||||
|
|
||||||
auto v = water(x, y) * velocity(x, y) / 100.f;
|
auto v = water(x, y) * velocity(x, y) / 40.f;
|
||||||
|
|
||||||
float max_length = 0.02f;
|
float max_length = 0.05f;
|
||||||
auto l = math::length(v);
|
auto l = math::length(v);
|
||||||
|
|
||||||
auto color = gfx::to_coloru8(gfx::color_4f{1.f, std::exp(- 10.f * l), 0.f, 1.f});
|
// auto color = gfx::to_coloru8(gfx::color_4f{1.f, std::exp(- 10.f * l), 0.f, 1.f});
|
||||||
|
auto color = gfx::color_rgba{255, 255, 255, 255};
|
||||||
|
|
||||||
float s = std::min(1.f, l / max_length);
|
float s = std::min(1.f, l / max_length);
|
||||||
|
// s = 1.f;
|
||||||
|
|
||||||
painter.line(center, center + math::normalized(v) * max_length * s, s * 0.004f, s * 0.002f, color, color, false);
|
auto d = math::normalized(v) * max_length * s / 2.f;
|
||||||
|
|
||||||
|
painter.line(center - d, center + d, s * 0.001f, 0.f, color, color, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (show_particles)
|
||||||
|
{
|
||||||
|
for (auto const & p : particles)
|
||||||
|
{
|
||||||
|
auto pos = math::lerp(simulation_area, math::vector{p.position[0] / N, p.position[1] / N});
|
||||||
|
painter.circle(pos, 0.005f, {255, 255, 255, 127}, 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto mouse = math::cast<float>(state().mouse);
|
auto mouse = math::cast<float>(state().mouse);
|
||||||
|
|
||||||
mouse[0] = math::lerp(view_area[0], mouse[0] / state().size[0]);
|
mouse[0] = math::lerp(view_area[0], mouse[0] / state().size[0]);
|
||||||
|
|
@ -447,6 +531,11 @@ void water_2d_app::present()
|
||||||
}
|
}
|
||||||
|
|
||||||
painter.render(transform);
|
painter.render(transform);
|
||||||
|
|
||||||
|
{
|
||||||
|
painter.text({20.f, 20.f}, std::format("{} particles", particles.size()), {.scale = {2.f, 2.f}, .x = gfx::painter::x_align::left, .y = gfx::painter::y_align::top, .c = {0, 0, 0, 255}});
|
||||||
|
painter.render(math::window_camera{state().size[0], state().size[1]}.transform());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void water_2d_app::on_event(app::resize_event const & event)
|
void water_2d_app::on_event(app::resize_event const & event)
|
||||||
|
|
@ -467,6 +556,9 @@ void water_2d_app::on_event(app::key_event const & event)
|
||||||
if (event.down && event.key == app::keycode::V)
|
if (event.down && event.key == app::keycode::V)
|
||||||
show_velocity ^= true;
|
show_velocity ^= true;
|
||||||
|
|
||||||
|
if (event.down && event.key == app::keycode::P)
|
||||||
|
show_particles ^= true;
|
||||||
|
|
||||||
if (event.down && event.key == app::keycode::E)
|
if (event.down && event.key == app::keycode::E)
|
||||||
erosion_on ^= true;
|
erosion_on ^= true;
|
||||||
|
|
||||||
|
|
|
||||||
349
examples/water_2d_hex.cpp
Normal file
|
|
@ -0,0 +1,349 @@
|
||||||
|
#include <psemek/app/application_base.hpp>
|
||||||
|
#include <psemek/app/default_application_factory.hpp>
|
||||||
|
#include <psemek/gfx/gl.hpp>
|
||||||
|
#include <psemek/gfx/painter.hpp>
|
||||||
|
#include <psemek/math/box.hpp>
|
||||||
|
#include <psemek/math/camera.hpp>
|
||||||
|
#include <psemek/math/gauss.hpp>
|
||||||
|
#include <psemek/util/ndarray.hpp>
|
||||||
|
#include <psemek/random/generator.hpp>
|
||||||
|
#include <psemek/random/device.hpp>
|
||||||
|
#include <psemek/random/uniform_sphere.hpp>
|
||||||
|
#include <psemek/pcg/perlin.hpp>
|
||||||
|
#include <psemek/prof/profiler.hpp>
|
||||||
|
|
||||||
|
using namespace psemek;
|
||||||
|
|
||||||
|
static const math::vector x_axis {1.f, 0.f};
|
||||||
|
static const math::vector y_axis {0.5f, std::sqrt(0.75f)};
|
||||||
|
static const math::vector z_axis = y_axis - x_axis;
|
||||||
|
static const int N = 64;
|
||||||
|
|
||||||
|
static const float dt = 0.1f;
|
||||||
|
static const float dx = 1.f;
|
||||||
|
static float const g = 10.f;
|
||||||
|
static float const friction = std::pow(0.875f, dt);
|
||||||
|
|
||||||
|
math::point<float, 2> to_world(int x, int y)
|
||||||
|
{
|
||||||
|
return math::point{0.f, 0.f} + x_axis * (x - N / 2.f) + y_axis * (y - N / 2.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
math::point<float, 2> to_grid(math::point<float, 2> const & p)
|
||||||
|
{
|
||||||
|
static auto matrix = *math::inverse(math::by_columns(x_axis, y_axis));
|
||||||
|
static auto zero = math::point{0.f, 0.f};
|
||||||
|
|
||||||
|
return zero + matrix * (p - zero) + math::vector{N / 2.f, N / 2.f};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct water_2d_hex_app
|
||||||
|
: app::application_base
|
||||||
|
{
|
||||||
|
water_2d_hex_app(options const &, context const &);
|
||||||
|
|
||||||
|
void update() override;
|
||||||
|
void present() override;
|
||||||
|
|
||||||
|
void on_event(app::resize_event const & event) override;
|
||||||
|
void on_event(app::key_event const & event) override;
|
||||||
|
|
||||||
|
void stop() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool vsync_on_ = true;
|
||||||
|
std::function<void(bool)> set_vsync_;
|
||||||
|
|
||||||
|
random::generator rng_{random::device{}};
|
||||||
|
|
||||||
|
float aspect_ratio_ = 1.f;
|
||||||
|
math::vector<int, 2> screen_size_;
|
||||||
|
math::box<float, 2> view_area_;
|
||||||
|
gfx::painter painter_;
|
||||||
|
|
||||||
|
bool paused_ = false;
|
||||||
|
float time_ = 0.f;
|
||||||
|
|
||||||
|
bool show_velocity_ = false;
|
||||||
|
|
||||||
|
util::ndarray<float, 2> bed_;
|
||||||
|
util::ndarray<float, 2> water_;
|
||||||
|
util::ndarray<float, 2> flow_x_; // flow_x(x, y) is (x-1, y) => (x, y)
|
||||||
|
util::ndarray<float, 2> flow_y_; // flow_y(x, y) is (x, y-1) => (x, y)
|
||||||
|
util::ndarray<float, 2> flow_z_; // flow_z(x, y) is (x, y-1) => (x-1, y)
|
||||||
|
};
|
||||||
|
|
||||||
|
water_2d_hex_app::water_2d_hex_app(options const &, context const & ctx)
|
||||||
|
: set_vsync_(ctx.vsync)
|
||||||
|
{
|
||||||
|
set_vsync_(vsync_on_);
|
||||||
|
|
||||||
|
bed_.resize({N + 1, N + 1}, 0.f);
|
||||||
|
water_.resize({N + 1, N + 1}, 0.f);
|
||||||
|
flow_x_.resize({N + 2, N + 1}, 0.f);
|
||||||
|
flow_y_.resize({N + 1, N + 2}, 0.f);
|
||||||
|
flow_z_.resize({N + 2, N + 2}, 0.f);
|
||||||
|
|
||||||
|
random::uniform_sphere_vector_distribution<float, 2> d_grad;
|
||||||
|
|
||||||
|
util::ndarray<math::vector<float, 2>, 2> perlin_grad({17, 17});
|
||||||
|
for (auto & v : perlin_grad)
|
||||||
|
v = d_grad(rng_);
|
||||||
|
|
||||||
|
pcg::perlin<float, 2> noise(std::move(perlin_grad));
|
||||||
|
|
||||||
|
for (int y = 0; y <= N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x <= N; ++x)
|
||||||
|
{
|
||||||
|
auto q = (to_world(x, y) - math::point{0.f, 0.f}) / (1.f * N);
|
||||||
|
auto p = q + math::vector{0.5f, 0.5f};
|
||||||
|
|
||||||
|
(void)p;
|
||||||
|
|
||||||
|
// Canyon
|
||||||
|
bed_(x, y) = std::max(0.f, 10.f * std::abs(2.f * noise(p) - 1.f) - 1.5f);
|
||||||
|
|
||||||
|
// Islands
|
||||||
|
// bed_(x, y) = 5.f * math::smoothstep(math::clamp(math::unlerp({0.45f, 0.55f}, noise(p) - 1.f * math::length(q)), {0.f, 1.f}));
|
||||||
|
|
||||||
|
water_(x, y) = 0.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void water_2d_hex_app::update()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
float y_extent = (N / 2) * y_axis[1];
|
||||||
|
view_area_[1] = {- y_extent, y_extent};
|
||||||
|
view_area_[0] = {- y_extent * aspect_ratio_, y_extent * aspect_ratio_};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state().mouse_button_down.contains(app::mouse_button::left))
|
||||||
|
{
|
||||||
|
auto m = math::lerp(view_area_, math::vector{state().mouse[0] * 1.f / screen_size_[0], 1.f - state().mouse[1] * 1.f / screen_size_[1]});
|
||||||
|
auto p = to_grid(m);
|
||||||
|
|
||||||
|
int const R = 4;
|
||||||
|
|
||||||
|
for (int dy = -R; dy <= R; ++dy)
|
||||||
|
{
|
||||||
|
for (int dx = -R; dx <= R; ++dx)
|
||||||
|
{
|
||||||
|
if (dx + dy < -R || dx + dy > R) continue;
|
||||||
|
|
||||||
|
int mx = std::round(p[0]) + dx;
|
||||||
|
int my = std::round(p[1]) + dy;
|
||||||
|
|
||||||
|
if (mx >= 0 && mx <= N && my >= 0 && my <= N)
|
||||||
|
{
|
||||||
|
auto c = to_world(mx, my);
|
||||||
|
water_(mx, my) += 10.f * std::exp(- 0.25f * math::distance_sqr(c, m)) * dt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paused_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
prof::profiler prof("update");
|
||||||
|
|
||||||
|
time_ += dt;
|
||||||
|
|
||||||
|
// Init boundary flows
|
||||||
|
for (int i = 0; i <= N / 2; ++i)
|
||||||
|
if (bed_(N / 2 - i, i) < 0.5f)
|
||||||
|
flow_x_(N / 2 - i, i) = 300.f / N;
|
||||||
|
// flow_x_(N / 2 - i, i) = 0*std::pow(std::sin(0.5f * time_) * std::sin(math::pi * (i * 4.f / N)), 5.f) * 300.f / N;
|
||||||
|
|
||||||
|
for (int i = 0; i <= N / 2; ++i)
|
||||||
|
if (bed_(N - i, N / 2 + i) < 0.5f)
|
||||||
|
flow_x_(N - i + 1, N / 2 + i) = 300.f / N;
|
||||||
|
|
||||||
|
// Update X flows
|
||||||
|
for (int y = 0; y <= N; ++y)
|
||||||
|
// for (int x = 1; x <= N; ++x)
|
||||||
|
for (int x = std::max(1, N / 2 + 1 - y); x <= std::min(N, 3 * N / 2 - y); ++x)
|
||||||
|
// if (x + y - 1 >= N / 2 && x + y <= 3 * N / 2)
|
||||||
|
flow_x_(x, y) = friction * flow_x_(x, y) + g * dt * (water_(x - 1, y) + bed_(x - 1, y) - water_(x, y) - bed_(x, y));
|
||||||
|
|
||||||
|
// Update Y flows
|
||||||
|
for (int y = 1; y <= N; ++y)
|
||||||
|
// for (int x = 0; x <= N; ++x)
|
||||||
|
for (int x = std::max(0, N / 2 + 1 - y); x <= std::min(N, 3 * N / 2 - y); ++x)
|
||||||
|
// if (x + y - 1 >= N / 2 && x + y <= 3 * N / 2)
|
||||||
|
flow_y_(x, y) = friction * flow_y_(x, y) + g * dt * (water_(x, y - 1) + bed_(x, y - 1) - water_(x, y) - bed_(x, y));
|
||||||
|
|
||||||
|
// Update Z flows
|
||||||
|
for (int y = 1; y <= N; ++y)
|
||||||
|
// for (int x = 1; x <= N; ++x)
|
||||||
|
for (int x = std::max(1, N / 2 + 1 - y); x <= std::min(N, 3 * N / 2 + 1 - y); ++x)
|
||||||
|
// if (x + y - 1 >= N / 2 && x + y - 1 <= 3 * N / 2)
|
||||||
|
flow_z_(x, y) = friction * flow_z_(x, y) + g * dt * (water_(x, y - 1) + bed_(x, y - 1) - water_(x - 1, y) - bed_(x - 1, y));
|
||||||
|
|
||||||
|
// Scale flows
|
||||||
|
for (int y = 0; y <= N; ++y)
|
||||||
|
{
|
||||||
|
// for (int x = 0; x <= N; ++x)
|
||||||
|
for (int x = std::max(0, N / 2 - y); x <= std::min(N, 3 * N / 2 - y); ++x)
|
||||||
|
{
|
||||||
|
// if (x + y >= N / 2 && x + y <= 3 * N / 2)
|
||||||
|
{
|
||||||
|
float outflow = 0.f;
|
||||||
|
|
||||||
|
float & fin1 = flow_x_(x , y );
|
||||||
|
float & fin2 = flow_y_(x , y );
|
||||||
|
float & fin3 = flow_z_(x + 1, y );
|
||||||
|
|
||||||
|
float & fout1 = flow_x_(x + 1, y );
|
||||||
|
float & fout2 = flow_y_(x , y + 1);
|
||||||
|
float & fout3 = flow_z_(x , y + 1);
|
||||||
|
|
||||||
|
outflow += std::max(0.f, -fin1);
|
||||||
|
outflow += std::max(0.f, -fin2);
|
||||||
|
outflow += std::max(0.f, -fin3);
|
||||||
|
outflow += std::max(0.f, fout1);
|
||||||
|
outflow += std::max(0.f, fout2);
|
||||||
|
outflow += std::max(0.f, fout3);
|
||||||
|
|
||||||
|
if (outflow > 0.f)
|
||||||
|
{
|
||||||
|
float max_outflow = water_(x, y) * dx * dx / dt;
|
||||||
|
|
||||||
|
float scale = std::min(1.f, max_outflow / outflow);
|
||||||
|
|
||||||
|
fin1 *= (fin1 < 0.f ? scale : 1.f);
|
||||||
|
fin2 *= (fin2 < 0.f ? scale : 1.f);
|
||||||
|
fin3 *= (fin3 < 0.f ? scale : 1.f);
|
||||||
|
|
||||||
|
fout1 *= (fout1 > 0.f ? scale : 1.f);
|
||||||
|
fout2 *= (fout2 > 0.f ? scale : 1.f);
|
||||||
|
fout3 *= (fout3 > 0.f ? scale : 1.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update water
|
||||||
|
for (int y = 0; y <= N; ++y)
|
||||||
|
// for (int x = 0; x <= N; ++x)
|
||||||
|
for (int x = std::max(0, N / 2 - y); x <= std::min(N, 3 * N / 2 - y); ++x)
|
||||||
|
// if (x + y >= N / 2 && x + y <= 3 * N / 2)
|
||||||
|
water_(x, y) += dt / dx / dx * (flow_x_(x, y) + flow_y_(x, y) + flow_z_(x + 1,y) - flow_x_(x + 1, y) - flow_y_(x, y + 1) - flow_z_(x, y + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void water_2d_hex_app::present()
|
||||||
|
{
|
||||||
|
gl::ClearColor(0.f, 0.f, 0.f, 0.f);
|
||||||
|
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
auto color = [this](int x, int y)
|
||||||
|
{
|
||||||
|
auto bed = gfx::color_4f{0.9f, 0.7f, 0.5f, - std::expm1(- bed_(x, y))};
|
||||||
|
auto water = gfx::color_4f{0.0f, 0.25f, 1.f, - std::expm1(- water_(x, y))};
|
||||||
|
|
||||||
|
auto color = gfx::color_4f{0.f, 0.f, 0.f, 0.f};
|
||||||
|
color = gfx::blend(color, bed);
|
||||||
|
color = gfx::blend(color, water);
|
||||||
|
return gfx::to_coloru8(color);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto p00 = to_world(x, y);
|
||||||
|
auto p01 = to_world(x + 1, y);
|
||||||
|
auto p10 = to_world(x, y + 1);
|
||||||
|
auto p11 = to_world(x + 1, y + 1);
|
||||||
|
|
||||||
|
auto b00 = color(x, y);
|
||||||
|
auto b01 = color(x + 1, y);
|
||||||
|
auto b10 = color(x, y + 1);
|
||||||
|
auto b11 = color(x + 1, y + 1);
|
||||||
|
|
||||||
|
if (x + y >= N / 2 && x + y < (3 * N) / 2)
|
||||||
|
painter_.triangle(p00, p01, p10, b00, b01, b10);
|
||||||
|
|
||||||
|
if (x + y + 1 >= N / 2 && x + y + 1 < (3 * N) / 2)
|
||||||
|
painter_.triangle(p10, p01, p11, b10, b01, b11);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_velocity_)
|
||||||
|
for (int y = 0; y <= N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x <= N; ++x)
|
||||||
|
{
|
||||||
|
if (x + y >= N / 2 && x + y <= (3 * N) / 2)
|
||||||
|
{
|
||||||
|
auto p = to_world(x, y);
|
||||||
|
|
||||||
|
auto vx = (flow_x_(x, y) + flow_x_(x + 1, y)) * 0.5f * x_axis;
|
||||||
|
auto vy = (flow_y_(x, y) + flow_y_(x, y + 1)) * 0.5f * y_axis;
|
||||||
|
auto vz = (flow_z_(x + 1, y) + flow_z_(x, y + 1)) * 0.5f * z_axis;
|
||||||
|
|
||||||
|
auto v = (vx + vy + vz);
|
||||||
|
|
||||||
|
auto M = 1.f;
|
||||||
|
auto l = math::length(v);
|
||||||
|
float s = std::min(1.f, l / M);
|
||||||
|
float c = -std::expm1(- 0.1f * l);
|
||||||
|
|
||||||
|
auto color = gfx::to_coloru8(gfx::color_4f{1.f, 1.f - c, 1.f - c, 1.f});
|
||||||
|
|
||||||
|
painter_.line(p, p + v * (s / l), 0.25f * s, 0.f, color, color, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
painter_.render(math::orthographic_camera{view_area_}.transform());
|
||||||
|
}
|
||||||
|
|
||||||
|
void water_2d_hex_app::on_event(app::resize_event const & event)
|
||||||
|
{
|
||||||
|
gl::Viewport(0, 0, event.size[0], event.size[1]);
|
||||||
|
screen_size_ = event.size;
|
||||||
|
aspect_ratio_ = (event.size[0] * 1.f) / event.size[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
void water_2d_hex_app::on_event(app::key_event const & event)
|
||||||
|
{
|
||||||
|
if (event.down)
|
||||||
|
{
|
||||||
|
switch (event.key)
|
||||||
|
{
|
||||||
|
case app::keycode::V:
|
||||||
|
vsync_on_ ^= true;
|
||||||
|
set_vsync_(vsync_on_);
|
||||||
|
break;
|
||||||
|
case app::keycode::C:
|
||||||
|
show_velocity_ ^= true;
|
||||||
|
break;
|
||||||
|
case app::keycode::SPACE:
|
||||||
|
paused_ ^= true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void water_2d_hex_app::stop()
|
||||||
|
{
|
||||||
|
app::application_base::stop();
|
||||||
|
prof::dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace psemek::app
|
||||||
|
{
|
||||||
|
|
||||||
|
std::unique_ptr<application::factory> make_application_factory()
|
||||||
|
{
|
||||||
|
return default_application_factory<water_2d_hex_app>({.name = "Water 2D hex example", .multisampling = 4});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
881
examples/weather.cpp
Normal file
|
|
@ -0,0 +1,881 @@
|
||||||
|
#include <psemek/app/application_base.hpp>
|
||||||
|
#include <psemek/app/default_application_factory.hpp>
|
||||||
|
#include <psemek/gfx/painter.hpp>
|
||||||
|
#include <psemek/gfx/gl.hpp>
|
||||||
|
#include <psemek/math/camera.hpp>
|
||||||
|
#include <psemek/math/gradient.hpp>
|
||||||
|
#include <psemek/random/generator.hpp>
|
||||||
|
#include <psemek/random/device.hpp>
|
||||||
|
#include <psemek/random/uniform_ball.hpp>
|
||||||
|
#include <psemek/pcg/perlin.hpp>
|
||||||
|
#include <psemek/pcg/fractal.hpp>
|
||||||
|
#include <psemek/util/ndarray.hpp>
|
||||||
|
#include <psemek/log/log.hpp>
|
||||||
|
#include <psemek/io/file_stream.hpp>
|
||||||
|
|
||||||
|
using namespace psemek;
|
||||||
|
|
||||||
|
auto make_perlin(random::generator & rng, int min_octave, int max_octave, float power)
|
||||||
|
{
|
||||||
|
std::vector<pcg::perlin<float, 2>> octaves;
|
||||||
|
std::vector<float> weights;
|
||||||
|
|
||||||
|
random::uniform_sphere_vector_distribution<float, 2> random_vector{};
|
||||||
|
|
||||||
|
for (int octave = min_octave; octave < max_octave; ++octave)
|
||||||
|
{
|
||||||
|
int size = 1 << octave;
|
||||||
|
util::ndarray<math::vector<float, 2>, 2> gradients({size + 1, size + 1});
|
||||||
|
for (auto & g : gradients)
|
||||||
|
g = random_vector(rng);
|
||||||
|
octaves.emplace_back(std::move(gradients));
|
||||||
|
weights.push_back(std::pow(power, - octave));
|
||||||
|
}
|
||||||
|
|
||||||
|
float weight_sum = 0.f;
|
||||||
|
for (auto w : weights)
|
||||||
|
weight_sum += w;
|
||||||
|
for (auto & w : weights)
|
||||||
|
w /= weight_sum;
|
||||||
|
|
||||||
|
return pcg::fractal<pcg::perlin<float, 2>>(std::move(octaves), std::move(weights));
|
||||||
|
}
|
||||||
|
|
||||||
|
void make_force_field(random::generator & rng, util::ndarray<math::vector<float, 2>, 2> & result, float scale)
|
||||||
|
{
|
||||||
|
auto noise_1 = make_perlin(rng, 4, 6, 2.f);
|
||||||
|
auto noise_2 = make_perlin(rng, 4, 6, 2.f);
|
||||||
|
|
||||||
|
for (int y = 0; y < result.height(); ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < result.width(); ++x)
|
||||||
|
{
|
||||||
|
math::point p{(x + 0.5f) / result.height(), (y + 0.5f) / result.width()};
|
||||||
|
result(x, y) = scale * (math::vector{noise_1(p), noise_2(p)} * 2.f - math::vector{1.f, 1.f});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct weather_app
|
||||||
|
: app::application_base
|
||||||
|
{
|
||||||
|
static constexpr int N = 128;
|
||||||
|
|
||||||
|
const bool static_mode = false;
|
||||||
|
|
||||||
|
const float dt = 20.f;
|
||||||
|
const float viscosity = static_mode ? 0.01f : 0.f;
|
||||||
|
const bool temperature_advection = true;
|
||||||
|
const float advection_magnification = 1.f;
|
||||||
|
const float temperature_diffusion = 0.0004f;
|
||||||
|
const float cooling = 0.01f / 300.f;
|
||||||
|
const float cooling_factor = std::exp(- cooling * dt);
|
||||||
|
const float heating = 323.f * (std::exp(cooling * dt) - 1.f) / dt;
|
||||||
|
const float water_heating_factor = 0.9f;
|
||||||
|
const float coriolis = 0.001f;
|
||||||
|
const float coriolis_bands = 6.f;
|
||||||
|
const float band_force = 0.00001f;
|
||||||
|
const float friction = 0.f;
|
||||||
|
const float slope_friction = 1.f;
|
||||||
|
const float slope_force = 0.001f;
|
||||||
|
const float land_force = 0.05f;
|
||||||
|
const float buoyancy_factor = 0.0002f * 0;
|
||||||
|
const float vorticity_confinement = 0.f;
|
||||||
|
const float elevation_temperature_drop = 30.f;
|
||||||
|
const float evaporation = 1.0f;
|
||||||
|
const float max_humidity_factor = 1.f;
|
||||||
|
const float precipitation_factor = 0.0001f;
|
||||||
|
const float force_field_amplitude = 0.00005f;
|
||||||
|
const float random_forces = 0.25f * (static_mode ? 0.f : 1.f);
|
||||||
|
const float force_field_switch_duration = 720.f * 7.5f; // 7.5 days
|
||||||
|
const int force_field_switch_frames = std::round(force_field_switch_duration / dt);
|
||||||
|
// const float friction_factor = 1.f - std::exp(- friction * dt);
|
||||||
|
|
||||||
|
const bool periodic_x = true;
|
||||||
|
|
||||||
|
// random::generator rng{random::device{}};
|
||||||
|
random::generator rng{0, 0};
|
||||||
|
|
||||||
|
gfx::pixmap_rgba biomes_map;
|
||||||
|
|
||||||
|
float expected_temperature_at(int y, bool water) const
|
||||||
|
{
|
||||||
|
// float latitude = (y - N * 0.5f) * 2.f / N;
|
||||||
|
// return std::cos(latitude * float(math::pi));
|
||||||
|
|
||||||
|
return temperature_income_at(y) * (water ? water_heating_factor : 1.f) * dt / (std::exp(cooling * dt) - 1.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float temperature_income_at(int y) const
|
||||||
|
{
|
||||||
|
// float latitude = (y - N * 0.5f) * 2.f / N;
|
||||||
|
float latitude = y * 1.f / N;
|
||||||
|
return heating * math::lerp(0.75f, 1.f, std::cos(latitude * float(math::pi) / 2.f));
|
||||||
|
// return heating * math::lerp(0.8f, 1.f, 1.f - std::abs(latitude));
|
||||||
|
}
|
||||||
|
|
||||||
|
int wrap(int i) const
|
||||||
|
{
|
||||||
|
return (i + N) % N;
|
||||||
|
}
|
||||||
|
|
||||||
|
weather_app(options const &, context const &)
|
||||||
|
{
|
||||||
|
simulation_box_ = {{{0.f, N}, {0.f, N}}};
|
||||||
|
|
||||||
|
terrain_.resize({N, N}, 0.f);
|
||||||
|
velocity_.resize({N, N});
|
||||||
|
new_velocity_.resize({N, N});
|
||||||
|
pressure_.resize({N, N}, 0.f);
|
||||||
|
temperature_.resize({N, N}, -1.f);
|
||||||
|
new_temperature_.resize({N, N});
|
||||||
|
average_temperature_.resize({N, N}, 0.f);
|
||||||
|
force_field_main_.resize({N, N});
|
||||||
|
force_field_current_.resize({N, N});
|
||||||
|
force_field_next_.resize({N, N});
|
||||||
|
vorticity_.resize({N, N});
|
||||||
|
humidity_.resize({N, N});
|
||||||
|
new_humidity_.resize({N, N});
|
||||||
|
precipitation_.resize({N, N});
|
||||||
|
average_precipitation_.resize({N, N});
|
||||||
|
|
||||||
|
|
||||||
|
auto terrain_noise = make_perlin(rng, 2, 10, 1.6f);
|
||||||
|
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
auto d = math::length(math::vector{x - N / 2.f, y - N / 2.f}) / (N / 2.f);
|
||||||
|
(void)d;
|
||||||
|
float value = terrain_noise((x + 0.5f) / N, (y + 0.5f) / N);
|
||||||
|
value = pow(value, 4.f) - d / 4.f;
|
||||||
|
value = math::lerp(1.f, 16.f, value);
|
||||||
|
terrain_(x, y) = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto heightmap = gfx::read_image<std::uint8_t>(io::file_istream{std::filesystem::path{PSEMEK_EXAMPLES_DIR} / "heightmap_seed_1.png"});
|
||||||
|
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
terrain_(x, y) = ((heightmap(x, y) / 255.f) * 2048.f - 512.f) / 1024.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
random::uniform_ball_vector_distribution<float, 2> random_velocity{};
|
||||||
|
|
||||||
|
// for (auto & v : velocity_)
|
||||||
|
// {
|
||||||
|
// v = random_velocity(rng) * 0.01f;
|
||||||
|
// v += std::cos(0.5f * float(math::pi) * latitude * coriolis_bands
|
||||||
|
// }
|
||||||
|
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
float latitude = (N * 0.5f - y) * 2.f / N;
|
||||||
|
velocity_(x, y) = random_velocity(rng) * 0.f + 0.f * math::vector{-std::cos(0.5f * float(math::pi) * latitude * coriolis_bands), 0.f};
|
||||||
|
temperature_(x, y) = expected_temperature_at(y, terrain_(x, y) <= 0.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
if (terrain_(x, y) > 0.f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float max_humidity = std::max(0.f, temperature_(x, y) - 223.f) * max_humidity_factor;
|
||||||
|
humidity_(x, y) = max_humidity + dt * evaporation * std::max(0.f, temperature_(x, y) - 273.f) * (1.f - precipitation_factor * dt) / precipitation_factor / dt;
|
||||||
|
humidity_(x, y) = 0.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_force_field(rng, force_field_main_, 0.5f);
|
||||||
|
make_force_field(rng, force_field_next_, 0.5f);
|
||||||
|
|
||||||
|
biomes_map = gfx::read_image<gfx::color_rgba>(io::file_istream{std::filesystem::path{PSEMEK_EXAMPLES_DIR} / "biomes.png"});
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_event(app::key_event const & event) override
|
||||||
|
{
|
||||||
|
if (event.down && event.key == app::keycode::SPACE)
|
||||||
|
paused_ ^= true;
|
||||||
|
|
||||||
|
if (event.down && event.key == app::keycode::V)
|
||||||
|
show_velocity_ ^= true;
|
||||||
|
|
||||||
|
if (event.down && event.key == app::keycode::T)
|
||||||
|
show_temperature_ ^= true;
|
||||||
|
|
||||||
|
if (event.down && event.key == app::keycode::D)
|
||||||
|
show_temperature_delta_ ^= true;
|
||||||
|
|
||||||
|
if (event.down && event.key == app::keycode::A)
|
||||||
|
show_average_temperature_delta_ ^= true;
|
||||||
|
|
||||||
|
if (event.down && event.key == app::keycode::P)
|
||||||
|
show_pressure_ ^= true;
|
||||||
|
|
||||||
|
if (event.down && event.key == app::keycode::H)
|
||||||
|
show_land_ ^= true;
|
||||||
|
|
||||||
|
if (event.down && event.key == app::keycode::W)
|
||||||
|
show_water_vapor_ ^= true;
|
||||||
|
|
||||||
|
if (event.down && event.key == app::keycode::R)
|
||||||
|
show_precipitation_ ^= true;
|
||||||
|
|
||||||
|
if (event.down && event.key == app::keycode::Q)
|
||||||
|
show_average_precipitation_ ^= true;
|
||||||
|
|
||||||
|
if (event.down && event.key == app::keycode::B)
|
||||||
|
show_biomes_ ^= true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update() override
|
||||||
|
{
|
||||||
|
if (paused_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Update force field
|
||||||
|
if ((frame_ % force_field_switch_frames) == 0)
|
||||||
|
{
|
||||||
|
std::swap(force_field_current_, force_field_next_);
|
||||||
|
make_force_field(rng, force_field_next_, 0.5f);
|
||||||
|
}
|
||||||
|
[[maybe_unused]] float const force_field_t = ((frame_ % force_field_switch_frames) + 0.5f) / force_field_switch_frames;
|
||||||
|
|
||||||
|
int xmin = periodic_x ? 0 : 1;
|
||||||
|
int xmax = periodic_x ? N : N - 1; // exclusive
|
||||||
|
|
||||||
|
// Temperature source
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
bool const is_water = terrain_(x, y) <= 0.f;
|
||||||
|
// temperature_(x, y) = math::lerp(temperature_(x, y), expected_temperature_at(y), 1.f - std::exp(- heating * dt));
|
||||||
|
temperature_(x, y) += dt * temperature_income_at(y) * (is_water ? water_heating_factor : 1.f);
|
||||||
|
temperature_(x, y) *= cooling_factor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaporation
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
if (terrain_(x, y) <= 0.f)
|
||||||
|
humidity_(x, y) += dt * evaporation * std::max(0.f, temperature_(x, y) - 273.f);
|
||||||
|
|
||||||
|
// float discharge = std::min(humidity_(x, y), precipitation_factor * dt);
|
||||||
|
// float discharge = humidity_(x, y) * precipitation_factor * dt;
|
||||||
|
// float max_humidity = temperature_(x, y) * max_humidity_factor;
|
||||||
|
float max_humidity = temperature_(x, y) * temperature_(x, y) * max_humidity_factor;
|
||||||
|
float discharge = std::max(0.f, humidity_(x, y) - max_humidity) * precipitation_factor * dt;
|
||||||
|
humidity_(x, y) -= discharge;
|
||||||
|
precipitation_(x, y) = discharge / dt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Velocity & temperature advection
|
||||||
|
for (int i = 0; i < N; ++i)
|
||||||
|
{
|
||||||
|
new_temperature_(i, 0) = temperature_(i, 0);
|
||||||
|
new_temperature_(i, N - 1) = temperature_(i, N - 1);
|
||||||
|
|
||||||
|
new_humidity_(i, 0) = humidity_(i, 0);
|
||||||
|
new_humidity_(i, N - 1) = humidity_(i, N - 1);
|
||||||
|
|
||||||
|
if (!periodic_x)
|
||||||
|
{
|
||||||
|
new_temperature_(0, i) = temperature_(0, i);
|
||||||
|
new_temperature_(N - 1, i) = temperature_(N - 1, i);
|
||||||
|
|
||||||
|
new_humidity_(0, i) = humidity_(0, i);
|
||||||
|
new_humidity_(N - 1, i) = humidity_(N - 1, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int y = 1; y < N - 1; ++y)
|
||||||
|
{
|
||||||
|
for (int x = xmin; x < xmax; ++x)
|
||||||
|
{
|
||||||
|
auto v = velocity_(x, y);
|
||||||
|
auto p = math::point{x + 0.5f, y + 0.5f} - (advection_magnification * dt) * v;
|
||||||
|
p[0] = p[0] - 0.5f;
|
||||||
|
p[1] = math::clamp(p[1] - 0.5f, {0.f, N - 1.f});
|
||||||
|
|
||||||
|
if (!periodic_x)
|
||||||
|
p[0] = math::clamp(p[0], {0.f, N - 1.f});
|
||||||
|
|
||||||
|
int ix = std::floor(p[0]);
|
||||||
|
int iy = std::min<int>(N - 1, std::floor(p[1]));
|
||||||
|
|
||||||
|
if (!periodic_x)
|
||||||
|
ix = std::min(N - 1, ix);
|
||||||
|
|
||||||
|
float tx = p[0] - ix;
|
||||||
|
float ty = p[1] - iy;
|
||||||
|
|
||||||
|
new_velocity_(x, y) = math::lerp(
|
||||||
|
math::lerp(velocity_(wrap(ix + 0), iy + 0), velocity_(wrap(ix + 1), iy + 0), tx),
|
||||||
|
math::lerp(velocity_(wrap(ix + 0), iy + 1), velocity_(wrap(ix + 1), iy + 1), tx),
|
||||||
|
ty
|
||||||
|
);
|
||||||
|
|
||||||
|
new_temperature_(x, y) = math::lerp(
|
||||||
|
math::lerp(temperature_(wrap(ix + 0), iy + 0), temperature_(wrap(ix + 1), iy + 0), tx),
|
||||||
|
math::lerp(temperature_(wrap(ix + 0), iy + 1), temperature_(wrap(ix + 1), iy + 1), tx),
|
||||||
|
ty
|
||||||
|
);
|
||||||
|
|
||||||
|
new_humidity_(x, y) = math::lerp(
|
||||||
|
math::lerp(humidity_(wrap(ix + 0), iy + 0), humidity_(wrap(ix + 1), iy + 0), tx),
|
||||||
|
math::lerp(humidity_(wrap(ix + 0), iy + 1), humidity_(wrap(ix + 1), iy + 1), tx),
|
||||||
|
ty
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::swap(velocity_, new_velocity_);
|
||||||
|
if (temperature_advection) std::swap(temperature_, new_temperature_);
|
||||||
|
std::swap(humidity_, new_humidity_);
|
||||||
|
|
||||||
|
// Apply velocity diffusion
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
new_velocity_(x, y) = velocity_(x, y);
|
||||||
|
for (int y = 1; y < N - 1; ++y)
|
||||||
|
{
|
||||||
|
for (int x = xmin; x < xmax; ++x)
|
||||||
|
{
|
||||||
|
// Velocity Laplacian
|
||||||
|
auto laplacian = velocity_(wrap(x + 1), y) + velocity_(wrap(x - 1), y) + velocity_(x, y + 1) + velocity_(x, y - 1) - 4.f * velocity_(x, y);
|
||||||
|
new_velocity_(x, y) = velocity_(x, y) + viscosity * dt * laplacian;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::swap(velocity_, new_velocity_);
|
||||||
|
|
||||||
|
// Apply temperature diffusion
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
new_temperature_(x, y) = temperature_(x, y);
|
||||||
|
for (int y = 1; y < N - 1; ++y)
|
||||||
|
{
|
||||||
|
for (int x = xmin; x < xmax; ++x)
|
||||||
|
{
|
||||||
|
// Temperature Laplacian
|
||||||
|
auto laplacian = temperature_(wrap(x + 1), y) + temperature_(wrap(x - 1), y) + temperature_(x, y + 1) + temperature_(x, y - 1) - 4.f * temperature_(x, y);
|
||||||
|
new_temperature_(x, y) = temperature_(x, y) + temperature_diffusion * dt * laplacian;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::swap(temperature_, new_temperature_);
|
||||||
|
|
||||||
|
// Compute vorticity
|
||||||
|
for (int y = 1; y < N - 1; ++y)
|
||||||
|
for (int x = xmin; x < xmax; ++x)
|
||||||
|
vorticity_(x, y) = (velocity_(x, y + 1)[0] - velocity_(x, y - 1)[0]) / 2.f - (velocity_(wrap(x + 1), y)[1] - velocity_(wrap(x - 1), y)[1]) / 2;
|
||||||
|
|
||||||
|
// Apply forces & friction
|
||||||
|
for (int y = 1; y < N - 1; ++y)
|
||||||
|
{
|
||||||
|
for (int x = xmin; x < xmax; ++x)
|
||||||
|
{
|
||||||
|
// float latitude = (N * 0.5f - y) * 2.f / N;
|
||||||
|
[[maybe_unused]] float latitude = (N - y) * 1.f / N;
|
||||||
|
// velocity_(x, y) += math::ort(velocity_(x, y)) * (coriolis * dt * std::sin(0.5f * float(math::pi) * latitude * coriolis_bands));
|
||||||
|
velocity_(x, y) = math::rotate(velocity_(x, y), coriolis * dt * std::sin(0.5f * float(math::pi) * latitude * coriolis_bands));
|
||||||
|
|
||||||
|
// auto force = force_field_main_(x, y) + random_forces * math::lerp(force_field_current_(x, y), force_field_next_(x, y), force_field_t);
|
||||||
|
// velocity_(x, y) += (dt * force_field_amplitude) * force;
|
||||||
|
|
||||||
|
// velocity_(x, y)[0] += dt * 0.000001f * std::cos(0.5f * float(math::pi) * latitude * 4.f);
|
||||||
|
// velocity_(x, y)[0] += dt * 0.000001f;
|
||||||
|
|
||||||
|
velocity_(x, y)[0] += dt * band_force * std::sin(0.5f * float(math::pi) * latitude * coriolis_bands);
|
||||||
|
|
||||||
|
[[maybe_unused]] math::vector terrain_gradient
|
||||||
|
{
|
||||||
|
(std::max(0.f, terrain_(x + 1, y)) - std::max(0.f, terrain_(x - 1, y))) / 2.f,
|
||||||
|
(std::max(0.f, terrain_(x, y + 1)) - std::max(0.f, terrain_(x, y - 1))) / 2.f,
|
||||||
|
};
|
||||||
|
|
||||||
|
[[maybe_unused]] math::vector temperature_gradient
|
||||||
|
{
|
||||||
|
(temperature_(x + 1, y) - temperature_(x - 1, y)) / 2.f,
|
||||||
|
(temperature_(x, y + 1) - temperature_(x, y - 1)) / 2.f,
|
||||||
|
};
|
||||||
|
|
||||||
|
velocity_(x, y) += temperature_gradient * buoyancy_factor * dt;
|
||||||
|
|
||||||
|
[[maybe_unused]] float slope_factor = std::exp(- dt * slope_friction * math::dot(math::normalized(velocity_(x, y)), terrain_gradient));
|
||||||
|
velocity_(x, y) *= std::min(1.f, slope_factor);
|
||||||
|
// velocity_(x, y) -= terrain_gradient * slope_force * dt;
|
||||||
|
|
||||||
|
// [[maybe_unused]] float slope_factor = std::exp(- dt * slope_force * std::pow(math::length(terrain_gradient), 4.f));
|
||||||
|
[[maybe_unused]] float land_factor = std::exp(- dt * land_force * std::pow(std::max(0.f, terrain_(x, y)), 1.f));
|
||||||
|
velocity_(x, y) *= land_factor;
|
||||||
|
|
||||||
|
// Directional external force
|
||||||
|
// velocity_(x, y)[1] += 0.001f * dt * std::sin(0.5f * float(math::pi) * latitude * coriolis_bands);
|
||||||
|
// velocity_(x, y) += math::direction(frame_ * dt * 2.f * float(math::pi) / 10080.f) * 0.001f * dt;
|
||||||
|
|
||||||
|
math::vector vorticity_gradient
|
||||||
|
{
|
||||||
|
(vorticity_(wrap(x + 1), y) - vorticity_(wrap(x - 1), y)) / 2.f,
|
||||||
|
(vorticity_(x, y + 1) - vorticity_(x, y - 1)) / 2.f,
|
||||||
|
};
|
||||||
|
if (auto l = math::length(vorticity_gradient); l > 0.f)
|
||||||
|
vorticity_gradient /= l;
|
||||||
|
|
||||||
|
velocity_(x, y) += vorticity_confinement * dt * vorticity_(x, y) * math::ort(vorticity_gradient);
|
||||||
|
|
||||||
|
float local_friction = friction * terrain_(x, y);
|
||||||
|
// velocity_(x, y) -= local_friction * velocity_(x, y) * math::length(velocity_(x, y));
|
||||||
|
[[maybe_unused]] float local_friction_factor = std::exp(- local_friction * dt);
|
||||||
|
// velocity_(x, y) *= local_friction_factor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solve Poisson equation for pressure
|
||||||
|
for (int iteration = 0; iteration < 16; ++iteration)
|
||||||
|
{
|
||||||
|
int ymin = ((iteration % 2) == 0) ? 1 : N - 2;
|
||||||
|
int ymax = ((iteration % 2) == 0) ? N - 1 : 0;
|
||||||
|
int ystep = ((iteration % 2) == 0) ? 1 : -1;
|
||||||
|
|
||||||
|
for (int y = ymin; y != ymax; y += ystep)
|
||||||
|
{
|
||||||
|
for (int x = xmin; x < xmax; ++x)
|
||||||
|
{
|
||||||
|
// Velocity divergence
|
||||||
|
float divergence = (velocity_(wrap(x + 1), y)[0] - velocity_(wrap(x - 1), y)[0] + velocity_(x, y + 1)[1] - velocity_(x, y - 1)[1]) / 2.f;
|
||||||
|
|
||||||
|
// Gauss-Seidel iteration step
|
||||||
|
pressure_(x, y) = (pressure_(wrap(x - 1), y) + pressure_(wrap(x + 1), y) + pressure_(x, y - 1) + pressure_(x, y + 1) - divergence) / 4.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply boundary conditions for pressure
|
||||||
|
for (int i = 0; i < N; ++i)
|
||||||
|
{
|
||||||
|
if (!periodic_x)
|
||||||
|
{
|
||||||
|
pressure_(0, i) = pressure_(1, i);
|
||||||
|
pressure_(N - 1, i) = pressure_(N - 2, i);
|
||||||
|
}
|
||||||
|
pressure_(i, 0) = pressure_(i, 1);
|
||||||
|
pressure_(i, N - 1) = pressure_(i, N - 2);
|
||||||
|
}
|
||||||
|
if (!periodic_x)
|
||||||
|
{
|
||||||
|
pressure_(0, 0) = (pressure_(0, 1) + pressure_(1, 0)) / 2.f;
|
||||||
|
pressure_(N-1, 0) = (pressure_(N-1, 1) + pressure_(N-2, 0)) / 2.f;
|
||||||
|
pressure_(0, N-1) = (pressure_(0, N-2) + pressure_(1, N-2)) / 2.f;
|
||||||
|
pressure_(N-1, N-1) = (pressure_(N-1, N-2) + pressure_(N-2, N-1)) / 2.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize pressure
|
||||||
|
float average_pressure = 0.f;
|
||||||
|
for (auto const & value : pressure_)
|
||||||
|
average_pressure += value;
|
||||||
|
average_pressure /= (1.f * N * N);
|
||||||
|
for (auto & value : pressure_)
|
||||||
|
value -= average_pressure;
|
||||||
|
|
||||||
|
// Project velocity into divergence-free space
|
||||||
|
// by subtracting pressure gradient
|
||||||
|
for (int y = 1; y < N - 1; ++y)
|
||||||
|
{
|
||||||
|
for (int x = xmin; x < xmax; ++x)
|
||||||
|
{
|
||||||
|
// Pressure gradient
|
||||||
|
math::vector gradient{
|
||||||
|
(pressure_(wrap(x + 1), y) - pressure_(wrap(x - 1), y)) / 2.f,
|
||||||
|
(pressure_(x, y + 1) - pressure_(x, y - 1)) / 2.f
|
||||||
|
};
|
||||||
|
velocity_(x, y) -= gradient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply boundary conditions for velocity
|
||||||
|
for (int i = 0; i < N; ++i)
|
||||||
|
{
|
||||||
|
if (!periodic_x)
|
||||||
|
{
|
||||||
|
float left_boundary_flow = 0.f;//0.01f * std::sin((i * 1.f / N) * float(math::pi) * 4.f);
|
||||||
|
float right_boundary_flow = -left_boundary_flow;
|
||||||
|
|
||||||
|
velocity_(1, i)[0] = left_boundary_flow;
|
||||||
|
velocity_(N-2, i)[0] = right_boundary_flow;
|
||||||
|
|
||||||
|
velocity_(0, i)[0] = - velocity_(1, i)[0];
|
||||||
|
velocity_(N-1, i)[0] = - velocity_(N-2, i)[0];
|
||||||
|
}
|
||||||
|
velocity_(i, 0)[1] = -velocity_(i, 1)[1];
|
||||||
|
velocity_(i, N-2)[1] = -velocity_(i, N-2)[1];
|
||||||
|
|
||||||
|
// velocity_(i, 1)[0] = 0.01f;
|
||||||
|
// velocity_(i, N-2)[0] = 0.01f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uncomment to visualize the force field
|
||||||
|
// for (int y = 0; y < N; ++y)
|
||||||
|
// for (int x = 0; x < N; ++x)
|
||||||
|
// velocity_(x, y) = 100000.f * force_field_(x, y);
|
||||||
|
|
||||||
|
// Uncomment to visualize the terrain gradient field
|
||||||
|
// for (int y = 1; y < N - 1; ++y)
|
||||||
|
// {
|
||||||
|
// for (int x = 1; x < N - 1; ++x)
|
||||||
|
// {
|
||||||
|
// math::vector terrain_gradient
|
||||||
|
// {
|
||||||
|
// (terrain_(x + 1, y) - terrain_(x - 1, y)) / 2.f,
|
||||||
|
// (terrain_(x, y + 1) - terrain_(x, y - 1)) / 2.f,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// velocity_(x, y) = terrain_gradient;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Apply boundary conditions for humidity
|
||||||
|
for (int i = 0; i < N; ++i)
|
||||||
|
{
|
||||||
|
if (!periodic_x)
|
||||||
|
{
|
||||||
|
humidity_(0, i) = 0.f;
|
||||||
|
humidity_(N - 1, i) = 0.f;
|
||||||
|
}
|
||||||
|
humidity_(i, 0) = 0.f;
|
||||||
|
humidity_(i, N - 1) = 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
++frame_;
|
||||||
|
|
||||||
|
// Update all-time average temperature & precipitation
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
// float t = 1.f / frame_;
|
||||||
|
// float t = 1.f / std::min(8192, frame_);
|
||||||
|
float t = 1.f / std::min<float>(86400.f / dt, frame_); // year average
|
||||||
|
|
||||||
|
if (static_mode)
|
||||||
|
t = 1.f;
|
||||||
|
|
||||||
|
average_temperature_(x, y) = math::lerp(average_temperature_(x, y), temperature_(x, y), t);
|
||||||
|
average_precipitation_(x, y) = math::lerp(average_precipitation_(x, y), precipitation_(x, y), t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void present() override
|
||||||
|
{
|
||||||
|
gl::ClearColor(0.f, 0.f, 0.f, 0.f);
|
||||||
|
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
float const aspect_ratio = state().size[0] * 1.f / state().size[1];
|
||||||
|
math::box<float, 2> view_box = math::expand(simulation_box_, 1.f);
|
||||||
|
if (view_box[0].length() / view_box[1].length() > aspect_ratio)
|
||||||
|
view_box[1] = math::expand(view_box[1], (view_box[0].length() / aspect_ratio - view_box[1].length()) / 2.f);
|
||||||
|
else
|
||||||
|
view_box[0] = math::expand(view_box[0], (view_box[1].length() * aspect_ratio - view_box[0].length()) / 2.f);
|
||||||
|
|
||||||
|
std::optional<math::vector<int, 2>> mouseover_cell;
|
||||||
|
{
|
||||||
|
auto mouse = math::lerp(view_box, math::vector{state().mouse[0] * 1.f / state().size[0], 1.f - state().mouse[1] * 1.f / state().size[1]});
|
||||||
|
int x = std::floor(mouse[0]);
|
||||||
|
int y = std::floor(mouse[1]);
|
||||||
|
|
||||||
|
if (x >= 0 && x < N && y >= 0 && y < N)
|
||||||
|
mouseover_cell = {x, y};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mouseover_cell && (state().mouse_button_down.contains(app::mouse_button::left) || state().mouse_button_down.contains(app::mouse_button::right)))
|
||||||
|
{
|
||||||
|
float delta = state().mouse_button_down.contains(app::mouse_button::left) ? 1.f : -1.f;
|
||||||
|
|
||||||
|
int R = 4;
|
||||||
|
|
||||||
|
for (int iy = -R; iy <= R; ++iy)
|
||||||
|
{
|
||||||
|
for (int ix = -R; ix <= R; ++ix)
|
||||||
|
{
|
||||||
|
int x = (*mouseover_cell)[0] + ix;
|
||||||
|
int y = (*mouseover_cell)[1] + iy;
|
||||||
|
|
||||||
|
if (x >= 0 && x < N && y >= 0 && y < N)
|
||||||
|
{
|
||||||
|
auto d = math::vector<float, 2>{ix, iy} / 2.f;
|
||||||
|
terrain_(x, y) += delta * 0.05f * std::exp(- math::dot(d, d));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mouseover_cell && state().mouse_button_down.contains(app::mouse_button::middle))
|
||||||
|
{
|
||||||
|
int R = 4;
|
||||||
|
|
||||||
|
float average = 0.f;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (int iy = -R; iy <= R; ++iy)
|
||||||
|
{
|
||||||
|
for (int ix = -R; ix <= R; ++ix)
|
||||||
|
{
|
||||||
|
int x = (*mouseover_cell)[0] + ix;
|
||||||
|
int y = (*mouseover_cell)[1] + iy;
|
||||||
|
|
||||||
|
if (x >= 0 && x < N && y >= 0 && y < N)
|
||||||
|
{
|
||||||
|
average += terrain_(x, y);
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
average /= count;
|
||||||
|
|
||||||
|
for (int iy = -R; iy <= R; ++iy)
|
||||||
|
{
|
||||||
|
for (int ix = -R; ix <= R; ++ix)
|
||||||
|
{
|
||||||
|
int x = (*mouseover_cell)[0] + ix;
|
||||||
|
int y = (*mouseover_cell)[1] + iy;
|
||||||
|
|
||||||
|
if (x >= 0 && x < N && y >= 0 && y < N)
|
||||||
|
{
|
||||||
|
auto d = math::vector<float, 2>{ix, iy} / 2.f;
|
||||||
|
terrain_(x, y) += (average - terrain_(x, y)) * 0.05f * std::exp(- math::dot(d, d));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] float const pixel_size = view_box[0].length() / state().size[0];
|
||||||
|
|
||||||
|
auto map_color = [](float value, gfx::color_4f const & negative, gfx::color_4f const & positive){
|
||||||
|
return math::lerp(negative, positive, 1.f/ (1.f + std::exp(- value)));
|
||||||
|
};
|
||||||
|
|
||||||
|
auto map_temperature = [&](float value) {
|
||||||
|
return map_color(2.f * std::round(value / 20.f), {0.125f, 0.5f, 1.f, 0.75f}, {1.f, 0.5f, 0.125f, 0.75f});
|
||||||
|
};
|
||||||
|
|
||||||
|
auto map_biome = [this](float temperature, float precipitation)
|
||||||
|
{
|
||||||
|
auto x = math::clamp<int>(math::unlerp({ -3.f, 5.f}, precipitation) * biomes_map.width() , {0, biomes_map.width() - 1});
|
||||||
|
auto y = math::clamp<int>(math::unlerp({-10.f, 30.f}, temperature ) * biomes_map.height(), {0, biomes_map.height() - 1});
|
||||||
|
|
||||||
|
return gfx::to_colorf(biomes_map(x, y));
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
gfx::color_4f color = gfx::color_4f::zero();
|
||||||
|
|
||||||
|
if (show_land_ || show_biomes_)
|
||||||
|
{
|
||||||
|
if (!show_biomes_)
|
||||||
|
{
|
||||||
|
if (terrain_(x, y) <= 0.f)
|
||||||
|
color = {0.5f, 0.5f, 1.f, 1.f};
|
||||||
|
else
|
||||||
|
color = {1.f, 1.f, 1.f, 1.f};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (terrain_(x, y) <= 0.f)
|
||||||
|
color = map_color(8.f * terrain_(x, y), {0.f, 0.f, 0.125f, 1.f}, {0.f, 1.f, 1.5f, 1.f});
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float temperature = average_temperature_(x, y) - 273.f - std::max(0.f, terrain_(x, y)) * elevation_temperature_drop;
|
||||||
|
// float precipitation = (std::log10(std::max(1e-9f, average_precipitation_(x, y))) + 3.f) * 2.f;
|
||||||
|
// float precipitation = std::pow(average_precipitation_(x, y) * 125.f, 2.f) * 8.f;
|
||||||
|
float precipitation = std::log2(std::max(1e-9f, average_precipitation_(x, y)));
|
||||||
|
color = map_biome(temperature, precipitation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_land_ && x > 0 && x + 1 < N && y > 0 && y + 1 < N)
|
||||||
|
{
|
||||||
|
math::vector terrain_gradient
|
||||||
|
{
|
||||||
|
(terrain_(x + 1, y) - terrain_(x - 1, y)) / 2.f,
|
||||||
|
(terrain_(x, y + 1) - terrain_(x, y - 1)) / 2.f,
|
||||||
|
};
|
||||||
|
|
||||||
|
auto terrain_normal = math::normalized(math::vector{-terrain_gradient[0], -terrain_gradient[1], 0.125f});
|
||||||
|
|
||||||
|
float lightness = 0.5f + 0.5f * math::dot(terrain_normal, math::normalized(math::vector{1.f, 2.f, 3.f}));
|
||||||
|
|
||||||
|
color = gfx::dark(color, 1.f - lightness);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool const is_water = terrain_(x, y) <= 0.f;
|
||||||
|
|
||||||
|
if (show_temperature_)
|
||||||
|
color = map_temperature(temperature_(x, y) - 273.f);
|
||||||
|
|
||||||
|
if (show_temperature_delta_)
|
||||||
|
color = gfx::blend(color, map_color((temperature_(x, y) - expected_temperature_at(y, is_water)), {0.125f, 0.5f, 1.f, 0.75f}, {1.f, 0.5f, 0.125f, 0.75f}));
|
||||||
|
|
||||||
|
if (show_average_temperature_delta_)
|
||||||
|
color = gfx::blend(color, map_color((average_temperature_(x, y) - expected_temperature_at(y, is_water)), {0.125f, 0.5f, 1.f, 0.75f}, {1.f, 0.5f, 0.125f, 0.75f}));
|
||||||
|
|
||||||
|
if (show_pressure_)
|
||||||
|
color = gfx::blend(color, map_color(10000.f * pressure_(x, y), {0.f, 0.f, 1.f, 0.75f}, {1.f, 0.f, 0.f, 0.75f}));
|
||||||
|
|
||||||
|
if (show_water_vapor_)
|
||||||
|
color = gfx::blend(color, map_color(humidity_(x, y) * 0.00001f, {0.f, 1.f, 1.f, -0.75f}, {0.f, 1.f, 1.f, 0.75f}));
|
||||||
|
|
||||||
|
if (show_precipitation_)
|
||||||
|
{
|
||||||
|
// color = gfx::blend(color, map_color(precipitation_(x, y), {1.f, 1.f, 1.f, -1.f}, {1.f, 1.f, 1.f, 1.f}));
|
||||||
|
|
||||||
|
float alpha = 2.f / (1.f + std::exp(- 0.1f * precipitation_(x, y))) - 1.f;
|
||||||
|
|
||||||
|
gfx::color_4f cloud_color{1.f, 1.f, 1.f, alpha * 0.875f};
|
||||||
|
|
||||||
|
if (y > 0 && y + 1 < N)
|
||||||
|
{
|
||||||
|
if (periodic_x || (x > 0 && x + 1 < N))
|
||||||
|
{
|
||||||
|
math::vector gradient
|
||||||
|
{
|
||||||
|
(precipitation_(wrap(x + 1), y) - precipitation_(wrap(x - 1), y)) / 2.f,
|
||||||
|
(precipitation_(x, y + 1) - precipitation_(x, y - 1)) / 2.f,
|
||||||
|
};
|
||||||
|
|
||||||
|
auto normal = math::normalized(math::vector{-gradient[0], -gradient[1], 2.f});
|
||||||
|
auto lightness = 0.5f + 0.5f * math::dot(normal, math::normalized(math::vector{1.f, 2.f, 3.f}));
|
||||||
|
cloud_color = gfx::dark(cloud_color, 1.f - lightness);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
color = gfx::blend(color, cloud_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_average_precipitation_)
|
||||||
|
color = gfx::blend(color, map_color(average_precipitation_(x, y), {0.f, 1.f, 1.f, -0.75f}, {0.f, 1.f, 1.f, 0.75f}));
|
||||||
|
|
||||||
|
painter_.rect({{{x, x + 1.f}, {y, y + 1.f}}}, gfx::to_coloru8(color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_velocity_)
|
||||||
|
{
|
||||||
|
for (int y = 0; y < N; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < N; ++x)
|
||||||
|
{
|
||||||
|
math::point center{x + 0.5f, y + 0.5f};
|
||||||
|
auto v = velocity_(x, y);
|
||||||
|
auto color = gfx::color_4f::zero();
|
||||||
|
if (auto l = math::length(v); l > 0.f)
|
||||||
|
{
|
||||||
|
float const magnification = 200.f;
|
||||||
|
float const max_length = 1.5f;
|
||||||
|
v *= 0.5f * max_length * (1.f - std::exp(- magnification * l)) / l;
|
||||||
|
|
||||||
|
// color = gfx::lerp(gfx::color_4f{0.5f, 1.f, 0.f, 1.f}, gfx::color_4f{1.f, 0.f, 0.f, 1.f}, 1.f - std::exp(- 0.25f * magnification * l));
|
||||||
|
color = map_color(0.1f * (temperature_(x, y) - 273.f), {0.125f, 0.5f, 1.f, 1.f}, {1.f, 0.5f, 0.125f, 1.f});
|
||||||
|
}
|
||||||
|
auto n = math::ort(v) * 0.3f;
|
||||||
|
|
||||||
|
painter_.triangle(center - v - n, center - v + n, center + v, gfx::to_coloru8(color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto push_text = [&, row = 0](std::string const & text) mutable
|
||||||
|
{
|
||||||
|
painter_.text(view_box.corner(0.f, 1.f) - math::vector{0.f, row * pixel_size * 2.f * 12.f}, text, {.scale = {2.f * pixel_size, - 2.f * pixel_size}, .x = gfx::painter::x_align::left, .y = gfx::painter::y_align::top, .c = {255, 255, 255, 255}});
|
||||||
|
++row;
|
||||||
|
};
|
||||||
|
|
||||||
|
push_text(std::format("Frame {}", frame_));
|
||||||
|
push_text(std::format("Day {:.2f}", frame_ / (720.f / dt)));
|
||||||
|
|
||||||
|
if (mouseover_cell)
|
||||||
|
{
|
||||||
|
int x = (*mouseover_cell)[0];
|
||||||
|
int y = (*mouseover_cell)[1];
|
||||||
|
painter_.rect({{{x, x + 1.f}, {y, y + 1.f}}}, {255, 255, 255, 127});
|
||||||
|
|
||||||
|
push_text(std::format("{} {}", x, y));
|
||||||
|
push_text(std::format("V = {:.3f} {:.3f}", velocity_(x, y)[0] * 1000.f, velocity_(x, y)[1] * 1000.f));
|
||||||
|
push_text(std::format("P = {:.3f}", pressure_(x, y) * 1000.f));
|
||||||
|
push_text(std::format("T = {:.3f}", temperature_(x, y) - 273.f));
|
||||||
|
push_text(std::format("A = {:.3f}", average_temperature_(x, y) - 273.f));
|
||||||
|
push_text(std::format("E = {:.3f}", expected_temperature_at(y, terrain_(x, y) <= 0.f) - 273.f));
|
||||||
|
push_text(std::format("H = {:.3f}", terrain_(x, y)));
|
||||||
|
push_text(std::format("W = {:.3f}", humidity_(x, y)));
|
||||||
|
push_text(std::format("R = {:.3f}", precipitation_(x, y)));
|
||||||
|
push_text(std::format("AR= {:.3f}", average_precipitation_(x, y)));
|
||||||
|
}
|
||||||
|
|
||||||
|
painter_.render(math::orthographic_camera{view_box}.transform());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
gfx::painter painter_;
|
||||||
|
|
||||||
|
math::box<float, 2> simulation_box_;
|
||||||
|
|
||||||
|
bool paused_ = true;
|
||||||
|
bool show_velocity_ = true;
|
||||||
|
bool show_temperature_ = false;
|
||||||
|
bool show_temperature_delta_ = false;
|
||||||
|
bool show_average_temperature_delta_ = false;
|
||||||
|
bool show_pressure_ = false;
|
||||||
|
bool show_land_ = true;
|
||||||
|
bool show_biomes_ = true;
|
||||||
|
bool show_water_vapor_ = false;
|
||||||
|
bool show_precipitation_ = false;
|
||||||
|
bool show_average_precipitation_ = false;
|
||||||
|
|
||||||
|
util::ndarray<float, 2> terrain_;
|
||||||
|
util::ndarray<math::vector<float, 2>, 2> velocity_;
|
||||||
|
util::ndarray<math::vector<float, 2>, 2> new_velocity_;
|
||||||
|
util::ndarray<float, 2> pressure_;
|
||||||
|
util::ndarray<float, 2> vorticity_;
|
||||||
|
|
||||||
|
util::ndarray<float, 2> temperature_;
|
||||||
|
util::ndarray<float, 2> new_temperature_;
|
||||||
|
util::ndarray<float, 2> average_temperature_;
|
||||||
|
|
||||||
|
util::ndarray<float, 2> humidity_;
|
||||||
|
util::ndarray<float, 2> new_humidity_;
|
||||||
|
util::ndarray<float, 2> precipitation_;
|
||||||
|
util::ndarray<float, 2> average_precipitation_;
|
||||||
|
|
||||||
|
util::ndarray<math::vector<float, 2>, 2> force_field_main_;
|
||||||
|
util::ndarray<math::vector<float, 2>, 2> force_field_current_;
|
||||||
|
util::ndarray<math::vector<float, 2>, 2> force_field_next_;
|
||||||
|
|
||||||
|
int frame_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace psemek::app
|
||||||
|
{
|
||||||
|
|
||||||
|
std::unique_ptr<application::factory> make_application_factory()
|
||||||
|
{
|
||||||
|
return default_application_factory<weather_app>({.name = "Weather simulation test"});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
1265
examples/weather_v2.cpp
Normal file
|
|
@ -3,6 +3,7 @@
|
||||||
#include <psemek/app/event_handler.hpp>
|
#include <psemek/app/event_handler.hpp>
|
||||||
|
|
||||||
#if defined(PSEMEK_GRAPHICS_API_WEBGPU)
|
#if defined(PSEMEK_GRAPHICS_API_WEBGPU)
|
||||||
|
#include <psemek/wgpu/instance.hpp>
|
||||||
#include <psemek/wgpu/adapter.hpp>
|
#include <psemek/wgpu/adapter.hpp>
|
||||||
#include <psemek/wgpu/surface.hpp>
|
#include <psemek/wgpu/surface.hpp>
|
||||||
#include <psemek/wgpu/device.hpp>
|
#include <psemek/wgpu/device.hpp>
|
||||||
|
|
@ -24,9 +25,9 @@ namespace psemek::app
|
||||||
int multisampling = 4;
|
int multisampling = 4;
|
||||||
bool highdpi = false;
|
bool highdpi = false;
|
||||||
#if defined(PSEMEK_GRAPHICS_API_WEBGPU)
|
#if defined(PSEMEK_GRAPHICS_API_WEBGPU)
|
||||||
std::vector<wgpu::feature> required_features;
|
std::vector<wgpu::feature> required_features = {};
|
||||||
std::optional<wgpu::limits> required_limits;
|
std::optional<wgpu::limits> required_limits = {};
|
||||||
std::optional<wgpu::native_limits> required_native_limits;
|
std::optional<wgpu::native_limits> required_native_limits = {};
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -37,10 +38,12 @@ namespace psemek::app
|
||||||
std::function<void(bool)> show_cursor;
|
std::function<void(bool)> show_cursor;
|
||||||
std::function<void(bool)> relative_mouse_mode;
|
std::function<void(bool)> relative_mouse_mode;
|
||||||
std::function<void(bool)> windowed;
|
std::function<void(bool)> windowed;
|
||||||
|
std::function<void(bool)> text_input;
|
||||||
#if defined(PSEMEK_GRAPHICS_API_OPENGL)
|
#if defined(PSEMEK_GRAPHICS_API_OPENGL)
|
||||||
std::function<void(bool)> vsync;
|
std::function<void(bool)> vsync;
|
||||||
#endif
|
#endif
|
||||||
#if defined(PSEMEK_GRAPHICS_API_WEBGPU)
|
#if defined(PSEMEK_GRAPHICS_API_WEBGPU)
|
||||||
|
wgpu::instance instance;
|
||||||
wgpu::adapter adapter;
|
wgpu::adapter adapter;
|
||||||
wgpu::surface surface;
|
wgpu::surface surface;
|
||||||
wgpu::device device;
|
wgpu::device device;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ namespace psemek::app
|
||||||
void on_event(touch_up_event const &) override;
|
void on_event(touch_up_event const &) override;
|
||||||
void on_event(touch_move_event const &) override;
|
void on_event(touch_move_event const &) override;
|
||||||
void on_event(key_event const &) override;
|
void on_event(key_event const &) override;
|
||||||
|
void on_event(text_input_event const &) override;
|
||||||
|
|
||||||
void stop() override;
|
void stop() override;
|
||||||
bool running() const override;
|
bool running() const override;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ namespace psemek::app
|
||||||
virtual void on_event(touch_up_event const &) {}
|
virtual void on_event(touch_up_event const &) {}
|
||||||
virtual void on_event(touch_move_event const &) {}
|
virtual void on_event(touch_move_event const &) {}
|
||||||
virtual void on_event(key_event const &) {}
|
virtual void on_event(key_event const &) {}
|
||||||
|
virtual void on_event(text_input_event const &) {}
|
||||||
|
|
||||||
virtual ~event_handler() {}
|
virtual ~event_handler() {}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -61,4 +61,7 @@ namespace psemek::app
|
||||||
state.key_down.erase(event.key);
|
state.key_down.erase(event.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void apply(event_state &, text_input_event const &)
|
||||||
|
{}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
#include <psemek/math/point.hpp>
|
#include <psemek/math/point.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace psemek::app
|
namespace psemek::app
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
@ -172,4 +174,9 @@ namespace psemek::app
|
||||||
bool down;
|
bool down;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct text_input_event
|
||||||
|
{
|
||||||
|
std::string input;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ namespace psemek::app
|
||||||
void on_event(touch_up_event const &) override;
|
void on_event(touch_up_event const &) override;
|
||||||
void on_event(touch_move_event const &) override;
|
void on_event(touch_move_event const &) override;
|
||||||
void on_event(key_event const &) override;
|
void on_event(key_event const &) override;
|
||||||
|
void on_event(text_input_event const &) override;
|
||||||
|
|
||||||
void stop() override;
|
void stop() override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,9 @@ namespace psemek::app
|
||||||
apply(state_, event);
|
apply(state_, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void application_base::on_event(text_input_event const &)
|
||||||
|
{}
|
||||||
|
|
||||||
void application_base::stop()
|
void application_base::stop()
|
||||||
{
|
{
|
||||||
running_ = false;
|
running_ = false;
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,11 @@ namespace psemek::app
|
||||||
on_event_impl(event);
|
on_event_impl(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void scene_application::on_event(text_input_event const & event)
|
||||||
|
{
|
||||||
|
on_event_impl(event);
|
||||||
|
}
|
||||||
|
|
||||||
void scene_application::stop()
|
void scene_application::stop()
|
||||||
{
|
{
|
||||||
if (auto scene = current_scene())
|
if (auto scene = current_scene())
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,8 @@ file(GLOB_RECURSE PSEMEK_ASYNC_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "s
|
||||||
|
|
||||||
psemek_add_library(psemek-async ${PSEMEK_ASYNC_HEADERS} ${PSEMEK_ASYNC_SOURCES})
|
psemek_add_library(psemek-async ${PSEMEK_ASYNC_HEADERS} ${PSEMEK_ASYNC_SOURCES})
|
||||||
target_include_directories(psemek-async PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
target_include_directories(psemek-async PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||||
target_link_libraries(psemek-async PUBLIC psemek-log psemek-util)
|
target_link_libraries(psemek-async PUBLIC
|
||||||
|
psemek-log
|
||||||
|
psemek-util
|
||||||
|
psemek-prof
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ namespace psemek::async
|
||||||
|
|
||||||
void stop() override;
|
void stop() override;
|
||||||
|
|
||||||
|
void clear() override;
|
||||||
|
|
||||||
void wait() override;
|
void wait() override;
|
||||||
|
|
||||||
void wait_for(clock::duration period) override;
|
void wait_for(clock::duration period) override;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
#include <psemek/util/function.hpp>
|
#include <psemek/util/function.hpp>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace psemek::async
|
namespace psemek::async
|
||||||
{
|
{
|
||||||
|
|
@ -30,6 +29,9 @@ namespace psemek::async
|
||||||
// NB: the executor must call stop() from destructor.
|
// NB: the executor must call stop() from destructor.
|
||||||
virtual void stop() = 0;
|
virtual void stop() = 0;
|
||||||
|
|
||||||
|
// Clear the pending tasks queue.
|
||||||
|
virtual void clear() = 0;
|
||||||
|
|
||||||
// Wait for all the tasks to be executed.
|
// Wait for all the tasks to be executed.
|
||||||
// May take forever.
|
// May take forever.
|
||||||
virtual void wait() = 0;
|
virtual void wait() = 0;
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,18 @@ namespace psemek::async
|
||||||
using type = util::function<void()>;
|
using type = util::function<void()>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct future_result_type
|
||||||
|
{
|
||||||
|
using type = T &;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct future_result_type<void>
|
||||||
|
{
|
||||||
|
using type = void;
|
||||||
|
};
|
||||||
|
|
||||||
struct cancel_token
|
struct cancel_token
|
||||||
{};
|
{};
|
||||||
|
|
||||||
|
|
@ -55,7 +67,8 @@ namespace psemek::async
|
||||||
std::weak_ptr<cancel_token> weak_cancel;
|
std::weak_ptr<cancel_token> weak_cancel;
|
||||||
|
|
||||||
std::mutex then_mutex;
|
std::mutex then_mutex;
|
||||||
typename then_function<T>::type then_func;
|
std::vector<typename then_function<T>::type> then_funcs;
|
||||||
|
std::vector<util::function<void(std::exception_ptr)>> then_exception_funcs;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -85,6 +98,7 @@ namespace psemek::async
|
||||||
using result_type = T;
|
using result_type = T;
|
||||||
|
|
||||||
future() = default;
|
future() = default;
|
||||||
|
future(future const &) = default;
|
||||||
future(future &&) = default;
|
future(future &&) = default;
|
||||||
|
|
||||||
future(std::shared_ptr<detail::task_state<T>> state, std::shared_ptr<detail::cancel_token> cancel)
|
future(std::shared_ptr<detail::task_state<T>> state, std::shared_ptr<detail::cancel_token> cancel)
|
||||||
|
|
@ -92,6 +106,7 @@ namespace psemek::async
|
||||||
, cancel_(std::move(cancel))
|
, cancel_(std::move(cancel))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
future & operator = (future const &) = default;
|
||||||
future & operator = (future &&) = default;
|
future & operator = (future &&) = default;
|
||||||
|
|
||||||
~future()
|
~future()
|
||||||
|
|
@ -136,7 +151,7 @@ namespace psemek::async
|
||||||
return has_value_unsafe();
|
return has_value_unsafe();
|
||||||
}
|
}
|
||||||
|
|
||||||
T get()
|
typename detail::future_result_type<T>::type get()
|
||||||
{
|
{
|
||||||
if (!state_)
|
if (!state_)
|
||||||
throw empty_future_error{};
|
throw empty_future_error{};
|
||||||
|
|
@ -148,7 +163,7 @@ namespace psemek::async
|
||||||
if constexpr (std::is_same_v<T, void>)
|
if constexpr (std::is_same_v<T, void>)
|
||||||
return;
|
return;
|
||||||
else
|
else
|
||||||
return std::move(*(state_->value));
|
return *(state_->value);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
std::rethrow_exception(state_->exception);
|
std::rethrow_exception(state_->exception);
|
||||||
|
|
@ -239,8 +254,10 @@ namespace psemek::async
|
||||||
state_->value = value;
|
state_->value = value;
|
||||||
}
|
}
|
||||||
state_->value_cv.notify_all();
|
state_->value_cv.notify_all();
|
||||||
if (state_->then_func)
|
|
||||||
state_->then_func(*state_->value);
|
std::lock_guard lock{state_->then_mutex};
|
||||||
|
for (auto & func : state_->then_funcs)
|
||||||
|
func(*state_->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_value(T && value)
|
void set_value(T && value)
|
||||||
|
|
@ -252,8 +269,10 @@ namespace psemek::async
|
||||||
state_->value = std::move(value);
|
state_->value = std::move(value);
|
||||||
}
|
}
|
||||||
state_->value_cv.notify_all();
|
state_->value_cv.notify_all();
|
||||||
if (state_->then_func)
|
|
||||||
state_->then_func(*state_->value);
|
std::lock_guard lock{state_->then_mutex};
|
||||||
|
for (auto & func : state_->then_funcs)
|
||||||
|
func(*state_->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_exception(std::exception_ptr e)
|
void set_exception(std::exception_ptr e)
|
||||||
|
|
@ -263,6 +282,10 @@ namespace psemek::async
|
||||||
std::lock_guard lock{state_->value_mutex};
|
std::lock_guard lock{state_->value_mutex};
|
||||||
if (state_->value || state_->exception) throw satisfied_promise_error{};
|
if (state_->value || state_->exception) throw satisfied_promise_error{};
|
||||||
state_->exception = std::move(e);
|
state_->exception = std::move(e);
|
||||||
|
|
||||||
|
std::lock_guard lock_then{state_->then_mutex};
|
||||||
|
for (auto & func : state_->then_exception_funcs)
|
||||||
|
func(state_->exception);
|
||||||
}
|
}
|
||||||
state_->value_cv.notify_all();
|
state_->value_cv.notify_all();
|
||||||
}
|
}
|
||||||
|
|
@ -324,8 +347,10 @@ namespace psemek::async
|
||||||
state_->value = true;
|
state_->value = true;
|
||||||
}
|
}
|
||||||
state_->value_cv.notify_all();
|
state_->value_cv.notify_all();
|
||||||
if (state_->then_func)
|
|
||||||
state_->then_func();
|
std::lock_guard lock{state_->then_mutex};
|
||||||
|
for (auto & func : state_->then_funcs)
|
||||||
|
func();
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_exception(std::exception_ptr e)
|
void set_exception(std::exception_ptr e)
|
||||||
|
|
@ -335,6 +360,10 @@ namespace psemek::async
|
||||||
std::lock_guard lock{state_->value_mutex};
|
std::lock_guard lock{state_->value_mutex};
|
||||||
if (state_->value || state_->exception) throw satisfied_promise_error{};
|
if (state_->value || state_->exception) throw satisfied_promise_error{};
|
||||||
state_->exception = std::move(e);
|
state_->exception = std::move(e);
|
||||||
|
|
||||||
|
std::lock_guard lock_then{state_->then_mutex};
|
||||||
|
for (auto & func : state_->then_exception_funcs)
|
||||||
|
func(state_->exception);
|
||||||
}
|
}
|
||||||
state_->value_cv.notify_all();
|
state_->value_cv.notify_all();
|
||||||
}
|
}
|
||||||
|
|
@ -362,6 +391,14 @@ namespace psemek::async
|
||||||
return p.get_future();
|
return p.get_future();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
future<T> make_ready_future(T const & x)
|
||||||
|
{
|
||||||
|
promise<T> p;
|
||||||
|
p.set_value(x);
|
||||||
|
return p.get_future();
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Signature>
|
template <typename Signature>
|
||||||
struct packaged_task;
|
struct packaged_task;
|
||||||
|
|
||||||
|
|
@ -488,7 +525,7 @@ namespace psemek::async
|
||||||
auto fut = t.get_future();
|
auto fut = t.get_future();
|
||||||
|
|
||||||
std::lock_guard lock{state_->then_mutex};
|
std::lock_guard lock{state_->then_mutex};
|
||||||
state_->then_func = std::move(t);
|
state_->then_funcs.push_back(std::move(t));
|
||||||
return fut;
|
return fut;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -498,7 +535,7 @@ namespace psemek::async
|
||||||
auto fut = t.get_future();
|
auto fut = t.get_future();
|
||||||
|
|
||||||
std::lock_guard lock{state_->then_mutex};
|
std::lock_guard lock{state_->then_mutex};
|
||||||
state_->then_func = std::move(t);
|
state_->then_funcs.push_back(std::move(t));
|
||||||
return fut;
|
return fut;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ namespace psemek::async
|
||||||
|
|
||||||
void stop() override;
|
void stop() override;
|
||||||
|
|
||||||
|
void clear() override;
|
||||||
|
|
||||||
void wait() override;
|
void wait() override;
|
||||||
|
|
||||||
void wait_for(clock::duration period) override;
|
void wait_for(clock::duration period) override;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
#include <psemek/util/thread.hpp>
|
#include <psemek/util/thread.hpp>
|
||||||
#include <psemek/util/synchronyzed_queue.hpp>
|
#include <psemek/util/synchronyzed_queue.hpp>
|
||||||
|
|
||||||
#include <future>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
@ -32,6 +31,8 @@ namespace psemek::async
|
||||||
|
|
||||||
void stop() override;
|
void stop() override;
|
||||||
|
|
||||||
|
void clear() override;
|
||||||
|
|
||||||
void wait() override;
|
void wait() override;
|
||||||
|
|
||||||
void wait_for(clock::duration period) override;
|
void wait_for(clock::duration period) override;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
#include <psemek/async/event_loop.hpp>
|
#include <psemek/async/event_loop.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace psemek::async
|
namespace psemek::async
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
@ -20,6 +18,11 @@ namespace psemek::async
|
||||||
queue_.clear();
|
queue_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void event_loop::clear()
|
||||||
|
{
|
||||||
|
queue_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void event_loop::wait()
|
void event_loop::wait()
|
||||||
{
|
{
|
||||||
while (!queue_.empty())
|
while (!queue_.empty())
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
#include <psemek/async/synchronous_executor.hpp>
|
#include <psemek/async/synchronous_executor.hpp>
|
||||||
#include <psemek/util/exception.hpp>
|
#include <psemek/util/exception.hpp>
|
||||||
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
namespace psemek::async
|
namespace psemek::async
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
@ -19,6 +17,9 @@ namespace psemek::async
|
||||||
void synchronous_executor::stop()
|
void synchronous_executor::stop()
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
void synchronous_executor::clear()
|
||||||
|
{}
|
||||||
|
|
||||||
void synchronous_executor::wait()
|
void synchronous_executor::wait()
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,13 @@
|
||||||
#include <psemek/util/unused.hpp>
|
#include <psemek/util/unused.hpp>
|
||||||
#include <psemek/util/to_string.hpp>
|
#include <psemek/util/to_string.hpp>
|
||||||
#include <psemek/util/exception.hpp>
|
#include <psemek/util/exception.hpp>
|
||||||
|
#include <psemek/prof/profiler.hpp>
|
||||||
|
|
||||||
#include <psemek/log/log.hpp>
|
#include <psemek/log/log.hpp>
|
||||||
|
|
||||||
namespace psemek::async
|
namespace psemek::async
|
||||||
{
|
{
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
|
|
||||||
struct stop_execution{};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
threadpool::threadpool(std::string const & name, std::size_t thread_count)
|
threadpool::threadpool(std::string const & name, std::size_t thread_count)
|
||||||
: working_count_{0}
|
: working_count_{0}
|
||||||
{
|
{
|
||||||
|
|
@ -25,10 +19,13 @@ namespace psemek::async
|
||||||
threads_.emplace_back([this, tname = std::move(tname)]() mutable
|
threads_.emplace_back([this, tname = std::move(tname)]() mutable
|
||||||
{
|
{
|
||||||
log::thread_registrator reg(std::move(tname));
|
log::thread_registrator reg(std::move(tname));
|
||||||
for (bool running = true; running;)
|
while (true)
|
||||||
{
|
{
|
||||||
auto task = task_queue_.pop();
|
auto task = task_queue_.pop();
|
||||||
|
|
||||||
|
if (!task)
|
||||||
|
break;
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard lock{working_count_mutex_};
|
std::lock_guard lock{working_count_mutex_};
|
||||||
++working_count_;
|
++working_count_;
|
||||||
|
|
@ -36,12 +33,9 @@ namespace psemek::async
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
prof::profiler prof("task");
|
||||||
task();
|
task();
|
||||||
}
|
}
|
||||||
catch (stop_execution const &)
|
|
||||||
{
|
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
catch (util::exception const & e)
|
catch (util::exception const & e)
|
||||||
{
|
{
|
||||||
log::error() << "Unhandled exception in threadpool executor: " << e;
|
log::error() << "Unhandled exception in threadpool executor: " << e;
|
||||||
|
|
@ -86,11 +80,16 @@ namespace psemek::async
|
||||||
for (auto const & thread: threads_)
|
for (auto const & thread: threads_)
|
||||||
{
|
{
|
||||||
unused(thread);
|
unused(thread);
|
||||||
task_queue_.push([]{ throw stop_execution{}; });
|
task_queue_.push(nullptr);
|
||||||
}
|
}
|
||||||
threads_.clear();
|
threads_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void threadpool::clear()
|
||||||
|
{
|
||||||
|
task_queue_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void threadpool::wait()
|
void threadpool::wait()
|
||||||
{
|
{
|
||||||
std::unique_lock lock{working_count_mutex_};
|
std::unique_lock lock{working_count_mutex_};
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,12 @@ namespace psemek::audio
|
||||||
|
|
||||||
stream_ptr stream() const
|
stream_ptr stream() const
|
||||||
{
|
{
|
||||||
return std::atomic_load(&stream_);
|
return stream_.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
stream_ptr stream(stream_ptr new_stream)
|
stream_ptr stream(stream_ptr new_stream)
|
||||||
{
|
{
|
||||||
return std::atomic_exchange(&stream_, std::move(new_stream));
|
return stream_.exchange(std::move(new_stream));
|
||||||
}
|
}
|
||||||
|
|
||||||
stream_ptr stop()
|
stream_ptr stop()
|
||||||
|
|
@ -37,7 +37,7 @@ namespace psemek::audio
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
stream_ptr stream_;
|
std::atomic<stream_ptr> stream_;
|
||||||
};
|
};
|
||||||
|
|
||||||
using channel_ptr = std::shared_ptr<channel>;
|
using channel_ptr = std::shared_ptr<channel>;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,3 @@ file(GLOB_RECURSE PSEMEK_BT_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "sour
|
||||||
psemek_add_library(psemek-bt ${PSEMEK_BT_HEADERS} ${PSEMEK_BT_SOURCES})
|
psemek_add_library(psemek-bt ${PSEMEK_BT_HEADERS} ${PSEMEK_BT_SOURCES})
|
||||||
target_include_directories(psemek-bt PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
target_include_directories(psemek-bt PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||||
target_link_libraries(psemek-bt PUBLIC psemek-util psemek-log)
|
target_link_libraries(psemek-bt PUBLIC psemek-util psemek-log)
|
||||||
if(PSEMEK_BT_LOG)
|
|
||||||
target_compile_definitions(psemek-bt PUBLIC -DPSEMEK_BT_LOG=1)
|
|
||||||
endif()
|
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,7 @@ namespace psemek::bt
|
||||||
{
|
{
|
||||||
if (condition_result_)
|
if (condition_result_)
|
||||||
{
|
{
|
||||||
if (*condition_result_)
|
return current_node()->update(dt, args...);
|
||||||
return true_branch_->update(dt, args...);
|
|
||||||
else
|
|
||||||
return false_branch_->update(dt, args...);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -51,11 +48,8 @@ namespace psemek::bt
|
||||||
if (auto finished = std::get_if<typename node_type::finished>(&result))
|
if (auto finished = std::get_if<typename node_type::finished>(&result))
|
||||||
{
|
{
|
||||||
condition_result_ = finished->result;
|
condition_result_ = finished->result;
|
||||||
if (*condition_result_)
|
current_node()->start(args...);
|
||||||
true_branch_->start(args...);
|
return current_node()->update(Time{}, args...);
|
||||||
else
|
|
||||||
false_branch_->start(args...);
|
|
||||||
return running{};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -71,10 +65,7 @@ namespace psemek::bt
|
||||||
{
|
{
|
||||||
if (condition_result_)
|
if (condition_result_)
|
||||||
{
|
{
|
||||||
if (*condition_result_)
|
return current_node()->event(event, args...);
|
||||||
return true_branch_->event(event, args...);
|
|
||||||
else
|
|
||||||
return false_branch_->event(event, args...);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return condition_->event(event, args...);
|
return condition_->event(event, args...);
|
||||||
|
|
@ -85,6 +76,11 @@ namespace psemek::bt
|
||||||
node_ptr<tree_type> true_branch_;
|
node_ptr<tree_type> true_branch_;
|
||||||
node_ptr<tree_type> false_branch_;
|
node_ptr<tree_type> false_branch_;
|
||||||
std::optional<bool> condition_result_;
|
std::optional<bool> condition_result_;
|
||||||
|
|
||||||
|
node<tree_type> * current_node()
|
||||||
|
{
|
||||||
|
return *condition_result_ ? true_branch_.get() : false_branch_.get();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Tree>
|
template <typename Tree>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#ifdef PSEMEK_BT_LOG
|
|
||||||
#include <psemek/log/log.hpp>
|
|
||||||
#define bt_log ::psemek::log::debug
|
|
||||||
#else
|
|
||||||
#include <psemek/util/null_ostream.hpp>
|
|
||||||
#define bt_log ::psemek::util::null_ostream
|
|
||||||
#endif
|
|
||||||
|
|
@ -32,7 +32,7 @@ namespace psemek::bt
|
||||||
|
|
||||||
status update(Time dt, Args ... args) override
|
status update(Time dt, Args ... args) override
|
||||||
{
|
{
|
||||||
if (current_index_ < children_.size())
|
while (current_index_ < children_.size())
|
||||||
{
|
{
|
||||||
if (!current_started_)
|
if (!current_started_)
|
||||||
{
|
{
|
||||||
|
|
@ -41,13 +41,14 @@ namespace psemek::bt
|
||||||
}
|
}
|
||||||
|
|
||||||
auto result = children_[current_index_]->update(dt, args...);
|
auto result = children_[current_index_]->update(dt, args...);
|
||||||
|
dt = Time{};
|
||||||
if (auto f = std::get_if<finished>(&result))
|
if (auto f = std::get_if<finished>(&result))
|
||||||
{
|
{
|
||||||
if (f->result)
|
if (f->result)
|
||||||
return finished{true};
|
return finished{true};
|
||||||
++current_index_;
|
++current_index_;
|
||||||
current_started_ = false;
|
current_started_ = false;
|
||||||
return running{};
|
continue;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ namespace psemek::bt
|
||||||
|
|
||||||
status update(Time dt, Args ... args) override
|
status update(Time dt, Args ... args) override
|
||||||
{
|
{
|
||||||
if (current_index_ < children_.size())
|
while (current_index_ < children_.size())
|
||||||
{
|
{
|
||||||
if (!current_started_)
|
if (!current_started_)
|
||||||
{
|
{
|
||||||
|
|
@ -41,13 +41,14 @@ namespace psemek::bt
|
||||||
}
|
}
|
||||||
|
|
||||||
auto result = children_[current_index_]->update(dt, args...);
|
auto result = children_[current_index_]->update(dt, args...);
|
||||||
|
dt = Time{};
|
||||||
if (auto f = std::get_if<finished>(&result))
|
if (auto f = std::get_if<finished>(&result))
|
||||||
{
|
{
|
||||||
if (!f->result)
|
if (!f->result)
|
||||||
return finished{false};
|
return finished{false};
|
||||||
++current_index_;
|
++current_index_;
|
||||||
current_started_ = false;
|
current_started_ = false;
|
||||||
return running{};
|
continue;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,9 @@ namespace psemek::cg
|
||||||
template <typename Iterator>
|
template <typename Iterator>
|
||||||
kdtree(Iterator begin, Iterator end);
|
kdtree(Iterator begin, Iterator end);
|
||||||
|
|
||||||
|
template <typename Iterator>
|
||||||
|
void assign(Iterator begin, Iterator end);
|
||||||
|
|
||||||
bool empty() const { return nodes_.empty(); }
|
bool empty() const { return nodes_.empty(); }
|
||||||
|
|
||||||
bool insert(value_type && value);
|
bool insert(value_type && value);
|
||||||
|
|
@ -84,11 +87,19 @@ namespace psemek::cg
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
bool remove(point_type const & point) const;
|
bool remove(point_type const & point) const;
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
// TODO: alternative non-const version that allows modifying value.data
|
// TODO: alternative non-const version that allows modifying value.data
|
||||||
value_type const * find(point_type const & point) const;
|
value_type const * find(point_type const & point) const;
|
||||||
|
|
||||||
value_type const & closest(point_type const & target);
|
value_type const & closest(point_type const & target) const;
|
||||||
|
|
||||||
|
template <typename Iterator>
|
||||||
|
Iterator closer_than(point_type const & target, scalar_type max_distance, Iterator out) const;
|
||||||
|
|
||||||
|
template <typename Callback>
|
||||||
|
void closer_than_map(point_type const & target, scalar_type max_distance, Callback && callback) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
|
@ -106,6 +117,7 @@ namespace psemek::cg
|
||||||
{
|
{
|
||||||
value_type value;
|
value_type value;
|
||||||
node_id children[2] {null, null};
|
node_id children[2] {null, null};
|
||||||
|
std::uint32_t size = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<node> nodes_;
|
std::vector<node> nodes_;
|
||||||
|
|
@ -116,6 +128,12 @@ namespace psemek::cg
|
||||||
bool insert_impl(value_type && value, node_id id, std::uint32_t split_axis);
|
bool insert_impl(value_type && value, node_id id, std::uint32_t split_axis);
|
||||||
|
|
||||||
value_type const * closest_impl(point_type const & target, scalar_type & best_distance_sqr, node_id id, std::uint32_t split_axis) const;
|
value_type const * closest_impl(point_type const & target, scalar_type & best_distance_sqr, node_id id, std::uint32_t split_axis) const;
|
||||||
|
|
||||||
|
template <typename Iterator>
|
||||||
|
Iterator closer_than_impl(point_type const & target, scalar_type max_distance_sqr, Iterator out, node_id id, std::uint32_t split_axis) const;
|
||||||
|
|
||||||
|
template <typename Callback>
|
||||||
|
void closer_than_map_impl(point_type const & target, scalar_type max_distance_sqr, Callback & callback, node_id id, std::uint32_t split_axis) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, std::size_t N, typename Data>
|
template <typename T, std::size_t N, typename Data>
|
||||||
|
|
@ -125,6 +143,14 @@ namespace psemek::cg
|
||||||
build_node_impl(begin, end, 0);
|
build_node_impl(begin, end, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N, typename Data>
|
||||||
|
template <typename Iterator>
|
||||||
|
void kdtree<T, N, Data>::assign(Iterator begin, Iterator end)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
build_node_impl(begin, end, 0);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t N, typename Data>
|
template <typename T, std::size_t N, typename Data>
|
||||||
bool kdtree<T, N, Data>::insert(value_type && value)
|
bool kdtree<T, N, Data>::insert(value_type && value)
|
||||||
{
|
{
|
||||||
|
|
@ -132,7 +158,13 @@ namespace psemek::cg
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t N, typename Data>
|
template <typename T, std::size_t N, typename Data>
|
||||||
kdtree<T, N, Data>::value_type const & kdtree<T, N, Data>::closest(point_type const & target)
|
void kdtree<T, N, Data>::clear()
|
||||||
|
{
|
||||||
|
nodes_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N, typename Data>
|
||||||
|
kdtree<T, N, Data>::value_type const & kdtree<T, N, Data>::closest(point_type const & target) const
|
||||||
{
|
{
|
||||||
if (nodes_.empty())
|
if (nodes_.empty())
|
||||||
throw util::exception("empty kdtree");
|
throw util::exception("empty kdtree");
|
||||||
|
|
@ -141,6 +173,26 @@ namespace psemek::cg
|
||||||
return *closest_impl(target, best_distance_sqr, 0, 0);
|
return *closest_impl(target, best_distance_sqr, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N, typename Data>
|
||||||
|
template <typename Iterator>
|
||||||
|
Iterator kdtree<T, N, Data>::closer_than(point_type const & target, scalar_type max_distance, Iterator out) const
|
||||||
|
{
|
||||||
|
if (nodes_.empty())
|
||||||
|
return out;
|
||||||
|
|
||||||
|
return closer_than_impl(target, math::sqr(max_distance), out, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N, typename Data>
|
||||||
|
template <typename Callback>
|
||||||
|
void kdtree<T, N, Data>::closer_than_map(point_type const & target, scalar_type max_distance, Callback && callback) const
|
||||||
|
{
|
||||||
|
if (nodes_.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
return closer_than_map_impl(target, math::sqr(max_distance), callback, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t N, typename Data>
|
template <typename T, std::size_t N, typename Data>
|
||||||
template <typename Iterator>
|
template <typename Iterator>
|
||||||
kdtree<T, N, Data>::node_id kdtree<T, N, Data>::build_node_impl(Iterator begin, Iterator end, std::uint32_t split_axis)
|
kdtree<T, N, Data>::node_id kdtree<T, N, Data>::build_node_impl(Iterator begin, Iterator end, std::uint32_t split_axis)
|
||||||
|
|
@ -160,6 +212,7 @@ namespace psemek::cg
|
||||||
auto result = static_cast<node_id>(nodes_.size());
|
auto result = static_cast<node_id>(nodes_.size());
|
||||||
auto & node = nodes_.emplace_back();
|
auto & node = nodes_.emplace_back();
|
||||||
node.value = std::move(*middle);
|
node.value = std::move(*middle);
|
||||||
|
node.size = (end - begin);
|
||||||
|
|
||||||
nodes_[result].children[0] = build_node_impl(begin, middle, next_axis(split_axis));
|
nodes_[result].children[0] = build_node_impl(begin, middle, next_axis(split_axis));
|
||||||
nodes_[result].children[1] = build_node_impl(std::next(middle), end, next_axis(split_axis));
|
nodes_[result].children[1] = build_node_impl(std::next(middle), end, next_axis(split_axis));
|
||||||
|
|
@ -174,6 +227,7 @@ namespace psemek::cg
|
||||||
{
|
{
|
||||||
auto & node = nodes_.emplace_back();
|
auto & node = nodes_.emplace_back();
|
||||||
node.value = std::move(value);
|
node.value = std::move(value);
|
||||||
|
node.size = 1;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,7 +238,7 @@ namespace psemek::cg
|
||||||
if (node_point == point)
|
if (node_point == point)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
int child = (node.point[split_axis] < point[split_axis]) ? 0 : 1;
|
int child = (point[split_axis] < node_point[split_axis]) ? 0 : 1;
|
||||||
|
|
||||||
if (node.children[child] == null)
|
if (node.children[child] == null)
|
||||||
{
|
{
|
||||||
|
|
@ -193,10 +247,18 @@ namespace psemek::cg
|
||||||
|
|
||||||
auto & child_node = nodes_.emplace_back();
|
auto & child_node = nodes_.emplace_back();
|
||||||
child_node.value = std::move(value);
|
child_node.value = std::move(value);
|
||||||
|
child_node.size = 1;
|
||||||
|
nodes_[id].size += 1;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return insert_impl(std::move(value), node.children[child], next_axis(split_axis));
|
if (insert_impl(std::move(value), node.children[child], next_axis(split_axis)))
|
||||||
|
{
|
||||||
|
nodes_[id].size += 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t N, typename Data>
|
template <typename T, std::size_t N, typename Data>
|
||||||
|
|
@ -224,4 +286,46 @@ namespace psemek::cg
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N, typename Data>
|
||||||
|
template <typename Iterator>
|
||||||
|
Iterator kdtree<T, N, Data>::closer_than_impl(point_type const & target, scalar_type max_distance_sqr, Iterator out, node_id id, std::uint32_t split_axis) const
|
||||||
|
{
|
||||||
|
auto const & node = nodes_[id];
|
||||||
|
auto const & node_point = detail::get_point(node.value);
|
||||||
|
|
||||||
|
if (math::distance_sqr(node_point, target) < max_distance_sqr)
|
||||||
|
*out++ = node.value;
|
||||||
|
|
||||||
|
auto delta = target[split_axis] - node_point[split_axis];
|
||||||
|
auto delta_sqr = math::sqr(delta);
|
||||||
|
|
||||||
|
if (node.children[0] != null && (delta < 0 || delta_sqr < max_distance_sqr))
|
||||||
|
out = closer_than_impl(target, max_distance_sqr, out, node.children[0], next_axis(split_axis));
|
||||||
|
|
||||||
|
if (node.children[1] != null && (delta >= 0 || delta_sqr < max_distance_sqr))
|
||||||
|
out = closer_than_impl(target, max_distance_sqr, out, node.children[1], next_axis(split_axis));
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N, typename Data>
|
||||||
|
template <typename Callback>
|
||||||
|
void kdtree<T, N, Data>::closer_than_map_impl(point_type const & target, scalar_type max_distance_sqr, Callback & callback, node_id id, std::uint32_t split_axis) const
|
||||||
|
{
|
||||||
|
auto const & node = nodes_[id];
|
||||||
|
auto const & node_point = detail::get_point(node.value);
|
||||||
|
|
||||||
|
if (math::distance_sqr(node_point, target) < max_distance_sqr)
|
||||||
|
callback(node.value);
|
||||||
|
|
||||||
|
auto delta = target[split_axis] - node_point[split_axis];
|
||||||
|
auto delta_sqr = math::sqr(delta);
|
||||||
|
|
||||||
|
if (node.children[0] != null && (delta < 0 || delta_sqr < max_distance_sqr))
|
||||||
|
closer_than_map_impl(target, max_distance_sqr, callback, node.children[0], next_axis(split_axis));
|
||||||
|
|
||||||
|
if (node.children[1] != null && (delta >= 0 || delta_sqr < max_distance_sqr))
|
||||||
|
closer_than_map_impl(target, max_distance_sqr, callback, node.children[1], next_axis(split_axis));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,8 @@ namespace psemek::ecs
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - Fully document which functions can be called from which callbacks
|
// - Fully document which functions can be called from which callbacks
|
||||||
// - Modification callbacks implementation (const-only)
|
|
||||||
// - Tables serialization
|
// - Tables serialization
|
||||||
// - Refactor query caches
|
// - Refactor query caches
|
||||||
// - Index API
|
|
||||||
// - Index implementation
|
|
||||||
|
|
||||||
struct container
|
struct container
|
||||||
{
|
{
|
||||||
|
|
@ -82,6 +79,9 @@ namespace psemek::ecs
|
||||||
*/
|
*/
|
||||||
void destroy(handle entity);
|
void destroy(handle entity);
|
||||||
|
|
||||||
|
/** Same as destroy(), but via finally() */
|
||||||
|
void destroy_finally(handle entity);
|
||||||
|
|
||||||
/** Get an accessor for an entity, which provides access to the entity's components.
|
/** Get an accessor for an entity, which provides access to the entity's components.
|
||||||
* It is designed to be a single-use object; it cannot be stored as a reference to
|
* It is designed to be a single-use object; it cannot be stored as a reference to
|
||||||
* the entity. Use handle for that instead.
|
* the entity. Use handle for that instead.
|
||||||
|
|
@ -172,6 +172,10 @@ namespace psemek::ecs
|
||||||
template <typename ... Components>
|
template <typename ... Components>
|
||||||
void detach(handle entity);
|
void detach(handle entity);
|
||||||
|
|
||||||
|
/** Same as detach(), but via finally() */
|
||||||
|
template <typename ... Components>
|
||||||
|
void detach_finally(handle entity);
|
||||||
|
|
||||||
/** Create a query cache that can be used to speed up `apply()` calls.
|
/** Create a query cache that can be used to speed up `apply()` calls.
|
||||||
*
|
*
|
||||||
* The constness of the component types is ignored.
|
* The constness of the component types is ignored.
|
||||||
|
|
@ -296,12 +300,7 @@ namespace psemek::ecs
|
||||||
* The constructor function must have the same signature as a function
|
* The constructor function must have the same signature as a function
|
||||||
* passed to the `apply<Components...>()` call.
|
* passed to the `apply<Components...>()` call.
|
||||||
*
|
*
|
||||||
* The constuctor is not considered to be a modification of the entity, i.e. it
|
|
||||||
* doesn't trigger modification callbacks.
|
|
||||||
*
|
|
||||||
* @param function A function to be applied to created entity's components
|
* @param function A function to be applied to created entity's components
|
||||||
* @return An ownerwhip token; destroying this token removes
|
|
||||||
* the constructor from this container
|
|
||||||
* @warning If any two of the passed component types are equal, the call fails with
|
* @warning If any two of the passed component types are equal, the call fails with
|
||||||
* a compilation error
|
* a compilation error
|
||||||
* @warning If the constructor modifies the entity's archetype (i.e. attaches or
|
* @warning If the constructor modifies the entity's archetype (i.e. attaches or
|
||||||
|
|
@ -335,11 +334,7 @@ namespace psemek::ecs
|
||||||
*
|
*
|
||||||
* Note that there is no way to cancel the entity's destruction.
|
* Note that there is no way to cancel the entity's destruction.
|
||||||
*
|
*
|
||||||
* The destructor is not considered to be a modification of the entity, i.e. it
|
|
||||||
* doesn't trigger modification callbacks.
|
|
||||||
*
|
|
||||||
* @param function A function to be applied to a to-be destroyed entity's components
|
* @param function A function to be applied to a to-be destroyed entity's components
|
||||||
* @return An ownerwhip token; destroying it removes the destructor from the container
|
|
||||||
* @warning If any two of the passed component types are equal, the call fails with
|
* @warning If any two of the passed component types are equal, the call fails with
|
||||||
* a compilation error
|
* a compilation error
|
||||||
* @warning If the destructor modifies the entity's archetype (i.e. attaches or
|
* @warning If the destructor modifies the entity's archetype (i.e. attaches or
|
||||||
|
|
@ -349,45 +344,36 @@ namespace psemek::ecs
|
||||||
template <typename ... Components, typename Function>
|
template <typename ... Components, typename Function>
|
||||||
void destructor(Function && function);
|
void destructor(Function && function);
|
||||||
|
|
||||||
/** Register a component modification callback. Each time an entity that has
|
/** Call a callback after exiting all currently executing ECS container methods.
|
||||||
* the specified set of components is modified, and the modification affects
|
|
||||||
* one of this callback's component types, the callback is called.
|
|
||||||
*
|
*
|
||||||
* The component types can be const-qualified, in which case the corresponding function must
|
* If no ECS container method is currently executed, call the callback immediately instead.
|
||||||
* also accept the corresponding components by a const reference.
|
|
||||||
*
|
*
|
||||||
* The component types can be equal to ecs::without<Component>, indicating that entities having
|
* The function must have one of the following signatures:
|
||||||
* this component type will not be watched by this callback. These component types are not
|
* void()
|
||||||
* included in the called function signature.
|
* void(container)
|
||||||
*
|
*
|
||||||
* If the modification occurred via an accessor, the callback is called
|
* This method is meant to be called from inside other callbacks (typically
|
||||||
* after the accessor is destroyed, allowing for transaction-like modification.
|
* constructors and destructors) and can be used to add extra modifications
|
||||||
|
* to entities that don't clash with other callbacks and archetype changes.
|
||||||
*
|
*
|
||||||
* If the modification occurred via an `apply()` or `batch_apply()` call,
|
* Generally, if a constructor, destructor, apply or batch apply function
|
||||||
* the callback is called immediately after this call.
|
* changes the archetype of some entities, it should do so using `finally`
|
||||||
|
* instead of doing that directly.
|
||||||
*
|
*
|
||||||
* Note that modifications from within a modification callback are not considered
|
* @param function A callback to be called before the topmost currently executing
|
||||||
* as modifications, i.e. they don't recursively invoke modification callbacks.
|
* ECS container method returns.
|
||||||
*
|
* @warning If a callback adds new callbacks via `finally`, those will also get executed,
|
||||||
* The callback function must have the same signature as a function
|
* which potentially leads to an infinite loop.
|
||||||
* passed to the `apply<Components...>()` call.
|
|
||||||
*
|
|
||||||
* @param function A function to be applied to modified entity's components
|
|
||||||
* @return An ownerwhip token; destroying it removes the modification callback from the container
|
|
||||||
* @warning If any two of the passed component types are equal, the call fails with
|
|
||||||
* a compilation error
|
|
||||||
* @warning If the modification callback modifies the entity's archetype (i.e. attaches or
|
|
||||||
* detaches components), the behavior is undefined
|
|
||||||
*
|
|
||||||
* TODO: can we allow the callback to modify the archetype?
|
|
||||||
*/
|
*/
|
||||||
// TODO: implement
|
template <typename Function>
|
||||||
template <typename ... Components, typename Function>
|
void finally(Function && function);
|
||||||
void watch(Function && function);
|
|
||||||
|
|
||||||
template <typename Index, typename ... Args>
|
template <typename Index, typename ... Args>
|
||||||
Index & index(Args && ... args);
|
Index & index(Args && ... args);
|
||||||
|
|
||||||
|
template <typename Index, typename Factory>
|
||||||
|
Index & index_factory(Factory && factory);
|
||||||
|
|
||||||
template <typename ... Components>
|
template <typename ... Components>
|
||||||
std::size_t memory_usage();
|
std::size_t memory_usage();
|
||||||
|
|
||||||
|
|
@ -412,10 +398,18 @@ namespace psemek::ecs
|
||||||
util::object_pool<std::vector<util::uuid>> uuid_list_pool_;
|
util::object_pool<std::vector<util::uuid>> uuid_list_pool_;
|
||||||
util::object_pool<util::hash_set<util::uuid>> uuid_set_pool_;
|
util::object_pool<util::hash_set<util::uuid>> uuid_set_pool_;
|
||||||
|
|
||||||
|
std::size_t method_recursion_depth_ = 0;
|
||||||
|
std::vector<util::function<void(container &)>> finally_callbacks_;
|
||||||
|
|
||||||
|
#ifdef PSEMEK_DEBUG
|
||||||
|
util::hash_set<ecs::handle> currently_changing_archetype_;
|
||||||
|
#endif
|
||||||
|
|
||||||
detail::table * insert_table(std::vector<std::unique_ptr<detail::column>> columns);
|
detail::table * insert_table(std::vector<std::unique_ptr<detail::column>> columns);
|
||||||
void do_destroy(handle entity);
|
void do_destroy(handle entity);
|
||||||
void remove_row(detail::table & table, std::uint32_t row, util::span<detail::entity_data> entities);
|
void remove_row(detail::table & table, std::uint32_t row, util::span<detail::entity_data> entities);
|
||||||
void finalize_iteration(detail::table & table);
|
void finalize_iteration(detail::table & table);
|
||||||
|
void finalize_method();
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Component>
|
template <typename Component>
|
||||||
|
|
@ -429,6 +423,8 @@ namespace psemek::ecs
|
||||||
{
|
{
|
||||||
static_assert(detail::all_different_types_v<std::remove_cvref_t<Components>...>, "all component types must be different");
|
static_assert(detail::all_different_types_v<std::remove_cvref_t<Components>...>, "all component types must be different");
|
||||||
|
|
||||||
|
++method_recursion_depth_;
|
||||||
|
|
||||||
(register_component<std::remove_cvref_t<Components>>(), ...);
|
(register_component<std::remove_cvref_t<Components>>(), ...);
|
||||||
|
|
||||||
detail::component_uuid_helper<std::remove_cvref_t<Components>...> uuids;
|
detail::component_uuid_helper<std::remove_cvref_t<Components>...> uuids;
|
||||||
|
|
@ -449,6 +445,9 @@ namespace psemek::ecs
|
||||||
auto row = table->row_count();
|
auto row = table->row_count();
|
||||||
auto id = entity_list_.create(table, row);
|
auto id = entity_list_.create(table, row);
|
||||||
handle handle{id, entity_list_.get_entities()[id].epoch};
|
handle handle{id, entity_list_.get_entities()[id].epoch};
|
||||||
|
#ifdef PSEMEK_DEBUG
|
||||||
|
currently_changing_archetype_.insert(handle);
|
||||||
|
#endif
|
||||||
[[maybe_unused]] accessor accessor = get(handle);
|
[[maybe_unused]] accessor accessor = get(handle);
|
||||||
|
|
||||||
table->push_row(handle);
|
table->push_row(handle);
|
||||||
|
|
@ -456,6 +455,14 @@ namespace psemek::ecs
|
||||||
|
|
||||||
table->trigger_constructors(*this, row);
|
table->trigger_constructors(*this, row);
|
||||||
|
|
||||||
|
#ifdef PSEMEK_DEBUG
|
||||||
|
currently_changing_archetype_.erase(handle);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
finalize_method();
|
||||||
|
|
||||||
|
--method_recursion_depth_;
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -464,6 +471,13 @@ namespace psemek::ecs
|
||||||
{
|
{
|
||||||
static_assert(detail::all_different_types_v<std::remove_cvref_t<Components>...>, "all component types must be different");
|
static_assert(detail::all_different_types_v<std::remove_cvref_t<Components>...>, "all component types must be different");
|
||||||
|
|
||||||
|
#ifdef PSEMEK_DEBUG
|
||||||
|
assert(!currently_changing_archetype_.contains(entity));
|
||||||
|
currently_changing_archetype_.insert(entity);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
++method_recursion_depth_;
|
||||||
|
|
||||||
(register_component<Components>(), ...);
|
(register_component<Components>(), ...);
|
||||||
|
|
||||||
auto uuids = uuid_list_pool_.get();
|
auto uuids = uuid_list_pool_.get();
|
||||||
|
|
@ -479,6 +493,9 @@ namespace psemek::ecs
|
||||||
for (auto const & uuid : attached_uuid_set)
|
for (auto const & uuid : attached_uuid_set)
|
||||||
uuids.push_back(uuid);
|
uuids.push_back(uuid);
|
||||||
|
|
||||||
|
if (archetype_changed)
|
||||||
|
data->table->trigger_destructors(*this, data->row, attached_uuid_set, {});
|
||||||
|
|
||||||
auto table = table_container_.get(uuids);
|
auto table = table_container_.get(uuids);
|
||||||
|
|
||||||
if (!table)
|
if (!table)
|
||||||
|
|
@ -508,13 +525,21 @@ namespace psemek::ecs
|
||||||
((accessor.get<std::remove_cvref_t<Components>>() = std::forward<Components>(components)), ...);
|
((accessor.get<std::remove_cvref_t<Components>>() = std::forward<Components>(components)), ...);
|
||||||
|
|
||||||
if (archetype_changed)
|
if (archetype_changed)
|
||||||
table->trigger_constructors(*this, data->row, attached_uuid_set);
|
table->trigger_constructors(*this, data->row, attached_uuid_set, {});
|
||||||
|
|
||||||
attached_uuid_set.clear();
|
attached_uuid_set.clear();
|
||||||
uuids.clear();
|
uuids.clear();
|
||||||
|
|
||||||
uuid_set_pool_.put(std::move(attached_uuid_set));
|
uuid_set_pool_.put(std::move(attached_uuid_set));
|
||||||
uuid_list_pool_.put(std::move(uuids));
|
uuid_list_pool_.put(std::move(uuids));
|
||||||
|
|
||||||
|
#ifdef PSEMEK_DEBUG
|
||||||
|
currently_changing_archetype_.erase(entity);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
finalize_method();
|
||||||
|
|
||||||
|
--method_recursion_depth_;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ... Components>
|
template <typename ... Components>
|
||||||
|
|
@ -522,6 +547,13 @@ namespace psemek::ecs
|
||||||
{
|
{
|
||||||
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
|
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
|
||||||
|
|
||||||
|
#ifdef PSEMEK_DEBUG
|
||||||
|
assert(!currently_changing_archetype_.contains(entity));
|
||||||
|
currently_changing_archetype_.insert(entity);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
++method_recursion_depth_;
|
||||||
|
|
||||||
auto detached_uuid_set = uuid_set_pool_.get();
|
auto detached_uuid_set = uuid_set_pool_.get();
|
||||||
(detached_uuid_set.insert(std::remove_const_t<Components>::uuid()), ...);
|
(detached_uuid_set.insert(std::remove_const_t<Components>::uuid()), ...);
|
||||||
|
|
||||||
|
|
@ -530,10 +562,9 @@ namespace psemek::ecs
|
||||||
auto * data = entity_list_.get_entities().begin() + entity.id;
|
auto * data = entity_list_.get_entities().begin() + entity.id;
|
||||||
for (auto const & column : data->table->columns())
|
for (auto const & column : data->table->columns())
|
||||||
{
|
{
|
||||||
if (detached_uuid_set.contains(column->uuid()))
|
auto const column_uuid = column->uuid();
|
||||||
detached_uuid_set.insert(column->uuid());
|
if (!detached_uuid_set.contains(column_uuid))
|
||||||
else
|
uuids.push_back(column_uuid);
|
||||||
uuids.push_back(column->uuid());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool const archetype_changed = sizeof...(Components) > 0;
|
bool const archetype_changed = sizeof...(Components) > 0;
|
||||||
|
|
@ -541,7 +572,7 @@ namespace psemek::ecs
|
||||||
auto table = table_container_.get(uuids);
|
auto table = table_container_.get(uuids);
|
||||||
|
|
||||||
if (archetype_changed)
|
if (archetype_changed)
|
||||||
data->table->trigger_destructors(*this, data->row, detached_uuid_set);
|
data->table->trigger_destructors(*this, data->row, {}, detached_uuid_set);
|
||||||
|
|
||||||
// Destructors could lead to reallocation of entity list
|
// Destructors could lead to reallocation of entity list
|
||||||
data = entity_list_.get_entities().begin() + entity.id;
|
data = entity_list_.get_entities().begin() + entity.id;
|
||||||
|
|
@ -568,12 +599,32 @@ namespace psemek::ecs
|
||||||
data->row = new_row;
|
data->row = new_row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (archetype_changed)
|
||||||
|
table->trigger_constructors(*this, data->row, {}, detached_uuid_set);
|
||||||
|
|
||||||
detached_uuid_set.clear();
|
detached_uuid_set.clear();
|
||||||
uuids.clear();
|
uuids.clear();
|
||||||
detached_uuid_set.clear();
|
detached_uuid_set.clear();
|
||||||
|
|
||||||
uuid_list_pool_.put(std::move(uuids));
|
uuid_list_pool_.put(std::move(uuids));
|
||||||
uuid_set_pool_.put(std::move(detached_uuid_set));
|
uuid_set_pool_.put(std::move(detached_uuid_set));
|
||||||
|
|
||||||
|
#ifdef PSEMEK_DEBUG
|
||||||
|
currently_changing_archetype_.erase(entity);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
finalize_method();
|
||||||
|
|
||||||
|
--method_recursion_depth_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ... Components>
|
||||||
|
void container::detach_finally(handle entity)
|
||||||
|
{
|
||||||
|
finally([entity](container & world){
|
||||||
|
if (world.alive(entity))
|
||||||
|
world.detach<Components...>(entity);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ... Components>
|
template <typename ... Components>
|
||||||
|
|
@ -590,6 +641,8 @@ namespace psemek::ecs
|
||||||
{
|
{
|
||||||
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
|
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
|
||||||
|
|
||||||
|
++method_recursion_depth_;
|
||||||
|
|
||||||
using invocable_type = typename detail::filter_with<detail::invocable, std::tuple<Components...>, Function>::type;
|
using invocable_type = typename detail::filter_with<detail::invocable, std::tuple<Components...>, Function>::type;
|
||||||
|
|
||||||
static_assert(invocable_type::value, "function is not invocable with these components");
|
static_assert(invocable_type::value, "function is not invocable with these components");
|
||||||
|
|
@ -617,6 +670,10 @@ namespace psemek::ecs
|
||||||
finalize_iteration(*entry.table);
|
finalize_iteration(*entry.table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finalize_method();
|
||||||
|
|
||||||
|
--method_recursion_depth_;
|
||||||
|
|
||||||
return cache;
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -625,6 +682,8 @@ namespace psemek::ecs
|
||||||
{
|
{
|
||||||
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
|
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
|
||||||
|
|
||||||
|
++method_recursion_depth_;
|
||||||
|
|
||||||
using invocable_type = typename detail::filter_with<detail::batch_invocable, std::tuple<Components...>, Function>::type;
|
using invocable_type = typename detail::filter_with<detail::batch_invocable, std::tuple<Components...>, Function>::type;
|
||||||
|
|
||||||
static_assert(invocable_type::value, "function is not batch-invocable with these components");
|
static_assert(invocable_type::value, "function is not batch-invocable with these components");
|
||||||
|
|
@ -649,6 +708,10 @@ namespace psemek::ecs
|
||||||
finalize_iteration(*entry.table);
|
finalize_iteration(*entry.table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finalize_method();
|
||||||
|
|
||||||
|
--method_recursion_depth_;
|
||||||
|
|
||||||
return cache;
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -664,9 +727,12 @@ namespace psemek::ecs
|
||||||
auto id = next_constructor_id_++;
|
auto id = next_constructor_id_++;
|
||||||
|
|
||||||
auto constructor_factory = [function = std::move(function)](std::vector<std::uint32_t> const & column_indices) -> detail::table_callback {
|
auto constructor_factory = [function = std::move(function)](std::vector<std::uint32_t> const & column_indices) -> detail::table_callback {
|
||||||
return [function, column_indices](container & container, detail::table & table, std::uint32_t row, util::hash_set<util::uuid> const & attached_components, bool force){
|
return [function, column_indices](container & container, detail::table & table, std::uint32_t row, util::hash_set<util::uuid> const & attached_components, util::hash_set<util::uuid> const & detached_components, bool force){
|
||||||
|
|
||||||
bool const invoke = force || (detail::contains_helper<Components>::contains(attached_components) || ...);
|
bool const invoke = force
|
||||||
|
|| (detail::contains_helper<Components>::contains(attached_components) || ...)
|
||||||
|
|| (detail::contains_helper<Components>::contains_without(detached_components) || ...)
|
||||||
|
;
|
||||||
if (!invoke)
|
if (!invoke)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -699,9 +765,11 @@ namespace psemek::ecs
|
||||||
auto id = next_destructor_id_++;
|
auto id = next_destructor_id_++;
|
||||||
|
|
||||||
auto destructor_factory = [function = std::move(function)](std::vector<std::uint32_t> const & column_indices) -> detail::table_callback {
|
auto destructor_factory = [function = std::move(function)](std::vector<std::uint32_t> const & column_indices) -> detail::table_callback {
|
||||||
return [function, column_indices](container & container, detail::table & table, std::uint32_t row, util::hash_set<util::uuid> const & detached_components, bool force){
|
return [function, column_indices](container & container, detail::table & table, std::uint32_t row, util::hash_set<util::uuid> const & attached_components, util::hash_set<util::uuid> const & detached_components, bool force){
|
||||||
|
bool const invoke = force
|
||||||
bool const invoke = force || (detail::contains_helper<Components>::contains(detached_components) || ...);
|
|| (detail::contains_helper<Components>::contains_without(attached_components) || ...)
|
||||||
|
|| (detail::contains_helper<Components>::contains(detached_components) || ...)
|
||||||
|
;
|
||||||
if (!invoke)
|
if (!invoke)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -722,12 +790,37 @@ namespace psemek::ecs
|
||||||
entry.table->add_destructor({id, destructor_factory(entry.columns_indices)});
|
entry.table->add_destructor({id, destructor_factory(entry.columns_indices)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Function>
|
||||||
|
void container::finally(Function && function)
|
||||||
|
{
|
||||||
|
util::function<void(container &)> wrapper;
|
||||||
|
if constexpr (std::is_invocable_v<Function, container &>)
|
||||||
|
{
|
||||||
|
wrapper = std::move(function);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wrapper = [function = std::move(function)](ecs::container &){ function(); };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method_recursion_depth_ == 0)
|
||||||
|
wrapper(*this);
|
||||||
|
else
|
||||||
|
finally_callbacks_.push_back(std::move(wrapper));
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Index, typename ... Args>
|
template <typename Index, typename ... Args>
|
||||||
Index & container::index(Args && ... args)
|
Index & container::index(Args && ... args)
|
||||||
{
|
{
|
||||||
return index_container_.get<Index>(*this, std::forward<Args>(args)...);
|
return index_container_.get<Index>(*this, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Index, typename Factory>
|
||||||
|
Index & container::index_factory(Factory && factory)
|
||||||
|
{
|
||||||
|
return index_container_.set<Index>(std::move(factory));
|
||||||
|
}
|
||||||
|
|
||||||
template <typename ... Components>
|
template <typename ... Components>
|
||||||
std::size_t container::memory_usage()
|
std::size_t container::memory_usage()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ namespace psemek::ecs
|
||||||
|
|
||||||
struct table;
|
struct table;
|
||||||
|
|
||||||
using table_callback = util::function<void(container &, table &, std::uint32_t, util::hash_set<util::uuid> const &, bool)>;
|
using table_callback = util::function<void(container &, table &, std::uint32_t, util::hash_set<util::uuid> const &, util::hash_set<util::uuid> const &, bool)>;
|
||||||
|
|
||||||
struct ordered_table_callback
|
struct ordered_table_callback
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include <psemek/util/hash_table.hpp>
|
#include <psemek/util/hash_table.hpp>
|
||||||
#include <psemek/util/type_name.hpp>
|
#include <psemek/util/type_name.hpp>
|
||||||
#include <psemek/util/exception.hpp>
|
#include <psemek/util/exception.hpp>
|
||||||
|
#include <psemek/util/function.hpp>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
|
@ -24,19 +25,21 @@ namespace psemek::ecs::detail
|
||||||
{
|
{
|
||||||
auto uuid = Index::uuid();
|
auto uuid = Index::uuid();
|
||||||
if (auto it = storage_.find(uuid); it != storage_.end())
|
if (auto it = storage_.find(uuid); it != storage_.end())
|
||||||
return *reinterpret_cast<Index *>(it->second.get());
|
return *reinterpret_cast<Index *>(it->second());
|
||||||
|
|
||||||
if constexpr (std::is_constructible_v<Index, ecs::container &, Args && ...>)
|
if constexpr (std::is_constructible_v<Index, ecs::container &, Args && ...>)
|
||||||
{
|
{
|
||||||
auto ptr = std::make_shared<Index>(container, std::forward<Args>(args)...);
|
auto ptr = std::make_unique<Index>(container, std::forward<Args>(args)...);
|
||||||
storage_.insert({uuid, ptr});
|
auto result = ptr.get();
|
||||||
return *ptr;
|
storage_.insert({uuid, [ptr = std::move(ptr)]{ return ptr.get(); }});
|
||||||
|
return *result;
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_constructible_v<Index, Args && ...>)
|
else if constexpr (std::is_constructible_v<Index, Args && ...>)
|
||||||
{
|
{
|
||||||
auto ptr = std::make_shared<Index>(std::forward<Args>(args)...);
|
auto ptr = std::make_unique<Index>(std::forward<Args>(args)...);
|
||||||
storage_.insert({uuid, ptr});
|
auto result = ptr.get();
|
||||||
return *ptr;
|
storage_.insert({uuid, [ptr = std::move(ptr)]{ return ptr.get(); }});
|
||||||
|
return *result;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -44,8 +47,20 @@ namespace psemek::ecs::detail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Index, typename Factory>
|
||||||
|
Index & set(Factory && factory)
|
||||||
|
{
|
||||||
|
auto uuid = Index::uuid();
|
||||||
|
if (storage_.contains(uuid))
|
||||||
|
throw util::exception("Index " + util::type_name<Index>() + " is already set");
|
||||||
|
|
||||||
|
auto result = factory();
|
||||||
|
storage_[Index::uuid()] = std::move(factory);
|
||||||
|
return *result;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
util::hash_map<util::uuid, std::shared_ptr<void>> storage_;
|
util::hash_map<util::uuid, util::function<void*()>> storage_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,10 +74,10 @@ namespace psemek::ecs::detail
|
||||||
void add_destructor(ordered_table_callback callback);
|
void add_destructor(ordered_table_callback callback);
|
||||||
|
|
||||||
void trigger_constructors(container & container, std::uint32_t row);
|
void trigger_constructors(container & container, std::uint32_t row);
|
||||||
void trigger_constructors(container & container, std::uint32_t row, util::hash_set<util::uuid> const & attached_components);
|
void trigger_constructors(container & container, std::uint32_t row, util::hash_set<util::uuid> const & attached_components, util::hash_set<util::uuid> const & detached_components);
|
||||||
|
|
||||||
void trigger_destructors(container & container, std::uint32_t row);
|
void trigger_destructors(container & container, std::uint32_t row);
|
||||||
void trigger_destructors(container & container, std::uint32_t row, util::hash_set<util::uuid> const & detached_components);
|
void trigger_destructors(container & container, std::uint32_t row, util::hash_set<util::uuid> const & attached_components, util::hash_set<util::uuid> const & detached_components);
|
||||||
|
|
||||||
std::size_t memory_usage() const;
|
std::size_t memory_usage() const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,12 @@ namespace psemek::ecs::detail
|
||||||
{
|
{
|
||||||
return container.contains(Component::uuid());
|
return container.contains(Component::uuid());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Container>
|
||||||
|
static bool contains_without(Container const &)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Component>
|
template <typename Component>
|
||||||
|
|
@ -97,6 +103,12 @@ namespace psemek::ecs::detail
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Container>
|
||||||
|
static bool contains_without(Container const & container)
|
||||||
|
{
|
||||||
|
return container.contains(Component::uuid());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,19 @@
|
||||||
namespace psemek::ecs
|
namespace psemek::ecs
|
||||||
{
|
{
|
||||||
|
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
|
||||||
|
struct default_context_factory
|
||||||
|
{
|
||||||
|
int operator()() const noexcept
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
struct dispatcher
|
struct dispatcher
|
||||||
{
|
{
|
||||||
dispatcher() = default;
|
dispatcher() = default;
|
||||||
|
|
@ -42,12 +55,14 @@ namespace psemek::ecs
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Event, typename ... Components, typename System>
|
template <typename Event, typename ... Components, typename System, typename ContextFactory = detail::default_context_factory>
|
||||||
void system(System system)
|
void system(System system, ContextFactory && context_factory = ContextFactory{})
|
||||||
{
|
{
|
||||||
handlers_[Event::uuid()].push_back([system = std::move(system), this, cache = container_->cache<Components...>()](void const * event_ptr) mutable {
|
handlers_[Event::uuid()].push_back([system = std::move(system), this, cache = container_->cache<Components...>(), context_factory = std::move(context_factory)](void const * event_ptr) mutable {
|
||||||
auto const & event = *reinterpret_cast<Event const *>(event_ptr);
|
auto const & event = *reinterpret_cast<Event const *>(event_ptr);
|
||||||
|
|
||||||
|
[[maybe_unused]] auto context = context_factory();
|
||||||
|
|
||||||
container_->apply<Components...>([&]<typename ... FilteredComponents>(ecs::container & container, ecs::handle entity, FilteredComponents & ... filtered_components){
|
container_->apply<Components...>([&]<typename ... FilteredComponents>(ecs::container & container, ecs::handle entity, FilteredComponents & ... filtered_components){
|
||||||
if constexpr (std::invocable<System, Event const &, ecs::container &, handle, FilteredComponents & ...>)
|
if constexpr (std::invocable<System, Event const &, ecs::container &, handle, FilteredComponents & ...>)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include <psemek/ecs/detail/id.hpp>
|
#include <psemek/ecs/detail/id.hpp>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
namespace psemek::ecs
|
namespace psemek::ecs
|
||||||
{
|
{
|
||||||
|
|
@ -46,4 +47,18 @@ namespace std
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
struct formatter<::psemek::ecs::handle, Char>
|
||||||
|
{
|
||||||
|
constexpr auto parse(std::format_parse_context & ctx)
|
||||||
|
{
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto format(::psemek::ecs::handle const & handle, std::format_context & ctx) const
|
||||||
|
{
|
||||||
|
return std::format_to(ctx.out(), "({},{})", handle.id, handle.epoch);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#include <psemek/ecs/container.hpp>
|
#include <psemek/ecs/container.hpp>
|
||||||
#include <psemek/util/assert.hpp>
|
#include <psemek/util/assert.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace psemek::ecs
|
namespace psemek::ecs
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
@ -11,13 +13,36 @@ namespace psemek::ecs
|
||||||
|
|
||||||
void container::destroy(handle entity)
|
void container::destroy(handle entity)
|
||||||
{
|
{
|
||||||
|
#ifdef PSEMEK_DEBUG
|
||||||
|
assert(!currently_changing_archetype_.contains(entity));
|
||||||
|
currently_changing_archetype_.insert(entity);
|
||||||
|
#endif
|
||||||
|
|
||||||
assert(alive(entity));
|
assert(alive(entity));
|
||||||
|
|
||||||
|
++method_recursion_depth_;
|
||||||
|
|
||||||
auto const data = entity_list_.get_entities()[entity.id];
|
auto const data = entity_list_.get_entities()[entity.id];
|
||||||
data.table->trigger_destructors(*this, data.row);
|
data.table->trigger_destructors(*this, data.row);
|
||||||
|
|
||||||
do_destroy(entity);
|
do_destroy(entity);
|
||||||
entity_list_.destroy(entity.id);
|
entity_list_.destroy(entity.id);
|
||||||
|
|
||||||
|
#ifdef PSEMEK_DEBUG
|
||||||
|
currently_changing_archetype_.erase(entity);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
finalize_method();
|
||||||
|
|
||||||
|
--method_recursion_depth_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void container::destroy_finally(handle entity)
|
||||||
|
{
|
||||||
|
finally([entity](container & world){
|
||||||
|
if (world.alive(entity))
|
||||||
|
world.destroy(entity);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
accessor container::get(handle entity)
|
accessor container::get(handle entity)
|
||||||
|
|
@ -50,6 +75,8 @@ namespace psemek::ecs
|
||||||
|
|
||||||
std::optional<handle> container::try_clone(handle entity)
|
std::optional<handle> container::try_clone(handle entity)
|
||||||
{
|
{
|
||||||
|
++method_recursion_depth_;
|
||||||
|
|
||||||
auto const data = entity_list_.get_entities()[entity.id];
|
auto const data = entity_list_.get_entities()[entity.id];
|
||||||
if (!data.table->non_copyable_components().empty())
|
if (!data.table->non_copyable_components().empty())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
@ -66,6 +93,10 @@ namespace psemek::ecs
|
||||||
|
|
||||||
table->trigger_constructors(*this, row);
|
table->trigger_constructors(*this, row);
|
||||||
|
|
||||||
|
finalize_method();
|
||||||
|
|
||||||
|
--method_recursion_depth_;
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,6 +154,21 @@ namespace psemek::ecs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void container::finalize_method()
|
||||||
|
{
|
||||||
|
if (method_recursion_depth_ > 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (!finally_callbacks_.empty())
|
||||||
|
{
|
||||||
|
auto callbacks = std::move(finally_callbacks_);
|
||||||
|
finally_callbacks_.clear();
|
||||||
|
|
||||||
|
for (auto & callback : callbacks)
|
||||||
|
callback(*this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::size_t container::entity_count()
|
std::size_t container::entity_count()
|
||||||
{
|
{
|
||||||
return entity_list_.size();
|
return entity_list_.size();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#include <psemek/ecs/detail/table.hpp>
|
#include <psemek/ecs/detail/table.hpp>
|
||||||
#include <psemek/ecs/detail/unordered_component_hash.hpp>
|
#include <psemek/ecs/detail/unordered_component_hash.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace psemek::ecs::detail
|
namespace psemek::ecs::detail
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
@ -159,25 +161,25 @@ namespace psemek::ecs::detail
|
||||||
void table::trigger_constructors(container & container, std::uint32_t row)
|
void table::trigger_constructors(container & container, std::uint32_t row)
|
||||||
{
|
{
|
||||||
for (auto const & callback : *constructors_)
|
for (auto const & callback : *constructors_)
|
||||||
callback.callback(container, *this, row, column_uuid_set_, true);
|
callback.callback(container, *this, row, column_uuid_set_, {}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void table::trigger_constructors(container & container, std::uint32_t row, util::hash_set<util::uuid> const & attached_components)
|
void table::trigger_constructors(container & container, std::uint32_t row, util::hash_set<util::uuid> const & attached_components, util::hash_set<util::uuid> const & detached_components)
|
||||||
{
|
{
|
||||||
for (auto const & callback : *constructors_)
|
for (auto const & callback : *constructors_)
|
||||||
callback.callback(container, *this, row, attached_components, false);
|
callback.callback(container, *this, row, attached_components, detached_components, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void table::trigger_destructors(container & container, std::uint32_t row)
|
void table::trigger_destructors(container & container, std::uint32_t row)
|
||||||
{
|
{
|
||||||
for (auto const & callback : *destructors_)
|
for (auto const & callback : *destructors_)
|
||||||
callback.callback(container, *this, row, column_uuid_set_, true);
|
callback.callback(container, *this, row, {}, column_uuid_set_, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void table::trigger_destructors(container & container, std::uint32_t row, util::hash_set<util::uuid> const & detached_components)
|
void table::trigger_destructors(container & container, std::uint32_t row, util::hash_set<util::uuid> const & attached_components, util::hash_set<util::uuid> const & detached_components)
|
||||||
{
|
{
|
||||||
for (auto const & callback : *destructors_)
|
for (auto const & callback : *destructors_)
|
||||||
callback.callback(container, *this, row, detached_components, false);
|
callback.callback(container, *this, row, attached_components, detached_components, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t table::memory_usage() const
|
std::size_t table::memory_usage() const
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace psemek::fonts
|
namespace psemek::fonts::inline v1
|
||||||
{
|
{
|
||||||
|
|
||||||
enum class font_type
|
enum class font_type
|
||||||
|
|
|
||||||
|
|
@ -92,4 +92,7 @@ namespace psemek::fonts
|
||||||
|
|
||||||
std::unique_ptr<font_builder> load_freetype_font(wgpu::device device, std::filesystem::path const & path);
|
std::unique_ptr<font_builder> load_freetype_font(wgpu::device device, std::filesystem::path const & path);
|
||||||
|
|
||||||
|
// Font paths are specified in order
|
||||||
|
std::unique_ptr<font_builder> load_combined_freetype_font(wgpu::device device, std::vector<std::filesystem::path> const & paths);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
#include <psemek/fonts/font.hpp>
|
#include <psemek/fonts/font.hpp>
|
||||||
#include <psemek/fonts/bmfont.hpp>
|
#include <psemek/fonts/bmfont.hpp>
|
||||||
|
|
||||||
namespace psemek::fonts
|
namespace psemek::fonts::inline v1
|
||||||
{
|
{
|
||||||
|
|
||||||
struct kerned_font
|
struct kerned_font
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
#include <psemek/fonts/font.hpp>
|
#include <psemek/fonts/font.hpp>
|
||||||
#include <psemek/fonts/kerned_font.hpp>
|
#include <psemek/fonts/kerned_font.hpp>
|
||||||
|
|
||||||
namespace psemek::fonts
|
namespace psemek::fonts::inline v1
|
||||||
{
|
{
|
||||||
|
|
||||||
struct msdf_font
|
struct msdf_font
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
namespace psemek::fonts
|
namespace psemek::fonts::inline v1
|
||||||
{
|
{
|
||||||
|
|
||||||
std::unique_ptr<font> make_default_monospace_9x12_font()
|
std::unique_ptr<font> make_default_monospace_9x12_font()
|
||||||
|
|
|
||||||
|
|
@ -72,11 +72,19 @@ namespace psemek::fonts
|
||||||
struct face_shared
|
struct face_shared
|
||||||
{
|
{
|
||||||
wgpu::device device;
|
wgpu::device device;
|
||||||
|
|
||||||
|
struct face_data
|
||||||
|
{
|
||||||
|
util::blob data;
|
||||||
FT_Face face;
|
FT_Face face;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<face_data> faces = {};
|
||||||
|
|
||||||
~face_shared()
|
~face_shared()
|
||||||
{
|
{
|
||||||
FT_Done_Face(face);
|
for (auto const & face : faces)
|
||||||
|
FT_Done_Face(face.face);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -88,6 +96,9 @@ namespace psemek::fonts
|
||||||
, type_(type)
|
, type_(type)
|
||||||
, size_(size)
|
, size_(size)
|
||||||
{
|
{
|
||||||
|
for (auto const & face : state_->faces)
|
||||||
|
ft_check_result(FT_Set_Char_Size(face.face, 0, size_ << 6, 0, 0), "Failed to select freetype face size: ");
|
||||||
|
|
||||||
font::shape("x", {});
|
font::shape("x", {});
|
||||||
xheight_ = -shaped_text_[0].position[1].min;
|
xheight_ = -shaped_text_[0].position[1].min;
|
||||||
shaped_text_.clear();
|
shaped_text_.clear();
|
||||||
|
|
@ -100,7 +111,7 @@ namespace psemek::fonts
|
||||||
|
|
||||||
std::string_view name() const override
|
std::string_view name() const override
|
||||||
{
|
{
|
||||||
return FT_Get_Postscript_Name(state_->face);
|
return FT_Get_Postscript_Name(state_->faces[0].face);
|
||||||
}
|
}
|
||||||
|
|
||||||
math::vector<int, 2> size() const override
|
math::vector<int, 2> size() const override
|
||||||
|
|
@ -134,7 +145,7 @@ namespace psemek::fonts
|
||||||
|
|
||||||
struct page
|
struct page
|
||||||
{
|
{
|
||||||
util::array<std::uint8_t, 2> pixmap;
|
util::ndarray<std::uint8_t, 2> pixmap;
|
||||||
|
|
||||||
int current_row_start = 0;
|
int current_row_start = 0;
|
||||||
int current_row_height = 0;
|
int current_row_height = 0;
|
||||||
|
|
@ -147,6 +158,12 @@ namespace psemek::fonts
|
||||||
|
|
||||||
std::vector<page> pages_;
|
std::vector<page> pages_;
|
||||||
|
|
||||||
|
struct glyph_mapping_data
|
||||||
|
{
|
||||||
|
std::uint32_t font_index;
|
||||||
|
std::uint32_t glyph_index;
|
||||||
|
};
|
||||||
|
|
||||||
struct glyph_data
|
struct glyph_data
|
||||||
{
|
{
|
||||||
int page;
|
int page;
|
||||||
|
|
@ -155,8 +172,8 @@ namespace psemek::fonts
|
||||||
math::vector<int, 2> advance;
|
math::vector<int, 2> advance;
|
||||||
};
|
};
|
||||||
|
|
||||||
util::hash_map<char32_t, std::uint32_t> glyph_mapping_;
|
util::hash_map<char32_t, glyph_mapping_data> glyph_mapping_;
|
||||||
util::hash_map<std::uint32_t, glyph_data> glyphs_;
|
util::hash_map<std::uint32_t, util::hash_map<std::uint32_t, glyph_data>> glyphs_;
|
||||||
|
|
||||||
std::vector<shaped_glyph> shaped_text_;
|
std::vector<shaped_glyph> shaped_text_;
|
||||||
|
|
||||||
|
|
@ -167,37 +184,53 @@ namespace psemek::fonts
|
||||||
|
|
||||||
shaped_text_.clear();
|
shaped_text_.clear();
|
||||||
|
|
||||||
auto face = state_->face;
|
|
||||||
|
|
||||||
ft_check_result(FT_Set_Char_Size(face, 0, size_ << 6, 0, 0), "Failed to select freetype face size: ");
|
|
||||||
|
|
||||||
bool need_update_pages = false;
|
bool need_update_pages = false;
|
||||||
|
|
||||||
for (char32_t ch : string)
|
for (char32_t ch : string)
|
||||||
{
|
{
|
||||||
std::uint32_t glyph_id;
|
glyph_mapping_data mapping{0, 0};
|
||||||
|
|
||||||
if (auto it = glyph_mapping_.find(ch); it != glyph_mapping_.end())
|
if (auto it = glyph_mapping_.find(ch); it != glyph_mapping_.end())
|
||||||
{
|
{
|
||||||
glyph_id = it->second;
|
mapping = it->second;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
glyph_id = FT_Get_Char_Index(face, ch);
|
int glyph_index = 0;
|
||||||
glyph_mapping_[ch] = glyph_id;
|
for (int font_index = 0; font_index < state_->faces.size(); ++font_index)
|
||||||
|
{
|
||||||
|
glyph_index = FT_Get_Char_Index(state_->faces[font_index].face, ch);
|
||||||
|
if (glyph_index != 0)
|
||||||
|
{
|
||||||
|
mapping = {font_index, glyph_index};
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (glyph_index == 0)
|
||||||
|
{
|
||||||
|
mapping.font_index = 0;
|
||||||
|
mapping.glyph_index = FT_Get_Char_Index(state_->faces[0].face, '?');
|
||||||
|
}
|
||||||
|
|
||||||
|
glyph_mapping_[ch] = mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto face = state_->faces[mapping.font_index].face;
|
||||||
|
|
||||||
glyph_data * data = nullptr;
|
glyph_data * data = nullptr;
|
||||||
|
|
||||||
if (auto it = glyphs_.find(glyph_id); it != glyphs_.end())
|
auto & glyphs = glyphs_[mapping.font_index];
|
||||||
|
|
||||||
|
if (auto it = glyphs.find(mapping.glyph_index); it != glyphs.end())
|
||||||
{
|
{
|
||||||
data = &(it->second);
|
data = &(it->second);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
data = &glyphs_[glyph_id];
|
data = &glyphs[mapping.glyph_index];
|
||||||
|
|
||||||
FT_Load_Glyph(face, glyph_id, type_ == font_type::bitmap ? FT_LOAD_TARGET_LIGHT : FT_LOAD_DEFAULT);
|
FT_Load_Glyph(face, mapping.glyph_index, type_ == font_type::bitmap ? FT_LOAD_TARGET_LIGHT : FT_LOAD_DEFAULT);
|
||||||
FT_Render_Glyph(face->glyph, type_ == font_type::bitmap ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_SDF);
|
FT_Render_Glyph(face->glyph, type_ == font_type::bitmap ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_SDF);
|
||||||
|
|
||||||
if (face->glyph->bitmap.width + 2 * padding > page_size || face->glyph->bitmap.rows + 2 * padding > page_size)
|
if (face->glyph->bitmap.width + 2 * padding > page_size || face->glyph->bitmap.rows + 2 * padding > page_size)
|
||||||
|
|
@ -206,7 +239,7 @@ namespace psemek::fonts
|
||||||
data->part = {{{0, 0}, {0, 0}}};
|
data->part = {{{0, 0}, {0, 0}}};
|
||||||
data->offset = {0, 0};
|
data->offset = {0, 0};
|
||||||
data->advance = {size_, 0};
|
data->advance = {size_, 0};
|
||||||
log::warning() << "Glyph with ID " << glyph_id << " (U+" << std::hex << (int)ch << ") is larger than font atlas page: " << face->glyph->bitmap.width << "x" << face->glyph->bitmap.rows;
|
log::warning() << "Glyph with ID " << mapping.glyph_index << " (U+" << std::hex << (int)ch << ") is larger than font atlas page: " << face->glyph->bitmap.width << "x" << face->glyph->bitmap.rows;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -315,7 +348,7 @@ namespace psemek::fonts
|
||||||
|
|
||||||
std::string_view name() const override
|
std::string_view name() const override
|
||||||
{
|
{
|
||||||
return FT_Get_Postscript_Name(state_->face);
|
return FT_Get_Postscript_Name(state_->faces[0].face);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<font> create(font_type type, int size) override
|
std::unique_ptr<font> create(font_type type, int size) override
|
||||||
|
|
@ -333,15 +366,26 @@ namespace psemek::fonts
|
||||||
|
|
||||||
std::unique_ptr<font_builder> load_freetype_font(wgpu::device device, std::filesystem::path const & path)
|
std::unique_ptr<font_builder> load_freetype_font(wgpu::device device, std::filesystem::path const & path)
|
||||||
{
|
{
|
||||||
FT_Face face;
|
return load_combined_freetype_font(device, {path});
|
||||||
auto path_str = path.string();
|
}
|
||||||
ft_check_result(FT_New_Face(ft_library(), path_str.data(), 0, &face), "Failed to load font " + path_str + ": ");
|
|
||||||
|
|
||||||
auto result = std::make_unique<freetype_font_builder>(std::make_shared<face_shared>(device, face));
|
std::unique_ptr<font_builder> load_combined_freetype_font(wgpu::device device, std::vector<std::filesystem::path> const & paths)
|
||||||
|
{
|
||||||
|
auto result = std::make_shared<face_shared>(device);
|
||||||
|
|
||||||
log::debug() << "Loaded font " << result->name() << " (" << path << ")";
|
for (auto const & path : paths)
|
||||||
|
{
|
||||||
|
auto font_data = io::read_full(io::file_istream{path});
|
||||||
|
|
||||||
return result;
|
FT_Face face = nullptr;
|
||||||
|
ft_check_result(FT_New_Memory_Face(ft_library(), reinterpret_cast<FT_Byte const *>(font_data.data()), font_data.size(), 0, &face), util::to_string("Failed to load font ", path, ": "));
|
||||||
|
|
||||||
|
result->faces.push_back({std::move(font_data), face});
|
||||||
|
|
||||||
|
log::debug() << "Loaded font " << FT_Get_Postscript_Name(face) << " (" << path << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<freetype_font_builder>(std::move(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
#include <psemek/util/unicode.hpp>
|
#include <psemek/util/unicode.hpp>
|
||||||
#include <psemek/util/exception.hpp>
|
#include <psemek/util/exception.hpp>
|
||||||
|
|
||||||
namespace psemek::fonts
|
namespace psemek::fonts::inline v1
|
||||||
{
|
{
|
||||||
|
|
||||||
kerned_font::kerned_font(bmfont_data data, gfx::texture_2d atlas)
|
kerned_font::kerned_font(bmfont_data data, gfx::texture_2d atlas)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#include <psemek/fonts/msdf_font.hpp>
|
#include <psemek/fonts/msdf_font.hpp>
|
||||||
#include <psemek/gfx/pixmap.hpp>
|
#include <psemek/gfx/pixmap.hpp>
|
||||||
|
|
||||||
namespace psemek::fonts
|
namespace psemek::fonts::inline v1
|
||||||
{
|
{
|
||||||
|
|
||||||
std::unique_ptr<font> make_msdf_font(io::istream && description, io::istream && texture)
|
std::unique_ptr<font> make_msdf_font(io::istream && description, io::istream && texture)
|
||||||
|
|
|
||||||
|
|
@ -317,6 +317,77 @@ namespace psemek::gfx
|
||||||
return generic_color{dark(c.c, darkness)};
|
return generic_color{dark(c.c, darkness)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
math::vector<T, 3> rgb_to_hsv(math::vector<T, 3> const & rgb)
|
||||||
|
{
|
||||||
|
T max = std::max({rgb[0], rgb[1], rgb[2]});
|
||||||
|
T min = std::min({rgb[0], rgb[1], rgb[2]});
|
||||||
|
T delta = max - min;
|
||||||
|
|
||||||
|
T hue = T{0};
|
||||||
|
|
||||||
|
if (delta > T{0})
|
||||||
|
{
|
||||||
|
if (rgb[0] > rgb[1] && rgb[0] > rgb[2])
|
||||||
|
{
|
||||||
|
hue = (rgb[1] - rgb[2]) / delta;
|
||||||
|
}
|
||||||
|
else if (rgb[1] > rgb[2])
|
||||||
|
{
|
||||||
|
hue = 2.f + (rgb[2] - rgb[0]) / delta;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hue = 4.f + (rgb[0] - rgb[1]) / delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hue *= math::rad<T>(60);
|
||||||
|
|
||||||
|
if (hue < 0)
|
||||||
|
hue += math::rad<T>(360);
|
||||||
|
|
||||||
|
math::vector<T, 3> result;
|
||||||
|
result[0] = hue;
|
||||||
|
result[1] = (max > T{0}) ? (delta / max) : T{0};
|
||||||
|
result[2] = max;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
math::vector<T, 3> hsv_to_rgb(math::vector<T, 3> const & hsv)
|
||||||
|
{
|
||||||
|
auto c = hsv[1] * hsv[2];
|
||||||
|
auto x = c * (T{1} - std::abs(std::fmod(hsv[0] / math::rad<T>(60), 2.f) - 1.f));
|
||||||
|
auto m = hsv[2] - c;
|
||||||
|
|
||||||
|
math::vector<T, 3> result;
|
||||||
|
|
||||||
|
if (hsv[0] < math::rad<T>(60))
|
||||||
|
result = {c, x, T{0}};
|
||||||
|
else if (hsv[0] < math::rad<T>(120))
|
||||||
|
result = {x, c, T{0}};
|
||||||
|
else if (hsv[0] < math::rad<T>(180))
|
||||||
|
result = {T{0}, c, x};
|
||||||
|
else if (hsv[0] < math::rad<T>(240))
|
||||||
|
result = {T{0}, x, c};
|
||||||
|
else if (hsv[0] < math::rad<T>(300))
|
||||||
|
result = {x, T{0}, c};
|
||||||
|
else
|
||||||
|
result = {c, T{0}, x};
|
||||||
|
|
||||||
|
result += math::vector{m, m, m};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::floating_point T, std::size_t N>
|
||||||
|
T luminance(math::vector<T, N> const & c)
|
||||||
|
{
|
||||||
|
return c[0] * T(0.299) + c[1] * T(0.587) + c[2] * T(0.114);
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<color_rgba> parse_color(std::string_view const & text);
|
std::optional<color_rgba> parse_color(std::string_view const & text);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/* stb_image - v2.28 - public domain image loader - http://nothings.org/stb
|
/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb
|
||||||
no warranty implied; use at your own risk
|
no warranty implied; use at your own risk
|
||||||
|
|
||||||
Do this:
|
Do this:
|
||||||
|
|
@ -48,6 +48,8 @@ LICENSE
|
||||||
|
|
||||||
RECENT REVISION HISTORY:
|
RECENT REVISION HISTORY:
|
||||||
|
|
||||||
|
2.30 (2024-05-31) avoid erroneous gcc warning
|
||||||
|
2.29 (2023-05-xx) optimizations
|
||||||
2.28 (2023-01-29) many error fixes, security errors, just tons of stuff
|
2.28 (2023-01-29) many error fixes, security errors, just tons of stuff
|
||||||
2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes
|
2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes
|
||||||
2.26 (2020-07-13) many minor fixes
|
2.26 (2020-07-13) many minor fixes
|
||||||
|
|
@ -1072,8 +1074,8 @@ static int stbi__addints_valid(int a, int b)
|
||||||
return a <= INT_MAX - b;
|
return a <= INT_MAX - b;
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns 1 if the product of two signed shorts is valid, 0 on overflow.
|
// returns 1 if the product of two ints fits in a signed short, 0 on overflow.
|
||||||
static int stbi__mul2shorts_valid(short a, short b)
|
static int stbi__mul2shorts_valid(int a, int b)
|
||||||
{
|
{
|
||||||
if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow
|
if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow
|
||||||
if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid
|
if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid
|
||||||
|
|
@ -3384,13 +3386,13 @@ static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int stbi__skip_jpeg_junk_at_end(stbi__jpeg *j)
|
static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j)
|
||||||
{
|
{
|
||||||
// some JPEGs have junk at end, skip over it but if we find what looks
|
// some JPEGs have junk at end, skip over it but if we find what looks
|
||||||
// like a valid marker, resume there
|
// like a valid marker, resume there
|
||||||
while (!stbi__at_eof(j->s)) {
|
while (!stbi__at_eof(j->s)) {
|
||||||
int x = stbi__get8(j->s);
|
stbi_uc x = stbi__get8(j->s);
|
||||||
while (x == 255) { // might be a marker
|
while (x == 0xff) { // might be a marker
|
||||||
if (stbi__at_eof(j->s)) return STBI__MARKER_none;
|
if (stbi__at_eof(j->s)) return STBI__MARKER_none;
|
||||||
x = stbi__get8(j->s);
|
x = stbi__get8(j->s);
|
||||||
if (x != 0x00 && x != 0xff) {
|
if (x != 0x00 && x != 0xff) {
|
||||||
|
|
@ -4176,6 +4178,7 @@ typedef struct
|
||||||
{
|
{
|
||||||
stbi_uc *zbuffer, *zbuffer_end;
|
stbi_uc *zbuffer, *zbuffer_end;
|
||||||
int num_bits;
|
int num_bits;
|
||||||
|
int hit_zeof_once;
|
||||||
stbi__uint32 code_buffer;
|
stbi__uint32 code_buffer;
|
||||||
|
|
||||||
char *zout;
|
char *zout;
|
||||||
|
|
@ -4242,10 +4245,21 @@ stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z)
|
||||||
int b,s;
|
int b,s;
|
||||||
if (a->num_bits < 16) {
|
if (a->num_bits < 16) {
|
||||||
if (stbi__zeof(a)) {
|
if (stbi__zeof(a)) {
|
||||||
return -1; /* report error for unexpected end of data. */
|
if (!a->hit_zeof_once) {
|
||||||
|
// This is the first time we hit eof, insert 16 extra padding btis
|
||||||
|
// to allow us to keep going; if we actually consume any of them
|
||||||
|
// though, that is invalid data. This is caught later.
|
||||||
|
a->hit_zeof_once = 1;
|
||||||
|
a->num_bits += 16; // add 16 implicit zero bits
|
||||||
|
} else {
|
||||||
|
// We already inserted our extra 16 padding bits and are again
|
||||||
|
// out, this stream is actually prematurely terminated.
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
stbi__fill_bits(a);
|
stbi__fill_bits(a);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
b = z->fast[a->code_buffer & STBI__ZFAST_MASK];
|
b = z->fast[a->code_buffer & STBI__ZFAST_MASK];
|
||||||
if (b) {
|
if (b) {
|
||||||
s = b >> 9;
|
s = b >> 9;
|
||||||
|
|
@ -4309,6 +4323,13 @@ static int stbi__parse_huffman_block(stbi__zbuf *a)
|
||||||
int len,dist;
|
int len,dist;
|
||||||
if (z == 256) {
|
if (z == 256) {
|
||||||
a->zout = zout;
|
a->zout = zout;
|
||||||
|
if (a->hit_zeof_once && a->num_bits < 16) {
|
||||||
|
// The first time we hit zeof, we inserted 16 extra zero bits into our bit
|
||||||
|
// buffer so the decoder can just do its speculative decoding. But if we
|
||||||
|
// actually consumed any of those bits (which is the case when num_bits < 16),
|
||||||
|
// the stream actually read past the end so it is malformed.
|
||||||
|
return stbi__err("unexpected end","Corrupt PNG");
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data
|
if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data
|
||||||
|
|
@ -4320,7 +4341,7 @@ static int stbi__parse_huffman_block(stbi__zbuf *a)
|
||||||
dist = stbi__zdist_base[z];
|
dist = stbi__zdist_base[z];
|
||||||
if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]);
|
if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]);
|
||||||
if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG");
|
if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG");
|
||||||
if (zout + len > a->zout_end) {
|
if (len > a->zout_end - zout) {
|
||||||
if (!stbi__zexpand(a, zout, len)) return 0;
|
if (!stbi__zexpand(a, zout, len)) return 0;
|
||||||
zout = a->zout;
|
zout = a->zout;
|
||||||
}
|
}
|
||||||
|
|
@ -4464,6 +4485,7 @@ static int stbi__parse_zlib(stbi__zbuf *a, int parse_header)
|
||||||
if (!stbi__parse_zlib_header(a)) return 0;
|
if (!stbi__parse_zlib_header(a)) return 0;
|
||||||
a->num_bits = 0;
|
a->num_bits = 0;
|
||||||
a->code_buffer = 0;
|
a->code_buffer = 0;
|
||||||
|
a->hit_zeof_once = 0;
|
||||||
do {
|
do {
|
||||||
final = stbi__zreceive(a,1);
|
final = stbi__zreceive(a,1);
|
||||||
type = stbi__zreceive(a,2);
|
type = stbi__zreceive(a,2);
|
||||||
|
|
@ -4619,9 +4641,8 @@ enum {
|
||||||
STBI__F_up=2,
|
STBI__F_up=2,
|
||||||
STBI__F_avg=3,
|
STBI__F_avg=3,
|
||||||
STBI__F_paeth=4,
|
STBI__F_paeth=4,
|
||||||
// synthetic filters used for first scanline to avoid needing a dummy row of 0s
|
// synthetic filter used for first scanline to avoid needing a dummy row of 0s
|
||||||
STBI__F_avg_first,
|
STBI__F_avg_first
|
||||||
STBI__F_paeth_first
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static stbi_uc first_row_filter[5] =
|
static stbi_uc first_row_filter[5] =
|
||||||
|
|
@ -4630,22 +4651,47 @@ static stbi_uc first_row_filter[5] =
|
||||||
STBI__F_sub,
|
STBI__F_sub,
|
||||||
STBI__F_none,
|
STBI__F_none,
|
||||||
STBI__F_avg_first,
|
STBI__F_avg_first,
|
||||||
STBI__F_paeth_first
|
STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub
|
||||||
};
|
};
|
||||||
|
|
||||||
static int stbi__paeth(int a, int b, int c)
|
static int stbi__paeth(int a, int b, int c)
|
||||||
{
|
{
|
||||||
int p = a + b - c;
|
// This formulation looks very different from the reference in the PNG spec, but is
|
||||||
int pa = abs(p-a);
|
// actually equivalent and has favorable data dependencies and admits straightforward
|
||||||
int pb = abs(p-b);
|
// generation of branch-free code, which helps performance significantly.
|
||||||
int pc = abs(p-c);
|
int thresh = c*3 - (a + b);
|
||||||
if (pa <= pb && pa <= pc) return a;
|
int lo = a < b ? a : b;
|
||||||
if (pb <= pc) return b;
|
int hi = a < b ? b : a;
|
||||||
return c;
|
int t0 = (hi <= thresh) ? lo : c;
|
||||||
|
int t1 = (thresh <= lo) ? hi : t0;
|
||||||
|
return t1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 };
|
static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 };
|
||||||
|
|
||||||
|
// adds an extra all-255 alpha channel
|
||||||
|
// dest == src is legal
|
||||||
|
// img_n must be 1 or 3
|
||||||
|
static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
// must process data backwards since we allow dest==src
|
||||||
|
if (img_n == 1) {
|
||||||
|
for (i=x-1; i >= 0; --i) {
|
||||||
|
dest[i*2+1] = 255;
|
||||||
|
dest[i*2+0] = src[i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
STBI_ASSERT(img_n == 3);
|
||||||
|
for (i=x-1; i >= 0; --i) {
|
||||||
|
dest[i*4+3] = 255;
|
||||||
|
dest[i*4+2] = src[i*3+2];
|
||||||
|
dest[i*4+1] = src[i*3+1];
|
||||||
|
dest[i*4+0] = src[i*3+0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// create the png data from post-deflated data
|
// create the png data from post-deflated data
|
||||||
static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color)
|
static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color)
|
||||||
{
|
{
|
||||||
|
|
@ -4653,6 +4699,8 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
|
||||||
stbi__context *s = a->s;
|
stbi__context *s = a->s;
|
||||||
stbi__uint32 i,j,stride = x*out_n*bytes;
|
stbi__uint32 i,j,stride = x*out_n*bytes;
|
||||||
stbi__uint32 img_len, img_width_bytes;
|
stbi__uint32 img_len, img_width_bytes;
|
||||||
|
stbi_uc *filter_buf;
|
||||||
|
int all_ok = 1;
|
||||||
int k;
|
int k;
|
||||||
int img_n = s->img_n; // copy it into a local for later
|
int img_n = s->img_n; // copy it into a local for later
|
||||||
|
|
||||||
|
|
@ -4664,8 +4712,11 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
|
||||||
a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into
|
a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into
|
||||||
if (!a->out) return stbi__err("outofmem", "Out of memory");
|
if (!a->out) return stbi__err("outofmem", "Out of memory");
|
||||||
|
|
||||||
|
// note: error exits here don't need to clean up a->out individually,
|
||||||
|
// stbi__do_png always does on error.
|
||||||
if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG");
|
if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG");
|
||||||
img_width_bytes = (((img_n * x * depth) + 7) >> 3);
|
img_width_bytes = (((img_n * x * depth) + 7) >> 3);
|
||||||
|
if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG");
|
||||||
img_len = (img_width_bytes + 1) * y;
|
img_len = (img_width_bytes + 1) * y;
|
||||||
|
|
||||||
// we used to check for exact match between raw_len and img_len on non-interlaced PNGs,
|
// we used to check for exact match between raw_len and img_len on non-interlaced PNGs,
|
||||||
|
|
@ -4673,188 +4724,136 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
|
||||||
// so just check for raw_len < img_len always.
|
// so just check for raw_len < img_len always.
|
||||||
if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG");
|
if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG");
|
||||||
|
|
||||||
for (j=0; j < y; ++j) {
|
// Allocate two scan lines worth of filter workspace buffer.
|
||||||
stbi_uc *cur = a->out + stride*j;
|
filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0);
|
||||||
stbi_uc *prior;
|
if (!filter_buf) return stbi__err("outofmem", "Out of memory");
|
||||||
int filter = *raw++;
|
|
||||||
|
|
||||||
if (filter > 4)
|
|
||||||
return stbi__err("invalid filter","Corrupt PNG");
|
|
||||||
|
|
||||||
|
// Filtering for low-bit-depth images
|
||||||
if (depth < 8) {
|
if (depth < 8) {
|
||||||
if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG");
|
|
||||||
cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place
|
|
||||||
filter_bytes = 1;
|
filter_bytes = 1;
|
||||||
width = img_width_bytes;
|
width = img_width_bytes;
|
||||||
}
|
}
|
||||||
prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above
|
|
||||||
|
for (j=0; j < y; ++j) {
|
||||||
|
// cur/prior filter buffers alternate
|
||||||
|
stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes;
|
||||||
|
stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes;
|
||||||
|
stbi_uc *dest = a->out + stride*j;
|
||||||
|
int nk = width * filter_bytes;
|
||||||
|
int filter = *raw++;
|
||||||
|
|
||||||
|
// check filter type
|
||||||
|
if (filter > 4) {
|
||||||
|
all_ok = stbi__err("invalid filter","Corrupt PNG");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// if first row, use special filter that doesn't sample previous row
|
// if first row, use special filter that doesn't sample previous row
|
||||||
if (j == 0) filter = first_row_filter[filter];
|
if (j == 0) filter = first_row_filter[filter];
|
||||||
|
|
||||||
// handle first byte explicitly
|
// perform actual filtering
|
||||||
for (k=0; k < filter_bytes; ++k) {
|
|
||||||
switch (filter) {
|
switch (filter) {
|
||||||
case STBI__F_none : cur[k] = raw[k]; break;
|
case STBI__F_none:
|
||||||
case STBI__F_sub : cur[k] = raw[k]; break;
|
memcpy(cur, raw, nk);
|
||||||
case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;
|
break;
|
||||||
case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break;
|
case STBI__F_sub:
|
||||||
case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break;
|
memcpy(cur, raw, filter_bytes);
|
||||||
case STBI__F_avg_first : cur[k] = raw[k]; break;
|
for (k = filter_bytes; k < nk; ++k)
|
||||||
case STBI__F_paeth_first: cur[k] = raw[k]; break;
|
cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]);
|
||||||
}
|
break;
|
||||||
}
|
case STBI__F_up:
|
||||||
|
|
||||||
if (depth == 8) {
|
|
||||||
if (img_n != out_n)
|
|
||||||
cur[img_n] = 255; // first pixel
|
|
||||||
raw += img_n;
|
|
||||||
cur += out_n;
|
|
||||||
prior += out_n;
|
|
||||||
} else if (depth == 16) {
|
|
||||||
if (img_n != out_n) {
|
|
||||||
cur[filter_bytes] = 255; // first pixel top byte
|
|
||||||
cur[filter_bytes+1] = 255; // first pixel bottom byte
|
|
||||||
}
|
|
||||||
raw += filter_bytes;
|
|
||||||
cur += output_bytes;
|
|
||||||
prior += output_bytes;
|
|
||||||
} else {
|
|
||||||
raw += 1;
|
|
||||||
cur += 1;
|
|
||||||
prior += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is a little gross, so that we don't switch per-pixel or per-component
|
|
||||||
if (depth < 8 || img_n == out_n) {
|
|
||||||
int nk = (width - 1)*filter_bytes;
|
|
||||||
#define STBI__CASE(f) \
|
|
||||||
case f: \
|
|
||||||
for (k = 0; k < nk; ++k)
|
for (k = 0; k < nk; ++k)
|
||||||
switch (filter) {
|
cur[k] = STBI__BYTECAST(raw[k] + prior[k]);
|
||||||
// "none" filter turns into a memcpy here; make that explicit.
|
break;
|
||||||
case STBI__F_none: memcpy(cur, raw, nk); break;
|
case STBI__F_avg:
|
||||||
STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break;
|
for (k = 0; k < filter_bytes; ++k)
|
||||||
STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
|
cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1));
|
||||||
STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break;
|
for (k = filter_bytes; k < nk; ++k)
|
||||||
STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break;
|
cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1));
|
||||||
STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break;
|
break;
|
||||||
STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break;
|
case STBI__F_paeth:
|
||||||
|
for (k = 0; k < filter_bytes; ++k)
|
||||||
|
cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0)
|
||||||
|
for (k = filter_bytes; k < nk; ++k)
|
||||||
|
cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes]));
|
||||||
|
break;
|
||||||
|
case STBI__F_avg_first:
|
||||||
|
memcpy(cur, raw, filter_bytes);
|
||||||
|
for (k = filter_bytes; k < nk; ++k)
|
||||||
|
cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
#undef STBI__CASE
|
|
||||||
raw += nk;
|
raw += nk;
|
||||||
|
|
||||||
|
// expand decoded bits in cur to dest, also adding an extra alpha channel if desired
|
||||||
|
if (depth < 8) {
|
||||||
|
stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range
|
||||||
|
stbi_uc *in = cur;
|
||||||
|
stbi_uc *out = dest;
|
||||||
|
stbi_uc inb = 0;
|
||||||
|
stbi__uint32 nsmp = x*img_n;
|
||||||
|
|
||||||
|
// expand bits to bytes first
|
||||||
|
if (depth == 4) {
|
||||||
|
for (i=0; i < nsmp; ++i) {
|
||||||
|
if ((i & 1) == 0) inb = *in++;
|
||||||
|
*out++ = scale * (inb >> 4);
|
||||||
|
inb <<= 4;
|
||||||
|
}
|
||||||
|
} else if (depth == 2) {
|
||||||
|
for (i=0; i < nsmp; ++i) {
|
||||||
|
if ((i & 3) == 0) inb = *in++;
|
||||||
|
*out++ = scale * (inb >> 6);
|
||||||
|
inb <<= 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
STBI_ASSERT(depth == 1);
|
||||||
|
for (i=0; i < nsmp; ++i) {
|
||||||
|
if ((i & 7) == 0) inb = *in++;
|
||||||
|
*out++ = scale * (inb >> 7);
|
||||||
|
inb <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert alpha=255 values if desired
|
||||||
|
if (img_n != out_n)
|
||||||
|
stbi__create_png_alpha_expand8(dest, dest, x, img_n);
|
||||||
|
} else if (depth == 8) {
|
||||||
|
if (img_n == out_n)
|
||||||
|
memcpy(dest, cur, x*img_n);
|
||||||
|
else
|
||||||
|
stbi__create_png_alpha_expand8(dest, cur, x, img_n);
|
||||||
|
} else if (depth == 16) {
|
||||||
|
// convert the image data from big-endian to platform-native
|
||||||
|
stbi__uint16 *dest16 = (stbi__uint16*)dest;
|
||||||
|
stbi__uint32 nsmp = x*img_n;
|
||||||
|
|
||||||
|
if (img_n == out_n) {
|
||||||
|
for (i = 0; i < nsmp; ++i, ++dest16, cur += 2)
|
||||||
|
*dest16 = (cur[0] << 8) | cur[1];
|
||||||
} else {
|
} else {
|
||||||
STBI_ASSERT(img_n+1 == out_n);
|
STBI_ASSERT(img_n+1 == out_n);
|
||||||
#define STBI__CASE(f) \
|
|
||||||
case f: \
|
|
||||||
for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \
|
|
||||||
for (k=0; k < filter_bytes; ++k)
|
|
||||||
switch (filter) {
|
|
||||||
STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break;
|
|
||||||
STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break;
|
|
||||||
STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
|
|
||||||
STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break;
|
|
||||||
STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break;
|
|
||||||
STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break;
|
|
||||||
STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break;
|
|
||||||
}
|
|
||||||
#undef STBI__CASE
|
|
||||||
|
|
||||||
// the loop above sets the high byte of the pixels' alpha, but for
|
|
||||||
// 16 bit png files we also need the low byte set. we'll do that here.
|
|
||||||
if (depth == 16) {
|
|
||||||
cur = a->out + stride*j; // start at the beginning of the row again
|
|
||||||
for (i=0; i < x; ++i,cur+=output_bytes) {
|
|
||||||
cur[filter_bytes+1] = 255;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// we make a separate pass to expand bits to pixels; for performance,
|
|
||||||
// this could run two scanlines behind the above code, so it won't
|
|
||||||
// intefere with filtering but will still be in the cache.
|
|
||||||
if (depth < 8) {
|
|
||||||
for (j=0; j < y; ++j) {
|
|
||||||
stbi_uc *cur = a->out + stride*j;
|
|
||||||
stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes;
|
|
||||||
// unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit
|
|
||||||
// png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop
|
|
||||||
stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range
|
|
||||||
|
|
||||||
// note that the final byte might overshoot and write more data than desired.
|
|
||||||
// we can allocate enough data that this never writes out of memory, but it
|
|
||||||
// could also overwrite the next scanline. can it overwrite non-empty data
|
|
||||||
// on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel.
|
|
||||||
// so we need to explicitly clamp the final ones
|
|
||||||
|
|
||||||
if (depth == 4) {
|
|
||||||
for (k=x*img_n; k >= 2; k-=2, ++in) {
|
|
||||||
*cur++ = scale * ((*in >> 4) );
|
|
||||||
*cur++ = scale * ((*in ) & 0x0f);
|
|
||||||
}
|
|
||||||
if (k > 0) *cur++ = scale * ((*in >> 4) );
|
|
||||||
} else if (depth == 2) {
|
|
||||||
for (k=x*img_n; k >= 4; k-=4, ++in) {
|
|
||||||
*cur++ = scale * ((*in >> 6) );
|
|
||||||
*cur++ = scale * ((*in >> 4) & 0x03);
|
|
||||||
*cur++ = scale * ((*in >> 2) & 0x03);
|
|
||||||
*cur++ = scale * ((*in ) & 0x03);
|
|
||||||
}
|
|
||||||
if (k > 0) *cur++ = scale * ((*in >> 6) );
|
|
||||||
if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03);
|
|
||||||
if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03);
|
|
||||||
} else if (depth == 1) {
|
|
||||||
for (k=x*img_n; k >= 8; k-=8, ++in) {
|
|
||||||
*cur++ = scale * ((*in >> 7) );
|
|
||||||
*cur++ = scale * ((*in >> 6) & 0x01);
|
|
||||||
*cur++ = scale * ((*in >> 5) & 0x01);
|
|
||||||
*cur++ = scale * ((*in >> 4) & 0x01);
|
|
||||||
*cur++ = scale * ((*in >> 3) & 0x01);
|
|
||||||
*cur++ = scale * ((*in >> 2) & 0x01);
|
|
||||||
*cur++ = scale * ((*in >> 1) & 0x01);
|
|
||||||
*cur++ = scale * ((*in ) & 0x01);
|
|
||||||
}
|
|
||||||
if (k > 0) *cur++ = scale * ((*in >> 7) );
|
|
||||||
if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01);
|
|
||||||
if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01);
|
|
||||||
if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01);
|
|
||||||
if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01);
|
|
||||||
if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01);
|
|
||||||
if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01);
|
|
||||||
}
|
|
||||||
if (img_n != out_n) {
|
|
||||||
int q;
|
|
||||||
// insert alpha = 255
|
|
||||||
cur = a->out + stride*j;
|
|
||||||
if (img_n == 1) {
|
if (img_n == 1) {
|
||||||
for (q=x-1; q >= 0; --q) {
|
for (i = 0; i < x; ++i, dest16 += 2, cur += 2) {
|
||||||
cur[q*2+1] = 255;
|
dest16[0] = (cur[0] << 8) | cur[1];
|
||||||
cur[q*2+0] = cur[q];
|
dest16[1] = 0xffff;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
STBI_ASSERT(img_n == 3);
|
STBI_ASSERT(img_n == 3);
|
||||||
for (q=x-1; q >= 0; --q) {
|
for (i = 0; i < x; ++i, dest16 += 4, cur += 6) {
|
||||||
cur[q*4+3] = 255;
|
dest16[0] = (cur[0] << 8) | cur[1];
|
||||||
cur[q*4+2] = cur[q*3+2];
|
dest16[1] = (cur[2] << 8) | cur[3];
|
||||||
cur[q*4+1] = cur[q*3+1];
|
dest16[2] = (cur[4] << 8) | cur[5];
|
||||||
cur[q*4+0] = cur[q*3+0];
|
dest16[3] = 0xffff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (depth == 16) {
|
|
||||||
// force the image data from big-endian to platform-native.
|
|
||||||
// this is done in a separate pass due to the decoding relying
|
|
||||||
// on the data being untouched, but could probably be done
|
|
||||||
// per-line during decode if care is taken.
|
|
||||||
stbi_uc *cur = a->out;
|
|
||||||
stbi__uint16 *cur16 = (stbi__uint16*)cur;
|
|
||||||
|
|
||||||
for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) {
|
STBI_FREE(filter_buf);
|
||||||
*cur16 = (cur[0] << 8) | cur[1];
|
if (!all_ok) return 0;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -5161,9 +5160,11 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
|
||||||
// non-paletted with tRNS = constant alpha. if header-scanning, we can stop now.
|
// non-paletted with tRNS = constant alpha. if header-scanning, we can stop now.
|
||||||
if (scan == STBI__SCAN_header) { ++s->img_n; return 1; }
|
if (scan == STBI__SCAN_header) { ++s->img_n; return 1; }
|
||||||
if (z->depth == 16) {
|
if (z->depth == 16) {
|
||||||
for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is
|
for (k = 0; k < s->img_n && k < 3; ++k) // extra loop test to suppress false GCC warning
|
||||||
|
tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is
|
||||||
} else {
|
} else {
|
||||||
for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger
|
for (k = 0; k < s->img_n && k < 3; ++k)
|
||||||
|
tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -93,14 +93,12 @@ namespace psemek::gfx
|
||||||
|
|
||||||
static auto pointer(math::matrix<T, R, C> & value)
|
static auto pointer(math::matrix<T, R, C> & value)
|
||||||
{
|
{
|
||||||
return &value[0][0];
|
return value.values().begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void finalize(math::matrix<T, R, C> & value)
|
static void finalize(math::matrix<T, R, C> & value)
|
||||||
{
|
{
|
||||||
math::matrix<T, C, R> temp;
|
value = math::transpose(value);
|
||||||
std::copy(value.coords, value.coords + R * C, temp.coords);
|
|
||||||
value = math::transpose(temp);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <psemek/gfx/gltf_parser.hpp>
|
#include <psemek/gfx/gltf_parser.hpp>
|
||||||
#include <psemek/math/scale.hpp>
|
#include <psemek/math/trs.hpp>
|
||||||
#include <psemek/math/rotation.hpp>
|
|
||||||
#include <psemek/math/translation.hpp>
|
|
||||||
#include <psemek/util/exception.hpp>
|
#include <psemek/util/exception.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
@ -147,7 +145,7 @@ namespace psemek::gfx
|
||||||
|
|
||||||
math::interval<float> range() const;
|
math::interval<float> range() const;
|
||||||
|
|
||||||
math::affine_transform<float, 3, 3> operator()(float time) const;
|
math::trs<float, 3> operator()(float time) const;
|
||||||
|
|
||||||
gltf_scale_animation const & scale() const { return scale_; }
|
gltf_scale_animation const & scale() const { return scale_; }
|
||||||
gltf_rotation_animation const & rotation() const { return rotation_; }
|
gltf_rotation_animation const & rotation() const { return rotation_; }
|
||||||
|
|
|
||||||
|
|
@ -195,11 +195,11 @@ namespace psemek::gfx
|
||||||
GLenum index_type() const { return info_.index_type_; }
|
GLenum index_type() const { return info_.index_type_; }
|
||||||
|
|
||||||
buffer & vertex_buffer() { return vertex_buffer_; }
|
buffer & vertex_buffer() { return vertex_buffer_; }
|
||||||
buffer & index_buffer() { return index_buffer_; }
|
auto & index_buffer() { return index_buffer_; }
|
||||||
buffer & instance_buffer() { return instance_buffer_; }
|
buffer & instance_buffer() { return instance_buffer_; }
|
||||||
|
|
||||||
buffer const & vertex_buffer() const { return vertex_buffer_; }
|
buffer const & vertex_buffer() const { return vertex_buffer_; }
|
||||||
buffer const & index_buffer() const { return index_buffer_; }
|
auto const & index_buffer() const { return index_buffer_; }
|
||||||
buffer const & instance_buffer() const { return instance_buffer_; }
|
buffer const & instance_buffer() const { return instance_buffer_; }
|
||||||
|
|
||||||
// Drawing commands
|
// Drawing commands
|
||||||
|
|
@ -211,7 +211,7 @@ namespace psemek::gfx
|
||||||
private:
|
private:
|
||||||
array array_;
|
array array_;
|
||||||
buffer vertex_buffer_ = buffer::null();
|
buffer vertex_buffer_ = buffer::null();
|
||||||
buffer index_buffer_ = buffer::null();
|
basic_buffer<gl::ELEMENT_ARRAY_BUFFER> index_buffer_ = basic_buffer<gl::ELEMENT_ARRAY_BUFFER>::null();
|
||||||
buffer instance_buffer_ = buffer::null();
|
buffer instance_buffer_ = buffer::null();
|
||||||
|
|
||||||
struct mesh_info
|
struct mesh_info
|
||||||
|
|
@ -298,11 +298,8 @@ namespace psemek::gfx
|
||||||
assert((index_count % (*n)) == 0);
|
assert((index_count % (*n)) == 0);
|
||||||
|
|
||||||
if (!index_buffer_)
|
if (!index_buffer_)
|
||||||
{
|
index_buffer_ = {};
|
||||||
index_buffer_ = buffer{};
|
|
||||||
array_.bind();
|
array_.bind();
|
||||||
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, index_buffer_.id());
|
|
||||||
}
|
|
||||||
index_buffer_.load(indices, index_count, usage);
|
index_buffer_.load(indices, index_count, usage);
|
||||||
info_.index_count_ = index_count;
|
info_.index_count_ = index_count;
|
||||||
info_.indexed_ = true;
|
info_.indexed_ = true;
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ namespace psemek::gfx
|
||||||
void triangle(math::point<float, 2> const & p0, math::point<float, 2> const & p1, math::point<float, 2> const & p2, color const & c0, color const & c1, color const & c2);
|
void triangle(math::point<float, 2> const & p0, math::point<float, 2> const & p1, math::point<float, 2> const & p2, color const & c0, color const & c1, color const & c2);
|
||||||
void rect(math::box<float, 2> const & box, color const & c);
|
void rect(math::box<float, 2> const & box, color const & c);
|
||||||
void circle(math::point<float, 2> const & center, float radius, color const & c, int quality = 24);
|
void circle(math::point<float, 2> const & center, float radius, color const & c, int quality = 24);
|
||||||
|
void circle(math::point<float, 2> const & center, float radius, color const & c0, color const & c1, int quality = 24);
|
||||||
void line(math::point<float, 2> const & p0, math::point<float, 2> const & p1, float width, color const & c, bool smooth = true);
|
void line(math::point<float, 2> const & p0, math::point<float, 2> const & p1, float width, color const & c, bool smooth = true);
|
||||||
void line(math::point<float, 2> const & p0, math::point<float, 2> const & p1, float w0, float w1, color const & c0, color const & c1, bool smooth = true);
|
void line(math::point<float, 2> const & p0, math::point<float, 2> const & p1, float w0, float w1, color const & c0, color const & c1, bool smooth = true);
|
||||||
void besier(math::point<float, 2> const & p0, math::point<float, 2> const & p1, math::point<float, 2> const & p2, float width, color const & c, int quality = 8, bool smooth = true);
|
void besier(math::point<float, 2> const & p0, math::point<float, 2> const & p1, math::point<float, 2> const & p2, float width, color const & c, int quality = 8, bool smooth = true);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <psemek/util/array.hpp>
|
#include <psemek/util/ndarray.hpp>
|
||||||
#include <psemek/gfx/color.hpp>
|
#include <psemek/gfx/color.hpp>
|
||||||
#include <psemek/io/stream.hpp>
|
#include <psemek/io/stream.hpp>
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ namespace psemek::gfx
|
||||||
{
|
{
|
||||||
|
|
||||||
template <typename Pixel>
|
template <typename Pixel>
|
||||||
using basic_pixmap = util::array<Pixel, 2>;
|
using basic_pixmap = util::ndarray<Pixel, 2>;
|
||||||
|
|
||||||
using pixmap_monochrome = basic_pixmap<std::uint8_t>;
|
using pixmap_monochrome = basic_pixmap<std::uint8_t>;
|
||||||
using pixmap_rgb = basic_pixmap<color_rgb>;
|
using pixmap_rgb = basic_pixmap<color_rgb>;
|
||||||
|
|
@ -48,7 +48,7 @@ namespace psemek::gfx
|
||||||
// Utilities
|
// Utilities
|
||||||
|
|
||||||
template <typename Pixel, std::size_t N>
|
template <typename Pixel, std::size_t N>
|
||||||
auto to_srgb(util::array<Pixel, N> pm, float g = 1.f / 2.2f)
|
auto to_srgb(util::ndarray<Pixel, N> pm, float g = 1.f / 2.2f)
|
||||||
{
|
{
|
||||||
for (auto & c : pm)
|
for (auto & c : pm)
|
||||||
c = to_srgb(c, g);
|
c = to_srgb(c, g);
|
||||||
|
|
@ -56,7 +56,7 @@ namespace psemek::gfx
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Pixel, std::size_t N>
|
template <typename Pixel, std::size_t N>
|
||||||
auto to_linear(util::array<Pixel, N> pm, float g = 1.f / 2.2f)
|
auto to_linear(util::ndarray<Pixel, N> pm, float g = 1.f / 2.2f)
|
||||||
{
|
{
|
||||||
return to_srgb(std::move(pm), 1.f / g);
|
return to_srgb(std::move(pm), 1.f / g);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,10 @@ namespace psemek::gfx
|
||||||
void load_srgb(math::vector<std::size_t, D> const & size, Pixel const * data = nullptr);
|
void load_srgb(math::vector<std::size_t, D> const & size, Pixel const * data = nullptr);
|
||||||
|
|
||||||
template <typename Pixel>
|
template <typename Pixel>
|
||||||
void load(util::array<Pixel, D> const & p);
|
void load(util::ndarray<Pixel, D> const & p);
|
||||||
|
|
||||||
template <typename Pixel>
|
template <typename Pixel>
|
||||||
void load_srgb(util::array<Pixel, D> const & p);
|
void load_srgb(util::ndarray<Pixel, D> const & p);
|
||||||
|
|
||||||
#ifndef PSEMEK_GLES
|
#ifndef PSEMEK_GLES
|
||||||
void pixels(GLenum format, GLenum type, void * data, int layer = 0) const;
|
void pixels(GLenum format, GLenum type, void * data, int layer = 0) const;
|
||||||
|
|
@ -142,7 +142,7 @@ namespace psemek::gfx
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Pixel>
|
template <typename Pixel>
|
||||||
void load(int f, util::array<Pixel, 2> const & p)
|
void load(int f, util::ndarray<Pixel, 2> const & p)
|
||||||
{
|
{
|
||||||
math::vector<std::size_t, 2> size;
|
math::vector<std::size_t, 2> size;
|
||||||
for (std::size_t i = 0; i < 2; ++i) size[i] = p.dim(i);
|
for (std::size_t i = 0; i < 2; ++i) size[i] = p.dim(i);
|
||||||
|
|
@ -392,7 +392,7 @@ namespace psemek::gfx
|
||||||
|
|
||||||
template <std::size_t D, GLenum Target>
|
template <std::size_t D, GLenum Target>
|
||||||
template <typename Pixel>
|
template <typename Pixel>
|
||||||
void basic_texture<D, Target>::load(util::array<Pixel, D> const & p)
|
void basic_texture<D, Target>::load(util::ndarray<Pixel, D> const & p)
|
||||||
{
|
{
|
||||||
math::vector<std::size_t, D> size;
|
math::vector<std::size_t, D> size;
|
||||||
for (std::size_t i = 0; i < D; ++i) size[i] = p.dim(i);
|
for (std::size_t i = 0; i < D; ++i) size[i] = p.dim(i);
|
||||||
|
|
@ -401,7 +401,7 @@ namespace psemek::gfx
|
||||||
|
|
||||||
template <std::size_t D, GLenum Target>
|
template <std::size_t D, GLenum Target>
|
||||||
template <typename Pixel>
|
template <typename Pixel>
|
||||||
void basic_texture<D, Target>::load_srgb(util::array<Pixel, D> const & p)
|
void basic_texture<D, Target>::load_srgb(util::ndarray<Pixel, D> const & p)
|
||||||
{
|
{
|
||||||
math::vector<std::size_t, D> size;
|
math::vector<std::size_t, D> size;
|
||||||
for (std::size_t i = 0; i < D; ++i) size[i] = p.dim(i);
|
for (std::size_t i = 0; i < D; ++i) size[i] = p.dim(i);
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ namespace psemek::gfx
|
||||||
texture_2d & texture() { return texture_; }
|
texture_2d & texture() { return texture_; }
|
||||||
texture_2d const & texture() const { return texture_; }
|
texture_2d const & texture() const { return texture_; }
|
||||||
|
|
||||||
util::array<Pixel, 2> const & pixmap() const { return atlas_.array(); }
|
util::ndarray<Pixel, 2> const & pixmap() const { return atlas_.array(); }
|
||||||
|
|
||||||
std::pair<texture_view, bool> insert(Key const & key, util::array<Pixel, 2> const & data);
|
std::pair<texture_view, bool> insert(Key const & key, util::ndarray<Pixel, 2> const & data);
|
||||||
texture_view find(Key const & key) const;
|
texture_view find(Key const & key) const;
|
||||||
texture_view at(Key const & key) const;
|
texture_view at(Key const & key) const;
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ namespace psemek::gfx
|
||||||
{}
|
{}
|
||||||
|
|
||||||
template <typename Pixel, typename Key, typename Compare>
|
template <typename Pixel, typename Key, typename Compare>
|
||||||
std::pair<texture_view_2d, bool> texture_atlas_2d<Pixel, Key, Compare>::insert(Key const & key, util::array<Pixel, 2> const & data)
|
std::pair<texture_view_2d, bool> texture_atlas_2d<Pixel, Key, Compare>::insert(Key const & key, util::ndarray<Pixel, 2> const & data)
|
||||||
{
|
{
|
||||||
auto result = atlas_.insert(key, data);
|
auto result = atlas_.insert(key, data);
|
||||||
if (result.second)
|
if (result.second)
|
||||||
|
|
|
||||||
|
|
@ -90,11 +90,9 @@ namespace psemek::gfx
|
||||||
return scale_.range() | rotation_.range() | translation_.range();
|
return scale_.range() | rotation_.range() | translation_.range();
|
||||||
}
|
}
|
||||||
|
|
||||||
math::affine_transform<float, 3, 3> gltf_animation::operator()(float time) const
|
math::trs<float, 3> gltf_animation::operator()(float time) const
|
||||||
{
|
{
|
||||||
return math::translation(translation_(time)).transform()
|
return {translation_(time), rotation_(time), scale_(time)};
|
||||||
* math::quaternion_rotation(rotation_(time)).transform()
|
|
||||||
* math::scale(scale_(time)).transform();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -144,11 +144,8 @@ namespace psemek::gfx
|
||||||
assert(vertex_buffer_);
|
assert(vertex_buffer_);
|
||||||
|
|
||||||
if (!index_buffer_)
|
if (!index_buffer_)
|
||||||
{
|
index_buffer_ = {};
|
||||||
index_buffer_ = buffer{};
|
|
||||||
array_.bind();
|
array_.bind();
|
||||||
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, index_buffer_.id());
|
|
||||||
}
|
|
||||||
vertex_buffer_.load(vertices, vertex_size * vertex_count, usage);
|
vertex_buffer_.load(vertices, vertex_size * vertex_count, usage);
|
||||||
index_buffer_.load(indices, index_size(index_type) * index_count, usage);
|
index_buffer_.load(indices, index_size(index_type) * index_count, usage);
|
||||||
info_.vertex_count_ = vertex_count;
|
info_.vertex_count_ = vertex_count;
|
||||||
|
|
|
||||||
|
|
@ -216,14 +216,19 @@ namespace psemek::gfx
|
||||||
}
|
}
|
||||||
|
|
||||||
void painter::circle(math::point<float, 2> const & p, float r, color const & c, int quality)
|
void painter::circle(math::point<float, 2> const & p, float r, color const & c, int quality)
|
||||||
|
{
|
||||||
|
circle(p, r, c, c, quality);
|
||||||
|
}
|
||||||
|
|
||||||
|
void painter::circle(math::point<float, 2> const & p, float r, color const & c0, color const & c1, int quality)
|
||||||
{
|
{
|
||||||
std::uint32_t const base = impl().vertices.size();
|
std::uint32_t const base = impl().vertices.size();
|
||||||
|
|
||||||
impl().vertices.push_back({{p[0], p[1], 0.f}, c});
|
impl().vertices.push_back({{p[0], p[1], 0.f}, c0});
|
||||||
for (int i = 0; i < quality; ++i)
|
for (int i = 0; i < quality; ++i)
|
||||||
{
|
{
|
||||||
float const a = (math::pi * 2.f * i) / quality;
|
float const a = (math::pi * 2.f * i) / quality;
|
||||||
impl().vertices.push_back({{p[0] + r * std::cos(a), p[1] + r * std::sin(a), 0.f}, c});
|
impl().vertices.push_back({{p[0] + r * std::cos(a), p[1] + r * std::sin(a), 0.f}, c1});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < quality; ++i)
|
for (int i = 0; i < quality; ++i)
|
||||||
|
|
@ -301,18 +306,37 @@ namespace psemek::gfx
|
||||||
math::vector<float, 2> painter::text_size(std::string_view str, font f)
|
math::vector<float, 2> painter::text_size(std::string_view str, font f)
|
||||||
{
|
{
|
||||||
// TODO: multiline text
|
// TODO: multiline text
|
||||||
math::vector<float, 2> s;
|
math::vector<float, 2> font_size;
|
||||||
switch (f)
|
switch (f)
|
||||||
{
|
{
|
||||||
case font::font_9x12:
|
case font::font_9x12:
|
||||||
s = {9.f, 12.f};
|
font_size = {9.f, 12.f};
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw util::unknown_enum_value_exception(f);
|
throw util::unknown_enum_value_exception(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
s[0] *= str.size();
|
int max_line_width = 0;
|
||||||
return s;
|
int line_count = 0;
|
||||||
|
|
||||||
|
int last_line_start = 0;
|
||||||
|
for (int i = 0; i < str.size(); ++i)
|
||||||
|
{
|
||||||
|
if (str[i] == '\n')
|
||||||
|
{
|
||||||
|
math::make_max(max_line_width, i - last_line_start);
|
||||||
|
last_line_start = i;
|
||||||
|
++line_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last_line_start + 1 != str.size())
|
||||||
|
{
|
||||||
|
math::make_max(max_line_width, static_cast<int>(str.size()) - last_line_start);
|
||||||
|
++line_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {font_size[0] * max_line_width, font_size[1] * line_count};
|
||||||
}
|
}
|
||||||
|
|
||||||
void painter::text(math::point<float, 2> const & p, std::string_view str, text_options const & opts)
|
void painter::text(math::point<float, 2> const & p, std::string_view str, text_options const & opts)
|
||||||
|
|
@ -489,17 +513,17 @@ namespace psemek::gfx
|
||||||
{
|
{
|
||||||
auto const size = math::pointwise_mult(text_size(str, opts.f), opts.scale);
|
auto const size = math::pointwise_mult(text_size(str, opts.f), opts.scale);
|
||||||
|
|
||||||
math::vector<float, 3> pen { 0.f, 0.f, 0.f };
|
math::vector<float, 3> origin { 0.f, 0.f, 0.f };
|
||||||
|
|
||||||
switch (opts.x)
|
switch (opts.x)
|
||||||
{
|
{
|
||||||
case x_align::left:
|
case x_align::left:
|
||||||
break;
|
break;
|
||||||
case x_align::center:
|
case x_align::center:
|
||||||
pen[0] -= size[0] / 2.f;
|
origin[0] -= size[0] / 2.f;
|
||||||
break;
|
break;
|
||||||
case x_align::right:
|
case x_align::right:
|
||||||
pen[0] -= size[0];
|
origin[0] -= size[0];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw util::unknown_enum_value_exception(opts.x);
|
throw util::unknown_enum_value_exception(opts.x);
|
||||||
|
|
@ -510,15 +534,17 @@ namespace psemek::gfx
|
||||||
case y_align::top:
|
case y_align::top:
|
||||||
break;
|
break;
|
||||||
case y_align::center:
|
case y_align::center:
|
||||||
pen[1] -= size[1] / 2.f;
|
origin[1] -= size[1] / 2.f;
|
||||||
break;
|
break;
|
||||||
case y_align::bottom:
|
case y_align::bottom:
|
||||||
pen[1] -= size[1];
|
origin[1] -= size[1];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw util::unknown_enum_value_exception(opts.y);
|
throw util::unknown_enum_value_exception(opts.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto pen = origin;
|
||||||
|
|
||||||
math::vector<float, 3> const sx = {9.f * opts.scale[0], 0.f, 0.f};
|
math::vector<float, 3> const sx = {9.f * opts.scale[0], 0.f, 0.f};
|
||||||
math::vector<float, 3> const sy = {0.f, 12.f * opts.scale[1], 0.f};
|
math::vector<float, 3> const sy = {0.f, 12.f * opts.scale[1], 0.f};
|
||||||
|
|
||||||
|
|
@ -529,6 +555,13 @@ namespace psemek::gfx
|
||||||
|
|
||||||
for (char c : str)
|
for (char c : str)
|
||||||
{
|
{
|
||||||
|
if (c == '\n')
|
||||||
|
{
|
||||||
|
pen[0] = origin[0];
|
||||||
|
pen += sy;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Guard against unsigned char
|
// Guard against unsigned char
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
|
|
|
||||||
|
|
@ -153,47 +153,47 @@ namespace psemek::gfx
|
||||||
|
|
||||||
void program::uniform_proxy::operator = (math::matrix<float, 2, 2> const & m)
|
void program::uniform_proxy::operator = (math::matrix<float, 2, 2> const & m)
|
||||||
{
|
{
|
||||||
gl::UniformMatrix2fv(location_, 1, gl::TRUE, m.coords);
|
gl::UniformMatrix2fv(location_, 1, gl::TRUE, &m[0][0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void program::uniform_proxy::operator = (math::matrix<float, 2, 3> const & m)
|
void program::uniform_proxy::operator = (math::matrix<float, 2, 3> const & m)
|
||||||
{
|
{
|
||||||
gl::UniformMatrix3x2fv(location_, 1, gl::TRUE, m.coords);
|
gl::UniformMatrix3x2fv(location_, 1, gl::TRUE, &m[0][0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void program::uniform_proxy::operator = (math::matrix<float, 2, 4> const & m)
|
void program::uniform_proxy::operator = (math::matrix<float, 2, 4> const & m)
|
||||||
{
|
{
|
||||||
gl::UniformMatrix4x2fv(location_, 1, gl::TRUE, m.coords);
|
gl::UniformMatrix4x2fv(location_, 1, gl::TRUE, &m[0][0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void program::uniform_proxy::operator = (math::matrix<float, 3, 2> const & m)
|
void program::uniform_proxy::operator = (math::matrix<float, 3, 2> const & m)
|
||||||
{
|
{
|
||||||
gl::UniformMatrix2x3fv(location_, 1, gl::TRUE, m.coords);
|
gl::UniformMatrix2x3fv(location_, 1, gl::TRUE, &m[0][0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void program::uniform_proxy::operator = (math::matrix<float, 3, 3> const & m)
|
void program::uniform_proxy::operator = (math::matrix<float, 3, 3> const & m)
|
||||||
{
|
{
|
||||||
gl::UniformMatrix3fv(location_, 1, gl::TRUE, m.coords);
|
gl::UniformMatrix3fv(location_, 1, gl::TRUE, &m[0][0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void program::uniform_proxy::operator = (math::matrix<float, 3, 4> const & m)
|
void program::uniform_proxy::operator = (math::matrix<float, 3, 4> const & m)
|
||||||
{
|
{
|
||||||
gl::UniformMatrix4x3fv(location_, 1, gl::TRUE, m.coords);
|
gl::UniformMatrix4x3fv(location_, 1, gl::TRUE, &m[0][0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void program::uniform_proxy::operator = (math::matrix<float, 4, 2> const & m)
|
void program::uniform_proxy::operator = (math::matrix<float, 4, 2> const & m)
|
||||||
{
|
{
|
||||||
gl::UniformMatrix2x4fv(location_, 1, gl::TRUE, m.coords);
|
gl::UniformMatrix2x4fv(location_, 1, gl::TRUE, &m[0][0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void program::uniform_proxy::operator = (math::matrix<float, 4, 3> const & m)
|
void program::uniform_proxy::operator = (math::matrix<float, 4, 3> const & m)
|
||||||
{
|
{
|
||||||
gl::UniformMatrix3x4fv(location_, 1, gl::TRUE, m.coords);
|
gl::UniformMatrix3x4fv(location_, 1, gl::TRUE, &m[0][0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void program::uniform_proxy::operator = (math::matrix<float, 4, 4> const & m)
|
void program::uniform_proxy::operator = (math::matrix<float, 4, 4> const & m)
|
||||||
{
|
{
|
||||||
gl::UniformMatrix4fv(location_, 1, gl::TRUE, m.coords);
|
gl::UniformMatrix4fv(location_, 1, gl::TRUE, &m[0][0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void program::uniform_proxy::operator = (math::interval<int> const & i)
|
void program::uniform_proxy::operator = (math::interval<int> const & i)
|
||||||
|
|
|
||||||
|
|
@ -1069,7 +1069,7 @@ void main(){}
|
||||||
camera_distance[i] = dist;
|
camera_distance[i] = dist;
|
||||||
}
|
}
|
||||||
|
|
||||||
util::array<bin, 3> bins({opts.grid_size[0], opts.grid_size[1], opts.grid_size[2]});
|
util::ndarray<bin, 3> bins({opts.grid_size[0], opts.grid_size[1], opts.grid_size[2]});
|
||||||
|
|
||||||
math::box<float, 3> lit_bbox;
|
math::box<float, 3> lit_bbox;
|
||||||
math::box<float, 3> casts_shadow_bbox;
|
math::box<float, 3> casts_shadow_bbox;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/group/permutation.hpp>
|
||||||
#include <psemek/util/range.hpp>
|
#include <psemek/util/range.hpp>
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
@ -55,6 +56,14 @@ namespace psemek::group
|
||||||
return repr_;
|
return repr_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
group::permutation<N, Repr> permutation() const
|
||||||
|
{
|
||||||
|
typename group::permutation<N, Repr>::repr_type repr;
|
||||||
|
for (Repr i = 0; i < N; ++i)
|
||||||
|
repr[i] = (i + repr_) % N;
|
||||||
|
return group::permutation<N, Repr>::from_repr(repr);
|
||||||
|
}
|
||||||
|
|
||||||
struct value_iterator
|
struct value_iterator
|
||||||
{
|
{
|
||||||
using difference_type = Repr;
|
using difference_type = Repr;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <psemek/group/cyclic.hpp>
|
#include <psemek/group/cyclic.hpp>
|
||||||
|
#include <psemek/group/permutation.hpp>
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
@ -87,6 +88,25 @@ namespace psemek::group
|
||||||
return repr_ >= N;
|
return repr_ >= N;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
group::permutation<N, Repr> permutation() const
|
||||||
|
{
|
||||||
|
if (repr_ < N)
|
||||||
|
{
|
||||||
|
typename group::permutation<N, Repr>::repr_type repr;
|
||||||
|
for (Repr i = 0; i < N; ++i)
|
||||||
|
repr[i] = (i + repr_) % N;
|
||||||
|
return group::permutation<N, Repr>::from_repr(repr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
typename group::permutation<N, Repr>::repr_type repr;
|
||||||
|
for (Repr i = 0; i < N; ++i)
|
||||||
|
repr[i] = (N - i) % N;
|
||||||
|
|
||||||
|
return rotation(repr_ % N).permutation() * group::permutation<N, Repr>::from_repr(repr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct value_iterator
|
struct value_iterator
|
||||||
{
|
{
|
||||||
using difference_type = Repr;
|
using difference_type = Repr;
|
||||||
|
|
|
||||||
154
libs/group/include/psemek/group/permutation.hpp
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/util/range.hpp>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <limits>
|
||||||
|
#include <array>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
namespace psemek::group
|
||||||
|
{
|
||||||
|
|
||||||
|
template <std::size_t N, typename Repr = std::size_t>
|
||||||
|
struct permutation
|
||||||
|
{
|
||||||
|
static_assert(std::is_integral_v<Repr> && std::is_unsigned_v<Repr>);
|
||||||
|
static_assert(std::numeric_limits<Repr>::max() > N);
|
||||||
|
|
||||||
|
using repr_type = std::array<Repr, N>;
|
||||||
|
|
||||||
|
static constexpr std::size_t size()
|
||||||
|
{
|
||||||
|
return N;
|
||||||
|
}
|
||||||
|
|
||||||
|
permutation() = default;
|
||||||
|
|
||||||
|
static permutation identity()
|
||||||
|
{
|
||||||
|
return permutation{};
|
||||||
|
}
|
||||||
|
|
||||||
|
static permutation from_repr(repr_type const & value)
|
||||||
|
{
|
||||||
|
return permutation{value};
|
||||||
|
}
|
||||||
|
|
||||||
|
repr_type const & value() const
|
||||||
|
{
|
||||||
|
return repr_;
|
||||||
|
}
|
||||||
|
|
||||||
|
repr_type operator() (repr_type const & value) const
|
||||||
|
{
|
||||||
|
repr_type result;
|
||||||
|
for (Repr i = 0; i < N; ++i)
|
||||||
|
result[repr_[i]] = value[i];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct value_iterator
|
||||||
|
{
|
||||||
|
using difference_type = Repr;
|
||||||
|
using value_type = permutation<N, Repr>;
|
||||||
|
using pointer = value_type *;
|
||||||
|
using reference = value_type &;
|
||||||
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
|
||||||
|
repr_type repr;
|
||||||
|
bool end;
|
||||||
|
|
||||||
|
permutation<N, Repr> operator *() const
|
||||||
|
{
|
||||||
|
return permutation<N, Repr>{repr};
|
||||||
|
}
|
||||||
|
|
||||||
|
value_iterator & operator++()
|
||||||
|
{
|
||||||
|
end = std::next_permutation(repr.begin(), repr.end());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator == (value_iterator const & it1, value_iterator const & it2)
|
||||||
|
{
|
||||||
|
return it1.repr == it2.repr && it1.end == it2.end;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static auto values()
|
||||||
|
{
|
||||||
|
repr_type repr;
|
||||||
|
std::iota(repr.begin(), repr.end(), Repr{0});
|
||||||
|
return util::range{value_iterator{repr, false}, value_iterator{repr, true}};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
repr_type repr_{0};
|
||||||
|
|
||||||
|
explicit permutation(repr_type const & repr)
|
||||||
|
: repr_(repr)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <std::size_t N, typename Repr>
|
||||||
|
bool operator == (permutation<N, Repr> const & g1, permutation<N, Repr> const & g2)
|
||||||
|
{
|
||||||
|
return g1.value() == g2.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N, typename Repr>
|
||||||
|
auto operator <=> (permutation<N, Repr> const & g1, permutation<N, Repr> const & g2)
|
||||||
|
{
|
||||||
|
return g1.value() <=> g2.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N, typename Repr>
|
||||||
|
permutation<N, Repr> operator * (permutation<N, Repr> const & g1, permutation<N, Repr> const & g2)
|
||||||
|
{
|
||||||
|
typename permutation<N, Repr>::repr_type repr;
|
||||||
|
for (Repr i = 0; i < N; ++i)
|
||||||
|
repr[i] = g1.value()[g2.value()[i]];
|
||||||
|
return permutation<N, Repr>::from_repr(repr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N, typename Repr>
|
||||||
|
permutation<N, Repr> inverse(permutation<N, Repr> const & g)
|
||||||
|
{
|
||||||
|
typename permutation<N, Repr>::repr_type repr;
|
||||||
|
for (Repr i = 0; i < N; ++i)
|
||||||
|
repr[g.value()[i]] = i;
|
||||||
|
return permutation<N, Repr>::from_repr(repr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OStream, std::size_t N, typename Repr>
|
||||||
|
OStream & operator << (OStream & os, permutation<N, Repr> const & g)
|
||||||
|
{
|
||||||
|
os << 'S' << N << '(';
|
||||||
|
for (Repr i = 0; i < N; ++i)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
os << ", ";
|
||||||
|
os << g.value()[i];
|
||||||
|
}
|
||||||
|
os << ')';
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OStream, std::size_t N, typename Repr>
|
||||||
|
void write(OStream & out, permutation<N, Repr> const & g)
|
||||||
|
{
|
||||||
|
write(out, g.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename IStream, std::size_t N, typename Repr>
|
||||||
|
void read(IStream & in, permutation<N, Repr> & g)
|
||||||
|
{
|
||||||
|
typename permutation<N, Repr>::repr_type value;
|
||||||
|
read(in, value);
|
||||||
|
g = permutation<N, Repr>::from_repr(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,27 +1,72 @@
|
||||||
#include <psemek/io/file_stream.hpp>
|
#include <psemek/io/file_stream.hpp>
|
||||||
#include <psemek/util/system_error.hpp>
|
#include <psemek/util/system_error.hpp>
|
||||||
|
#include <psemek/util/enum.hpp>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <codecvt>
|
|
||||||
|
|
||||||
namespace psemek::io
|
namespace psemek::io
|
||||||
{
|
{
|
||||||
|
|
||||||
static void throw_fopen [[noreturn]] (std::filesystem::path const & path)
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
void throw_fopen [[noreturn]] (std::filesystem::path const & path)
|
||||||
{
|
{
|
||||||
throw util::system_error(std::error_code{errno, std::system_category()}, "Failed to open " + path.string());
|
throw util::system_error(std::error_code{errno, std::system_category()}, "Failed to open " + path.string());
|
||||||
}
|
}
|
||||||
|
|
||||||
static FILE * safe_fopen(std::filesystem::path const & path, const char * mode)
|
#ifdef _WIN32
|
||||||
|
wchar_t const * fopen_read_mode()
|
||||||
{
|
{
|
||||||
std::string path_str = path.string();
|
return L"rb";
|
||||||
auto f = std::fopen(path_str.c_str(), mode);
|
}
|
||||||
|
|
||||||
|
wchar_t const * fopen_write_mode(unsigned flags)
|
||||||
|
{
|
||||||
|
switch (flags)
|
||||||
|
{
|
||||||
|
case 0: return L"wb";
|
||||||
|
case file_ostream::append: return L"ab";
|
||||||
|
}
|
||||||
|
|
||||||
|
throw util::exception("Unknown file_ostream open flags");
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE * safe_fopen(std::filesystem::path const & path, wchar_t const * mode)
|
||||||
|
{
|
||||||
|
auto f = _wfopen(path.c_str(), mode);
|
||||||
if (!f) throw_fopen(path);
|
if (!f) throw_fopen(path);
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
char const * fopen_read_mode()
|
||||||
|
{
|
||||||
|
return "rb";
|
||||||
|
}
|
||||||
|
|
||||||
|
char const * fopen_write_mode(unsigned flags)
|
||||||
|
{
|
||||||
|
switch (flags)
|
||||||
|
{
|
||||||
|
case 0: return "wb";
|
||||||
|
case file_ostream::append: return "ab";
|
||||||
|
}
|
||||||
|
|
||||||
|
throw util::exception("Unknown file_ostream open flags");
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE * safe_fopen(std::filesystem::path const & path, char const * mode)
|
||||||
|
{
|
||||||
|
auto f = std::fopen(path.c_str(), mode);
|
||||||
|
if (!f) throw_fopen(path);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
file_istream::file_istream(std::filesystem::path const & path)
|
file_istream::file_istream(std::filesystem::path const & path)
|
||||||
: file_{safe_fopen(path.c_str(), "rb")}
|
: file_{safe_fopen(path, fopen_read_mode())}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void file_istream::reset()
|
void file_istream::reset()
|
||||||
|
|
@ -43,18 +88,8 @@ namespace psemek::io
|
||||||
return std::feof(file_) != 0;
|
return std::feof(file_) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char const * fopen_write_mode(unsigned flags)
|
|
||||||
{
|
|
||||||
switch (flags)
|
|
||||||
{
|
|
||||||
case 0: return "wb";
|
|
||||||
case file_ostream::append: return "ab";
|
|
||||||
default: throw util::exception("Unknown file_ostream open flags");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file_ostream::file_ostream(std::filesystem::path const & path, unsigned flags)
|
file_ostream::file_ostream(std::filesystem::path const & path, unsigned flags)
|
||||||
: file_{safe_fopen(path.c_str(), fopen_write_mode(flags))}
|
: file_{safe_fopen(path, fopen_write_mode(flags))}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void file_ostream::reset()
|
void file_ostream::reset()
|
||||||
|
|
|
||||||
6
libs/journal/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
file(GLOB_RECURSE PSEMEK_JOURNAL_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp")
|
||||||
|
file(GLOB_RECURSE PSEMEK_JOURNAL_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp")
|
||||||
|
|
||||||
|
psemek_add_library(psemek-journal ${PSEMEK_JOURNAL_HEADERS} ${PSEMEK_JOURNAL_SOURCES})
|
||||||
|
target_include_directories(psemek-journal PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ${SQLite3_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(psemek-journal PUBLIC psemek-util psemek-log)
|
||||||
31
libs/journal/include/psemek/journal/event.hpp
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace psemek::journal
|
||||||
|
{
|
||||||
|
|
||||||
|
struct event_metadata
|
||||||
|
{
|
||||||
|
std::string source_file;
|
||||||
|
int source_line;
|
||||||
|
std::string name;
|
||||||
|
std::vector<std::string> columns;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct event_data
|
||||||
|
{
|
||||||
|
std::string time;
|
||||||
|
std::vector<std::string> values;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct event
|
||||||
|
{
|
||||||
|
event_metadata metadata;
|
||||||
|
event_data data;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string current_time();
|
||||||
|
|
||||||
|
}
|
||||||
25
libs/journal/include/psemek/journal/journal.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/journal/event.hpp>
|
||||||
|
#include <psemek/util/pimpl.hpp>
|
||||||
|
|
||||||
|
namespace psemek::journal
|
||||||
|
{
|
||||||
|
|
||||||
|
struct journal
|
||||||
|
{
|
||||||
|
journal();
|
||||||
|
~journal();
|
||||||
|
|
||||||
|
bool enabled() const;
|
||||||
|
void set_enabled(bool enabled);
|
||||||
|
|
||||||
|
void log_event(event const & event);
|
||||||
|
|
||||||
|
std::vector<std::pair<event_metadata const *, event_data const *>> select();
|
||||||
|
|
||||||
|
private:
|
||||||
|
psemek_declare_pimpl
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
25
libs/journal/include/psemek/journal/log_event.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/journal/journal.hpp>
|
||||||
|
|
||||||
|
#include <boost/preprocessor/seq/for_each.hpp>
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
#define psemek_journal_log_event_extract_key(key, value) key
|
||||||
|
#define psemek_journal_log_event_extract_value(key, value) std::format("{}", value)
|
||||||
|
|
||||||
|
#define psemek_journal_log_event_single_attribute_key(r, data, elem) psemek_journal_log_event_extract_key elem,
|
||||||
|
#define psemek_journal_log_event_single_attribute_value(r, data, elem) psemek_journal_log_event_extract_value elem,
|
||||||
|
|
||||||
|
#define psemek_journal_log_event(JOURNAL, NAME, ATTRIBUTES) \
|
||||||
|
if ((JOURNAL).enabled()) \
|
||||||
|
(JOURNAL).log_event({{ \
|
||||||
|
.source_file = __FILE__, \
|
||||||
|
.source_line = __LINE__, \
|
||||||
|
.name = NAME, \
|
||||||
|
.columns = { BOOST_PP_SEQ_FOR_EACH(psemek_journal_log_event_single_attribute_key, _, ATTRIBUTES) }, \
|
||||||
|
}, { \
|
||||||
|
.time = ::psemek::journal::current_time(), \
|
||||||
|
.values = { BOOST_PP_SEQ_FOR_EACH(psemek_journal_log_event_single_attribute_value, _, ATTRIBUTES) }, \
|
||||||
|
}})
|
||||||
15
libs/journal/source/event.cpp
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#include <psemek/journal/event.hpp>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
namespace psemek::journal
|
||||||
|
{
|
||||||
|
|
||||||
|
std::string current_time()
|
||||||
|
{
|
||||||
|
const auto now = std::chrono::high_resolution_clock::now();
|
||||||
|
return std::format("{:%FT%TZ}", now);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
100
libs/journal/source/journal.cpp
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
#include <psemek/journal/journal.hpp>
|
||||||
|
#include <psemek/util/hash_table.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace psemek::journal
|
||||||
|
{
|
||||||
|
|
||||||
|
struct journal::impl
|
||||||
|
{
|
||||||
|
struct table
|
||||||
|
{
|
||||||
|
event_metadata metadata;
|
||||||
|
mutable std::vector<event_data> events;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct table_hash
|
||||||
|
{
|
||||||
|
std::uint64_t operator()(std::string const & name) const noexcept
|
||||||
|
{
|
||||||
|
return std::hash<std::string>{}(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t operator()(table const & table) const noexcept
|
||||||
|
{
|
||||||
|
return std::hash<std::string>{}(table.metadata.name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct table_equal
|
||||||
|
{
|
||||||
|
bool operator()(std::string const & name1, table const & table2) const noexcept
|
||||||
|
{
|
||||||
|
return name1 == table2.metadata.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(table const & table1, table const & table2) const noexcept
|
||||||
|
{
|
||||||
|
return table1.metadata.name == table2.metadata.name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool enabled = true;
|
||||||
|
util::hash_set<table, table_hash, table_equal> tables;
|
||||||
|
|
||||||
|
void log_event(event const & event)
|
||||||
|
{
|
||||||
|
if (!enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto table_it = tables.find(event.metadata.name);
|
||||||
|
if (table_it == tables.end())
|
||||||
|
table_it = tables.insert(table{event.metadata, {}}).first;
|
||||||
|
table_it->events.push_back(event.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<event_metadata const *, event_data const *>> select()
|
||||||
|
{
|
||||||
|
if (!enabled)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::vector<std::pair<event_metadata const *, event_data const *>> result;
|
||||||
|
|
||||||
|
for (auto const & table : tables)
|
||||||
|
for (auto const & event : table.events)
|
||||||
|
result.push_back({&table.metadata, &event});
|
||||||
|
|
||||||
|
std::sort(result.begin(), result.end(), [](auto const & e1, auto const & e2){ return e1.second->time < e2.second->time; });
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
journal::journal()
|
||||||
|
: pimpl_(make_impl())
|
||||||
|
{}
|
||||||
|
|
||||||
|
journal::~journal() = default;
|
||||||
|
|
||||||
|
bool journal::enabled() const
|
||||||
|
{
|
||||||
|
return impl().enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void journal::set_enabled(bool enabled)
|
||||||
|
{
|
||||||
|
impl().enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void journal::log_event(event const & event)
|
||||||
|
{
|
||||||
|
impl().log_event(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<event_metadata const *, event_data const *>> journal::select()
|
||||||
|
{
|
||||||
|
return impl().select();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -24,7 +24,7 @@ namespace psemek::log
|
||||||
std::atomic<std::size_t> max_thread_name_length = 3;
|
std::atomic<std::size_t> max_thread_name_length = 3;
|
||||||
|
|
||||||
std::mutex thread_names_mutex;
|
std::mutex thread_names_mutex;
|
||||||
util::hash_map<std::thread::id, std::string> thread_names;
|
util::hash_map<std::thread::id, std::unique_ptr<std::string>> thread_names;
|
||||||
|
|
||||||
std::mutex sinks_mutex;
|
std::mutex sinks_mutex;
|
||||||
std::vector<std::unique_ptr<sink>> sinks;
|
std::vector<std::unique_ptr<sink>> sinks;
|
||||||
|
|
@ -165,10 +165,14 @@ namespace psemek::log
|
||||||
if (it != thread_names.end())
|
if (it != thread_names.end())
|
||||||
throw util::exception("Thread \"" + name + "\" already registered!");
|
throw util::exception("Thread \"" + name + "\" already registered!");
|
||||||
|
|
||||||
thread_names[id] = name;
|
thread_names[id] = std::make_unique<std::string>(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
max_thread_name_length = std::max(max_thread_name_length.load(), name.size());
|
{
|
||||||
|
std::size_t current_max = max_thread_name_length.load();
|
||||||
|
while(current_max < name.size() && !max_thread_name_length.compare_exchange_weak(current_max, name.size()))
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
|
||||||
put_message(level::info, "Thread \"" + name + "\" registered");
|
put_message(level::info, "Thread \"" + name + "\" registered");
|
||||||
}
|
}
|
||||||
|
|
@ -187,7 +191,7 @@ namespace psemek::log
|
||||||
throw util::exception(os.str());
|
throw util::exception(os.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
name = std::move(it->second);
|
name = std::move(*(it->second));
|
||||||
thread_names.erase(it);
|
thread_names.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,7 +242,7 @@ namespace psemek::log
|
||||||
if (it == thread_names.end())
|
if (it == thread_names.end())
|
||||||
thread_name = &unknown_thread_name;
|
thread_name = &unknown_thread_name;
|
||||||
else
|
else
|
||||||
thread_name = &(it->second);
|
thread_name = it->second.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
put_message(l, message, *thread_name);
|
put_message(l, message, *thread_name);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
option(PSEMEK_ROBUST_PREDICATES "Use robust geometric predicates" OFF)
|
option(PSEMEK_ROBUST_PREDICATES "Use robust geometric predicates" OFF)
|
||||||
|
|
||||||
if(NOT (CMAKE_SYSTEM_NAME STREQUAL Emscripten))
|
|
||||||
find_package(Boost REQUIRED)
|
|
||||||
endif()
|
|
||||||
if(PSEMEK_ROBUST_PREDICATES)
|
if(PSEMEK_ROBUST_PREDICATES)
|
||||||
|
find_package(Boost REQUIRED CONFIG)
|
||||||
find_package(GMP REQUIRED)
|
find_package(GMP REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
@ -12,8 +10,9 @@ file(GLOB_RECURSE PSEMEK_GEOM_SOURCES "source/*.cpp")
|
||||||
|
|
||||||
psemek_add_library(psemek-math ${PSEMEK_GEOM_HEADERS} ${PSEMEK_GEOM_SOURCES})
|
psemek_add_library(psemek-math ${PSEMEK_GEOM_HEADERS} ${PSEMEK_GEOM_SOURCES})
|
||||||
target_include_directories(psemek-math PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
target_include_directories(psemek-math PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||||
target_link_libraries(psemek-math PUBLIC psemek-util psemek-group Boost::boost)
|
target_link_libraries(psemek-math PUBLIC psemek-util psemek-group)
|
||||||
if(PSEMEK_ROBUST_PREDICATES)
|
if(PSEMEK_ROBUST_PREDICATES)
|
||||||
|
target_link_libraries(psemek-math PUBLIC Boost::boost)
|
||||||
target_link_libraries(psemek-math PUBLIC gmp)
|
target_link_libraries(psemek-math PUBLIC gmp)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <psemek/math/detail/array.hpp>
|
|
||||||
#include <psemek/math/interval.hpp>
|
#include <psemek/math/interval.hpp>
|
||||||
#include <psemek/math/point.hpp>
|
#include <psemek/math/point.hpp>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
namespace psemek::math
|
namespace psemek::math
|
||||||
{
|
{
|
||||||
|
|
@ -12,7 +12,7 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t N>
|
template <typename T, std::size_t N>
|
||||||
struct box
|
struct box
|
||||||
{
|
{
|
||||||
typename detail::array<interval<T>, N>::type axes;
|
interval<T> axes[N];
|
||||||
|
|
||||||
using point_type = point<T, N>;
|
using point_type = point<T, N>;
|
||||||
using vector_type = vector<T, N>;
|
using vector_type = vector<T, N>;
|
||||||
|
|
@ -348,3 +348,26 @@ namespace psemek::math
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace std
|
||||||
|
{
|
||||||
|
|
||||||
|
template <typename T, std::size_t N, typename Char>
|
||||||
|
struct formatter<::psemek::math::box<T, N>, Char>
|
||||||
|
: formatter<::psemek::math::interval<T>, Char>
|
||||||
|
{
|
||||||
|
using formatter<::psemek::math::interval<T>, Char>::parse;
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(::psemek::math::box<T, N> const & b, FormatContext & ctx) const
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < b.dimension(); ++i)
|
||||||
|
{
|
||||||
|
if (i > 0) ctx.advance_to(std::format_to(ctx.out(), "x"));
|
||||||
|
ctx.advance_to(formatter<::psemek::math::interval<T>, Char>::format(b[i], ctx));
|
||||||
|
}
|
||||||
|
return ctx.out();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <psemek/math/vector.hpp>
|
#include <cstddef>
|
||||||
|
|
||||||
namespace psemek::math
|
namespace psemek::math
|
||||||
{
|
{
|
||||||
|
|
||||||
template <typename T, std::size_t ... Ns>
|
template <template <typename, std::size_t> typename V, typename T, std::size_t ... Ns>
|
||||||
auto concat(vector<T, Ns> const & ... vs)
|
auto concat(V<T, Ns> const & ... vs)
|
||||||
{
|
{
|
||||||
vector<T, (Ns + ...)> result;
|
V<T, (Ns + ...)> result;
|
||||||
|
|
||||||
std::size_t i = 0;
|
std::size_t i = 0;
|
||||||
|
|
||||||
auto apply = [&]<std::size_t N>(vector<T, N> const & v)
|
auto apply = [&]<std::size_t N>(V<T, N> const & v)
|
||||||
{
|
{
|
||||||
for (std::size_t j = 0; j < N;)
|
for (std::size_t j = 0; j < N;)
|
||||||
result[i++] = v[j++];
|
result[i++] = v[j++];
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/math/dynamic.hpp>
|
||||||
#include <psemek/util/exception.hpp>
|
#include <psemek/util/exception.hpp>
|
||||||
|
|
||||||
namespace psemek::math::detail
|
namespace psemek::math::detail
|
||||||
|
|
@ -9,14 +10,46 @@ namespace psemek::math::detail
|
||||||
: util::exception
|
: util::exception
|
||||||
{
|
{
|
||||||
empty_array_exception(util::stacktrace stacktrace = {})
|
empty_array_exception(util::stacktrace stacktrace = {})
|
||||||
: util::exception("Indexing an empty array", std::move(stacktrace))
|
: util::exception("Indexing into a zero-dimensional array", std::move(stacktrace))
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
struct dynamic_dimensions
|
||||||
|
{};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct dynamic_dimensions<dynamic>
|
||||||
|
{
|
||||||
|
std::size_t size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
void check_dynamic_size(dynamic_dimensions<N> d1, dynamic_dimensions<N> d2)
|
||||||
|
{
|
||||||
|
if constexpr (N == dynamic)
|
||||||
|
{
|
||||||
|
if (d1.size != d2.size)
|
||||||
|
throw dynamic_size_mismatch(d1.size, d2.size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t N>
|
template <typename T, std::size_t N>
|
||||||
struct array
|
struct array
|
||||||
{
|
{
|
||||||
using type = T[N];
|
struct type
|
||||||
|
{
|
||||||
|
static constexpr std::size_t size = N;
|
||||||
|
|
||||||
|
T data[N];
|
||||||
|
|
||||||
|
type(dynamic_dimensions<N>){}
|
||||||
|
|
||||||
|
dynamic_dimensions<N> dimensions() const { return {}; }
|
||||||
|
|
||||||
|
T const & operator[](std::size_t i) const { return data[i]; }
|
||||||
|
T & operator[](std::size_t i) { return data[i]; }
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|
@ -24,10 +57,34 @@ namespace psemek::math::detail
|
||||||
{
|
{
|
||||||
struct type
|
struct type
|
||||||
{
|
{
|
||||||
|
static constexpr std::size_t size = 0;
|
||||||
|
|
||||||
|
type(dynamic_dimensions<0>){}
|
||||||
|
|
||||||
|
dynamic_dimensions<0> dimensions() const { return {}; }
|
||||||
|
|
||||||
T const & operator[](std::size_t) const { throw empty_array_exception{}; }
|
T const & operator[](std::size_t) const { throw empty_array_exception{}; }
|
||||||
T & operator[](std::size_t) { throw empty_array_exception{}; }
|
T & operator[](std::size_t) { throw empty_array_exception{}; }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
type operator + (std::size_t) const { return *this; }
|
template <typename T>
|
||||||
|
struct array<T, dynamic>
|
||||||
|
{
|
||||||
|
struct type
|
||||||
|
{
|
||||||
|
std::size_t size;
|
||||||
|
std::unique_ptr<T[]> data;
|
||||||
|
|
||||||
|
type(dynamic_dimensions<dynamic> dimensions)
|
||||||
|
: size(dimensions.size)
|
||||||
|
, data(std::make_unique_for_overwrite<T[]>(dimensions.size))
|
||||||
|
{}
|
||||||
|
|
||||||
|
dynamic_dimensions<dynamic> dimensions() const { return {.size = size}; }
|
||||||
|
|
||||||
|
T const & operator[](std::size_t i) const { return data[i]; }
|
||||||
|
T & operator[](std::size_t i) { return data[i]; }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
242
libs/math/include/psemek/math/detail/array_2d.hpp
Normal file
|
|
@ -0,0 +1,242 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/math/detail/array.hpp>
|
||||||
|
|
||||||
|
namespace psemek::math::detail
|
||||||
|
{
|
||||||
|
|
||||||
|
template <std::size_t R, std::size_t C>
|
||||||
|
struct dynamic_dimensions_2d
|
||||||
|
{
|
||||||
|
dynamic_dimensions<R> rows = {};
|
||||||
|
dynamic_dimensions<C> columns = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <std::size_t R, std::size_t C>
|
||||||
|
void check_dynamic_size(dynamic_dimensions_2d<R, C> d1, dynamic_dimensions_2d<R, C> d2)
|
||||||
|
{
|
||||||
|
check_dynamic_size(d1.rows, d2.rows);
|
||||||
|
check_dynamic_size(d1.columns, d2.columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
|
struct array_2d
|
||||||
|
{
|
||||||
|
struct type
|
||||||
|
{
|
||||||
|
T data[R * C];
|
||||||
|
|
||||||
|
static constexpr std::size_t rows = R;
|
||||||
|
static constexpr std::size_t columns = C;
|
||||||
|
|
||||||
|
type(dynamic_dimensions_2d<R, C>) {}
|
||||||
|
|
||||||
|
dynamic_dimensions_2d<R, C> dimensions() const { return {}; }
|
||||||
|
|
||||||
|
T const * operator[](std::size_t row) const { return data + C * row; }
|
||||||
|
T * operator[](std::size_t row) { return data + C * row; }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, std::size_t R>
|
||||||
|
struct array_2d<T, R, 0>
|
||||||
|
{
|
||||||
|
struct type
|
||||||
|
{
|
||||||
|
static constexpr std::size_t rows = R;
|
||||||
|
static constexpr std::size_t columns = 0;
|
||||||
|
|
||||||
|
type(dynamic_dimensions_2d<R, 0>) {}
|
||||||
|
|
||||||
|
dynamic_dimensions_2d<R, 0> dimensions() const { return {}; }
|
||||||
|
|
||||||
|
T const * operator[](std::size_t) const { throw empty_array_exception{}; }
|
||||||
|
T * operator[](std::size_t) { throw empty_array_exception{}; }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct array_2d<T, dynamic, 0>
|
||||||
|
{
|
||||||
|
struct type
|
||||||
|
{
|
||||||
|
std::size_t rows;
|
||||||
|
static constexpr std::size_t columns = 0;
|
||||||
|
|
||||||
|
type(dynamic_dimensions_2d<dynamic, 0> dimensions)
|
||||||
|
: rows(dimensions.rows.size)
|
||||||
|
{}
|
||||||
|
|
||||||
|
dynamic_dimensions_2d<dynamic, 0> dimensions() const { return {.rows = rows}; }
|
||||||
|
|
||||||
|
T const * operator[](std::size_t) const { throw empty_array_exception{}; }
|
||||||
|
T * operator[](std::size_t) { throw empty_array_exception{}; }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, std::size_t C>
|
||||||
|
struct array_2d<T, 0, C>
|
||||||
|
{
|
||||||
|
struct type
|
||||||
|
{
|
||||||
|
static constexpr std::size_t rows = 0;
|
||||||
|
static constexpr std::size_t columns = C;
|
||||||
|
|
||||||
|
type(dynamic_dimensions_2d<0, C>) {}
|
||||||
|
|
||||||
|
dynamic_dimensions_2d<0, C> dimensions() const { return {}; }
|
||||||
|
|
||||||
|
T const * operator[](std::size_t) const { throw empty_array_exception{}; }
|
||||||
|
T * operator[](std::size_t) { throw empty_array_exception{}; }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct array_2d<T, 0, dynamic>
|
||||||
|
{
|
||||||
|
struct type
|
||||||
|
{
|
||||||
|
static constexpr std::size_t rows = 0;
|
||||||
|
std::size_t columns;
|
||||||
|
|
||||||
|
type(dynamic_dimensions_2d<0, dynamic> dimensions)
|
||||||
|
: columns(dimensions.columns.size)
|
||||||
|
{}
|
||||||
|
|
||||||
|
dynamic_dimensions_2d<0, dynamic> dimensions() const { return {.columns = columns}; }
|
||||||
|
|
||||||
|
T const * operator[](std::size_t) const { throw empty_array_exception{}; }
|
||||||
|
T * operator[](std::size_t) { throw empty_array_exception{}; }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct array_2d<T, 0, 0>
|
||||||
|
{
|
||||||
|
struct type
|
||||||
|
{
|
||||||
|
static constexpr std::size_t rows = 0;
|
||||||
|
static constexpr std::size_t columns = 0;
|
||||||
|
|
||||||
|
type(dynamic_dimensions_2d<0, 0>) {}
|
||||||
|
|
||||||
|
dynamic_dimensions_2d<0, 0> dimensions() const { return {}; }
|
||||||
|
|
||||||
|
T const * operator[](std::size_t) const { throw empty_array_exception{}; }
|
||||||
|
T * operator[](std::size_t) { throw empty_array_exception{}; }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, std::size_t R>
|
||||||
|
struct array_2d<T, R, dynamic>
|
||||||
|
{
|
||||||
|
struct type
|
||||||
|
{
|
||||||
|
static constexpr std::size_t rows = R;
|
||||||
|
std::size_t columns;
|
||||||
|
std::unique_ptr<T[]> data;
|
||||||
|
|
||||||
|
type(dynamic_dimensions_2d<R, dynamic> dimensions)
|
||||||
|
: columns(dimensions.columns.size)
|
||||||
|
, data(std::make_unique_for_overwrite<T[]>(rows * columns))
|
||||||
|
{}
|
||||||
|
|
||||||
|
dynamic_dimensions_2d<R, dynamic> dimensions() const { return {.columns = columns}; }
|
||||||
|
|
||||||
|
T * operator[] (std::size_t row)
|
||||||
|
{
|
||||||
|
return data.get() + row * columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
T const * operator[] (std::size_t row) const
|
||||||
|
{
|
||||||
|
return data.get() + row * columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
type copy() const
|
||||||
|
{
|
||||||
|
type result;
|
||||||
|
result.columns = columns;
|
||||||
|
result.data = std::make_unique_for_overwrite<T[]>(rows * columns);
|
||||||
|
std::copy(data.get(), data.get() + rows * columns, result.data.get());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, std::size_t C>
|
||||||
|
struct array_2d<T, dynamic, C>
|
||||||
|
{
|
||||||
|
struct type
|
||||||
|
{
|
||||||
|
std::size_t rows;
|
||||||
|
static constexpr std::size_t columns = C;
|
||||||
|
std::unique_ptr<T[]> data;
|
||||||
|
|
||||||
|
type(dynamic_dimensions_2d<dynamic, C> dimensions)
|
||||||
|
: rows(dimensions.rows.size)
|
||||||
|
, data(std::make_unique_for_overwrite<T[]>(rows * columns))
|
||||||
|
{}
|
||||||
|
|
||||||
|
dynamic_dimensions_2d<dynamic, C> dimensions() const { return {.rows = rows}; }
|
||||||
|
|
||||||
|
T * operator[] (std::size_t row)
|
||||||
|
{
|
||||||
|
return data.get() + row * columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
T const * operator[] (std::size_t row) const
|
||||||
|
{
|
||||||
|
return data.get() + row * columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
type copy() const
|
||||||
|
{
|
||||||
|
type result;
|
||||||
|
result.rows = rows;
|
||||||
|
result.data = std::make_unique_for_overwrite<T[]>(rows * columns);
|
||||||
|
std::copy(data.get(), data.get() + rows * columns, result.data.get());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct array_2d<T, dynamic, dynamic>
|
||||||
|
{
|
||||||
|
struct type
|
||||||
|
{
|
||||||
|
std::size_t rows;
|
||||||
|
std::size_t columns;
|
||||||
|
std::unique_ptr<T[]> data;
|
||||||
|
|
||||||
|
type(dynamic_dimensions_2d<dynamic, dynamic> dimensions)
|
||||||
|
: rows(dimensions.rows.size)
|
||||||
|
, columns(dimensions.columns.size)
|
||||||
|
, data(std::make_unique_for_overwrite<T[]>(rows * columns))
|
||||||
|
{}
|
||||||
|
|
||||||
|
dynamic_dimensions_2d<dynamic, dynamic> dimensions() const { return {.rows = rows, .columns = columns}; }
|
||||||
|
|
||||||
|
T * operator[] (std::size_t row)
|
||||||
|
{
|
||||||
|
return data.get() + row * columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
T const * operator[] (std::size_t row) const
|
||||||
|
{
|
||||||
|
return data.get() + row * columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
type copy() const
|
||||||
|
{
|
||||||
|
type result;
|
||||||
|
result.rows = rows;
|
||||||
|
result.data = std::make_unique_for_overwrite<T[]>(rows * columns);
|
||||||
|
std::copy(data.get(), data.get() + rows * columns, result.data.get());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include <psemek/math/vector.hpp>
|
#include <psemek/math/vector.hpp>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <compare>
|
||||||
|
|
||||||
namespace psemek::math
|
namespace psemek::math
|
||||||
{
|
{
|
||||||
|
|
@ -148,6 +149,57 @@ namespace psemek::math
|
||||||
return v * inverse(d);
|
return v * inverse(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exists mostly to make max/min/clamp/etc work
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
std::partial_ordering operator <=> (dual<T, N> const & d1, dual<T, N> const & d2)
|
||||||
|
{
|
||||||
|
return d1.scalar <=> d2.scalar;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
dual<T, N> abs(dual<T, N> const & d)
|
||||||
|
{
|
||||||
|
return d.scalar > T{0} ? d : -d;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
dual<T, N> exp(dual<T, N> const & d)
|
||||||
|
{
|
||||||
|
auto e = std::exp(d.scalar);
|
||||||
|
return {e, e * d.delta};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
dual<T, N> log(dual<T, N> const & d)
|
||||||
|
{
|
||||||
|
return {std::log(d.scalar), d.delta / d.scalar};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
dual<T, N> sqrt(dual<T, N> const & d)
|
||||||
|
{
|
||||||
|
auto s = std::sqrt(d.scalar);
|
||||||
|
return {s, d.delta / (T{2} * s)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
dual<T, N> sin(dual<T, N> const & d)
|
||||||
|
{
|
||||||
|
return {std::sin(d.scalar), std::cos(d.scalar) * d.delta};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
dual<T, N> cos(dual<T, N> const & d)
|
||||||
|
{
|
||||||
|
return {std::cos(d.scalar), - std::sin(d.scalar) * d.delta};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
dual<T, N> pow(dual<T, N> const & d1, dual<T, N> const & d2)
|
||||||
|
{
|
||||||
|
return exp(log(d1) * d2);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t N>
|
template <typename T, std::size_t N>
|
||||||
std::ostream & operator << (std::ostream & os, dual<T, N> const & d)
|
std::ostream & operator << (std::ostream & os, dual<T, N> const & d)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
21
libs/math/include/psemek/math/dynamic.hpp
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/util/exception.hpp>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
namespace psemek::math
|
||||||
|
{
|
||||||
|
|
||||||
|
constexpr std::size_t dynamic = static_cast<std::size_t>(-1);
|
||||||
|
|
||||||
|
struct dynamic_size_mismatch
|
||||||
|
: util::exception
|
||||||
|
{
|
||||||
|
dynamic_size_mismatch(std::size_t size1, std::size_t size2, util::stacktrace stacktrace = {})
|
||||||
|
: util::exception(std::format("Dynamic array size mismatch: {} != {}", size1, size2), std::move(stacktrace))
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -4,40 +4,73 @@
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
namespace psemek::math
|
namespace psemek::math
|
||||||
{
|
{
|
||||||
|
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
concept has_min = requires ()
|
||||||
|
{
|
||||||
|
{ T::min() } -> std::same_as<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
concept has_max = requires ()
|
||||||
|
{
|
||||||
|
{ T::max() } -> std::same_as<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Can be specialized in client code
|
// Can be specialized in client code
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct limits
|
struct limits
|
||||||
{
|
{
|
||||||
static constexpr T min()
|
static constexpr T min()
|
||||||
{
|
{
|
||||||
if constexpr (std::is_floating_point_v<T>)
|
if constexpr (detail::has_min<T>)
|
||||||
|
{
|
||||||
|
return T::min();
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_floating_point_v<T>)
|
||||||
{
|
{
|
||||||
return -std::numeric_limits<T>::infinity();
|
return -std::numeric_limits<T>::infinity();
|
||||||
}
|
}
|
||||||
else
|
else if constexpr (std::is_integral_v<T>)
|
||||||
{
|
{
|
||||||
return std::numeric_limits<T>::min();
|
return std::numeric_limits<T>::min();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
static_assert("unknown type");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr T max()
|
static constexpr T max()
|
||||||
{
|
{
|
||||||
if constexpr (std::is_floating_point_v<T>)
|
if constexpr (detail::has_max<T>)
|
||||||
|
{
|
||||||
|
return T::max();
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_floating_point_v<T>)
|
||||||
{
|
{
|
||||||
return std::numeric_limits<T>::infinity();
|
return std::numeric_limits<T>::infinity();
|
||||||
}
|
}
|
||||||
else
|
else if constexpr (std::is_integral_v<T>)
|
||||||
{
|
{
|
||||||
return std::numeric_limits<T>::max();
|
return std::numeric_limits<T>::max();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
static_assert("unknown type");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct interval_iterator
|
struct interval_iterator
|
||||||
{
|
{
|
||||||
|
|
@ -97,7 +130,7 @@ namespace psemek::math
|
||||||
|
|
||||||
T center() const
|
T center() const
|
||||||
{
|
{
|
||||||
return min + (max - min) / 2;
|
return min + (max - min) / T(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
using iterator = interval_iterator<T>;
|
using iterator = interval_iterator<T>;
|
||||||
|
|
@ -302,3 +335,25 @@ namespace psemek::math
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace std
|
||||||
|
{
|
||||||
|
|
||||||
|
template <typename T, typename Char>
|
||||||
|
struct formatter<::psemek::math::interval<T>, Char>
|
||||||
|
: formatter<T, Char>
|
||||||
|
{
|
||||||
|
using formatter<T, Char>::parse;
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(::psemek::math::interval<T> const & i, FormatContext & ctx) const
|
||||||
|
{
|
||||||
|
ctx.advance_to(std::format_to(ctx.out(), "["));
|
||||||
|
ctx.advance_to(formatter<T, Char>::format(i.min, ctx));
|
||||||
|
ctx.advance_to(std::format_to(ctx.out(), " .. "));
|
||||||
|
ctx.advance_to(formatter<T, Char>::format(i.max, ctx));
|
||||||
|
return std::format_to(ctx.out(), "]");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,13 +35,13 @@ namespace psemek::math
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T deg(T x)
|
T deg(T x)
|
||||||
{
|
{
|
||||||
return static_cast<T>((180 * x) / pi);
|
return static_cast<T>((180 * x) / static_cast<T>(pi));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T rad(T x)
|
T rad(T x)
|
||||||
{
|
{
|
||||||
return static_cast<T>((x * pi) / 180);
|
return static_cast<T>((x * static_cast<T>(pi)) / 180);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename X, typename T>
|
template <typename X, typename T>
|
||||||
|
|
@ -128,19 +128,22 @@ namespace psemek::math
|
||||||
return std::pair{x1, x2};
|
return std::pair{x1, x2};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Moves the angle to the range [-pi, pi]
|
||||||
|
template <typename T>
|
||||||
|
T normalize_angle(T a)
|
||||||
|
{
|
||||||
|
while (a < -static_cast<T>(pi))
|
||||||
|
a += static_cast<T>(2 * pi);
|
||||||
|
while (a > static_cast<T>(pi))
|
||||||
|
a -= static_cast<T>(2 * pi);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
// returns (a1 - a0)
|
// returns (a1 - a0)
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T angle_difference(T a0, T a1)
|
T angle_difference(T a0, T a1)
|
||||||
{
|
{
|
||||||
T const x0 = std::cos(a0);
|
return normalize_angle(a1 - a0);
|
||||||
T const x1 = std::cos(a1);
|
|
||||||
T const y0 = std::sin(a0);
|
|
||||||
T const y1 = std::sin(a1);
|
|
||||||
|
|
||||||
T const x = x0 * x1 + y0 * y1;
|
|
||||||
T const y = x0 * y1 - y0 * x1;
|
|
||||||
|
|
||||||
return std::atan2(y, x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|
@ -174,6 +177,12 @@ namespace psemek::math
|
||||||
return (x / y) + ((x % y) == 0 ? 0 : 1);
|
return (x / y) + ((x % y) == 0 ? 0 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T fmod(T x, T m)
|
||||||
|
{
|
||||||
|
return (x >= 0) ? std::fmod(x, m) : (m - std::fmod(-x, m));
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
bool make_min(T & target, T const & source)
|
bool make_min(T & target, T const & source)
|
||||||
{
|
{
|
||||||
|
|
@ -210,13 +219,20 @@ namespace psemek::math
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T smoothstep(T x)
|
T smoothstep(T x)
|
||||||
{
|
{
|
||||||
return x * x * (3 - 2 * x);
|
return x * x * (T{3} - T{2} * x);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T smootherstep(T x)
|
T smootherstep(T x)
|
||||||
{
|
{
|
||||||
return x * x * x * (10 - x * (15 - 6 * x));
|
auto x2 = x * x;
|
||||||
|
return x * x2 * (T{10} - x * T{15} + x2 * T{6});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T inverse_smoothstep(T x)
|
||||||
|
{
|
||||||
|
return T{1} / T{2} - std::sin(std::asin(T{1} - T{2} * x) / T{3});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <psemek/math/detail/array.hpp>
|
#include <psemek/math/detail/array_2d.hpp>
|
||||||
#include <psemek/math/vector.hpp>
|
#include <psemek/math/vector.hpp>
|
||||||
#include <psemek/math/math.hpp>
|
#include <psemek/math/math.hpp>
|
||||||
|
#include <psemek/util/span.hpp>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
|
@ -18,26 +19,64 @@ namespace psemek::math
|
||||||
static constexpr std::size_t static_rows = R;
|
static constexpr std::size_t static_rows = R;
|
||||||
static constexpr std::size_t static_columns = C;
|
static constexpr std::size_t static_columns = C;
|
||||||
|
|
||||||
typename detail::array<T, R * C>::type coords;
|
using dynamic_dimensions_type = detail::dynamic_dimensions_2d<R, C>;
|
||||||
|
|
||||||
|
typename detail::array_2d<T, R, C>::type coords;
|
||||||
|
|
||||||
|
// Internal
|
||||||
|
matrix(dynamic_dimensions_type dimensions);
|
||||||
|
|
||||||
|
matrix();
|
||||||
|
|
||||||
|
template <typename ... Args>
|
||||||
|
matrix(Args && ... args) requires (R != dynamic && C != dynamic && sizeof...(Args) == R * C && detail::all_convertible_to<T, Args...>::value);
|
||||||
|
|
||||||
|
explicit matrix(std::size_t rows) requires(R == dynamic && C != dynamic);
|
||||||
|
explicit matrix(std::size_t columns) requires(R != dynamic && C == dynamic);
|
||||||
|
matrix(std::size_t rows, std::size_t columns) requires(R == dynamic && C == dynamic);
|
||||||
|
|
||||||
std::size_t rows() const
|
std::size_t rows() const
|
||||||
{
|
{
|
||||||
return R;
|
return coords.rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t columns() const
|
std::size_t columns() const
|
||||||
{
|
{
|
||||||
return C;
|
return coords.columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto operator[](std::size_t i)
|
auto operator[](std::size_t i)
|
||||||
{
|
{
|
||||||
return coords + C * i;
|
return coords[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
auto operator[](std::size_t i) const
|
auto operator[](std::size_t i) const
|
||||||
{
|
{
|
||||||
return coords + C * i;
|
return coords[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
util::span<T> values()
|
||||||
|
{
|
||||||
|
return {&coords[0][0], rows() * columns()};
|
||||||
|
}
|
||||||
|
|
||||||
|
util::span<T const> values() const
|
||||||
|
{
|
||||||
|
return {&coords[0][0], rows() * columns()};
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix copy() const
|
||||||
|
{
|
||||||
|
if constexpr (R == dynamic || C == dynamic)
|
||||||
|
{
|
||||||
|
matrix r;
|
||||||
|
r.coords = coords.copy();
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
matrix & operator *= (T const & s);
|
matrix & operator *= (T const & s);
|
||||||
|
|
@ -46,32 +85,72 @@ namespace psemek::math
|
||||||
matrix & operator += (matrix const & v);
|
matrix & operator += (matrix const & v);
|
||||||
matrix & operator -= (matrix const & v);
|
matrix & operator -= (matrix const & v);
|
||||||
|
|
||||||
static matrix zero();
|
static matrix zero(dynamic_dimensions_type dimensions = {});
|
||||||
static matrix identity();
|
static matrix identity(dynamic_dimensions_type dimensions = {});
|
||||||
static matrix scalar(T const & s);
|
static matrix scalar(T const & s, dynamic_dimensions_type dimensions = {});
|
||||||
static matrix diagonal(vector<T, std::min(R, C)> const & d);
|
static matrix diagonal(vector<T, std::min(R, C)> const & d);
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
matrix<T, R, C> matrix<T, R, C>::zero()
|
void check_dynamic_size(matrix<T, R, C> const & m1, matrix<T, R, C> const & m2)
|
||||||
{
|
{
|
||||||
matrix<T, R, C> m;
|
check_dynamic_size(m1.coords.dimensions(), m2.coords.dimensions());
|
||||||
for (std::size_t i = 0; i < R * C; ++i)
|
}
|
||||||
m.coords[i] = 0;
|
|
||||||
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
|
matrix<T, R, C>::matrix(dynamic_dimensions_type dimensions)
|
||||||
|
: coords(dimensions)
|
||||||
|
{}
|
||||||
|
|
||||||
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
|
matrix<T, R, C>::matrix()
|
||||||
|
: matrix(dynamic_dimensions_type{})
|
||||||
|
{}
|
||||||
|
|
||||||
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
|
template <typename ... Args>
|
||||||
|
matrix<T, R, C>::matrix(Args && ... args) requires (R != dynamic && C != dynamic && sizeof...(Args) == R * C && detail::all_convertible_to<T, Args...>::value)
|
||||||
|
: coords({})
|
||||||
|
{
|
||||||
|
auto out = values().begin();
|
||||||
|
((*out++ = args), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
|
matrix<T, R, C>::matrix(std::size_t rows) requires(R == dynamic && C != dynamic)
|
||||||
|
: coords({.rows = rows})
|
||||||
|
{}
|
||||||
|
|
||||||
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
|
matrix<T, R, C>::matrix(std::size_t columns) requires(R != dynamic && C == dynamic)
|
||||||
|
: coords({.columns = columns})
|
||||||
|
{}
|
||||||
|
|
||||||
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
|
matrix<T, R, C>::matrix(std::size_t rows, std::size_t columns) requires(R == dynamic && C == dynamic)
|
||||||
|
: coords({.rows = rows, .columns = columns})
|
||||||
|
{}
|
||||||
|
|
||||||
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
|
matrix<T, R, C> matrix<T, R, C>::zero(dynamic_dimensions_type dimensions)
|
||||||
|
{
|
||||||
|
matrix<T, R, C> m(dimensions);
|
||||||
|
for (auto & v : m.values())
|
||||||
|
v = T(0);
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
matrix<T, R, C> matrix<T, R, C>::identity()
|
matrix<T, R, C> matrix<T, R, C>::identity(dynamic_dimensions_type dimensions)
|
||||||
{
|
{
|
||||||
return scalar(T{1});
|
return scalar(T(1), dimensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
matrix<T, R, C> matrix<T, R, C>::scalar(T const & s)
|
matrix<T, R, C> matrix<T, R, C>::scalar(T const & s, dynamic_dimensions_type dimensions)
|
||||||
{
|
{
|
||||||
matrix<T, R, C> m = zero();
|
matrix<T, R, C> m = zero(dimensions);
|
||||||
for (std::size_t i = 0; i < std::min(R, C); ++i)
|
for (std::size_t i = 0; i < std::min(m.rows(), m.columns()); ++i)
|
||||||
m[i][i] = s;
|
m[i][i] = s;
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +158,17 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
matrix<T, R, C> matrix<T, R, C>::diagonal(vector<T, std::min(R, C)> const & d)
|
matrix<T, R, C> matrix<T, R, C>::diagonal(vector<T, std::min(R, C)> const & d)
|
||||||
{
|
{
|
||||||
matrix<T, R, C> m = zero();
|
dynamic_dimensions_type dimensions;
|
||||||
|
if constexpr (R == dynamic)
|
||||||
|
{
|
||||||
|
dimensions.rows = d.dimension();
|
||||||
|
}
|
||||||
|
if constexpr (C == dynamic)
|
||||||
|
{
|
||||||
|
dimensions.columns = d.dimension();
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix<T, R, C> m = zero(dimensions);
|
||||||
for (std::size_t i = 0; i < std::min(R, C); ++i)
|
for (std::size_t i = 0; i < std::min(R, C); ++i)
|
||||||
m[i][i] = d[i];
|
m[i][i] = d[i];
|
||||||
return m;
|
return m;
|
||||||
|
|
@ -88,21 +177,33 @@ namespace psemek::math
|
||||||
template <typename T1, typename T, std::size_t R, std::size_t C>
|
template <typename T1, typename T, std::size_t R, std::size_t C>
|
||||||
matrix<T1, R, C> cast(matrix<T, R, C> const & m)
|
matrix<T1, R, C> cast(matrix<T, R, C> const & m)
|
||||||
{
|
{
|
||||||
matrix<T1, R, C> r;
|
matrix<T1, R, C> r(m.coords.dimensions());
|
||||||
for (std::size_t i = 0; i < R * C; ++i)
|
|
||||||
r.coords[i] = static_cast<T1>(m.coords[i]);
|
auto out = r.values().begin();
|
||||||
|
for (auto const & value : m.values())
|
||||||
|
*out++ = static_cast<T1>(value);
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
std::strong_ordering operator <=> (matrix<T, R, C> const & m1, matrix<T, R, C> const & m2)
|
std::strong_ordering operator <=> (matrix<T, R, C> const & m1, matrix<T, R, C> const & m2)
|
||||||
{
|
{
|
||||||
for (std::size_t i = 0; i < R * C; ++i)
|
check_dynamic_size(m1, m2);
|
||||||
|
auto begin1 = m1.values().begin();
|
||||||
|
auto begin2 = m2.values().begin();
|
||||||
|
auto end1 = m1.values().end();
|
||||||
|
|
||||||
|
// Intentionally don't use std::lexicographical_compare_three_way to fake strong ordering
|
||||||
|
// (actual ordering might be partial & weak due to NaN's and signed zeros
|
||||||
|
while (begin1 != end1)
|
||||||
{
|
{
|
||||||
if (m1.coords[i] < m2.coords[i])
|
if (*begin1 < *begin2)
|
||||||
return std::strong_ordering::less;
|
return std::strong_ordering::less;
|
||||||
else if (m1.coords[i] > m2.coords[i])
|
else if (*begin1 > *begin2)
|
||||||
return std::strong_ordering::greater;
|
return std::strong_ordering::greater;
|
||||||
|
++begin1;
|
||||||
|
++begin2;
|
||||||
}
|
}
|
||||||
return std::strong_ordering::equal;
|
return std::strong_ordering::equal;
|
||||||
}
|
}
|
||||||
|
|
@ -146,27 +247,30 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
matrix<T, R, C> operator * (matrix<T, R, C> const & m, T const & s)
|
matrix<T, R, C> operator * (matrix<T, R, C> const & m, T const & s)
|
||||||
{
|
{
|
||||||
matrix<T, R, C> r;
|
matrix<T, R, C> r(m.coords.dimensions());
|
||||||
for (std::size_t i = 0; i < R * C; ++i)
|
auto out = r.values().begin();
|
||||||
r.coords[i] = m.coords[i] * s;
|
for (auto const & value : m.values())
|
||||||
|
*out++ = value * s;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
matrix<T, R, C> operator * (T const & s, matrix<T, R, C> const & m)
|
matrix<T, R, C> operator * (T const & s, matrix<T, R, C> const & m)
|
||||||
{
|
{
|
||||||
matrix<T, R, C> r;
|
matrix<T, R, C> r(m.coords.dimensions());
|
||||||
for (std::size_t i = 0; i < R * C; ++i)
|
auto out = r.values().begin();
|
||||||
r.coords[i] = s * m.coords[i];
|
for (auto const & value : m.values())
|
||||||
|
*out++ = s * value;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
matrix<T, R, C> operator / (matrix<T, R, C> const & m, T const & s)
|
matrix<T, R, C> operator / (matrix<T, R, C> const & m, T const & s)
|
||||||
{
|
{
|
||||||
matrix<T, R, C> r;
|
matrix<T, R, C> r(m.coords.dimensions());
|
||||||
for (std::size_t i = 0; i < R * C; ++i)
|
auto out = r.values().begin();
|
||||||
r.coords[i] = m.coords[i] / s;
|
for (auto const & value : m.values())
|
||||||
|
*out++ = value / s;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,27 +291,34 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
matrix<T, R, C> operator - (matrix<T, R, C> const & m)
|
matrix<T, R, C> operator - (matrix<T, R, C> const & m)
|
||||||
{
|
{
|
||||||
matrix<T, R, C> r;
|
matrix<T, R, C> r(m.coords.dimensions());
|
||||||
for (std::size_t i = 0; i < R * C; ++i)
|
auto out = r.values().begin();
|
||||||
r.coords[i] = -m.coords[i];
|
for (auto const & value : m.values())
|
||||||
|
*out++ = -value;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
matrix<T, R, C> operator + (matrix<T, R, C> const & m1, matrix<T, R, C> const & m2)
|
matrix<T, R, C> operator + (matrix<T, R, C> const & m1, matrix<T, R, C> const & m2)
|
||||||
{
|
{
|
||||||
matrix<T, R, C> r;
|
check_dynamic_size(m1, m2);
|
||||||
for (std::size_t i = 0; i < R * C; ++i)
|
matrix<T, R, C> r(m1.coords.dimensions());
|
||||||
r.coords[i] = m1.coords[i] + m2.coords[i];
|
auto in1 = m1.values().begin();
|
||||||
|
auto in2 = m2.values().begin();
|
||||||
|
for (auto & value : r.values())
|
||||||
|
value = (*in1++) + (*in2++);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
matrix<T, R, C> operator - (matrix<T, R, C> const & m1, matrix<T, R, C> const & m2)
|
matrix<T, R, C> operator - (matrix<T, R, C> const & m1, matrix<T, R, C> const & m2)
|
||||||
{
|
{
|
||||||
matrix<T, R, C> r;
|
check_dynamic_size(m1, m2);
|
||||||
for (std::size_t i = 0; i < R * C; ++i)
|
matrix<T, R, C> r(m1.coords.dimensions());
|
||||||
r.coords[i] = m1.coords[i] - m2.coords[i];
|
auto in1 = m1.values().begin();
|
||||||
|
auto in2 = m2.values().begin();
|
||||||
|
for (auto & value : r.values())
|
||||||
|
value = (*in1++) - (*in2++);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,11 +339,11 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
vector<T, R> operator * (matrix<T, R, C> const & m, vector<T, C> const & v)
|
vector<T, R> operator * (matrix<T, R, C> const & m, vector<T, C> const & v)
|
||||||
{
|
{
|
||||||
vector<T, R> r;
|
detail::check_dynamic_size(m.coords.dimensions().columns, v.coords.dimensions());
|
||||||
for (std::size_t i = 0; i < R; ++i)
|
auto r = vector<T, R>::zero(m.coords.dimensions().rows);
|
||||||
|
for (std::size_t i = 0; i < m.rows(); ++i)
|
||||||
{
|
{
|
||||||
r[i] = T{};
|
for (std::size_t j = 0; j < m.columns(); ++j)
|
||||||
for (std::size_t j = 0; j < C; ++j)
|
|
||||||
r[i] += m[i][j] * v[j];
|
r[i] += m[i][j] * v[j];
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
|
|
@ -241,12 +352,12 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
vector<T, C> operator * (vector<T, R> const & v, matrix<T, R, C> const & m)
|
vector<T, C> operator * (vector<T, R> const & v, matrix<T, R, C> const & m)
|
||||||
{
|
{
|
||||||
vector<T, C> r;
|
detail::check_dynamic_size(v.coords.dimensions(), m.coords.dimensions().rows);
|
||||||
for (std::size_t j = 0; j < C; ++j)
|
auto r = vector<T, C>::zero(m.coords.dimensions().columns);
|
||||||
|
for (std::size_t i = 0; i < m.rows(); ++i)
|
||||||
{
|
{
|
||||||
r[j] = T{};
|
for (std::size_t j = 0; j < m.columns(); ++j)
|
||||||
for (std::size_t i = 0; i < R; ++i)
|
r[j] += v[i] * m[i][j];
|
||||||
r[i] += v[j] * m[i][j];
|
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
@ -254,14 +365,13 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t R, std::size_t K, std::size_t C>
|
template <typename T, std::size_t R, std::size_t K, std::size_t C>
|
||||||
matrix<T, R, C> operator * (matrix<T, R, K> const & m1, matrix<T, K, C> const & m2)
|
matrix<T, R, C> operator * (matrix<T, R, K> const & m1, matrix<T, K, C> const & m2)
|
||||||
{
|
{
|
||||||
matrix<T, R, C> r;
|
detail::check_dynamic_size(m1.coords.dimensions().columns, m2.coords.dimensions().rows);
|
||||||
for (std::size_t i = 0; i < R; ++i)
|
auto r = matrix<T, R, C>::zero();
|
||||||
|
for (std::size_t i = 0; i < m1.rows(); ++i)
|
||||||
{
|
{
|
||||||
for (std::size_t j = 0; j < C; ++j)
|
for (std::size_t j = 0; j < m2.columns(); ++j)
|
||||||
{
|
{
|
||||||
r[i][j] = T{};
|
for (std::size_t k = 0; k < m1.columns(); ++k)
|
||||||
|
|
||||||
for (std::size_t k = 0; k < K; ++k)
|
|
||||||
r[i][j] += m1[i][k] * m2[k][j];
|
r[i][j] += m1[i][k] * m2[k][j];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -271,9 +381,9 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
matrix<T, C, R> transpose(matrix<T, R, C> const & m)
|
matrix<T, C, R> transpose(matrix<T, R, C> const & m)
|
||||||
{
|
{
|
||||||
matrix<T, C, R> r;
|
matrix<T, C, R> r({.rows = m.coords.dimensions().columns, .columns = m.coords.dimensions().rows});
|
||||||
for (std::size_t i = 0; i < R; ++i)
|
for (std::size_t i = 0; i < m.rows(); ++i)
|
||||||
for (std::size_t j = 0; j < C; ++j)
|
for (std::size_t j = 0; j < m.columns(); ++j)
|
||||||
r[j][i] = m[i][j];
|
r[j][i] = m[i][j];
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
@ -281,32 +391,35 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t N, typename ... Rows>
|
template <typename T, std::size_t N, typename ... Rows>
|
||||||
auto by_rows(vector<T, N> const & v, Rows const & ... rows)
|
auto by_rows(vector<T, N> const & v, Rows const & ... rows)
|
||||||
{
|
{
|
||||||
|
(check_dynamic_size(v, rows), ...);
|
||||||
|
|
||||||
vector<T, N> m[] = {v, rows...};
|
vector<T, N> m[] = {v, rows...};
|
||||||
|
|
||||||
matrix<T, sizeof...(Rows) + 1, N> result;
|
matrix<T, sizeof...(Rows) + 1, N> r({.columns = v.coords.dimensions()});
|
||||||
for (std::size_t i = 0; i < result.rows(); ++i)
|
for (std::size_t i = 0; i < r.rows(); ++i)
|
||||||
for (std::size_t j = 0; j < result.columns(); ++j)
|
for (std::size_t j = 0; j < r.columns(); ++j)
|
||||||
result[i][j] = m[i][j];
|
r[i][j] = m[i][j];
|
||||||
return result;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t N, typename ... Columns>
|
template <typename T, std::size_t N, typename ... Columns>
|
||||||
auto by_columns(vector<T, N> const & v, Columns const & ... columns)
|
auto by_columns(vector<T, N> const & v, Columns const & ... columns)
|
||||||
{
|
{
|
||||||
|
(check_dynamic_size(v, columns), ...);
|
||||||
vector<T, N> m[] = {v, columns...};
|
vector<T, N> m[] = {v, columns...};
|
||||||
|
|
||||||
matrix<T, N, sizeof...(Columns) + 1> result;
|
matrix<T, N, sizeof...(Columns) + 1> r({.rows = v.coords.dimensions()});
|
||||||
for (std::size_t i = 0; i < result.rows(); ++i)
|
for (std::size_t i = 0; i < r.rows(); ++i)
|
||||||
for (std::size_t j = 0; j < result.columns(); ++j)
|
for (std::size_t j = 0; j < r.columns(); ++j)
|
||||||
result[i][j] = m[j][i];
|
r[i][j] = m[j][i];
|
||||||
return result;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
vector<T, C> row(matrix<T, R, C> const & m, std::size_t i)
|
vector<T, C> row(matrix<T, R, C> const & m, std::size_t i)
|
||||||
{
|
{
|
||||||
vector<T, C> r;
|
vector<T, C> r(m.coords.dimensions().columns);
|
||||||
for (std::size_t j = 0; j < C; ++j)
|
for (std::size_t j = 0; j < m.columns(); ++j)
|
||||||
r[j] = m[i][j];
|
r[j] = m[i][j];
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
@ -314,8 +427,8 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
vector<T, R> column(matrix<T, R, C> const & m, std::size_t j)
|
vector<T, R> column(matrix<T, R, C> const & m, std::size_t j)
|
||||||
{
|
{
|
||||||
vector<T, R> r;
|
vector<T, R> r(m.coords.dimensions().rows);
|
||||||
for (std::size_t i = 0; i < R; ++i)
|
for (std::size_t i = 0; i < m.rows(); ++i)
|
||||||
r[i] = m[i][j];
|
r[i] = m[i][j];
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
@ -324,28 +437,47 @@ namespace psemek::math
|
||||||
T frobenius_norm_sqr(matrix<T, R, C> const & m)
|
T frobenius_norm_sqr(matrix<T, R, C> const & m)
|
||||||
{
|
{
|
||||||
T r{0};
|
T r{0};
|
||||||
for (std::size_t i = 0; i < R; ++i)
|
for (auto const & value : m.values())
|
||||||
for (std::size_t j = 0; j < C; ++j)
|
r += sqr(value);
|
||||||
r += sqr(m[i][j]);
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
|
T linf_norm(matrix<T, R, C> const & m)
|
||||||
|
{
|
||||||
|
using std::abs;
|
||||||
|
using std::max;
|
||||||
|
T r = abs(m[0][0]);
|
||||||
|
for (auto const & value : m.values())
|
||||||
|
make_max(r, abs(value));
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
T frobenius_norm(matrix<T, R, C> const & m)
|
T frobenius_norm(matrix<T, R, C> const & m)
|
||||||
{
|
{
|
||||||
return std::sqrt(frobenius_norm_sqr(m));
|
T const max = linf_norm(m);
|
||||||
|
if (max == T(0)) return max;
|
||||||
|
|
||||||
|
T sum{};
|
||||||
|
for (auto const & value : m.values())
|
||||||
|
sum += sqr(value / max);
|
||||||
|
|
||||||
|
using std::sqrt;
|
||||||
|
return max * sqrt(sum);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
T trace(matrix<T, R, C> const & m)
|
T trace(matrix<T, R, C> const & m)
|
||||||
{
|
{
|
||||||
T r{0};
|
T r{0};
|
||||||
for (std::size_t i = 0; i < std::min(R, C); ++i)
|
for (std::size_t i = 0; i < std::min(m.rows(), m.columns()); ++i)
|
||||||
r += m[i][i];
|
r += m[i][i];
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <std::size_t R1, std::size_t C1, std::size_t R2, std::size_t C2, typename T, std::size_t R, std::size_t C>
|
template <std::size_t R1, std::size_t C1, std::size_t R2, std::size_t C2, typename T, std::size_t R, std::size_t C>
|
||||||
|
requires (R1 != dynamic && C1 != dynamic && R2 != dynamic && C2 != dynamic)
|
||||||
matrix<T, R2 - R1, C2 - C1> submatrix(matrix<T, R, C> const & m)
|
matrix<T, R2 - R1, C2 - C1> submatrix(matrix<T, R, C> const & m)
|
||||||
{
|
{
|
||||||
static_assert(R1 < R2);
|
static_assert(R1 < R2);
|
||||||
|
|
@ -354,13 +486,9 @@ namespace psemek::math
|
||||||
static_assert(C2 <= C);
|
static_assert(C2 <= C);
|
||||||
|
|
||||||
matrix<T, R2 - R1, C2 - C1> result;
|
matrix<T, R2 - R1, C2 - C1> result;
|
||||||
for (std::size_t i = 0; i < R2 - R1; ++i)
|
for (std::size_t i = 0; i < result.rows(); ++i)
|
||||||
{
|
for (std::size_t j = 0; j < result.columns(); ++j)
|
||||||
for (std::size_t j = 0; j < C2 - C1; ++j)
|
|
||||||
{
|
|
||||||
result[i][j] = m[i + R1][j + C1];
|
result[i][j] = m[i + R1][j + C1];
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -377,12 +505,12 @@ namespace psemek::math
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t N>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
matrix<T, N, N> outer_product(vector<T, N> const & v1, vector<T, N> const & v2)
|
matrix<T, R, C> outer_product(vector<T, R> const & v1, vector<T, C> const & v2)
|
||||||
{
|
{
|
||||||
matrix<T, N, N> r;
|
matrix<T, R, C> r({.rows = v1.coords.dimensions(), .columns = v2.coords.dimensions()});
|
||||||
for (std::size_t i = 0; i < N; ++i)
|
for (std::size_t i = 0; i < r.rows(); ++i)
|
||||||
for (std::size_t j = 0; j < N; ++j)
|
for (std::size_t j = 0; j < r.columns(); ++j)
|
||||||
r[i][j] = v1[i] * v2[j];
|
r[i][j] = v1[i] * v2[j];
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
@ -390,9 +518,9 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
std::ostream & operator << (std::ostream & os, matrix<T, R, C> const & m)
|
std::ostream & operator << (std::ostream & os, matrix<T, R, C> const & m)
|
||||||
{
|
{
|
||||||
for (std::size_t i = 0; i < R; ++i)
|
for (std::size_t i = 0; i < m.rows(); ++i)
|
||||||
{
|
{
|
||||||
for (std::size_t j = 0; j < C; ++j)
|
for (std::size_t j = 0; j < m.columns(); ++j)
|
||||||
os << m[i][j] << ' ';
|
os << m[i][j] << ' ';
|
||||||
os << '\n';
|
os << '\n';
|
||||||
}
|
}
|
||||||
|
|
@ -409,9 +537,9 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t R, std::size_t C>
|
template <typename T, std::size_t R, std::size_t C>
|
||||||
std::ostream & operator << (std::ostream & os, setw<T, R, C> const & w)
|
std::ostream & operator << (std::ostream & os, setw<T, R, C> const & w)
|
||||||
{
|
{
|
||||||
for (std::size_t i = 0; i < R; ++i)
|
for (std::size_t i = 0; i < w.m.rows(); ++i)
|
||||||
{
|
{
|
||||||
for (std::size_t j = 0; j < C; ++j)
|
for (std::size_t j = 0; j < w.m.columns(); ++j)
|
||||||
os << std::fixed << std::right << std::setw(w.w) << w.m[i][j] << ' ';
|
os << std::fixed << std::right << std::setw(w.w) << w.m[i][j] << ' ';
|
||||||
os << '\n';
|
os << '\n';
|
||||||
}
|
}
|
||||||
|
|
@ -419,3 +547,30 @@ namespace psemek::math
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace std
|
||||||
|
{
|
||||||
|
|
||||||
|
template <typename T, std::size_t R, std::size_t C, typename Char>
|
||||||
|
struct formatter<::psemek::math::matrix<T, R, C>, Char>
|
||||||
|
: formatter<T, Char>
|
||||||
|
{
|
||||||
|
using formatter<T, Char>::parse;
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(::psemek::math::matrix<T, R, C> const & m, FormatContext & ctx) const
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < m.rows(); ++i)
|
||||||
|
{
|
||||||
|
for (std::size_t j = 0; j < m.columns(); ++j)
|
||||||
|
{
|
||||||
|
ctx.advance_to(formatter<T, Char>::format(m[i][j], ctx));
|
||||||
|
ctx.advance_to(std::format_to(ctx.out(), " "));
|
||||||
|
}
|
||||||
|
ctx.advance_to(std::format_to(ctx.out(), "\n"));
|
||||||
|
}
|
||||||
|
return ctx.out();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include <psemek/math/vector.hpp>
|
#include <psemek/math/vector.hpp>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
namespace psemek::math
|
namespace psemek::math
|
||||||
{
|
{
|
||||||
|
|
@ -18,17 +19,42 @@ namespace psemek::math
|
||||||
|
|
||||||
typename detail::array<T, N>::type coords;
|
typename detail::array<T, N>::type coords;
|
||||||
|
|
||||||
point() = default;
|
using dynamic_dimensions_type = detail::dynamic_dimensions<N>;
|
||||||
|
|
||||||
|
// Internal
|
||||||
|
point(dynamic_dimensions_type dimensions)
|
||||||
|
: coords(dimensions)
|
||||||
|
{}
|
||||||
|
|
||||||
|
point()
|
||||||
|
: coords({})
|
||||||
|
{}
|
||||||
|
|
||||||
|
explicit point(std::size_t size) requires (N == dynamic)
|
||||||
|
: coords({.size = size})
|
||||||
|
{}
|
||||||
|
|
||||||
template <typename ... Args>
|
template <typename ... Args>
|
||||||
requires((sizeof...(Args) == N) && detail::all_convertible_to<T, Args...>::value)
|
requires(N != dynamic && sizeof...(Args) == N && detail::all_convertible_to<T, Args...>::value)
|
||||||
point(Args && ... args)
|
point(Args && ... args)
|
||||||
: coords{ static_cast<T>(std::forward<Args>(args))... }
|
: coords({})
|
||||||
{}
|
{
|
||||||
|
auto out = values().begin();
|
||||||
|
((*out++ = args), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ... Args>
|
||||||
|
requires(N == dynamic && detail::all_convertible_to<T, Args...>::value)
|
||||||
|
point(Args && ... args)
|
||||||
|
: coords({.size = sizeof...(Args)})
|
||||||
|
{
|
||||||
|
auto out = values().begin();
|
||||||
|
((*out++ = args), ...);
|
||||||
|
}
|
||||||
|
|
||||||
std::size_t dimension() const
|
std::size_t dimension() const
|
||||||
{
|
{
|
||||||
return N;
|
return coords.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
T & operator[](std::size_t i)
|
T & operator[](std::size_t i)
|
||||||
|
|
@ -41,29 +67,65 @@ namespace psemek::math
|
||||||
return coords[i];
|
return coords[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
util::span<T> values()
|
||||||
|
{
|
||||||
|
return {&coords[0], dimension()};
|
||||||
|
}
|
||||||
|
|
||||||
|
util::span<T const> values() const
|
||||||
|
{
|
||||||
|
return {&coords[0], dimension()};
|
||||||
|
}
|
||||||
|
|
||||||
|
point copy() const
|
||||||
|
{
|
||||||
|
if constexpr (N == dynamic)
|
||||||
|
{
|
||||||
|
point result;
|
||||||
|
result.coords = coords.copy();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
point & operator += (vector<T, N> const & v);
|
point & operator += (vector<T, N> const & v);
|
||||||
point & operator -= (vector<T, N> const & v);
|
point & operator -= (vector<T, N> const & v);
|
||||||
|
|
||||||
static point zero();
|
static point zero(dynamic_dimensions_type dimensions = {});
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename ... Args>
|
template <typename ... Args>
|
||||||
point(Args && ...) -> point<std::common_type_t<Args...>, sizeof...(Args)>;
|
point(Args && ...) -> point<std::common_type_t<Args...>, sizeof...(Args)>;
|
||||||
|
|
||||||
template <typename T, std::size_t N>
|
template <typename T, std::size_t N>
|
||||||
point<T, N> point<T, N>::zero()
|
void check_dynamic_size(point<T, N> const & p1, point<T, N> const & p2)
|
||||||
{
|
{
|
||||||
point<T, N> p;
|
check_dynamic_size(p1.coords.dimensions(), p2.coords.dimensions());
|
||||||
for (std::size_t i = 0; i < N; ++i)
|
}
|
||||||
p[i] = 0;
|
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
void check_dynamic_size(point<T, N> const & p, vector<T, N> const & v)
|
||||||
|
{
|
||||||
|
check_dynamic_size(p.coords.dimensions(), v.coords.dimensions());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
point<T, N> point<T, N>::zero(dynamic_dimensions_type dimensions)
|
||||||
|
{
|
||||||
|
point<T, N> p(dimensions);
|
||||||
|
for (std::size_t i = 0; i < p.dimension(); ++i)
|
||||||
|
p[i] = T(0);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T1, typename T, std::size_t N>
|
template <typename T1, typename T, std::size_t N>
|
||||||
point<T1, N> cast(point<T, N> const & p)
|
point<T1, N> cast(point<T, N> const & p)
|
||||||
{
|
{
|
||||||
point<T1, N> r;
|
point<T1, N> r(p.coords.dimensions());
|
||||||
for (std::size_t i = 0; i < N; ++i)
|
for (std::size_t i = 0; i < r.dimension(); ++i)
|
||||||
r[i] = T1(p[i]);
|
r[i] = T1(p[i]);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +169,9 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t N>
|
template <typename T, std::size_t N>
|
||||||
std::strong_ordering operator <=> (point<T, N> const & p1, point<T, N> const & p2)
|
std::strong_ordering operator <=> (point<T, N> const & p1, point<T, N> const & p2)
|
||||||
{
|
{
|
||||||
for (std::size_t i = 0; i < N; ++i)
|
check_dynamic_size(p1, p2);
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < p1.dimension(); ++i)
|
||||||
{
|
{
|
||||||
if (p1[i] < p2[i])
|
if (p1[i] < p2[i])
|
||||||
return std::strong_ordering::less;
|
return std::strong_ordering::less;
|
||||||
|
|
@ -120,7 +184,8 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t N>
|
template <typename T, std::size_t N>
|
||||||
point<T, N> operator + (point<T, N> const & p, vector<T, N> const & v)
|
point<T, N> operator + (point<T, N> const & p, vector<T, N> const & v)
|
||||||
{
|
{
|
||||||
point<T, N> r;
|
check_dynamic_size(p, v);
|
||||||
|
point<T, N> r(p.coords.dimensions());
|
||||||
for (std::size_t i = 0; i < N; ++i)
|
for (std::size_t i = 0; i < N; ++i)
|
||||||
r[i] = p[i] + v[i];
|
r[i] = p[i] + v[i];
|
||||||
return r;
|
return r;
|
||||||
|
|
@ -129,7 +194,8 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t N>
|
template <typename T, std::size_t N>
|
||||||
point<T, N> operator + (vector<T, N> const & v, point<T, N> const & p)
|
point<T, N> operator + (vector<T, N> const & v, point<T, N> const & p)
|
||||||
{
|
{
|
||||||
point<T, N> r;
|
check_dynamic_size(p, v);
|
||||||
|
point<T, N> r(p.coords.dimensions());
|
||||||
for (std::size_t i = 0; i < N; ++i)
|
for (std::size_t i = 0; i < N; ++i)
|
||||||
r[i] = v[i] + p[i];
|
r[i] = v[i] + p[i];
|
||||||
return r;
|
return r;
|
||||||
|
|
@ -138,7 +204,8 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t N>
|
template <typename T, std::size_t N>
|
||||||
point<T, N> operator - (point<T, N> const & p, vector<T, N> const & v)
|
point<T, N> operator - (point<T, N> const & p, vector<T, N> const & v)
|
||||||
{
|
{
|
||||||
point<T, N> r;
|
check_dynamic_size(p, v);
|
||||||
|
point<T, N> r(p.coords.dimensions());
|
||||||
for (std::size_t i = 0; i < N; ++i)
|
for (std::size_t i = 0; i < N; ++i)
|
||||||
r[i] = p[i] - v[i];
|
r[i] = p[i] - v[i];
|
||||||
return r;
|
return r;
|
||||||
|
|
@ -147,7 +214,8 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t N>
|
template <typename T, std::size_t N>
|
||||||
vector<T, N> operator - (point<T, N> const & p1, point<T, N> const & p2)
|
vector<T, N> operator - (point<T, N> const & p1, point<T, N> const & p2)
|
||||||
{
|
{
|
||||||
vector<T, N> r;
|
check_dynamic_size(p1, p2);
|
||||||
|
vector<T, N> r(p1.coords.dimensions());
|
||||||
for (std::size_t i = 0; i < N; ++i)
|
for (std::size_t i = 0; i < N; ++i)
|
||||||
r[i] = p1[i] - p2[i];
|
r[i] = p1[i] - p2[i];
|
||||||
return r;
|
return r;
|
||||||
|
|
@ -198,8 +266,14 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t N>
|
template <typename T, std::size_t N>
|
||||||
std::ostream & operator << (std::ostream & os, point<T, N> const & p)
|
std::ostream & operator << (std::ostream & os, point<T, N> const & p)
|
||||||
{
|
{
|
||||||
|
if constexpr (N == 0)
|
||||||
|
{
|
||||||
|
os << "()";
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
os << '(' << p[0];
|
os << '(' << p[0];
|
||||||
for (std::size_t i = 1; i < N; ++i)
|
for (std::size_t i = 1; i < p.dimension(); ++i)
|
||||||
os << ", " << p[i];
|
os << ", " << p[i];
|
||||||
os << ')';
|
os << ')';
|
||||||
return os;
|
return os;
|
||||||
|
|
@ -208,7 +282,7 @@ namespace psemek::math
|
||||||
template <typename T, std::size_t N>
|
template <typename T, std::size_t N>
|
||||||
bool isfinite(point<T, N> const & p)
|
bool isfinite(point<T, N> const & p)
|
||||||
{
|
{
|
||||||
for (std::size_t i = 0; i < N; ++i)
|
for (std::size_t i = 0; i < p.dimension(); ++i)
|
||||||
if (!std::isfinite(p[i]))
|
if (!std::isfinite(p[i]))
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -222,14 +296,40 @@ namespace std
|
||||||
template <typename T, std::size_t N>
|
template <typename T, std::size_t N>
|
||||||
struct hash<::psemek::math::point<T, N>>
|
struct hash<::psemek::math::point<T, N>>
|
||||||
{
|
{
|
||||||
std::uint64_t operator()(::psemek::math::point<T, N> const & v) const noexcept
|
std::uint64_t operator()(::psemek::math::point<T, N> const & p) const noexcept
|
||||||
{
|
{
|
||||||
hash<T> h;
|
hash<T> h;
|
||||||
std::uint64_t r = 0;
|
std::uint64_t r = 0;
|
||||||
for (std::size_t i = 0; i < N; ++i)
|
for (std::size_t i = 0; i < N; ++i)
|
||||||
::psemek::util::hash_combine(r, h(v[i]));
|
::psemek::util::hash_combine(r, h(p[i]));
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T, std::size_t N, typename Char>
|
||||||
|
struct formatter<::psemek::math::point<T, N>, Char>
|
||||||
|
: formatter<T, Char>
|
||||||
|
{
|
||||||
|
using formatter<T, Char>::parse;
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(::psemek::math::point<T, N> const & p, FormatContext & ctx) const
|
||||||
|
{
|
||||||
|
if constexpr (N == 0)
|
||||||
|
{
|
||||||
|
return std::format_to(ctx.out(), "()");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ctx.advance_to(std::format_to(ctx.out(), "("));
|
||||||
|
for (std::size_t i = 0; i < p.dimension(); ++i)
|
||||||
|
{
|
||||||
|
if (i > 0) ctx.advance_to(std::format_to(ctx.out(), ", "));
|
||||||
|
ctx.advance_to(formatter<T, Char>::format(p[i], ctx));
|
||||||
|
}
|
||||||
|
return std::format_to(ctx.out(), ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@
|
||||||
#include <psemek/math/matrix.hpp>
|
#include <psemek/math/matrix.hpp>
|
||||||
#include <psemek/math/interval.hpp>
|
#include <psemek/math/interval.hpp>
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
|
||||||
namespace psemek::math
|
namespace psemek::math
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
@ -256,28 +258,25 @@ namespace psemek::math
|
||||||
template <typename T>
|
template <typename T>
|
||||||
quaternion<T> slerp(quaternion<T> const & q0, quaternion<T> const & q1, T const & t)
|
quaternion<T> slerp(quaternion<T> const & q0, quaternion<T> const & q1, T const & t)
|
||||||
{
|
{
|
||||||
// threshold is chosen so that for abs(x) < threshold the second term in
|
using std::sin;
|
||||||
// sin(x) Taylor series is less than the minimum value representable by T
|
using std::acos;
|
||||||
static auto const threshold = std::pow(6 * std::numeric_limits<T>::min(), T{1}/T{3});
|
using std::abs;
|
||||||
|
|
||||||
auto const d = clamp(dot(normalized(q0.coords), normalized(q1.coords)), {T(-1), T(1)});
|
auto const d = dot(q0.coords, q1.coords);
|
||||||
auto const omega = std::acos(std::abs(d));
|
|
||||||
|
// prevent division by zero
|
||||||
|
if (d >= T{1})
|
||||||
|
return quaternion<T>{lerp(q0.coords, q1.coords, t)};
|
||||||
|
|
||||||
|
auto const omega = acos(abs(d));
|
||||||
|
|
||||||
// NB: the case of omega ~ pi is ambiguous and isn't handled in any special way
|
// NB: the case of omega ~ pi is ambiguous and isn't handled in any special way
|
||||||
|
|
||||||
if (std::abs(omega) < threshold)
|
auto const s = sin(omega);
|
||||||
{
|
auto const w0 = sin((1 - t) * omega) / s;
|
||||||
// prevent division by zero
|
auto const w1 = sin(t * omega) / s * ((d > T{0}) ? T{1} : -T{1});
|
||||||
return quaternion<T>{normalized(lerp(q0.coords, q1.coords, t))};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto const s = std::sin(omega);
|
|
||||||
auto const w0 = std::sin((1 - t) * omega) / s;
|
|
||||||
auto const w1 = std::sin(t * omega) / s * ((d > 0) ? 1 : -1);
|
|
||||||
return quaternion<T>{q0.coords * w0 + q1.coords * w1};
|
return quaternion<T>{q0.coords * w0 + q1.coords * w1};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
quaternion<T> exp(quaternion<T> const & q)
|
quaternion<T> exp(quaternion<T> const & q)
|
||||||
|
|
@ -338,3 +337,22 @@ namespace psemek::math
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace std
|
||||||
|
{
|
||||||
|
|
||||||
|
template <typename T, typename Char>
|
||||||
|
struct formatter<::psemek::math::quaternion<T>, Char>
|
||||||
|
: formatter<::psemek::math::vector<T, 4>>
|
||||||
|
{
|
||||||
|
using formatter<::psemek::math::vector<T, 4>>::parse;
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(::psemek::math::quaternion<T> const & q, FormatContext & ctx) const
|
||||||
|
{
|
||||||
|
formatter<::psemek::math::vector<T, 4>>::format(q.coords, ctx);
|
||||||
|
return ctx.out();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,12 @@ namespace psemek::math
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T1, typename T, std::size_t N>
|
||||||
|
ray<T1, N> cast(ray<T, N> const & r)
|
||||||
|
{
|
||||||
|
return {cast<T1>(r.origin), cast<T1>(r.direction)};
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T, std::size_t N>
|
template <typename T, std::size_t N>
|
||||||
std::ostream & operator << (std::ostream & os, ray<T, N> const & r)
|
std::ostream & operator << (std::ostream & os, ray<T, N> const & r)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ namespace psemek::math
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
#pragma GCC diagnostic ignored "-Warray-bounds"
|
#pragma GCC diagnostic ignored "-Warray-bounds"
|
||||||
|
|
||||||
template <std::size_t ... I>
|
template <int ... I>
|
||||||
constexpr auto swizzle = [](auto const & x){
|
constexpr auto swizzle = [](auto const & x){
|
||||||
static_assert(((I == -1 || I < std::decay_t<decltype(x)>::static_dimension) && ...));
|
static_assert(((I == -1 || I < std::decay_t<decltype(x)>::static_dimension) && ...));
|
||||||
using result = typename detail::swizzle_rebind<std::decay_t<decltype(x)>>::template type<sizeof...(I)>;
|
using result = typename detail::swizzle_rebind<std::decay_t<decltype(x)>>::template type<sizeof...(I)>;
|
||||||
|
|
|
||||||
88
libs/math/include/psemek/math/trs.hpp
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/math/scale.hpp>
|
||||||
|
#include <psemek/math/rotation.hpp>
|
||||||
|
#include <psemek/math/translation.hpp>
|
||||||
|
|
||||||
|
namespace psemek::math
|
||||||
|
{
|
||||||
|
|
||||||
|
// NB: composition of TRS triplets is not defined as the result
|
||||||
|
// might not be expressible in TRS form
|
||||||
|
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
struct trs;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct trs<T, 3>
|
||||||
|
{
|
||||||
|
vector<T, 3> translation = {T{0}, T{0}, T{0}};
|
||||||
|
quaternion<T> rotation = quaternion<T>::identity();
|
||||||
|
vector<T, 3> scale = {T{1}, T{1}, T{1}};
|
||||||
|
|
||||||
|
static trs<T, 3> identity()
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static trs<T, 3> from(matrix<T, 3, 4> const & m)
|
||||||
|
{
|
||||||
|
trs<T, 3> result;
|
||||||
|
|
||||||
|
result.translation[0] = m[0][3];
|
||||||
|
result.translation[1] = m[1][3];
|
||||||
|
result.translation[2] = m[2][3];
|
||||||
|
|
||||||
|
result.scale[0] = length(vector{m[0][0], m[1][0], m[2][0]});
|
||||||
|
result.scale[1] = length(vector{m[0][1], m[1][1], m[2][1]});
|
||||||
|
result.scale[2] = length(vector{m[0][2], m[1][2], m[2][2]});
|
||||||
|
|
||||||
|
matrix<T, 3, 3> r;
|
||||||
|
for (int i = 0; i < 3; ++i)
|
||||||
|
for (int j = 0; j < 3; ++j)
|
||||||
|
r[i][j] = m[i][j] / result.scale[j];
|
||||||
|
|
||||||
|
result.rotation = quaternion<T>::rotation(r);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix<T, 3, 4> affine_matrix() const
|
||||||
|
{
|
||||||
|
return transform().affine_matrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix<T, 3, 3> linear_matrix() const
|
||||||
|
{
|
||||||
|
return transform().linear_matrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<T, 3> translation_vector() const
|
||||||
|
{
|
||||||
|
return translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix<T, 4, 4> homogeneous_matrix() const
|
||||||
|
{
|
||||||
|
return transform().homogeneous_matrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<T, 3> operator()(vector<T, 3> const & v) const
|
||||||
|
{
|
||||||
|
return rotate(rotation, pointwise_mult(v, scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
point<T, 3> operator()(point<T, 3> const & p) const
|
||||||
|
{
|
||||||
|
return p.zero() + rotate(rotation, pointwise_mult(p - p.zero(), scale)) + translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
affine_transform<T, 3, 3> transform() const
|
||||||
|
{
|
||||||
|
return math::translation(translation).transform()
|
||||||
|
* quaternion_rotation(rotation).transform()
|
||||||
|
* math::scale(scale).transform();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||