Compare commits
2 Commits
main
...
efficient-
| Author | SHA1 | Date | |
|---|---|---|---|
|
592c4b6cc0
|
|||
|
c7361fe47e
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -6,8 +6,3 @@ cmake-build-release
|
|||||||
/.gdb_history
|
/.gdb_history
|
||||||
/valgrind.log
|
/valgrind.log
|
||||||
.idea
|
.idea
|
||||||
/perf.data
|
|
||||||
/perf.data.old
|
|
||||||
/clusters.puzzle
|
|
||||||
/benchs.json
|
|
||||||
/benchs.old.json
|
|
||||||
|
|||||||
@ -1,67 +1,54 @@
|
|||||||
cmake_minimum_required(VERSION 3.28)
|
cmake_minimum_required(VERSION 3.25)
|
||||||
project(MassSprings)
|
project(MassSprings)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 26)
|
set(CMAKE_CXX_STANDARD 26)
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
# Disable boost warning because our cmake/boost are recent enough
|
|
||||||
if(POLICY CMP0167)
|
if(POLICY CMP0167)
|
||||||
cmake_policy(SET CMP0167 NEW)
|
cmake_policy(SET CMP0167 NEW)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
option(DISABLE_THREADPOOL "Disable additional physics threads" OFF)
|
|
||||||
option(DISABLE_BACKWARD "Disable backward stacktrace printer" OFF)
|
option(DISABLE_BACKWARD "Disable backward stacktrace printer" OFF)
|
||||||
option(DISABLE_TRACY "Disable the Tracy profiler client" OFF)
|
option(DISABLE_TRACY "Disable the Tracy profiler client" ON)
|
||||||
option(DISABLE_TESTS "Disable building tests" OFF)
|
option(DISABLE_TESTS "Disable building and running tests" ON)
|
||||||
option(DISABLE_BENCH "Disable building benchmarks" OFF)
|
|
||||||
|
|
||||||
# Headers + Sources (excluding main.cpp)
|
# Headers + Sources
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
src/backward.cpp
|
src/backward.cpp
|
||||||
src/bits.cpp
|
|
||||||
src/cpu_layout_engine.cpp
|
|
||||||
src/cpu_spring_system.cpp
|
|
||||||
src/graph_distances.cpp
|
src/graph_distances.cpp
|
||||||
src/input_handler.cpp
|
src/input_handler.cpp
|
||||||
src/load_save.cpp
|
src/mass_spring_system.cpp
|
||||||
src/octree.cpp
|
src/octree.cpp
|
||||||
src/orbit_camera.cpp
|
src/orbit_camera.cpp
|
||||||
src/puzzle.cpp
|
|
||||||
src/renderer.cpp
|
src/renderer.cpp
|
||||||
src/state_manager.cpp
|
src/state_manager.cpp
|
||||||
|
src/threaded_physics.cpp
|
||||||
src/user_interface.cpp
|
src/user_interface.cpp
|
||||||
|
src/puzzle.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Libraries
|
# Libraries
|
||||||
include(FetchContent)
|
|
||||||
find_package(raylib REQUIRED)
|
find_package(raylib REQUIRED)
|
||||||
find_package(GLEW REQUIRED)
|
find_package(Boost REQUIRED)
|
||||||
find_package(libmorton REQUIRED)
|
set(LIBS raylib Boost::headers)
|
||||||
find_package(Boost COMPONENTS program_options REQUIRED)
|
|
||||||
set(LIBS raylib GLEW::GLEW Boost::headers Boost::program_options)
|
|
||||||
set(FLAGS "")
|
set(FLAGS "")
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
list(APPEND LIBS opengl32 gdi32 winmm)
|
list(APPEND LIBS opengl32 gdi32 winmm)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT DISABLE_THREADPOOL)
|
include(FetchContent)
|
||||||
list(APPEND FLAGS THREADPOOL)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT DISABLE_BACKWARD)
|
if(NOT DISABLE_BACKWARD)
|
||||||
find_package(Backward REQUIRED)
|
find_package(Backward REQUIRED)
|
||||||
|
|
||||||
list(APPEND LIBS Backward::Backward)
|
list(APPEND LIBS Backward::Backward)
|
||||||
list(APPEND FLAGS BACKWARD)
|
list(APPEND FLAGS BACKWARD)
|
||||||
|
|
||||||
message("-- BACKWARD: Enabled")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT DISABLE_TRACY)
|
if(NOT DISABLE_TRACY)
|
||||||
FetchContent_Declare(tracy
|
FetchContent_Declare(tracy
|
||||||
GIT_REPOSITORY https://github.com/wolfpld/tracy.git
|
GIT_REPOSITORY https://github.com/wolfpld/tracy.git
|
||||||
GIT_TAG v0.13.1
|
GIT_TAG v0.11.1
|
||||||
GIT_SHALLOW TRUE
|
GIT_SHALLOW TRUE
|
||||||
GIT_PROGRESS TRUE
|
GIT_PROGRESS TRUE
|
||||||
)
|
)
|
||||||
@ -71,15 +58,12 @@ if(NOT DISABLE_TRACY)
|
|||||||
|
|
||||||
list(APPEND LIBS TracyClient)
|
list(APPEND LIBS TracyClient)
|
||||||
list(APPEND FLAGS TRACY)
|
list(APPEND FLAGS TRACY)
|
||||||
|
|
||||||
message("-- TRACY: Enabled")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Set this after fetching tracy to hide tracy's warnings.
|
# Set this after fetching tracy to hide tracy's warnings
|
||||||
# We set -Wno-alloc-size-larger-than because it prevents BS::thread_pool from building with current gcc
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wfloat-equal -Wundef -Wshadow -Wpointer-arith -Wcast-align -Wno-unused-parameter -Wunreachable-code")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wfloat-equal -Wundef -Wshadow -Wpointer-arith -Wcast-align -Wno-unused-parameter -Wunreachable-code -Wno-alloc-size-larger-than")
|
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -ggdb -O0")
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -ggdb -O0")
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ggdb -O3 -ffast-math -march=native")
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ggdb -Ofast -march=native")
|
||||||
|
|
||||||
message("-- CMAKE_C_FLAGS: ${CMAKE_C_FLAGS}")
|
message("-- CMAKE_C_FLAGS: ${CMAKE_C_FLAGS}")
|
||||||
message("-- CMAKE_C_FLAGS_DEBUG: ${CMAKE_C_FLAGS_DEBUG}")
|
message("-- CMAKE_C_FLAGS_DEBUG: ${CMAKE_C_FLAGS_DEBUG}")
|
||||||
@ -94,54 +78,36 @@ target_include_directories(masssprings PRIVATE include)
|
|||||||
target_link_libraries(masssprings PRIVATE ${LIBS})
|
target_link_libraries(masssprings PRIVATE ${LIBS})
|
||||||
target_compile_definitions(masssprings PRIVATE ${FLAGS})
|
target_compile_definitions(masssprings PRIVATE ${FLAGS})
|
||||||
|
|
||||||
# Testing
|
# Testing sources
|
||||||
if(NOT DISABLE_TESTS AND NOT WIN32)
|
if(NOT DISABLE_TESTS AND NOT WIN32)
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
||||||
FetchContent_Declare(Catch2
|
FetchContent_Declare(Catch2
|
||||||
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||||
GIT_TAG v3.13.0
|
GIT_TAG v3.13.0
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(Catch2)
|
FetchContent_MakeAvailable(Catch2)
|
||||||
|
|
||||||
set(TEST_SOURCES
|
set(TEST_SOURCES
|
||||||
test/bits.cpp
|
test/bits.cpp
|
||||||
test/bitmap.cpp
|
|
||||||
test/bitmap_find_first_empty.cpp
|
|
||||||
# test/puzzle.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(tests ${TEST_SOURCES} ${SOURCES})
|
add_executable(tests ${TEST_SOURCES} ${SOURCES})
|
||||||
target_include_directories(tests PRIVATE include)
|
target_include_directories(tests PRIVATE include)
|
||||||
target_link_libraries(tests Catch2::Catch2WithMain raylib GLEW::GLEW)
|
target_link_libraries(tests Catch2::Catch2WithMain raylib)
|
||||||
|
|
||||||
include(Catch)
|
include(Catch)
|
||||||
catch_discover_tests(tests)
|
catch_discover_tests(tests)
|
||||||
|
|
||||||
message("-- TESTS: Enabled")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Benchmarking
|
|
||||||
if(NOT DISABLE_BENCH AND NOT WIN32)
|
|
||||||
find_package(benchmark REQUIRED)
|
|
||||||
|
|
||||||
set(BENCH_SOURCES
|
|
||||||
benchmark/state_space.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
add_executable(benchmarks ${BENCH_SOURCES} ${SOURCES})
|
|
||||||
target_include_directories(benchmarks PRIVATE include)
|
|
||||||
target_link_libraries(benchmarks benchmark raylib GLEW::GLEW)
|
|
||||||
|
|
||||||
message("-- BENCHMARKS: Enabled")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# LTO
|
# LTO
|
||||||
|
#if(NOT WIN32)
|
||||||
include(CheckIPOSupported)
|
include(CheckIPOSupported)
|
||||||
check_ipo_supported(RESULT supported OUTPUT error)
|
check_ipo_supported(RESULT supported OUTPUT error)
|
||||||
if(supported)
|
if(supported)
|
||||||
message("-- IPO/LTO: Enabled")
|
message(STATUS "IPO / LTO enabled")
|
||||||
set_property(TARGET masssprings PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
|
set_property(TARGET masssprings PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||||
else()
|
else()
|
||||||
message("-- IPO/LTO: Disabled")
|
message(STATUS "IPO / LTO not supported")
|
||||||
endif()
|
endif()
|
||||||
|
#endif()
|
||||||
@ -1,212 +0,0 @@
|
|||||||
// ReSharper disable CppTooWideScope
|
|
||||||
// ReSharper disable CppDFAUnreadVariable
|
|
||||||
#include "puzzle.hpp"
|
|
||||||
|
|
||||||
#include <random>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <benchmark/benchmark.h>
|
|
||||||
#include <boost/unordered/unordered_flat_map.hpp>
|
|
||||||
|
|
||||||
static std::vector<std::string> puzzles = {
|
|
||||||
// 0: RushHour 1
|
|
||||||
"S:[6x6] G:[4,2] M:[R] B:[{3x1 _ _ _ _ 1x3} {_ _ _ _ _ _} {_ _ 1x2 2X1 _ _} {_ _ _ 1x2 2x1 _} {1x2 _ 1x2 _ 2x1 _} {_ _ _ 3x1 _ _}]",
|
|
||||||
// 1: RushHour 2
|
|
||||||
"S:[6x6] G:[4,2] M:[R] B:[{1x2 3x1 _ _ 1x2 1x3} {_ 3x1 _ _ _ _} {2X1 _ 1x2 1x2 1x2 _} {2x1 _ _ _ _ _} {_ _ _ 1x2 2x1 _} {_ _ _ _ 2x1 _}]",
|
|
||||||
// 2: RushHour 3
|
|
||||||
"S:[6x6] G:[4,2] M:[R] B:[{3x1 _ _ 1x2 _ _} {1x2 2x1 _ _ _ 1x2} {_ 2X1 _ 1x2 1x2 _} {2x1 _ 1x2 _ _ 1x2} {_ _ _ 2x1 _ _} {_ 2x1 _ 2x1 _ _}]",
|
|
||||||
// 3: RushHour 4
|
|
||||||
"S:[6x6] G:[4,2] M:[R] B:[{1x3 2x1 _ _ 1x2 _} {_ 1x2 1x2 _ _ 1x3} {_ _ _ 2X1 _ _} {3x1 _ _ 1x2 _ _} {_ _ 1x2 _ 2x1 _} {2x1 _ _ 2x1 _ _}]",
|
|
||||||
// 4: RushHour + Walls 1
|
|
||||||
"S:[6x6] G:[4,2] M:[R] B:[{1x2 2x1 _ 1*1 _ _} {_ _ _ 1x2 2x1 _} {1x2 2X1 _ _ _ _} {_ _ 1x2 2x1 _ 1x3} {2x1 _ _ _ _ _} {2x1 _ 3x1 _ _ _}]",
|
|
||||||
// 5: RushHour + Walls 2
|
|
||||||
"S:[6x6] G:[4,2] M:[R] B:[{2x1 _ _ 1x2 1x2 1*1} {3x1 _ _ _ _ _} {1x2 2X1 _ 1x2 _ _} {_ _ 1x2 _ 2x1 _} {_ _ _ 2x1 _ 1x2} {_ _ 2x1 _ 1*1 _}]",
|
|
||||||
// 6: Dad's Puzzler
|
|
||||||
"S:[4x5] G:[0,3] M:[F] B:[{2X2 _ 2x1 _} {_ _ 2x1 _} {1x1 1x1 _ _} {1x2 1x2 2x1 _} {_ _ 2x1 _}]",
|
|
||||||
// 7: Nine Blocks
|
|
||||||
"S:[4x5] G:[0,3] M:[F] B:[{1x2 1x2 _ _} {_ _ 2x1 _} {1x2 1x2 2x1 _} {_ _ 2X2 _} {1x1 1x1 _ _}]",
|
|
||||||
// 8: Quzzle
|
|
||||||
"S:[4x5] G:[2,0] M:[F] B:[{2X2 _ 2x1 _} {_ _ 1x2 1x2} {_ _ _ _} {1x2 2x1 _ 1x1} {_ 2x1 _ 1x1}]",
|
|
||||||
// 9: Thin Klotski
|
|
||||||
"S:[4x5] G:[1,4] M:[F] B:[{1x2 _ 2X1 _} {_ 2x2 _ 1x1} {_ _ _ 1x1} {2x2 _ 1x1 1x1} {_ _ 1x1 1x1}]",
|
|
||||||
// 10: Fat Klotski
|
|
||||||
"S:[4x5] G:[1,3] M:[F] B:[{_ 2X2 _ 1x1} {1x1 _ _ 1x2} {1x1 2x2 _ _} {1x1 _ _ _} {1x1 1x1 2x1 _}]",
|
|
||||||
// 11: Klotski
|
|
||||||
"S:[4x5] G:[1,3] M:[F] B:[{1x2 2X2 _ 1x2} {_ _ _ _} {1x2 2x1 _ 1x2} {_ 1x1 1x1 _} {1x1 _ _ 1x1}]",
|
|
||||||
// 12: Century
|
|
||||||
"S:[4x5] G:[1,3] M:[F] B:[{1x1 2X2 _ 1x1} {1x2 _ _ 1x2} {_ 1x2 _ _} {1x1 _ _ 1x1} {2x1 _ 2x1 _}]",
|
|
||||||
// 13: Super Century
|
|
||||||
"S:[4x5] G:[1,3] M:[F] B:[{1x2 1x1 1x1 1x1} {_ 1x2 2X2 _} {1x2 _ _ _} {_ 2x1 _ 1x1} {_ 2x1 _ _}]",
|
|
||||||
// 14: Supercompo
|
|
||||||
"S:[4x5] G:[1,3] M:[F] B:[{_ 2X2 _ _} {1x1 _ _ 1x1} {1x2 2x1 _ 1x2} {_ 2x1 _ _} {1x1 2x1 _ 1x1}]",
|
|
||||||
};
|
|
||||||
|
|
||||||
template <u8 N>
|
|
||||||
struct uint_hasher
|
|
||||||
{
|
|
||||||
int64_t nums;
|
|
||||||
|
|
||||||
auto operator()(const std::array<u64, N>& ints) const noexcept -> size_t
|
|
||||||
{
|
|
||||||
size_t h = 0;
|
|
||||||
for (size_t i = 0; i < N; ++i) {
|
|
||||||
puzzle::hash_combine(h, ints[i]);
|
|
||||||
}
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <u8 N>
|
|
||||||
static auto unordered_set_uint64(benchmark::State& state) -> void
|
|
||||||
{
|
|
||||||
std::random_device random_device;
|
|
||||||
std::mt19937 generator(random_device());
|
|
||||||
std::uniform_int_distribution<u64> distribution(
|
|
||||||
std::numeric_limits<u64>::min(),
|
|
||||||
std::numeric_limits<u64>::max()
|
|
||||||
);
|
|
||||||
|
|
||||||
std::unordered_set<std::array<u64, N>, uint_hasher<N>> set;
|
|
||||||
std::array<u64, N> ints;
|
|
||||||
for (size_t i = 0; i < N; ++i) {
|
|
||||||
ints[i] = distribution(generator);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto _ : state) {
|
|
||||||
for (size_t i = 0; i < 100000; ++i) {
|
|
||||||
set.emplace(ints);
|
|
||||||
}
|
|
||||||
|
|
||||||
benchmark::DoNotOptimize(set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <u8 N>
|
|
||||||
static auto unordered_flat_set_uint64(benchmark::State& state) -> void
|
|
||||||
{
|
|
||||||
std::random_device random_device;
|
|
||||||
std::mt19937 generator(random_device());
|
|
||||||
std::uniform_int_distribution<u64> distribution(
|
|
||||||
std::numeric_limits<u64>::min(),
|
|
||||||
std::numeric_limits<u64>::max()
|
|
||||||
);
|
|
||||||
|
|
||||||
boost::unordered_flat_set<std::array<u64, N>, uint_hasher<N>> set;
|
|
||||||
std::array<u64, N> ints;
|
|
||||||
for (size_t i = 0; i < N; ++i) {
|
|
||||||
ints[i] = distribution(generator);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto _ : state) {
|
|
||||||
for (size_t i = 0; i < 100000; ++i) {
|
|
||||||
set.emplace(ints);
|
|
||||||
}
|
|
||||||
|
|
||||||
benchmark::DoNotOptimize(set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static auto unordered_flat_set_block_hasher(benchmark::State& state) -> void
|
|
||||||
{
|
|
||||||
blockset set;
|
|
||||||
const block b = block(2, 3, 1, 2, true, false);
|
|
||||||
|
|
||||||
for (auto _ : state) {
|
|
||||||
for (size_t i = 0; i < 100000; ++i) {
|
|
||||||
set.emplace(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
benchmark::DoNotOptimize(set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static auto unordered_flat_set_block_hasher2(benchmark::State& state) -> void
|
|
||||||
{
|
|
||||||
blockset2 set;
|
|
||||||
const block b = block(2, 3, 1, 2, true, false);
|
|
||||||
|
|
||||||
for (auto _ : state) {
|
|
||||||
for (size_t i = 0; i < 100000; ++i) {
|
|
||||||
set.emplace(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
benchmark::DoNotOptimize(set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static auto unordered_flat_set_puzzle_hasher(benchmark::State& state) -> void
|
|
||||||
{
|
|
||||||
puzzleset set;
|
|
||||||
const puzzle p = puzzle(puzzles[0]);
|
|
||||||
|
|
||||||
for (auto _ : state) {
|
|
||||||
for (size_t i = 0; i < 100000; ++i) {
|
|
||||||
set.emplace(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
benchmark::DoNotOptimize(set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static auto explore_state_space(benchmark::State& state) -> void
|
|
||||||
{
|
|
||||||
const puzzle p = puzzle(puzzles[state.range(0)]);
|
|
||||||
|
|
||||||
for (auto _ : state) {
|
|
||||||
auto space = p.explore_state_space();
|
|
||||||
|
|
||||||
benchmark::DoNotOptimize(space);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static auto explore_rush_hour_puzzle_space(benchmark::State& state) -> void
|
|
||||||
{
|
|
||||||
constexpr u8 max_blocks = 5;
|
|
||||||
|
|
||||||
constexpr u8 board_width = 4;
|
|
||||||
constexpr u8 board_height = 5;
|
|
||||||
constexpr u8 goal_x = board_width - 1;
|
|
||||||
constexpr u8 goal_y = 2;
|
|
||||||
constexpr bool restricted = true;
|
|
||||||
|
|
||||||
const blockset2 permitted_blocks = {
|
|
||||||
block(0, 0, 2, 1, false, false),
|
|
||||||
block(0, 0, 3, 1, false, false),
|
|
||||||
block(0, 0, 1, 2, false, false),
|
|
||||||
block(0, 0, 1, 3, false, false)
|
|
||||||
};
|
|
||||||
const block target_block = block(0, 0, 2, 1, true, false);
|
|
||||||
constexpr std::tuple<u8, u8, u8, u8> target_block_pos_range = {
|
|
||||||
0,
|
|
||||||
goal_y,
|
|
||||||
goal_x,
|
|
||||||
goal_y
|
|
||||||
};
|
|
||||||
|
|
||||||
const puzzle p = puzzle(board_width, board_height, goal_x, goal_y, restricted, true);
|
|
||||||
|
|
||||||
for (auto _ : state) {
|
|
||||||
puzzleset result = p.explore_puzzle_space(
|
|
||||||
permitted_blocks,
|
|
||||||
target_block,
|
|
||||||
target_block_pos_range,
|
|
||||||
max_blocks,
|
|
||||||
0,
|
|
||||||
std::nullopt);
|
|
||||||
|
|
||||||
benchmark::DoNotOptimize(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BENCHMARK(unordered_set_uint64<4>)->Unit(benchmark::kMicrosecond);
|
|
||||||
BENCHMARK(unordered_set_uint64<8>)->Unit(benchmark::kMicrosecond);
|
|
||||||
BENCHMARK(unordered_set_uint64<16>)->Unit(benchmark::kMicrosecond);
|
|
||||||
BENCHMARK(unordered_flat_set_uint64<4>)->Unit(benchmark::kMicrosecond);
|
|
||||||
BENCHMARK(unordered_flat_set_uint64<8>)->Unit(benchmark::kMicrosecond);
|
|
||||||
BENCHMARK(unordered_flat_set_uint64<16>)->Unit(benchmark::kMicrosecond);
|
|
||||||
BENCHMARK(unordered_flat_set_block_hasher)->Unit(benchmark::kMicrosecond);
|
|
||||||
BENCHMARK(unordered_flat_set_block_hasher2)->Unit(benchmark::kMicrosecond);
|
|
||||||
BENCHMARK(unordered_flat_set_puzzle_hasher)->Unit(benchmark::kMicrosecond);
|
|
||||||
BENCHMARK(explore_state_space)->DenseRange(0, puzzles.size() - 1)->Unit(benchmark::kMicrosecond);
|
|
||||||
BENCHMARK(explore_rush_hour_puzzle_space)->Unit(benchmark::kSecond);
|
|
||||||
|
|
||||||
BENCHMARK_MAIN();
|
|
||||||
6
flake.lock
generated
6
flake.lock
generated
@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1773201692,
|
"lastModified": 1770843696,
|
||||||
"narHash": "sha256-NXrKzNMniu4Oam2kAFvqJ3GB2kAvlAFIriTAheaY8hw=",
|
"narHash": "sha256-LovWTGDwXhkfCOmbgLVA10bvsi/P8eDDpRudgk68HA8=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "b6067cc0127d4db9c26c79e4de0513e58d0c40c9",
|
"rev": "2343bbb58f99267223bc2aac4fc9ea301a155a16",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
440
flake.nix
440
flake.nix
@ -14,128 +14,26 @@ rec {
|
|||||||
# Create a shell (and possibly package) for each possible system, not only x86_64-linux
|
# Create a shell (and possibly package) for each possible system, not only x86_64-linux
|
||||||
flake-utils.lib.eachDefaultSystem (
|
flake-utils.lib.eachDefaultSystem (
|
||||||
system: let
|
system: let
|
||||||
# =========================================================================================
|
|
||||||
# Define pkgs/stdenvs
|
|
||||||
# =========================================================================================
|
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
config.allowUnfree = true;
|
config.allowUnfree = true;
|
||||||
overlays = [];
|
overlays = [];
|
||||||
};
|
};
|
||||||
|
inherit (pkgs) lib stdenv;
|
||||||
clangPkgs = import nixpkgs {
|
|
||||||
inherit system;
|
|
||||||
config.allowUnfree = true;
|
|
||||||
overlays = [];
|
|
||||||
|
|
||||||
# Use this to change the compiler:
|
|
||||||
# - GCC: pkgs.stdenv
|
|
||||||
# - Clang: pkgs.clangStdenv
|
|
||||||
# NixOS packages are built using GCC by default. Using clang requires a full rebuild/redownload.
|
|
||||||
config.replaceStdenv = {pkgs}: pkgs.clangStdenv;
|
|
||||||
};
|
|
||||||
|
|
||||||
windowsPkgs = import nixpkgs {
|
windowsPkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
config.allowUnfree = true;
|
|
||||||
overlays = [];
|
|
||||||
|
|
||||||
# Use this to cross compile to a different system
|
|
||||||
crossSystem = {
|
crossSystem = {
|
||||||
config = "x86_64-w64-mingw32";
|
config = "x86_64-w64-mingw32";
|
||||||
};
|
};
|
||||||
|
config.allowUnfree = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
inherit (pkgs) lib stdenv;
|
|
||||||
|
|
||||||
# =========================================================================================
|
|
||||||
# Define shell environment
|
|
||||||
# =========================================================================================
|
|
||||||
|
|
||||||
# Setup the shell when entering the "nix develop" environment (bash script).
|
|
||||||
shellHook = let
|
|
||||||
mkCmakeScript = type: let
|
|
||||||
typeLower = lib.toLower type;
|
|
||||||
in
|
|
||||||
pkgs.writers.writeFish "cmake-${typeLower}.fish" ''
|
|
||||||
cd $FLAKE_PROJECT_ROOT
|
|
||||||
|
|
||||||
echo "Removing build directory ./cmake-build-${typeLower}/"
|
|
||||||
rm -rf ./cmake-build-${typeLower}
|
|
||||||
|
|
||||||
echo "Creating build directory"
|
|
||||||
mkdir cmake-build-${typeLower}
|
|
||||||
cd cmake-build-${typeLower}
|
|
||||||
|
|
||||||
echo "Running cmake"
|
|
||||||
cmake -G "Ninja" \
|
|
||||||
-DCMAKE_BUILD_TYPE="${type}" \
|
|
||||||
..
|
|
||||||
|
|
||||||
echo "Linking compile_commands.json"
|
|
||||||
cd ..
|
|
||||||
ln -sf ./cmake-build-${typeLower}/compile_commands.json ./compile_commands.json
|
|
||||||
'';
|
|
||||||
|
|
||||||
cmakeDebug = mkCmakeScript "Debug";
|
|
||||||
cmakeRelease = mkCmakeScript "Release";
|
|
||||||
|
|
||||||
mkBuildScript = type: let
|
|
||||||
typeLower = lib.toLower type;
|
|
||||||
in
|
|
||||||
pkgs.writers.writeFish "cmake-build.fish" ''
|
|
||||||
cd $FLAKE_PROJECT_ROOT/cmake-build-${typeLower}
|
|
||||||
|
|
||||||
echo "Running cmake"
|
|
||||||
NIX_ENFORCE_NO_NATIVE=0 cmake --build . -j$(nproc)
|
|
||||||
'';
|
|
||||||
|
|
||||||
buildDebug = mkBuildScript "Debug";
|
|
||||||
buildRelease = mkBuildScript "Release";
|
|
||||||
|
|
||||||
# Use this to specify commands that should be ran after entering fish shell
|
|
||||||
initProjectShell = pkgs.writers.writeFish "init-shell.fish" ''
|
|
||||||
echo "Entering \"${description}\" environment..."
|
|
||||||
|
|
||||||
# Determine the project root, used e.g. in cmake scripts
|
|
||||||
set -g -x FLAKE_PROJECT_ROOT (git rev-parse --show-toplevel)
|
|
||||||
|
|
||||||
# C/C++:
|
|
||||||
abbr -a cmake-debug "${cmakeDebug}"
|
|
||||||
abbr -a cmake-release "${cmakeRelease}"
|
|
||||||
abbr -a build-debug "${buildDebug}"
|
|
||||||
abbr -a build-release "${buildRelease}"
|
|
||||||
abbr -a debug-clean "${cmakeDebug} && ${buildDebug} && ./cmake-build-debug/masssprings"
|
|
||||||
abbr -a release-clean "${cmakeRelease} && ${buildRelease} && ./cmake-build-release/masssprings"
|
|
||||||
abbr -a debug "${buildDebug} && ./cmake-build-debug/masssprings"
|
|
||||||
abbr -a release "${buildRelease} && ./cmake-build-release/masssprings"
|
|
||||||
|
|
||||||
abbr -a run "${buildRelease} && ./cmake-build-release/masssprings"
|
|
||||||
abbr -a runclusters "${buildRelease} && ./cmake-build-release/masssprings --output=clusters.puzzle --space=rh --moves=10 --blocks=4"
|
|
||||||
abbr -a runtests "${buildDebug} && ./cmake-build-debug/tests"
|
|
||||||
abbr -a runbenchs "mv -f benchs.json benchs.old.json; ${buildRelease} && sudo cpupower frequency-set --governor performance && ./cmake-build-release/benchmarks --benchmark_out=benchs.json --benchmark_out_format=console; sudo cpupower frequency-set --governor powersave"
|
|
||||||
abbr -a rungdb "${buildDebug} && gdb --tui ./cmake-build-debug/masssprings"
|
|
||||||
abbr -a runvalgrind "${buildDebug} && valgrind --leak-check=full --show-reachable=no --show-leak-kinds=definite,indirect,possible --track-origins=no --suppressions=valgrind.supp --log-file=valgrind.log ./cmake-build-debug/masssprings && cat valgrind.log"
|
|
||||||
abbr -a runperf "${buildRelease} && perf record -g ./cmake-build-release/masssprings && hotspot ./perf.data"
|
|
||||||
abbr -a runperf-graph "${buildRelease} && perf record -g ./cmake-build-release/benchmarks --benchmark_filter='explore_state_space' && hotspot ./perf.data"
|
|
||||||
abbr -a runperf-space "${buildRelease} && perf record -g ./cmake-build-release/benchmarks --benchmark_filter='explore_rush_hour_puzzle_space' && hotspot ./perf.data"
|
|
||||||
abbr -a runtracy "tracy -a 127.0.0.1 &; ${buildRelease} && sudo -E ./cmake-build-release/masssprings"
|
|
||||||
|
|
||||||
abbr -a runclion "clion ./CMakeLists.txt 2>/dev/null 1>&2 & disown;"
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
builtins.concatStringsSep "\n" [
|
|
||||||
# Launch into pure fish shell
|
|
||||||
''
|
|
||||||
exec "$(type -p fish)" -C "source ${initProjectShell}"
|
|
||||||
''
|
|
||||||
];
|
|
||||||
|
|
||||||
# ===========================================================================================
|
# ===========================================================================================
|
||||||
# Define custom dependencies
|
# Define custom dependencies
|
||||||
# ===========================================================================================
|
# ===========================================================================================
|
||||||
|
|
||||||
raygui = stdenv.mkDerivation rec {
|
raygui = stdenv.mkDerivation (finalAttrs: {
|
||||||
pname = "raygui";
|
pname = "raygui";
|
||||||
version = "4.0-unstable-2026-02-24";
|
version = "4.0-unstable-2026-02-24";
|
||||||
|
|
||||||
@ -163,13 +61,13 @@ rec {
|
|||||||
Name: raygui
|
Name: raygui
|
||||||
Description: Simple and easy-to-use immediate-mode gui library
|
Description: Simple and easy-to-use immediate-mode gui library
|
||||||
URL: https://github.com/raysan5/raygui
|
URL: https://github.com/raysan5/raygui
|
||||||
Version: ${version}
|
Version: ${finalAttrs.version}
|
||||||
Cflags: -I"{includedir}"
|
Cflags: -I"{includedir}"
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
runHook postInstall
|
runHook postInstall
|
||||||
'';
|
'';
|
||||||
};
|
});
|
||||||
|
|
||||||
thread-pool = stdenv.mkDerivation {
|
thread-pool = stdenv.mkDerivation {
|
||||||
pname = "thread-pool";
|
pname = "thread-pool";
|
||||||
@ -185,69 +83,8 @@ rec {
|
|||||||
# Header-only library
|
# Header-only library
|
||||||
dontBuild = true;
|
dontBuild = true;
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
runHook preInstall
|
|
||||||
|
|
||||||
mkdir -p $out
|
mkdir -p $out
|
||||||
cp -rv ./include $out/include
|
mv ./include $out/include
|
||||||
|
|
||||||
runHook postInstall
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# We can use the pkgs.stdenv for Linux+Windows because it's a header only library.
|
|
||||||
# The build is required to create the pkg-config/cmake configuration files.
|
|
||||||
libmorton = stdenv.mkDerivation {
|
|
||||||
pname = "libmorton";
|
|
||||||
version = "0.2.12-unstable-2023-05-24";
|
|
||||||
|
|
||||||
src = pkgs.fetchFromGitHub {
|
|
||||||
owner = "Forceflow";
|
|
||||||
repo = "libmorton";
|
|
||||||
rev = "7923faa88d7e564020b2d5d408bf8c186ecbe363";
|
|
||||||
hash = "sha256-5LHiWu2GIuDmfM2gXGbRsFasE7AmVCSRphNdFElbbjk=";
|
|
||||||
};
|
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [cmake];
|
|
||||||
|
|
||||||
cmakeFlags = [
|
|
||||||
"-DBUILD_TESTING=OFF"
|
|
||||||
"-DCMAKE_INSTALL_INCLUDEDIR=include"
|
|
||||||
"-DCMAKE_INSTALL_DATADIR=share"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
glew-windows = windowsPkgs.stdenv.mkDerivation rec {
|
|
||||||
pname = "glew-windows";
|
|
||||||
version = "2.2.0";
|
|
||||||
|
|
||||||
src = pkgs.fetchurl {
|
|
||||||
url = "https://github.com/nigels-com/glew/releases/download/glew-${version}/glew-${version}.tgz";
|
|
||||||
hash = "sha256-1PyCiTz7ABCVeNChojN/uMozWzzsz5e5flzH8I5DU+E=";
|
|
||||||
};
|
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [
|
|
||||||
cmake
|
|
||||||
ninja
|
|
||||||
pkg-config
|
|
||||||
];
|
|
||||||
|
|
||||||
preConfigure = ''
|
|
||||||
cd build/cmake
|
|
||||||
'';
|
|
||||||
|
|
||||||
cmakeFlags = [
|
|
||||||
"-DBUILD_UTILS=OFF"
|
|
||||||
"-DGLEW_OSMESA=OFF"
|
|
||||||
"-DBUILD_SHARED_LIBS=ON"
|
|
||||||
"-DCMAKE_POLICY_VERSION_MINIMUM=3.5"
|
|
||||||
];
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
runHook preInstall
|
|
||||||
|
|
||||||
cmake --install . --prefix "$out"
|
|
||||||
|
|
||||||
runHook postInstall
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -261,20 +98,29 @@ rec {
|
|||||||
# - Those which are needed on $PATH during the build, for example cmake and pkg-config
|
# - Those which are needed on $PATH during the build, for example cmake and pkg-config
|
||||||
# - Setup hooks, for example makeWrapper
|
# - Setup hooks, for example makeWrapper
|
||||||
# - Interpreters needed by patchShebangs for build scripts (with the --build flag), which can be the case for e.g. perl
|
# - Interpreters needed by patchShebangs for build scripts (with the --build flag), which can be the case for e.g. perl
|
||||||
# NOTE: Do not add compiler here, they are provided by the stdenv
|
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
# Languages:
|
# Languages:
|
||||||
# binutils
|
binutils
|
||||||
|
gcc
|
||||||
|
|
||||||
# C/C++:
|
# C/C++:
|
||||||
cmake
|
|
||||||
ninja
|
|
||||||
gdb
|
gdb
|
||||||
valgrind
|
valgrind
|
||||||
kdePackages.kcachegrind
|
# gnumake
|
||||||
|
cmake
|
||||||
|
ninja
|
||||||
|
# cling
|
||||||
|
# pkg-config
|
||||||
|
# clang-tools
|
||||||
|
# compdb
|
||||||
|
# pprof
|
||||||
|
# gprof2dot
|
||||||
perf
|
perf
|
||||||
hotspot
|
hotspot
|
||||||
# heaptrack
|
kdePackages.kcachegrind
|
||||||
|
gdbgui
|
||||||
|
massif-visualizer
|
||||||
|
heaptrack
|
||||||
# renderdoc
|
# renderdoc
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -285,23 +131,18 @@ rec {
|
|||||||
# C/C++:
|
# C/C++:
|
||||||
raylib
|
raylib
|
||||||
raygui
|
raygui
|
||||||
glew
|
|
||||||
thread-pool
|
thread-pool
|
||||||
libmorton
|
|
||||||
boost
|
boost
|
||||||
|
|
||||||
# Debugging/Testing/Profiling
|
# Debugging
|
||||||
tracy_0_13
|
tracy-wayland
|
||||||
backward-cpp
|
backward-cpp
|
||||||
libbfd
|
libbfd
|
||||||
catch2_3
|
catch2_3
|
||||||
gbenchmark
|
|
||||||
];
|
];
|
||||||
|
|
||||||
# ===========================================================================================
|
# ===========================================================================================
|
||||||
# Define buildable + installable packages
|
# Define buildable + installable packages
|
||||||
# ===========================================================================================
|
# ===========================================================================================
|
||||||
|
|
||||||
package = stdenv.mkDerivation rec {
|
package = stdenv.mkDerivation rec {
|
||||||
inherit buildInputs;
|
inherit buildInputs;
|
||||||
pname = "masssprings";
|
pname = "masssprings";
|
||||||
@ -311,17 +152,12 @@ rec {
|
|||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
gcc
|
gcc
|
||||||
cmake
|
cmake
|
||||||
|
|
||||||
# Fix the working directory so the auxiliary files are always available
|
|
||||||
makeWrapper
|
|
||||||
];
|
];
|
||||||
|
|
||||||
cmakeFlags = [
|
cmakeFlags = [
|
||||||
"-DDISABLE_THREADPOOL=Off"
|
|
||||||
"-DDISABLE_TRACY=On"
|
"-DDISABLE_TRACY=On"
|
||||||
"-DDISABLE_BACKWARD=On"
|
"-DDISABLE_BACKWARD=On"
|
||||||
"-DDISABLE_TESTS=On"
|
"-DDISABLE_TESTS=On"
|
||||||
"-DDISABLE_BENCH=On"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
hardeningDisable = ["all"];
|
hardeningDisable = ["all"];
|
||||||
@ -331,29 +167,11 @@ rec {
|
|||||||
'';
|
'';
|
||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
runHook preInstall
|
|
||||||
|
|
||||||
mkdir -p $out/lib
|
|
||||||
cp ./${pname} $out/lib/
|
|
||||||
cp -rv $src/default.puzzle $out/lib/
|
|
||||||
cp -rv $src/fonts $out/lib/fonts
|
|
||||||
cp -rv $src/shader $out/lib/shader
|
|
||||||
|
|
||||||
# The wrapper enters the correct working dir, so fonts/shaders/presets are available
|
|
||||||
mkdir -p $out/bin
|
mkdir -p $out/bin
|
||||||
makeWrapper $out/lib/${pname} $out/bin/${pname} --chdir "$out/lib"
|
cp ./${pname} $out/bin/
|
||||||
|
cp $src/default.puzzle $out/bin/
|
||||||
# Generate a .desktop file
|
cp -r $src/fonts $out/bin/fonts
|
||||||
mkdir -p $out/share/applications
|
cp -r $src/shader $out/bin/shader
|
||||||
cat <<INI > $out/share/applications/${pname}.desktop
|
|
||||||
[Desktop Entry]
|
|
||||||
Terminal=true
|
|
||||||
Name=PuzzleSpaces
|
|
||||||
Exec=$out/bin/${pname} %f
|
|
||||||
Type=Application
|
|
||||||
INI
|
|
||||||
|
|
||||||
runHook postInstall
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -371,39 +189,22 @@ rec {
|
|||||||
buildInputs = with windowsPkgs; [
|
buildInputs = with windowsPkgs; [
|
||||||
raylib
|
raylib
|
||||||
raygui
|
raygui
|
||||||
glew-windows
|
|
||||||
thread-pool
|
thread-pool
|
||||||
libmorton
|
boost
|
||||||
|
|
||||||
# Disable stacktrace since that's platform dependant and won't cross compile to windows
|
|
||||||
# https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/libraries/boost/generic.nix#L43
|
|
||||||
(boost.override {
|
|
||||||
enableShared = false;
|
|
||||||
extraB2Args = [
|
|
||||||
"--without-stacktrace"
|
|
||||||
];
|
|
||||||
})
|
|
||||||
];
|
];
|
||||||
|
|
||||||
cmakeFlags = [
|
cmakeFlags = [
|
||||||
"-DCMAKE_SYSTEM_NAME=Windows"
|
"-DCMAKE_SYSTEM_NAME=Windows"
|
||||||
"-DDISABLE_THREADPOOL=Off"
|
|
||||||
"-DDISABLE_TRACY=On"
|
"-DDISABLE_TRACY=On"
|
||||||
"-DDISABLE_BACKWARD=On"
|
"-DDISABLE_BACKWARD=On"
|
||||||
"-DDISABLE_TESTS=On"
|
|
||||||
"-DDISABLE_BENCH=On"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
runHook preInstall
|
|
||||||
|
|
||||||
mkdir -p $out/bin
|
mkdir -p $out/bin
|
||||||
cp -rv ./${pname}.exe $out/bin/
|
cp ./${pname}.exe $out/bin/
|
||||||
cp -rv $src/default.puzzle $out/bin/
|
cp $src/default.puzzle $out/bin/
|
||||||
cp -rv $src/fonts $out/bin/fonts
|
cp -r $src/fonts $out/bin/fonts
|
||||||
cp -rv $src/shader $out/bin/shader
|
cp -r $src/shader $out/bin/shader
|
||||||
|
|
||||||
runHook postInstall
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
in rec {
|
in rec {
|
||||||
@ -417,55 +218,156 @@ rec {
|
|||||||
# Provide environment for "nix develop"
|
# Provide environment for "nix develop"
|
||||||
devShells = {
|
devShells = {
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
inherit nativeBuildInputs buildInputs shellHook;
|
inherit nativeBuildInputs buildInputs;
|
||||||
name = description;
|
name = description;
|
||||||
|
|
||||||
# =========================================================================================
|
# =========================================================================================
|
||||||
# Define environment variables
|
# Define environment variables
|
||||||
# =========================================================================================
|
# =========================================================================================
|
||||||
|
|
||||||
|
# Custom dynamic libraries:
|
||||||
|
# LD_LIBRARY_PATH = builtins.concatStringsSep ":" [
|
||||||
|
# # Rust Bevy GUI app:
|
||||||
|
# # "${pkgs.xorg.libX11}/lib"
|
||||||
|
# # "${pkgs.xorg.libXcursor}/lib"
|
||||||
|
# # "${pkgs.xorg.libXrandr}/lib"
|
||||||
|
# # "${pkgs.xorg.libXi}/lib"
|
||||||
|
# # "${pkgs.libGL}/lib"
|
||||||
|
#
|
||||||
|
# # JavaFX app:
|
||||||
|
# # "${pkgs.libGL}/lib"
|
||||||
|
# # "${pkgs.gtk3}/lib"
|
||||||
|
# # "${pkgs.glib.out}/lib"
|
||||||
|
# # "${pkgs.xorg.libXtst}/lib"
|
||||||
|
# ];
|
||||||
|
|
||||||
# Dynamic libraries from buildinputs:
|
# Dynamic libraries from buildinputs:
|
||||||
LD_LIBRARY_PATH = nixpkgs.lib.makeLibraryPath buildInputs;
|
LD_LIBRARY_PATH = nixpkgs.lib.makeLibraryPath buildInputs;
|
||||||
|
|
||||||
|
# =========================================================================================
|
||||||
|
# Define shell environment
|
||||||
|
# =========================================================================================
|
||||||
|
|
||||||
|
# Setup the shell when entering the "nix develop" environment (bash script).
|
||||||
|
shellHook = let
|
||||||
|
mkCmakeScript = type: let
|
||||||
|
typeLower = lib.toLower type;
|
||||||
|
in
|
||||||
|
pkgs.writers.writeFish "cmake-${typeLower}.fish" ''
|
||||||
|
cd $FLAKE_PROJECT_ROOT
|
||||||
|
|
||||||
|
# set -g -x CC ${pkgs.clang}/bin/clang
|
||||||
|
# set -g -x CXX ${pkgs.clang}/bin/clang++
|
||||||
|
|
||||||
|
echo "Removing build directory ./cmake-build-${typeLower}/"
|
||||||
|
rm -rf ./cmake-build-${typeLower}
|
||||||
|
|
||||||
|
echo "Creating build directory"
|
||||||
|
mkdir cmake-build-${typeLower}
|
||||||
|
cd cmake-build-${typeLower}
|
||||||
|
|
||||||
|
echo "Running cmake"
|
||||||
|
cmake -G "Ninja" \
|
||||||
|
-DCMAKE_BUILD_TYPE="${type}" \
|
||||||
|
..
|
||||||
|
|
||||||
|
echo "Linking compile_commands.json"
|
||||||
|
cd ..
|
||||||
|
ln -sf ./cmake-build-${typeLower}/compile_commands.json ./compile_commands.json
|
||||||
|
'';
|
||||||
|
|
||||||
|
cmakeDebug = mkCmakeScript "Debug";
|
||||||
|
cmakeRelease = mkCmakeScript "Release";
|
||||||
|
|
||||||
|
mkBuildScript = type: let
|
||||||
|
typeLower = lib.toLower type;
|
||||||
|
in
|
||||||
|
pkgs.writers.writeFish "cmake-build.fish" ''
|
||||||
|
cd $FLAKE_PROJECT_ROOT/cmake-build-${typeLower}
|
||||||
|
|
||||||
|
echo "Running cmake"
|
||||||
|
NIX_ENFORCE_NO_NATIVE=0 cmake --build . -j$(nproc)
|
||||||
|
'';
|
||||||
|
|
||||||
|
buildDebug = mkBuildScript "Debug";
|
||||||
|
buildRelease = mkBuildScript "Release";
|
||||||
|
|
||||||
|
# Use this to specify commands that should be ran after entering fish shell
|
||||||
|
initProjectShell = pkgs.writers.writeFish "init-shell.fish" ''
|
||||||
|
echo "Entering \"${description}\" environment..."
|
||||||
|
|
||||||
|
# Determine the project root, used e.g. in cmake scripts
|
||||||
|
set -g -x FLAKE_PROJECT_ROOT (git rev-parse --show-toplevel)
|
||||||
|
|
||||||
|
# C/C++:
|
||||||
|
abbr -a cmake-debug "${cmakeDebug}"
|
||||||
|
abbr -a cmake-release "${cmakeRelease}"
|
||||||
|
abbr -a build-debug "${buildDebug}"
|
||||||
|
abbr -a build-release "${buildRelease}"
|
||||||
|
abbr -a debug "${buildDebug} && ./cmake-build-debug/masssprings"
|
||||||
|
abbr -a release "${buildRelease} && ./cmake-build-release/masssprings"
|
||||||
|
abbr -a debug-clean "${cmakeDebug} && ${buildDebug} && ./cmake-build-debug/masssprings"
|
||||||
|
abbr -a release-clean "${cmakeRelease} && ${buildRelease} && ./cmake-build-release/masssprings"
|
||||||
|
|
||||||
|
abbr -a rungdb "${buildDebug} && gdb --tui ./cmake-build-debug/masssprings"
|
||||||
|
abbr -a runperf "${buildRelease} && perf record -g ./cmake-build-release/masssprings && hotspot ./perf.data"
|
||||||
|
abbr -a runtracy "tracy -a 127.0.0.1 &; ${buildRelease} && sudo -E ./cmake-build-release/masssprings"
|
||||||
|
abbr -a runvalgrind "${buildDebug} && valgrind --leak-check=full --show-reachable=no --show-leak-kinds=definite,indirect,possible --track-origins=no --suppressions=valgrind.supp --log-file=valgrind.log ./cmake-build-debug/masssprings && cat valgrind.log"
|
||||||
|
abbr -a runtests "${buildDebug} && ./cmake-build-debug/tests"
|
||||||
|
|
||||||
|
abbr -a runclion "clion ./CMakeLists.txt 2>/dev/null 1>&2 & disown;"
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
builtins.concatStringsSep "\n" [
|
||||||
|
# Launch into pure fish shell
|
||||||
|
''
|
||||||
|
exec "$(type -p fish)" -C "source ${initProjectShell} && abbr -a menu '${pkgs.bat}/bin/bat "${initProjectShell}"'"
|
||||||
|
''
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
# Provide environment with clang stdenv for "nix develop .#clang"
|
# TODO: Can't get renderdoc in FHS to work
|
||||||
# TODO: Broken. Clang can't find stdlib headers or library headers (raylib, backward, ...).
|
|
||||||
# Does the clangStdenv not automatically collect the include paths?
|
|
||||||
clang =
|
|
||||||
pkgs.mkShell.override {
|
|
||||||
stdenv = pkgs.clangStdenv;
|
|
||||||
} {
|
|
||||||
inherit shellHook;
|
|
||||||
name = description;
|
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [
|
# FHS environment for renderdoc. Access with "nix develop .#renderdoc".
|
||||||
cmake
|
# https://ryantm.github.io/nixpkgs/builders/special/fhs-environments
|
||||||
ninja
|
# renderdoc =
|
||||||
];
|
# (pkgs.buildFHSEnv {
|
||||||
|
# name = "renderdoc-env";
|
||||||
buildInputs = with pkgs; [
|
#
|
||||||
# C/C++:
|
# targetPkgs = pkgs:
|
||||||
raylib
|
# with pkgs; [
|
||||||
raygui
|
# # RenderDoc
|
||||||
glew
|
# renderdoc
|
||||||
thread-pool
|
#
|
||||||
libmorton
|
# # Build tools
|
||||||
boost
|
# gcc
|
||||||
|
# cmake
|
||||||
# Debugging/Testing/Profiling
|
#
|
||||||
backward-cpp
|
# # Raylib
|
||||||
libbfd
|
# raylib
|
||||||
catch2_3
|
# libGL
|
||||||
gbenchmark
|
# mesa
|
||||||
];
|
#
|
||||||
|
# # X11
|
||||||
# =========================================================================================
|
# libx11
|
||||||
# Define environment variables
|
# libxcursor
|
||||||
# =========================================================================================
|
# libxrandr
|
||||||
|
# libxinerama
|
||||||
# Dynamic libraries from buildinputs:
|
# libxi
|
||||||
LD_LIBRARY_PATH = nixpkgs.lib.makeLibraryPath buildInputs;
|
# libxext
|
||||||
};
|
# libxfixes
|
||||||
|
#
|
||||||
|
# # Wayland
|
||||||
|
# wayland
|
||||||
|
# wayland-protocols
|
||||||
|
# libxkbcommon
|
||||||
|
# ];
|
||||||
|
#
|
||||||
|
# runScript = "fish";
|
||||||
|
#
|
||||||
|
# profile = ''
|
||||||
|
# '';
|
||||||
|
# }).env;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,69 +0,0 @@
|
|||||||
#ifndef BITS_HPP_
|
|
||||||
#define BITS_HPP_
|
|
||||||
|
|
||||||
#include "util.hpp"
|
|
||||||
|
|
||||||
#include <concepts>
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
requires std::unsigned_integral<T>
|
|
||||||
// ReSharper disable once CppRedundantInlineSpecifier
|
|
||||||
INLINE inline auto create_mask(const u8 first, const u8 last) -> T
|
|
||||||
{
|
|
||||||
// If the mask width is equal the type width return all 1s instead of shifting
|
|
||||||
// as shifting by type-width is undefined behavior.
|
|
||||||
if (static_cast<size_t>(last - first + 1) >= sizeof(T) * 8) {
|
|
||||||
return ~T{0};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Example: first=4, last=7, 7-4+1=4
|
|
||||||
// 1 << 4 = 0b00010000
|
|
||||||
// 32 - 1 = 0b00001111
|
|
||||||
// 31 << 4 = 0b11110000
|
|
||||||
// Subtracting 1 generates a consecutive mask.
|
|
||||||
return ((T{1} << (last - first + 1)) - 1) << first;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
requires std::unsigned_integral<T>
|
|
||||||
// ReSharper disable once CppRedundantInlineSpecifier
|
|
||||||
INLINE inline auto clear_bits(T& bits, const u8 first, const u8 last) -> void
|
|
||||||
{
|
|
||||||
const T mask = create_mask<T>(first, last);
|
|
||||||
|
|
||||||
bits = bits & ~mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class T, class U>
|
|
||||||
requires std::unsigned_integral<T> && std::unsigned_integral<U>
|
|
||||||
// ReSharper disable once CppRedundantInlineSpecifier
|
|
||||||
INLINE inline auto set_bits(T& bits, const u8 first, const u8 last, const U value) -> void
|
|
||||||
{
|
|
||||||
const T mask = create_mask<T>(first, last);
|
|
||||||
|
|
||||||
// Example: first=4, last=6, value=0b1110, bits = 0b 01111110
|
|
||||||
// mask = 0b 01110000
|
|
||||||
// bits & ~mask = 0b 00001110
|
|
||||||
// value << 4 = 0b 11100000
|
|
||||||
// (value << 4) & mask = 0b 01100000
|
|
||||||
// (bits & ~mask) | (value << 4) & mask = 0b 01101110
|
|
||||||
// Insert position: ^^^
|
|
||||||
// First clear the bits, then | with the value positioned at the insertion point.
|
|
||||||
// The value may be larger than [first, last], extra bits are ignored.
|
|
||||||
bits = (bits & ~mask) | ((static_cast<T>(value) << first) & mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
requires std::unsigned_integral<T>
|
|
||||||
// ReSharper disable once CppRedundantInlineSpecifier
|
|
||||||
INLINE inline auto get_bits(const T bits, const u8 first, const u8 last) -> T
|
|
||||||
{
|
|
||||||
const T mask = create_mask<T>(first, last);
|
|
||||||
|
|
||||||
// We can >> without sign extension because T is unsigned_integral
|
|
||||||
return (bits & mask) >> first;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto print_bitmap(u64 bitmap, u8 w, u8 h, const std::string& title) -> void;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -3,32 +3,12 @@
|
|||||||
|
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
|
|
||||||
// Calculate the octree parallel to the layout calculation.
|
#define THREADPOOL // Enable physics threadpool
|
||||||
// Layout uses the octree from last frame.
|
|
||||||
#define ASYNC_OCTREE
|
|
||||||
|
|
||||||
// Gets set by CMake
|
// Gets set by CMake
|
||||||
// #define THREADPOOL // Enable physics threadpool
|
|
||||||
// #define BACKWARD // Enable pretty stack traces
|
// #define BACKWARD // Enable pretty stack traces
|
||||||
// #define TRACY // Enable tracy profiling support
|
// #define TRACY // Enable tracy profiling support
|
||||||
|
|
||||||
#ifdef TRACY
|
|
||||||
#include <tracy/Tracy.hpp>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(_WIN32)
|
|
||||||
#define NOGDI // All GDI defines and routines
|
|
||||||
#define NOUSER // All USER defines and routines
|
|
||||||
#endif
|
|
||||||
#define BS_THREAD_POOL_NATIVE_EXTENSIONS
|
|
||||||
// ReSharper disable once CppUnusedIncludeDirective
|
|
||||||
#include <BS_thread_pool.hpp>
|
|
||||||
using threadpool = std::optional<BS::thread_pool<>* const>;
|
|
||||||
#if defined(_WIN32) // raylib uses these names as function parameters
|
|
||||||
#undef near
|
|
||||||
#undef far
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Window
|
// Window
|
||||||
constexpr int INITIAL_WIDTH = 600;
|
constexpr int INITIAL_WIDTH = 600;
|
||||||
constexpr int INITIAL_HEIGHT = 600;
|
constexpr int INITIAL_HEIGHT = 600;
|
||||||
@ -47,14 +27,12 @@ constexpr int FONT_SIZE = 26;
|
|||||||
// Camera Controls
|
// Camera Controls
|
||||||
constexpr float CAMERA_FOV = 90.0;
|
constexpr float CAMERA_FOV = 90.0;
|
||||||
constexpr float FOV_SPEED = 1.0;
|
constexpr float FOV_SPEED = 1.0;
|
||||||
constexpr float FOV_MULTIPLIER = 4.0;
|
|
||||||
constexpr float MIN_FOV = 10.0;
|
constexpr float MIN_FOV = 10.0;
|
||||||
constexpr float MAX_PERSP_FOV = 120.0;
|
constexpr float MAX_FOV = 180.0;
|
||||||
constexpr float MAX_ORTHO_FOV = 540.0;
|
constexpr float CAMERA_DISTANCE = 20.0;
|
||||||
constexpr float CAMERA_DISTANCE = 150.0;
|
constexpr float ZOOM_SPEED = 2.5;
|
||||||
constexpr float MIN_CAMERA_DISTANCE = 2.0;
|
constexpr float MIN_CAMERA_DISTANCE = 2.0;
|
||||||
constexpr float MAX_CAMERA_DISTANCE = 2000.0;
|
constexpr float MAX_CAMERA_DISTANCE = 2000.0;
|
||||||
constexpr float ZOOM_SPEED = 2.5;
|
|
||||||
constexpr float ZOOM_MULTIPLIER = 4.0;
|
constexpr float ZOOM_MULTIPLIER = 4.0;
|
||||||
constexpr float PAN_SPEED = 2.0;
|
constexpr float PAN_SPEED = 2.0;
|
||||||
constexpr float PAN_MULTIPLIER = 10.0;
|
constexpr float PAN_MULTIPLIER = 10.0;
|
||||||
@ -66,27 +44,24 @@ constexpr float TARGET_UPS = 90; // How often to update physics
|
|||||||
constexpr float TIMESTEP = 1.0 / TARGET_UPS; // Update interval in seconds
|
constexpr float TIMESTEP = 1.0 / TARGET_UPS; // Update interval in seconds
|
||||||
constexpr float SIM_SPEED = 4.0; // How large each update should be
|
constexpr float SIM_SPEED = 4.0; // How large each update should be
|
||||||
constexpr float MASS = 1.0; // Mass spring system
|
constexpr float MASS = 1.0; // Mass spring system
|
||||||
constexpr float SPRING_K = 4.0; // Mass spring system
|
constexpr float SPRING_CONSTANT = 5.0; // Mass spring system
|
||||||
constexpr float DAMPENING_K = 1.5; // Mass spring system
|
constexpr float DAMPENING_CONSTANT = 1.0; // Mass spring system
|
||||||
constexpr float REST_LENGTH = 3.0; // Mass spring system
|
constexpr float REST_LENGTH = 3.0; // Mass spring system
|
||||||
constexpr float VERLET_DAMPENING = 0.1; // [0, 1]
|
constexpr float VERLET_DAMPENING = 0.05; // [0, 1]
|
||||||
constexpr float BH_FORCE = 2.5; // Barnes-Hut [1.0, 3.0]
|
constexpr float BH_FORCE = 2.5; // Barnes-Hut [1.0, 3.0]
|
||||||
constexpr float THETA = 1.0; // Barnes-Hut [0.5, 1.0]
|
constexpr float THETA = 0.8; // Barnes-Hut [0.5, 1.0]
|
||||||
constexpr float SOFTENING = 0.05; // Barnes-Hut [0.01, 1.0]
|
constexpr float SOFTENING = 0.01; // Barnes-Hut [0.01, 1.0]
|
||||||
|
|
||||||
// Graph Drawing
|
// Graph Drawing
|
||||||
static const Color EDGE_COLOR = Fade(BLUE, 0.3);
|
constexpr Color EDGE_COLOR = DARKBLUE;
|
||||||
constexpr int DRAW_EDGES_LIMIT = 5'000'000;
|
constexpr float VERTEX_SIZE = 0.5;
|
||||||
constexpr float VERTEX_SIZE = 0.75;
|
static const Color VERTEX_COLOR = Fade(BLUE, 0.5);
|
||||||
constexpr int DRAW_VERTICES_LIMIT = 1'000'000;
|
constexpr Color VERTEX_VISITED_COLOR = DARKBLUE;
|
||||||
static const Color VERTEX_COLOR = Fade(BLUE, 0.8);
|
|
||||||
constexpr Color VERTEX_VISITED_COLOR = ORANGE;
|
|
||||||
constexpr Color VERTEX_START_COLOR = ORANGE;
|
|
||||||
constexpr Color VERTEX_CURRENT_COLOR = ORANGE;
|
|
||||||
constexpr Color VERTEX_PATH_COLOR = GREEN;
|
constexpr Color VERTEX_PATH_COLOR = GREEN;
|
||||||
constexpr Color VERTEX_TARGET_COLOR = GREEN;
|
constexpr Color VERTEX_TARGET_COLOR = RED;
|
||||||
static const Color VERTEX_CLOSEST_COLOR = Fade(PINK, 1.0);
|
constexpr Color VERTEX_START_COLOR = ORANGE;
|
||||||
static const Color VERTEX_FARTHEST_COLOR = Fade(DARKBLUE, 0.8);
|
constexpr Color VERTEX_CURRENT_COLOR = PURPLE;
|
||||||
|
constexpr int DRAW_VERTICES_LIMIT = 1000000;
|
||||||
|
|
||||||
// Klotski Drawing
|
// Klotski Drawing
|
||||||
constexpr int BOARD_PADDING = 10;
|
constexpr int BOARD_PADDING = 10;
|
||||||
@ -97,8 +72,4 @@ constexpr Color BLOCK_COLOR = DARKBLUE;
|
|||||||
constexpr Color TARGET_BLOCK_COLOR = RED;
|
constexpr Color TARGET_BLOCK_COLOR = RED;
|
||||||
constexpr Color WALL_COLOR = BLACK;
|
constexpr Color WALL_COLOR = BLACK;
|
||||||
|
|
||||||
// Threadpool
|
|
||||||
static constexpr int SMALL_TASK_BLOCK_SIZE = 256; // Weirdly larger blocks decrease performance...
|
|
||||||
static constexpr int LARGE_TASK_BLOCK_SIZE = 256;
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@ -1,47 +0,0 @@
|
|||||||
#ifndef MASS_SPRING_SYSTEM_HPP_
|
|
||||||
#define MASS_SPRING_SYSTEM_HPP_
|
|
||||||
|
|
||||||
#include "octree.hpp"
|
|
||||||
#include "config.hpp"
|
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
#include <raylib.h>
|
|
||||||
|
|
||||||
using spring = std::pair<size_t, size_t>;
|
|
||||||
|
|
||||||
class cpu_spring_system
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
octree tree;
|
|
||||||
|
|
||||||
// This is the main ownership of all the states/masses/springs.
|
|
||||||
std::vector<Vector3> positions;
|
|
||||||
std::vector<Vector3> previous_positions; // for verlet integration
|
|
||||||
std::vector<Vector3> velocities;
|
|
||||||
std::vector<Vector3> forces;
|
|
||||||
|
|
||||||
std::vector<spring> springs;
|
|
||||||
|
|
||||||
public:
|
|
||||||
cpu_spring_system() {}
|
|
||||||
|
|
||||||
NO_COPY_NO_MOVE(cpu_spring_system);
|
|
||||||
|
|
||||||
public:
|
|
||||||
auto clear() -> void;
|
|
||||||
auto add_mass() -> void;
|
|
||||||
auto add_spring(size_t a, size_t b) -> void;
|
|
||||||
|
|
||||||
auto clear_forces() -> void;
|
|
||||||
auto calculate_spring_force(size_t s) -> void;
|
|
||||||
auto calculate_spring_forces(threadpool thread_pool = std::nullopt) -> void;
|
|
||||||
auto calculate_repulsion_forces(threadpool thread_pool = std::nullopt) -> void;
|
|
||||||
auto integrate_velocity(size_t m, float dt) -> void;
|
|
||||||
auto integrate_position(size_t m, float dt) -> void;
|
|
||||||
auto verlet_update(size_t m, float dt) -> void;
|
|
||||||
auto update(float dt, threadpool thread_pool = std::nullopt) -> void;
|
|
||||||
|
|
||||||
auto center_masses(threadpool thread_pool = std::nullopt) -> void;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,8 +1,7 @@
|
|||||||
#ifndef DISTANCE_HPP_
|
#ifndef DISTANCE_HPP_
|
||||||
#define DISTANCE_HPP_
|
#define DISTANCE_HPP_
|
||||||
|
|
||||||
#include "cpu_spring_system.hpp"
|
#include <cstddef>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class graph_distances
|
class graph_distances
|
||||||
@ -16,11 +15,10 @@ public:
|
|||||||
auto clear() -> void;
|
auto clear() -> void;
|
||||||
[[nodiscard]] auto empty() const -> bool;
|
[[nodiscard]] auto empty() const -> bool;
|
||||||
|
|
||||||
auto calculate_distances(size_t node_count,
|
auto calculate_distances(size_t node_count, const std::vector<std::pair<size_t, size_t>>& edges,
|
||||||
const std::vector<spring>& edges,
|
|
||||||
const std::vector<size_t>& targets) -> void;
|
const std::vector<size_t>& targets) -> void;
|
||||||
|
|
||||||
[[nodiscard]] auto get_shortest_path(size_t source) const -> std::vector<size_t>;
|
[[nodiscard]] auto get_shortest_path(size_t source) const -> std::vector<size_t>;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
#ifndef INPUT_HANDLER_HPP_
|
#ifndef INPUT_HPP_
|
||||||
#define INPUT_HANDLER_HPP_
|
#define INPUT_HPP_
|
||||||
|
|
||||||
#include "orbit_camera.hpp"
|
#include "orbit_camera.hpp"
|
||||||
#include "state_manager.hpp"
|
#include "state_manager.hpp"
|
||||||
@ -22,7 +22,8 @@ struct show_yes_no_message
|
|||||||
std::function<void()> on_yes;
|
std::function<void()> on_yes;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct show_save_preset_window {};
|
struct show_save_preset_window
|
||||||
|
{};
|
||||||
|
|
||||||
using ui_command = std::variant<show_ok_message, show_yes_no_message, show_save_preset_window>;
|
using ui_command = std::variant<show_ok_message, show_yes_no_message, show_save_preset_window>;
|
||||||
|
|
||||||
@ -74,11 +75,10 @@ public:
|
|||||||
bool mark_path = false;
|
bool mark_path = false;
|
||||||
bool mark_solutions = false;
|
bool mark_solutions = false;
|
||||||
bool connect_solutions = false;
|
bool connect_solutions = false;
|
||||||
bool color_by_distance = false;
|
|
||||||
|
|
||||||
// Camera
|
// Camera
|
||||||
bool camera_lock = true;
|
bool camera_lock = true;
|
||||||
bool camera_mass_center_lock = true;
|
bool camera_mass_center_lock = false;
|
||||||
bool camera_panning = false;
|
bool camera_panning = false;
|
||||||
bool camera_rotating = false;
|
bool camera_rotating = false;
|
||||||
|
|
||||||
@ -86,17 +86,16 @@ public:
|
|||||||
Vector2 mouse = Vector2Zero();
|
Vector2 mouse = Vector2Zero();
|
||||||
Vector2 last_mouse = Vector2Zero();
|
Vector2 last_mouse = Vector2Zero();
|
||||||
|
|
||||||
// State selection from graph
|
|
||||||
size_t collision_mass = -1;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
input_handler(state_manager& _state, orbit_camera& _camera)
|
input_handler(state_manager& _state, orbit_camera& _camera) : state(_state), camera(_camera)
|
||||||
: state(_state), camera(_camera)
|
|
||||||
{
|
{
|
||||||
init_handlers();
|
init_handlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
NO_COPY_NO_MOVE(input_handler);
|
input_handler(const input_handler& copy) = delete;
|
||||||
|
auto operator=(const input_handler& copy) -> input_handler& = delete;
|
||||||
|
input_handler(input_handler&& move) = delete;
|
||||||
|
auto operator=(input_handler&& move) -> input_handler& = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
auto init_handlers() -> void;
|
auto init_handlers() -> void;
|
||||||
@ -123,7 +122,6 @@ public:
|
|||||||
auto add_block() -> void;
|
auto add_block() -> void;
|
||||||
auto remove_block() -> void;
|
auto remove_block() -> void;
|
||||||
auto place_goal() const -> void;
|
auto place_goal() const -> void;
|
||||||
auto select_state() const -> void;
|
|
||||||
|
|
||||||
// Key actions
|
// Key actions
|
||||||
auto toggle_camera_lock() -> void;
|
auto toggle_camera_lock() -> void;
|
||||||
@ -141,7 +139,6 @@ public:
|
|||||||
auto clear_graph() -> void;
|
auto clear_graph() -> void;
|
||||||
auto toggle_mark_solutions() -> void;
|
auto toggle_mark_solutions() -> void;
|
||||||
auto toggle_connect_solutions() -> void;
|
auto toggle_connect_solutions() -> void;
|
||||||
auto toggle_color_by_distance() -> void;
|
|
||||||
auto toggle_mark_path() -> void;
|
auto toggle_mark_path() -> void;
|
||||||
auto goto_optimal_next_state() const -> void;
|
auto goto_optimal_next_state() const -> void;
|
||||||
auto goto_most_distant_state() const -> void;
|
auto goto_most_distant_state() const -> void;
|
||||||
@ -161,14 +158,18 @@ public:
|
|||||||
// General
|
// General
|
||||||
auto register_generic_handler(const std::function<void(input_handler&)>& handler) -> void;
|
auto register_generic_handler(const std::function<void(input_handler&)>& handler) -> void;
|
||||||
|
|
||||||
auto register_mouse_pressed_handler(MouseButton button, const std::function<void(input_handler&)>& handler) -> void;
|
auto register_mouse_pressed_handler(MouseButton button,
|
||||||
|
const std::function<void(input_handler&)>& handler) -> void;
|
||||||
|
|
||||||
auto register_mouse_released_handler(MouseButton button,
|
auto register_mouse_released_handler(MouseButton button,
|
||||||
const std::function<void(input_handler&)>& handler) -> void;
|
const std::function<void(input_handler&)>& handler)
|
||||||
|
-> void;
|
||||||
|
|
||||||
auto register_key_pressed_handler(KeyboardKey key, const std::function<void(input_handler&)>& handler) -> void;
|
auto register_key_pressed_handler(KeyboardKey key,
|
||||||
|
const std::function<void(input_handler&)>& handler) -> void;
|
||||||
|
|
||||||
auto register_key_released_handler(KeyboardKey key, const std::function<void(input_handler&)>& handler) -> void;
|
auto register_key_released_handler(KeyboardKey key,
|
||||||
|
const std::function<void(input_handler&)>& handler) -> void;
|
||||||
|
|
||||||
auto handle_input() -> void;
|
auto handle_input() -> void;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
#ifndef LOAD_SAVE_HPP_
|
|
||||||
#define LOAD_SAVE_HPP_
|
|
||||||
|
|
||||||
#include "puzzle.hpp"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
auto parse_preset_file(const std::string& preset_file) -> std::pair<std::vector<puzzle>, std::vector<std::string>>;
|
|
||||||
auto append_preset_file(const std::string& preset_file, const std::string& preset_name, const puzzle& p) -> bool;
|
|
||||||
auto append_preset_file_quiet(const std::string& preset_file,
|
|
||||||
const std::string& preset_name,
|
|
||||||
const puzzle& p,
|
|
||||||
bool validate) -> bool;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
114
include/mass_spring_system.hpp
Normal file
114
include/mass_spring_system.hpp
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
#ifndef MASS_SPRING_SYSTEM_HPP_
|
||||||
|
#define MASS_SPRING_SYSTEM_HPP_
|
||||||
|
|
||||||
|
#include "octree.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
#include "config.hpp"
|
||||||
|
|
||||||
|
#include <raylib.h>
|
||||||
|
#include <raymath.h>
|
||||||
|
|
||||||
|
#ifdef THREADPOOL
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#define NOGDI // All GDI defines and routines
|
||||||
|
#define NOUSER // All USER defines and routines
|
||||||
|
#endif
|
||||||
|
#define BS_THREAD_POOL_NATIVE_EXTENSIONS
|
||||||
|
#include <BS_thread_pool.hpp>
|
||||||
|
#if defined(_WIN32) // raylib uses these names as function parameters
|
||||||
|
#undef near
|
||||||
|
#undef far
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class mass_spring_system
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
class mass
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Vector3 position = Vector3Zero();
|
||||||
|
Vector3 previous_position = Vector3Zero(); // for verlet integration
|
||||||
|
Vector3 velocity = Vector3Zero();
|
||||||
|
Vector3 force = Vector3Zero();
|
||||||
|
|
||||||
|
public:
|
||||||
|
mass() = delete;
|
||||||
|
|
||||||
|
explicit mass(const Vector3 _position)
|
||||||
|
: position(_position), previous_position(_position) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
auto clear_force() -> void;
|
||||||
|
auto calculate_velocity(float delta_time) -> void;
|
||||||
|
auto calculate_position(float delta_time) -> void;
|
||||||
|
auto verlet_update(float delta_time) -> void;
|
||||||
|
};
|
||||||
|
|
||||||
|
class spring
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
size_t a;
|
||||||
|
size_t b;
|
||||||
|
|
||||||
|
public:
|
||||||
|
spring(const size_t _a, const size_t _b)
|
||||||
|
: a(_a), b(_b) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
static auto calculate_spring_force(mass& _a, mass& _b) -> void;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef THREADPOOL
|
||||||
|
BS::thread_pool<> threads;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
octree tree;
|
||||||
|
|
||||||
|
// This is the main ownership of all the states/masses/springs.
|
||||||
|
std::vector<mass> masses;
|
||||||
|
std::vector<spring> springs;
|
||||||
|
|
||||||
|
public:
|
||||||
|
mass_spring_system()
|
||||||
|
#ifdef THREADPOOL
|
||||||
|
: threads(std::thread::hardware_concurrency() - 1, set_thread_name)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
infoln("Using Barnes-Hut + Octree repulsion force calculation.");
|
||||||
|
|
||||||
|
#ifdef THREADPOOL
|
||||||
|
infoln("Thread-pool: {} threads.", threads.get_thread_count());
|
||||||
|
#else
|
||||||
|
infoln("Thread-pool: Disabled.");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
mass_spring_system(const mass_spring_system& copy) = delete;
|
||||||
|
auto operator=(const mass_spring_system& copy) -> mass_spring_system& = delete;
|
||||||
|
mass_spring_system(mass_spring_system& move) = delete;
|
||||||
|
auto operator=(mass_spring_system&& move) -> mass_spring_system& = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef THREADPOOL
|
||||||
|
static auto set_thread_name(size_t idx) -> void;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto build_octree() -> void;
|
||||||
|
|
||||||
|
public:
|
||||||
|
auto clear() -> void;
|
||||||
|
auto add_mass() -> void;
|
||||||
|
auto add_spring(size_t a, size_t b) -> void;
|
||||||
|
|
||||||
|
auto clear_forces() -> void;
|
||||||
|
auto calculate_spring_forces() -> void;
|
||||||
|
auto calculate_repulsion_forces() -> void;
|
||||||
|
auto verlet_update(float delta_time) -> void;
|
||||||
|
|
||||||
|
auto center_masses() -> void;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,15 +1,10 @@
|
|||||||
#ifndef OCTREE_HPP_
|
#ifndef OCTREE_HPP_
|
||||||
#define OCTREE_HPP_
|
#define OCTREE_HPP_
|
||||||
|
|
||||||
#include "util.hpp"
|
|
||||||
#include "config.hpp"
|
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
#include <libmorton/morton.h>
|
#include <vector>
|
||||||
|
|
||||||
class octree
|
class octree
|
||||||
{
|
{
|
||||||
@ -18,193 +13,40 @@ class octree
|
|||||||
public:
|
public:
|
||||||
Vector3 mass_center = Vector3Zero();
|
Vector3 mass_center = Vector3Zero();
|
||||||
float mass_total = 0.0;
|
float mass_total = 0.0;
|
||||||
u8 depth = 0;
|
Vector3 box_min = Vector3Zero(); // area start
|
||||||
float size = 0.0f; // Because our octree cells are cubic we don't need to store the bounds
|
Vector3 box_max = Vector3Zero(); // area end
|
||||||
std::array<int, 8> children = {-1, -1, -1, -1, -1, -1, -1, -1};
|
std::array<int, 8> children = {-1, -1, -1, -1, -1, -1, -1, -1};
|
||||||
int mass_id = -1;
|
int mass_id = -1;
|
||||||
bool leaf = true;
|
bool leaf = true;
|
||||||
|
|
||||||
|
public:
|
||||||
|
[[nodiscard]] auto child_count() const -> int;
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
public:
|
||||||
// 21 * 3 = 63, fits in u64 for combined x/y/z morton-code
|
static constexpr int MAX_DEPTH = 20;
|
||||||
static constexpr int MAX_DEPTH = 21;
|
|
||||||
|
|
||||||
std::vector<node> nodes;
|
std::vector<node> nodes;
|
||||||
|
|
||||||
// This approach is actually slower than the array of nodes
|
|
||||||
// beacuse we access all the attributes in the same function
|
|
||||||
// std::vector<Vector3> mass_centers;
|
|
||||||
// std::vector<float> mass_totals;
|
|
||||||
// std::vector<Vector3> box_mins;
|
|
||||||
// std::vector<Vector3> box_maxs;
|
|
||||||
// std::vector<std::array<int, 8>> childrens;
|
|
||||||
// std::vector<int> mass_ids;
|
|
||||||
// std::vector<u8> leafs; // bitpacked std::vector<bool> is a lot slower
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
octree() = default;
|
octree() = default;
|
||||||
|
|
||||||
// Required for async octree
|
octree(const octree& copy) = delete;
|
||||||
// NO_COPY_NO_MOVE(octree);
|
auto operator=(const octree& copy) -> octree& = delete;
|
||||||
|
octree(octree&& move) = delete;
|
||||||
private:
|
auto operator=(octree&& move) -> octree& = delete;
|
||||||
[[nodiscard]] INLINE static inline auto get_octant(const Vector3& box_min,
|
|
||||||
const Vector3& box_max,
|
|
||||||
const Vector3& pos) -> int;
|
|
||||||
[[nodiscard]] INLINE static inline auto get_child_bounds(const Vector3& box_min,
|
|
||||||
const Vector3& box_max,
|
|
||||||
int octant) -> std::pair<Vector3, Vector3>;
|
|
||||||
|
|
||||||
// Map a floating point coordinate to a discrete integer (so its morton-code can be computed)
|
|
||||||
// The "bits" parameter determines the discrete axis resolution
|
|
||||||
[[nodiscard]] INLINE static inline auto quantize_axis(float coordinate,
|
|
||||||
float box_min,
|
|
||||||
float box_max,
|
|
||||||
int bits) -> u32;
|
|
||||||
|
|
||||||
[[nodiscard]] INLINE static inline auto pos_to_morton(const Vector3& p,
|
|
||||||
const Vector3& root_min,
|
|
||||||
const Vector3& root_max) -> u64;
|
|
||||||
|
|
||||||
[[nodiscard]] INLINE static inline auto jitter_pos(Vector3 p,
|
|
||||||
u32 seed,
|
|
||||||
const Vector3& root_min,
|
|
||||||
const Vector3& root_max,
|
|
||||||
float root_extent) -> Vector3;
|
|
||||||
|
|
||||||
// Use this to obtain an ancestor node of a leaf node (on any level).
|
|
||||||
// Because the morton codes (interleaved coordinates) encode the octree path, we can take
|
|
||||||
// the morten code of any leaf and only take the 3*n first interleaved bits to find the
|
|
||||||
// leaf ancestor on level n.
|
|
||||||
// Leaf Code: [101 110 100 001] -> Ancestors (from leaf to root):
|
|
||||||
// - [101 110 100]
|
|
||||||
// - [101 110]
|
|
||||||
// - [101] (root)
|
|
||||||
[[nodiscard]] INLINE static inline auto path_to_ancestor(u64 leaf_code, int leaf_depth, int depth) -> u64;
|
|
||||||
|
|
||||||
// Use this to obtain the octant a leaf node is contained in (on any level).
|
|
||||||
// The 3 interleaved bits in the morten code encode the octant [0, 7].
|
|
||||||
// Leaf Code: [101 110 100 001] -> Octants:
|
|
||||||
// - [100] (Level 2)
|
|
||||||
// - [110] (Level 1)
|
|
||||||
// - [101] (Level 0)
|
|
||||||
[[nodiscard]] INLINE static inline auto octant_at_level(u64 leaf_code, int level, int leaf_depth) -> int;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto clear() -> void;
|
auto create_empty_leaf(const Vector3& box_min, const Vector3& box_max) -> int;
|
||||||
auto reserve(size_t count) -> void;
|
|
||||||
[[nodiscard]] auto empty() const -> bool;
|
|
||||||
[[nodiscard]] auto root() const -> const node&;
|
|
||||||
|
|
||||||
// Morton/linear octree implementation
|
[[nodiscard]] auto get_octant(int node_idx, const Vector3& pos) const -> int;
|
||||||
static auto build_octree_morton(octree& t,
|
|
||||||
const std::vector<Vector3>& positions,
|
[[nodiscard]] auto get_child_bounds(int node_idx, int octant) const
|
||||||
const std::optional<BS::thread_pool<>*>& thread_pool) -> void;
|
-> std::pair<Vector3, Vector3>;
|
||||||
[[nodiscard]] auto calculate_force_morton(int node_idx, const Vector3& pos, int self_id) const -> Vector3;
|
|
||||||
|
auto insert(int node_idx, int mass_id, const Vector3& pos, float mass, int depth) -> void;
|
||||||
|
|
||||||
|
[[nodiscard]] auto calculate_force(int node_idx, const Vector3& pos) const -> Vector3;
|
||||||
};
|
};
|
||||||
|
|
||||||
INLINE inline auto octree::get_octant(const Vector3& box_min, const Vector3& box_max, const Vector3& pos) -> int
|
#endif
|
||||||
{
|
|
||||||
auto [cx, cy, cz] = (box_min + box_max) / 2.0f;
|
|
||||||
|
|
||||||
// The octant is encoded as a 3-bit integer "zyx". The node area is split
|
|
||||||
// along all 3 axes, if a position is right of an axis, this bit is set to 1.
|
|
||||||
// If a position is right of the x-axis and y-axis and left of the z-axis, the
|
|
||||||
// encoded octant is "011".
|
|
||||||
return (pos.x >= cx) | ((pos.y >= cy) << 1) | ((pos.z >= cz) << 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
INLINE inline auto octree::get_child_bounds(const Vector3& box_min,
|
|
||||||
const Vector3& box_max,
|
|
||||||
const int octant) -> std::pair<Vector3, Vector3>
|
|
||||||
{
|
|
||||||
auto [cx, cy, cz] = (box_min + box_max) / 2.0f;
|
|
||||||
|
|
||||||
Vector3 min = Vector3Zero();
|
|
||||||
Vector3 max = Vector3Zero();
|
|
||||||
|
|
||||||
// If (octant & 1), the octant is to the right of the node region's x-axis
|
|
||||||
// (see GetOctant). This means the left bound is the x-axis and the right
|
|
||||||
// bound the node's region max.
|
|
||||||
min.x = octant & 1 ? cx : box_min.x;
|
|
||||||
max.x = octant & 1 ? box_max.x : cx;
|
|
||||||
min.y = octant & 2 ? cy : box_min.y;
|
|
||||||
max.y = octant & 2 ? box_max.y : cy;
|
|
||||||
min.z = octant & 4 ? cz : box_min.z;
|
|
||||||
max.z = octant & 4 ? box_max.z : cz;
|
|
||||||
|
|
||||||
return std::make_pair(min, max);
|
|
||||||
}
|
|
||||||
|
|
||||||
INLINE inline auto octree::quantize_axis(const float coordinate,
|
|
||||||
const float box_min,
|
|
||||||
const float box_max,
|
|
||||||
const int bits) -> u32
|
|
||||||
{
|
|
||||||
const float extent = box_max - box_min;
|
|
||||||
if (extent <= 0.0f) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
float normalized = (coordinate - box_min) / extent; // normalize to [0,1]
|
|
||||||
normalized = std::max(0.0f, std::min(normalized, std::nextafter(1.0f, 0.0f))); // avoid exactly 1.0
|
|
||||||
|
|
||||||
// bits up to 21 => (1u << bits) safe in 32-bit
|
|
||||||
const u32 grid_max = (1u << bits) - 1u;
|
|
||||||
return static_cast<u32>(normalized * static_cast<float>(grid_max));
|
|
||||||
}
|
|
||||||
|
|
||||||
INLINE inline auto octree::pos_to_morton(const Vector3& p, const Vector3& root_min, const Vector3& root_max) -> u64
|
|
||||||
{
|
|
||||||
const u32 x = quantize_axis(p.x, root_min.x, root_max.x, MAX_DEPTH);
|
|
||||||
const u32 y = quantize_axis(p.y, root_min.y, root_max.y, MAX_DEPTH);
|
|
||||||
const u32 z = quantize_axis(p.z, root_min.z, root_max.z, MAX_DEPTH);
|
|
||||||
return libmorton::morton3D_64_encode(x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
INLINE inline auto octree::jitter_pos(Vector3 p,
|
|
||||||
const u32 seed,
|
|
||||||
const Vector3& root_min,
|
|
||||||
const Vector3& root_max,
|
|
||||||
const float root_extent) -> Vector3
|
|
||||||
{
|
|
||||||
// Use a hash to calculate a deterministic jitter: The same position should always get the same jitter.
|
|
||||||
// We want this to get stable physics, particles at the same position shouldn't get different jitters
|
|
||||||
// across frames...
|
|
||||||
u32 h = (seed ^ 61u) ^ (seed >> 16);
|
|
||||||
h *= 9u;
|
|
||||||
h = h ^ (h >> 4);
|
|
||||||
h *= 0x27d4eb2du;
|
|
||||||
h = h ^ (h >> 15);
|
|
||||||
|
|
||||||
// finest cell size at depth L
|
|
||||||
const float finest_cell = root_extent / static_cast<float>(1u << MAX_DEPTH);
|
|
||||||
const float s = finest_cell * 1e-4f; // small pp
|
|
||||||
|
|
||||||
p.x += (h & 1u) ? +s : -s;
|
|
||||||
p.y += (h & 2u) ? +s : -s;
|
|
||||||
p.z += (h & 4u) ? +s : -s;
|
|
||||||
|
|
||||||
// clamp back into bounds just in case
|
|
||||||
p.x = std::max(root_min.x, std::min(p.x, root_max.x));
|
|
||||||
p.y = std::max(root_min.y, std::min(p.y, root_max.y));
|
|
||||||
p.z = std::max(root_min.z, std::min(p.z, root_max.z));
|
|
||||||
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
INLINE inline auto octree::path_to_ancestor(const u64 leaf_code, const int leaf_depth, const int depth) -> u64
|
|
||||||
{
|
|
||||||
// keep top 3*depth bits; drop the rest
|
|
||||||
const int drop = 3 * (leaf_depth - depth);
|
|
||||||
return (drop > 0) ? (leaf_code >> drop) : leaf_code;
|
|
||||||
}
|
|
||||||
|
|
||||||
INLINE inline auto octree::octant_at_level(const u64 leaf_code, const int level, const int leaf_depth) -> int
|
|
||||||
{
|
|
||||||
// level 1 => child of root => topmost 3 bits
|
|
||||||
const int shift = 3 * (leaf_depth - level);
|
|
||||||
return static_cast<int>((leaf_code >> shift) & 0x7ull);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@ -24,7 +24,8 @@ public:
|
|||||||
|
|
||||||
auto pan(Vector2 last_mouse, Vector2 mouse) -> void;
|
auto pan(Vector2 last_mouse, Vector2 mouse) -> void;
|
||||||
|
|
||||||
auto update(const Vector3& current_target, const Vector3& mass_center, bool lock, bool mass_center_lock) -> void;
|
auto update(const Vector3& current_target, const Vector3& mass_center, bool lock,
|
||||||
|
bool mass_center_lock) -> void;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,8 @@
|
|||||||
#ifndef RENDERER_HPP_
|
#ifndef RENDERER_HPP_
|
||||||
#define RENDERER_HPP_
|
#define RENDERER_HPP_
|
||||||
|
|
||||||
#include "config.hpp"
|
|
||||||
#include "orbit_camera.hpp"
|
#include "orbit_camera.hpp"
|
||||||
|
#include "config.hpp"
|
||||||
#include "input_handler.hpp"
|
#include "input_handler.hpp"
|
||||||
#include "state_manager.hpp"
|
#include "state_manager.hpp"
|
||||||
#include "user_interface.hpp"
|
#include "user_interface.hpp"
|
||||||
@ -14,73 +14,55 @@ class renderer
|
|||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
const state_manager& state;
|
const state_manager& state;
|
||||||
input_handler& input;
|
const input_handler& input;
|
||||||
user_interface& gui;
|
user_interface& gui;
|
||||||
|
|
||||||
const orbit_camera& camera;
|
const orbit_camera& camera;
|
||||||
RenderTexture graph_target = LoadRenderTexture(GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT);
|
RenderTexture render_target =
|
||||||
|
LoadRenderTexture(GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT);
|
||||||
|
|
||||||
// TODO: Those should be moved to the user_interface.h
|
// TODO: Those should be moved to the user_interface.h
|
||||||
RenderTexture klotski_target = LoadRenderTexture(GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT);
|
RenderTexture klotski_target =
|
||||||
|
LoadRenderTexture(GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT);
|
||||||
RenderTexture menu_target = LoadRenderTexture(GetScreenWidth(), MENU_HEIGHT);
|
RenderTexture menu_target = LoadRenderTexture(GetScreenWidth(), MENU_HEIGHT);
|
||||||
|
|
||||||
// Edges
|
// Batching
|
||||||
unsigned int edge_vao_id = 0;
|
|
||||||
unsigned int edge_vbo_id = 0;
|
|
||||||
std::vector<Vector3> edge_vertices;
|
|
||||||
Shader edge_shader = LoadShader("shader/edge_vertex.glsl", "shader/edge_fragment.glsl");
|
|
||||||
int edge_color_loc = -1;
|
|
||||||
std::vector<std::pair<Vector3, Vector3>> connections;
|
std::vector<std::pair<Vector3, Vector3>> connections;
|
||||||
|
|
||||||
// Vertex instancing
|
// Instancing
|
||||||
static constexpr int INSTANCE_COLOR_ATTR = 5;
|
static constexpr int INSTANCE_COLOR_ATTR = 5;
|
||||||
std::vector<Matrix> transforms;
|
std::vector<Matrix> transforms;
|
||||||
std::vector<Color> colors;
|
std::vector<Color> colors;
|
||||||
Material vertex_mat = LoadMaterialDefault();
|
Material vertex_mat = LoadMaterialDefault();
|
||||||
Mesh cube_instance = GenMeshCube(VERTEX_SIZE, VERTEX_SIZE, VERTEX_SIZE);
|
Mesh cube_instance = GenMeshCube(VERTEX_SIZE, VERTEX_SIZE, VERTEX_SIZE);
|
||||||
Shader instancing_shader = LoadShader("shader/instancing_vertex.glsl", "shader/instancing_fragment.glsl");
|
Shader instancing_shader =
|
||||||
|
LoadShader("shader/instancing_vertex.glsl", "shader/instancing_fragment.glsl");
|
||||||
|
|
||||||
unsigned int color_vbo_id = 0;
|
unsigned int color_vbo_id = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// TODO: I am allocating HUGE vertex buffers instead of resizing dynamically...
|
renderer(const orbit_camera& _camera, const state_manager& _state, const input_handler& _input,
|
||||||
// Edges: 5'000'000 * 2 * 12 Byte ~= 115 MB
|
user_interface& _gui)
|
||||||
// Verts: 1'000'000 * 16 Byte ~= 15 MB
|
|
||||||
// This is also allocated on the CPU by the vectors
|
|
||||||
renderer(const orbit_camera& _camera, const state_manager& _state, input_handler& _input, user_interface& _gui)
|
|
||||||
: state(_state), input(_input), gui(_gui), camera(_camera)
|
: state(_state), input(_input), gui(_gui), camera(_camera)
|
||||||
{
|
{
|
||||||
// Edges
|
|
||||||
edge_shader.locs[SHADER_LOC_VERTEX_POSITION] = GetShaderLocationAttrib(edge_shader, "vertexPosition");
|
|
||||||
edge_shader.locs[SHADER_LOC_MATRIX_MVP] = GetShaderLocation(edge_shader, "mvp");
|
|
||||||
edge_shader.locs[SHADER_LOC_COLOR_DIFFUSE] = GetShaderLocation(edge_shader, "colDiffuse");
|
|
||||||
edge_color_loc = GetShaderLocation(edge_shader, "colDiffuse");
|
|
||||||
|
|
||||||
edge_vertices.reserve(DRAW_EDGES_LIMIT * 2);
|
|
||||||
|
|
||||||
edge_vao_id = rlLoadVertexArray();
|
|
||||||
edge_vbo_id = rlLoadVertexBuffer(nullptr, DRAW_EDGES_LIMIT * 2 * sizeof(Vector3), true);
|
|
||||||
|
|
||||||
rlEnableVertexArray(edge_vao_id);
|
|
||||||
rlEnableVertexBuffer(edge_vbo_id);
|
|
||||||
|
|
||||||
rlSetVertexAttribute(0, 3, RL_FLOAT, false, sizeof(Vector3), 0);
|
|
||||||
rlEnableVertexAttribute(0);
|
|
||||||
|
|
||||||
rlDisableVertexBuffer();
|
|
||||||
rlDisableVertexArray();
|
|
||||||
|
|
||||||
// Vertex instancing
|
|
||||||
instancing_shader.locs[SHADER_LOC_MATRIX_MVP] = GetShaderLocation(instancing_shader, "mvp");
|
instancing_shader.locs[SHADER_LOC_MATRIX_MVP] = GetShaderLocation(instancing_shader, "mvp");
|
||||||
instancing_shader.locs[SHADER_LOC_MATRIX_MODEL] = GetShaderLocationAttrib(
|
instancing_shader.locs[SHADER_LOC_MATRIX_MODEL] =
|
||||||
instancing_shader,
|
GetShaderLocationAttrib(instancing_shader, "instanceTransform");
|
||||||
"instanceTransform");
|
instancing_shader.locs[SHADER_LOC_VECTOR_VIEW] =
|
||||||
instancing_shader.locs[SHADER_LOC_VECTOR_VIEW] = GetShaderLocation(instancing_shader, "viewPos");
|
GetShaderLocation(instancing_shader, "viewPos");
|
||||||
|
|
||||||
|
// infoln("LOC vertexPosition: {}",
|
||||||
|
// rlGetLocationAttrib(instancing_shader.id, "vertexPosition"));
|
||||||
|
// infoln("LOC instanceTransform: {}",
|
||||||
|
// rlGetLocationAttrib(instancing_shader.id, "instanceTransform"));
|
||||||
|
// infoln("LOC instanceColor: {}", rlGetLocationAttrib(instancing_shader.id, "instanceColor"));
|
||||||
|
|
||||||
|
// vertex_mat.maps[MATERIAL_MAP_DIFFUSE].color = VERTEX_COLOR;
|
||||||
vertex_mat.shader = instancing_shader;
|
vertex_mat.shader = instancing_shader;
|
||||||
|
|
||||||
transforms.reserve(DRAW_VERTICES_LIMIT);
|
transforms.reserve(DRAW_VERTICES_LIMIT);
|
||||||
colors.reserve(DRAW_VERTICES_LIMIT);
|
colors.reserve(DRAW_VERTICES_LIMIT);
|
||||||
color_vbo_id = rlLoadVertexBuffer(nullptr, DRAW_VERTICES_LIMIT * sizeof(Color), true);
|
color_vbo_id = rlLoadVertexBuffer(colors.data(), DRAW_VERTICES_LIMIT * sizeof(Color), true);
|
||||||
|
|
||||||
rlEnableVertexArray(cube_instance.vaoId);
|
rlEnableVertexArray(cube_instance.vaoId);
|
||||||
rlEnableVertexBuffer(color_vbo_id);
|
rlEnableVertexBuffer(color_vbo_id);
|
||||||
@ -92,27 +74,23 @@ public:
|
|||||||
rlDisableVertexArray();
|
rlDisableVertexArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
NO_COPY_NO_MOVE(renderer);
|
renderer(const renderer& copy) = delete;
|
||||||
|
auto operator=(const renderer& copy) -> renderer& = delete;
|
||||||
|
renderer(renderer&& move) = delete;
|
||||||
|
auto operator=(renderer&& move) -> renderer& = delete;
|
||||||
|
|
||||||
~renderer()
|
~renderer()
|
||||||
{
|
{
|
||||||
UnloadRenderTexture(graph_target);
|
UnloadRenderTexture(render_target);
|
||||||
UnloadRenderTexture(klotski_target);
|
UnloadRenderTexture(klotski_target);
|
||||||
UnloadRenderTexture(menu_target);
|
UnloadRenderTexture(menu_target);
|
||||||
|
|
||||||
// Edges
|
|
||||||
rlUnloadVertexArray(edge_vao_id);
|
|
||||||
rlUnloadVertexBuffer(edge_vbo_id);
|
|
||||||
UnloadShader(edge_shader);
|
|
||||||
|
|
||||||
// Instancing
|
// Instancing
|
||||||
UnloadMaterial(vertex_mat);
|
UnloadMaterial(vertex_mat);
|
||||||
UnloadMesh(cube_instance);
|
UnloadMesh(cube_instance);
|
||||||
|
|
||||||
// I think the shader already gets unloaded with the material?
|
// I think the shader already gets unloaded with the material?
|
||||||
// UnloadShader(instancing_shader);
|
// UnloadShader(instancing_shader);
|
||||||
|
|
||||||
rlUnloadVertexBuffer(color_vbo_id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -124,7 +102,8 @@ private:
|
|||||||
auto draw_textures(int fps, int ups, size_t mass_count, size_t spring_count) const -> void;
|
auto draw_textures(int fps, int ups, size_t mass_count, size_t spring_count) const -> void;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto render(const std::vector<Vector3>& masses, int fps, int ups, size_t mass_count, size_t spring_count) -> void;
|
auto render(const std::vector<Vector3>& masses, int fps, int ups, size_t mass_count,
|
||||||
|
size_t spring_count) -> void;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@ -2,17 +2,17 @@
|
|||||||
#define STATE_MANAGER_HPP_
|
#define STATE_MANAGER_HPP_
|
||||||
|
|
||||||
#include "graph_distances.hpp"
|
#include "graph_distances.hpp"
|
||||||
#include "load_save.hpp"
|
#include "threaded_physics.hpp"
|
||||||
#include "cpu_layout_engine.hpp"
|
|
||||||
#include "puzzle.hpp"
|
#include "puzzle.hpp"
|
||||||
|
|
||||||
|
#include <stack>
|
||||||
#include <boost/unordered/unordered_flat_map.hpp>
|
#include <boost/unordered/unordered_flat_map.hpp>
|
||||||
#include <boost/unordered/unordered_flat_set.hpp>
|
#include <boost/unordered/unordered_flat_set.hpp>
|
||||||
|
|
||||||
class state_manager
|
class state_manager
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
cpu_layout_engine& physics;
|
threaded_physics& physics;
|
||||||
|
|
||||||
std::string preset_file;
|
std::string preset_file;
|
||||||
size_t current_preset = 0;
|
size_t current_preset = 0;
|
||||||
@ -22,9 +22,9 @@ private:
|
|||||||
// State storage (store states twice for bidirectional lookup).
|
// State storage (store states twice for bidirectional lookup).
|
||||||
// Everything else should only store indices to state_pool.
|
// Everything else should only store indices to state_pool.
|
||||||
|
|
||||||
std::vector<puzzle> state_pool; // Indices are equal to mass_springs mass indices
|
std::vector<puzzle> state_pool; // Indices are equal to mass_springs mass indices
|
||||||
puzzlemap<size_t> state_indices; // Maps states to indices
|
boost::unordered_flat_map<puzzle, size_t, puzzle_hasher> state_indices; // Maps states to indices
|
||||||
std::vector<spring> links; // Indices are equal to mass_springs springs indices
|
std::vector<std::pair<size_t, size_t>> links; // Indices are equal to mass_springs springs indices
|
||||||
|
|
||||||
graph_distances node_target_distances; // Buffered and reused if the graph doesn't change
|
graph_distances node_target_distances; // Buffered and reused if the graph doesn't change
|
||||||
boost::unordered_flat_set<size_t> winning_indices; // Indices of all states where the board is solved
|
boost::unordered_flat_set<size_t> winning_indices; // Indices of all states where the board is solved
|
||||||
@ -42,13 +42,17 @@ private:
|
|||||||
bool edited = false;
|
bool edited = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
state_manager(cpu_layout_engine& _physics, const std::string& _preset_file)
|
state_manager(threaded_physics& _physics, const std::string& _preset_file)
|
||||||
: physics(_physics), preset_file(_preset_file)
|
: physics(_physics)
|
||||||
{
|
{
|
||||||
reload_preset_file();
|
parse_preset_file(_preset_file);
|
||||||
|
load_preset(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
NO_COPY_NO_MOVE(state_manager);
|
state_manager(const state_manager& copy) = delete;
|
||||||
|
auto operator=(const state_manager& copy) -> state_manager& = delete;
|
||||||
|
state_manager(state_manager&& move) = delete;
|
||||||
|
auto operator=(state_manager&& move) -> state_manager& = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
@ -80,7 +84,8 @@ private:
|
|||||||
* @param states List of states to insert
|
* @param states List of states to insert
|
||||||
* @param _links List of links to insert
|
* @param _links List of links to insert
|
||||||
*/
|
*/
|
||||||
auto synced_insert_statespace(const std::vector<puzzle>& states, const std::vector<spring>& _links) -> void;
|
auto synced_insert_statespace(const std::vector<puzzle>& states,
|
||||||
|
const std::vector<std::pair<size_t, size_t>>& _links) -> void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears all states and links (and related) from the state_manager and the physics system.
|
* Clears all states and links (and related) from the state_manager and the physics system.
|
||||||
@ -90,13 +95,15 @@ private:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
// Presets
|
// Presets
|
||||||
auto save_current_to_preset_file(const std::string& preset_comment) -> void;
|
|
||||||
auto reload_preset_file() -> void;
|
auto parse_preset_file(const std::string& _preset_file) -> bool;
|
||||||
|
auto append_preset_file(const std::string& preset_name) -> bool;
|
||||||
auto load_preset(size_t preset) -> void;
|
auto load_preset(size_t preset) -> void;
|
||||||
auto load_previous_preset() -> void;
|
auto load_previous_preset() -> void;
|
||||||
auto load_next_preset() -> void;
|
auto load_next_preset() -> void;
|
||||||
|
|
||||||
// Update current_state
|
// Update current_state
|
||||||
|
|
||||||
auto update_current_state(const puzzle& p) -> void;
|
auto update_current_state(const puzzle& p) -> void;
|
||||||
auto edit_starting_state(const puzzle& p) -> void;
|
auto edit_starting_state(const puzzle& p) -> void;
|
||||||
auto goto_starting_state() -> void;
|
auto goto_starting_state() -> void;
|
||||||
@ -106,6 +113,7 @@ public:
|
|||||||
auto goto_closest_target_state() -> void;
|
auto goto_closest_target_state() -> void;
|
||||||
|
|
||||||
// Update graph
|
// Update graph
|
||||||
|
|
||||||
auto populate_graph() -> void;
|
auto populate_graph() -> void;
|
||||||
auto clear_graph_and_add_current(const puzzle& p) -> void;
|
auto clear_graph_and_add_current(const puzzle& p) -> void;
|
||||||
auto clear_graph_and_add_current() -> void;
|
auto clear_graph_and_add_current() -> void;
|
||||||
@ -114,6 +122,7 @@ public:
|
|||||||
auto populate_winning_path() -> void;
|
auto populate_winning_path() -> void;
|
||||||
|
|
||||||
// Index mapping
|
// Index mapping
|
||||||
|
|
||||||
[[nodiscard]] auto get_index(const puzzle& state) const -> size_t;
|
[[nodiscard]] auto get_index(const puzzle& state) const -> size_t;
|
||||||
[[nodiscard]] auto get_current_index() const -> size_t;
|
[[nodiscard]] auto get_current_index() const -> size_t;
|
||||||
[[nodiscard]] auto get_starting_index() const -> size_t;
|
[[nodiscard]] auto get_starting_index() const -> size_t;
|
||||||
@ -126,7 +135,7 @@ public:
|
|||||||
[[nodiscard]] auto get_target_count() const -> size_t;
|
[[nodiscard]] auto get_target_count() const -> size_t;
|
||||||
[[nodiscard]] auto get_link_count() const -> size_t;
|
[[nodiscard]] auto get_link_count() const -> size_t;
|
||||||
[[nodiscard]] auto get_path_length() const -> size_t;
|
[[nodiscard]] auto get_path_length() const -> size_t;
|
||||||
[[nodiscard]] auto get_links() const -> const std::vector<spring>&;
|
[[nodiscard]] auto get_links() const -> const std::vector<std::pair<size_t, size_t>>&;
|
||||||
[[nodiscard]] auto get_winning_indices() const -> const boost::unordered_flat_set<size_t>&;
|
[[nodiscard]] auto get_winning_indices() const -> const boost::unordered_flat_set<size_t>&;
|
||||||
[[nodiscard]] auto get_visit_counts() const -> const boost::unordered_flat_map<size_t, int>&;
|
[[nodiscard]] auto get_visit_counts() const -> const boost::unordered_flat_map<size_t, int>&;
|
||||||
[[nodiscard]] auto get_winning_path() const -> const std::vector<size_t>&;
|
[[nodiscard]] auto get_winning_path() const -> const std::vector<size_t>&;
|
||||||
@ -137,7 +146,6 @@ public:
|
|||||||
[[nodiscard]] auto get_current_preset_comment() const -> const std::string&;
|
[[nodiscard]] auto get_current_preset_comment() const -> const std::string&;
|
||||||
[[nodiscard]] auto has_history() const -> bool;
|
[[nodiscard]] auto has_history() const -> bool;
|
||||||
[[nodiscard]] auto has_distances() const -> bool;
|
[[nodiscard]] auto has_distances() const -> bool;
|
||||||
[[nodiscard]] auto get_distances() const -> std::vector<int>;
|
|
||||||
[[nodiscard]] auto get_total_moves() const -> size_t;
|
[[nodiscard]] auto get_total_moves() const -> size_t;
|
||||||
[[nodiscard]] auto was_edited() const -> bool;
|
[[nodiscard]] auto was_edited() const -> bool;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
#ifndef PHYSICS_HPP_
|
#ifndef PHYSICS_HPP_
|
||||||
#define PHYSICS_HPP_
|
#define PHYSICS_HPP_
|
||||||
|
|
||||||
#include "config.hpp"
|
|
||||||
#include "cpu_spring_system.hpp"
|
|
||||||
#include "util.hpp"
|
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@ -15,10 +11,13 @@
|
|||||||
#include <variant>
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class cpu_layout_engine
|
#ifdef TRACY
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class threaded_physics
|
||||||
{
|
{
|
||||||
struct add_mass
|
struct add_mass {};
|
||||||
{};
|
|
||||||
|
|
||||||
struct add_spring
|
struct add_spring
|
||||||
{
|
{
|
||||||
@ -26,25 +25,24 @@ class cpu_layout_engine
|
|||||||
size_t b;
|
size_t b;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct clear_graph
|
struct clear_graph {};
|
||||||
{};
|
|
||||||
|
|
||||||
using command = std::variant<add_mass, add_spring, clear_graph>;
|
using command = std::variant<add_mass, add_spring, clear_graph>;
|
||||||
|
|
||||||
struct physics_state
|
struct physics_state
|
||||||
{
|
{
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
TracyLockable(std::mutex, command_mtx);
|
TracyLockable(std::mutex, command_mtx);
|
||||||
#else
|
#else
|
||||||
std::mutex command_mtx;
|
std::mutex command_mtx;
|
||||||
#endif
|
#endif
|
||||||
std::queue<command> pending_commands;
|
std::queue<command> pending_commands;
|
||||||
|
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
TracyLockable(std::mutex, data_mtx);
|
TracyLockable(std::mutex, data_mtx);
|
||||||
#else
|
#else
|
||||||
std::mutex data_mtx;
|
std::mutex data_mtx;
|
||||||
#endif
|
#endif
|
||||||
std::condition_variable_any data_ready_cnd;
|
std::condition_variable_any data_ready_cnd;
|
||||||
std::condition_variable_any data_consumed_cnd;
|
std::condition_variable_any data_consumed_cnd;
|
||||||
Vector3 mass_center = Vector3Zero();
|
Vector3 mass_center = Vector3Zero();
|
||||||
@ -59,21 +57,21 @@ class cpu_layout_engine
|
|||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
threadpool thread_pool;
|
|
||||||
std::thread physics;
|
std::thread physics;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
physics_state state;
|
physics_state state;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit cpu_layout_engine(
|
threaded_physics()
|
||||||
const threadpool _thread_pool = std::nullopt)
|
: physics(physics_thread, std::ref(state)) {}
|
||||||
: thread_pool(_thread_pool), physics(physics_thread, std::ref(state), std::ref(thread_pool))
|
|
||||||
{}
|
|
||||||
|
|
||||||
NO_COPY_NO_MOVE(cpu_layout_engine);
|
threaded_physics(const threaded_physics& copy) = delete;
|
||||||
|
auto operator=(const threaded_physics& copy) -> threaded_physics& = delete;
|
||||||
|
threaded_physics(threaded_physics&& move) = delete;
|
||||||
|
auto operator=(threaded_physics&& move) -> threaded_physics& = delete;
|
||||||
|
|
||||||
~cpu_layout_engine()
|
~threaded_physics()
|
||||||
{
|
{
|
||||||
state.running = false;
|
state.running = false;
|
||||||
state.data_ready_cnd.notify_all();
|
state.data_ready_cnd.notify_all();
|
||||||
@ -82,19 +80,16 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#ifdef ASYNC_OCTREE
|
static auto physics_thread(physics_state& state) -> void;
|
||||||
static auto set_octree_pool_thread_name(size_t idx) -> void;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static auto physics_thread(physics_state& state,
|
|
||||||
threadpool thread_pool) -> void;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto clear_cmd() -> void;
|
|
||||||
auto add_mass_cmd() -> void;
|
auto add_mass_cmd() -> void;
|
||||||
|
|
||||||
auto add_spring_cmd(size_t a, size_t b) -> void;
|
auto add_spring_cmd(size_t a, size_t b) -> void;
|
||||||
auto add_mass_springs_cmd(size_t num_masses,
|
|
||||||
const std::vector<spring>& springs) -> void;
|
auto clear_cmd() -> void;
|
||||||
|
|
||||||
|
auto add_mass_springs_cmd(size_t num_masses, const std::vector<std::pair<size_t, size_t>>& springs) -> void;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@ -1,5 +1,5 @@
|
|||||||
#ifndef USER_INTERFACE_HPP_
|
#ifndef GUI_HPP_
|
||||||
#define USER_INTERFACE_HPP_
|
#define GUI_HPP_
|
||||||
|
|
||||||
#include "orbit_camera.hpp"
|
#include "orbit_camera.hpp"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
@ -22,17 +22,15 @@ class user_interface
|
|||||||
const int padding;
|
const int padding;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
grid(const int _x,
|
grid(const int _x, const int _y, const int _width, const int _height, const int _columns,
|
||||||
const int _y,
|
const int _rows, const int _padding)
|
||||||
const int _width,
|
: x(_x), y(_y), width(_width), height(_height), columns(_columns), rows(_rows),
|
||||||
const int _height,
|
padding(_padding)
|
||||||
const int _columns,
|
{}
|
||||||
const int _rows,
|
|
||||||
const int _padding)
|
|
||||||
: x(_x), y(_y), width(_width), height(_height), columns(_columns), rows(_rows), padding(_padding) {}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto update_bounds(int _x, int _y, int _width, int _height, int _columns, int _rows) -> void;
|
auto update_bounds(int _x, int _y, int _width, int _height, int _columns, int _rows)
|
||||||
|
-> void;
|
||||||
auto update_bounds(int _x, int _y, int _width, int _height) -> void;
|
auto update_bounds(int _x, int _y, int _width, int _height) -> void;
|
||||||
auto update_bounds(int _x, int _y) -> void;
|
auto update_bounds(int _x, int _y) -> void;
|
||||||
|
|
||||||
@ -40,7 +38,8 @@ class user_interface
|
|||||||
[[nodiscard]] auto bounds(int _x, int _y, int _width, int _height) const -> Rectangle;
|
[[nodiscard]] auto bounds(int _x, int _y, int _width, int _height) const -> Rectangle;
|
||||||
|
|
||||||
[[nodiscard]] auto square_bounds() const -> Rectangle;
|
[[nodiscard]] auto square_bounds() const -> Rectangle;
|
||||||
[[nodiscard]] auto square_bounds(int _x, int _y, int _width, int _height) const -> Rectangle;
|
[[nodiscard]] auto square_bounds(int _x, int _y, int _width, int _height) const
|
||||||
|
-> Rectangle;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct style
|
struct style
|
||||||
@ -88,17 +87,14 @@ private:
|
|||||||
|
|
||||||
grid menu_grid = grid(0, 0, GetScreenWidth(), MENU_HEIGHT, MENU_COLS, MENU_ROWS, MENU_PAD);
|
grid menu_grid = grid(0, 0, GetScreenWidth(), MENU_HEIGHT, MENU_COLS, MENU_ROWS, MENU_PAD);
|
||||||
|
|
||||||
grid board_grid = grid(0,
|
grid board_grid =
|
||||||
MENU_HEIGHT,
|
grid(0, MENU_HEIGHT, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT,
|
||||||
GetScreenWidth() / 2,
|
state.get_current_state().get_width(), state.get_current_state().get_height(), BOARD_PADDING);
|
||||||
GetScreenHeight() - MENU_HEIGHT,
|
|
||||||
state.get_current_state().get_width(),
|
|
||||||
state.get_current_state().get_height(),
|
|
||||||
BOARD_PADDING);
|
|
||||||
|
|
||||||
grid graph_overlay_grid = grid(GetScreenWidth() / 2, MENU_HEIGHT, 200, 100, 1, 4, MENU_PAD);
|
grid graph_overlay_grid = grid(GetScreenWidth() / 2, MENU_HEIGHT, 200, 100, 1, 4, MENU_PAD);
|
||||||
|
|
||||||
grid debug_overlay_grid = grid(GetScreenWidth() / 2, GetScreenHeight() - 75, 200, 75, 1, 3, MENU_PAD);
|
grid debug_overlay_grid =
|
||||||
|
grid(GetScreenWidth() / 2, GetScreenHeight() - 75, 200, 75, 1, 3, MENU_PAD);
|
||||||
|
|
||||||
// Windows
|
// Windows
|
||||||
|
|
||||||
@ -108,7 +104,7 @@ private:
|
|||||||
bool ok_message = false;
|
bool ok_message = false;
|
||||||
bool yes_no_message = false;
|
bool yes_no_message = false;
|
||||||
bool save_window = false;
|
bool save_window = false;
|
||||||
std::array<char, 256> preset_comment = {};
|
std::array<char, 256> preset_name = {};
|
||||||
bool help_window = false;
|
bool help_window = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -118,7 +114,10 @@ public:
|
|||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
NO_COPY_NO_MOVE(user_interface);
|
user_interface(const user_interface& copy) = delete;
|
||||||
|
auto operator=(const user_interface& copy) -> user_interface& = delete;
|
||||||
|
user_interface(user_interface&& move) = delete;
|
||||||
|
auto operator=(user_interface&& move) -> user_interface& = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static auto init() -> void;
|
static auto init() -> void;
|
||||||
@ -134,68 +133,32 @@ private:
|
|||||||
|
|
||||||
[[nodiscard]] static auto popup_bounds() -> Rectangle;
|
[[nodiscard]] static auto popup_bounds() -> Rectangle;
|
||||||
|
|
||||||
auto draw_button(Rectangle bounds,
|
auto draw_button(Rectangle bounds, const std::string& label, Color color, bool enabled = true,
|
||||||
const std::string& label,
|
|
||||||
Color color,
|
|
||||||
bool enabled = true,
|
|
||||||
int font_size = FONT_SIZE) const -> int;
|
int font_size = FONT_SIZE) const -> int;
|
||||||
|
|
||||||
auto draw_menu_button(int x,
|
auto draw_menu_button(int x, int y, int width, int height, const std::string& label,
|
||||||
int y,
|
Color color, bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
const std::string& label,
|
|
||||||
Color color,
|
|
||||||
bool enabled = true,
|
|
||||||
int font_size = FONT_SIZE) const -> int;
|
|
||||||
|
|
||||||
auto draw_toggle_slider(Rectangle bounds,
|
auto draw_toggle_slider(Rectangle bounds, const std::string& off_label,
|
||||||
const std::string& off_label,
|
const std::string& on_label, int* active, Color color,
|
||||||
const std::string& on_label,
|
bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||||
int* active,
|
|
||||||
Color color,
|
|
||||||
bool enabled = true,
|
|
||||||
int font_size = FONT_SIZE) const -> int;
|
|
||||||
|
|
||||||
auto draw_menu_toggle_slider(int x,
|
auto draw_menu_toggle_slider(int x, int y, int width, int height, const std::string& off_label,
|
||||||
int y,
|
const std::string& on_label, int* active, Color color,
|
||||||
int width,
|
bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||||
int height,
|
|
||||||
const std::string& off_label,
|
|
||||||
const std::string& on_label,
|
|
||||||
int* active,
|
|
||||||
Color color,
|
|
||||||
bool enabled = true,
|
|
||||||
int font_size = FONT_SIZE) const -> int;
|
|
||||||
|
|
||||||
auto draw_spinner(Rectangle bounds,
|
auto draw_spinner(Rectangle bounds, const std::string& label, int* value, int min, int max,
|
||||||
const std::string& label,
|
Color color, bool enabled = true, int font_size = FONT_SIZE) const -> int;
|
||||||
int* value,
|
|
||||||
int min,
|
|
||||||
int max,
|
|
||||||
Color color,
|
|
||||||
bool enabled = true,
|
|
||||||
int font_size = FONT_SIZE) const -> int;
|
|
||||||
|
|
||||||
auto draw_menu_spinner(int x,
|
auto draw_menu_spinner(int x, int y, int width, int height, const std::string& label,
|
||||||
int y,
|
int* value, int min, int max, Color color, bool enabled = true,
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
const std::string& label,
|
|
||||||
int* value,
|
|
||||||
int min,
|
|
||||||
int max,
|
|
||||||
Color color,
|
|
||||||
bool enabled = true,
|
|
||||||
int font_size = FONT_SIZE) const -> int;
|
int font_size = FONT_SIZE) const -> int;
|
||||||
|
|
||||||
auto draw_label(Rectangle bounds,
|
auto draw_label(Rectangle bounds, const std::string& text, Color color, bool enabled = true,
|
||||||
const std::string& text,
|
|
||||||
Color color,
|
|
||||||
bool enabled = true,
|
|
||||||
int font_size = FONT_SIZE) const -> int;
|
int font_size = FONT_SIZE) const -> int;
|
||||||
|
|
||||||
auto draw_board_block(int x, int y, int width, int height, Color color, bool enabled = true) const -> bool;
|
auto draw_board_block(int x, int y, int width, int height, Color color,
|
||||||
|
bool enabled = true) const -> bool;
|
||||||
|
|
||||||
[[nodiscard]] auto window_open() const -> bool;
|
[[nodiscard]] auto window_open() const -> bool;
|
||||||
|
|
||||||
|
|||||||
166
include/util.hpp
166
include/util.hpp
@ -1,34 +1,67 @@
|
|||||||
#ifndef UTIL_HPP_
|
#ifndef UTIL_HPP_
|
||||||
#define UTIL_HPP_
|
#define UTIL_HPP_
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
|
|
||||||
#define INLINE __attribute__((always_inline))
|
// Bit shifting + masking
|
||||||
#define PACKED __attribute__((packed))
|
|
||||||
|
|
||||||
#define STARTTIME const auto start = std::chrono::high_resolution_clock::now()
|
template <class T>
|
||||||
#define ENDTIME(msg, cast, unit) const auto end = std::chrono::high_resolution_clock::now(); \
|
requires std::unsigned_integral<T>
|
||||||
infoln("{}. Took {}{}.", msg, std::chrono::duration_cast<cast>(end - start).count(), unit)
|
auto create_mask(const uint8_t first, const uint8_t last) -> T
|
||||||
|
{
|
||||||
|
// If the mask width is equal the type width return all 1s instead of shifting
|
||||||
|
// as shifting by type-width is undefined behavior.
|
||||||
|
if (static_cast<size_t>(last - first + 1) >= sizeof(T) * 8) {
|
||||||
|
return ~T{0};
|
||||||
|
}
|
||||||
|
|
||||||
#define COMMENT if (false)
|
// Example: first=4, last=7, 7-4+1=4
|
||||||
|
// 1 << 4 = 0b00010000
|
||||||
|
// 32 - 1 = 0b00001111
|
||||||
|
// 31 << 4 = 0b11110000
|
||||||
|
// Subtracting 1 generates a consecutive mask.
|
||||||
|
return ((T{1} << (last - first + 1)) - 1) << first;
|
||||||
|
}
|
||||||
|
|
||||||
#define NO_COPY_NO_MOVE(typename) \
|
template <class T>
|
||||||
typename(const typename& copy) = delete; \
|
requires std::unsigned_integral<T>
|
||||||
auto operator=(const typename& copy) -> typename& = delete; \
|
auto clear_bits(T& bits, const uint8_t first, const uint8_t last) -> void
|
||||||
typename(typename&& move) = delete; \
|
{
|
||||||
auto operator=(typename&& move) -> typename& = delete;
|
const T mask = create_mask<T>(first, last);
|
||||||
|
|
||||||
using u8 = uint8_t;
|
bits = bits & ~mask;
|
||||||
using u16 = uint16_t;
|
}
|
||||||
using u32 = uint32_t;
|
|
||||||
using u64 = uint64_t;
|
|
||||||
|
|
||||||
using i8 = int8_t;
|
template <class T, class U>
|
||||||
using i16 = int16_t;
|
requires std::unsigned_integral<T> && std::unsigned_integral<U>
|
||||||
using i32 = int32_t;
|
auto set_bits(T& bits, const uint8_t first, const uint8_t last, const U value) -> void
|
||||||
using i64 = int64_t;
|
{
|
||||||
|
const T mask = create_mask<T>(first, last);
|
||||||
|
|
||||||
|
// Example: first=4, last=6, value=0b1110, bits = 0b 01111110
|
||||||
|
// mask = 0b 01110000
|
||||||
|
// bits & ~mask = 0b 00001110
|
||||||
|
// value << 4 = 0b 11100000
|
||||||
|
// (value << 4) & mask = 0b 01100000
|
||||||
|
// (bits & ~mask) | (value << 4) & mask = 0b 01101110
|
||||||
|
// Insert position: ^^^
|
||||||
|
// First clear the bits, then | with the value positioned at the insertion point.
|
||||||
|
// The value may be larger than [first, last], extra bits are ignored.
|
||||||
|
bits = (bits & ~mask) | ((static_cast<T>(value) << first) & mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
requires std::unsigned_integral<T>
|
||||||
|
auto get_bits(const T bits, const uint8_t first, const uint8_t last) -> T
|
||||||
|
{
|
||||||
|
const T mask = create_mask<T>(first, last);
|
||||||
|
|
||||||
|
// We can >> without sign extension because T is unsigned_integral
|
||||||
|
return (bits & mask) >> first;
|
||||||
|
}
|
||||||
|
|
||||||
|
// std::variant visitor
|
||||||
|
|
||||||
// https://en.cppreference.com/w/cpp/utility/variant/visit
|
// https://en.cppreference.com/w/cpp/utility/variant/visit
|
||||||
template <class... Ts>
|
template <class... Ts>
|
||||||
@ -37,21 +70,9 @@ struct overloads : Ts...
|
|||||||
using Ts::operator()...;
|
using Ts::operator()...;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline auto binom(const int n, const int k) -> int
|
|
||||||
{
|
|
||||||
std::vector<int> solutions(k);
|
|
||||||
solutions[0] = n - k + 1;
|
|
||||||
|
|
||||||
for (int i = 1; i < k; ++i) {
|
|
||||||
solutions[i] = solutions[i - 1] * (n - k + 1 + i) / (i + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return solutions[k - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enums
|
// Enums
|
||||||
|
|
||||||
enum dir : u8
|
enum direction
|
||||||
{
|
{
|
||||||
nor = 1 << 0,
|
nor = 1 << 0,
|
||||||
eas = 1 << 1,
|
eas = 1 << 1,
|
||||||
@ -59,9 +80,7 @@ enum dir : u8
|
|||||||
wes = 1 << 3,
|
wes = 1 << 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ansi
|
enum ctrl
|
||||||
|
|
||||||
enum class ctrl : u8
|
|
||||||
{
|
{
|
||||||
reset = 0,
|
reset = 0,
|
||||||
bold_bright = 1,
|
bold_bright = 1,
|
||||||
@ -72,40 +91,30 @@ enum class ctrl : u8
|
|||||||
inverse_off = 27
|
inverse_off = 27
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class fg : u8
|
enum fg
|
||||||
{
|
{
|
||||||
black = 30,
|
fg_black = 30,
|
||||||
red = 31,
|
fg_red = 31,
|
||||||
green = 32,
|
fg_green = 32,
|
||||||
yellow = 33,
|
fg_yellow = 33,
|
||||||
blue = 34,
|
fg_blue = 34,
|
||||||
magenta = 35,
|
fg_magenta = 35,
|
||||||
cyan = 36,
|
fg_cyan = 36,
|
||||||
white = 37
|
fg_white = 37
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class bg : u8
|
enum bg
|
||||||
{
|
{
|
||||||
black = 40,
|
bg_black = 40,
|
||||||
red = 41,
|
bg_red = 41,
|
||||||
green = 42,
|
bg_green = 42,
|
||||||
yellow = 43,
|
bg_yellow = 43,
|
||||||
blue = 44,
|
bg_blue = 44,
|
||||||
magenta = 45,
|
bg_magenta = 45,
|
||||||
cyan = 46,
|
bg_cyan = 46,
|
||||||
white = 47
|
bg_white = 47
|
||||||
};
|
};
|
||||||
|
|
||||||
inline auto ansi_bold_fg(const fg color) -> std::string
|
|
||||||
{
|
|
||||||
return std::format("\033[{};{}m", static_cast<int>(ctrl::bold_bright), static_cast<int>(color));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline auto ansi_reset() -> std::string
|
|
||||||
{
|
|
||||||
return std::format("\033[{}m", static_cast<int>(ctrl::reset));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output
|
// Output
|
||||||
|
|
||||||
inline auto operator<<(std::ostream& os, const Vector2& v) -> std::ostream&
|
inline auto operator<<(std::ostream& os, const Vector2& v) -> std::ostream&
|
||||||
@ -120,37 +129,36 @@ inline auto operator<<(std::ostream& os, const Vector3& v) -> std::ostream&
|
|||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
// std::println doesn't work with mingw
|
inline auto ansi_bold_fg(const fg color) -> std::string
|
||||||
template <typename... Args>
|
|
||||||
auto traceln(std::format_string<Args...> fmt, Args&&... args) -> void
|
|
||||||
{
|
{
|
||||||
std::cout << std::format("[{}TRACE{}]: ", ansi_bold_fg(fg::cyan), ansi_reset()) << std::format(
|
return std::format("\033[1;{}m", static_cast<int>(color));
|
||||||
fmt,
|
|
||||||
std::forward<Args>(args)...) << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline auto ansi_reset() -> std::string
|
||||||
|
{
|
||||||
|
return "\033[0m";
|
||||||
|
}
|
||||||
|
|
||||||
|
// std::println doesn't work with mingw
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
auto infoln(std::format_string<Args...> fmt, Args&&... args) -> void
|
auto infoln(std::format_string<Args...> fmt, Args&&... args) -> void
|
||||||
{
|
{
|
||||||
std::cout << std::format("[{}INFO{}]: ", ansi_bold_fg(fg::blue), ansi_reset()) << std::format(
|
std::cout << std::format("[{}INFO{}]: ", ansi_bold_fg(fg_blue), ansi_reset()) << std::format(
|
||||||
fmt,
|
fmt, std::forward<Args>(args)...) << std::endl;
|
||||||
std::forward<Args>(args)...) << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
auto warnln(std::format_string<Args...> fmt, Args&&... args) -> void
|
auto warnln(std::format_string<Args...> fmt, Args&&... args) -> void
|
||||||
{
|
{
|
||||||
std::cout << std::format("[{}WARNING{}]: ", ansi_bold_fg(fg::yellow), ansi_reset()) << std::format(
|
std::cout << std::format("[{}WARNING{}]: ", ansi_bold_fg(fg_yellow), ansi_reset()) << std::format(
|
||||||
fmt,
|
fmt, std::forward<Args>(args)...) << std::endl;
|
||||||
std::forward<Args>(args)...) << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
auto errln(std::format_string<Args...> fmt, Args&&... args) -> void
|
auto errln(std::format_string<Args...> fmt, Args&&... args) -> void
|
||||||
{
|
{
|
||||||
std::cout << std::format("[{}ERROR{}]: ", ansi_bold_fg(fg::red), ansi_reset()) << std::format(
|
std::cout << std::format("[{}ERROR{}]: ", ansi_bold_fg(fg_red), ansi_reset()) << std::format(
|
||||||
fmt,
|
fmt, std::forward<Args>(args)...) << std::endl;
|
||||||
std::forward<Args>(args)...) << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
|
Before Width: | Height: | Size: 795 KiB After Width: | Height: | Size: 326 KiB |
@ -1,9 +0,0 @@
|
|||||||
#version 330
|
|
||||||
|
|
||||||
uniform vec4 colDiffuse;
|
|
||||||
out vec4 finalColor;
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
finalColor = colDiffuse;
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
#version 330
|
|
||||||
|
|
||||||
in vec3 vertexPosition;
|
|
||||||
uniform mat4 mvp;
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
gl_Position = mvp * vec4(vertexPosition, 1.0);
|
|
||||||
}
|
|
||||||
15
src/bits.cpp
15
src/bits.cpp
@ -1,15 +0,0 @@
|
|||||||
#include "bits.hpp"
|
|
||||||
|
|
||||||
auto print_bitmap(const u64 bitmap, const u8 w, const u8 h, const std::string& title) -> void {
|
|
||||||
traceln("{}:", title);
|
|
||||||
traceln("{}", std::string(2 * w - 1, '='));
|
|
||||||
for (size_t y = 0; y < h; ++y) {
|
|
||||||
std::cout << " ";
|
|
||||||
for (size_t x = 0; x < w; ++x) {
|
|
||||||
std::cout << static_cast<int>(get_bits(bitmap, y * w + x, y * w + x)) << " ";
|
|
||||||
}
|
|
||||||
std::cout << "\n";
|
|
||||||
}
|
|
||||||
std::cout << std::flush;
|
|
||||||
traceln("{}", std::string(2 * w - 1, '='));
|
|
||||||
}
|
|
||||||
@ -1,209 +0,0 @@
|
|||||||
#include "cpu_spring_system.hpp"
|
|
||||||
#include "config.hpp"
|
|
||||||
|
|
||||||
#include <cfloat>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
auto cpu_spring_system::clear() -> void
|
|
||||||
{
|
|
||||||
positions.clear();
|
|
||||||
previous_positions.clear();
|
|
||||||
velocities.clear();
|
|
||||||
forces.clear();
|
|
||||||
springs.clear();
|
|
||||||
tree.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cpu_spring_system::add_mass() -> void
|
|
||||||
{
|
|
||||||
// Adding all positions to (0, 0, 0) breaks the octree
|
|
||||||
|
|
||||||
// Done when adding springs
|
|
||||||
// Vector3 position{
|
|
||||||
// static_cast<float>(GetRandomValue(-100, 100)), static_cast<float>(GetRandomValue(-100,
|
|
||||||
// 100)), static_cast<float>(GetRandomValue(-100, 100))
|
|
||||||
// };
|
|
||||||
// position = Vector3Scale(Vector3Normalize(position), REST_LENGTH * 2.0);
|
|
||||||
|
|
||||||
positions.emplace_back(Vector3Zero());
|
|
||||||
previous_positions.emplace_back(Vector3Zero());
|
|
||||||
velocities.emplace_back(Vector3Zero());
|
|
||||||
forces.emplace_back(Vector3Zero());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cpu_spring_system::add_spring(size_t a, size_t b) -> void
|
|
||||||
{
|
|
||||||
// Update masses to be located along a random walk when adding the springs
|
|
||||||
const Vector3& mass_a = positions[a];
|
|
||||||
const Vector3& mass_b = positions[b];
|
|
||||||
|
|
||||||
Vector3 offset{
|
|
||||||
static_cast<float>(GetRandomValue(-100, 100)),
|
|
||||||
static_cast<float>(GetRandomValue(-100, 100)),
|
|
||||||
static_cast<float>(GetRandomValue(-100, 100))
|
|
||||||
};
|
|
||||||
|
|
||||||
// By spawning the masses close together, we "explode" them naturally, so they cluster faster (also looks cool)
|
|
||||||
offset = Vector3Normalize(offset) * REST_LENGTH * 0.1;
|
|
||||||
|
|
||||||
// If the offset moves the mass closer to the current center of mass, flip it
|
|
||||||
if (!tree.empty()) {
|
|
||||||
const Vector3 mass_center_direction = Vector3Subtract(positions[a], tree.root().mass_center);
|
|
||||||
const float mass_center_distance = Vector3Length(mass_center_direction);
|
|
||||||
|
|
||||||
if (mass_center_distance > 0 && Vector3DotProduct(offset, mass_center_direction) < 0.0f) {
|
|
||||||
offset = Vector3Negate(offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
positions[b] = mass_a + offset;
|
|
||||||
previous_positions[b] = mass_b;
|
|
||||||
|
|
||||||
// infoln("Adding spring: ({}, {}, {})->({}, {}, {})", mass_a.position.x, mass_a.position.y,
|
|
||||||
// mass_a.position.z,
|
|
||||||
// mass_b.position.x, mass_b.position.y, mass_b.position.z);
|
|
||||||
|
|
||||||
springs.emplace_back(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cpu_spring_system::clear_forces() -> void
|
|
||||||
{
|
|
||||||
#ifdef TRACY
|
|
||||||
ZoneScoped;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
memset(forces.data(), 0, forces.size() * sizeof(Vector3));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cpu_spring_system::calculate_spring_force(const size_t s) -> void
|
|
||||||
{
|
|
||||||
const spring _s = springs[s];
|
|
||||||
const Vector3 a_pos = positions[_s.first];
|
|
||||||
const Vector3 b_pos = positions[_s.second];
|
|
||||||
const Vector3 a_vel = velocities[_s.first];
|
|
||||||
const Vector3 b_vel = velocities[_s.second];
|
|
||||||
|
|
||||||
const Vector3 delta_pos = a_pos - b_pos;
|
|
||||||
const Vector3 delta_vel = a_vel - b_vel;
|
|
||||||
|
|
||||||
const float sq_len = Vector3DotProduct(delta_pos, delta_pos);
|
|
||||||
const float inv_len = 1.0f / sqrt(sq_len);
|
|
||||||
const float len = sq_len * inv_len;
|
|
||||||
|
|
||||||
const float hooke = SPRING_K * (len - REST_LENGTH);
|
|
||||||
const float dampening = DAMPENING_K * Vector3DotProduct(delta_vel, delta_pos) * inv_len;
|
|
||||||
|
|
||||||
const Vector3 a_force = Vector3Scale(delta_pos, -(hooke + dampening) * inv_len);
|
|
||||||
const Vector3 b_force = a_force * -1.0f;
|
|
||||||
|
|
||||||
forces[_s.first] += a_force;
|
|
||||||
forces[_s.second] += b_force;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cpu_spring_system::calculate_spring_forces(const threadpool thread_pool) -> void
|
|
||||||
{
|
|
||||||
#ifdef TRACY
|
|
||||||
ZoneScoped;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const auto solve_spring_force = [&](const int i)
|
|
||||||
{
|
|
||||||
calculate_spring_force(i);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (thread_pool) {
|
|
||||||
(*thread_pool)->submit_loop(0, springs.size(), solve_spring_force, SMALL_TASK_BLOCK_SIZE).wait();
|
|
||||||
} else {
|
|
||||||
for (size_t i = 0; i < springs.size(); ++i) {
|
|
||||||
solve_spring_force(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cpu_spring_system::calculate_repulsion_forces(const threadpool thread_pool) -> void
|
|
||||||
{
|
|
||||||
#ifdef TRACY
|
|
||||||
ZoneScoped;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const auto solve_octree = [&](const int i)
|
|
||||||
{
|
|
||||||
const Vector3 force = tree.calculate_force_morton(0, positions[i], i);
|
|
||||||
forces[i] += force;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate forces using Barnes-Hut
|
|
||||||
if (thread_pool) {
|
|
||||||
(*thread_pool)->submit_loop(0, positions.size(), solve_octree, LARGE_TASK_BLOCK_SIZE).wait();
|
|
||||||
} else {
|
|
||||||
for (size_t i = 0; i < positions.size(); ++i) {
|
|
||||||
solve_octree(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cpu_spring_system::integrate_velocity(const size_t m, const float dt) -> void
|
|
||||||
{
|
|
||||||
const Vector3 acc = forces[m] / MASS;
|
|
||||||
velocities[m] += acc * dt;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cpu_spring_system::integrate_position(const size_t m, const float dt) -> void
|
|
||||||
{
|
|
||||||
previous_positions[m] = positions[m];
|
|
||||||
positions[m] += velocities[m] * dt;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cpu_spring_system::verlet_update(const size_t m, const float dt) -> void
|
|
||||||
{
|
|
||||||
const Vector3 acc = (forces[m] / MASS) * dt * dt;
|
|
||||||
const Vector3 pos = positions[m];
|
|
||||||
|
|
||||||
Vector3 delta_pos = pos - previous_positions[m];
|
|
||||||
delta_pos *= 1.0 - VERLET_DAMPENING; // Minimal dampening
|
|
||||||
|
|
||||||
positions[m] += delta_pos + acc;
|
|
||||||
previous_positions[m] = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cpu_spring_system::update(const float dt, const threadpool thread_pool) -> void
|
|
||||||
{
|
|
||||||
#ifdef TRACY
|
|
||||||
ZoneScoped;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const auto update = [&](const int i)
|
|
||||||
{
|
|
||||||
verlet_update(i, dt);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (thread_pool) {
|
|
||||||
(*thread_pool)->submit_loop(0, positions.size(), update, SMALL_TASK_BLOCK_SIZE).wait();
|
|
||||||
} else {
|
|
||||||
for (size_t i = 0; i < positions.size(); ++i) {
|
|
||||||
update(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cpu_spring_system::center_masses(const threadpool thread_pool) -> void
|
|
||||||
{
|
|
||||||
Vector3 mean = Vector3Zero();
|
|
||||||
for (const Vector3& pos : positions) {
|
|
||||||
mean += pos;
|
|
||||||
}
|
|
||||||
mean /= static_cast<float>(positions.size());
|
|
||||||
|
|
||||||
const auto center_mass = [&](const int i)
|
|
||||||
{
|
|
||||||
positions[i] -= mean;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (thread_pool) {
|
|
||||||
(*thread_pool)->submit_loop(0, positions.size(), center_mass, SMALL_TASK_BLOCK_SIZE).wait();
|
|
||||||
} else {
|
|
||||||
for (size_t i = 0; i < positions.size(); ++i) {
|
|
||||||
center_mass(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
|
#ifdef TRACY
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
auto graph_distances::clear() -> void
|
auto graph_distances::clear() -> void
|
||||||
{
|
{
|
||||||
distances.clear();
|
distances.clear();
|
||||||
@ -15,7 +19,7 @@ auto graph_distances::empty() const -> bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto graph_distances::calculate_distances(const size_t node_count,
|
auto graph_distances::calculate_distances(const size_t node_count,
|
||||||
const std::vector<spring>& edges,
|
const std::vector<std::pair<size_t, size_t>>& edges,
|
||||||
const std::vector<size_t>& targets) -> void
|
const std::vector<size_t>& targets) -> void
|
||||||
{
|
{
|
||||||
// Build a list of adjacent nodes to speed up BFS
|
// Build a list of adjacent nodes to speed up BFS
|
||||||
|
|||||||
@ -18,7 +18,6 @@ auto input_handler::init_handlers() -> void
|
|||||||
register_mouse_pressed_handler(MOUSE_BUTTON_LEFT, &input_handler::add_block);
|
register_mouse_pressed_handler(MOUSE_BUTTON_LEFT, &input_handler::add_block);
|
||||||
register_mouse_pressed_handler(MOUSE_BUTTON_LEFT, &input_handler::start_add_block);
|
register_mouse_pressed_handler(MOUSE_BUTTON_LEFT, &input_handler::start_add_block);
|
||||||
register_mouse_pressed_handler(MOUSE_BUTTON_MIDDLE, &input_handler::place_goal);
|
register_mouse_pressed_handler(MOUSE_BUTTON_MIDDLE, &input_handler::place_goal);
|
||||||
register_mouse_pressed_handler(MOUSE_BUTTON_MIDDLE, &input_handler::select_state);
|
|
||||||
register_mouse_pressed_handler(MOUSE_BUTTON_RIGHT, &input_handler::camera_start_rotate);
|
register_mouse_pressed_handler(MOUSE_BUTTON_RIGHT, &input_handler::camera_start_rotate);
|
||||||
register_mouse_pressed_handler(MOUSE_BUTTON_RIGHT, &input_handler::remove_block);
|
register_mouse_pressed_handler(MOUSE_BUTTON_RIGHT, &input_handler::remove_block);
|
||||||
register_mouse_pressed_handler(MOUSE_BUTTON_RIGHT, &input_handler::clear_add_block);
|
register_mouse_pressed_handler(MOUSE_BUTTON_RIGHT, &input_handler::clear_add_block);
|
||||||
@ -43,7 +42,6 @@ auto input_handler::init_handlers() -> void
|
|||||||
register_key_pressed_handler(KEY_C, &input_handler::clear_graph);
|
register_key_pressed_handler(KEY_C, &input_handler::clear_graph);
|
||||||
register_key_pressed_handler(KEY_I, &input_handler::toggle_mark_solutions);
|
register_key_pressed_handler(KEY_I, &input_handler::toggle_mark_solutions);
|
||||||
register_key_pressed_handler(KEY_O, &input_handler::toggle_connect_solutions);
|
register_key_pressed_handler(KEY_O, &input_handler::toggle_connect_solutions);
|
||||||
register_key_pressed_handler(KEY_Z, &input_handler::toggle_color_by_distance);
|
|
||||||
|
|
||||||
register_key_pressed_handler(KEY_TAB, &input_handler::toggle_editing);
|
register_key_pressed_handler(KEY_TAB, &input_handler::toggle_editing);
|
||||||
register_key_pressed_handler(KEY_F, &input_handler::toggle_restricted_movement);
|
register_key_pressed_handler(KEY_F, &input_handler::toggle_restricted_movement);
|
||||||
@ -144,17 +142,12 @@ auto input_handler::camera_zoom() const -> void
|
|||||||
|
|
||||||
auto input_handler::camera_fov() const -> void
|
auto input_handler::camera_fov() const -> void
|
||||||
{
|
{
|
||||||
if (!mouse_in_graph_pane() || !IsKeyDown(KEY_LEFT_CONTROL)) {
|
if (!mouse_in_graph_pane() || !IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_LEFT_SHIFT)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float wheel = GetMouseWheelMove();
|
const float wheel = GetMouseWheelMove();
|
||||||
|
camera.fov -= wheel * FOV_SPEED;
|
||||||
if (IsKeyDown(KEY_LEFT_SHIFT)) {
|
|
||||||
camera.fov -= wheel * FOV_SPEED * FOV_MULTIPLIER;
|
|
||||||
} else {
|
|
||||||
camera.fov -= wheel * FOV_SPEED;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto input_handler::select_block() -> void
|
auto input_handler::select_block() -> void
|
||||||
@ -208,7 +201,7 @@ auto input_handler::add_block() -> void
|
|||||||
has_block_add_xy = false;
|
has_block_add_xy = false;
|
||||||
} else if (current.covers(block_add_x, block_add_y, block_add_width, block_add_height)) {
|
} else if (current.covers(block_add_x, block_add_y, block_add_width, block_add_height)) {
|
||||||
const std::optional<puzzle>& next = current.try_add_block(
|
const std::optional<puzzle>& next = current.try_add_block(
|
||||||
block(block_add_x, block_add_y, block_add_width, block_add_height, false));
|
puzzle::block(block_add_x, block_add_y, block_add_width, block_add_height, false));
|
||||||
|
|
||||||
if (next) {
|
if (next) {
|
||||||
sel_x = block_add_x;
|
sel_x = block_add_x;
|
||||||
@ -224,7 +217,7 @@ auto input_handler::add_block() -> void
|
|||||||
auto input_handler::remove_block() -> void
|
auto input_handler::remove_block() -> void
|
||||||
{
|
{
|
||||||
const puzzle& current = state.get_current_state();
|
const puzzle& current = state.get_current_state();
|
||||||
const std::optional<block>& b = current.try_get_block(hov_x, hov_y);
|
const std::optional<puzzle::block>& b = current.try_get_block(hov_x, hov_y);
|
||||||
if (!editing || has_block_add_xy || !b) {
|
if (!editing || has_block_add_xy || !b) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -246,7 +239,7 @@ auto input_handler::remove_block() -> void
|
|||||||
auto input_handler::place_goal() const -> void
|
auto input_handler::place_goal() const -> void
|
||||||
{
|
{
|
||||||
const puzzle& current = state.get_current_state();
|
const puzzle& current = state.get_current_state();
|
||||||
if (!editing || !mouse_in_board_pane() || !current.covers(hov_x, hov_y)) {
|
if (!editing || !current.covers(hov_x, hov_y)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,16 +251,6 @@ auto input_handler::place_goal() const -> void
|
|||||||
state.edit_starting_state(*next);
|
state.edit_starting_state(*next);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto input_handler::select_state() const -> void
|
|
||||||
{
|
|
||||||
if (!mouse_in_graph_pane() || collision_mass == static_cast<size_t>(-1)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const puzzle& selected = state.get_state(collision_mass);
|
|
||||||
state.update_current_state(selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto input_handler::toggle_camera_lock() -> void
|
auto input_handler::toggle_camera_lock() -> void
|
||||||
{
|
{
|
||||||
if (!camera_lock) {
|
if (!camera_lock) {
|
||||||
@ -295,7 +278,7 @@ auto input_handler::toggle_camera_projection() const -> void
|
|||||||
auto input_handler::move_block_nor() -> void
|
auto input_handler::move_block_nor() -> void
|
||||||
{
|
{
|
||||||
const puzzle& current = state.get_current_state();
|
const puzzle& current = state.get_current_state();
|
||||||
const std::optional<puzzle>& next = current.try_move_block_at(sel_x, sel_y, nor);
|
const std::optional<puzzle>& next = current.try_move_block_at_fast(sel_x, sel_y, nor);
|
||||||
if (!next) {
|
if (!next) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -307,7 +290,7 @@ auto input_handler::move_block_nor() -> void
|
|||||||
auto input_handler::move_block_wes() -> void
|
auto input_handler::move_block_wes() -> void
|
||||||
{
|
{
|
||||||
const puzzle& current = state.get_current_state();
|
const puzzle& current = state.get_current_state();
|
||||||
const std::optional<puzzle>& next = current.try_move_block_at(sel_x, sel_y, wes);
|
const std::optional<puzzle>& next = current.try_move_block_at_fast(sel_x, sel_y, wes);
|
||||||
if (!next) {
|
if (!next) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -319,7 +302,7 @@ auto input_handler::move_block_wes() -> void
|
|||||||
auto input_handler::move_block_sou() -> void
|
auto input_handler::move_block_sou() -> void
|
||||||
{
|
{
|
||||||
const puzzle& current = state.get_current_state();
|
const puzzle& current = state.get_current_state();
|
||||||
const std::optional<puzzle>& next = current.try_move_block_at(sel_x, sel_y, sou);
|
const std::optional<puzzle>& next = current.try_move_block_at_fast(sel_x, sel_y, sou);
|
||||||
if (!next) {
|
if (!next) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -331,7 +314,7 @@ auto input_handler::move_block_sou() -> void
|
|||||||
auto input_handler::move_block_eas() -> void
|
auto input_handler::move_block_eas() -> void
|
||||||
{
|
{
|
||||||
const puzzle& current = state.get_current_state();
|
const puzzle& current = state.get_current_state();
|
||||||
const std::optional<puzzle>& next = current.try_move_block_at(sel_x, sel_y, eas);
|
const std::optional<puzzle>& next = current.try_move_block_at_fast(sel_x, sel_y, eas);
|
||||||
if (!next) {
|
if (!next) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -424,11 +407,6 @@ auto input_handler::toggle_connect_solutions() -> void
|
|||||||
connect_solutions = !connect_solutions;
|
connect_solutions = !connect_solutions;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto input_handler::toggle_color_by_distance() -> void
|
|
||||||
{
|
|
||||||
color_by_distance = !color_by_distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto input_handler::toggle_mark_path() -> void
|
auto input_handler::toggle_mark_path() -> void
|
||||||
{
|
{
|
||||||
mark_path = !mark_path;
|
mark_path = !mark_path;
|
||||||
|
|||||||
@ -1,82 +0,0 @@
|
|||||||
#include "load_save.hpp"
|
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
auto parse_preset_file(const std::string& preset_file) -> std::pair<std::vector<puzzle>, std::vector<std::string>>
|
|
||||||
{
|
|
||||||
std::fstream file(preset_file, std::ios::in);
|
|
||||||
if (!file) {
|
|
||||||
infoln("Preset file \"{}\" couldn't be opened.", preset_file);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string line;
|
|
||||||
std::vector<std::string> comment_lines;
|
|
||||||
std::vector<std::string> preset_lines;
|
|
||||||
while (std::getline(file, line)) {
|
|
||||||
if (line.starts_with("S")) {
|
|
||||||
preset_lines.push_back(line);
|
|
||||||
} else if (line.starts_with("#")) {
|
|
||||||
comment_lines.push_back(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preset_lines.empty() || comment_lines.size() != preset_lines.size()) {
|
|
||||||
infoln("Preset file \"{}\" couldn't be opened.", preset_file);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<puzzle> preset_states;
|
|
||||||
for (const auto& preset : preset_lines) {
|
|
||||||
// Each char is a bit
|
|
||||||
const puzzle& p = puzzle(preset);
|
|
||||||
|
|
||||||
if (const std::optional<std::string>& reason = p.try_get_invalid_reason()) {
|
|
||||||
infoln("Preset file \"{}\" contained invalid presets: {}", preset_file, *reason);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
preset_states.emplace_back(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
infoln("Loaded {} presets from \"{}\".", preset_lines.size(), preset_file);
|
|
||||||
|
|
||||||
return {preset_states, comment_lines};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto append_preset_file(const std::string& preset_file, const std::string& preset_name, const puzzle& p) -> bool
|
|
||||||
{
|
|
||||||
infoln(R"(Saving preset "{}" to "{}")", preset_name, preset_file);
|
|
||||||
|
|
||||||
if (p.try_get_invalid_reason()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::fstream file(preset_file, std::ios_base::app | std::ios_base::out);
|
|
||||||
if (!file) {
|
|
||||||
infoln("Preset file \"{}\" couldn't be opened.", preset_file);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
file << "\n# " << preset_name << "\n" << p.string_repr() << std::flush;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto append_preset_file_quiet(const std::string& preset_file,
|
|
||||||
const std::string& preset_name,
|
|
||||||
const puzzle& p,
|
|
||||||
const bool validate) -> bool
|
|
||||||
{
|
|
||||||
if (validate && p.try_get_invalid_reason()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::fstream file(preset_file, std::ios_base::app | std::ios_base::out);
|
|
||||||
if (!file) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
file << "\n# " << preset_name << "\n" << p.string_repr() << std::flush;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
364
src/main.cpp
364
src/main.cpp
@ -1,64 +1,78 @@
|
|||||||
|
#include <chrono>
|
||||||
|
#include <mutex>
|
||||||
|
#include <raylib.h>
|
||||||
|
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include "input_handler.hpp"
|
#include "input_handler.hpp"
|
||||||
#include "cpu_layout_engine.hpp"
|
#include "mass_spring_system.hpp"
|
||||||
|
#include "threaded_physics.hpp"
|
||||||
#include "renderer.hpp"
|
#include "renderer.hpp"
|
||||||
#include "state_manager.hpp"
|
#include "state_manager.hpp"
|
||||||
#include "user_interface.hpp"
|
#include "user_interface.hpp"
|
||||||
|
|
||||||
#include <chrono>
|
#ifdef TRACY
|
||||||
#include <mutex>
|
#include <tracy/Tracy.hpp>
|
||||||
#include <GL/glew.h>
|
|
||||||
#include <raylib.h>
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
#if not defined(_WIN32)
|
|
||||||
#include <boost/program_options.hpp>
|
|
||||||
namespace po = boost::program_options;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Threadpool setup
|
// TODO: Add some popups (my split between input.cpp/gui.cpp makes this ugly)
|
||||||
#ifdef THREADPOOL
|
// - Clear graph: Notify that this will clear the visited states and move
|
||||||
auto set_pool_thread_name(size_t idx) -> void
|
// history
|
||||||
|
// - Reset state: Notify that this will reset the move count
|
||||||
|
|
||||||
|
// TODO: Reduce memory usage
|
||||||
|
// - The memory model of the puzzle board is terrible (bitboards?)
|
||||||
|
|
||||||
|
// TODO: Improve solver
|
||||||
|
// - Move discovery is terrible
|
||||||
|
// - Instead of trying each direction for each block, determine the
|
||||||
|
// possible moves more efficiently (requires a different memory model)
|
||||||
|
// - Implement state discovery/enumeration
|
||||||
|
// - Find all possible initial board states (single one for each
|
||||||
|
// possible statespace). Currently wer're just finding all states
|
||||||
|
// given the initial state
|
||||||
|
// - Would allow to generate random puzzles with a certain move count
|
||||||
|
|
||||||
|
// TODO: Move selection accordingly when undoing moves (need to diff two states
|
||||||
|
// and get the moved blocks)
|
||||||
|
|
||||||
|
// TODO: Click states in the graph to display them in the board
|
||||||
|
|
||||||
|
// NOTE: Tracy uses a huge amount of memory. For longer testing disable Tracy.
|
||||||
|
|
||||||
|
// For profiling explore_state_space
|
||||||
|
auto main2(int argc, char* argv[]) -> int
|
||||||
{
|
{
|
||||||
BS::this_thread::set_os_thread_name(std::format("worker-{}", idx));
|
const puzzle p = puzzle(
|
||||||
|
"S:[4x5] G:[1,3] M:[F] B:[{_ 2X2 _ _} {1x1 _ _ 1x1} {1x2 2x1 _ 1x2} {_ 2x1 _ _} {1x1 2x1 _ 1x1}]");
|
||||||
|
|
||||||
|
for (int i = 0; i < 50; ++i) {
|
||||||
|
auto space = p.explore_state_space();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
BS::thread_pool<> threads(std::thread::hardware_concurrency() - 2, set_pool_thread_name);
|
auto main(int argc, char* argv[]) -> int
|
||||||
constexpr threadpool thread_pool = &threads;
|
|
||||||
#else
|
|
||||||
constexpr threadpool thread_pool = std::nullopt;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Argparse defaults
|
|
||||||
std::string preset_file = "default.puzzle";
|
|
||||||
std::string output_file = "clusters.puzzle";
|
|
||||||
int max_blocks = 5;
|
|
||||||
int min_moves = 10;
|
|
||||||
|
|
||||||
// Puzzle space setup
|
|
||||||
int board_width;
|
|
||||||
int board_height;
|
|
||||||
int goal_x;
|
|
||||||
int goal_y;
|
|
||||||
bool restricted;
|
|
||||||
blockset2 permitted_blocks;
|
|
||||||
block target_block;
|
|
||||||
std::tuple<u8, u8, u8, u8> target_block_pos_range;
|
|
||||||
|
|
||||||
// TODO: Export cluster to graphviz
|
|
||||||
// TODO: Fix naming:
|
|
||||||
// - Target: The block that has to leave the board to win
|
|
||||||
// - Goal: The opening in the board for the target
|
|
||||||
// - Puzzle (not board or state): A puzzle configuration (width, height, goal_x, goal_y, restricted, goal)
|
|
||||||
// - Block: A puzzle block (x, y, width, height, target, immovable)
|
|
||||||
// - Puzzle State: A specific puzzle state (width, height, goal_x, goal_y, restricted, goal, blocks)
|
|
||||||
// - Cluster: A graph of puzzle states connected by moves, generated from a specific Puzzle State
|
|
||||||
// - Puzzle Space: A number of Clusters generated from a generic Puzzle
|
|
||||||
// TODO: Add state space generation time to debug overlay
|
|
||||||
// TODO: Move selection accordingly when undoing moves (need to diff two states and get the moved blocks)
|
|
||||||
|
|
||||||
auto ui_mode() -> int
|
|
||||||
{
|
{
|
||||||
|
std::string preset_file;
|
||||||
|
if (argc != 2) {
|
||||||
|
preset_file = "default.puzzle";
|
||||||
|
} else {
|
||||||
|
preset_file = argv[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef BACKWARD
|
||||||
|
infoln("Backward stack-traces enabled.");
|
||||||
|
#else
|
||||||
|
infoln("Backward stack-traces disabled.");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef TRACY
|
||||||
|
infoln("Tracy adapter enabled.");
|
||||||
|
#else
|
||||||
|
infoln("Tracy adapter disabled.");
|
||||||
|
#endif
|
||||||
|
|
||||||
// RayLib window setup
|
// RayLib window setup
|
||||||
SetTraceLogLevel(LOG_ERROR);
|
SetTraceLogLevel(LOG_ERROR);
|
||||||
SetConfigFlags(FLAG_VSYNC_HINT);
|
SetConfigFlags(FLAG_VSYNC_HINT);
|
||||||
@ -67,15 +81,8 @@ auto ui_mode() -> int
|
|||||||
SetConfigFlags(FLAG_WINDOW_ALWAYS_RUN);
|
SetConfigFlags(FLAG_WINDOW_ALWAYS_RUN);
|
||||||
InitWindow(INITIAL_WIDTH * 2, INITIAL_HEIGHT + MENU_HEIGHT, "MassSprings");
|
InitWindow(INITIAL_WIDTH * 2, INITIAL_HEIGHT + MENU_HEIGHT, "MassSprings");
|
||||||
|
|
||||||
// GLEW setup
|
|
||||||
glewExperimental = GL_TRUE;
|
|
||||||
const GLenum glew_err = glewInit();
|
|
||||||
if (glew_err != GLEW_OK) {
|
|
||||||
TraceLog(LOG_FATAL, "Failed to initialize GLEW: %s", glewGetErrorString(glew_err));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Game setup
|
// Game setup
|
||||||
cpu_layout_engine physics(thread_pool);
|
threaded_physics physics;
|
||||||
state_manager state(physics, preset_file);
|
state_manager state(physics, preset_file);
|
||||||
orbit_camera camera;
|
orbit_camera camera;
|
||||||
input_handler input(state, camera);
|
input_handler input(state, camera);
|
||||||
@ -143,8 +150,8 @@ auto ui_mode() -> int
|
|||||||
// Update the camera after the physics, so target lock is smooth
|
// Update the camera after the physics, so target lock is smooth
|
||||||
size_t current_index = state.get_current_index();
|
size_t current_index = state.get_current_index();
|
||||||
if (masses.size() > current_index) {
|
if (masses.size() > current_index) {
|
||||||
const Vector3& current_mass = masses[current_index];
|
const mass_spring_system::mass& current_mass = mass_spring_system::mass(masses.at(current_index));
|
||||||
camera.update(current_mass, mass_center, input.camera_lock, input.camera_mass_center_lock);
|
camera.update(current_mass.position, mass_center, input.camera_lock, input.camera_mass_center_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
@ -159,248 +166,11 @@ auto ui_mode() -> int
|
|||||||
++loop_iterations;
|
++loop_iterations;
|
||||||
|
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
FrameMark;
|
FrameMark; FrameMarkEnd("MainThread");
|
||||||
FrameMarkEnd("MainThread");
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseWindow();
|
CloseWindow();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
|
||||||
|
|
||||||
auto rush_hour_puzzle_space() -> void
|
|
||||||
{
|
|
||||||
board_width = 6;
|
|
||||||
board_height = 6;
|
|
||||||
goal_x = 4;
|
|
||||||
goal_y = 2;
|
|
||||||
restricted = true;
|
|
||||||
permitted_blocks = {
|
|
||||||
block(0, 0, 2, 1, false, false),
|
|
||||||
block(0, 0, 3, 1, false, false),
|
|
||||||
block(0, 0, 1, 2, false, false),
|
|
||||||
block(0, 0, 1, 3, false, false)
|
|
||||||
};
|
|
||||||
target_block = block(0, 0, 2, 1, true, false);
|
|
||||||
target_block_pos_range = {0, goal_y, board_width - target_block.get_width(), goal_y};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto klotski_puzzle_space() -> void
|
|
||||||
{
|
|
||||||
board_width = 4;
|
|
||||||
board_height = 5;
|
|
||||||
goal_x = 1;
|
|
||||||
goal_y = 3;
|
|
||||||
restricted = false;
|
|
||||||
permitted_blocks = {
|
|
||||||
block(0, 0, 1, 1, false, false),
|
|
||||||
block(0, 0, 1, 2, false, false),
|
|
||||||
block(0, 0, 2, 1, false, false),
|
|
||||||
};
|
|
||||||
target_block = block(0, 0, 2, 2, true, false);
|
|
||||||
target_block_pos_range = {
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
board_width - target_block.get_width(),
|
|
||||||
board_height - target_block.get_height(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto puzzle_space() -> int
|
|
||||||
{
|
|
||||||
// We don't only pick max_blocks out of n (with duplicates), but also 1 out of n, 2, 3, ... max_blocks-1 out of n
|
|
||||||
int upper_set_count = 0;
|
|
||||||
for (int i = 1; i <= max_blocks; ++i) {
|
|
||||||
upper_set_count += binom(permitted_blocks.size() + i - 1, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
infoln("Exploring puzzle space:");
|
|
||||||
infoln("- Size: {}x{}", board_width, board_height);
|
|
||||||
infoln("- Goal: {},{}", goal_x, goal_y);
|
|
||||||
infoln("- Restricted: {}", restricted);
|
|
||||||
infoln("- Max Blocks: {}", max_blocks);
|
|
||||||
infoln("- Min Moves: {}", min_moves);
|
|
||||||
infoln("- Target: {}x{}", target_block.get_width(), target_block.get_height());
|
|
||||||
infoln("- Max Sets: {}", upper_set_count);
|
|
||||||
infoln("- Permitted block sizes:");
|
|
||||||
std::cout << " ";
|
|
||||||
for (const block b : permitted_blocks) {
|
|
||||||
std::cout << std::format(" {}x{},", b.get_width(), b.get_height());
|
|
||||||
}
|
|
||||||
std::cout << std::endl;
|
|
||||||
|
|
||||||
const puzzle p = puzzle(board_width, board_height, goal_x, goal_y, restricted, true);
|
|
||||||
|
|
||||||
STARTTIME;
|
|
||||||
const puzzleset result = p.explore_puzzle_space(
|
|
||||||
permitted_blocks,
|
|
||||||
target_block,
|
|
||||||
target_block_pos_range,
|
|
||||||
max_blocks,
|
|
||||||
min_moves,
|
|
||||||
thread_pool);
|
|
||||||
ENDTIME(std::format("Found {} different clusters", result.size()), std::chrono::seconds, "s");
|
|
||||||
|
|
||||||
// TODO: The exported clusters are the numerically smallest state of the cluster.
|
|
||||||
// Not the state with the longest path.
|
|
||||||
|
|
||||||
infoln("Sorting clusters...");
|
|
||||||
std::vector<puzzle> result_sorted{result.begin(), result.end()};
|
|
||||||
std::ranges::sort(result_sorted, std::ranges::greater{});
|
|
||||||
|
|
||||||
infoln("Saving clusters...");
|
|
||||||
size_t i = 0;
|
|
||||||
size_t success = 0;
|
|
||||||
std::filesystem::remove(output_file);
|
|
||||||
for (const puzzle& _p : result_sorted) {
|
|
||||||
if (append_preset_file_quiet(output_file, std::format("Cluster {}", i), _p, true)) {
|
|
||||||
++success;
|
|
||||||
}
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
if (success != result_sorted.size()) {
|
|
||||||
warnln("Saved {} of {} clusters", success, result_sorted.size());
|
|
||||||
} else {
|
|
||||||
infoln("Saved {} of {} clusters", success, result_sorted.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class runmode
|
|
||||||
{
|
|
||||||
USER_INTERFACE,
|
|
||||||
RUSH_HOUR_PUZZLE_SPACE,
|
|
||||||
KLOTSKI_PUZZLE_SPACE,
|
|
||||||
EXIT,
|
|
||||||
};
|
|
||||||
|
|
||||||
auto argparse(const int argc, char* argv[]) -> runmode
|
|
||||||
{
|
|
||||||
#if not defined(_WIN32)
|
|
||||||
po::options_description desc("Allowed options");
|
|
||||||
desc.add_options() //
|
|
||||||
("help", "produce help message") //
|
|
||||||
("presets", po::value<std::string>()->default_value(preset_file), "load presets from file") //
|
|
||||||
("output", po::value<std::string>()->default_value(output_file), "output file for generated clusters") //
|
|
||||||
("space", po::value<std::string>()->value_name("rh|klotski"), "generate puzzle space with ruleset") //
|
|
||||||
// ("w", po::value<int>()->default_value(board_width)->value_name("[3, 8]"), "board width") //
|
|
||||||
// ("h", po::value<int>()->default_value(board_height)->value_name("[3, 8"), "board height") //
|
|
||||||
// ("gx", po::value<int>()->default_value(goal_x)->value_name("[0, w-1]"), "board goal horizontal position") //
|
|
||||||
// ("gy", po::value<int>()->default_value(goal_y)->value_name("[0, h-1]"), "board goal vertical position") //
|
|
||||||
// ("free", "allow free block movement") //
|
|
||||||
("blocks",
|
|
||||||
po::value<int>()->default_value(max_blocks)->value_name("[1, 15]"),
|
|
||||||
"block limit for puzzle space generation") //
|
|
||||||
("moves",
|
|
||||||
po::value<int>()->default_value(min_moves),
|
|
||||||
"only save puzzles with at least this many required moves") //
|
|
||||||
;
|
|
||||||
|
|
||||||
po::positional_options_description positional;
|
|
||||||
positional.add("presets", -1);
|
|
||||||
|
|
||||||
po::variables_map vm;
|
|
||||||
po::store(po::command_line_parser(argc, argv).options(desc).positional(positional).run(), vm);
|
|
||||||
po::notify(vm);
|
|
||||||
|
|
||||||
if (vm.contains("help")) {
|
|
||||||
std::cout << desc << std::endl;
|
|
||||||
return runmode::EXIT;
|
|
||||||
}
|
|
||||||
if (vm.contains("output")) {
|
|
||||||
output_file = vm["output"].as<std::string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (vm.contains("w")) {
|
|
||||||
// board_width = vm["w"].as<int>();
|
|
||||||
// board_width = std::max(static_cast<int>(puzzle::MIN_WIDTH),
|
|
||||||
// std::min(board_width, static_cast<int>(puzzle::MAX_WIDTH)));
|
|
||||||
// }
|
|
||||||
// if (vm.contains("h")) {
|
|
||||||
// board_height = vm["h"].as<int>();
|
|
||||||
// board_height = std::max(static_cast<int>(puzzle::MIN_HEIGHT),
|
|
||||||
// std::min(board_height, static_cast<int>(puzzle::MAX_HEIGHT)));
|
|
||||||
// }
|
|
||||||
// if (vm.contains("gx")) {
|
|
||||||
// goal_x = vm["gx"].as<int>();
|
|
||||||
// goal_x = std::max(0, std::min(goal_x, static_cast<int>(puzzle::MAX_WIDTH) - 1));
|
|
||||||
// }
|
|
||||||
// if (vm.contains("gy")) {
|
|
||||||
// goal_y = vm["gy"].as<int>();
|
|
||||||
// goal_y = std::max(0, std::min(goal_y, static_cast<int>(puzzle::MAX_HEIGHT) - 1));
|
|
||||||
// }
|
|
||||||
// if (vm.contains("free")) {
|
|
||||||
// restricted = false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (vm.contains("blocks")) {
|
|
||||||
max_blocks = vm["blocks"].as<int>();
|
|
||||||
max_blocks = std::max(1, std::min(max_blocks, static_cast<int>(puzzle::MAX_BLOCKS)));
|
|
||||||
}
|
|
||||||
if (vm.contains("moves")) {
|
|
||||||
min_moves = vm["moves"].as<int>();
|
|
||||||
min_moves = std::max(0, min_moves);
|
|
||||||
}
|
|
||||||
if (vm.contains("space")) {
|
|
||||||
const std::string ruleset = vm["space"].as<std::string>();
|
|
||||||
if (ruleset == "rh") {
|
|
||||||
return runmode::RUSH_HOUR_PUZZLE_SPACE;
|
|
||||||
}
|
|
||||||
if (ruleset == "klotski") {
|
|
||||||
return runmode::KLOTSKI_PUZZLE_SPACE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (vm.contains("presets")) {
|
|
||||||
preset_file = vm["presets"].as<std::string>();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return runmode::USER_INTERFACE;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto main(const int argc, char* argv[]) -> int
|
|
||||||
{
|
|
||||||
#ifdef BACKWARD
|
|
||||||
infoln("Backward stack-traces enabled.");
|
|
||||||
#else
|
|
||||||
infoln("Backward stack-traces disabled.");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef TRACY
|
|
||||||
infoln("Tracy adapter enabled.");
|
|
||||||
#else
|
|
||||||
infoln("Tracy adapter disabled.");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
infoln("Using background thread for physics.");
|
|
||||||
infoln("Using linear octree + Barnes-Hut for graph layout.");
|
|
||||||
|
|
||||||
#ifdef ASYNC_OCTREE
|
|
||||||
infoln("Using asynchronous octree build.");
|
|
||||||
#else
|
|
||||||
infoln("Using synchronous octree build.");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef THREADPOOL
|
|
||||||
infoln("Additional thread-pool enabled ({} threads).", threads.get_thread_count());
|
|
||||||
#else
|
|
||||||
infoln("Additional thread-pool disabled.");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
switch (argparse(argc, argv)) {
|
|
||||||
case runmode::USER_INTERFACE:
|
|
||||||
return ui_mode();
|
|
||||||
case runmode::RUSH_HOUR_PUZZLE_SPACE:
|
|
||||||
rush_hour_puzzle_space();
|
|
||||||
return puzzle_space();
|
|
||||||
case runmode::KLOTSKI_PUZZLE_SPACE:
|
|
||||||
klotski_puzzle_space();
|
|
||||||
return puzzle_space();
|
|
||||||
case runmode::EXIT:
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
231
src/mass_spring_system.cpp
Normal file
231
src/mass_spring_system.cpp
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
#include "mass_spring_system.hpp"
|
||||||
|
#include "config.hpp"
|
||||||
|
|
||||||
|
#include <cfloat>
|
||||||
|
|
||||||
|
#ifdef TRACY
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto mass_spring_system::mass::clear_force() -> void
|
||||||
|
{
|
||||||
|
force = Vector3Zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mass_spring_system::mass::calculate_velocity(const float delta_time) -> void
|
||||||
|
{
|
||||||
|
const Vector3 acceleration = Vector3Scale(force, 1.0 / MASS);
|
||||||
|
const Vector3 temp = Vector3Scale(acceleration, delta_time);
|
||||||
|
velocity = Vector3Add(velocity, temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mass_spring_system::mass::calculate_position(const float delta_time) -> void
|
||||||
|
{
|
||||||
|
previous_position = position;
|
||||||
|
|
||||||
|
const Vector3 temp = Vector3Scale(velocity, delta_time);
|
||||||
|
position = Vector3Add(position, temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mass_spring_system::mass::verlet_update(const float delta_time) -> void
|
||||||
|
{
|
||||||
|
const Vector3 acceleration = Vector3Scale(force, 1.0 / MASS);
|
||||||
|
const Vector3 temp_position = position;
|
||||||
|
|
||||||
|
Vector3 displacement = Vector3Subtract(position, previous_position);
|
||||||
|
const Vector3 accel_term = Vector3Scale(acceleration, delta_time * delta_time);
|
||||||
|
|
||||||
|
// Minimal dampening
|
||||||
|
displacement = Vector3Scale(displacement, 1.0 - VERLET_DAMPENING);
|
||||||
|
|
||||||
|
position = Vector3Add(Vector3Add(position, displacement), accel_term);
|
||||||
|
previous_position = temp_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mass_spring_system::spring::calculate_spring_force(mass& _a, mass& _b) -> void
|
||||||
|
{
|
||||||
|
// TODO: Use a bungee force here instead of springs, since we already have global repulsion?
|
||||||
|
const Vector3 delta_position = Vector3Subtract(_a.position, _b.position);
|
||||||
|
const float current_length = Vector3Length(delta_position);
|
||||||
|
const Vector3 delta_velocity = Vector3Subtract(_a.velocity, _b.velocity);
|
||||||
|
|
||||||
|
const float hooke = SPRING_CONSTANT * (current_length - REST_LENGTH);
|
||||||
|
const float dampening = DAMPENING_CONSTANT * Vector3DotProduct(delta_velocity, delta_position) / current_length;
|
||||||
|
|
||||||
|
const Vector3 force_a = Vector3Scale(delta_position, -(hooke + dampening) / current_length);
|
||||||
|
const Vector3 force_b = Vector3Scale(force_a, -1.0);
|
||||||
|
|
||||||
|
_a.force = Vector3Add(_a.force, force_a);
|
||||||
|
_b.force = Vector3Add(_b.force, force_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mass_spring_system::clear() -> void
|
||||||
|
{
|
||||||
|
masses.clear();
|
||||||
|
springs.clear();
|
||||||
|
tree.nodes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mass_spring_system::add_mass() -> void
|
||||||
|
{
|
||||||
|
// Adding all positions to (0, 0, 0) breaks the octree
|
||||||
|
|
||||||
|
// Done when adding springs
|
||||||
|
// Vector3 position{
|
||||||
|
// static_cast<float>(GetRandomValue(-100, 100)), static_cast<float>(GetRandomValue(-100,
|
||||||
|
// 100)), static_cast<float>(GetRandomValue(-100, 100))
|
||||||
|
// };
|
||||||
|
// position = Vector3Scale(Vector3Normalize(position), REST_LENGTH * 2.0);
|
||||||
|
|
||||||
|
masses.emplace_back(Vector3Zero());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mass_spring_system::add_spring(size_t a, size_t b) -> void
|
||||||
|
{
|
||||||
|
// Update masses to be located along a random walk when adding the springs
|
||||||
|
const mass& mass_a = masses.at(a);
|
||||||
|
mass& mass_b = masses.at(b);
|
||||||
|
|
||||||
|
Vector3 offset{
|
||||||
|
static_cast<float>(GetRandomValue(-100, 100)), static_cast<float>(GetRandomValue(-100, 100)),
|
||||||
|
static_cast<float>(GetRandomValue(-100, 100))
|
||||||
|
};
|
||||||
|
offset = Vector3Normalize(offset) * REST_LENGTH;
|
||||||
|
|
||||||
|
// If the offset moves the mass closer to the current center of mass, flip it
|
||||||
|
if (!tree.nodes.empty()) {
|
||||||
|
const Vector3 mass_center_direction = Vector3Subtract(mass_a.position, tree.nodes.at(0).mass_center);
|
||||||
|
const float mass_center_distance = Vector3Length(mass_center_direction);
|
||||||
|
|
||||||
|
if (mass_center_distance > 0 && Vector3DotProduct(offset, mass_center_direction) < 0.0f) {
|
||||||
|
offset = Vector3Negate(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mass_b.position = mass_a.position + offset;
|
||||||
|
mass_b.previous_position = mass_b.position;
|
||||||
|
|
||||||
|
// infoln("Adding spring: ({}, {}, {})->({}, {}, {})", mass_a.position.x, mass_a.position.y,
|
||||||
|
// mass_a.position.z,
|
||||||
|
// mass_b.position.x, mass_b.position.y, mass_b.position.z);
|
||||||
|
|
||||||
|
springs.emplace_back(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mass_spring_system::clear_forces() -> void
|
||||||
|
{
|
||||||
|
#ifdef TRACY
|
||||||
|
ZoneScoped;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (auto& m : masses) {
|
||||||
|
m.clear_force();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mass_spring_system::calculate_spring_forces() -> void
|
||||||
|
{
|
||||||
|
#ifdef TRACY
|
||||||
|
ZoneScoped;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (const auto s : springs) {
|
||||||
|
mass& a = masses.at(s.a);
|
||||||
|
mass& b = masses.at(s.b);
|
||||||
|
spring::calculate_spring_force(a, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef THREADPOOL
|
||||||
|
auto mass_spring_system::set_thread_name(size_t idx) -> void
|
||||||
|
{
|
||||||
|
BS::this_thread::set_os_thread_name(std::format("bh-worker-{}", idx));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto mass_spring_system::build_octree() -> void
|
||||||
|
{
|
||||||
|
#ifdef TRACY
|
||||||
|
ZoneScoped;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
tree.nodes.clear();
|
||||||
|
tree.nodes.reserve(masses.size() * 2);
|
||||||
|
|
||||||
|
// Compute bounding box around all masses
|
||||||
|
Vector3 min{FLT_MAX, FLT_MAX, FLT_MAX};
|
||||||
|
Vector3 max{-FLT_MAX, -FLT_MAX, -FLT_MAX};
|
||||||
|
for (const auto& m : masses) {
|
||||||
|
min.x = std::min(min.x, m.position.x);
|
||||||
|
max.x = std::max(max.x, m.position.x);
|
||||||
|
min.y = std::min(min.y, m.position.y);
|
||||||
|
max.y = std::max(max.y, m.position.y);
|
||||||
|
min.z = std::min(min.z, m.position.z);
|
||||||
|
max.z = std::max(max.z, m.position.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad the bounding box
|
||||||
|
constexpr float pad = 1.0;
|
||||||
|
min = Vector3Subtract(min, Vector3Scale(Vector3One(), pad));
|
||||||
|
max = Vector3Add(max, Vector3Scale(Vector3One(), pad));
|
||||||
|
|
||||||
|
// Make it cubic (so subdivisions are balanced)
|
||||||
|
const float max_extent = std::max({max.x - min.x, max.y - min.y, max.z - min.z});
|
||||||
|
max = Vector3Add(min, Vector3Scale(Vector3One(), max_extent));
|
||||||
|
|
||||||
|
// Root node spans the entire area
|
||||||
|
const int root = tree.create_empty_leaf(min, max);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < masses.size(); ++i) {
|
||||||
|
tree.insert(root, static_cast<int>(i), masses[i].position, MASS, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mass_spring_system::calculate_repulsion_forces() -> void
|
||||||
|
{
|
||||||
|
#ifdef TRACY
|
||||||
|
ZoneScoped;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
build_octree();
|
||||||
|
|
||||||
|
auto solve_octree = [&](const int i)
|
||||||
|
{
|
||||||
|
const Vector3 force = tree.calculate_force(0, masses[i].position);
|
||||||
|
masses[i].force = Vector3Add(masses[i].force, force);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate forces using Barnes-Hut
|
||||||
|
#ifdef THREADPOOL
|
||||||
|
const BS::multi_future<void> loop_future = threads.submit_loop(0, masses.size(), solve_octree, 256);
|
||||||
|
loop_future.wait();
|
||||||
|
#else
|
||||||
|
for (size_t i = 0; i < masses.size(); ++i) {
|
||||||
|
solve_octree(i);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mass_spring_system::verlet_update(const float delta_time) -> void
|
||||||
|
{
|
||||||
|
#ifdef TRACY
|
||||||
|
ZoneScoped;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (auto& m : masses) {
|
||||||
|
m.verlet_update(delta_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mass_spring_system::center_masses() -> void
|
||||||
|
{
|
||||||
|
Vector3 mean = Vector3Zero();
|
||||||
|
for (const auto& m : masses) {
|
||||||
|
mean += m.position;
|
||||||
|
}
|
||||||
|
mean /= static_cast<float>(masses.size());
|
||||||
|
|
||||||
|
for (auto& m : masses) {
|
||||||
|
m.position -= mean;
|
||||||
|
}
|
||||||
|
}
|
||||||
430
src/octree.cpp
430
src/octree.cpp
@ -1,335 +1,193 @@
|
|||||||
#include "octree.hpp"
|
#include "octree.hpp"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
#include <cfloat>
|
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
|
|
||||||
auto octree::clear() -> void
|
#ifdef TRACY
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto octree::node::child_count() const -> int
|
||||||
{
|
{
|
||||||
nodes.clear();
|
int child_count = 0;
|
||||||
|
for (const int child : children) {
|
||||||
|
if (child != -1) {
|
||||||
|
++child_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return child_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto octree::reserve(const size_t count) -> void
|
auto octree::create_empty_leaf(const Vector3& box_min, const Vector3& box_max) -> int
|
||||||
{
|
{
|
||||||
nodes.reserve(count);
|
node n;
|
||||||
|
n.box_min = box_min;
|
||||||
|
n.box_max = box_max;
|
||||||
|
nodes.emplace_back(n);
|
||||||
|
|
||||||
|
return static_cast<int>(nodes.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto octree::empty() const -> bool
|
auto octree::get_octant(const int node_idx, const Vector3& pos) const -> int
|
||||||
{
|
{
|
||||||
return nodes.empty();
|
const node& n = nodes[node_idx];
|
||||||
|
auto [cx, cy, cz] = Vector3((n.box_min.x + n.box_max.x) / 2.0f, (n.box_min.y + n.box_max.y) / 2.0f,
|
||||||
|
(n.box_min.z + n.box_max.z) / 2.0f);
|
||||||
|
|
||||||
|
// The octant is encoded as a 3-bit integer "zyx". The node area is split
|
||||||
|
// along all 3 axes, if a position is right of an axis, this bit is set to 1.
|
||||||
|
// If a position is right of the x-axis and y-axis and left of the z-axis, the
|
||||||
|
// encoded octant is "011".
|
||||||
|
int octant = 0;
|
||||||
|
if (pos.x >= cx) {
|
||||||
|
octant |= 1;
|
||||||
|
}
|
||||||
|
if (pos.y >= cy) {
|
||||||
|
octant |= 2;
|
||||||
|
}
|
||||||
|
if (pos.z >= cz) {
|
||||||
|
octant |= 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
return octant;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto octree::root() const -> const node&
|
auto octree::get_child_bounds(const int node_idx, const int octant) const -> std::pair<Vector3, Vector3>
|
||||||
{
|
{
|
||||||
return nodes[0];
|
const node& n = nodes[node_idx];
|
||||||
|
auto [cx, cy, cz] = Vector3((n.box_min.x + n.box_max.x) / 2.0f, (n.box_min.y + n.box_max.y) / 2.0f,
|
||||||
|
(n.box_min.z + n.box_max.z) / 2.0f);
|
||||||
|
|
||||||
|
Vector3 min = Vector3Zero();
|
||||||
|
Vector3 max = Vector3Zero();
|
||||||
|
|
||||||
|
// If (octant & 1), the octant is to the right of the node region's x-axis
|
||||||
|
// (see GetOctant). This means the left bound is the x-axis and the right
|
||||||
|
// bound the node's region max.
|
||||||
|
min.x = octant & 1 ? cx : n.box_min.x;
|
||||||
|
max.x = octant & 1 ? n.box_max.x : cx;
|
||||||
|
min.y = octant & 2 ? cy : n.box_min.y;
|
||||||
|
max.y = octant & 2 ? n.box_max.y : cy;
|
||||||
|
min.z = octant & 4 ? cz : n.box_min.z;
|
||||||
|
max.z = octant & 4 ? n.box_max.z : cz;
|
||||||
|
|
||||||
|
return std::make_pair(min, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replaced the 50 line recursive octree insertion with this morton bitch to gain 5 UPS, FML
|
auto octree::insert(const int node_idx, const int mass_id, const Vector3& pos, const float mass,
|
||||||
auto octree::build_octree_morton(octree& t,
|
const int depth) -> void
|
||||||
const std::vector<Vector3>& positions,
|
|
||||||
const std::optional<BS::thread_pool<>*>& thread_pool) -> void
|
|
||||||
{
|
{
|
||||||
#ifdef TRACY
|
// infoln("Inserting position ({}, {}, {}) into octree at node {} (depth {})", pos.x, pos.y,
|
||||||
ZoneScoped;
|
// pos.z, node_idx, depth);
|
||||||
#endif
|
if (depth > MAX_DEPTH) {
|
||||||
|
throw std::runtime_error(std::format("MAX_DEPTH! node={} box_min=({},{},{}) box_max=({},{},{}) pos=({},{},{})",
|
||||||
|
node_idx, nodes[node_idx].box_min.x, nodes[node_idx].box_min.y,
|
||||||
|
nodes[node_idx].box_min.z, nodes[node_idx].box_max.x,
|
||||||
|
nodes[node_idx].box_max.y, nodes[node_idx].box_max.z, pos.x, pos.y,
|
||||||
|
pos.z));
|
||||||
|
}
|
||||||
|
|
||||||
t.clear();
|
// NOTE: Do not store a nodes[node_idx] reference as the nodes vector might reallocate during
|
||||||
if (positions.empty()) {
|
// this function
|
||||||
|
|
||||||
|
// We can place the particle in the empty leaf
|
||||||
|
if (nodes[node_idx].leaf && nodes[node_idx].mass_id == -1) {
|
||||||
|
nodes[node_idx].mass_id = mass_id;
|
||||||
|
nodes[node_idx].mass_center = pos;
|
||||||
|
nodes[node_idx].mass_total = mass;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute bounding box around all masses
|
// The leaf is occupied, we need to subdivide
|
||||||
Vector3 root_min{FLT_MAX, FLT_MAX, FLT_MAX};
|
if (nodes[node_idx].leaf) {
|
||||||
Vector3 root_max{-FLT_MAX, -FLT_MAX, -FLT_MAX};
|
const int existing_id = nodes[node_idx].mass_id;
|
||||||
for (const auto& [x, y, z] : positions) {
|
const Vector3 existing_pos = nodes[node_idx].mass_center;
|
||||||
root_min.x = std::min(root_min.x, x);
|
const float existing_mass = nodes[node_idx].mass_total;
|
||||||
root_max.x = std::max(root_max.x, x);
|
|
||||||
root_min.y = std::min(root_min.y, y);
|
|
||||||
root_max.y = std::max(root_max.y, y);
|
|
||||||
root_min.z = std::min(root_min.z, z);
|
|
||||||
root_max.z = std::max(root_max.z, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr float pad = 1.0f;
|
// If positions are identical we jitter the particles
|
||||||
root_min = Vector3Subtract(root_min, Vector3Scale(Vector3One(), pad));
|
const Vector3 diff = Vector3Subtract(pos, existing_pos);
|
||||||
root_max = Vector3Add(root_max, Vector3Scale(Vector3One(), pad));
|
if (diff == Vector3Zero()) {
|
||||||
|
// warnln("Trying to insert an identical partical into octree (jittering position)");
|
||||||
|
|
||||||
const float max_extent = std::max({root_max.x - root_min.x, root_max.y - root_min.y, root_max.z - root_min.z});
|
Vector3 jittered = pos;
|
||||||
root_max = Vector3Add(root_min, Vector3Scale(Vector3One(), max_extent));
|
jittered.x += 0.001;
|
||||||
|
jittered.y += 0.001;
|
||||||
|
insert(node_idx, mass_id, jittered, mass, depth);
|
||||||
|
return;
|
||||||
|
|
||||||
const float root_extent = root_max.x - root_min.x; // cubic
|
// Could also merge them, but that leads to the octree having less leafs than we have
|
||||||
|
// masses nodes[node_idx].mass_total += mass; return;
|
||||||
// Container for building the particle list before sorting by morton code
|
|
||||||
struct sort_node
|
|
||||||
{
|
|
||||||
u64 code;
|
|
||||||
u32 id;
|
|
||||||
Vector3 pos;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate morton code for each node
|
|
||||||
std::vector<sort_node> sort_container;
|
|
||||||
sort_container.resize(positions.size());
|
|
||||||
|
|
||||||
const auto calculate_morton = [&](const u32 i)
|
|
||||||
{
|
|
||||||
sort_container[i] = {pos_to_morton(positions[i], root_min, root_max), i, positions[i]};
|
|
||||||
};
|
|
||||||
|
|
||||||
if (thread_pool) {
|
|
||||||
(*thread_pool)->submit_loop(0, positions.size(), calculate_morton, SMALL_TASK_BLOCK_SIZE).wait();
|
|
||||||
} else {
|
|
||||||
for (u32 i = 0; i < positions.size(); ++i) {
|
|
||||||
calculate_morton(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort the list by morton codes. Because positions close to each other have similar morten codes,
|
|
||||||
// this provides us with "spatial locality" in the datastructure.
|
|
||||||
auto sort_by_code = [&]()
|
|
||||||
{
|
|
||||||
std::ranges::sort(sort_container,
|
|
||||||
[](const sort_node& a, const sort_node& b)
|
|
||||||
{
|
|
||||||
if (a.code != b.code) {
|
|
||||||
return a.code < b.code;
|
|
||||||
}
|
|
||||||
return a.id < b.id;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
sort_by_code();
|
|
||||||
|
|
||||||
// Resolve duplicates by jittering the later one deterministically and re-encoding.
|
|
||||||
for (int seed = 0; seed < 8; ++seed) {
|
|
||||||
bool had_dupes = false;
|
|
||||||
|
|
||||||
for (size_t i = 1; i < sort_container.size(); ++i) {
|
|
||||||
// Because elements are spatially ordered after sorting, we can scan for duplicates in O(n)
|
|
||||||
if (sort_container[i].code == sort_container[i - 1].code) {
|
|
||||||
had_dupes = true;
|
|
||||||
sort_container[i].pos = jitter_pos(sort_container[i].pos,
|
|
||||||
sort_container[i].id + seed * 0x9e3779b9u,
|
|
||||||
root_min,
|
|
||||||
root_max,
|
|
||||||
root_extent);
|
|
||||||
sort_container[i].code = pos_to_morton(sort_container[i].pos, root_min, root_max);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!had_dupes) {
|
// Convert the leaf to an internal node
|
||||||
break;
|
nodes[node_idx].mass_id = -1;
|
||||||
|
nodes[node_idx].leaf = false;
|
||||||
|
nodes[node_idx].mass_total = 0.0;
|
||||||
|
nodes[node_idx].mass_center = Vector3Zero();
|
||||||
|
|
||||||
|
// Re-insert the existing mass into a new empty leaf (see above)
|
||||||
|
const int oct = get_octant(node_idx, existing_pos);
|
||||||
|
if (nodes[node_idx].children[oct] == -1) {
|
||||||
|
const auto& [min, max] = get_child_bounds(node_idx, oct);
|
||||||
|
const int child_idx = create_empty_leaf(min, max);
|
||||||
|
nodes[node_idx].children[oct] = child_idx;
|
||||||
}
|
}
|
||||||
sort_by_code();
|
insert(nodes[node_idx].children[oct], existing_id, existing_pos, existing_mass, depth + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanity check
|
// Insert the new mass
|
||||||
for (size_t i = 1; i < sort_container.size(); ++i) {
|
const int oct = get_octant(node_idx, pos);
|
||||||
if (sort_container[i].code == sort_container[i - 1].code) {
|
if (nodes[node_idx].children[oct] == -1) {
|
||||||
throw std::runtime_error("Duplicates remain after jittering");
|
const auto& [min, max] = get_child_bounds(node_idx, oct);
|
||||||
}
|
const int child_idx = create_empty_leaf(min, max);
|
||||||
|
nodes[node_idx].children[oct] = child_idx;
|
||||||
}
|
}
|
||||||
|
insert(nodes[node_idx].children[oct], mass_id, pos, mass, depth + 1);
|
||||||
|
|
||||||
std::vector<std::vector<node>> tree_levels;
|
// Update the center of mass
|
||||||
tree_levels.assign(MAX_DEPTH + 1, {});
|
const float new_mass = nodes[node_idx].mass_total + mass;
|
||||||
|
nodes[node_idx].mass_center.x = (nodes[node_idx].mass_center.x * nodes[node_idx].mass_total + pos.x) / new_mass;
|
||||||
// Leaves at MAX_DEPTH: 1 particle per leaf in morton order (close particles close together)
|
nodes[node_idx].mass_center.y = (nodes[node_idx].mass_center.y * nodes[node_idx].mass_total + pos.y) / new_mass;
|
||||||
auto& leafs = tree_levels[MAX_DEPTH];
|
nodes[node_idx].mass_center.z = (nodes[node_idx].mass_center.z * nodes[node_idx].mass_total + pos.z) / new_mass;
|
||||||
leafs.reserve(sort_container.size());
|
nodes[node_idx].mass_total = new_mass;
|
||||||
const float leaf_size = root_extent / static_cast<float>(1u << MAX_DEPTH);
|
|
||||||
for (const auto& [code, id, pos] : sort_container) {
|
|
||||||
node leaf;
|
|
||||||
leaf.leaf = true;
|
|
||||||
leaf.mass_id = static_cast<int>(id);
|
|
||||||
leaf.depth = MAX_DEPTH;
|
|
||||||
leaf.size = leaf_size;
|
|
||||||
leaf.mass_total = MASS;
|
|
||||||
leaf.mass_center = pos; // force uses mass_center instead of jittered position
|
|
||||||
leaf.children.fill(-1);
|
|
||||||
leafs.push_back(leaf);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We now have to group the particles (currently we have only sorted the leaves),
|
|
||||||
// but upwards subdivisions have to be created.
|
|
||||||
// For grouping, store a nodes local index in its level.
|
|
||||||
struct leaf
|
|
||||||
{
|
|
||||||
u64 leaf_code;
|
|
||||||
int depth;
|
|
||||||
int level_index;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<leaf> leaves;
|
|
||||||
leaves.reserve(tree_levels[MAX_DEPTH].size());
|
|
||||||
for (int i = 0; i < static_cast<int>(tree_levels[MAX_DEPTH].size()); ++i) {
|
|
||||||
leaves.emplace_back(sort_container[static_cast<size_t>(i)].code, MAX_DEPTH, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build internal levels from MAX_DEPTH-1 to 0
|
|
||||||
for (int current_depth = MAX_DEPTH - 1; current_depth >= 0; --current_depth) {
|
|
||||||
auto& current_level = tree_levels[current_depth];
|
|
||||||
current_level.clear();
|
|
||||||
|
|
||||||
std::vector<leaf> next_refs;
|
|
||||||
next_refs.reserve(leaves.size());
|
|
||||||
|
|
||||||
const float parent_size = root_extent / static_cast<float>(1u << current_depth);
|
|
||||||
|
|
||||||
size_t i = 0;
|
|
||||||
while (i < leaves.size()) {
|
|
||||||
const u64 key = path_to_ancestor(leaves[i].leaf_code, MAX_DEPTH, current_depth);
|
|
||||||
|
|
||||||
size_t j = i + 1;
|
|
||||||
while (j < leaves.size() && path_to_ancestor(leaves[j].leaf_code, MAX_DEPTH, current_depth) == key) {
|
|
||||||
++j;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t group_size = j - i;
|
|
||||||
|
|
||||||
// Unary compression: just carry the child ref upward unchanged.
|
|
||||||
if (group_size == 1) {
|
|
||||||
next_refs.push_back(leaves[i]);
|
|
||||||
i = j;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
node parent;
|
|
||||||
parent.leaf = false;
|
|
||||||
parent.mass_id = -1;
|
|
||||||
parent.depth = current_depth;
|
|
||||||
parent.size = parent_size;
|
|
||||||
parent.children.fill(-1);
|
|
||||||
|
|
||||||
float mass_total = 0.0f;
|
|
||||||
Vector3 mass_center_acc = Vector3Zero();
|
|
||||||
|
|
||||||
for (size_t k = i; k < j; ++k) {
|
|
||||||
const int child_depth = leaves[k].depth;
|
|
||||||
const int child_local = leaves[k].level_index;
|
|
||||||
|
|
||||||
// Read the child from its actual stored level.
|
|
||||||
const node& child = tree_levels[child_depth][child_local];
|
|
||||||
|
|
||||||
// Which octant of this parent does it belong to?
|
|
||||||
// Octant comes from the NEXT level after current_depth,
|
|
||||||
// but the child might skip levels due to compression.
|
|
||||||
// We must use the child's first level under the parent (current_depth+1).
|
|
||||||
const int oct = octant_at_level(leaves[k].leaf_code, current_depth + 1, MAX_DEPTH);
|
|
||||||
|
|
||||||
// Store global child reference: we only have an int slot, so we need a single index space.
|
|
||||||
parent.children[oct] = (child_depth << 24) | (child_local & 0x00FFFFFF);
|
|
||||||
|
|
||||||
mass_total += child.mass_total;
|
|
||||||
mass_center_acc = Vector3Add(mass_center_acc, Vector3Scale(child.mass_center, child.mass_total));
|
|
||||||
}
|
|
||||||
|
|
||||||
parent.mass_total = mass_total;
|
|
||||||
parent.mass_center = (mass_total > 0.0f) ? Vector3Scale(mass_center_acc, 1.0f / mass_total) : Vector3Zero();
|
|
||||||
|
|
||||||
const int parent_local = static_cast<int>(current_level.size());
|
|
||||||
current_level.push_back(parent);
|
|
||||||
|
|
||||||
next_refs.push_back({leaves[i].leaf_code, current_depth, parent_local});
|
|
||||||
i = j;
|
|
||||||
}
|
|
||||||
|
|
||||||
leaves.swap(next_refs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flatten levels and fix child indices from local->global.
|
|
||||||
// All depth 0 nodes come first, then depth 1, last depth MAX_DEPTH.
|
|
||||||
std::vector<int> offsets(tree_levels.size(), 0);
|
|
||||||
int total = 0;
|
|
||||||
for (int d = 0; d <= MAX_DEPTH; ++d) {
|
|
||||||
offsets[d] = total;
|
|
||||||
total += static_cast<int>(tree_levels[d].size());
|
|
||||||
}
|
|
||||||
|
|
||||||
t.nodes.clear();
|
|
||||||
t.nodes.reserve(total);
|
|
||||||
for (int d = 0; d <= MAX_DEPTH; ++d) {
|
|
||||||
t.nodes.insert(t.nodes.end(), tree_levels[d].begin(), tree_levels[d].end());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fix child indices: convert local index in levels[d+1] to global index in t.nodes
|
|
||||||
for (int d = 0; d <= MAX_DEPTH; ++d) {
|
|
||||||
const int base = offsets[d];
|
|
||||||
for (int i2 = 0; i2 < static_cast<int>(tree_levels[d].size()); ++i2) {
|
|
||||||
node& n = t.nodes[base + i2];
|
|
||||||
if (!n.leaf) {
|
|
||||||
for (int c = 0; c < 8; ++c) {
|
|
||||||
int packed = n.children[c];
|
|
||||||
if (packed >= 0) {
|
|
||||||
const int child_depth = (packed >> 24) & 0xFF;
|
|
||||||
const int child_local = packed & 0x00FFFFFF;
|
|
||||||
n.children[c] = offsets[child_depth] + child_local;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// const size_t _leaves = tree_levels[MAX_DEPTH].size();
|
|
||||||
// const size_t _total = t.nodes.size();
|
|
||||||
// const size_t _internal = _total - _leaves;
|
|
||||||
// traceln("Morton octree nodes: {}, leaves: {}, ratio: {:.3} children per internal node.",
|
|
||||||
// _total,
|
|
||||||
// _leaves,
|
|
||||||
// static_cast<float>(_total - 1) / _internal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto octree::calculate_force_morton(const int node_idx, const Vector3& pos, const int self_id) const -> Vector3
|
auto octree::calculate_force(const int node_idx, const Vector3& pos) const -> Vector3
|
||||||
{
|
{
|
||||||
if (node_idx < 0) {
|
if (node_idx < 0) {
|
||||||
return Vector3Zero();
|
return Vector3Zero();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force accumulator
|
const node& n = nodes[node_idx];
|
||||||
float fx = 0.0f;
|
if (std::abs(n.mass_total) <= 0.001f) {
|
||||||
float fy = 0.0f;
|
return Vector3Zero();
|
||||||
float fz = 0.0f;
|
}
|
||||||
|
|
||||||
std::vector<int> stack;
|
const Vector3 diff = Vector3Subtract(pos, n.mass_center);
|
||||||
stack.reserve(512);
|
float dist_sq = diff.x * diff.x + diff.y * diff.y + diff.z * diff.z;
|
||||||
stack.push_back(node_idx);
|
|
||||||
|
|
||||||
constexpr float theta2 = THETA * THETA;
|
// Softening
|
||||||
|
dist_sq += SOFTENING;
|
||||||
|
|
||||||
while (!stack.empty()) {
|
// Barnes-Hut
|
||||||
const int idx = stack.back();
|
const float size = n.box_max.x - n.box_min.x;
|
||||||
stack.pop_back();
|
if (n.leaf || size * size / dist_sq < THETA * THETA) {
|
||||||
|
const float dist = std::sqrt(dist_sq);
|
||||||
|
const float force_mag = BH_FORCE * n.mass_total / dist_sq;
|
||||||
|
|
||||||
const node& n = nodes[idx];
|
return Vector3Scale(diff, force_mag / dist);
|
||||||
|
}
|
||||||
|
|
||||||
// No self-force for single-particle leafs
|
// Collect child forces
|
||||||
if (n.leaf && n.mass_id == self_id) {
|
Vector3 force = Vector3Zero();
|
||||||
continue;
|
for (const int child : n.children) {
|
||||||
}
|
if (child >= 0) {
|
||||||
|
const Vector3 child_force = calculate_force(child, pos);
|
||||||
|
|
||||||
const float dx = pos.x - n.mass_center.x;
|
force = Vector3Add(force, child_force);
|
||||||
const float dy = pos.y - n.mass_center.y;
|
|
||||||
const float dz = pos.z - n.mass_center.z;
|
|
||||||
|
|
||||||
const float dist_sq = dx * dx + dy * dy + dz * dz + SOFTENING;
|
|
||||||
|
|
||||||
// Barnes–Hut criterion
|
|
||||||
if (n.leaf || ((n.size * n.size) / dist_sq) < theta2) {
|
|
||||||
const float inv_dist = 1.0f / std::sqrt(dist_sq);
|
|
||||||
const float force_mag = (BH_FORCE * n.mass_total) / dist_sq; // ~ 1/r^2
|
|
||||||
const float s = force_mag * inv_dist; // scale by 1/r to get vector
|
|
||||||
fx += dx * s;
|
|
||||||
fy += dy * s;
|
|
||||||
fz += dz * s;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int c = 0; c < 8; ++c) {
|
|
||||||
const int child = n.children[c];
|
|
||||||
if (child >= 0) {
|
|
||||||
stack.push_back(child);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Vector3{fx, fy, fz};
|
return force;
|
||||||
}
|
}
|
||||||
@ -4,6 +4,10 @@
|
|||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
|
|
||||||
|
#ifdef TRACY
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
auto orbit_camera::rotate(const Vector2 last_mouse, const Vector2 mouse) -> void
|
auto orbit_camera::rotate(const Vector2 last_mouse, const Vector2 mouse) -> void
|
||||||
{
|
{
|
||||||
const auto [dx, dy] = Vector2Subtract(mouse, last_mouse);
|
const auto [dx, dy] = Vector2Subtract(mouse, last_mouse);
|
||||||
@ -31,27 +35,24 @@ auto orbit_camera::pan(const Vector2 last_mouse, const Vector2 mouse) -> void
|
|||||||
const Vector3 right = Vector3Normalize(Vector3CrossProduct(forward, camera.up));
|
const Vector3 right = Vector3Normalize(Vector3CrossProduct(forward, camera.up));
|
||||||
const Vector3 up = Vector3Normalize(Vector3CrossProduct(right, forward));
|
const Vector3 up = Vector3Normalize(Vector3CrossProduct(right, forward));
|
||||||
|
|
||||||
const Vector3 offset = Vector3Add(Vector3Scale(right, -dx * speed), Vector3Scale(up, dy * speed));
|
const Vector3 offset =
|
||||||
|
Vector3Add(Vector3Scale(right, -dx * speed), Vector3Scale(up, dy * speed));
|
||||||
|
|
||||||
target = Vector3Add(target, offset);
|
target = Vector3Add(target, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto orbit_camera::update(const Vector3& current_target,
|
auto orbit_camera::update(const Vector3& current_target, const Vector3& mass_center,
|
||||||
const Vector3& mass_center,
|
const bool lock, const bool mass_center_lock) -> void
|
||||||
const bool lock,
|
|
||||||
const bool mass_center_lock) -> void
|
|
||||||
{
|
{
|
||||||
if (lock) {
|
if (lock) {
|
||||||
if (mass_center_lock) {
|
if (mass_center_lock) {
|
||||||
target = Vector3MoveTowards(target,
|
target = Vector3MoveTowards(target, mass_center,
|
||||||
mass_center,
|
CAMERA_SMOOTH_SPEED * GetFrameTime() *
|
||||||
CAMERA_SMOOTH_SPEED * GetFrameTime() * Vector3Length(
|
Vector3Length(Vector3Subtract(target, mass_center)));
|
||||||
Vector3Subtract(target, mass_center)));
|
|
||||||
} else {
|
} else {
|
||||||
target = Vector3MoveTowards(target,
|
target = Vector3MoveTowards(target, current_target,
|
||||||
current_target,
|
CAMERA_SMOOTH_SPEED * GetFrameTime() *
|
||||||
CAMERA_SMOOTH_SPEED * GetFrameTime() * Vector3Length(
|
Vector3Length(Vector3Subtract(target, current_target)));
|
||||||
Vector3Subtract(target, current_target)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,14 +67,10 @@ auto orbit_camera::update(const Vector3& current_target,
|
|||||||
const float y = sin(angle_y) * actual_distance;
|
const float y = sin(angle_y) * actual_distance;
|
||||||
const float z = cos(angle_y) * cos(angle_x) * actual_distance;
|
const float z = cos(angle_y) * cos(angle_x) * actual_distance;
|
||||||
|
|
||||||
if (projection == CAMERA_ORTHOGRAPHIC) {
|
fov = Clamp(fov, MIN_FOV, MAX_FOV);
|
||||||
fov = Clamp(fov, MIN_FOV, MAX_ORTHO_FOV);
|
|
||||||
} else {
|
|
||||||
fov = Clamp(fov, MIN_FOV, MAX_PERSP_FOV);
|
|
||||||
}
|
|
||||||
|
|
||||||
camera.position = Vector3Add(target, Vector3(x, y, z));
|
camera.position = Vector3Add(target, Vector3(x, y, z));
|
||||||
camera.target = target;
|
camera.target = target;
|
||||||
camera.fovy = fov;
|
camera.fovy = fov;
|
||||||
camera.projection = projection;
|
camera.projection = projection;
|
||||||
}
|
}
|
||||||
|
|||||||
842
src/puzzle.cpp
842
src/puzzle.cpp
File diff suppressed because it is too large
Load Diff
237
src/renderer.cpp
237
src/renderer.cpp
@ -4,7 +4,10 @@
|
|||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
#include <rlgl.h>
|
#include <rlgl.h>
|
||||||
#include <GL/glew.h>
|
|
||||||
|
#ifdef TRACY
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
auto renderer::update_texture_sizes() -> void
|
auto renderer::update_texture_sizes() -> void
|
||||||
{
|
{
|
||||||
@ -12,142 +15,70 @@ auto renderer::update_texture_sizes() -> void
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UnloadRenderTexture(graph_target);
|
UnloadRenderTexture(render_target);
|
||||||
UnloadRenderTexture(klotski_target);
|
UnloadRenderTexture(klotski_target);
|
||||||
UnloadRenderTexture(menu_target);
|
UnloadRenderTexture(menu_target);
|
||||||
|
|
||||||
const int width = GetScreenWidth() / 2;
|
const int width = GetScreenWidth() / 2;
|
||||||
const int height = GetScreenHeight() - MENU_HEIGHT;
|
const int height = GetScreenHeight() - MENU_HEIGHT;
|
||||||
|
|
||||||
graph_target = LoadRenderTexture(width, height);
|
render_target = LoadRenderTexture(width, height);
|
||||||
klotski_target = LoadRenderTexture(width, height);
|
klotski_target = LoadRenderTexture(width, height);
|
||||||
menu_target = LoadRenderTexture(width * 2, MENU_HEIGHT);
|
menu_target = LoadRenderTexture(width * 2, MENU_HEIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
|
auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
|
||||||
{
|
{
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (masses.size() != state.get_state_count()) {
|
if (masses.size() != state.get_state_count()) {
|
||||||
// Because the physics run in a different thread, it might need time to catch up
|
// Because the physics run in a different thread, it might need time to catch up
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare edge buffer
|
// Prepare connection batching
|
||||||
{
|
{
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
ZoneNamedN(prepare_edge_buffers, "PrepareEdgeBuffers", true);
|
ZoneNamedN(prepare_masses, "PrepareConnectionsBatching", true);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
edge_vertices.clear();
|
|
||||||
for (const auto& [from, to] : state.get_links()) {
|
|
||||||
edge_vertices.push_back(masses[from]);
|
|
||||||
edge_vertices.push_back(masses[to]);
|
|
||||||
}
|
|
||||||
rlUpdateVertexBuffer(edge_vbo_id, edge_vertices.data(), edge_vertices.size() * sizeof(Vector3), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare connection drawing
|
|
||||||
{
|
|
||||||
#ifdef TRACY
|
|
||||||
ZoneNamedN(prepare_connections, "PrepareConnectionsDrawing", true);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
connections.clear();
|
connections.clear();
|
||||||
connections.reserve(state.get_target_count());
|
connections.reserve(state.get_target_count());
|
||||||
if (input.connect_solutions) {
|
if (input.connect_solutions) {
|
||||||
for (const size_t& _state : state.get_winning_indices()) {
|
for (const size_t& _state : state.get_winning_indices()) {
|
||||||
const Vector3& current_mass = masses[state.get_current_index()];
|
const Vector3& current_mass = masses.at(state.get_current_index());
|
||||||
const Vector3& winning_mass = masses[_state];
|
const Vector3& winning_mass = masses.at(_state);
|
||||||
connections.emplace_back(current_mass, winning_mass);
|
connections.emplace_back(current_mass, winning_mass);
|
||||||
|
DrawLine3D(current_mass, winning_mass, Fade(TARGET_BLOCK_COLOR, 0.5));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare cube instancing
|
// Prepare cube instancing
|
||||||
{
|
{
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
ZoneNamedN(prepare_masses, "PrepareMassInstancing", true);
|
ZoneNamedN(prepare_masses, "PrepareMassInstancing", true);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (masses.size() < DRAW_VERTICES_LIMIT) {
|
if (masses.size() < DRAW_VERTICES_LIMIT) {
|
||||||
// Don't have to reserve, capacity is already set to DRAW_VERTICES_LIMIT in constructor
|
// Don't have to reserve, capacity is already set to DRAW_VERTICES_LIMIT in constructor
|
||||||
transforms.clear();
|
transforms.clear();
|
||||||
colors.clear();
|
colors.clear();
|
||||||
|
|
||||||
// Collisions
|
|
||||||
// TODO: This would benefit greatly from a spatial data structure.
|
|
||||||
// Would it be worth to copy the octree from the physics thread?
|
|
||||||
input.collision_mass = -1;
|
|
||||||
if (input.mouse_in_graph_pane() && IsKeyDown(KEY_Q)) {
|
|
||||||
#ifdef TRACY
|
|
||||||
ZoneNamedN(mass_collisions, "MassCollisions", true);
|
|
||||||
#endif
|
|
||||||
const Ray ray = GetScreenToWorldRayEx(
|
|
||||||
GetMousePosition() - Vector2(GetScreenWidth() / 2.0f, MENU_HEIGHT),
|
|
||||||
camera.camera,
|
|
||||||
graph_target.texture.width,
|
|
||||||
graph_target.texture.height);
|
|
||||||
// Ray collision hit info
|
|
||||||
|
|
||||||
size_t mass = 0;
|
|
||||||
for (const auto& [x, y, z] : masses) {
|
|
||||||
const RayCollision collision = GetRayCollisionBox(ray,
|
|
||||||
BoundingBox{
|
|
||||||
{
|
|
||||||
x - VERTEX_SIZE / 2.0f,
|
|
||||||
y - VERTEX_SIZE / 2.0f,
|
|
||||||
z - VERTEX_SIZE / 2.0f
|
|
||||||
},
|
|
||||||
{
|
|
||||||
x + VERTEX_SIZE / 2.0f,
|
|
||||||
y + VERTEX_SIZE / 2.0f,
|
|
||||||
z + VERTEX_SIZE / 2.0f
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (collision.hit) {
|
|
||||||
input.collision_mass = mass;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++mass;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find max distance to interpolate colors in the given [0, max] range
|
|
||||||
int max_distance = 0;
|
|
||||||
for (const int distance : state.get_distances()) {
|
|
||||||
if (distance > max_distance) {
|
|
||||||
max_distance = distance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto lerp_color = [&](const Color from, const Color to, const int distance)
|
|
||||||
{
|
|
||||||
const float weight = 1.0 - static_cast<float>(distance) / max_distance;
|
|
||||||
|
|
||||||
Color result;
|
|
||||||
result.r = static_cast<u8>((1 - weight) * from.r + weight * to.r);
|
|
||||||
result.g = static_cast<u8>((1 - weight) * from.g + weight * to.g);
|
|
||||||
result.b = static_cast<u8>((1 - weight) * from.b + weight * to.b);
|
|
||||||
result.a = static_cast<u8>((1 - weight) * from.a + weight * to.a);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const std::vector<int>& distances = state.get_distances();
|
|
||||||
|
|
||||||
size_t mass = 0;
|
size_t mass = 0;
|
||||||
for (const auto& [x, y, z] : masses) {
|
for (const auto& [x, y, z] : masses) {
|
||||||
transforms.emplace_back(MatrixTranslate(x, y, z));
|
transforms.emplace_back(MatrixTranslate(x, y, z));
|
||||||
|
|
||||||
// Normal vertex
|
// Normal vertex
|
||||||
Color c = VERTEX_COLOR;
|
Color c = VERTEX_COLOR;
|
||||||
if ((input.mark_solutions || input.mark_path) && state.get_winning_indices().contains(mass)) {
|
if ((input.mark_solutions || input.mark_path) &&
|
||||||
|
state.get_winning_indices().contains(mass)) {
|
||||||
// Winning vertex
|
// Winning vertex
|
||||||
c = VERTEX_TARGET_COLOR;
|
c = VERTEX_TARGET_COLOR;
|
||||||
} else if ((input.mark_solutions || input.mark_path) && state.get_path_indices().contains(mass)) {
|
} else if ((input.mark_solutions || input.mark_path) &&
|
||||||
|
state.get_path_indices().contains(mass)) {
|
||||||
// Path vertex
|
// Path vertex
|
||||||
c = VERTEX_PATH_COLOR;
|
c = VERTEX_PATH_COLOR;
|
||||||
} else if (mass == state.get_starting_index()) {
|
} else if (mass == state.get_starting_index()) {
|
||||||
@ -156,14 +87,9 @@ auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
|
|||||||
} else if (state.get_visit_counts().at(mass) > 0) {
|
} else if (state.get_visit_counts().at(mass) > 0) {
|
||||||
// Visited vertex
|
// Visited vertex
|
||||||
c = VERTEX_VISITED_COLOR;
|
c = VERTEX_VISITED_COLOR;
|
||||||
} else if (input.color_by_distance && distances.size() == masses.size()) {
|
|
||||||
c = lerp_color(VERTEX_FARTHEST_COLOR, VERTEX_CLOSEST_COLOR, static_cast<float>(distances[mass]));
|
|
||||||
}
|
}
|
||||||
if (mass == input.collision_mass) {
|
|
||||||
c = RED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current vertex is drawn as individual cube to increase its size
|
// Current vertex is drawn as individual cube to increase its size
|
||||||
|
|
||||||
colors.emplace_back(c);
|
colors.emplace_back(c);
|
||||||
++mass;
|
++mass;
|
||||||
}
|
}
|
||||||
@ -172,71 +98,42 @@ auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
|
|||||||
rlUpdateVertexBuffer(color_vbo_id, colors.data(), colors.size() * sizeof(Color), 0);
|
rlUpdateVertexBuffer(color_vbo_id, colors.data(), colors.size() * sizeof(Color), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
BeginTextureMode(graph_target);
|
BeginTextureMode(render_target);
|
||||||
ClearBackground(RAYWHITE);
|
ClearBackground(RAYWHITE);
|
||||||
BeginMode3D(camera.camera);
|
BeginMode3D(camera.camera);
|
||||||
|
|
||||||
rlDrawRenderBatchActive();
|
// Draw springs (batched)
|
||||||
|
|
||||||
// Draw edges
|
|
||||||
{
|
{
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
ZoneNamedN(draw_springs, "DrawSprings", true);
|
ZoneNamedN(draw_springs, "DrawSprings", true);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
rlEnableShader(edge_shader.id);
|
rlBegin(RL_LINES);
|
||||||
|
for (const auto& [from, to] : state.get_links()) {
|
||||||
Matrix modelview = rlGetMatrixModelview();
|
if (masses.size() > from && masses.size() > to) {
|
||||||
Matrix projection = rlGetMatrixProjection();
|
const auto& [ax, ay, az] = masses.at(from);
|
||||||
Matrix mvp = MatrixMultiply(modelview, projection);
|
const auto& [bx, by, bz] = masses.at(to);
|
||||||
rlSetUniformMatrix(edge_shader.locs[SHADER_LOC_MATRIX_MVP], mvp);
|
rlColor4ub(EDGE_COLOR.r, EDGE_COLOR.g, EDGE_COLOR.b, EDGE_COLOR.a);
|
||||||
|
rlVertex3f(ax, ay, az);
|
||||||
const std::array<float, 4> edge_color = {
|
rlVertex3f(bx, by, bz);
|
||||||
EDGE_COLOR.r / 255.0f,
|
}
|
||||||
EDGE_COLOR.g / 255.0f,
|
}
|
||||||
EDGE_COLOR.b / 255.0f,
|
rlEnd();
|
||||||
EDGE_COLOR.a / 255.0f
|
|
||||||
};
|
|
||||||
rlSetUniform(edge_color_loc, edge_color.data(), SHADER_UNIFORM_VEC4, 1);
|
|
||||||
|
|
||||||
glBindVertexArray(edge_vao_id);
|
|
||||||
glDrawArrays(GL_LINES, 0, edge_vertices.size());
|
|
||||||
glBindVertexArray(0);
|
|
||||||
|
|
||||||
rlDisableShader();
|
|
||||||
|
|
||||||
// This draws triangles:
|
|
||||||
// rlEnableVertexArray(edge_vao_id);
|
|
||||||
// rlColor4ub(EDGE_COLOR.r, EDGE_COLOR.g, EDGE_COLOR.b, EDGE_COLOR.a);
|
|
||||||
// rlDrawVertexArray(0, edge_vertices.size());
|
|
||||||
// rlDisableVertexArray();
|
|
||||||
|
|
||||||
// This is fucking slow:
|
|
||||||
// rlBegin(RL_LINES);
|
|
||||||
// for (const auto& [from, to] : state.get_links()) {
|
|
||||||
// if (masses.size() > from && masses.size() > to) {
|
|
||||||
// const auto& [ax, ay, az] = masses[from];
|
|
||||||
// const auto& [bx, by, bz] = masses[to];
|
|
||||||
// rlColor4ub(EDGE_COLOR.r, EDGE_COLOR.g, EDGE_COLOR.b, EDGE_COLOR.a);
|
|
||||||
// rlVertex3f(ax, ay, az);
|
|
||||||
// rlVertex3f(bx, by, bz);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// rlEnd();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw masses (instanced)
|
// Draw masses (instanced)
|
||||||
{
|
{
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
ZoneNamedN(draw_masses, "DrawMasses", true);
|
ZoneNamedN(draw_masses, "DrawMasses", true);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (masses.size() < DRAW_VERTICES_LIMIT) {
|
if (masses.size() < DRAW_VERTICES_LIMIT) {
|
||||||
// NOTE: I don't know if drawing all this inside a shader would make it
|
// NOTE: I don't know if drawing all this inside a shader would make it
|
||||||
// much faster... The amount of data sent to the GPU would be
|
// much faster... The amount of data sent to the GPU would be
|
||||||
// reduced (just positions instead of matrices), but is this
|
// reduced (just positions instead of matrices), but is this
|
||||||
// noticable for < 100000 cubes?
|
// noticable for < 100000 cubes?
|
||||||
DrawMeshInstanced(cube_instance, vertex_mat, transforms.data(), masses.size()); // NOLINT(*-narrowing-conversions)
|
DrawMeshInstanced(cube_instance, vertex_mat, transforms.data(),
|
||||||
|
masses.size()); // NOLINT(*-narrowing-conversions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,8 +152,9 @@ auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
|
|||||||
// Mark current state
|
// Mark current state
|
||||||
const size_t current_index = state.get_current_index();
|
const size_t current_index = state.get_current_index();
|
||||||
if (masses.size() > current_index) {
|
if (masses.size() > current_index) {
|
||||||
const Vector3& current_mass = masses[current_index];
|
const Vector3& current_mass = masses.at(current_index);
|
||||||
DrawCube(current_mass, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_CURRENT_COLOR);
|
DrawCube(current_mass, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_SIZE * 2,
|
||||||
|
VERTEX_CURRENT_COLOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
EndMode3D();
|
EndMode3D();
|
||||||
@ -265,9 +163,9 @@ auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
|
|||||||
|
|
||||||
auto renderer::draw_klotski() const -> void
|
auto renderer::draw_klotski() const -> void
|
||||||
{
|
{
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
BeginTextureMode(klotski_target);
|
BeginTextureMode(klotski_target);
|
||||||
ClearBackground(RAYWHITE);
|
ClearBackground(RAYWHITE);
|
||||||
@ -279,9 +177,9 @@ auto renderer::draw_klotski() const -> void
|
|||||||
|
|
||||||
auto renderer::draw_menu() const -> void
|
auto renderer::draw_menu() const -> void
|
||||||
{
|
{
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
BeginTextureMode(menu_target);
|
BeginTextureMode(menu_target);
|
||||||
ClearBackground(RAYWHITE);
|
ClearBackground(RAYWHITE);
|
||||||
@ -291,48 +189,37 @@ auto renderer::draw_menu() const -> void
|
|||||||
EndTextureMode();
|
EndTextureMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto renderer::draw_textures(const int fps,
|
auto renderer::draw_textures(const int fps, const int ups, const size_t mass_count,
|
||||||
const int ups,
|
|
||||||
const size_t mass_count,
|
|
||||||
const size_t spring_count) const -> void
|
const size_t spring_count) const -> void
|
||||||
{
|
{
|
||||||
BeginDrawing();
|
BeginDrawing();
|
||||||
|
|
||||||
DrawTextureRec(menu_target.texture,
|
DrawTextureRec(menu_target.texture,
|
||||||
Rectangle(0, 0, menu_target.texture.width, -menu_target.texture.height),
|
Rectangle(0, 0, menu_target.texture.width, -menu_target.texture.height),
|
||||||
Vector2(0, 0),
|
Vector2(0, 0), WHITE);
|
||||||
WHITE);
|
|
||||||
DrawTextureRec(klotski_target.texture,
|
DrawTextureRec(klotski_target.texture,
|
||||||
Rectangle(0, 0, klotski_target.texture.width, -klotski_target.texture.height),
|
Rectangle(0, 0, klotski_target.texture.width, -klotski_target.texture.height),
|
||||||
Vector2(0, MENU_HEIGHT),
|
Vector2(0, MENU_HEIGHT), WHITE);
|
||||||
WHITE);
|
DrawTextureRec(render_target.texture,
|
||||||
DrawTextureRec(graph_target.texture,
|
Rectangle(0, 0, render_target.texture.width, -render_target.texture.height),
|
||||||
Rectangle(0, 0, graph_target.texture.width, -graph_target.texture.height),
|
Vector2(GetScreenWidth() / 2.0f, MENU_HEIGHT), WHITE);
|
||||||
Vector2(GetScreenWidth() / 2.0f, MENU_HEIGHT),
|
|
||||||
WHITE);
|
|
||||||
|
|
||||||
// Draw borders
|
// Draw borders
|
||||||
DrawRectangleLinesEx(Rectangle(0, 0, GetScreenWidth(), MENU_HEIGHT), 1.0f, BLACK);
|
DrawRectangleLinesEx(Rectangle(0, 0, GetScreenWidth(), MENU_HEIGHT), 1.0f, BLACK);
|
||||||
DrawRectangleLinesEx(Rectangle(0, MENU_HEIGHT, GetScreenWidth() / 2.0f, GetScreenHeight() - MENU_HEIGHT),
|
DrawRectangleLinesEx(
|
||||||
1.0f,
|
Rectangle(0, MENU_HEIGHT, GetScreenWidth() / 2.0f, GetScreenHeight() - MENU_HEIGHT), 1.0f,
|
||||||
BLACK);
|
BLACK);
|
||||||
DrawRectangleLinesEx(Rectangle(GetScreenWidth() / 2.0f,
|
DrawRectangleLinesEx(Rectangle(GetScreenWidth() / 2.0f, MENU_HEIGHT, GetScreenWidth() / 2.0f,
|
||||||
MENU_HEIGHT,
|
|
||||||
GetScreenWidth() / 2.0f,
|
|
||||||
GetScreenHeight() - MENU_HEIGHT),
|
GetScreenHeight() - MENU_HEIGHT),
|
||||||
1.0f,
|
1.0f, BLACK);
|
||||||
BLACK);
|
|
||||||
|
|
||||||
gui.draw(fps, ups, mass_count, spring_count);
|
gui.draw(fps, ups, mass_count, spring_count);
|
||||||
|
|
||||||
EndDrawing();
|
EndDrawing();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto renderer::render(const std::vector<Vector3>& masses,
|
auto renderer::render(const std::vector<Vector3>& masses, const int fps, const int ups,
|
||||||
const int fps,
|
const size_t mass_count, const size_t spring_count) -> void
|
||||||
const int ups,
|
|
||||||
const size_t mass_count,
|
|
||||||
const size_t spring_count) -> void
|
|
||||||
{
|
{
|
||||||
update_texture_sizes();
|
update_texture_sizes();
|
||||||
|
|
||||||
@ -340,4 +227,4 @@ auto renderer::render(const std::vector<Vector3>& masses,
|
|||||||
draw_klotski();
|
draw_klotski();
|
||||||
draw_menu();
|
draw_menu();
|
||||||
draw_textures(fps, ups, mass_count, spring_count);
|
draw_textures(fps, ups, mass_count, spring_count);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,10 +2,17 @@
|
|||||||
#include "graph_distances.hpp"
|
#include "graph_distances.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <ios>
|
||||||
|
|
||||||
|
#ifdef TRACY
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
auto state_manager::synced_try_insert_state(const puzzle& state) -> size_t
|
auto state_manager::synced_try_insert_state(const puzzle& state) -> size_t
|
||||||
{
|
{
|
||||||
if (state_indices.contains(state)) {
|
if (state_indices.contains(state)) {
|
||||||
return state_indices[state];
|
return state_indices.at(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
const size_t index = state_pool.size();
|
const size_t index = state_pool.size();
|
||||||
@ -28,7 +35,7 @@ auto state_manager::synced_insert_link(size_t first_index, size_t second_index)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto state_manager::synced_insert_statespace(const std::vector<puzzle>& states,
|
auto state_manager::synced_insert_statespace(const std::vector<puzzle>& states,
|
||||||
const std::vector<spring>& _links) -> void
|
const std::vector<std::pair<size_t, size_t>>& _links) -> void
|
||||||
{
|
{
|
||||||
if (!state_pool.empty() || !state_indices.empty() || !links.empty()) {
|
if (!state_pool.empty() || !state_indices.empty() || !links.empty()) {
|
||||||
warnln("Inserting statespace but collections haven't been cleared");
|
warnln("Inserting statespace but collections haven't been cleared");
|
||||||
@ -70,27 +77,78 @@ auto state_manager::synced_clear_statespace() -> void
|
|||||||
physics.clear_cmd();
|
physics.clear_cmd();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto state_manager::save_current_to_preset_file(const std::string& preset_comment) -> void
|
auto state_manager::parse_preset_file(const std::string& _preset_file) -> bool
|
||||||
{
|
{
|
||||||
if (append_preset_file(preset_file, preset_comment, get_current_state())) {
|
preset_file = _preset_file;
|
||||||
current_preset = preset_states.size();
|
|
||||||
reload_preset_file();
|
std::ifstream file(preset_file);
|
||||||
|
if (!file) {
|
||||||
|
infoln("Preset file \"{}\" couldn't be loaded.", preset_file);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
std::vector<std::string> comment_lines;
|
||||||
|
std::vector<std::string> preset_lines;
|
||||||
|
while (std::getline(file, line)) {
|
||||||
|
if (line.starts_with("S")) {
|
||||||
|
preset_lines.push_back(line);
|
||||||
|
} else if (line.starts_with("#")) {
|
||||||
|
comment_lines.push_back(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preset_lines.empty() || comment_lines.size() != preset_lines.size()) {
|
||||||
|
infoln("Preset file \"{}\" couldn't be loaded.", preset_file);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
preset_states.clear();
|
||||||
|
for (const auto& preset : preset_lines) {
|
||||||
|
// Each char is a bit
|
||||||
|
const puzzle& p = puzzle(preset);
|
||||||
|
|
||||||
|
if (const std::optional<std::string>& reason = p.try_get_invalid_reason()) {
|
||||||
|
preset_states = {puzzle(4, 5, 0, 0, true, false)};
|
||||||
|
infoln("Preset file \"{}\" contained invalid presets: {}", preset_file, *reason);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
preset_states.emplace_back(p);
|
||||||
|
}
|
||||||
|
preset_comments = comment_lines;
|
||||||
|
|
||||||
|
infoln("Loaded {} presets from \"{}\".", preset_lines.size(), preset_file);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto state_manager::reload_preset_file() -> void
|
auto state_manager::append_preset_file(const std::string& preset_name) -> bool
|
||||||
{
|
{
|
||||||
const auto [presets, comments] = parse_preset_file(preset_file);
|
infoln(R"(Saving preset "{}" to "{}")", preset_name, preset_file);
|
||||||
if (!presets.empty()) {
|
|
||||||
preset_states = presets;
|
if (get_current_state().try_get_invalid_reason()) {
|
||||||
preset_comments = comments;
|
return false;
|
||||||
}
|
}
|
||||||
load_preset(current_preset);
|
|
||||||
|
std::ofstream file(preset_file, std::ios_base::app | std::ios_base::out);
|
||||||
|
if (!file) {
|
||||||
|
infoln("Preset file \"{}\" couldn't be loaded.", preset_file);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file << "\n# " << preset_name << "\n" << get_current_state().string_repr() << std::flush;
|
||||||
|
|
||||||
|
infoln("Refreshing presets...");
|
||||||
|
if (parse_preset_file(preset_file)) {
|
||||||
|
load_preset(preset_states.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto state_manager::load_preset(const size_t preset) -> void
|
auto state_manager::load_preset(const size_t preset) -> void
|
||||||
{
|
{
|
||||||
clear_graph_and_add_current(preset_states[preset]);
|
clear_graph_and_add_current(preset_states.at(preset));
|
||||||
current_preset = preset;
|
current_preset = preset;
|
||||||
edited = false;
|
edited = false;
|
||||||
}
|
}
|
||||||
@ -212,8 +270,8 @@ auto state_manager::goto_most_distant_state() -> void
|
|||||||
int max_distance = 0;
|
int max_distance = 0;
|
||||||
size_t max_distance_index = 0;
|
size_t max_distance_index = 0;
|
||||||
for (size_t i = 0; i < node_target_distances.distances.size(); ++i) {
|
for (size_t i = 0; i < node_target_distances.distances.size(); ++i) {
|
||||||
if (node_target_distances.distances[i] > max_distance) {
|
if (node_target_distances.distances.at(i) > max_distance) {
|
||||||
max_distance = node_target_distances.distances[i];
|
max_distance = node_target_distances.distances.at(i);
|
||||||
max_distance_index = i;
|
max_distance_index = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -227,7 +285,7 @@ auto state_manager::goto_closest_target_state() -> void
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
update_current_state(get_state(node_target_distances.nearest_targets[current_state_index]));
|
update_current_state(get_state(node_target_distances.nearest_targets.at(current_state_index)));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto state_manager::populate_graph() -> void
|
auto state_manager::populate_graph() -> void
|
||||||
@ -240,23 +298,17 @@ auto state_manager::populate_graph() -> void
|
|||||||
const puzzle s = get_starting_state();
|
const puzzle s = get_starting_state();
|
||||||
const puzzle p = get_current_state();
|
const puzzle p = get_current_state();
|
||||||
|
|
||||||
|
|
||||||
// Clear the graph first so we don't add duplicates somehow
|
// Clear the graph first so we don't add duplicates somehow
|
||||||
synced_clear_statespace();
|
synced_clear_statespace();
|
||||||
|
|
||||||
// Explore the entire statespace starting from the current state
|
// Explore the entire statespace starting from the current state
|
||||||
const std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
|
|
||||||
|
|
||||||
const auto& [states, _links] = s.explore_state_space();
|
const auto& [states, _links] = s.explore_state_space();
|
||||||
synced_insert_statespace(states, _links);
|
synced_insert_statespace(states, _links);
|
||||||
|
|
||||||
const std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
|
current_state_index = state_indices.at(p);
|
||||||
|
|
||||||
infoln("Explored puzzle. Took {}ms.", std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count());
|
|
||||||
infoln("State space has size {} with {} transitions.", state_pool.size(), links.size());
|
|
||||||
|
|
||||||
current_state_index = state_indices[p];
|
|
||||||
previous_state_index = current_state_index;
|
previous_state_index = current_state_index;
|
||||||
starting_state_index = state_indices[s];
|
starting_state_index = state_indices.at(s);
|
||||||
|
|
||||||
// Search for cool stuff
|
// Search for cool stuff
|
||||||
populate_winning_indices();
|
populate_winning_indices();
|
||||||
@ -341,7 +393,7 @@ auto state_manager::get_starting_index() const -> size_t
|
|||||||
|
|
||||||
auto state_manager::get_state(const size_t index) const -> const puzzle&
|
auto state_manager::get_state(const size_t index) const -> const puzzle&
|
||||||
{
|
{
|
||||||
return state_pool[index];
|
return state_pool.at(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto state_manager::get_current_state() const -> const puzzle&
|
auto state_manager::get_current_state() const -> const puzzle&
|
||||||
@ -374,7 +426,7 @@ auto state_manager::get_path_length() const -> size_t
|
|||||||
return winning_path.size();
|
return winning_path.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto state_manager::get_links() const -> const std::vector<spring>&
|
auto state_manager::get_links() const -> const std::vector<std::pair<size_t, size_t>>&
|
||||||
{
|
{
|
||||||
return links;
|
return links;
|
||||||
}
|
}
|
||||||
@ -416,7 +468,7 @@ auto state_manager::get_preset_count() const -> size_t
|
|||||||
|
|
||||||
auto state_manager::get_current_preset_comment() const -> const std::string&
|
auto state_manager::get_current_preset_comment() const -> const std::string&
|
||||||
{
|
{
|
||||||
return preset_comments[current_preset];
|
return preset_comments.at(current_preset);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto state_manager::has_history() const -> bool
|
auto state_manager::has_history() const -> bool
|
||||||
@ -429,11 +481,6 @@ auto state_manager::has_distances() const -> bool
|
|||||||
return !node_target_distances.empty();
|
return !node_target_distances.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto state_manager::get_distances() const -> std::vector<int>
|
|
||||||
{
|
|
||||||
return node_target_distances.distances;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto state_manager::get_total_moves() const -> size_t
|
auto state_manager::get_total_moves() const -> size_t
|
||||||
{
|
{
|
||||||
return total_moves;
|
return total_moves;
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
#include "cpu_layout_engine.hpp"
|
#include "threaded_physics.hpp"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include "cpu_spring_system.hpp"
|
#include "mass_spring_system.hpp"
|
||||||
#include "util.hpp"
|
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
@ -9,29 +8,20 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifdef ASYNC_OCTREE
|
#ifdef TRACY
|
||||||
auto cpu_layout_engine::set_octree_pool_thread_name(size_t idx) -> void
|
#include <tracy/Tracy.hpp>
|
||||||
{
|
|
||||||
BS::this_thread::set_os_thread_name(std::format("octree-{}", idx));
|
|
||||||
// traceln("Using thread \"{}\"", BS::this_thread::get_os_thread_name().value_or("INVALID NAME"));
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto cpu_layout_engine::physics_thread(physics_state& state, const threadpool thread_pool) -> void
|
auto threaded_physics::physics_thread(physics_state& state) -> void
|
||||||
{
|
{
|
||||||
cpu_spring_system mass_springs;
|
#ifdef THREADPOOL
|
||||||
|
|
||||||
#ifdef ASYNC_OCTREE
|
|
||||||
BS::this_thread::set_os_thread_name("physics");
|
BS::this_thread::set_os_thread_name("physics");
|
||||||
|
|
||||||
BS::thread_pool<> octree_thread(1, set_octree_pool_thread_name);
|
|
||||||
std::future<void> octree_future;
|
|
||||||
octree tree_buffer;
|
|
||||||
size_t last_mass_count = 0;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
mass_spring_system mass_springs;
|
||||||
|
|
||||||
const auto visitor = overloads{
|
const auto visitor = overloads{
|
||||||
[&](const struct add_mass&)
|
[&](const struct add_mass& am)
|
||||||
{
|
{
|
||||||
mass_springs.add_mass();
|
mass_springs.add_mass();
|
||||||
},
|
},
|
||||||
@ -39,7 +29,7 @@ auto cpu_layout_engine::physics_thread(physics_state& state, const threadpool th
|
|||||||
{
|
{
|
||||||
mass_springs.add_spring(as.a, as.b);
|
mass_springs.add_spring(as.a, as.b);
|
||||||
},
|
},
|
||||||
[&](const struct clear_graph&)
|
[&](const struct clear_graph& cg)
|
||||||
{
|
{
|
||||||
mass_springs.clear();
|
mass_springs.clear();
|
||||||
},
|
},
|
||||||
@ -76,58 +66,26 @@ auto cpu_layout_engine::physics_thread(physics_state& state, const threadpool th
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mass_springs.positions.empty()) {
|
if (mass_springs.masses.empty()) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Physics update
|
// Physics update
|
||||||
if (physics_accumulator.count() > TIMESTEP) {
|
if (physics_accumulator.count() > TIMESTEP) {
|
||||||
#ifdef ASYNC_OCTREE
|
|
||||||
// Snapshot the positions so mass_springs is not mutating the vector while the octree is building
|
|
||||||
std::vector<Vector3> positions = mass_springs.positions;
|
|
||||||
|
|
||||||
// Start building the octree for the next physics update.
|
|
||||||
// Move the snapshot into the closure so it doesn't get captured by reference (don't use [&])
|
|
||||||
octree_future = octree_thread.submit_task([&tree_buffer, &thread_pool, positions = std::move(positions)]()
|
|
||||||
{
|
|
||||||
octree::build_octree_morton(tree_buffer, positions, thread_pool);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Rebuild the tree synchronously if we changed the number of masses to not use
|
|
||||||
// an empty tree from the last frame in the frame where the graph was generated
|
|
||||||
if (last_mass_count != mass_springs.positions.size()) {
|
|
||||||
traceln("Rebuilding octree synchronously because graph size changed");
|
|
||||||
octree::build_octree_morton(mass_springs.tree, mass_springs.positions, thread_pool);
|
|
||||||
last_mass_count = mass_springs.positions.size();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
octree::build_octree_morton(mass_springs.tree, mass_springs.positions, thread_pool);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
mass_springs.clear_forces();
|
mass_springs.clear_forces();
|
||||||
mass_springs.calculate_spring_forces(thread_pool);
|
mass_springs.calculate_spring_forces();
|
||||||
mass_springs.calculate_repulsion_forces(thread_pool);
|
mass_springs.calculate_repulsion_forces();
|
||||||
mass_springs.update(TIMESTEP * SIM_SPEED, thread_pool);
|
mass_springs.verlet_update(TIMESTEP * SIM_SPEED);
|
||||||
|
|
||||||
// This is only helpful if we're drawing a grid at (0, 0, 0). Otherwise, it's just
|
// This is only helpful if we're drawing a grid at (0, 0, 0). Otherwise, it's just
|
||||||
// expensive and yields no benefit since we can lock the camera to the center of mass
|
// expensive and yields no benefit since we can lock the camera to the center of mass
|
||||||
// cheaply.
|
// cheaply. mass_springs.center_masses();
|
||||||
// mass_springs.center_masses(thread_pool);
|
|
||||||
|
|
||||||
++loop_iterations;
|
++loop_iterations;
|
||||||
physics_accumulator -= std::chrono::duration<double>(TIMESTEP);
|
physics_accumulator -= std::chrono::duration<double>(TIMESTEP);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ASYNC_OCTREE
|
|
||||||
// Wait for the octree to be built
|
|
||||||
if (octree_future.valid()) {
|
|
||||||
octree_future.wait();
|
|
||||||
octree_future = std::future<void>{};
|
|
||||||
std::swap(mass_springs.tree, tree_buffer);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Publish the positions for the renderer (copy)
|
// Publish the positions for the renderer (copy)
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
FrameMarkStart("PhysicsThreadProduceLock");
|
FrameMarkStart("PhysicsThreadProduceLock");
|
||||||
@ -138,11 +96,10 @@ auto cpu_layout_engine::physics_thread(physics_state& state, const threadpool th
|
|||||||
#else
|
#else
|
||||||
std::unique_lock<std::mutex> lock(state.data_mtx);
|
std::unique_lock<std::mutex> lock(state.data_mtx);
|
||||||
#endif
|
#endif
|
||||||
state.data_consumed_cnd.wait(lock,
|
state.data_consumed_cnd.wait(lock, [&]
|
||||||
[&]
|
{
|
||||||
{
|
return state.data_consumed || !state.running.load();
|
||||||
return state.data_consumed || !state.running.load();
|
});
|
||||||
});
|
|
||||||
if (!state.running.load()) {
|
if (!state.running.load()) {
|
||||||
// Running turned false while we were waiting for the condition
|
// Running turned false while we were waiting for the condition
|
||||||
break;
|
break;
|
||||||
@ -154,19 +111,19 @@ auto cpu_layout_engine::physics_thread(physics_state& state, const threadpool th
|
|||||||
loop_iterations = 0;
|
loop_iterations = 0;
|
||||||
ups_accumulator = std::chrono::duration<double>(0);
|
ups_accumulator = std::chrono::duration<double>(0);
|
||||||
}
|
}
|
||||||
if (mass_springs.tree.empty()) {
|
if (mass_springs.tree.nodes.empty()) {
|
||||||
state.mass_center = Vector3Zero();
|
state.mass_center = Vector3Zero();
|
||||||
} else {
|
} else {
|
||||||
state.mass_center = mass_springs.tree.root().mass_center;
|
state.mass_center = mass_springs.tree.nodes.at(0).mass_center;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.masses.clear();
|
state.masses.clear();
|
||||||
state.masses.reserve(mass_springs.positions.size());
|
state.masses.reserve(mass_springs.masses.size());
|
||||||
for (const Vector3& pos : mass_springs.positions) {
|
for (const auto& mass : mass_springs.masses) {
|
||||||
state.masses.emplace_back(pos);
|
state.masses.emplace_back(mass.position);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.mass_count = mass_springs.positions.size();
|
state.mass_count = mass_springs.masses.size();
|
||||||
state.spring_count = mass_springs.springs.size();
|
state.spring_count = mass_springs.springs.size();
|
||||||
|
|
||||||
state.data_ready = true;
|
state.data_ready = true;
|
||||||
@ -174,27 +131,15 @@ auto cpu_layout_engine::physics_thread(physics_state& state, const threadpool th
|
|||||||
}
|
}
|
||||||
// Notify the rendering thread that new data is available
|
// Notify the rendering thread that new data is available
|
||||||
state.data_ready_cnd.notify_all();
|
state.data_ready_cnd.notify_all();
|
||||||
|
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
FrameMarkEnd("PhysicsThreadProduceLock");
|
FrameMarkEnd("PhysicsThreadProduceLock");
|
||||||
|
|
||||||
FrameMarkEnd("PhysicsThread");
|
FrameMarkEnd("PhysicsThread");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto cpu_layout_engine::clear_cmd() -> void
|
auto threaded_physics::add_mass_cmd() -> void
|
||||||
{
|
|
||||||
{
|
|
||||||
#ifdef TRACY
|
|
||||||
std::lock_guard<LockableBase(std::mutex)> lock(state.command_mtx);
|
|
||||||
#else
|
|
||||||
std::lock_guard<std::mutex> lock(state.command_mtx);
|
|
||||||
#endif
|
|
||||||
state.pending_commands.emplace(clear_graph{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cpu_layout_engine::add_mass_cmd() -> void
|
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
@ -206,7 +151,7 @@ auto cpu_layout_engine::add_mass_cmd() -> void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto cpu_layout_engine::add_spring_cmd(const size_t a, const size_t b) -> void
|
auto threaded_physics::add_spring_cmd(const size_t a, const size_t b) -> void
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
@ -218,7 +163,20 @@ auto cpu_layout_engine::add_spring_cmd(const size_t a, const size_t b) -> void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto cpu_layout_engine::add_mass_springs_cmd(const size_t num_masses, const std::vector<spring>& springs) -> void
|
auto threaded_physics::clear_cmd() -> void
|
||||||
|
{
|
||||||
|
{
|
||||||
|
#ifdef TRACY
|
||||||
|
std::lock_guard<LockableBase(std::mutex)> lock(state.command_mtx);
|
||||||
|
#else
|
||||||
|
std::lock_guard<std::mutex> lock(state.command_mtx);
|
||||||
|
#endif
|
||||||
|
state.pending_commands.emplace(clear_graph{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto threaded_physics::add_mass_springs_cmd(const size_t num_masses,
|
||||||
|
const std::vector<std::pair<size_t, size_t>>& springs) -> void
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
#ifdef TRACY
|
#ifdef TRACY
|
||||||
@ -7,12 +7,12 @@
|
|||||||
#define RAYGUI_IMPLEMENTATION
|
#define RAYGUI_IMPLEMENTATION
|
||||||
#include <raygui.h>
|
#include <raygui.h>
|
||||||
|
|
||||||
auto user_interface::grid::update_bounds(const int _x,
|
#ifdef TRACY
|
||||||
const int _y,
|
#include <tracy/Tracy.hpp>
|
||||||
const int _width,
|
#endif
|
||||||
const int _height,
|
|
||||||
const int _columns,
|
auto user_interface::grid::update_bounds(const int _x, const int _y, const int _width, const int _height,
|
||||||
const int _rows) -> void
|
const int _columns, const int _rows) -> void
|
||||||
{
|
{
|
||||||
x = _x;
|
x = _x;
|
||||||
y = _y;
|
y = _y;
|
||||||
@ -55,9 +55,7 @@ auto user_interface::grid::bounds(const int _x, const int _y, const int _width,
|
|||||||
const int cell_width = (width - padding) / columns;
|
const int cell_width = (width - padding) / columns;
|
||||||
const int cell_height = (height - padding) / rows;
|
const int cell_height = (height - padding) / rows;
|
||||||
|
|
||||||
return Rectangle(x + _x * cell_width + padding,
|
return Rectangle(x + _x * cell_width + padding, y + _y * cell_height + padding, _width * cell_width - padding,
|
||||||
y + _y * cell_height + padding,
|
|
||||||
_width * cell_width - padding,
|
|
||||||
_height * cell_height - padding);
|
_height * cell_height - padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,9 +69,7 @@ auto user_interface::grid::square_bounds() const -> Rectangle
|
|||||||
return bounds;
|
return bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto user_interface::grid::square_bounds(const int _x,
|
auto user_interface::grid::square_bounds(const int _x, const int _y, const int _width,
|
||||||
const int _y,
|
|
||||||
const int _width,
|
|
||||||
const int _height) const -> Rectangle
|
const int _height) const -> Rectangle
|
||||||
{
|
{
|
||||||
// Assumes each cell is square, so either width or height are not completely
|
// Assumes each cell is square, so either width or height are not completely
|
||||||
@ -92,10 +88,8 @@ auto user_interface::grid::square_bounds(const int _x,
|
|||||||
const int x_offset = (width - grid_width) / 2;
|
const int x_offset = (width - grid_width) / 2;
|
||||||
const int y_offset = (height - grid_height) / 2;
|
const int y_offset = (height - grid_height) / 2;
|
||||||
|
|
||||||
return Rectangle(x_offset + _x * (cell_size + padding) + padding,
|
return Rectangle(x_offset + _x * (cell_size + padding) + padding, y_offset + _y * (cell_size + padding) + padding,
|
||||||
y_offset + _y * (cell_size + padding) + padding,
|
_width * cell_size + padding * (_width - 1), _height * cell_size + padding * (_height - 1));
|
||||||
_width * cell_size + padding * (_width - 1),
|
|
||||||
_height * cell_size + padding * (_height - 1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto user_interface::init() -> void
|
auto user_interface::init() -> void
|
||||||
@ -156,26 +150,16 @@ auto user_interface::get_default_style() -> default_style
|
|||||||
// access...
|
// access...
|
||||||
return {
|
return {
|
||||||
{
|
{
|
||||||
GuiGetStyle(DEFAULT, BORDER_COLOR_NORMAL),
|
GuiGetStyle(DEFAULT, BORDER_COLOR_NORMAL), GuiGetStyle(DEFAULT, BASE_COLOR_NORMAL),
|
||||||
GuiGetStyle(DEFAULT, BASE_COLOR_NORMAL),
|
GuiGetStyle(DEFAULT, TEXT_COLOR_NORMAL), GuiGetStyle(DEFAULT, BORDER_COLOR_FOCUSED),
|
||||||
GuiGetStyle(DEFAULT, TEXT_COLOR_NORMAL),
|
GuiGetStyle(DEFAULT, BASE_COLOR_FOCUSED), GuiGetStyle(DEFAULT, TEXT_COLOR_FOCUSED),
|
||||||
GuiGetStyle(DEFAULT, BORDER_COLOR_FOCUSED),
|
GuiGetStyle(DEFAULT, BORDER_COLOR_PRESSED), GuiGetStyle(DEFAULT, BASE_COLOR_PRESSED),
|
||||||
GuiGetStyle(DEFAULT, BASE_COLOR_FOCUSED),
|
GuiGetStyle(DEFAULT, TEXT_COLOR_PRESSED), GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED),
|
||||||
GuiGetStyle(DEFAULT, TEXT_COLOR_FOCUSED),
|
GuiGetStyle(DEFAULT, BASE_COLOR_DISABLED), GuiGetStyle(DEFAULT, TEXT_COLOR_DISABLED)
|
||||||
GuiGetStyle(DEFAULT, BORDER_COLOR_PRESSED),
|
|
||||||
GuiGetStyle(DEFAULT, BASE_COLOR_PRESSED),
|
|
||||||
GuiGetStyle(DEFAULT, TEXT_COLOR_PRESSED),
|
|
||||||
GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED),
|
|
||||||
GuiGetStyle(DEFAULT, BASE_COLOR_DISABLED),
|
|
||||||
GuiGetStyle(DEFAULT, TEXT_COLOR_DISABLED)
|
|
||||||
},
|
},
|
||||||
GuiGetStyle(DEFAULT, BACKGROUND_COLOR),
|
GuiGetStyle(DEFAULT, BACKGROUND_COLOR), GuiGetStyle(DEFAULT, LINE_COLOR), GuiGetStyle(DEFAULT, TEXT_SIZE),
|
||||||
GuiGetStyle(DEFAULT, LINE_COLOR),
|
GuiGetStyle(DEFAULT, TEXT_SPACING), GuiGetStyle(DEFAULT, TEXT_LINE_SPACING),
|
||||||
GuiGetStyle(DEFAULT, TEXT_SIZE),
|
GuiGetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL), GuiGetStyle(DEFAULT, TEXT_WRAP_MODE)
|
||||||
GuiGetStyle(DEFAULT, TEXT_SPACING),
|
|
||||||
GuiGetStyle(DEFAULT, TEXT_LINE_SPACING),
|
|
||||||
GuiGetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL),
|
|
||||||
GuiGetStyle(DEFAULT, TEXT_WRAP_MODE)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,21 +195,14 @@ auto user_interface::get_component_style(const int component) -> component_style
|
|||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
{
|
{
|
||||||
GuiGetStyle(component, BORDER_COLOR_NORMAL),
|
GuiGetStyle(component, BORDER_COLOR_NORMAL), GuiGetStyle(component, BASE_COLOR_NORMAL),
|
||||||
GuiGetStyle(component, BASE_COLOR_NORMAL),
|
GuiGetStyle(component, TEXT_COLOR_NORMAL), GuiGetStyle(component, BORDER_COLOR_FOCUSED),
|
||||||
GuiGetStyle(component, TEXT_COLOR_NORMAL),
|
GuiGetStyle(component, BASE_COLOR_FOCUSED), GuiGetStyle(component, TEXT_COLOR_FOCUSED),
|
||||||
GuiGetStyle(component, BORDER_COLOR_FOCUSED),
|
GuiGetStyle(component, BORDER_COLOR_PRESSED), GuiGetStyle(component, BASE_COLOR_PRESSED),
|
||||||
GuiGetStyle(component, BASE_COLOR_FOCUSED),
|
GuiGetStyle(component, TEXT_COLOR_PRESSED), GuiGetStyle(component, BORDER_COLOR_DISABLED),
|
||||||
GuiGetStyle(component, TEXT_COLOR_FOCUSED),
|
GuiGetStyle(component, BASE_COLOR_DISABLED), GuiGetStyle(component, TEXT_COLOR_DISABLED)
|
||||||
GuiGetStyle(component, BORDER_COLOR_PRESSED),
|
|
||||||
GuiGetStyle(component, BASE_COLOR_PRESSED),
|
|
||||||
GuiGetStyle(component, TEXT_COLOR_PRESSED),
|
|
||||||
GuiGetStyle(component, BORDER_COLOR_DISABLED),
|
|
||||||
GuiGetStyle(component, BASE_COLOR_DISABLED),
|
|
||||||
GuiGetStyle(component, TEXT_COLOR_DISABLED)
|
|
||||||
},
|
},
|
||||||
GuiGetStyle(component, BORDER_WIDTH),
|
GuiGetStyle(component, BORDER_WIDTH), GuiGetStyle(component, TEXT_PADDING),
|
||||||
GuiGetStyle(component, TEXT_PADDING),
|
|
||||||
GuiGetStyle(component, TEXT_ALIGNMENT)
|
GuiGetStyle(component, TEXT_ALIGNMENT)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -256,16 +233,11 @@ auto user_interface::set_component_style(const int component, const component_st
|
|||||||
auto user_interface::popup_bounds() -> Rectangle
|
auto user_interface::popup_bounds() -> Rectangle
|
||||||
{
|
{
|
||||||
return Rectangle(static_cast<float>(GetScreenWidth()) / 2.0f - POPUP_WIDTH / 2.0f,
|
return Rectangle(static_cast<float>(GetScreenWidth()) / 2.0f - POPUP_WIDTH / 2.0f,
|
||||||
static_cast<float>(GetScreenHeight()) / 2.0f - POPUP_HEIGHT / 2.0f,
|
static_cast<float>(GetScreenHeight()) / 2.0f - POPUP_HEIGHT / 2.0f, POPUP_WIDTH, POPUP_HEIGHT);
|
||||||
POPUP_WIDTH,
|
|
||||||
POPUP_HEIGHT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto user_interface::draw_button(const Rectangle bounds,
|
auto user_interface::draw_button(const Rectangle bounds, const std::string& label, const Color color,
|
||||||
const std::string& label,
|
const bool enabled, const int font_size) const -> int
|
||||||
const Color color,
|
|
||||||
const bool enabled,
|
|
||||||
const int font_size) const -> int
|
|
||||||
{
|
{
|
||||||
// Save original styling
|
// Save original styling
|
||||||
const default_style original_default = get_default_style();
|
const default_style original_default = get_default_style();
|
||||||
@ -295,25 +267,16 @@ auto user_interface::draw_button(const Rectangle bounds,
|
|||||||
return pressed;
|
return pressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto user_interface::draw_menu_button(const int x,
|
auto user_interface::draw_menu_button(const int x, const int y, const int width, const int height,
|
||||||
const int y,
|
const std::string& label, const Color color, const bool enabled,
|
||||||
const int width,
|
|
||||||
const int height,
|
|
||||||
const std::string& label,
|
|
||||||
const Color color,
|
|
||||||
const bool enabled,
|
|
||||||
const int font_size) const -> int
|
const int font_size) const -> int
|
||||||
{
|
{
|
||||||
const Rectangle bounds = menu_grid.bounds(x, y, width, height);
|
const Rectangle bounds = menu_grid.bounds(x, y, width, height);
|
||||||
return draw_button(bounds, label, color, enabled, font_size);
|
return draw_button(bounds, label, color, enabled, font_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto user_interface::draw_toggle_slider(const Rectangle bounds,
|
auto user_interface::draw_toggle_slider(const Rectangle bounds, const std::string& off_label,
|
||||||
const std::string& off_label,
|
const std::string& on_label, int* active, Color color, bool enabled,
|
||||||
const std::string& on_label,
|
|
||||||
int* active,
|
|
||||||
Color color,
|
|
||||||
bool enabled,
|
|
||||||
int font_size) const -> int
|
int font_size) const -> int
|
||||||
{
|
{
|
||||||
// Save original styling
|
// Save original styling
|
||||||
@ -349,29 +312,16 @@ auto user_interface::draw_toggle_slider(const Rectangle bounds,
|
|||||||
return pressed;
|
return pressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto user_interface::draw_menu_toggle_slider(const int x,
|
auto user_interface::draw_menu_toggle_slider(const int x, const int y, const int width, const int height,
|
||||||
const int y,
|
const std::string& off_label, const std::string& on_label, int* active,
|
||||||
const int width,
|
const Color color, const bool enabled, const int font_size) const -> int
|
||||||
const int height,
|
|
||||||
const std::string& off_label,
|
|
||||||
const std::string& on_label,
|
|
||||||
int* active,
|
|
||||||
const Color color,
|
|
||||||
const bool enabled,
|
|
||||||
const int font_size) const -> int
|
|
||||||
{
|
{
|
||||||
const Rectangle bounds = menu_grid.bounds(x, y, width, height);
|
const Rectangle bounds = menu_grid.bounds(x, y, width, height);
|
||||||
return draw_toggle_slider(bounds, off_label, on_label, active, color, enabled, font_size);
|
return draw_toggle_slider(bounds, off_label, on_label, active, color, enabled, font_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto user_interface::draw_spinner(Rectangle bounds,
|
auto user_interface::draw_spinner(Rectangle bounds, const std::string& label, int* value, int min, int max, Color color,
|
||||||
const std::string& label,
|
bool enabled, int font_size) const -> int
|
||||||
int* value,
|
|
||||||
int min,
|
|
||||||
int max,
|
|
||||||
Color color,
|
|
||||||
bool enabled,
|
|
||||||
int font_size) const -> int
|
|
||||||
{
|
{
|
||||||
// Save original styling
|
// Save original styling
|
||||||
const default_style original_default = get_default_style();
|
const default_style original_default = get_default_style();
|
||||||
@ -406,26 +356,15 @@ auto user_interface::draw_spinner(Rectangle bounds,
|
|||||||
return pressed;
|
return pressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto user_interface::draw_menu_spinner(const int x,
|
auto user_interface::draw_menu_spinner(const int x, const int y, const int width, const int height,
|
||||||
const int y,
|
const std::string& label, int* value, const int min, const int max,
|
||||||
const int width,
|
const Color color, const bool enabled, const int font_size) const -> int
|
||||||
const int height,
|
|
||||||
const std::string& label,
|
|
||||||
int* value,
|
|
||||||
const int min,
|
|
||||||
const int max,
|
|
||||||
const Color color,
|
|
||||||
const bool enabled,
|
|
||||||
const int font_size) const -> int
|
|
||||||
{
|
{
|
||||||
const Rectangle bounds = menu_grid.bounds(x, y, width, height);
|
const Rectangle bounds = menu_grid.bounds(x, y, width, height);
|
||||||
return draw_spinner(bounds, label, value, min, max, color, enabled, font_size);
|
return draw_spinner(bounds, label, value, min, max, color, enabled, font_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto user_interface::draw_label(const Rectangle bounds,
|
auto user_interface::draw_label(const Rectangle bounds, const std::string& text, const Color color, const bool enabled,
|
||||||
const std::string& text,
|
|
||||||
const Color color,
|
|
||||||
const bool enabled,
|
|
||||||
const int font_size) const -> int
|
const int font_size) const -> int
|
||||||
{
|
{
|
||||||
// Save original styling
|
// Save original styling
|
||||||
@ -456,11 +395,7 @@ auto user_interface::draw_label(const Rectangle bounds,
|
|||||||
return pressed;
|
return pressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto user_interface::draw_board_block(const int x,
|
auto user_interface::draw_board_block(const int x, const int y, const int width, const int height, const Color color,
|
||||||
const int y,
|
|
||||||
const int width,
|
|
||||||
const int height,
|
|
||||||
const Color color,
|
|
||||||
const bool enabled) const -> bool
|
const bool enabled) const -> bool
|
||||||
{
|
{
|
||||||
component_style s = get_component_style(BUTTON);
|
component_style s = get_component_style(BUTTON);
|
||||||
@ -469,7 +404,7 @@ auto user_interface::draw_board_block(const int x,
|
|||||||
const Rectangle bounds = board_grid.square_bounds(x, y, width, height);
|
const Rectangle bounds = board_grid.square_bounds(x, y, width, height);
|
||||||
|
|
||||||
const bool focused = CheckCollisionPointRec(input.mouse - Vector2(0, MENU_HEIGHT), bounds);
|
const bool focused = CheckCollisionPointRec(input.mouse - Vector2(0, MENU_HEIGHT), bounds);
|
||||||
const bool pressed = block(x, y, width, height, false).covers(input.sel_x, input.sel_y);
|
const bool pressed = puzzle::block(x, y, width, height, false).covers(input.sel_x, input.sel_y);
|
||||||
|
|
||||||
// Background to make faded colors work
|
// Background to make faded colors work
|
||||||
DrawRectangleRec(bounds, RAYWHITE);
|
DrawRectangleRec(bounds, RAYWHITE);
|
||||||
@ -507,15 +442,7 @@ auto user_interface::window_open() const -> bool
|
|||||||
auto user_interface::draw_menu_header(const Color color) const -> void
|
auto user_interface::draw_menu_header(const Color color) const -> void
|
||||||
{
|
{
|
||||||
int preset = static_cast<int>(state.get_current_preset());
|
int preset = static_cast<int>(state.get_current_preset());
|
||||||
draw_menu_spinner(0,
|
draw_menu_spinner(0, 0, 1, 1, "Preset: ", &preset, -1, static_cast<int>(state.get_preset_count()), color,
|
||||||
0,
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
"Preset: ",
|
|
||||||
&preset,
|
|
||||||
-1,
|
|
||||||
static_cast<int>(state.get_preset_count()),
|
|
||||||
color,
|
|
||||||
!input.editing);
|
!input.editing);
|
||||||
if (preset > static_cast<int>(state.get_current_preset())) {
|
if (preset > static_cast<int>(state.get_current_preset())) {
|
||||||
input.load_next_preset();
|
input.load_next_preset();
|
||||||
@ -523,17 +450,11 @@ auto user_interface::draw_menu_header(const Color color) const -> void
|
|||||||
input.load_previous_preset();
|
input.load_previous_preset();
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_menu_button(1,
|
draw_menu_button(1, 0, 1, 1, std::format("{}: {}/{} Blocks",
|
||||||
0,
|
state.was_edited()
|
||||||
1,
|
? "Modified"
|
||||||
1,
|
: std::format("\"{}\"", state.get_current_preset_comment().substr(2)),
|
||||||
std::format("{}: {}/{} Blocks",
|
state.get_current_state().block_count(), puzzle::MAX_BLOCKS), color);
|
||||||
state.was_edited()
|
|
||||||
? "Modified"
|
|
||||||
: std::format("\"{}\"", state.get_current_preset_comment().substr(2)),
|
|
||||||
state.get_current_state().block_count(),
|
|
||||||
puzzle::MAX_BLOCKS),
|
|
||||||
color);
|
|
||||||
|
|
||||||
int editing = input.editing;
|
int editing = input.editing;
|
||||||
draw_menu_toggle_slider(2, 0, 1, 1, "Puzzle Mode (Tab)", "Edit Mode (Tab)", &editing, color);
|
draw_menu_toggle_slider(2, 0, 1, 1, "Puzzle Mode (Tab)", "Edit Mode (Tab)", &editing, color);
|
||||||
@ -544,22 +465,13 @@ auto user_interface::draw_menu_header(const Color color) const -> void
|
|||||||
|
|
||||||
auto user_interface::draw_graph_info(const Color color) const -> void
|
auto user_interface::draw_graph_info(const Color color) const -> void
|
||||||
{
|
{
|
||||||
draw_menu_button(0,
|
draw_menu_button(0, 1, 1, 1, std::format("Found {} States ({} Winning)", state.get_state_count(),
|
||||||
1,
|
state.get_target_count()), color);
|
||||||
1,
|
|
||||||
1,
|
|
||||||
std::format("Found {} States ({} Winning)", state.get_state_count(), state.get_target_count()),
|
|
||||||
color);
|
|
||||||
|
|
||||||
draw_menu_button(1, 1, 1, 1, std::format("Found {} Transitions", state.get_link_count()), color);
|
draw_menu_button(1, 1, 1, 1, std::format("Found {} Transitions", state.get_link_count()), color);
|
||||||
|
|
||||||
draw_menu_button(2,
|
draw_menu_button(2, 1, 1, 1, std::format("{} Moves to Nearest Solution",
|
||||||
1,
|
state.get_path_length() > 0 ? state.get_path_length() - 1 : 0), color);
|
||||||
1,
|
|
||||||
1,
|
|
||||||
std::format("{} Moves to Nearest Solution",
|
|
||||||
state.get_path_length() > 0 ? state.get_path_length() - 1 : 0),
|
|
||||||
color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto user_interface::draw_graph_controls(const Color color) const -> void
|
auto user_interface::draw_graph_controls(const Color color) const -> void
|
||||||
@ -596,14 +508,7 @@ auto user_interface::draw_camera_controls(const Color color) const -> void
|
|||||||
}
|
}
|
||||||
|
|
||||||
int lock_camera_mass_center = input.camera_mass_center_lock;
|
int lock_camera_mass_center = input.camera_mass_center_lock;
|
||||||
draw_menu_toggle_slider(1,
|
draw_menu_toggle_slider(1, 3, 1, 1, "Current Block (U)", "Graph Center (U)", &lock_camera_mass_center, color,
|
||||||
3,
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
"Current Block (U)",
|
|
||||||
"Graph Center (U)",
|
|
||||||
&lock_camera_mass_center,
|
|
||||||
color,
|
|
||||||
input.camera_lock);
|
input.camera_lock);
|
||||||
if (lock_camera_mass_center != input.camera_mass_center_lock) {
|
if (lock_camera_mass_center != input.camera_mass_center_lock) {
|
||||||
input.toggle_camera_mass_center_lock();
|
input.toggle_camera_mass_center_lock();
|
||||||
@ -636,12 +541,8 @@ auto user_interface::draw_puzzle_controls(const Color color) const -> void
|
|||||||
};
|
};
|
||||||
|
|
||||||
const int visits = state.get_current_visits();
|
const int visits = state.get_current_visits();
|
||||||
draw_menu_button(0,
|
draw_menu_button(0, 4, 1, 1, std::format("{} Moves ({}{} Time at this State)", state.get_total_moves(), visits,
|
||||||
4,
|
nth(visits)), color);
|
||||||
1,
|
|
||||||
1,
|
|
||||||
std::format("{} Moves ({}{} Time at this State)", state.get_total_moves(), visits, nth(visits)),
|
|
||||||
color);
|
|
||||||
|
|
||||||
if (draw_menu_button(1, 4, 1, 1, "Make Optimal Move (Space)", color, state.has_distances())) {
|
if (draw_menu_button(1, 4, 1, 1, "Make Optimal Move (Space)", color, state.has_distances())) {
|
||||||
input.goto_optimal_next_state();
|
input.goto_optimal_next_state();
|
||||||
@ -659,12 +560,7 @@ auto user_interface::draw_puzzle_controls(const Color color) const -> void
|
|||||||
input.goto_most_distant_state();
|
input.goto_most_distant_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (draw_menu_button(2,
|
if (draw_menu_button(2, 5, 1, 1, "Go to Starting State (R)", color,
|
||||||
5,
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
"Go to Starting State (R)",
|
|
||||||
color,
|
|
||||||
state.get_current_index() != state.get_starting_index())) {
|
state.get_current_index() != state.get_starting_index())) {
|
||||||
input.goto_starting_state();
|
input.goto_starting_state();
|
||||||
}
|
}
|
||||||
@ -742,19 +638,14 @@ auto user_interface::draw_save_preset_popup() -> void
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns the pressed button index
|
// Returns the pressed button index
|
||||||
const int button = GuiTextInputBox(popup_bounds(),
|
const int button = GuiTextInputBox(popup_bounds(), "Save as Preset", "Enter Preset Name", "Ok;Cancel",
|
||||||
"Save as Preset",
|
preset_name.data(), 255, nullptr);
|
||||||
"Enter Preset Name",
|
|
||||||
"Ok;Cancel",
|
|
||||||
preset_comment.data(),
|
|
||||||
255,
|
|
||||||
nullptr);
|
|
||||||
if (button == 1) {
|
if (button == 1) {
|
||||||
state.save_current_to_preset_file(preset_comment.data());
|
state.append_preset_file(preset_name.data());
|
||||||
}
|
}
|
||||||
if (button == 0 || button == 1 || button == 2) {
|
if (button == 0 || button == 1 || button == 2) {
|
||||||
save_window = false;
|
save_window = false;
|
||||||
TextCopy(preset_comment.data(), "\0");
|
TextCopy(preset_name.data(), "\0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -811,11 +702,7 @@ auto user_interface::draw_puzzle_board() -> void
|
|||||||
{
|
{
|
||||||
const puzzle& current = state.get_current_state();
|
const puzzle& current = state.get_current_state();
|
||||||
|
|
||||||
board_grid.update_bounds(0,
|
board_grid.update_bounds(0, MENU_HEIGHT, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT, current.get_width(),
|
||||||
MENU_HEIGHT,
|
|
||||||
GetScreenWidth() / 2,
|
|
||||||
GetScreenHeight() - MENU_HEIGHT,
|
|
||||||
current.get_width(),
|
|
||||||
current.get_height());
|
current.get_height());
|
||||||
|
|
||||||
// Draw outer border
|
// Draw outer border
|
||||||
@ -823,22 +710,18 @@ auto user_interface::draw_puzzle_board() -> void
|
|||||||
DrawRectangleRec(bounds, current.goal_reached() ? BOARD_COLOR_WON : BOARD_COLOR_RESTRICTED);
|
DrawRectangleRec(bounds, current.goal_reached() ? BOARD_COLOR_WON : BOARD_COLOR_RESTRICTED);
|
||||||
|
|
||||||
// Draw inner borders
|
// Draw inner borders
|
||||||
DrawRectangle(bounds.x + BOARD_PADDING,
|
DrawRectangle(bounds.x + BOARD_PADDING, bounds.y + BOARD_PADDING, bounds.width - 2 * BOARD_PADDING,
|
||||||
bounds.y + BOARD_PADDING,
|
|
||||||
bounds.width - 2 * BOARD_PADDING,
|
|
||||||
bounds.height - 2 * BOARD_PADDING,
|
bounds.height - 2 * BOARD_PADDING,
|
||||||
current.get_restricted() ? BOARD_COLOR_RESTRICTED : BOARD_COLOR_FREE);
|
current.get_restricted() ? BOARD_COLOR_RESTRICTED : BOARD_COLOR_FREE);
|
||||||
|
|
||||||
// Draw target opening
|
// Draw target opening
|
||||||
// TODO: Only draw single direction (in corner) if restricted (use target block principal
|
// TODO: Only draw single direction (in corner) if restricted (use target block principal
|
||||||
// direction)
|
// direction)
|
||||||
const std::optional<block> target_block = current.try_get_target_block();
|
const std::optional<puzzle::block> target_block = current.try_get_target_block();
|
||||||
const int target_x = current.get_goal_x();
|
const int target_x = current.get_goal_x();
|
||||||
const int target_y = current.get_goal_y();
|
const int target_y = current.get_goal_y();
|
||||||
if (current.get_goal() && target_block) {
|
if (current.get_goal() && target_block) {
|
||||||
auto [x, y, width, height] = board_grid.square_bounds(target_x,
|
auto [x, y, width, height] = board_grid.square_bounds(target_x, target_y, target_block->get_width(),
|
||||||
target_y,
|
|
||||||
target_block->get_width(),
|
|
||||||
target_block->get_height());
|
target_block->get_height());
|
||||||
|
|
||||||
const Color opening_color = Fade(current.goal_reached() ? BOARD_COLOR_WON : BOARD_COLOR_RESTRICTED, 0.3);
|
const Color opening_color = Fade(current.goal_reached() ? BOARD_COLOR_WON : BOARD_COLOR_RESTRICTED, 0.3);
|
||||||
@ -885,7 +768,7 @@ auto user_interface::draw_puzzle_board() -> void
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw blocks
|
// Draw blocks
|
||||||
for (const block b : current.block_view()) {
|
for (const puzzle::block b : current.block_view()) {
|
||||||
Color c = BLOCK_COLOR;
|
Color c = BLOCK_COLOR;
|
||||||
if (b.get_target()) {
|
if (b.get_target()) {
|
||||||
c = TARGET_BLOCK_COLOR;
|
c = TARGET_BLOCK_COLOR;
|
||||||
@ -903,22 +786,16 @@ auto user_interface::draw_puzzle_board() -> void
|
|||||||
if (current.covers(input.block_add_x, input.block_add_y) && input.hov_x >= input.block_add_x && input.hov_y >=
|
if (current.covers(input.block_add_x, input.block_add_y) && input.hov_x >= input.block_add_x && input.hov_y >=
|
||||||
input.block_add_y) {
|
input.block_add_y) {
|
||||||
bool collides = false;
|
bool collides = false;
|
||||||
for (const block b : current.block_view()) {
|
for (const puzzle::block b : current.block_view()) {
|
||||||
if (b.collides(block(input.block_add_x,
|
if (b.collides(puzzle::block(input.block_add_x, input.block_add_y, input.hov_x - input.block_add_x + 1,
|
||||||
input.block_add_y,
|
input.hov_y - input.block_add_y + 1, false))) {
|
||||||
input.hov_x - input.block_add_x + 1,
|
|
||||||
input.hov_y - input.block_add_y + 1,
|
|
||||||
false))) {
|
|
||||||
collides = true;
|
collides = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!collides) {
|
if (!collides) {
|
||||||
draw_board_block(input.block_add_x,
|
draw_board_block(input.block_add_x, input.block_add_y, input.hov_x - input.block_add_x + 1,
|
||||||
input.block_add_y,
|
input.hov_y - input.block_add_y + 1, PURPLE);
|
||||||
input.hov_x - input.block_add_x + 1,
|
|
||||||
input.hov_y - input.block_add_y + 1,
|
|
||||||
PURPLE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -930,8 +807,7 @@ auto user_interface::draw_puzzle_board() -> void
|
|||||||
// Draw goal boundaries when editing
|
// Draw goal boundaries when editing
|
||||||
if (input.editing && current.get_goal() && target_block) {
|
if (input.editing && current.get_goal() && target_block) {
|
||||||
DrawRectangleLinesEx(
|
DrawRectangleLinesEx(
|
||||||
board_grid.square_bounds(target_x, target_y, target_block->get_width(), target_block->get_height()),
|
board_grid.square_bounds(target_x, target_y, target_block->get_width(), target_block->get_height()), 2.0,
|
||||||
2.0,
|
|
||||||
TARGET_BLOCK_COLOR);
|
TARGET_BLOCK_COLOR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,74 +0,0 @@
|
|||||||
// ReSharper disable CppLocalVariableMayBeConst
|
|
||||||
#include "puzzle.hpp"
|
|
||||||
|
|
||||||
#include <random>
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
|
||||||
|
|
||||||
TEST_CASE("bitmap_is_full all bits set", "[puzzle][board]")
|
|
||||||
{
|
|
||||||
puzzle p1(5, 5);
|
|
||||||
puzzle p2(3, 4);
|
|
||||||
puzzle p3(5, 4);
|
|
||||||
puzzle p4(3, 7);
|
|
||||||
u64 bitmap = -1;
|
|
||||||
|
|
||||||
REQUIRE(p1.bitmap_is_full(bitmap));
|
|
||||||
REQUIRE(p2.bitmap_is_full(bitmap));
|
|
||||||
REQUIRE(p3.bitmap_is_full(bitmap));
|
|
||||||
REQUIRE(p4.bitmap_is_full(bitmap));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("bitmap_is_full no bits set", "[puzzle][board]")
|
|
||||||
{
|
|
||||||
puzzle p1(5, 5);
|
|
||||||
puzzle p2(3, 4);
|
|
||||||
puzzle p3(5, 4);
|
|
||||||
puzzle p4(3, 7);
|
|
||||||
u64 bitmap = 0;
|
|
||||||
|
|
||||||
REQUIRE_FALSE(p1.bitmap_is_full(bitmap));
|
|
||||||
REQUIRE_FALSE(p2.bitmap_is_full(bitmap));
|
|
||||||
REQUIRE_FALSE(p3.bitmap_is_full(bitmap));
|
|
||||||
REQUIRE_FALSE(p4.bitmap_is_full(bitmap));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("bitmap_is_full necessary bits set", "[puzzle][board]")
|
|
||||||
{
|
|
||||||
puzzle p1(5, 5);
|
|
||||||
puzzle p2(3, 4);
|
|
||||||
puzzle p3(5, 4);
|
|
||||||
puzzle p4(3, 7);
|
|
||||||
|
|
||||||
u64 bitmap1 = (1ull << 25) - 1; // 5 * 5
|
|
||||||
u64 bitmap2 = (1ull << 12) - 1; // 3 * 4
|
|
||||||
u64 bitmap3 = (1ull << 20) - 1; // 5 * 4
|
|
||||||
u64 bitmap4 = (1ull << 21) - 1; // 3 * 7
|
|
||||||
|
|
||||||
REQUIRE(p1.bitmap_is_full(bitmap1));
|
|
||||||
REQUIRE(p2.bitmap_is_full(bitmap2));
|
|
||||||
REQUIRE(p3.bitmap_is_full(bitmap3));
|
|
||||||
REQUIRE(p4.bitmap_is_full(bitmap4));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("bitmap_is_full necessary bits not set", "[puzzle][board]")
|
|
||||||
{
|
|
||||||
puzzle p1(5, 5);
|
|
||||||
puzzle p2(3, 4);
|
|
||||||
puzzle p3(5, 4);
|
|
||||||
puzzle p4(3, 7);
|
|
||||||
|
|
||||||
u64 bitmap1 = (1ull << 25) - 1; // 5 * 5
|
|
||||||
u64 bitmap2 = (1ull << 12) - 1; // 3 * 4
|
|
||||||
u64 bitmap3 = (1ull << 20) - 1; // 5 * 4
|
|
||||||
u64 bitmap4 = (1ull << 21) - 1; // 3 * 7
|
|
||||||
|
|
||||||
bitmap1 &= ~(1ull << 12);
|
|
||||||
bitmap2 &= ~(1ull << 6);
|
|
||||||
bitmap3 &= ~(1ull << 8);
|
|
||||||
bitmap4 &= ~(1ull << 18);
|
|
||||||
|
|
||||||
REQUIRE_FALSE(p1.bitmap_is_full(bitmap1));
|
|
||||||
REQUIRE_FALSE(p2.bitmap_is_full(bitmap2));
|
|
||||||
REQUIRE_FALSE(p3.bitmap_is_full(bitmap3));
|
|
||||||
REQUIRE_FALSE(p4.bitmap_is_full(bitmap4));
|
|
||||||
}
|
|
||||||
@ -1,266 +0,0 @@
|
|||||||
// ReSharper disable CppLocalVariableMayBeConst
|
|
||||||
#include "puzzle.hpp"
|
|
||||||
|
|
||||||
#include <random>
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
|
||||||
#include <catch2/generators/catch_generators.hpp>
|
|
||||||
|
|
||||||
static auto board_mask(const int w, const int h) -> u64
|
|
||||||
{
|
|
||||||
const int cells = w * h;
|
|
||||||
if (cells == 64) {
|
|
||||||
return ~0ULL;
|
|
||||||
}
|
|
||||||
return (1ULL << cells) - 1ULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Empty board returns (0,0)", "[puzzle][board]")
|
|
||||||
{
|
|
||||||
puzzle p(5, 5);
|
|
||||||
|
|
||||||
int x = -1, y = -1;
|
|
||||||
REQUIRE(p.bitmap_find_first_empty(0ULL, x, y));
|
|
||||||
|
|
||||||
REQUIRE(x == 0);
|
|
||||||
REQUIRE(y == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Full board detection respects width*height only", "[puzzle][board]")
|
|
||||||
{
|
|
||||||
auto [w, h] = GENERATE(std::tuple{3, 3}, std::tuple{4, 4}, std::tuple{5, 6}, std::tuple{8, 8});
|
|
||||||
|
|
||||||
puzzle p(w, h);
|
|
||||||
|
|
||||||
u64 mask = board_mask(w, h);
|
|
||||||
|
|
||||||
int x = -1, y = -1;
|
|
||||||
|
|
||||||
REQUIRE_FALSE(p.bitmap_find_first_empty(mask, x, y));
|
|
||||||
|
|
||||||
// Bits outside board should not affect fullness
|
|
||||||
REQUIRE_FALSE(p.bitmap_find_first_empty(mask | (~mask), x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Single empty cell at various positions", "[puzzle][board]")
|
|
||||||
{
|
|
||||||
auto [w, h] = GENERATE(std::tuple{3, 3}, std::tuple{4, 4}, std::tuple{5, 5}, std::tuple{8, 8});
|
|
||||||
|
|
||||||
puzzle p(w, h);
|
|
||||||
|
|
||||||
int cells = w * h;
|
|
||||||
|
|
||||||
auto empty_index = GENERATE_COPY(values<int>({ 0, cells / 2, cells - 1}));
|
|
||||||
|
|
||||||
u64 bitmap = board_mask(w, h);
|
|
||||||
bitmap &= ~(1ULL << empty_index);
|
|
||||||
|
|
||||||
int x = -1, y = -1;
|
|
||||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
|
||||||
|
|
||||||
REQUIRE(x == empty_index % w);
|
|
||||||
REQUIRE(y == empty_index / w);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Bits outside board are ignored", "[puzzle][board]")
|
|
||||||
{
|
|
||||||
puzzle p(3, 3); // 9 cells
|
|
||||||
|
|
||||||
u64 mask = board_mask(3, 3);
|
|
||||||
|
|
||||||
// Board is full
|
|
||||||
u64 bitmap = mask;
|
|
||||||
|
|
||||||
// Set extra bits outside 9 cells
|
|
||||||
bitmap |= (1ULL << 20);
|
|
||||||
bitmap |= (1ULL << 63);
|
|
||||||
|
|
||||||
int x = -1, y = -1;
|
|
||||||
REQUIRE_FALSE(p.bitmap_find_first_empty(bitmap, x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("First empty found in forward search branch", "[puzzle][branch]")
|
|
||||||
{
|
|
||||||
puzzle p(4, 4); // 16 cells
|
|
||||||
|
|
||||||
// Only MSB (within board) set
|
|
||||||
u64 bitmap = (1ULL << 15);
|
|
||||||
|
|
||||||
int x = -1, y = -1;
|
|
||||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
|
||||||
|
|
||||||
REQUIRE(x == 0);
|
|
||||||
REQUIRE(y == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Backward search branch finds gap before MSB cluster", "[puzzle][branch]")
|
|
||||||
{
|
|
||||||
puzzle p(4, 4); // 16 cells
|
|
||||||
|
|
||||||
// Set bits 15,14,13 but leave 12 empty
|
|
||||||
u64 bitmap = (1ULL << 15) | (1ULL << 14) | (1ULL << 13);
|
|
||||||
|
|
||||||
int x = -1, y = -1;
|
|
||||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
|
||||||
|
|
||||||
REQUIRE(x == 0);
|
|
||||||
REQUIRE(y == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Rectangular board coordinate mapping", "[puzzle][rect]")
|
|
||||||
{
|
|
||||||
puzzle p(5, 3); // 15 cells
|
|
||||||
|
|
||||||
int empty_index = 11;
|
|
||||||
|
|
||||||
u64 bitmap = board_mask(5, 3);
|
|
||||||
bitmap &= ~(1ULL << empty_index);
|
|
||||||
|
|
||||||
int x = -1, y = -1;
|
|
||||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
|
||||||
|
|
||||||
REQUIRE(x == empty_index % 5);
|
|
||||||
REQUIRE(y == empty_index / 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Non-64-sized board near limit", "[puzzle][limit]")
|
|
||||||
{
|
|
||||||
puzzle p(7, 8); // 56 cells
|
|
||||||
|
|
||||||
u64 mask = board_mask(7, 8);
|
|
||||||
|
|
||||||
// Full board should return false
|
|
||||||
int x = -1, y = -1;
|
|
||||||
REQUIRE_FALSE(p.bitmap_find_first_empty(mask, x, y));
|
|
||||||
|
|
||||||
// Clear highest valid cell
|
|
||||||
int empty_index = 55;
|
|
||||||
mask &= ~(1ULL << empty_index);
|
|
||||||
|
|
||||||
REQUIRE(p.bitmap_find_first_empty(mask, x, y));
|
|
||||||
REQUIRE(x == empty_index % 7);
|
|
||||||
REQUIRE(y == empty_index / 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Oracle: find first zero bit inside board ---
|
|
||||||
static auto oracle_find_first_empty(u64 bitmap, int w, int h, int& x, int& y) -> bool
|
|
||||||
{
|
|
||||||
int cells = w * h;
|
|
||||||
|
|
||||||
for (int i = 0; i < cells; ++i) {
|
|
||||||
if ((bitmap & (1ULL << i)) == 0) {
|
|
||||||
x = i % w;
|
|
||||||
y = i / w;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Oracle validation across board sizes 3x3 to 8x8", "[puzzle][oracle]")
|
|
||||||
{
|
|
||||||
auto [w, h] = GENERATE(std::tuple{3, 3}, std::tuple{4, 4}, std::tuple{5, 5}, std::tuple{6, 6}, std::tuple{7, 7},
|
|
||||||
std::tuple{8, 8}, std::tuple{3, 5}, std::tuple{5, 3}, std::tuple{7, 8}, std::tuple{8, 7});
|
|
||||||
|
|
||||||
puzzle p(w, h);
|
|
||||||
|
|
||||||
u64 mask = board_mask(w, h);
|
|
||||||
|
|
||||||
std::mt19937_64 rng(12345);
|
|
||||||
std::uniform_int_distribution<u64> dist(0, UINT64_MAX);
|
|
||||||
|
|
||||||
for (int iteration = 0; iteration < 200; ++iteration) {
|
|
||||||
u64 bitmap = dist(rng);
|
|
||||||
|
|
||||||
int ox = -1, oy = -1;
|
|
||||||
bool oracle_result = oracle_find_first_empty(bitmap, w, h, ox, oy);
|
|
||||||
|
|
||||||
int x = -1, y = -1;
|
|
||||||
bool result = p.bitmap_find_first_empty(bitmap, x, y);
|
|
||||||
|
|
||||||
REQUIRE(result == oracle_result);
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
REQUIRE(x == ox);
|
|
||||||
REQUIRE(y == oy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Bits set outside board only behaves as empty board", "[puzzle][outside]")
|
|
||||||
{
|
|
||||||
puzzle p(3, 3); // 9 cells
|
|
||||||
|
|
||||||
u64 bitmap = (1ULL << 40) | (1ULL << 63);
|
|
||||||
|
|
||||||
int x = -1, y = -1;
|
|
||||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
|
||||||
|
|
||||||
REQUIRE(x == 0);
|
|
||||||
REQUIRE(y == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Last valid cell empty stresses upper bound", "[puzzle][boundary]")
|
|
||||||
{
|
|
||||||
auto [w, h] = GENERATE(std::tuple{4, 4}, std::tuple{5, 6}, std::tuple{7, 8}, std::tuple{8, 8});
|
|
||||||
|
|
||||||
puzzle p(w, h);
|
|
||||||
|
|
||||||
int cells = w * h;
|
|
||||||
u64 bitmap = board_mask(w, h);
|
|
||||||
|
|
||||||
// Clear last valid bit
|
|
||||||
bitmap &= ~(1ULL << (cells - 1));
|
|
||||||
|
|
||||||
int x = -1, y = -1;
|
|
||||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
|
||||||
|
|
||||||
REQUIRE(x == (cells - 1) % w);
|
|
||||||
REQUIRE(y == (cells - 1) / w);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Board sizes between 33 and 63 cells", "[puzzle][midrange]")
|
|
||||||
{
|
|
||||||
auto [w, h] = GENERATE(std::tuple{6, 6}, // 36
|
|
||||||
std::tuple{7, 6}, // 42
|
|
||||||
std::tuple{7, 7}, // 49
|
|
||||||
std::tuple{8, 7}, // 56
|
|
||||||
std::tuple{7, 8} // 56
|
|
||||||
);
|
|
||||||
|
|
||||||
puzzle p(w, h);
|
|
||||||
|
|
||||||
int cells = w * h;
|
|
||||||
|
|
||||||
for (int index : {31, 32, cells - 2}) {
|
|
||||||
if (index >= cells) continue;
|
|
||||||
|
|
||||||
u64 bitmap = board_mask(w, h);
|
|
||||||
bitmap &= ~(1ULL << index);
|
|
||||||
|
|
||||||
int x = -1, y = -1;
|
|
||||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
|
||||||
|
|
||||||
REQUIRE(x == index % w);
|
|
||||||
REQUIRE(y == index / w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Multiple holes choose lowest index", "[puzzle][multiple]")
|
|
||||||
{
|
|
||||||
puzzle p(5, 5);
|
|
||||||
|
|
||||||
u64 bitmap = board_mask(5, 5);
|
|
||||||
|
|
||||||
// Clear several positions
|
|
||||||
bitmap &= ~(1ULL << 3);
|
|
||||||
bitmap &= ~(1ULL << 7);
|
|
||||||
bitmap &= ~(1ULL << 12);
|
|
||||||
|
|
||||||
int x = -1, y = -1;
|
|
||||||
REQUIRE(p.bitmap_find_first_empty(bitmap, x, y));
|
|
||||||
|
|
||||||
// Oracle expectation: index 3
|
|
||||||
REQUIRE(x == 3 % 5);
|
|
||||||
REQUIRE(y == 3 / 5);
|
|
||||||
}
|
|
||||||
100
test/bits.cpp
100
test/bits.cpp
@ -2,7 +2,7 @@
|
|||||||
#include <catch2/catch_template_test_macros.hpp>
|
#include <catch2/catch_template_test_macros.hpp>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#include "bits.hpp"
|
#include "util.hpp"
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Catch2
|
// Catch2
|
||||||
@ -31,8 +31,8 @@
|
|||||||
// 4. TEMPLATE_TEST_CASE(name, tags, Type1, Type2, ...)
|
// 4. TEMPLATE_TEST_CASE(name, tags, Type1, Type2, ...)
|
||||||
// A parameterised test that is instantiated once for each type listed.
|
// A parameterised test that is instantiated once for each type listed.
|
||||||
// Inside the test body, the alias `TestType` refers to the current type.
|
// Inside the test body, the alias `TestType` refers to the current type.
|
||||||
// This avoids duplicating identical logic for u8, u16, u32,
|
// This avoids duplicating identical logic for uint8_t, uint16_t, uint32_t,
|
||||||
// and u64. Catch2 automatically appends the type name to the test name
|
// and uint64_t. Catch2 automatically appends the type name to the test name
|
||||||
// in the output so you can see which instantiation failed.
|
// in the output so you can see which instantiation failed.
|
||||||
//
|
//
|
||||||
// 5. Tags (e.g. "[create_mask]", "[round-trip]")
|
// 5. Tags (e.g. "[create_mask]", "[round-trip]")
|
||||||
@ -49,7 +49,7 @@
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
TEMPLATE_TEST_CASE("create_mask produces correct masks", "[create_mask]",
|
TEMPLATE_TEST_CASE("create_mask produces correct masks", "[create_mask]",
|
||||||
u8, u16, u32, u64)
|
uint8_t, uint16_t, uint32_t, uint64_t)
|
||||||
{
|
{
|
||||||
SECTION("single bit mask at bit 0") {
|
SECTION("single bit mask at bit 0") {
|
||||||
auto m = create_mask<TestType>(0, 0);
|
auto m = create_mask<TestType>(0, 0);
|
||||||
@ -72,16 +72,16 @@ TEMPLATE_TEST_CASE("create_mask produces correct masks", "[create_mask]",
|
|||||||
}
|
}
|
||||||
|
|
||||||
SECTION("full-width mask returns all ones") {
|
SECTION("full-width mask returns all ones") {
|
||||||
constexpr u8 last = sizeof(TestType) * 8 - 1;
|
constexpr uint8_t last = sizeof(TestType) * 8 - 1;
|
||||||
auto m = create_mask<TestType>(0, last);
|
auto m = create_mask<TestType>(0, last);
|
||||||
REQUIRE(m == static_cast<TestType>(~TestType{0}));
|
REQUIRE(m == static_cast<TestType>(~TestType{0}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("create_mask 32-bit specific cases", "[create_mask]") {
|
TEST_CASE("create_mask 32-bit specific cases", "[create_mask]") {
|
||||||
REQUIRE(create_mask<u32>(0, 15) == 0x0000FFFF);
|
REQUIRE(create_mask<uint32_t>(0, 15) == 0x0000FFFF);
|
||||||
REQUIRE(create_mask<u32>(0, 31) == 0xFFFFFFFF);
|
REQUIRE(create_mask<uint32_t>(0, 31) == 0xFFFFFFFF);
|
||||||
REQUIRE(create_mask<u32>(16, 31) == 0xFFFF0000);
|
REQUIRE(create_mask<uint32_t>(16, 31) == 0xFFFF0000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -89,11 +89,11 @@ TEST_CASE("create_mask 32-bit specific cases", "[create_mask]") {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
TEMPLATE_TEST_CASE("clear_bits zeroes the specified range", "[clear_bits]",
|
TEMPLATE_TEST_CASE("clear_bits zeroes the specified range", "[clear_bits]",
|
||||||
u8, u16, u32, u64)
|
uint8_t, uint16_t, uint32_t, uint64_t)
|
||||||
{
|
{
|
||||||
SECTION("clear all bits") {
|
SECTION("clear all bits") {
|
||||||
TestType val = static_cast<TestType>(~TestType{0});
|
TestType val = static_cast<TestType>(~TestType{0});
|
||||||
constexpr u8 last = sizeof(TestType) * 8 - 1;
|
constexpr uint8_t last = sizeof(TestType) * 8 - 1;
|
||||||
clear_bits(val, 0, last);
|
clear_bits(val, 0, last);
|
||||||
REQUIRE(val == TestType{0});
|
REQUIRE(val == TestType{0});
|
||||||
}
|
}
|
||||||
@ -128,55 +128,55 @@ TEMPLATE_TEST_CASE("clear_bits zeroes the specified range", "[clear_bits]",
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
TEMPLATE_TEST_CASE("set_bits writes value into the specified range", "[set_bits]",
|
TEMPLATE_TEST_CASE("set_bits writes value into the specified range", "[set_bits]",
|
||||||
u8, u16, u32, u64)
|
uint8_t, uint16_t, uint32_t, uint64_t)
|
||||||
{
|
{
|
||||||
SECTION("set lower nibble on zero") {
|
SECTION("set lower nibble on zero") {
|
||||||
TestType val = TestType{0};
|
TestType val = TestType{0};
|
||||||
set_bits(val, u8{0}, u8{3}, static_cast<TestType>(0xA));
|
set_bits(val, uint8_t{0}, uint8_t{3}, static_cast<TestType>(0xA));
|
||||||
REQUIRE(val == static_cast<TestType>(0x0A));
|
REQUIRE(val == static_cast<TestType>(0x0A));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("set upper nibble on zero") {
|
SECTION("set upper nibble on zero") {
|
||||||
TestType val = TestType{0};
|
TestType val = TestType{0};
|
||||||
set_bits(val, u8{4}, u8{7}, static_cast<TestType>(0xB));
|
set_bits(val, uint8_t{4}, uint8_t{7}, static_cast<TestType>(0xB));
|
||||||
REQUIRE(val == static_cast<TestType>(0xB0));
|
REQUIRE(val == static_cast<TestType>(0xB0));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("set_bits replaces existing bits") {
|
SECTION("set_bits replaces existing bits") {
|
||||||
TestType val = static_cast<TestType>(0xFF);
|
TestType val = static_cast<TestType>(0xFF);
|
||||||
set_bits(val, u8{0}, u8{3}, static_cast<TestType>(0x5));
|
set_bits(val, uint8_t{0}, uint8_t{3}, static_cast<TestType>(0x5));
|
||||||
REQUIRE(val == static_cast<TestType>(0xF5));
|
REQUIRE(val == static_cast<TestType>(0xF5));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("set single bit to 1") {
|
SECTION("set single bit to 1") {
|
||||||
TestType val = TestType{0};
|
TestType val = TestType{0};
|
||||||
set_bits(val, u8{3}, u8{3}, static_cast<TestType>(1));
|
set_bits(val, uint8_t{3}, uint8_t{3}, static_cast<TestType>(1));
|
||||||
REQUIRE(val == static_cast<TestType>(0x08));
|
REQUIRE(val == static_cast<TestType>(0x08));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("set single bit to 0") {
|
SECTION("set single bit to 0") {
|
||||||
TestType val = static_cast<TestType>(0xFF);
|
TestType val = static_cast<TestType>(0xFF);
|
||||||
set_bits(val, u8{3}, u8{3}, static_cast<TestType>(0));
|
set_bits(val, uint8_t{3}, uint8_t{3}, static_cast<TestType>(0));
|
||||||
REQUIRE(val == static_cast<TestType>(0xF7));
|
REQUIRE(val == static_cast<TestType>(0xF7));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("setting value 0 clears the range") {
|
SECTION("setting value 0 clears the range") {
|
||||||
TestType val = static_cast<TestType>(0xFF);
|
TestType val = static_cast<TestType>(0xFF);
|
||||||
set_bits(val, u8{0}, u8{7}, static_cast<TestType>(0));
|
set_bits(val, uint8_t{0}, uint8_t{7}, static_cast<TestType>(0));
|
||||||
REQUIRE(val == TestType{0});
|
REQUIRE(val == TestType{0});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("set_bits with different value type (U != T)", "[set_bits]") {
|
TEST_CASE("set_bits with different value type (U != T)", "[set_bits]") {
|
||||||
u32 val = 0;
|
uint32_t val = 0;
|
||||||
constexpr u8 small_val = 0x3F;
|
constexpr uint8_t small_val = 0x3F;
|
||||||
set_bits(val, u8{8}, u8{13}, small_val);
|
set_bits(val, uint8_t{8}, uint8_t{13}, small_val);
|
||||||
REQUIRE(val == (u32{0x3F} << 8));
|
REQUIRE(val == (uint32_t{0x3F} << 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("set_bits preserves surrounding bits in 32-bit", "[set_bits]") {
|
TEST_CASE("set_bits preserves surrounding bits in 32-bit", "[set_bits]") {
|
||||||
u32 val = 0xDEADBEEF;
|
uint32_t val = 0xDEADBEEF;
|
||||||
set_bits(val, u8{8}, u8{15}, u32{0x42});
|
set_bits(val, uint8_t{8}, uint8_t{15}, uint32_t{0x42});
|
||||||
REQUIRE(val == 0xDEAD42EF);
|
REQUIRE(val == 0xDEAD42EF);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,53 +185,53 @@ TEST_CASE("set_bits preserves surrounding bits in 32-bit", "[set_bits]") {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
TEMPLATE_TEST_CASE("get_bits extracts the specified range", "[get_bits]",
|
TEMPLATE_TEST_CASE("get_bits extracts the specified range", "[get_bits]",
|
||||||
u8, u16, u32, u64)
|
uint8_t, uint16_t, uint32_t, uint64_t)
|
||||||
{
|
{
|
||||||
SECTION("get lower nibble") {
|
SECTION("get lower nibble") {
|
||||||
TestType val = static_cast<TestType>(0xAB);
|
TestType val = static_cast<TestType>(0xAB);
|
||||||
auto result = get_bits(val, u8{0}, u8{3});
|
auto result = get_bits(val, uint8_t{0}, uint8_t{3});
|
||||||
REQUIRE(result == TestType{0xB});
|
REQUIRE(result == TestType{0xB});
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("get upper nibble") {
|
SECTION("get upper nibble") {
|
||||||
TestType val = static_cast<TestType>(0xAB);
|
TestType val = static_cast<TestType>(0xAB);
|
||||||
auto result = get_bits(val, u8{4}, u8{7});
|
auto result = get_bits(val, uint8_t{4}, uint8_t{7});
|
||||||
REQUIRE(result == TestType{0xA});
|
REQUIRE(result == TestType{0xA});
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("get single bit that is set") {
|
SECTION("get single bit that is set") {
|
||||||
TestType val = static_cast<TestType>(0x08);
|
TestType val = static_cast<TestType>(0x08);
|
||||||
auto result = get_bits(val, u8{3}, u8{3});
|
auto result = get_bits(val, uint8_t{3}, uint8_t{3});
|
||||||
REQUIRE(result == TestType{1});
|
REQUIRE(result == TestType{1});
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("get single bit that is clear") {
|
SECTION("get single bit that is clear") {
|
||||||
TestType val = static_cast<TestType>(0xF7);
|
TestType val = static_cast<TestType>(0xF7);
|
||||||
auto result = get_bits(val, u8{3}, u8{3});
|
auto result = get_bits(val, uint8_t{3}, uint8_t{3});
|
||||||
REQUIRE(result == TestType{0});
|
REQUIRE(result == TestType{0});
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("get all bits") {
|
SECTION("get all bits") {
|
||||||
TestType val = static_cast<TestType>(~TestType{0});
|
TestType val = static_cast<TestType>(~TestType{0});
|
||||||
constexpr u8 last = sizeof(TestType) * 8 - 1;
|
constexpr uint8_t last = sizeof(TestType) * 8 - 1;
|
||||||
auto result = get_bits(val, u8{0}, last);
|
auto result = get_bits(val, uint8_t{0}, last);
|
||||||
REQUIRE(result == val);
|
REQUIRE(result == val);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("get from zero returns zero") {
|
SECTION("get from zero returns zero") {
|
||||||
TestType val = TestType{0};
|
TestType val = TestType{0};
|
||||||
auto result = get_bits(val, u8{0}, u8{7});
|
auto result = get_bits(val, uint8_t{0}, uint8_t{7});
|
||||||
REQUIRE(result == TestType{0});
|
REQUIRE(result == TestType{0});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("get_bits 32-bit specific extractions", "[get_bits]") {
|
TEST_CASE("get_bits 32-bit specific extractions", "[get_bits]") {
|
||||||
constexpr u32 val = 0xDEADBEEF;
|
constexpr uint32_t val = 0xDEADBEEF;
|
||||||
|
|
||||||
REQUIRE(get_bits(val, u8{0}, u8{7}) == 0xEF);
|
REQUIRE(get_bits(val, uint8_t{0}, uint8_t{7}) == 0xEF);
|
||||||
REQUIRE(get_bits(val, u8{8}, u8{15}) == 0xBE);
|
REQUIRE(get_bits(val, uint8_t{8}, uint8_t{15}) == 0xBE);
|
||||||
REQUIRE(get_bits(val, u8{16}, u8{23}) == 0xAD);
|
REQUIRE(get_bits(val, uint8_t{16}, uint8_t{23}) == 0xAD);
|
||||||
REQUIRE(get_bits(val, u8{24}, u8{31}) == 0xDE);
|
REQUIRE(get_bits(val, uint8_t{24}, uint8_t{31}) == 0xDE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -239,29 +239,29 @@ TEST_CASE("get_bits 32-bit specific extractions", "[get_bits]") {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
TEST_CASE("set_bits then get_bits round-trips correctly", "[round-trip]") {
|
TEST_CASE("set_bits then get_bits round-trips correctly", "[round-trip]") {
|
||||||
u32 reg = 0;
|
uint32_t reg = 0;
|
||||||
|
|
||||||
set_bits(reg, u8{4}, u8{11}, u32{0xAB});
|
set_bits(reg, uint8_t{4}, uint8_t{11}, uint32_t{0xAB});
|
||||||
REQUIRE(get_bits(reg, u8{4}, u8{11}) == 0xAB);
|
REQUIRE(get_bits(reg, uint8_t{4}, uint8_t{11}) == 0xAB);
|
||||||
|
|
||||||
REQUIRE(get_bits(reg, u8{0}, u8{3}) == 0x0);
|
REQUIRE(get_bits(reg, uint8_t{0}, uint8_t{3}) == 0x0);
|
||||||
REQUIRE(get_bits(reg, u8{12}, u8{31}) == 0x0);
|
REQUIRE(get_bits(reg, uint8_t{12}, uint8_t{31}) == 0x0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("multiple set_bits on different ranges", "[round-trip]") {
|
TEST_CASE("multiple set_bits on different ranges", "[round-trip]") {
|
||||||
u32 reg = 0;
|
uint32_t reg = 0;
|
||||||
|
|
||||||
set_bits(reg, u8{0}, u8{7}, u32{0x01});
|
set_bits(reg, uint8_t{0}, uint8_t{7}, uint32_t{0x01});
|
||||||
set_bits(reg, u8{8}, u8{15}, u32{0x02});
|
set_bits(reg, uint8_t{8}, uint8_t{15}, uint32_t{0x02});
|
||||||
set_bits(reg, u8{16}, u8{23}, u32{0x03});
|
set_bits(reg, uint8_t{16}, uint8_t{23}, uint32_t{0x03});
|
||||||
set_bits(reg, u8{24}, u8{31}, u32{0x04});
|
set_bits(reg, uint8_t{24}, uint8_t{31}, uint32_t{0x04});
|
||||||
|
|
||||||
REQUIRE(reg == 0x04030201);
|
REQUIRE(reg == 0x04030201);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("64-bit round-trip", "[round-trip]") {
|
TEST_CASE("64-bit round-trip", "[round-trip]") {
|
||||||
u64 reg = 0;
|
uint64_t reg = 0;
|
||||||
set_bits(reg, u8{32}, u8{63}, u64{0xCAFEBABE});
|
set_bits(reg, uint8_t{32}, uint8_t{63}, uint64_t{0xCAFEBABE});
|
||||||
REQUIRE(get_bits(reg, u8{32}, u8{63}) == u64{0xCAFEBABE});
|
REQUIRE(get_bits(reg, uint8_t{32}, uint8_t{63}) == uint64_t{0xCAFEBABE});
|
||||||
REQUIRE(get_bits(reg, u8{0}, u8{31}) == u64{0});
|
REQUIRE(get_bits(reg, uint8_t{0}, uint8_t{31}) == uint64_t{0});
|
||||||
}
|
}
|
||||||
1092
test/puzzle.cpp
1092
test/puzzle.cpp
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user