mirror of
https://git.lynn.is/Gwen/pretty-automata.git
synced 2024-05-11 12:11:09 +02:00
working but missing a bunch of features and slightly buggy
This commit is contained in:
parent
091005f8b8
commit
dee767a487
1
.clang-format
Normal file
1
.clang-format
Normal file
|
@ -0,0 +1 @@
|
|||
ColumnLimit: 0
|
132
CMakeLists.txt
132
CMakeLists.txt
|
@ -1,11 +1,137 @@
|
|||
cmake_minimum_required(VERSION 3.24)
|
||||
cmake_minimum_required(VERSION 3.25)
|
||||
project(shader-automaton)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(VERBOSE yes)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(GLEW REQUIRED)
|
||||
find_package(glfw3 3.3 REQUIRED)
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
include(EmbedResources.cmake)
|
||||
|
||||
FetchContent_Declare(
|
||||
perlin
|
||||
GIT_REPOSITORY https://github.com/Reputeless/PerlinNoise.git
|
||||
GIT_TAG bdf39fe92b2a585cdef485bcec2bca8ab5614095
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
imgui
|
||||
GIT_REPOSITORY https://github.com/ocornut/imgui.git
|
||||
GIT_TAG 70cca1eac07aa3809bdd3717253c3754a5b7cfcc
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
gtest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG bc860af08783b8113005ca7697da5f5d49a8056f
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
dejavufonts
|
||||
URL https://github.com/dejavu-fonts/dejavu-fonts/releases/download/version_2_37/dejavu-fonts-ttf-2.37.tar.bz2
|
||||
URL_HASH SHA1=7fa15e7b9676fc3915338c41e76ad454c344fff5
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(perlin)
|
||||
FetchContent_MakeAvailable(imgui)
|
||||
|
||||
|
||||
add_executable(shader-automaton main.cpp)
|
||||
target_link_libraries(shader-automaton PRIVATE OpenGL::GL GLEW::GLEW glfw)
|
||||
add_executable(shader-automaton
|
||||
src/main.cpp
|
||||
src/options.cpp
|
||||
src/colormaps/viridis.cpp
|
||||
src/rule_presets.cpp
|
||||
src/argparser.h
|
||||
|
||||
src/rule_presets.h
|
||||
src/options.h
|
||||
src/shader-helper.cpp
|
||||
src/shader-helper.h
|
||||
src/color.cpp
|
||||
src/color.h
|
||||
src/colormaps/colormaps.cpp
|
||||
src/colormaps/dualcolor.cpp
|
||||
src/colormaps/viridis.cpp
|
||||
src/colormaps/rainbow.cpp
|
||||
src/program.cpp
|
||||
src/program.h
|
||||
)
|
||||
|
||||
target_compile_definitions(shader-automaton PRIVATE PROGRAM_NAME="${CMAKE_PROJECT_NAME}" PROGRAM_VERSION="${CMAKE_PROJECT_VERSION}")
|
||||
|
||||
function(preprocess_shader name)
|
||||
add_custom_command(
|
||||
OUTPUT "preprocessed-shaders/${name}"
|
||||
COMMAND ./preprocess-shader.sh "${name}"
|
||||
DEPENDS "shaders/${name}"
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
PRE_BUILD
|
||||
VERBATIM
|
||||
)
|
||||
endfunction()
|
||||
|
||||
|
||||
preprocess_shader("automaton.comp")
|
||||
preprocess_shader("automaton.vert")
|
||||
preprocess_shader("automaton.frag")
|
||||
|
||||
# TODO: this whole shader preprocessing and embedding is just broken
|
||||
# it happens half at configure time rather than build time so the preprocessed files are outdated
|
||||
|
||||
# TODO: maybe rewrite this tool to put multiple namespaces into one target
|
||||
# and to maybe define the contents separately from the target, if possible
|
||||
add_embedded_binary_resources(
|
||||
shader-automaton-shaders
|
||||
OUT_DIR embedded-resources
|
||||
HEADER shaders.h
|
||||
NAMESPACE resources::shaders
|
||||
RESOURCE_NAMES automaton_comp automaton_vert automaton_frag
|
||||
RESOURCES
|
||||
"${PROJECT_SOURCE_DIR}/preprocessed-shaders/automaton.comp"
|
||||
"${PROJECT_SOURCE_DIR}/preprocessed-shaders/automaton.vert"
|
||||
"${PROJECT_SOURCE_DIR}/preprocessed-shaders/automaton.frag"
|
||||
)
|
||||
|
||||
|
||||
FetchContent_MakeAvailable(dejavufonts)
|
||||
add_embedded_binary_resources(
|
||||
shader-automaton-fonts
|
||||
OUT_DIR embedded-resources
|
||||
HEADER fonts.h
|
||||
NAMESPACE resources::fonts
|
||||
RESOURCE_NAMES DejaVuSansMono
|
||||
RESOURCES
|
||||
"${dejavufonts_SOURCE_DIR}/ttf/DejaVuSansMono.ttf"
|
||||
)
|
||||
|
||||
|
||||
|
||||
target_link_libraries(shader-automaton OpenGL::GL GLEW::GLEW glfw shader-automaton-shaders shader-automaton-fonts)
|
||||
|
||||
file(COPY_FILE "${perlin_SOURCE_DIR}/PerlinNoise.hpp" "${perlin_BINARY_DIR}/PerlinNoise.hpp")
|
||||
target_include_directories(shader-automaton PRIVATE ${perlin_BINARY_DIR})
|
||||
|
||||
target_include_directories(shader-automaton PRIVATE "${imgui_SOURCE_DIR}/backends" "${imgui_SOURCE_DIR}")
|
||||
target_sources(shader-automaton PRIVATE
|
||||
${imgui_SOURCE_DIR}/imgui.cpp
|
||||
${imgui_SOURCE_DIR}/imgui_demo.cpp
|
||||
${imgui_SOURCE_DIR}/imgui_draw.cpp
|
||||
${imgui_SOURCE_DIR}/imgui_tables.cpp
|
||||
${imgui_SOURCE_DIR}/imgui_widgets.cpp
|
||||
${imgui_SOURCE_DIR}/backends/imgui_impl_glfw.cpp
|
||||
${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp
|
||||
)
|
||||
|
||||
|
||||
enable_testing()
|
||||
FetchContent_MakeAvailable(gtest)
|
||||
|
||||
include(GoogleTest)
|
||||
|
||||
add_executable(ColorTest tests/color.cpp src/color.cpp)
|
||||
target_link_libraries(ColorTest GTest::gtest_main)
|
||||
target_include_directories(ColorTest PRIVATE src)
|
||||
gtest_discover_tests(ColorTest)
|
336
EmbedResources.cmake
Normal file
336
EmbedResources.cmake
Normal file
|
@ -0,0 +1,336 @@
|
|||
# Module for embedding resources in binaries
|
||||
|
||||
find_program(HEXDUMP_COMMAND NAMES xxd)
|
||||
|
||||
#[==[
|
||||
Adds an object library target containing embedded binary resources and
|
||||
generates a header file, where you can access them as `span<byte const>`.
|
||||
|
||||
To use with older C++ standards, you can override both the `span` template
|
||||
and the `byte` type to use with e.g. span-lite, Microsoft.GSL or range-v3
|
||||
(don't forget to link your span library of choice to the generated target).
|
||||
|
||||
Usage:
|
||||
add_embedded_binary_resources(
|
||||
name # Name of the created target
|
||||
|
||||
OUT_DIR <dir> # Directory where the header and sources will be created
|
||||
# (relative to the build directory)
|
||||
HEADER <header> # Name of the generated header
|
||||
NAMESPACE [namespace] # Namespace of created symbols (optional)
|
||||
RESOURCE_NAMES <names [...]> # Names (symbols) of the resources
|
||||
RESOURCES <resources [...]> # Resource files
|
||||
SPAN_TEMPLATE [template name] # Name of the `span` template, default `std::span`
|
||||
SPAN_HEADER [header] # Header with the `span` template, default `<span>`
|
||||
BYTE_TYPE [type name] # Name of the `byte` type, default `std::byte`
|
||||
BYTE_HEADER [header] # Header with the `byte` type, default `<cstddef>`
|
||||
)
|
||||
]==]
|
||||
function(add_embedded_binary_resources NAME)
|
||||
set(OPTIONS "")
|
||||
set(ONE_VALUE_ARGS OUT_DIR HEADER NAMESPACE SPAN_TEMPLATE SPAN_HEADER BYTE_TYPE BYTE_HEADER)
|
||||
set(MULTI_VALUE_ARGS RESOURCE_NAMES RESOURCES)
|
||||
cmake_parse_arguments(ARGS "${OPTIONS}" "${ONE_VALUE_ARGS}" "${MULTI_VALUE_ARGS}" ${ARGN})
|
||||
|
||||
if(NOT HEXDUMP_COMMAND)
|
||||
message(FATAL_ERROR "Cannot embed resources - xxd not found.")
|
||||
endif()
|
||||
|
||||
set(FULL_HEADER_PATH "${CMAKE_CURRENT_BINARY_DIR}/${ARGS_OUT_DIR}/${ARGS_HEADER}")
|
||||
|
||||
if(NOT DEFINED ARGS_SPAN_TEMPLATE)
|
||||
set(ARGS_SPAN_TEMPLATE "std::span")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED ARGS_SPAN_HEADER)
|
||||
set(ARGS_SPAN_HEADER "<span>")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED ARGS_BYTE_TYPE)
|
||||
set(ARGS_BYTE_TYPE "std::byte")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED ARGS_BYTE_HEADER)
|
||||
set(ARGS_BYTE_HEADER "<cstddef>")
|
||||
endif()
|
||||
|
||||
add_library("${NAME}" OBJECT)
|
||||
target_include_directories("${NAME}" PUBLIC "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
|
||||
if(ARGS_SPAN_HEADER STREQUAL "<span>")
|
||||
target_compile_features("${NAME}" PUBLIC cxx_std_20)
|
||||
elseif(ARGS_BYTE_HEADER STREQUAL "<cstddef>")
|
||||
target_compile_features("${NAME}" PUBLIC cxx_std_17)
|
||||
endif()
|
||||
|
||||
# fPIC not added automatically to object libraries due to defect in CMake
|
||||
set_target_properties(
|
||||
"${NAME}"
|
||||
|
||||
PROPERTIES
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
)
|
||||
|
||||
file(
|
||||
WRITE "${FULL_HEADER_PATH}"
|
||||
|
||||
"#pragma once\n"
|
||||
"\n"
|
||||
"#include ${ARGS_BYTE_HEADER}\n"
|
||||
"#include ${ARGS_SPAN_HEADER}\n"
|
||||
"\n"
|
||||
)
|
||||
|
||||
if(DEFINED ARGS_NAMESPACE)
|
||||
file(
|
||||
APPEND "${FULL_HEADER_PATH}"
|
||||
|
||||
"namespace ${ARGS_NAMESPACE}\n"
|
||||
"{\n"
|
||||
"\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
foreach(RESOURCE_NAME RESOURCE IN ZIP_LISTS ARGS_RESOURCE_NAMES ARGS_RESOURCES)
|
||||
set(FULL_RESOURCE_UNIT_PATH "${CMAKE_CURRENT_BINARY_DIR}/${ARGS_OUT_DIR}/${RESOURCE_NAME}.cpp")
|
||||
set(FULL_RESOURCE_HEX_PATH "${CMAKE_CURRENT_BINARY_DIR}/${ARGS_OUT_DIR}/${RESOURCE_NAME}.inc")
|
||||
|
||||
# Add symbol to header
|
||||
file(
|
||||
APPEND "${FULL_HEADER_PATH}"
|
||||
|
||||
"[[nodiscard]] ${ARGS_SPAN_TEMPLATE}<${ARGS_BYTE_TYPE} const>\n"
|
||||
"${RESOURCE_NAME}() noexcept;\n"
|
||||
"\n"
|
||||
)
|
||||
|
||||
# Write .cpp
|
||||
file(
|
||||
WRITE "${FULL_RESOURCE_UNIT_PATH}"
|
||||
|
||||
"#include \"${ARGS_HEADER}\"\n"
|
||||
"\n"
|
||||
"#include <cstdint>\n"
|
||||
"\n"
|
||||
)
|
||||
|
||||
if(DEFINED ARGS_NAMESPACE)
|
||||
file(
|
||||
APPEND "${FULL_RESOURCE_UNIT_PATH}"
|
||||
|
||||
"namespace ${ARGS_NAMESPACE}\n"
|
||||
"{\n"
|
||||
"\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
file(
|
||||
APPEND "${FULL_RESOURCE_UNIT_PATH}"
|
||||
|
||||
"namespace\n"
|
||||
"{\n"
|
||||
"\n"
|
||||
"std::uint8_t const ${RESOURCE_NAME}_data[] = {\n"
|
||||
"#include \"${RESOURCE_NAME}.inc\"\n"
|
||||
"};\n"
|
||||
"\n"
|
||||
"} // namespace\n"
|
||||
"\n"
|
||||
"${ARGS_SPAN_TEMPLATE}<${ARGS_BYTE_TYPE} const>\n"
|
||||
"${RESOURCE_NAME}() noexcept\n"
|
||||
"{\n"
|
||||
" return as_bytes(${ARGS_SPAN_TEMPLATE}<std::uint8_t const>{${RESOURCE_NAME}_data, sizeof(${RESOURCE_NAME}_data)});\n"
|
||||
"}\n"
|
||||
)
|
||||
|
||||
if(DEFINED ARGS_NAMESPACE)
|
||||
file(
|
||||
APPEND "${FULL_RESOURCE_UNIT_PATH}"
|
||||
|
||||
"\n"
|
||||
"} // namespace ${ARGS_NAMESPACE}\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_sources("${NAME}" PRIVATE "${FULL_RESOURCE_UNIT_PATH}")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${FULL_RESOURCE_HEX_PATH}"
|
||||
COMMAND "${HEXDUMP_COMMAND}" -i < "${RESOURCE}" > "${FULL_RESOURCE_HEX_PATH}"
|
||||
MAIN_DEPENDENCY "${RESOURCE}"
|
||||
)
|
||||
list(APPEND RESOURCES_HEX_FILES "${FULL_RESOURCE_HEX_PATH}")
|
||||
endforeach()
|
||||
|
||||
if(DEFINED ARGS_NAMESPACE)
|
||||
file(
|
||||
APPEND "${FULL_HEADER_PATH}"
|
||||
|
||||
"} // namespace ${ARGS_NAMESPACE}\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_sources("${NAME}" PUBLIC "${FULL_HEADER_PATH}")
|
||||
|
||||
add_custom_target("${NAME}_hexdump" DEPENDS "${RESOURCES_HEX_FILES}")
|
||||
add_dependencies("${NAME}" "${NAME}_hexdump")
|
||||
endfunction()
|
||||
|
||||
#[==[
|
||||
Adds an object library target containing embedded text resources and
|
||||
generates a header file, where you can access them as `string_view`.
|
||||
|
||||
To use with older C++ standards, you can override the `string_view` type
|
||||
to use with e.g. string-view-lite (don't forget to link your string view
|
||||
library of choice to the generated target).
|
||||
|
||||
Usage:
|
||||
add_embedded_text_resources(
|
||||
name # Name of the created target
|
||||
|
||||
OUT_DIR <dir> # Directory where the header and sources will be created
|
||||
# (relative to the build directory)
|
||||
HEADER <header> # Name of the generated header
|
||||
NAMESPACE [namespace] # Namespace of created symbols (optional)
|
||||
RESOURCE_NAMES <names [...]> # Names (symbols) of the resources
|
||||
RESOURCES <resources [...]> # Resource files
|
||||
STRING_VIEW_TYPE [type name] # Name of the `string_view` type, default `std::string_view`
|
||||
STRING_VIEW_HEADER [header] # Header with the `string_view` type, default `<string_view>`
|
||||
)
|
||||
]==]
|
||||
function(add_embedded_text_resources NAME)
|
||||
set(OPTIONS "")
|
||||
set(ONE_VALUE_ARGS OUT_DIR HEADER NAMESPACE STRING_VIEW_TYPE STRING_VIEW_HEADER)
|
||||
set(MULTI_VALUE_ARGS RESOURCE_NAMES RESOURCES)
|
||||
cmake_parse_arguments(ARGS "${OPTIONS}" "${ONE_VALUE_ARGS}" "${MULTI_VALUE_ARGS}" ${ARGN})
|
||||
|
||||
if(NOT HEXDUMP_COMMAND)
|
||||
message(FATAL_ERROR "Cannot embed resources - xxd not found.")
|
||||
endif()
|
||||
|
||||
set(FULL_HEADER_PATH "${CMAKE_CURRENT_BINARY_DIR}/${ARGS_OUT_DIR}/${ARGS_HEADER}")
|
||||
|
||||
if(NOT DEFINED ARGS_STRING_VIEW_TYPE)
|
||||
set(ARGS_STRING_VIEW_TYPE "std::string_view")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED ARGS_STRING_VIEW_HEADER)
|
||||
set(ARGS_STRING_VIEW_HEADER "<string_view>")
|
||||
endif()
|
||||
|
||||
add_library("${NAME}" OBJECT)
|
||||
target_include_directories("${NAME}" PUBLIC "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
|
||||
if(ARGS_STRING_VIEW_HEADER STREQUAL "<string_view>")
|
||||
target_compile_features("${NAME}" PUBLIC cxx_std_17)
|
||||
endif()
|
||||
|
||||
# fPIC not added automatically to object libraries due to defect in CMake
|
||||
set_target_properties(
|
||||
"${NAME}"
|
||||
|
||||
PROPERTIES
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
)
|
||||
|
||||
file(
|
||||
WRITE "${FULL_HEADER_PATH}"
|
||||
|
||||
"#pragma once\n"
|
||||
"\n"
|
||||
"#include ${ARGS_STRING_VIEW_HEADER}\n"
|
||||
"\n"
|
||||
)
|
||||
|
||||
if(DEFINED ARGS_NAMESPACE)
|
||||
file(
|
||||
APPEND "${FULL_HEADER_PATH}"
|
||||
|
||||
"namespace ${ARGS_NAMESPACE}\n"
|
||||
"{\n"
|
||||
"\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
foreach(RESOURCE_NAME RESOURCE IN ZIP_LISTS ARGS_RESOURCE_NAMES ARGS_RESOURCES)
|
||||
set(FULL_RESOURCE_UNIT_PATH "${CMAKE_CURRENT_BINARY_DIR}/${ARGS_OUT_DIR}/${RESOURCE_NAME}.cpp")
|
||||
set(FULL_RESOURCE_HEX_PATH "${CMAKE_CURRENT_BINARY_DIR}/${ARGS_OUT_DIR}/${RESOURCE_NAME}.inc")
|
||||
|
||||
# Add symbol to header
|
||||
file(
|
||||
APPEND "${FULL_HEADER_PATH}"
|
||||
|
||||
"[[nodiscard]] ${ARGS_STRING_VIEW_TYPE}\n"
|
||||
"${RESOURCE_NAME}() noexcept;\n"
|
||||
"\n"
|
||||
)
|
||||
|
||||
# Write .cpp
|
||||
file(
|
||||
WRITE "${FULL_RESOURCE_UNIT_PATH}"
|
||||
|
||||
"#include \"${ARGS_HEADER}\"\n"
|
||||
"\n"
|
||||
)
|
||||
|
||||
if(DEFINED ARGS_NAMESPACE)
|
||||
file(
|
||||
APPEND "${FULL_RESOURCE_UNIT_PATH}"
|
||||
|
||||
"namespace ${ARGS_NAMESPACE}\n"
|
||||
"{\n"
|
||||
"\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
file(
|
||||
APPEND "${FULL_RESOURCE_UNIT_PATH}"
|
||||
|
||||
"namespace\n"
|
||||
"{\n"
|
||||
"\n"
|
||||
"char const ${RESOURCE_NAME}_data[] = {\n"
|
||||
"#include \"${RESOURCE_NAME}.inc\"\n"
|
||||
"};\n"
|
||||
"\n"
|
||||
"} // namespace\n"
|
||||
"\n"
|
||||
"${ARGS_STRING_VIEW_TYPE}\n"
|
||||
"${RESOURCE_NAME}() noexcept\n"
|
||||
"{\n"
|
||||
" return ${ARGS_STRING_VIEW_TYPE}{${RESOURCE_NAME}_data, sizeof(${RESOURCE_NAME}_data)};\n"
|
||||
"}\n"
|
||||
)
|
||||
|
||||
if(DEFINED ARGS_NAMESPACE)
|
||||
file(
|
||||
APPEND "${FULL_RESOURCE_UNIT_PATH}"
|
||||
|
||||
"\n"
|
||||
"} // namespace ${ARGS_NAMESPACE}\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_sources("${NAME}" PRIVATE "${FULL_RESOURCE_UNIT_PATH}")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${FULL_RESOURCE_HEX_PATH}"
|
||||
COMMAND "${HEXDUMP_COMMAND}" -i < "${RESOURCE}" > "${FULL_RESOURCE_HEX_PATH}"
|
||||
MAIN_DEPENDENCY "${RESOURCE}"
|
||||
)
|
||||
list(APPEND RESOURCES_HEX_FILES "${FULL_RESOURCE_HEX_PATH}")
|
||||
endforeach()
|
||||
|
||||
if(DEFINED ARGS_NAMESPACE)
|
||||
file(
|
||||
APPEND "${FULL_HEADER_PATH}"
|
||||
|
||||
"} // namespace ${ARGS_NAMESPACE}\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_sources("${NAME}" PUBLIC "${FULL_HEADER_PATH}")
|
||||
|
||||
add_custom_target("${NAME}_hexdump" DEPENDS "${RESOURCES_HEX_FILES}")
|
||||
add_dependencies("${NAME}" "${NAME}_hexdump")
|
||||
endfunction()
|
182
main.cpp
182
main.cpp
|
@ -1,182 +0,0 @@
|
|||
#include <cstdio>
|
||||
|
||||
#include <GL/glew.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
GLuint loadShader(const char *source, GLenum shader_type) {
|
||||
GLuint shaderId = glCreateShader(shader_type);
|
||||
GLint result = GL_FALSE;
|
||||
int infoLogLength;
|
||||
// Compile Vertex Shader
|
||||
glShaderSource(shaderId, 1, &source, nullptr);
|
||||
glCompileShader(shaderId);
|
||||
glGetShaderiv(shaderId, GL_COMPILE_STATUS, &result);
|
||||
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &infoLogLength);
|
||||
if (infoLogLength > 0) {
|
||||
std::vector<char> VertexShaderErrorMessage(infoLogLength + 1);
|
||||
glGetShaderInfoLog(shaderId, infoLogLength, nullptr,
|
||||
&VertexShaderErrorMessage[0]);
|
||||
printf("%s\n", &VertexShaderErrorMessage[0]);
|
||||
exit(-1);
|
||||
}
|
||||
return shaderId;
|
||||
}
|
||||
|
||||
GLuint loadProgram(const std::vector<GLuint> &shaders) {
|
||||
GLuint programId = glCreateProgram();
|
||||
for (const auto &item : shaders) {
|
||||
glAttachShader(programId, item);
|
||||
}
|
||||
glLinkProgram(programId);
|
||||
|
||||
GLint result = GL_FALSE;
|
||||
int infoLogLength;
|
||||
|
||||
glGetProgramiv(programId, GL_LINK_STATUS, &result);
|
||||
glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &infoLogLength);
|
||||
if (infoLogLength > 0) {
|
||||
std::vector<char> ProgramErrorMessage(infoLogLength + 1);
|
||||
glGetProgramInfoLog(programId, infoLogLength, nullptr,
|
||||
&ProgramErrorMessage[0]);
|
||||
printf("%s\n", &ProgramErrorMessage[0]);
|
||||
}
|
||||
return programId;
|
||||
}
|
||||
|
||||
static const char *vertex_shader =
|
||||
"#version 460\n"
|
||||
"layout(location = 0) in vec3 vertexPosition_modelspace;\n"
|
||||
"out vec2 coord;\n"
|
||||
"void main(){\n"
|
||||
" gl_Position.xyz = vertexPosition_modelspace;\n"
|
||||
" gl_Position.w = 1.0;\n"
|
||||
" coord.xy = vertexPosition_modelspace.xy;\n"
|
||||
"}";
|
||||
|
||||
static const char *fragment_shader =
|
||||
"#version 460\n"
|
||||
"out vec3 color;\n"
|
||||
"in vec2 coord;\n"
|
||||
"layout (std430, binding = 2) restrict readonly buffer shader_data\n"
|
||||
"{\n"
|
||||
" int c[];\n"
|
||||
"};\n"
|
||||
|
||||
"void main(){\n"
|
||||
" int x = int(255. * (coord.x + 1.) / 2.);"
|
||||
" color = vec3(c[x]/255., 0.5,0.5);\n"
|
||||
"}";
|
||||
|
||||
static const char *compute_shader =
|
||||
"#version 460\n"
|
||||
"layout(local_size_x = 1, local_size_y = 1) in;\n"
|
||||
"layout (std430, binding = 2) restrict buffer shader_data\n"
|
||||
"{\n"
|
||||
" int c[];\n"
|
||||
"};\n"
|
||||
"void main(){\n"
|
||||
" c[gl_GlobalInvocationID.x] = (c[gl_GlobalInvocationID.x] + 1) % 256;\n"
|
||||
"}\n";
|
||||
|
||||
int main() {
|
||||
glewExperimental = true;
|
||||
if (!glfwInit()) {
|
||||
fprintf(stderr, "Failed to initialize GLFW\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
glfwWindowHint(GLFW_SAMPLES, 0);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
|
||||
GLFWwindow *window =
|
||||
glfwCreateWindow(1024, 768, "Tutorial 01", nullptr, nullptr);
|
||||
if (window == nullptr) {
|
||||
fprintf(stderr,
|
||||
"Failed to open GLFW window. If you have an Intel GPU, they are "
|
||||
"not 3.3 compatible. Try the 2.1 version of the tutorials.\n");
|
||||
glfwTerminate();
|
||||
return -1;
|
||||
}
|
||||
glfwMakeContextCurrent(window); // Initialize GLEW
|
||||
glewExperimental = true; // Needed in core profile
|
||||
if (glewInit() != GLEW_OK) {
|
||||
fprintf(stderr, "Failed to initialize GLEW\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);
|
||||
|
||||
GLuint VertexArrayID;
|
||||
glGenVertexArrays(1, &VertexArrayID);
|
||||
glBindVertexArray(VertexArrayID);
|
||||
const GLfloat g_vertex_buffer_data[] = {
|
||||
-1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
|
||||
};
|
||||
GLuint vertexbuffer;
|
||||
glGenBuffers(1, &vertexbuffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data),
|
||||
g_vertex_buffer_data, GL_STATIC_DRAW);
|
||||
|
||||
GLuint programId = loadProgram({
|
||||
loadShader(vertex_shader, GL_VERTEX_SHADER),
|
||||
loadShader(fragment_shader, GL_FRAGMENT_SHADER),
|
||||
});
|
||||
|
||||
GLuint computeProgramId = loadProgram({
|
||||
loadShader(compute_shader, GL_COMPUTE_SHADER),
|
||||
});
|
||||
|
||||
int c[256];
|
||||
|
||||
for (size_t i = 0; i < 256; ++i) {
|
||||
c[i] = i;
|
||||
}
|
||||
|
||||
GLuint ssbo = 0;
|
||||
glGenBuffers(1, &ssbo);
|
||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
|
||||
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(int) * 256, &c,
|
||||
GL_DYNAMIC_COPY);
|
||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
|
||||
|
||||
unsigned int block_index = glGetProgramResourceIndex(
|
||||
programId, GL_SHADER_STORAGE_BLOCK, "shader_data");
|
||||
GLuint ssbo_binding_point_index = 2;
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, ssbo_binding_point_index, ssbo);
|
||||
glShaderStorageBlockBinding(programId, block_index, ssbo_binding_point_index);
|
||||
|
||||
glUseProgram(computeProgramId);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, ssbo_binding_point_index, ssbo);
|
||||
unsigned int compute_block_index = glGetProgramResourceIndex(
|
||||
programId, GL_SHADER_STORAGE_BLOCK, "shader_data");
|
||||
glShaderStorageBlockBinding(computeProgramId, compute_block_index,
|
||||
ssbo_binding_point_index);
|
||||
|
||||
do {
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
glUseProgram(computeProgramId);
|
||||
glDispatchCompute(255, 1, 1);
|
||||
|
||||
glUseProgram(programId);
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
glDisableVertexAttribArray(0);
|
||||
|
||||
glfwSwapBuffers(window);
|
||||
glfwPollEvents();
|
||||
|
||||
} // Check if the ESC key was pressed or the window was closed
|
||||
while (glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS &&
|
||||
glfwWindowShouldClose(window) == 0);
|
||||
}
|
26
preprocess-shader.sh
Executable file
26
preprocess-shader.sh
Executable file
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "exactly one argument required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p preprocessed-shaders
|
||||
|
||||
preprocess_file() {
|
||||
input_file="shaders/$1"
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
if [[ $line = \#include* ]]; then
|
||||
name=$( echo "$line" | cut -d'"' -f2)
|
||||
preprocess_file "$name"
|
||||
else
|
||||
echo -e "$line"
|
||||
fi
|
||||
done < "$input_file"
|
||||
}
|
||||
|
||||
input_name="$1"
|
||||
output_file="preprocessed-shaders/$input_name"
|
||||
|
||||
preprocess_file "$input_name" > "$output_file"
|
||||
|
87
readme.md
Normal file
87
readme.md
Normal file
|
@ -0,0 +1,87 @@
|
|||
# TODO
|
||||
|
||||
- imgui
|
||||
- split up into commands: view, preview-colormap, save-image, save-video
|
||||
- colormap invert option
|
||||
- pass colormap values as command line option?
|
||||
- see if the rendering can be made better, maybe dynamically adjust generation duration
|
||||
|
||||
# Options
|
||||
|
||||
|
||||
## subcommands
|
||||
|
||||
- `view`
|
||||
- `preview-colormaps`
|
||||
- `save-image`
|
||||
- `save-video`
|
||||
|
||||
### `view`
|
||||
|
||||
#### automaton options
|
||||
|
||||
-[x] `--preset` `-p` `<preset-name>`
|
||||
-[x] `--size` `-s` `<uint>x<uint>`
|
||||
-[ ] `--birth` `-b` `<uint...>`
|
||||
-[ ] `--survive` `-u` `<uint...>`
|
||||
-[ ] `--max-age` `-a` `<max-age>`
|
||||
-[ ] `--starve-delay` `-d` `<uint>`
|
||||
-[ ] `--starve-recover` `-r` `<bool>`
|
||||
-[ ] `--initialise` `-i` `<init>`
|
||||
|
||||
#### display options
|
||||
-[ ] `--display-size` `-S` `<uint>x<uint>`
|
||||
-[ ] `--fullscreen` `-F`
|
||||
-[ ] `--color-alive` `-A` `<color-or-colormap>`
|
||||
-[ ] `--color-dead` `-D` `<color>`
|
||||
-[ ] `--generation-duration` `-G` `<uint>`
|
||||
-[ ] `--blend` `-B`
|
||||
|
||||
## formats
|
||||
|
||||
### `color`
|
||||
possible values:
|
||||
-[x] `#<hex>`
|
||||
-[x] `rgb(uint,uint,uint)`
|
||||
-[x] `lab(float,float,float)`
|
||||
-[x] `luv(float,float,float)`
|
||||
-[x] `oklab(float,float,float)`
|
||||
|
||||
### `colormap`
|
||||
TODO: this `:` syntax is inconsistent with the function-style syntax
|
||||
-[x] `<colormapdef>[,<colormap-scale>][,invert]` name of a predefined colormap, and optional scale, and optional invert flag
|
||||
### `colormapdef`:
|
||||
-[x] `<name>` name of a predefined colormap
|
||||
-[x] `map(<lab|luv|oklab>, (float,float,float),...)`
|
||||
-[x] `map([rgb,] <colorlist-rgb>)`
|
||||
|
||||
### `colorlist-rgb`:
|
||||
-[x] `<colorlist-color-rgb, ...>`
|
||||
|
||||
### `colorlist-color-rgb`:
|
||||
-[x] `#<hex>`
|
||||
-[x] `(<uint>,<uint>,<uint>)`
|
||||
|
||||
### `color-or-colormap`
|
||||
-[x] `<color>`
|
||||
-[x] `<colormap>`
|
||||
|
||||
### `colormap-scale`
|
||||
-[x] `<uint>`
|
||||
-[x] `inherent`
|
||||
-[x] `global-max-age` only possible if max-age is set, otherwise fallback to `inherent`
|
||||
-[x] `max-age` same as before
|
||||
|
||||
### `init`
|
||||
-[x] `rect(<uint>,<uint>)`
|
||||
-[x] `square(<uint>)`
|
||||
-[x] `ellipse(<uint>,<uint>)`
|
||||
-[x] `circle(<uint>)`
|
||||
-[x] `perlin(<float>,<float>)` scale and cutoff
|
||||
|
||||
### `max-age`
|
||||
-[x] `static(<uint>)`
|
||||
-[x] `radial(<uint>,<uint>)` center and corner max age, circular
|
||||
-[x] `radial-fit(<uint>,<uint>)` center and corner max age, elliptic fit into size
|
||||
-[x] `perlin-static(<float>,<uint>,<uint>)` scale and min/max
|
||||
-[x] `perlin-dynamic(<float>, <uint>, <uint>, <float>, <uint>)` scale, min, max, time-scale, time-step
|
104
shaders/automaton.comp
Normal file
104
shaders/automaton.comp
Normal file
|
@ -0,0 +1,104 @@
|
|||
#version 460
|
||||
|
||||
layout (local_size_x = 1, local_size_y = 1) in;
|
||||
|
||||
uniform uvec2 dimensions;
|
||||
uniform uint birth_rule;
|
||||
uniform uint survive_rule;
|
||||
uniform uint starve_delay;
|
||||
uniform bool starve_recover;
|
||||
uniform bool has_max_age;
|
||||
|
||||
|
||||
layout (std430) restrict readonly buffer buffer_data_in
|
||||
{
|
||||
uint data_in[];
|
||||
};
|
||||
layout (std430) restrict buffer buffer_data_out
|
||||
{
|
||||
uint data_out[];
|
||||
};
|
||||
|
||||
#include "cellstate_common.glsl"
|
||||
|
||||
bool check_condition(uint rule, uint count) {
|
||||
return ((rule >> count) & uint(1)) == 1;
|
||||
}
|
||||
|
||||
void main() {
|
||||
uint data_size = dimensions.x * dimensions.y;
|
||||
uint neighbour_count = 0;
|
||||
for (int ix = -1; ix < 2; ix++) {
|
||||
for (int iy = -1; iy < 2; iy++) {
|
||||
int cx = int(gl_GlobalInvocationID.x) + ix;
|
||||
int cy = int(gl_GlobalInvocationID.y) + iy;
|
||||
if (cx < 0) {
|
||||
cx += int(dimensions.x);
|
||||
} else if (cx >= dimensions.x) {
|
||||
cx -= int(dimensions.x);
|
||||
}
|
||||
if (cy < 0) {
|
||||
cy += int(dimensions.y);
|
||||
} else if (cy >= dimensions.y) {
|
||||
cy -= int(dimensions.y);
|
||||
}
|
||||
uint pos = cx + cy * dimensions.x;
|
||||
neighbour_count += cellstate_from_data(data_in[pos]).alive ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint pos = gl_GlobalInvocationID.x + gl_GlobalInvocationID.y * dimensions.x;
|
||||
CellState state = cellstate_from_data(data_in[pos]);
|
||||
uint max_age = data_in[data_size + pos];
|
||||
|
||||
|
||||
if (state.alive) {
|
||||
if (starve_recover) {
|
||||
if (check_condition(birth_rule, neighbour_count)) {
|
||||
state.starving = false;
|
||||
} else {
|
||||
if (state.starving) {
|
||||
state.starve_duration = state.starve_duration + 1;
|
||||
} else {
|
||||
state.starving = true;
|
||||
state.starve_duration = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!check_condition(survive_rule, neighbour_count)) {
|
||||
if (state.starving) {
|
||||
state.starve_duration = state.starve_duration + 1;
|
||||
} else {
|
||||
state.starving = true;
|
||||
state.starve_duration = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (has_max_age && state.age >= max_age) {
|
||||
state.alive = false;
|
||||
state.starving = false;
|
||||
state.age = 0;
|
||||
state.starve_duration = 0;
|
||||
}
|
||||
|
||||
if (state.starving && state.starve_duration == starve_delay) {
|
||||
state.starving = false;
|
||||
state.alive = false;
|
||||
state.age = 0;
|
||||
state.starve_duration = 0;
|
||||
} else {
|
||||
state.age = state.age + 1;
|
||||
}
|
||||
} else {
|
||||
if (check_condition(birth_rule, neighbour_count)) {
|
||||
state.alive = true;
|
||||
state.age = 0;
|
||||
}
|
||||
}
|
||||
|
||||
data_out[pos] = cellstate_to_data(state);
|
||||
|
||||
// propagate max-age
|
||||
data_out[data_size + pos] = data_in[data_size + pos];
|
||||
}
|
108
shaders/automaton.frag
Normal file
108
shaders/automaton.frag
Normal file
|
@ -0,0 +1,108 @@
|
|||
#version 460
|
||||
|
||||
in vec2 coord;
|
||||
|
||||
uniform bool preview_colormap;
|
||||
uniform uvec2 dimensions;
|
||||
uniform uint show_max_age;
|
||||
uniform uint global_max_age;
|
||||
uniform uint global_minimum_max_age;
|
||||
uniform float blend_step;
|
||||
uniform vec3 dead_color;
|
||||
uniform bool dead_color_is_cielab;
|
||||
uniform vec3 living_color;
|
||||
uniform sampler1D living_colormap;
|
||||
uniform bool living_use_colormap;
|
||||
uniform bool living_colormap_invert;
|
||||
uniform uint living_colormap_scale;
|
||||
uniform bool living_colormap_scale_is_max_age;
|
||||
uniform bool living_color_is_cielab;
|
||||
|
||||
out vec3 color;
|
||||
|
||||
layout (std430) restrict readonly buffer buffer_data_from
|
||||
{
|
||||
uint data_from[];
|
||||
};
|
||||
|
||||
layout (std430) restrict readonly buffer buffer_data_to
|
||||
{
|
||||
uint data_to[];
|
||||
};
|
||||
|
||||
#include "cellstate_common.glsl"
|
||||
#include "color_conversion.glsl"
|
||||
|
||||
vec3 blend_color(vec3 from, vec3 to, float step) {
|
||||
return vec3(
|
||||
from.x * (1. - step) + to.x * step,
|
||||
from.y * (1. - step) + to.y * step,
|
||||
from.z * (1. - step) + to.z * step
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: support cielab, cieluv and oklab?
|
||||
vec3 get_color(CellState state, uint living_colormap_scale_) {
|
||||
vec3 color;
|
||||
if (state.alive) {
|
||||
if (living_use_colormap) {
|
||||
float pos = float(state.age) / float(living_colormap_scale_);
|
||||
if (living_colormap_invert) {
|
||||
pos = 1. - pos;
|
||||
}
|
||||
return texture(living_colormap, pos).rgb;
|
||||
} else {
|
||||
return living_color;
|
||||
}
|
||||
} else {
|
||||
return dead_color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
uint x = uint(float(dimensions.x) * (coord.x + 1.) / 2.);
|
||||
uint y = uint(float(dimensions.y) * (coord.y + 1.) / 2.);
|
||||
uint pos = x + y * dimensions.x;
|
||||
|
||||
uint max_age = data_from[dimensions.x * dimensions.y + pos];
|
||||
float max_age_val = float(max_age - global_minimum_max_age) / float(global_max_age);
|
||||
|
||||
CellState state_from = cellstate_from_data(data_from[pos]);
|
||||
CellState state_to = cellstate_from_data(data_to[pos]);
|
||||
uint living_colormap_scale_from;
|
||||
uint living_colormap_scale_to;
|
||||
if (living_colormap_scale_is_max_age) {
|
||||
living_colormap_scale_from = data_from[dimensions.x * dimensions.y + pos];
|
||||
living_colormap_scale_to = data_to[dimensions.x * dimensions.y + pos];
|
||||
} else {
|
||||
living_colormap_scale_from = living_colormap_scale;
|
||||
living_colormap_scale_to = living_colormap_scale;
|
||||
}
|
||||
|
||||
vec3 color_from = get_color(state_from, living_colormap_scale_from);
|
||||
vec3 color_to = get_color(state_to, living_colormap_scale_to);
|
||||
|
||||
vec3 color_lab = blend_color(color_from, color_to, blend_step);
|
||||
vec3 color_rgb = convert_cielab_to_rgb(color_lab);
|
||||
|
||||
if (show_max_age == 0) {
|
||||
color = color_rgb;
|
||||
} else if (show_max_age == 1) {
|
||||
color = vec3(max_age_val,max_age_val,max_age_val);
|
||||
} else if (show_max_age == 2) {
|
||||
float r = color_rgb.r * .1 + max_age_val * .9;
|
||||
float g = color_rgb.g * .1 + max_age_val * .9;
|
||||
float b = color_rgb.b * .1 + max_age_val * .9;
|
||||
color = vec3(r,g,b);
|
||||
}
|
||||
|
||||
if (preview_colormap) {
|
||||
color = convert_cielab_to_rgb(texture(living_colormap, (coord.x+1.)/2.).rgb);
|
||||
}
|
||||
|
||||
}
|
||||
//void main(){
|
||||
//// color = texture(living_colormap, (coord.x+1.)/2.).rgb;
|
||||
// color = convert_cielab_to_rgb(texture(living_colormap, (coord.x+1.)/2.).rgb);
|
||||
//}
|
9
shaders/automaton.vert
Normal file
9
shaders/automaton.vert
Normal file
|
@ -0,0 +1,9 @@
|
|||
#version 460
|
||||
|
||||
layout (location = 0) in vec3 vertexPosition_modelspace;
|
||||
out vec2 coord;
|
||||
void main() {
|
||||
gl_Position.xyz = vertexPosition_modelspace;
|
||||
gl_Position.w = 1.0;
|
||||
coord.xy = vertexPosition_modelspace.xy;
|
||||
}
|
22
shaders/cellstate_common.glsl
Normal file
22
shaders/cellstate_common.glsl
Normal file
|
@ -0,0 +1,22 @@
|
|||
struct CellState {
|
||||
bool alive;
|
||||
bool starving;
|
||||
uint age; // max 65025
|
||||
uint starve_duration; // max 255
|
||||
};
|
||||
|
||||
CellState cellstate_from_data(uint data) {
|
||||
bool alive = (data & uint(0x1)) == 1;
|
||||
bool starving = ((data >> 1) & uint(0x1)) == 1;
|
||||
uint age = (data >> 8) & uint(0xFFFF);
|
||||
uint starve_duration = (data >> 24) & uint(0xFF);
|
||||
|
||||
return CellState(alive, starving, age, starve_duration);
|
||||
}
|
||||
|
||||
uint cellstate_to_data(CellState cellstate) {
|
||||
return (cellstate.starve_duration << 24)
|
||||
| (cellstate.age << 8)
|
||||
| (cellstate.starving ? uint(0x2) : uint(0x0))
|
||||
| (cellstate.alive ? uint(0x1) : uint(0x0));
|
||||
}
|
80
shaders/color_conversion.glsl
Normal file
80
shaders/color_conversion.glsl
Normal file
|
@ -0,0 +1,80 @@
|
|||
vec3 convert_ciexyz_to_linear_rgb(vec3 xyz) {
|
||||
// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html sRGB M^-1 matrix
|
||||
// mat3 M = mat3(
|
||||
// 3.2404542, -1.5371385, -0.4985314,
|
||||
// -0.9692660, 1.8760108, 0.0415560,
|
||||
// 0.0556434, -0.2040259, 1.0572252
|
||||
// );
|
||||
// TODO: figure out which is correct, GLSL uses column-major for matrices
|
||||
|
||||
mat3 M = mat3(
|
||||
3.2404542, -0.9692660, 0.0556434,
|
||||
-1.5371385, 1.8760108, -0.2040259,
|
||||
-0.4985314, 0.0415560, 1.0572252
|
||||
);
|
||||
return M * xyz;
|
||||
}
|
||||
|
||||
|
||||
vec3 convert_cielab_to_xyz(vec3 cielab, vec3 ref_white) {
|
||||
float Xn = ref_white.x;
|
||||
float Yn = ref_white.y;
|
||||
float Zn = ref_white.z;
|
||||
|
||||
float L = cielab.x;
|
||||
float a = cielab.y;
|
||||
float b = cielab.z;
|
||||
|
||||
float epsilon = 216./24289.;
|
||||
float kappa = 24389./27.;
|
||||
|
||||
float fy = (L + 16.) / 116.;
|
||||
float fz = fy - b / 200.;
|
||||
float fx = a / 500. + fy;
|
||||
|
||||
float xr;
|
||||
float yr;
|
||||
float zr;
|
||||
|
||||
if (pow(fx, 3.) > epsilon) {
|
||||
xr = pow(fx, 3.);
|
||||
} else {
|
||||
xr = (116. * fx - 16.) / kappa;
|
||||
}
|
||||
|
||||
if (L > kappa * epsilon) {
|
||||
yr = pow((L + 16.) / 116., 3.);
|
||||
} else {
|
||||
yr = L / kappa;
|
||||
}
|
||||
|
||||
if (pow(fz, 3.) > epsilon) {
|
||||
zr = pow(fz, 3.);
|
||||
} else {
|
||||
zr = (116. * fz - 16.) / kappa;
|
||||
}
|
||||
|
||||
float X = xr * Xn;
|
||||
float Y = yr * Yn;
|
||||
float Z = zr * Zn;
|
||||
return vec3(X, Y, Z);
|
||||
}
|
||||
|
||||
|
||||
vec3 convert_cielab_to_rgb(vec3 cielab) {
|
||||
// using D65
|
||||
float Xn = .950489;
|
||||
float Yn = 1.;
|
||||
float Zn = 1.0888;
|
||||
vec3 xyz = convert_cielab_to_xyz(cielab, vec3(Xn, Yn, Zn));
|
||||
vec3 linear_rgb = convert_ciexyz_to_linear_rgb(xyz);
|
||||
return linear_rgb;
|
||||
}
|
||||
|
||||
vec3 convert_cieluv_to_rgb(vec3 cieluv) {
|
||||
return cieluv;// TODO
|
||||
}
|
||||
|
||||
vec3 convert_oklab_to_rgb(vec3 oklab) {
|
||||
return oklab;// TODO
|
||||
}
|
7
shell.nix
Normal file
7
shell.nix
Normal file
|
@ -0,0 +1,7 @@
|
|||
{ pkgs ? import <nixpkgs> {} }:
|
||||
|
||||
pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.clang_16
|
||||
];
|
||||
}
|
1679
src/argparser.h
Normal file
1679
src/argparser.h
Normal file
File diff suppressed because it is too large
Load diff
321
src/color.cpp
Normal file
321
src/color.cpp
Normal file
|
@ -0,0 +1,321 @@
|
|||
#include "color.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
constexpr double cie_epsilon = 216. / 24389.;
|
||||
constexpr double cie_kappa = 24389. / 27.;
|
||||
|
||||
constexpr double D65_X = 0.95047;
|
||||
constexpr double D65_Y = 1.;
|
||||
constexpr double D65_Z = 1.08883;
|
||||
|
||||
static std::array<double, 3> linrgb_to_srgb(std::array<double, 3> linrgb);
|
||||
static std::array<double, 3> srgb_to_linrgb(std::array<double, 3> srgb);
|
||||
|
||||
static std::array<double, 3> linrgb_to_xyz(std::array<double, 3> linrgb);
|
||||
static std::array<double, 3> xyz_to_linrgb(std::array<double, 3> xyz);
|
||||
|
||||
static std::array<double, 3> cielab_to_xyz(std::array<double, 3> cielab);
|
||||
static std::array<double, 3> xyz_to_cielab(std::array<double, 3> xyz);
|
||||
|
||||
static std::array<double, 3> cieluv_to_xyz(std::array<double, 3> cieluv);
|
||||
static std::array<double, 3> xyz_to_cieluv(std::array<double, 3> xyz);
|
||||
|
||||
static std::array<double, 3> linrgb_to_oklab(std::array<double, 3> linrgb);
|
||||
static std::array<double, 3> oklab_to_linrgb(std::array<double, 3> oklab);
|
||||
|
||||
static std::array<double, 3> xyzmatrix_mult(const std::array<std::array<double, 3>, 3> M, const std::array<double, 3> v);
|
||||
|
||||
ColorSpace Color::default_colorspace = ColorSpace::CIELAB;
|
||||
|
||||
std::array<double, 3> Color::values() const {
|
||||
return {v1, v2, v3};
|
||||
}
|
||||
|
||||
std::array<uint8_t, 3> Color::to_rgb() const {
|
||||
std::array<double, 3> normalised = this->to_normalized_rgb();
|
||||
return {
|
||||
static_cast<uint8_t>(std::round(normalised[0]*255.)),
|
||||
static_cast<uint8_t>(std::round(normalised[1]*255.)),
|
||||
static_cast<uint8_t>(std::round(normalised[2]*255.)),
|
||||
};
|
||||
}
|
||||
|
||||
std::array<double, 3> Color::to_normalized_rgb() const {
|
||||
std::array<double, 3> linrgb{};
|
||||
switch (colorspace) {
|
||||
case ColorSpace::CIELAB:
|
||||
linrgb = xyz_to_linrgb(cielab_to_xyz({v1, v2, v3}));
|
||||
break;
|
||||
case ColorSpace::CIELUV:
|
||||
linrgb = xyz_to_linrgb(cieluv_to_xyz({v1, v2, v3}));
|
||||
break;
|
||||
case ColorSpace::OKLAB:
|
||||
linrgb = oklab_to_linrgb({v1, v2, v3});
|
||||
break;
|
||||
}
|
||||
return linrgb_to_srgb(linrgb);
|
||||
}
|
||||
|
||||
Color Color::from_rgb(const std::array<uint8_t, 3> rgb, ColorSpace colorspace) {
|
||||
return from_rgb(rgb[0], rgb[1], rgb[2], colorspace);
|
||||
}
|
||||
|
||||
Color Color::from_rgb(double red, double green, double blue, ColorSpace colorspace) {
|
||||
return from_normalized_rgb(red / 255., green / 255., blue / 255., colorspace);
|
||||
}
|
||||
|
||||
Color Color::from_rgb(const std::array<double, 3> rgb, ColorSpace colorspace) {
|
||||
return from_rgb(rgb[0], rgb[1], rgb[2], colorspace);
|
||||
}
|
||||
|
||||
Color Color::from_normalized_rgb(double red, double green, double blue, ColorSpace colorspace) {
|
||||
return from_normalized_rgb({red, green, blue}, colorspace);
|
||||
}
|
||||
|
||||
Color Color::from_normalized_rgb(const std::array<double, 3> rgb, ColorSpace colorspace) {
|
||||
auto linrgb = srgb_to_linrgb(rgb);
|
||||
std::array<double, 3> values{};
|
||||
switch (colorspace) {
|
||||
case ColorSpace::CIELAB:
|
||||
values = xyz_to_cielab(linrgb_to_xyz(linrgb));
|
||||
break;
|
||||
case ColorSpace::CIELUV:
|
||||
values = xyz_to_cieluv(linrgb_to_xyz(linrgb));
|
||||
break;
|
||||
case ColorSpace::OKLAB:
|
||||
values = linrgb_to_oklab(linrgb);
|
||||
break;
|
||||
}
|
||||
|
||||
return {values, colorspace};
|
||||
}
|
||||
|
||||
Color Color::from_rgb(uint8_t red, uint8_t green, uint8_t blue) {
|
||||
return from_rgb(red, green, blue, default_colorspace);
|
||||
}
|
||||
|
||||
Color Color::from_rgb(const std::array<uint8_t, 3> rgb) {
|
||||
return from_rgb(rgb, default_colorspace);
|
||||
}
|
||||
|
||||
Color Color::from_rgb(double red, double green, double blue) {
|
||||
return from_rgb(red, green, blue, default_colorspace);
|
||||
}
|
||||
|
||||
Color Color::from_rgb(const std::array<double, 3> rgb) {
|
||||
return from_rgb(rgb, default_colorspace);
|
||||
}
|
||||
|
||||
Color Color::from_normalized_rgb(double red, double green, double blue) {
|
||||
return from_normalized_rgb(red, green, blue, default_colorspace);
|
||||
}
|
||||
|
||||
Color Color::from_normalized_rgb(const std::array<double, 3> rgb) {
|
||||
return from_normalized_rgb(rgb, default_colorspace);
|
||||
}
|
||||
|
||||
void Color::set_default_colorspace(ColorSpace colorspace) {
|
||||
default_colorspace = colorspace;
|
||||
}
|
||||
|
||||
std::array<double, 3> linrgb_to_srgb(const std::array<double, 3> linrgb) {
|
||||
std::array<double, 3> srgb{};
|
||||
std::transform(linrgb.begin(), linrgb.end(), srgb.begin(), [](double v) {
|
||||
if (v <= 0.0031308) {
|
||||
return v * 12.92;
|
||||
} else {
|
||||
return 1.055 * pow(v, 1. / 2.4) - 0.055;
|
||||
}
|
||||
});
|
||||
return srgb;
|
||||
}
|
||||
|
||||
std::array<double, 3> srgb_to_linrgb(const std::array<double, 3> srgb) {
|
||||
std::array<double, 3> linrgb{};
|
||||
std::transform(srgb.begin(), srgb.end(), linrgb.begin(), [](double v) {
|
||||
if (v <= 0.04045) {
|
||||
return v / 12.92;
|
||||
} else {
|
||||
return pow(((v + 0.055) / 1.055), 2.4);
|
||||
}
|
||||
});
|
||||
return linrgb;
|
||||
}
|
||||
|
||||
static std::array<double, 3> xyzmatrix_mult(const std::array<std::array<double, 3>, 3> M, const std::array<double, 3> v) {
|
||||
return {
|
||||
M[0][0] * v[0] + M[0][1] * v[1] + M[0][2] * v[2],
|
||||
M[1][0] * v[0] + M[1][1] * v[1] + M[1][2] * v[2],
|
||||
M[2][0] * v[0] + M[2][1] * v[1] + M[2][2] * v[2],
|
||||
};
|
||||
}
|
||||
|
||||
constexpr std::array<std::array<double, 3>, 3> RGB_TO_XYZ_M = {
|
||||
std::array<double, 3>{0.4124564, 0.3575761, 0.1804375},
|
||||
std::array<double, 3>{0.2126729, 0.7151522, 0.0721750},
|
||||
std::array<double, 3>{0.0193339, 0.1191920, 0.9503041},
|
||||
};
|
||||
|
||||
constexpr std::array<std::array<double, 3>, 3> XYZ_TO_RGB_M = {
|
||||
std::array<double, 3>{3.2404542, -1.5371385, -0.4985314},
|
||||
std::array<double, 3>{-0.9692660, 1.8760108, 0.0415560},
|
||||
std::array<double, 3>{0.0556434, -0.2040259, 1.0572252},
|
||||
};
|
||||
|
||||
std::array<double, 3> linrgb_to_xyz(const std::array<double, 3> linrgb) {
|
||||
return xyzmatrix_mult(RGB_TO_XYZ_M, linrgb);
|
||||
}
|
||||
std::array<double, 3> xyz_to_linrgb(const std::array<double, 3> xyz) {
|
||||
return xyzmatrix_mult(XYZ_TO_RGB_M, xyz);
|
||||
}
|
||||
std::array<double, 3> cielab_to_xyz(const std::array<double, 3> cielab) {
|
||||
auto L = cielab[0];
|
||||
auto a = cielab[1];
|
||||
auto b = cielab[2];
|
||||
|
||||
auto fy = (L + 16.) / 116.;
|
||||
auto fz = fy - b / 200.;
|
||||
auto fx = a / 500. + fy;
|
||||
|
||||
double xr, yr, zr;
|
||||
if (pow(fx, 3.) > cie_epsilon) {
|
||||
xr = pow(fx, 3.);
|
||||
} else {
|
||||
xr = (116. * fx - 16.) / cie_kappa;
|
||||
}
|
||||
|
||||
if (L > cie_kappa * cie_epsilon) {
|
||||
yr = pow((L + 16.) / 116., 3.);
|
||||
} else {
|
||||
yr = L / cie_kappa;
|
||||
}
|
||||
|
||||
if (pow(fz, 3.) > cie_epsilon) {
|
||||
zr = pow(fz, 3.);
|
||||
} else {
|
||||
zr = (116. * fz - 16.) / cie_kappa;
|
||||
}
|
||||
|
||||
auto X = xr * D65_X;
|
||||
auto Y = yr * D65_Y;
|
||||
auto Z = zr * D65_Z;
|
||||
|
||||
return {X, Y, Z};
|
||||
}
|
||||
std::array<double, 3> xyz_to_cielab(const std::array<double, 3> xyz) {
|
||||
|
||||
double xr = xyz[0] / D65_X;
|
||||
double yr = xyz[1] / D65_Y;
|
||||
double zr = xyz[2] / D65_Z;
|
||||
|
||||
double fx, fy, fz;
|
||||
if (xr > cie_epsilon) {
|
||||
fx = cbrt(xr);
|
||||
} else {
|
||||
fx = (cie_kappa * xr + 16.) / 116.;
|
||||
}
|
||||
if (yr > cie_epsilon) {
|
||||
fy = cbrt(yr);
|
||||
} else {
|
||||
fy = (cie_kappa * yr + 16.) / 116.;
|
||||
}
|
||||
if (zr > cie_epsilon) {
|
||||
fz = cbrt(zr);
|
||||
} else {
|
||||
fz = (cie_kappa * zr + 16.) / 116.;
|
||||
}
|
||||
|
||||
double L = 116. * fy - 16.;
|
||||
double a = 500. * (fx - fy);
|
||||
double b = 200. * (fy - fz);
|
||||
|
||||
return {L, a, b};
|
||||
}
|
||||
std::array<double, 3> cieluv_to_xyz(const std::array<double, 3> cieluv) {
|
||||
constexpr double ur_ = (4. * D65_X) / (D65_X + 15. * D65_Y + 3. * D65_Z);
|
||||
constexpr double vr_ = (9. * D65_Y) / (D65_X + 15. * D65_Y + 3. * D65_Z);
|
||||
|
||||
double L = cieluv[0];
|
||||
double u = cieluv[1];
|
||||
double v = cieluv[2];
|
||||
|
||||
double Y;
|
||||
if (L > cie_kappa * cie_epsilon) {
|
||||
Y = pow((L + 16.) / 116., 3.);
|
||||
} else {
|
||||
Y = L / cie_kappa;
|
||||
}
|
||||
|
||||
double a_ = u + 13. * L * ur_;
|
||||
double a = a_ != 0. ? 1. / 3. * (52. * L / a_ - 1.) : 0.;
|
||||
double b = -5. * Y;
|
||||
double c = -1. / 3.;
|
||||
double d_ = v + 13. * L * vr_;
|
||||
double d = d_ != 0. ? Y * (39. * L / d_ - 5.) : 0.;
|
||||
|
||||
double X = (d - b) / (a - c);
|
||||
double Z = X * a + b;
|
||||
return {X, Y, Z};
|
||||
}
|
||||
std::array<double, 3> xyz_to_cieluv(const std::array<double, 3> xyz) {
|
||||
constexpr double ur_ = (4. * D65_X) / (D65_X + 15. * D65_Y + 3. * D65_Z);
|
||||
constexpr double vr_ = (9. * D65_Y) / (D65_X + 15. * D65_Y + 3. * D65_Z);
|
||||
|
||||
double X = xyz[0];
|
||||
double Y = xyz[1];
|
||||
double Z = xyz[2];
|
||||
|
||||
double yr = Y / D65_Y;
|
||||
double D = (X + 15. * Y + 3. * Z);
|
||||
double u_, v_;
|
||||
if (D != 0) {
|
||||
u_ = (4. * X) / D;
|
||||
v_ = (9. * Y) / D;
|
||||
} else {
|
||||
u_ = 0;
|
||||
v_ = 0;
|
||||
}
|
||||
|
||||
double L;
|
||||
if (yr > cie_epsilon) {
|
||||
L = 116. * cbrt(yr) - 16.;
|
||||
} else {
|
||||
L = cie_kappa * yr;
|
||||
}
|
||||
double u = 13. * L * (u_ - ur_);
|
||||
double v = 13. * L * (v_ - vr_);
|
||||
|
||||
return {L, u, v};
|
||||
}
|
||||
std::array<double, 3> linrgb_to_oklab(const std::array<double, 3> linrgb) {
|
||||
// https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab
|
||||
double l = 0.4122214708 * linrgb[0] + 0.5363325363 * linrgb[1] + 0.0514459929 * linrgb[2];
|
||||
double m = 0.2119034982 * linrgb[0] + 0.6806995451 * linrgb[1] + 0.1073969566 * linrgb[2];
|
||||
double s = 0.0883024619 * linrgb[0] + 0.2817188376 * linrgb[1] + 0.6299787005 * linrgb[2];
|
||||
|
||||
double l_ = cbrt(l);
|
||||
double m_ = cbrt(m);
|
||||
double s_ = cbrt(s);
|
||||
|
||||
return {
|
||||
0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
|
||||
1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
|
||||
0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
|
||||
};
|
||||
}
|
||||
std::array<double, 3> oklab_to_linrgb(const std::array<double, 3> oklab) {
|
||||
double l_ = oklab[0] + 0.3963377774 * oklab[1] + 0.2158037573 * oklab[2];
|
||||
double m_ = oklab[0] - 0.1055613458 * oklab[1] - 0.0638541728 * oklab[2];
|
||||
double s_ = oklab[0] - 0.0894841775 * oklab[1] - 1.2914855480 * oklab[2];
|
||||
|
||||
double l = l_ * l_ * l_;
|
||||
double m = m_ * m_ * m_;
|
||||
double s = s_ * s_ * s_;
|
||||
|
||||
return {
|
||||
+4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
||||
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
||||
-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s,
|
||||
};
|
||||
}
|
108
src/color.h
Normal file
108
src/color.h
Normal file
|
@ -0,0 +1,108 @@
|
|||
#ifndef SHADER_AUTOMATON_COLOR_H
|
||||
#define SHADER_AUTOMATON_COLOR_H
|
||||
|
||||
#include <GL/glew.h>
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
enum class ColorSpace {
|
||||
CIELAB,
|
||||
CIELUV,
|
||||
OKLAB,
|
||||
};
|
||||
|
||||
struct Color {
|
||||
Color() : colorspace(default_colorspace){}
|
||||
Color(const Color&)=default;
|
||||
Color(double v1, double v2, double v3, ColorSpace colorspace) : v1(v1), v2(v2), v3(v3), colorspace(colorspace) {}
|
||||
Color(double v1, double v2, double v3) : Color(v1, v2, v3, default_colorspace) {}
|
||||
Color(std::array<double, 3> values, ColorSpace colorspace) : Color(values[0], values[1], values[2], colorspace) {}
|
||||
explicit Color(std::array<double, 3> values) : Color(values, default_colorspace) {}
|
||||
|
||||
double v1{};
|
||||
double v2{};
|
||||
double v3{};
|
||||
ColorSpace colorspace;
|
||||
|
||||
[[nodiscard]] std::array<double, 3> values() const;
|
||||
[[nodiscard]] std::array<uint8_t, 3> to_rgb() const;
|
||||
[[nodiscard]] std::array<double, 3> to_normalized_rgb() const;
|
||||
|
||||
static Color from_rgb(double red, double green, double blue, ColorSpace colorspace);
|
||||
static Color from_rgb(std::array<uint8_t, 3> rgb, ColorSpace colorspace);
|
||||
static Color from_rgb(std::array<double, 3> rgb, ColorSpace colorspace);
|
||||
static Color from_normalized_rgb(double red, double green, double blue, ColorSpace colorspace);
|
||||
static Color from_normalized_rgb(std::array<double, 3> rgb, ColorSpace colorspace);
|
||||
static Color from_rgb(uint8_t red, uint8_t green, uint8_t blue);
|
||||
static Color from_rgb(std::array<uint8_t, 3> rgb);
|
||||
static Color from_rgb(double red, double green, double blue);
|
||||
static Color from_rgb(std::array<double, 3> rgb);
|
||||
static Color from_normalized_rgb(double red, double green, double blue);
|
||||
static Color from_normalized_rgb(std::array<double, 3> rgb);
|
||||
|
||||
static void set_default_colorspace(ColorSpace colorspace);
|
||||
|
||||
private:
|
||||
static ColorSpace default_colorspace;
|
||||
};
|
||||
|
||||
|
||||
//struct ColorMap {
|
||||
// ~ColorMap() {
|
||||
// if (this->owned_data) {
|
||||
// delete this->data;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// virtual std::string convertToRgbCode(const std::string &val) {
|
||||
// throw std::runtime_error("not implemented in base class");
|
||||
// };
|
||||
//
|
||||
// size_t length;
|
||||
// const float *data{};
|
||||
// bool owned_data = false;
|
||||
//};
|
||||
//
|
||||
//struct RgbColorMap : ColorMap {
|
||||
// RgbColorMap(size_t length, const float data[], bool normalized = false, bool take_ownership = false) {
|
||||
// this->length = length;
|
||||
// if (normalized) {
|
||||
// if (take_ownership) {
|
||||
// this->owned_data = true;
|
||||
// }
|
||||
// this->data = data;
|
||||
// } else {
|
||||
// auto normalized_data = new float[length * 3];
|
||||
// for (size_t i = 0; i < length * 3; ++i) {
|
||||
// normalized_data[i] = data[i] / 255.f;
|
||||
// }
|
||||
// this->data = normalized_data;
|
||||
// this->owned_data = true;
|
||||
// if (take_ownership) {
|
||||
// delete data;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// std::string convertToRgbCode(const std::string &val) override {
|
||||
// return val;
|
||||
// };
|
||||
//};
|
||||
|
||||
//struct CieLabColorMap : ColorMap {
|
||||
// CieLabColorMap(size_t length, const float data[], bool take_ownership = false) {
|
||||
// this->length = length;
|
||||
// this->data = data;
|
||||
// this->owned_data = take_ownership;
|
||||
// }
|
||||
//
|
||||
// std::string convertToRgbCode(const std::string &val) override {
|
||||
// return CieLabColor::convertToRgbCode(val);
|
||||
// };
|
||||
//};
|
||||
|
||||
#endif // SHADER_AUTOMATON_COLOR_H
|
37
src/colormaps/colormaps.cpp
Normal file
37
src/colormaps/colormaps.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#include "colormaps.h"
|
||||
#include <memory>
|
||||
|
||||
extern const float colormap_rainbow_data[];
|
||||
extern const float colormap_redgreen_data[];
|
||||
extern const float colormap_orangeblue_data[];
|
||||
extern const float colormap_magentacyan_data[];
|
||||
extern const float colormap_purpleyellow_data[];
|
||||
extern const float colormap_blackwhite_data[];
|
||||
|
||||
extern const float colormap_viridis_magma_data[];
|
||||
extern const float colormap_viridis_inferno_data[];
|
||||
extern const float colormap_viridis_plasma_data[];
|
||||
extern const float colormap_viridis_viridis_data[];
|
||||
extern const float colormap_viridis_cividis_data[];
|
||||
extern const float colormap_viridis_rocket_data[];
|
||||
extern const float colormap_viridis_mako_data[];
|
||||
extern const float colormap_viridis_turbo_data[];
|
||||
|
||||
const std::map<std::string, ColorMapData> colormaps = {
|
||||
{"redgreen", ColorMapData::fromArray(colormap_redgreen_data, 2, ColorSpace::CIELAB)},
|
||||
{"orangeblue", ColorMapData::fromArray(colormap_orangeblue_data, 2, ColorSpace::CIELAB)},
|
||||
{"magentacyan", ColorMapData::fromArray(colormap_magentacyan_data, 2, ColorSpace::CIELAB)},
|
||||
{"purpleyellow", ColorMapData::fromArray(colormap_purpleyellow_data, 2, ColorSpace::CIELAB)},
|
||||
{"blackwhite", ColorMapData::fromArray(colormap_blackwhite_data, 2, ColorSpace::CIELAB)},
|
||||
|
||||
{"rainbow", ColorMapData::fromArray(colormap_rainbow_data, 7, ColorSpace::CIELAB)},
|
||||
|
||||
{"magma", ColorMapData::fromArray(colormap_viridis_magma_data, 255, std::nullopt)},
|
||||
{"inferno", ColorMapData::fromArray(colormap_viridis_inferno_data, 255, std::nullopt)},
|
||||
{"plasma", ColorMapData::fromArray(colormap_viridis_plasma_data, 255, std::nullopt)},
|
||||
{"viridis", ColorMapData::fromArray(colormap_viridis_viridis_data, 255, std::nullopt)},
|
||||
{"cividis", ColorMapData::fromArray(colormap_viridis_cividis_data, 255, std::nullopt)},
|
||||
{"rocket", ColorMapData::fromArray(colormap_viridis_rocket_data, 255, std::nullopt)},
|
||||
{"mako", ColorMapData::fromArray(colormap_viridis_mako_data, 255, std::nullopt)},
|
||||
{"turbo", ColorMapData::fromArray(colormap_viridis_turbo_data, 255, std::nullopt)},
|
||||
};
|
58
src/colormaps/colormaps.h
Normal file
58
src/colormaps/colormaps.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
#ifndef SHADER_AUTOMATON_COLORMAPS_H
|
||||
#define SHADER_AUTOMATON_COLORMAPS_H
|
||||
#include "../color.h"
|
||||
#include <map>
|
||||
|
||||
struct ColorMapData {
|
||||
private:
|
||||
std::vector<float> data_{};
|
||||
size_t size_{};
|
||||
ColorSpace color_space_{};
|
||||
|
||||
public:
|
||||
ColorMapData() : color_space_(ColorSpace::CIELAB) {}
|
||||
explicit ColorMapData(const std::vector<Color>& colors) {
|
||||
size_ = colors.size();
|
||||
data_ = std::vector<float>(size_ * 3);
|
||||
size_t i = 0;
|
||||
for (const auto &item : colors) {
|
||||
data_[i++] = static_cast<float>(item.v1);
|
||||
data_[i++] = static_cast<float>(item.v2);
|
||||
data_[i++] = static_cast<float>(item.v3);
|
||||
}
|
||||
}
|
||||
|
||||
static ColorMapData fromArray(const float *data, size_t num_colors, std::optional<ColorSpace> color_space) {
|
||||
ColorMapData cmd{};
|
||||
if (color_space.has_value()) {
|
||||
cmd.color_space_ = color_space.value();
|
||||
cmd.data_.assign(data, data + num_colors * 3);
|
||||
} else {
|
||||
cmd.data_ = std::vector<float>(num_colors * 3);
|
||||
for (size_t i = 0; i < num_colors; ++i) {
|
||||
auto c = Color::from_normalized_rgb(data[i * 3], data[i * 3 + 1], data[i * 3 + 2]);
|
||||
cmd.data_[i * 3] = static_cast<float>(c.v1);
|
||||
cmd.data_[i * 3 + 1] = static_cast<float>(c.v2);
|
||||
cmd.data_[i * 3 + 2] = static_cast<float>(c.v3);
|
||||
}
|
||||
}
|
||||
cmd.size_ = num_colors;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
public:
|
||||
[[nodiscard]] size_t size() const {
|
||||
return size_;
|
||||
}
|
||||
|
||||
[[nodiscard]] const float *data() const {
|
||||
return data_.data();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
extern const std::map<std::string, ColorMapData> colormaps;
|
||||
|
||||
|
||||
#endif // SHADER_AUTOMATON_COLORMAPS_H
|
33
src/colormaps/dualcolor.cpp
Normal file
33
src/colormaps/dualcolor.cpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
|
||||
extern const float colormap_redgreen_data[];
|
||||
extern const float colormap_orangeblue_data[];
|
||||
extern const float colormap_magentacyan_data[];
|
||||
extern const float colormap_purpleyellow_data[];
|
||||
extern const float colormap_blackwhite_data[];
|
||||
|
||||
const float colormap_redgreen_data[] = {
|
||||
53.f, 80.f, 67.f,
|
||||
88.f, -86.f, 83.f,
|
||||
};
|
||||
|
||||
const float colormap_orangeblue_data[] = {
|
||||
67.f, 43.f, 74.f,
|
||||
21.f, 79.f, -108.f,
|
||||
};
|
||||
|
||||
const float colormap_magentacyan_data[] = {
|
||||
55.f,85.f,4.f,
|
||||
91.f,-48.f,-14.f
|
||||
};
|
||||
|
||||
|
||||
const float colormap_purpleyellow_data[] = {
|
||||
97.f,-22.f,94.f,
|
||||
41.f,83.f,-93.f,
|
||||
};
|
||||
|
||||
const float colormap_blackwhite_data[] = {
|
||||
0.f,0.f,0.f,
|
||||
100.f,0.f,0.f,
|
||||
};
|
||||
|
12
src/colormaps/rainbow.cpp
Normal file
12
src/colormaps/rainbow.cpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
|
||||
extern const float colormap_rainbow_data[];
|
||||
|
||||
const float colormap_rainbow_data[] = {
|
||||
53.f, 80.f, 67.f, // red
|
||||
75.f, 24.f, 79.f, // orange
|
||||
97.f, -22.f, 94.f, // yellow
|
||||
46.f, -52.f, 50.f, // green
|
||||
32.f, 79.f, -108.f, // blue
|
||||
20.f, 52.f, -53.f, // indigo
|
||||
70.f, 56.f, -37.f, // violet
|
||||
};
|
2081
src/colormaps/viridis.cpp
Normal file
2081
src/colormaps/viridis.cpp
Normal file
File diff suppressed because it is too large
Load diff
52
src/distinct-types.h
Normal file
52
src/distinct-types.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
#ifndef SHADER_AUTOMATON_DISTINCT_TYPES_H
|
||||
#define SHADER_AUTOMATON_DISTINCT_TYPES_H
|
||||
|
||||
#include <variant>
|
||||
// #include <type_traits>
|
||||
|
||||
namespace distinct_types_internal {
|
||||
|
||||
template <typename TList, typename... Ts>
|
||||
struct UniqueTypes;
|
||||
|
||||
template <template <typename...> typename X, typename... Ts>
|
||||
struct UniqueTypes<X<Ts...>> {
|
||||
using type = X<Ts...>;
|
||||
};
|
||||
|
||||
template <template <typename...> typename X, typename... Ts, typename T, typename... Us>
|
||||
struct UniqueTypes<X<Ts...>, T, Us...> {
|
||||
using type = typename UniqueTypes<
|
||||
typename std::conditional<
|
||||
std::disjunction<std::is_same<T, Ts>...>::value,
|
||||
X<Ts...>,
|
||||
X<Ts..., T>>::type,
|
||||
Us...>::type;
|
||||
};
|
||||
|
||||
template <template <typename...> typename X, typename... Ts>
|
||||
struct Distinct {
|
||||
using type = typename UniqueTypes<X<>, Ts...>::type;
|
||||
};
|
||||
} // namespace distinct_types_internal
|
||||
|
||||
template <typename... Ts>
|
||||
using distinct_types_variant = typename distinct_types_internal::Distinct<std::variant, Ts...>::type;
|
||||
|
||||
template <typename... Ts>
|
||||
using distinct_types_tuple = typename distinct_types_internal::Distinct<std::tuple, Ts...>::type;
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
using if_single_type = std::enable_if_t<std::tuple_size<distinct_types_tuple<Ts...>>::value == 1, T>;
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
using if_not_single_type = std::enable_if_t<(std::tuple_size<distinct_types_tuple<Ts...>>::value > 1), T>;
|
||||
|
||||
template <typename T, typename F, typename... Ts>
|
||||
using single_type_conditional = std::conditional<(std::tuple_size<distinct_types_tuple<Ts...>>::value == 1), T, F>;
|
||||
|
||||
|
||||
template <typename... Ts>
|
||||
using single_type = if_single_type<typename std::tuple_element<0, std::tuple<Ts...>>::type, Ts...>;
|
||||
|
||||
#endif // SHADER_AUTOMATON_DISTINCT_TYPES_H
|
16
src/main.cpp
Normal file
16
src/main.cpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#include "options.h"
|
||||
#include "program.h"
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <iostream>
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
Options options = parse_arguments(argc, argv);
|
||||
try {
|
||||
Program program(options);
|
||||
program.run();
|
||||
} catch (const std::exception &e) {
|
||||
std::cout << e.what();
|
||||
glfwTerminate();
|
||||
return -1;
|
||||
}
|
||||
}
|
184
src/options.cpp
Normal file
184
src/options.cpp
Normal file
|
@ -0,0 +1,184 @@
|
|||
#include "options.h"
|
||||
#include "argparser.h"
|
||||
#include "colormaps/colormaps.h"
|
||||
#include "rule_presets.h"
|
||||
#include <format>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
static Color parse_hex_color(const char *begin, const char *end, const char *&parse_end, argparser::internal::parser_allow_undelimited) {
|
||||
if (begin >= end) {
|
||||
throw std::runtime_error("cannot parse empty string");
|
||||
}
|
||||
if (*begin == '#') {
|
||||
throw std::runtime_error("input is not a hex color");
|
||||
}
|
||||
if (end - begin < 7) {
|
||||
throw std::runtime_error("input is too short");
|
||||
}
|
||||
uint8_t red, green, blue;
|
||||
auto res = std::from_chars(begin + 1, begin + 3, red, 16);
|
||||
if (res.ec != std::errc{} || res.ptr < begin + 3) {
|
||||
throw std::runtime_error("input is not a hex color");
|
||||
}
|
||||
res = std::from_chars(begin + 3, begin + 5, green, 16);
|
||||
if (res.ec != std::errc{} || res.ptr < begin + 5) {
|
||||
throw std::runtime_error("input is not a hex color");
|
||||
}
|
||||
res = std::from_chars(begin + 5, begin + 7, blue, 16);
|
||||
if (res.ec != std::errc{} || res.ptr < begin + 7) {
|
||||
throw std::runtime_error("input is not a hex color");
|
||||
}
|
||||
return Color::from_rgb(red, green, blue);
|
||||
}
|
||||
|
||||
Options do_parse_arguments(const std::vector<std::string> &args) {
|
||||
std::vector<std::string> colormap_names = {};
|
||||
for (const auto &c : colormaps) {
|
||||
colormap_names.push_back(c.first);
|
||||
}
|
||||
std::vector<std::string> preset_names = {};
|
||||
for (const auto &preset_ruleset : rule_presets) {
|
||||
preset_names.push_back(preset_ruleset.first);
|
||||
}
|
||||
|
||||
argparser::parser p{};
|
||||
auto uint8 = p.basic_type<uint8_t>("uint8");
|
||||
auto uint16 = p.basic_type<uint16_t>("uint16");
|
||||
auto uint32 = p.basic_type<uint32_t>("uint32");
|
||||
auto doubleType = p.basic_type<double>("float");
|
||||
auto neighbourcount = p.basic_type<uint8_t>("neighbourcount")->validate([](uint8_t val) { return val >= 0 && val <= 8; });
|
||||
auto neighbourList = p.list_type("neighbourcount-list", neighbourcount);
|
||||
auto sizeType = p.tuple_type("size", uint16, uint16);
|
||||
auto boolType = p.enum_type<bool>("bool", {{"true", true}, {"false", false}});
|
||||
auto hexColor = p.basic_type<Color>("hex-color", parse_hex_color);
|
||||
auto rgbColor = p.tuple_type<Color>("rgb", std::make_tuple(uint8, uint8, uint8), [](uint8_t r, uint8_t g, uint8_t b) {
|
||||
return Color::from_rgb(r, g, b);
|
||||
});
|
||||
auto normalizedRgbColor = p.tuple_type<Color>("rgb-n", std::make_tuple(doubleType, doubleType, doubleType), [](double r, double g, double b) {
|
||||
return Color::from_rgb(r, g, b);
|
||||
});
|
||||
auto labColor = p.tuple_type<Color>("lab", std::make_tuple(doubleType, doubleType, doubleType), [](double L, double a, double b) {
|
||||
return Color{L, a, b, ColorSpace::CIELAB};
|
||||
});
|
||||
auto luvColor = p.tuple_type<Color>("luv", std::make_tuple(doubleType, doubleType, doubleType), [](double L, double u, double v) {
|
||||
return Color{L, u, v, ColorSpace::CIELUV};
|
||||
});
|
||||
auto oklabColor = p.tuple_type<Color>("oklab", std::make_tuple(doubleType, doubleType, doubleType), [](double L, double a, double b) {
|
||||
return Color{L, a, b, ColorSpace::OKLAB};
|
||||
});
|
||||
auto color = p.union_type("color", hexColor, rgbColor, normalizedRgbColor, labColor, luvColor, oklabColor);
|
||||
auto colorList = p.list_type("colorlist", color);
|
||||
|
||||
auto colormapScaleEnum = p.enum_type<ColorMapScale>("colormap-scale-enum", {{"inherent", ColorMapScale::INHERENT},
|
||||
{"max-age", ColorMapScale::MAX_AGE},
|
||||
{"global-max-age", ColorMapScale::GLOBAL_MAX_AGE}});
|
||||
auto colormapScale = p.union_type("colormap-scale", colormapScaleEnum, uint16);
|
||||
auto map = p.tuple_type<ColorMapData>("map", colorList);
|
||||
|
||||
auto colormapName = p.enum_type("colormap-name", colormap_names);
|
||||
auto colormapDef = p.union_type("colormapdef", colormapName, map);
|
||||
auto colormapInvert = p.enum_type<bool>("colormap-invert", {{"invert", true}});
|
||||
auto colormap1 = p.tuple_type<ColorMapOption>("colormapopt1", colormapDef);
|
||||
auto colormap2 = p.tuple_type<ColorMapOption>("colormapopt2", colormapDef, colormapScale);
|
||||
auto colormap3 = p.tuple_type<ColorMapOption>("colormapopt3", colormapDef, colormapInvert);
|
||||
auto colormap4 = p.tuple_type<ColorMapOption>("colormapopt4", colormapDef, colormapScale, colormapInvert);
|
||||
auto colormap5 = p.tuple_type<ColorMapOption>("colormapopt5", colormapDef, colormapInvert, colormapScale);
|
||||
auto colormap = p.union_type("colormapopt", colormap1, colormap2, colormap3, colormap4, colormap5);
|
||||
|
||||
auto colorOrColormap = p.union_type("color-or-colormap", color, colormap);
|
||||
|
||||
auto initRect = p.tuple_type<std::shared_ptr<GridInitialiser>>("rect", std::make_tuple(uint16, uint16),
|
||||
[](uint16_t w, uint16_t h) { return std::make_shared<RectGridInitialiser>(w, h); });
|
||||
auto initSquare = p.tuple_type<std::shared_ptr<GridInitialiser>>("square", std::make_tuple(uint16),
|
||||
[](uint16_t w) { return std::make_shared<RectGridInitialiser>(w, w); });
|
||||
;
|
||||
auto initEllipse = p.tuple_type<std::shared_ptr<GridInitialiser>>("ellipse", std::make_tuple(uint16, uint16),
|
||||
[](uint16_t a, uint16_t b) { return std::make_shared<EllipseGridInitialiser>(a, b); });
|
||||
auto initCircle = p.tuple_type<std::shared_ptr<GridInitialiser>>("circle", std::make_tuple(uint16),
|
||||
[](uint16_t r) { return std::make_shared<EllipseGridInitialiser>(r, r); });
|
||||
auto initPerlin = p.tuple_type<std::shared_ptr<GridInitialiser>>("perlin", std::make_tuple(doubleType, doubleType),
|
||||
[](double scale, double cutoff) { return std::make_shared<PerlinGridInitialiser>(scale, cutoff); });
|
||||
auto init = p.union_type("init", initRect, initSquare, initEllipse, initCircle, initPerlin);
|
||||
|
||||
auto noMaxAge = p.enum_type<std::shared_ptr<MaxAgeProvider>>("none", {{"none", nullptr}});
|
||||
|
||||
auto uniformMaxAge = p.tuple_type<std::shared_ptr<MaxAgeProvider>>("uniform", std::make_tuple(uint16),
|
||||
[](uint16_t v) { return std::make_shared<UniformMaxAgeProvider>(v); });
|
||||
auto radialMaxAge = p.tuple_type<std::shared_ptr<MaxAgeProvider>>("radial", std::make_tuple(uint16, uint16),
|
||||
[](uint16_t center, uint16_t corners) { return std::make_shared<RadialMaxAgeProvider>(center, corners); });
|
||||
auto radialFitMaxAge = p.tuple_type<std::shared_ptr<MaxAgeProvider>>("radial-fit", std::make_tuple(uint16, uint16),
|
||||
[](uint16_t center, uint16_t corners) { return std::make_shared<RadialFitMaxAgeProvider>(center, corners); });
|
||||
auto perlinStaticMaxAge = p.tuple_type<std::shared_ptr<MaxAgeProvider>>("perlin-static", std::make_tuple(doubleType, uint16, uint16),
|
||||
[](double scale, uint16_t min, uint16_t max) { return std::make_shared<PerlinStaticMaxAgeProvider>(scale, min, max); });
|
||||
auto perlinDynamicMaxAge = p.tuple_type<std::shared_ptr<MaxAgeProvider>>("perlin-dynamic", std::make_tuple(doubleType, uint16, uint16, doubleType, uint16),
|
||||
[](double scale, uint16_t min, uint16_t max, double time_scale, uint16_t time_step) { return std::make_shared<PerlinDynamicMaxAgeProvider>(scale, min, max, time_scale, time_step); });
|
||||
auto maxAge = p.union_type("max-age", noMaxAge, uniformMaxAge, radialMaxAge, radialFitMaxAge, perlinStaticMaxAge, perlinDynamicMaxAge);
|
||||
|
||||
auto presetName = p.enum_type("preset-name", preset_names);
|
||||
|
||||
auto presetOpt = p.option("--preset", "-p", presetName);
|
||||
auto sizeOpt = p.option("--size", "-s", sizeType)->default_value(std::make_tuple(32, 32));
|
||||
auto birthOpt = p.option("--birth", "-b", neighbourList)->default_value({});
|
||||
auto surviveOpt = p.option("--survive", "-u", neighbourList)->default_value({});
|
||||
auto maxAgeOpt = p.option("--max-age", "-a", maxAge)->default_value(nullptr);
|
||||
auto starveDelayOpt = p.option("--starve-delay", "-d", uint16)->default_value(0);
|
||||
auto starveRecoverOpt = p.option("--starve-recover", "-r", boolType)->default_value(false);
|
||||
auto initialiseOpt = p.option("--init", "-i", init)->default_value(std::make_shared<RectGridInitialiser>(4, 4));
|
||||
|
||||
auto displaySizeOpt = p.option("--display-size", "-S", sizeType)->default_value(std::make_tuple(256, 256));
|
||||
auto fullscreenFlag = p.flag("--fullscreen", "-F");
|
||||
auto colorAliveOpt = p.option("--color-alive", "-A", colorOrColormap)->default_value(Color::from_rgb(1., 1., 1.));
|
||||
auto colorDeadOpt = p.option("--color-dead", "-D", color)->default_value(Color::from_rgb(0., 0., 0.));
|
||||
auto generationDurationOpt = p.option("--generation-duration", "-G", uint16)->default_value(0);
|
||||
auto blendFlag = p.flag("--blend", "-B");
|
||||
|
||||
auto pr = p.parse(args);
|
||||
|
||||
Options options{};
|
||||
auto size = sizeOpt->get(pr);
|
||||
options.automaton_options.width = std::get<0>(size);
|
||||
options.automaton_options.height = std::get<1>(size);
|
||||
if (initialiseOpt->has(pr)) {
|
||||
options.automaton_options.initialiser = initialiseOpt->get(pr);
|
||||
}
|
||||
bool uses_preset = false;
|
||||
if (presetOpt->has(pr)) {
|
||||
uses_preset = true;
|
||||
options.automaton_options.rule = rule_presets.at(presetOpt->get(pr));
|
||||
}
|
||||
if (birthOpt->has(pr) || !uses_preset) {
|
||||
options.automaton_options.rule.birth = birthOpt->get(pr);
|
||||
}
|
||||
if (surviveOpt->has(pr) || !uses_preset) {
|
||||
options.automaton_options.rule.survive = surviveOpt->get(pr);
|
||||
}
|
||||
if (maxAgeOpt->has(pr) || !uses_preset) {
|
||||
options.automaton_options.rule.max_age = maxAgeOpt->get(pr);
|
||||
}
|
||||
if (starveDelayOpt->has(pr) || !uses_preset) {
|
||||
options.automaton_options.rule.starve_delay = starveDelayOpt->get(pr);
|
||||
}
|
||||
if (starveRecoverOpt->has(pr) || !uses_preset) {
|
||||
options.automaton_options.rule.starve_recover = starveRecoverOpt->get(pr);
|
||||
}
|
||||
|
||||
auto displaySize = displaySizeOpt->get(pr);
|
||||
options.display_options.width = std::get<0>(displaySize);
|
||||
options.display_options.height = std::get<1>(displaySize);
|
||||
options.display_options.fullscreen = fullscreenFlag->get(pr);
|
||||
options.display_options.dead_color = colorDeadOpt->get(pr);
|
||||
options.display_options.alive_color = colorAliveOpt->get(pr);
|
||||
options.display_options.generation_duration_ms = generationDurationOpt->get(pr);
|
||||
options.display_options.blend = blendFlag->get(pr);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
Options parse_arguments(int argc, const char *const *argv) {
|
||||
std::vector<std::string> args(argc - 1);
|
||||
for (size_t i = 1; i < argc; ++i) {
|
||||
args[i - 1] = std::string(argv[i]);
|
||||
}
|
||||
return do_parse_arguments(args);
|
||||
}
|
300
src/options.h
Normal file
300
src/options.h
Normal file
|
@ -0,0 +1,300 @@
|
|||
#ifndef SHADER_AUTOMATON_CLI_PARSE_H
|
||||
#define SHADER_AUTOMATON_CLI_PARSE_H
|
||||
|
||||
#include "color.h"
|
||||
#include "colormaps/colormaps.h"
|
||||
#include <PerlinNoise.hpp>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
enum class ColorMapScale {
|
||||
INHERENT,
|
||||
MAX_AGE,
|
||||
GLOBAL_MAX_AGE,
|
||||
};
|
||||
|
||||
struct ColorMapOption {
|
||||
ColorMapOption(const ColorMapOption &) = default;
|
||||
explicit ColorMapOption(std::variant<std::string, ColorMapData> source) : ColorMapOption(source, false, ColorMapScale::INHERENT) {}
|
||||
ColorMapOption(std::variant<std::string, ColorMapData> source, std::variant<ColorMapScale, uint16_t> scale) : ColorMapOption(source, false, scale) {}
|
||||
ColorMapOption(std::variant<std::string, ColorMapData> source, bool invert) : ColorMapOption(source, invert, ColorMapScale::INHERENT) {}
|
||||
ColorMapOption(std::variant<std::string, ColorMapData> source, std::variant<ColorMapScale, uint16_t> scale, bool invert) : ColorMapOption(source, invert, scale) {}
|
||||
ColorMapOption(std::variant<std::string, ColorMapData> source, bool invert, std::variant<ColorMapScale, uint16_t> scale) : invert(invert), scale(scale), source(source) {}
|
||||
std::variant<std::string, ColorMapData> source;
|
||||
std::variant<ColorMapScale, uint16_t> scale;
|
||||
bool invert;
|
||||
};
|
||||
|
||||
struct GridInitialiser {
|
||||
virtual void setup(unsigned int width, unsigned int height) {
|
||||
this->grid_width = width;
|
||||
this->grid_height = height;
|
||||
}
|
||||
|
||||
virtual bool getCell(unsigned int x, unsigned int y) = 0;
|
||||
|
||||
unsigned int grid_width{};
|
||||
unsigned int grid_height{};
|
||||
};
|
||||
|
||||
struct RectGridInitialiser : GridInitialiser {
|
||||
RectGridInitialiser(unsigned int width, unsigned int height) {
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
}
|
||||
unsigned int width, height;
|
||||
|
||||
bool getCell(unsigned int x, unsigned int y) override {
|
||||
auto margin_x = (grid_width - this->width) / 2;
|
||||
auto margin_y = (grid_height - this->height) / 2;
|
||||
return x >= margin_x && x < grid_width - margin_x && y >= margin_y && y < grid_height - margin_y;
|
||||
}
|
||||
};
|
||||
|
||||
struct EllipseGridInitialiser : GridInitialiser {
|
||||
EllipseGridInitialiser(double rx, double ry) {
|
||||
this->radius_x = rx;
|
||||
this->radius_y = ry;
|
||||
}
|
||||
double radius_x;
|
||||
double radius_y;
|
||||
|
||||
bool getCell(unsigned int x, unsigned int y) override {
|
||||
auto width = static_cast<double>(grid_width);
|
||||
auto height = static_cast<double>(grid_height);
|
||||
auto X = static_cast<double>(x) - (width / 2);
|
||||
auto Y = static_cast<double>(y) - (height / 2);
|
||||
auto z = X * X / (radius_x * radius_x) + Y * Y / (radius_y * radius_y);
|
||||
return z <= 1;
|
||||
}
|
||||
};
|
||||
|
||||
struct PerlinGridInitialiser : GridInitialiser {
|
||||
PerlinGridInitialiser(double scale, double density) : scale(scale), density(density) {
|
||||
std::random_device r;
|
||||
std::default_random_engine e1(r());
|
||||
const siv::PerlinNoise::seed_type seed = e1();
|
||||
noise = siv::PerlinNoise{seed};
|
||||
}
|
||||
bool getCell(unsigned int x, unsigned int y) override {
|
||||
auto val = noise.noise2D_01(x * scale, y * scale);
|
||||
return val >= (1. - density);
|
||||
}
|
||||
double scale, density;
|
||||
siv::PerlinNoise noise{};
|
||||
};
|
||||
|
||||
struct MaxAgeProvider {
|
||||
public:
|
||||
virtual void setup(unsigned int width, unsigned int height) {
|
||||
this->grid_width = width;
|
||||
this->grid_height = height;
|
||||
}
|
||||
virtual uint16_t getAt(uint16_t x, uint16_t y, uint64_t generation) = 0;
|
||||
virtual uint16_t global_max() const = 0;
|
||||
virtual uint16_t global_min() const = 0;
|
||||
virtual bool is_dynamic() const {
|
||||
return false;
|
||||
}
|
||||
virtual uint16_t dynamic_step() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
unsigned int grid_width{};
|
||||
unsigned int grid_height{};
|
||||
};
|
||||
|
||||
struct UniformMaxAgeProvider : MaxAgeProvider {
|
||||
private:
|
||||
uint16_t value_;
|
||||
|
||||
public:
|
||||
explicit UniformMaxAgeProvider(uint16_t value) : value_(value) {}
|
||||
uint16_t getAt(uint16_t x, uint16_t y, uint64_t generation) override {
|
||||
return value_;
|
||||
}
|
||||
[[nodiscard]] uint16_t global_max() const override {
|
||||
return value_;
|
||||
}
|
||||
[[nodiscard]] uint16_t global_min() const override {
|
||||
return value_;
|
||||
}
|
||||
[[nodiscard]] uint16_t value() const { return value_; }
|
||||
};
|
||||
struct RadialMaxAgeProvider : MaxAgeProvider {
|
||||
private:
|
||||
uint16_t center_;
|
||||
uint16_t corners_;
|
||||
|
||||
public:
|
||||
RadialMaxAgeProvider(uint16_t center, uint16_t corners) : center_(center), corners_(corners) {}
|
||||
uint16_t getAt(uint16_t x, uint16_t y, uint64_t generation) override {
|
||||
auto cx = static_cast<double>(grid_width) / 2.;
|
||||
auto cy = static_cast<double>(grid_height) / 2.;
|
||||
auto corner_center_distance = sqrt(pow(cx, 2.) + pow(cy, 2.));
|
||||
auto px = static_cast<double>(x);
|
||||
auto py = static_cast<double>(y);
|
||||
auto point_center_distance = sqrt(pow(px - cx, 2.) + pow(py - cy, 2.));
|
||||
auto center_val = static_cast<double>(center_);
|
||||
auto corner_val = static_cast<double>(corners_);
|
||||
auto val = center_val + point_center_distance / corner_center_distance * (corner_val - center_val);
|
||||
return static_cast<uint16_t>(round(val));
|
||||
}
|
||||
[[nodiscard]] uint16_t global_max() const override {
|
||||
return center_ > corners_ ? center_ : corners_;
|
||||
}
|
||||
[[nodiscard]] uint16_t global_min() const override {
|
||||
return center_ < corners_ ? center_ : corners_;
|
||||
}
|
||||
[[nodiscard]] uint16_t center() const { return center_; }
|
||||
[[nodiscard]] uint16_t corners() const { return corners_; }
|
||||
};
|
||||
struct RadialFitMaxAgeProvider : MaxAgeProvider {
|
||||
private:
|
||||
uint16_t center_;
|
||||
uint16_t corners_;
|
||||
|
||||
public:
|
||||
RadialFitMaxAgeProvider(uint16_t center, uint16_t corners) : center_(center), corners_(corners) {}
|
||||
uint16_t getAt(uint16_t x, uint16_t y, uint64_t generation) override {
|
||||
auto cx = static_cast<double>(grid_width) / 2.;
|
||||
auto cy = static_cast<double>(grid_height) / 2.;
|
||||
auto px = static_cast<double>(x);
|
||||
auto py = static_cast<double>(y);
|
||||
auto point_center_distance = sqrt(pow((px - cx) / cx, 2.) + pow((py - cy) / cy, 2.));
|
||||
auto center_val = static_cast<double>(center_);
|
||||
auto corner_val = static_cast<double>(corners_);
|
||||
auto val = center_val + point_center_distance * (corner_val - center_val);
|
||||
return static_cast<uint16_t>(round(val));
|
||||
}
|
||||
[[nodiscard]] uint16_t global_max() const override {
|
||||
return center_ > corners_ ? center_ : corners_;
|
||||
}
|
||||
[[nodiscard]] uint16_t global_min() const override {
|
||||
return center_ < corners_ ? center_ : corners_;
|
||||
}
|
||||
[[nodiscard]] uint16_t center() const { return center_; }
|
||||
[[nodiscard]] uint16_t corners() const { return corners_; }
|
||||
};
|
||||
struct PerlinStaticMaxAgeProvider : MaxAgeProvider {
|
||||
private:
|
||||
double scale_;
|
||||
uint16_t min_;
|
||||
uint16_t max_;
|
||||
|
||||
siv::PerlinNoise noise{};
|
||||
|
||||
public:
|
||||
PerlinStaticMaxAgeProvider(double scale, uint16_t min, uint16_t max) : scale_(scale), min_(min), max_(max) {
|
||||
std::random_device r;
|
||||
std::default_random_engine e1(r());
|
||||
const siv::PerlinNoise::seed_type seed = e1();
|
||||
noise = siv::PerlinNoise{seed};
|
||||
}
|
||||
uint16_t getAt(uint16_t x, uint16_t y, uint64_t generation) override {
|
||||
auto x_ = static_cast<double>(x) * scale_;
|
||||
auto y_ = static_cast<double>(y) * scale_;
|
||||
auto v = noise.noise2D_01(x_, y_);
|
||||
return static_cast<uint16_t>(round(min_ + (max_ - min_) * v));
|
||||
}
|
||||
[[nodiscard]] uint16_t global_max() const override {
|
||||
return max_;
|
||||
}
|
||||
[[nodiscard]] uint16_t global_min() const override {
|
||||
return min_;
|
||||
}
|
||||
[[nodiscard]] double scale() const { return scale_; }
|
||||
[[nodiscard]] uint16_t min() const { return min_; }
|
||||
[[nodiscard]] uint16_t max() const { return max_; }
|
||||
};
|
||||
struct PerlinDynamicMaxAgeProvider : MaxAgeProvider {
|
||||
private:
|
||||
double scale_;
|
||||
uint16_t min_;
|
||||
uint16_t max_;
|
||||
double time_scale_;
|
||||
uint16_t time_step_;
|
||||
|
||||
siv::PerlinNoise noise{};
|
||||
|
||||
public:
|
||||
PerlinDynamicMaxAgeProvider(double scale, uint16_t min, uint16_t max, double time_scale, uint16_t time_step)
|
||||
: scale_(scale), min_(min), max_(max), time_scale_(time_scale), time_step_(time_step) {
|
||||
std::random_device r;
|
||||
std::default_random_engine e1(r());
|
||||
const siv::PerlinNoise::seed_type seed = e1();
|
||||
noise = siv::PerlinNoise{seed};
|
||||
}
|
||||
uint16_t getAt(uint16_t x, uint16_t y, uint64_t generation) override {
|
||||
auto x_ = static_cast<double>(x) * scale_;
|
||||
auto y_ = static_cast<double>(y) * scale_;
|
||||
auto t_ = static_cast<double>(generation) / static_cast<double>(time_step_) * time_scale_;
|
||||
auto v = noise.noise3D_01(x_, y_, t_);
|
||||
return static_cast<uint16_t>(round(min_ + (max_ - min_) * v));
|
||||
}
|
||||
[[nodiscard]] uint16_t global_max() const override {
|
||||
return max_;
|
||||
}
|
||||
[[nodiscard]] uint16_t global_min() const override {
|
||||
return min_;
|
||||
}
|
||||
[[nodiscard]] bool is_dynamic() const override {
|
||||
return true;
|
||||
}
|
||||
[[nodiscard]] uint16_t dynamic_step() const override {
|
||||
return time_step_;
|
||||
}
|
||||
[[nodiscard]] double scale() const { return scale_; }
|
||||
[[nodiscard]] uint16_t min() const { return min_; }
|
||||
[[nodiscard]] uint16_t max() const { return max_; }
|
||||
[[nodiscard]] double time_scale() const { return time_scale_; }
|
||||
[[nodiscard]] uint16_t time_step() const { return time_step_; }
|
||||
};
|
||||
|
||||
struct DisplayOptions {
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
bool fullscreen;
|
||||
size_t fullscreen_screen_number;
|
||||
Color dead_color;
|
||||
std::variant<Color, ColorMapOption> alive_color;
|
||||
uint64_t generation_duration_ms;
|
||||
bool blend;
|
||||
};
|
||||
|
||||
struct AutomatonRule {
|
||||
std::vector<uint8_t> birth;
|
||||
std::vector<uint8_t> survive;
|
||||
std::shared_ptr<MaxAgeProvider> max_age;
|
||||
uint8_t starve_delay;
|
||||
bool starve_recover;
|
||||
};
|
||||
|
||||
struct AutomatonOptions {
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
// bool wrap_edges;
|
||||
AutomatonRule rule;
|
||||
std::shared_ptr<GridInitialiser> initialiser;
|
||||
};
|
||||
|
||||
struct Options {
|
||||
AutomatonOptions automaton_options;
|
||||
DisplayOptions display_options;
|
||||
};
|
||||
|
||||
Options parse_arguments(int argc, const char *const *argv);
|
||||
|
||||
#endif // SHADER_AUTOMATON_CLI_PARSE_H
|
1099
src/program.cpp
Normal file
1099
src/program.cpp
Normal file
File diff suppressed because it is too large
Load diff
118
src/program.h
Normal file
118
src/program.h
Normal file
|
@ -0,0 +1,118 @@
|
|||
#ifndef SHADER_AUTOMATON_PROGRAM_H
|
||||
#define SHADER_AUTOMATON_PROGRAM_H
|
||||
|
||||
#include "options.h"
|
||||
#include <GL/glew.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
|
||||
class Program {
|
||||
public:
|
||||
explicit Program(Options opts);
|
||||
~Program();
|
||||
|
||||
void run();
|
||||
|
||||
void keyCallback(int key, int scancode, int action, int mods);
|
||||
|
||||
private:
|
||||
struct BufferInfo {
|
||||
GLuint bufferName;
|
||||
GLuint bindingPoint;
|
||||
};
|
||||
|
||||
void createWindow();
|
||||
[[nodiscard]] GLsync startCompute(BufferInfo in, BufferInfo out) const;
|
||||
void renderFrame(BufferInfo from, BufferInfo to, double step) const;
|
||||
void renderUI();
|
||||
void initializeFirstBuffer(BufferInfo buffer) const;
|
||||
void toggleFullscreen();
|
||||
|
||||
void imguiMainWindow();
|
||||
void imguiCloseWindow();
|
||||
|
||||
Options options;
|
||||
|
||||
GLFWwindow *window{};
|
||||
int window_pos_x{};
|
||||
int window_pos_y{};
|
||||
int window_width{};
|
||||
int window_height{};
|
||||
|
||||
GLuint renderProgram{};
|
||||
GLuint renderBlockIndexFrom{};
|
||||
GLuint renderBlockIndexTo{};
|
||||
GLint renderPreviewColormapLoc{};
|
||||
GLint renderDimensionsLoc{};
|
||||
GLint renderShowMaxAgeLoc{};
|
||||
GLint renderGlobalMaxAgeLoc{};
|
||||
GLint renderGlobalMinimumMaxAgeLoc{};
|
||||
GLint renderBlendStepLoc{};
|
||||
GLint renderDeadColorLoc{};
|
||||
GLint renderDeadColorIsCielabLoc{};
|
||||
GLint renderLivingColorLoc{};
|
||||
GLint renderLivingColormapLoc{};
|
||||
GLint renderLivingUseColormapLoc{};
|
||||
GLint renderLivingColormapInvertLoc{};
|
||||
GLint renderLivingColormapScaleLoc{};
|
||||
GLint renderLivingColormapScaleIsMaxAgeLoc{};
|
||||
GLint renderLivingColorIsCielabLoc{};
|
||||
|
||||
GLuint computeProgram{};
|
||||
GLuint computeBlockIndexInput{};
|
||||
GLuint computeBlockIndexOutput{};
|
||||
GLint computeDimensionsLoc{};
|
||||
GLint computeBirthRuleLoc{};
|
||||
GLint computeSurviveRuleLoc{};
|
||||
GLint computeStarveDelayLoc{};
|
||||
GLint computeStarveRecoverLoc{};
|
||||
GLint computeHasMaxAgeLoc{};
|
||||
|
||||
std::map<std::string, GLuint> colormap_textures{};
|
||||
|
||||
GLuint vertexBuffer{};
|
||||
|
||||
GLFWmonitor *fullscreenMonitor{};
|
||||
|
||||
bool is_fullscreen = false;
|
||||
bool show_ui = false;
|
||||
bool show_close_popup = false;
|
||||
|
||||
bool is_paused{}; // todo
|
||||
uint8_t render_max_age{};
|
||||
bool preview_colormap{};
|
||||
|
||||
void debugPrintBuffer(const std::string &text, BufferInfo buffer) const {
|
||||
return;
|
||||
auto width = options.automaton_options.width;
|
||||
auto height = options.automaton_options.height;
|
||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer.bufferName);
|
||||
auto *bufferData = static_cast<unsigned int *>(glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY));
|
||||
std::cout << "=====[" << text << " (" << buffer.bufferName << ")]=====" << std::endl;
|
||||
auto offset = width * height;
|
||||
for (size_t ix = 0; ix < width; ++ix) {
|
||||
for (size_t iy = 0; iy < height; ++iy) {
|
||||
std::cout << std::format("max-age at {}, {}: {}",ix, iy, bufferData[offset + ix + iy * width]) << std::endl;
|
||||
}
|
||||
}
|
||||
std::cout << "===============" << std::endl
|
||||
<< std::endl;
|
||||
std::cout << std::flush;
|
||||
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
|
||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
|
||||
}
|
||||
|
||||
void imguiAutomatonTab();
|
||||
void imguiDisplayTab();
|
||||
bool imguiBirthConditionTable(bool apply_changes, bool revert_changes);
|
||||
bool imguiMaxAge(bool apply_changes, bool revert_changes);
|
||||
bool imguiSurviveConditionTable(bool apply_changes, bool revert_changes);
|
||||
bool imguiStarve(bool apply_changes, bool revert_changes);
|
||||
void imguiDeadCells();
|
||||
void imguiLivingCells();
|
||||
uint32_t makeRuleBitfield(const std::vector<uint8_t> &rule) const;
|
||||
void initializeMaxAge(BufferInfo buffer, uint64_t generation) const;
|
||||
};
|
||||
|
||||
#endif // SHADER_AUTOMATON_PROGRAM_H
|
25
src/rule_presets.cpp
Normal file
25
src/rule_presets.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// Created by gwendolyn on 5/4/23.
|
||||
//
|
||||
|
||||
#include "rule_presets.h"
|
||||
|
||||
const std::map<std::string, AutomatonRule> rule_presets = {
|
||||
{"maze",
|
||||
{.birth = {3},
|
||||
.survive = {1, 2, 3, 4, 5},
|
||||
.max_age = nullptr,
|
||||
.starve_delay = 0,
|
||||
.starve_recover = false}},
|
||||
{"castle",
|
||||
{.birth = {4, 5, 6, 7, 8},
|
||||
.survive = {2, 3, 4, 5},
|
||||
.max_age = nullptr,
|
||||
.starve_delay = 0,
|
||||
.starve_recover = false}},
|
||||
{"conway",
|
||||
{.birth = {3},
|
||||
.survive = {2, 3},
|
||||
.max_age = nullptr,
|
||||
.starve_delay = 0,
|
||||
.starve_recover = false}}};
|
11
src/rule_presets.h
Normal file
11
src/rule_presets.h
Normal file
|
@ -0,0 +1,11 @@
|
|||
#ifndef SHADER_AUTOMATON_RULE_PRESETS_H
|
||||
#define SHADER_AUTOMATON_RULE_PRESETS_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "options.h"
|
||||
|
||||
|
||||
extern const std::map<std::string, AutomatonRule> rule_presets;
|
||||
|
||||
#endif // SHADER_AUTOMATON_RULE_PRESETS_H
|
51
src/shader-helper.cpp
Normal file
51
src/shader-helper.cpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#include "shader-helper.h"
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
|
||||
GLuint loadShader(std::string_view name, std::span<const std::byte> source, GLenum shader_type) {
|
||||
GLuint shaderId = glCreateShader(shader_type);
|
||||
GLint result = GL_FALSE;
|
||||
int infoLogLength;
|
||||
|
||||
auto data = reinterpret_cast<const char*>(source.data());
|
||||
auto size = static_cast<GLint>(source.size());
|
||||
|
||||
glShaderSource(shaderId, 1, &data, &size);
|
||||
glCompileShader(shaderId);
|
||||
glGetShaderiv(shaderId, GL_COMPILE_STATUS, &result);
|
||||
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &infoLogLength);
|
||||
if (infoLogLength > 0) {
|
||||
std::cout << "errors in shader " << name << std::endl;
|
||||
std::vector<char> VertexShaderErrorMessage(infoLogLength + 1);
|
||||
glGetShaderInfoLog(shaderId, infoLogLength, nullptr,
|
||||
&VertexShaderErrorMessage[0]);
|
||||
std::cout << &VertexShaderErrorMessage[0] << std::endl;
|
||||
std::cout << "=====" << std::endl;
|
||||
std::cout << std::string_view(data, size) << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
return shaderId;
|
||||
}
|
||||
|
||||
GLuint loadProgram(std::string_view name, const std::vector<GLuint> &shaders) {
|
||||
GLuint programId = glCreateProgram();
|
||||
for (const auto &item : shaders) {
|
||||
glAttachShader(programId, item);
|
||||
}
|
||||
glLinkProgram(programId);
|
||||
|
||||
GLint result = GL_FALSE;
|
||||
int infoLogLength;
|
||||
|
||||
glGetProgramiv(programId, GL_LINK_STATUS, &result);
|
||||
glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &infoLogLength);
|
||||
if (infoLogLength > 0) {
|
||||
std::cout << "errors in program " << name << std::endl;
|
||||
std::vector<char> ProgramErrorMessage(infoLogLength + 1);
|
||||
glGetProgramInfoLog(programId, infoLogLength, nullptr,
|
||||
&ProgramErrorMessage[0]);
|
||||
std::cout << &ProgramErrorMessage[0] << std::endl;
|
||||
}
|
||||
return programId;
|
||||
}
|
13
src/shader-helper.h
Normal file
13
src/shader-helper.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#ifndef SHADER_AUTOMATON_SHADER_HELPER_H
|
||||
#define SHADER_AUTOMATON_SHADER_HELPER_H
|
||||
|
||||
#include <GL/glew.h>
|
||||
#include <vector>
|
||||
#include <string_view>
|
||||
#include <span>
|
||||
|
||||
GLuint loadShader(std::string_view name, std::span<const std::byte> sources, GLenum shader_type);
|
||||
|
||||
GLuint loadProgram(std::string_view name, const std::vector<GLuint> &shaders);
|
||||
|
||||
#endif // SHADER_AUTOMATON_SHADER_HELPER_H
|
229
tests/color.cpp
Normal file
229
tests/color.cpp
Normal file
|
@ -0,0 +1,229 @@
|
|||
#include "gtest/gtest.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "color.h"
|
||||
|
||||
static std::array<uint8_t, 3> white_rgb = {255, 255, 255};
|
||||
static std::array<double, 3> white_cielab = {100, 0, 0};
|
||||
static std::array<double, 3> white_cieluv = {100, 0, 0};
|
||||
static std::array<double, 3> white_oklab = {1, 0, 0};
|
||||
|
||||
static std::array<uint8_t, 3> black_rgb = {0, 0, 0};
|
||||
static std::array<double, 3> black_cielab = {0, 0, 0};
|
||||
static std::array<double, 3> black_cieluv = {0, 0, 0};
|
||||
static std::array<double, 3> black_oklab = {0, 0, 0};
|
||||
|
||||
static std::array<uint8_t, 3> tiny_rgb = {4, 31, 13};
|
||||
static std::array<double, 3> tiny_cielab = {9.2779, -14.8940, 8.1560};
|
||||
static std::array<double, 3> tiny_cieluv = {9.2779, -7.2368, 6.6930};
|
||||
static std::array<double, 3> tiny_oklab = {0.2125, -0.0429, 0.023};
|
||||
|
||||
static std::array<uint8_t, 3> big_rgb = {247, 252, 255};
|
||||
static std::array<double, 3> big_cielab = {98.6759, -1.1049, -2.006};
|
||||
static std::array<double, 3> big_cieluv = {98.6759, -2.9261, -2.9267};
|
||||
static std::array<double, 3> big_oklab = {0.9882, -0.0039, -0.0054};
|
||||
|
||||
|
||||
static std::array<uint8_t, 3> medium_rgb = {89, 69, 219};
|
||||
static std::array<double, 3> medium_cielab = {40.3991, 49.4085, -73.8982};
|
||||
static std::array<double, 3> medium_cieluv = {40.3991, -2.9720, -108.8107};
|
||||
static std::array<double, 3> medium_oklab = {0.5074, 0.0429, -0.2129};
|
||||
|
||||
static double round_to_4_decimals(double val) {
|
||||
return std::round(val * 10000.) / 10000.;
|
||||
}
|
||||
|
||||
static void assert_equal_color_arr(Color c, std::array<double, 3> a) {
|
||||
ASSERT_DOUBLE_EQ(round_to_4_decimals(c.v1), a[0]);
|
||||
ASSERT_DOUBLE_EQ(round_to_4_decimals(c.v2), a[1]);
|
||||
ASSERT_DOUBLE_EQ(round_to_4_decimals(c.v3), a[2]);
|
||||
}
|
||||
|
||||
static void assert_equal_rgb(std::array<double, 3> a1, std::array<uint8_t, 3> a2) {
|
||||
ASSERT_EQ(static_cast<int>(round(a1[0]*255.)), a2[0]);
|
||||
ASSERT_EQ(static_cast<int>(round(a1[1]*255.)), a2[1]);
|
||||
ASSERT_EQ(static_cast<int>(round(a1[2]*255.)), a2[2]);
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
TEST(RgbToOklab, White) {
|
||||
auto color = Color::from_rgb(white_rgb, ColorSpace::OKLAB);
|
||||
assert_equal_color_arr(color, white_oklab);
|
||||
}
|
||||
|
||||
TEST(RgbToOklab, Black) {
|
||||
auto color = Color::from_rgb(black_rgb, ColorSpace::OKLAB);
|
||||
assert_equal_color_arr(color, black_oklab);
|
||||
}
|
||||
|
||||
TEST(RgbToOklab, TinyValues) {
|
||||
auto color = Color::from_rgb(tiny_rgb, ColorSpace::OKLAB);
|
||||
assert_equal_color_arr(color, tiny_oklab);
|
||||
}
|
||||
|
||||
TEST(RgbToOklab, BigValues) {
|
||||
auto color = Color::from_rgb(big_rgb, ColorSpace::OKLAB);
|
||||
assert_equal_color_arr(color, big_oklab);
|
||||
}
|
||||
|
||||
TEST(RgbToOklab, MediumValues) {
|
||||
auto color = Color::from_rgb(medium_rgb, ColorSpace::OKLAB);
|
||||
assert_equal_color_arr(color, medium_oklab);
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
TEST(OklabToRgb, White) {
|
||||
auto color = Color{white_oklab, ColorSpace::OKLAB}.to_normalized_rgb();
|
||||
assert_equal_rgb(color, white_rgb);
|
||||
}
|
||||
|
||||
TEST(OklabToRgb, Black) {
|
||||
auto color = Color{black_oklab, ColorSpace::OKLAB}.to_normalized_rgb();
|
||||
assert_equal_rgb(color, black_rgb);
|
||||
}
|
||||
|
||||
TEST(OklabToRgb, TinyValues) {
|
||||
auto color = Color{tiny_oklab, ColorSpace::OKLAB}.to_normalized_rgb();
|
||||
assert_equal_rgb(color, tiny_rgb);
|
||||
}
|
||||
|
||||
TEST(OklabToRgb, BigValues) {
|
||||
auto color = Color{big_oklab, ColorSpace::OKLAB}.to_normalized_rgb();
|
||||
assert_equal_rgb(color, big_rgb);
|
||||
}
|
||||
|
||||
TEST(OklabToRgb, MediumValues) {
|
||||
auto color = Color{medium_oklab, ColorSpace::OKLAB}.to_normalized_rgb();
|
||||
assert_equal_rgb(color, medium_rgb);
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
TEST(RgbToCielab, White) {
|
||||
auto color = Color::from_rgb(white_rgb, ColorSpace::CIELAB);
|
||||
assert_equal_color_arr(color, white_cielab);
|
||||
}
|
||||
|
||||
TEST(RgbToCielab, Black) {
|
||||
auto color = Color::from_rgb(black_rgb, ColorSpace::CIELAB);
|
||||
assert_equal_color_arr(color, black_cielab);
|
||||
}
|
||||
|
||||
TEST(RgbToCielab, TinyValues) {
|
||||
auto color = Color::from_rgb(tiny_rgb, ColorSpace::CIELAB);
|
||||
assert_equal_color_arr(color, tiny_cielab);
|
||||
}
|
||||
|
||||
TEST(RgbToCielab, BigValues) {
|
||||
auto color = Color::from_rgb(big_rgb, ColorSpace::CIELAB);
|
||||
assert_equal_color_arr(color, big_cielab);
|
||||
}
|
||||
|
||||
TEST(RgbToCielab, MediumValues) {
|
||||
auto color = Color::from_rgb(medium_rgb, ColorSpace::CIELAB);
|
||||
assert_equal_color_arr(color, medium_cielab);
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
TEST(CielabToRgb, White) {
|
||||
auto color = Color{white_cielab, ColorSpace::CIELAB}.to_normalized_rgb();
|
||||
assert_equal_rgb(color, white_rgb);
|
||||
}
|
||||
|
||||
TEST(CielabToRgb, Black) {
|
||||
auto color = Color{black_cielab, ColorSpace::CIELAB}.to_normalized_rgb();
|
||||
assert_equal_rgb(color, black_rgb);
|
||||
}
|
||||
|
||||
TEST(CielabToRgb, TinyValues) {
|
||||
auto color = Color{tiny_cielab, ColorSpace::CIELAB}.to_normalized_rgb();
|
||||
assert_equal_rgb(color, tiny_rgb);
|
||||
}
|
||||
|
||||
TEST(CielabToRgb, BigValues) {
|
||||
auto color = Color{big_cielab, ColorSpace::CIELAB}.to_normalized_rgb();
|
||||
assert_equal_rgb(color, big_rgb);
|
||||
}
|
||||
|
||||
TEST(CielabToRgb, MediumValues) {
|
||||
auto color = Color{medium_cielab, ColorSpace::CIELAB}.to_normalized_rgb();
|
||||
assert_equal_rgb(color, medium_rgb);
|
||||
}
|
||||
|
||||
/////
|
||||
|
||||
TEST(RgbToCieluv, White) {
|
||||
auto color = Color::from_rgb(white_rgb, ColorSpace::CIELUV);
|
||||
assert_equal_color_arr(color, white_cieluv);
|
||||
}
|
||||
|
||||
TEST(RgbToCieluv, Black) {
|
||||
auto color = Color::from_rgb(black_rgb, ColorSpace::CIELUV);
|
||||
assert_equal_color_arr(color, black_cieluv);
|
||||
}
|
||||
|
||||
TEST(RgbToCieluv, TinyValues) {
|
||||
auto color = Color::from_rgb(tiny_rgb, ColorSpace::CIELUV);
|
||||
assert_equal_color_arr(color, tiny_cieluv);
|
||||
}
|
||||
|
||||
TEST(RgbToCieluv, BigValues) {
|
||||
auto color = Color::from_rgb(big_rgb, ColorSpace::CIELUV);
|
||||
assert_equal_color_arr(color, big_cieluv);
|
||||
}
|
||||
|
||||
TEST(RgbToCieluv, MediumValues) {
|
||||
auto color = Color::from_rgb(medium_rgb, ColorSpace::CIELUV);
|
||||
assert_equal_color_arr(color, medium_cieluv);
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
TEST(CieluvToRgb, White) {
|
||||
auto color = Color{white_cieluv, ColorSpace::CIELUV}.to_normalized_rgb();
|
||||
assert_equal_rgb(color, white_rgb);
|
||||
}
|
||||
|
||||
TEST(CieluvToRgb, Black) {
|
||||
auto color = Color{black_cieluv, ColorSpace::CIELUV}.to_normalized_rgb();
|
||||
assert_equal_rgb(color, black_rgb);
|
||||
}
|
||||
|
||||
TEST(CieluvToRgb, TinyValues) {
|
||||
auto color = Color{tiny_cieluv, ColorSpace::CIELUV}.to_normalized_rgb();
|
||||
assert_equal_rgb(color, tiny_rgb);
|
||||
}
|
||||
|
||||
TEST(CieluvToRgb, BigValues) {
|
||||
auto color = Color{big_cieluv, ColorSpace::CIELUV}.to_normalized_rgb();
|
||||
assert_equal_rgb(color, big_rgb);
|
||||
}
|
||||
|
||||
TEST(CieluvToRgb, MediumValues) {
|
||||
auto color = Color{medium_cieluv, ColorSpace::CIELUV}.to_normalized_rgb();
|
||||
assert_equal_rgb(color, medium_rgb);
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
TEST(RgbNormalisation, FromRgb) {
|
||||
auto color1 = Color::from_rgb(medium_rgb);
|
||||
auto color2 = Color::from_normalized_rgb({
|
||||
static_cast<double>(medium_rgb[0])/255.,
|
||||
static_cast<double>(medium_rgb[1])/255.,
|
||||
static_cast<double>(medium_rgb[2])/255.,
|
||||
});
|
||||
ASSERT_DOUBLE_EQ(color1.v1, color2.v1);
|
||||
ASSERT_DOUBLE_EQ(color1.v2, color2.v2);
|
||||
ASSERT_DOUBLE_EQ(color1.v3, color2.v3);
|
||||
}
|
||||
|
||||
TEST(RgbNormalisation, ToRgb) {
|
||||
auto color1 = Color{medium_cielab}.to_rgb();
|
||||
auto color2 = Color{medium_cielab}.to_normalized_rgb();
|
||||
assert_equal_rgb(color2, color1);
|
||||
}
|
Loading…
Reference in a new issue