diff --git a/CMakeLists.txt b/CMakeLists.txt index 7152675..6c135a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.25) -project(image-gp-6 VERSION 0.0.13) +project(image-gp-6 VERSION 0.0.14) include(FetchContent) diff --git a/include/config.h b/include/config.h index fe3760e..5277839 100644 --- a/include/config.h +++ b/include/config.h @@ -33,7 +33,7 @@ static constexpr blt::size_t DATA_SIZE = IMAGE_SIZE * IMAGE_SIZE; static constexpr blt::size_t DATA_CHANNELS_SIZE = DATA_SIZE * CHANNELS; static constexpr blt::size_t BOX_COUNT = static_cast(log2(IMAGE_SIZE / 2)); static constexpr float THRESHOLD = 0.3; -static constexpr auto load_image = "../029a_-_Survival_of_the_Idiots_349.jpg"; +static constexpr auto load_image = "../GSab4SWWcAA1TNR.png"; blt::gp::prog_config_t config = blt::gp::prog_config_t() .set_initial_min_tree_size(4) @@ -44,6 +44,6 @@ blt::gp::prog_config_t config = blt::gp::prog_config_t() .set_crossover_chance(1.0) .set_reproduction_chance(0.5) .set_pop_size(POP_SIZE) - .set_thread_count(16); + .set_thread_count(0); #endif //IMAGE_GP_6_CONFIG_H diff --git a/include/custom_transformer.h b/include/custom_transformer.h new file mode 100644 index 0000000..0a3b550 --- /dev/null +++ b/include/custom_transformer.h @@ -0,0 +1,22 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef IMAGE_GP_6_CUSTOM_TRANSFORMER_H +#define IMAGE_GP_6_CUSTOM_TRANSFORMER_H + +#endif //IMAGE_GP_6_CUSTOM_TRANSFORMER_H diff --git a/lib/blt-gp b/lib/blt-gp index 5d72923..f62494d 160000 --- a/lib/blt-gp +++ b/lib/blt-gp @@ -1 +1 @@ -Subproject commit 5d72923998fd59cf76a7e777642a9c34e76bfb3d +Subproject commit f62494d7d896ec3a10266a878e644771ac84aebb diff --git a/src/custom_transformer.cpp b/src/custom_transformer.cpp new file mode 100644 index 0000000..ebd4c47 --- /dev/null +++ b/src/custom_transformer.cpp @@ -0,0 +1,18 @@ +/* + * + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index dc0cd99..acde118 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -44,6 +44,56 @@ blt::gfx::resource_manager resources; blt::gfx::batch_renderer_2d renderer_2d(resources, global_matrices); blt::gfx::first_person_camera_2d camera; +static constexpr blt::size_t TYPE_COUNT = 1; + +std::array fitness_values{}; +double last_fitness = 0; +double hovered_fitness = 0; +double hovered_fitness_value = 0; +bool evaluate = true; + +std::array has_literal_converter = { + true +}; + +std::array, TYPE_COUNT> literal_crossover_funcs = { + [](blt::gp::gp_program&, void* p1_in_ptr, void* p2_in_ptr, void* c1_out_ptr, void* c2_out_ptr) { + auto& p1_in = *static_cast(p1_in_ptr); + auto& p2_in = *static_cast(p2_in_ptr); + auto& c1_out = *static_cast(c1_out_ptr); + auto& c2_out = *static_cast(c2_out_ptr); + + for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) + { + auto diff = p1_in.rgb_data[i] - p2_in.rgb_data[i]; + c1_out.rgb_data[i] = p1_in.rgb_data[i] - diff; + c2_out.rgb_data[i] = p2_in.rgb_data[i] + diff; + } + } +}; + +std::array, TYPE_COUNT> literal_mutation_funcs = { + [](blt::gp::gp_program& program, void* p1_in_ptr, void* c1_out_ptr) { + auto& p1_in = *static_cast(p1_in_ptr); + auto& c1_out = *static_cast(c1_out_ptr); + + for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) + c1_out.rgb_data[i] = p1_in.rgb_data[i] + program.get_random().get_float(-1.0f, 1.0f); + } +}; + +class image_crossover_t : public blt::gp::crossover_t +{ + public: + blt::expected apply(blt::gp::gp_program& program, const blt::gp::tree_t& p1, const blt::gp::tree_t& p2) final + { + auto sel = program.get_random().choice(); + if (sel) + return blt::gp::crossover_t::apply(program, p1, p2); + std::abort(); + } +}; + struct context { float x, y; @@ -126,6 +176,14 @@ blt::gp::operation_t bitwise_or([](const full_image_t& a, const full_image_t& b) return img; }, "or"); +blt::gp::operation_t bitwise_invert([](const full_image_t& a) { + using blt::mem::type_cast; + full_image_t img{}; + for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) + img.rgb_data[i] = static_cast(~type_cast(a.rgb_data[i])); + return img; +}, "invert"); + blt::gp::operation_t bitwise_xor([](const full_image_t& a, const full_image_t& b) { using blt::mem::type_cast; full_image_t img{}; @@ -201,10 +259,13 @@ blt::gp::operation_t random_val([]() { i = program.get_random().get_float(0.0f, 1.0f); return img; }, "color_noise"); -static blt::gp::operation_t perlin([](const full_image_t& x, const full_image_t& y, const full_image_t& z) { +static blt::gp::operation_t perlin([](const full_image_t& x, const full_image_t& y, const full_image_t& z, const full_image_t& scale) { full_image_t img{}; for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) - img.rgb_data[i] = stb_perlin_noise3(x.rgb_data[i], y.rgb_data[i], z.rgb_data[i], 0, 0, 0); + { + auto s = scale.rgb_data[i]; + img.rgb_data[i] = stb_perlin_noise3(x.rgb_data[i] / s, y.rgb_data[i] / s, z.rgb_data[i] / s, 0, 0, 0); + } return img; }, "perlin"); static blt::gp::operation_t perlin_terminal([]() { @@ -212,10 +273,21 @@ static blt::gp::operation_t perlin_terminal([]() { for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) { auto ctx = get_ctx(i); - img.rgb_data[i] = stb_perlin_noise3(ctx.x / IMAGE_SIZE, ctx.y / IMAGE_SIZE, static_cast(i % CHANNELS) / CHANNELS, 0, 0, 0); + img.rgb_data[i] = + stb_perlin_noise3(ctx.x / IMAGE_SIZE, ctx.y / IMAGE_SIZE, static_cast(i % CHANNELS) / CHANNELS, 0, 0, 0); } return img; }, "perlin_term"); +static blt::gp::operation_t perlin_warped([](const full_image_t& u, const full_image_t& v) { + full_image_t img{}; + for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) + { + auto ctx = get_ctx(i); + img.rgb_data[i] = stb_perlin_noise3(ctx.x / IMAGE_SIZE + u.rgb_data[i], ctx.y / IMAGE_SIZE + v.rgb_data[i], + static_cast(i % CHANNELS) / CHANNELS, 0, 0, 0); + } + return img; +}, "perlin_warped"); static blt::gp::operation_t op_img_size([]() { full_image_t img{}; for (float& i : img.rgb_data) @@ -389,22 +461,28 @@ constexpr auto create_fitness_function() { return [](blt::gp::tree_t& current_tree, blt::gp::fitness_t& fitness, blt::size_t index) { auto& v = generation_images[index]; - v = current_tree.get_evaluation_value(nullptr); - - fitness.raw_fitness = 0; - for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) - fitness.raw_fitness += compare_values(v.rgb_data[i], base_image.rgb_data[i]); - - fitness.raw_fitness /= (IMAGE_SIZE * IMAGE_SIZE); - auto raw = get_fractal_value(v); - auto fit = std::max(0.0, 1.0 - std::abs(1.35 - raw.combined)); - auto fit2 = std::max(0.0, 1.0 - std::abs(1.35 - raw.total)); - //BLT_DEBUG("Fitness %lf %lf %lf || %lf => %lf (fit: %lf)", raw.r, raw.g, raw.b, raw.total, raw.combined, fit); - if (std::isnan(raw.total) || std::isnan(raw.combined)) - fitness.raw_fitness += 400; - else - fitness.raw_fitness += raw.total + raw.combined + 1.0; + if (evaluate) + v = current_tree.get_evaluation_value(nullptr); + if (fitness_values[index] < 0) + { + fitness.raw_fitness = 0; + for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) + fitness.raw_fitness += compare_values(v.rgb_data[i], base_image.rgb_data[i]); + + fitness.raw_fitness /= (IMAGE_SIZE * IMAGE_SIZE); + auto raw = get_fractal_value(v); + auto fit = std::max(0.0, 1.0 - std::abs(1.35 - raw.combined)); + auto fit2 = std::max(0.0, 1.0 - std::abs(1.35 - raw.total)); + //BLT_DEBUG("Fitness %lf %lf %lf || %lf => %lf (fit: %lf)", raw.r, raw.g, raw.b, raw.total, raw.combined, fit); + if (std::isnan(raw.total) || std::isnan(raw.combined)) + fitness.raw_fitness += 400; + else + fitness.raw_fitness += raw.total + raw.combined + 1.0; + + fitness.raw_fitness += last_fitness; + } else + fitness.raw_fitness = fitness_values[index]; fitness.standardized_fitness = fitness.raw_fitness; fitness.adjusted_fitness = (1.0 / (1.0 + fitness.standardized_fitness)); }; @@ -413,18 +491,29 @@ constexpr auto create_fitness_function() void execute_generation() { BLT_TRACE("------------{Begin Generation %ld}------------", program.get_current_generation()); + BLT_TRACE("Evaluate Fitness"); + BLT_START_INTERVAL("Image Test", "Fitness"); + evaluate = false; + program.evaluate_fitness(); + BLT_END_INTERVAL("Image Test", "Fitness"); BLT_START_INTERVAL("Image Test", "Gen"); auto sel = blt::gp::select_tournament_t{}; +// auto sel = blt::gp::select_fitness_proportionate_t{}; program.create_next_generation(sel, sel, sel); BLT_END_INTERVAL("Image Test", "Gen"); BLT_TRACE("Move to next generation"); program.next_generation(); - BLT_TRACE("Evaluate Fitness"); - BLT_START_INTERVAL("Image Test", "Fitness"); + BLT_TRACE("Evaluate Image"); + BLT_START_INTERVAL("Image Test", "Image Eval"); + evaluate = true; program.evaluate_fitness(); - BLT_END_INTERVAL("Image Test", "Fitness"); + BLT_END_INTERVAL("Image Test", "Image Eval"); BLT_TRACE("----------------------------------------------"); std::cout << std::endl; + // reset all fitness values. + for (auto& v : fitness_values) + v = -1; + last_fitness = 0; } void print_stats() @@ -474,8 +563,9 @@ void init(const blt::gfx::window_data&) type_system.register_type(); blt::gp::operator_builder builder{type_system}; - //builder.add_operator(perlin); + builder.add_operator(perlin); builder.add_operator(perlin_terminal); + builder.add_operator(perlin_warped); builder.add_operator(add); builder.add_operator(sub); @@ -490,18 +580,20 @@ void init(const blt::gfx::window_data&) builder.add_operator(op_v_mod); builder.add_operator(bitwise_and); builder.add_operator(bitwise_or); + builder.add_operator(bitwise_invert); builder.add_operator(bitwise_xor); builder.add_operator(dissolve); builder.add_operator(hsv_to_rgb); builder.add_operator(lit, true); builder.add_operator(random_val); - builder.add_operator(op_x_r); - builder.add_operator(op_x_g); - builder.add_operator(op_x_b); - builder.add_operator(op_y_r); - builder.add_operator(op_y_g); - builder.add_operator(op_y_b); + const bool state = false; + builder.add_operator(op_x_r, true); + builder.add_operator(op_x_g, true); + builder.add_operator(op_x_b, state); + builder.add_operator(op_y_r, state); + builder.add_operator(op_y_g, state); + builder.add_operator(op_y_b, state); program.set_operations(builder.build()); @@ -523,7 +615,7 @@ void update(const blt::gfx::window_data& data) if (ImGui::Begin("Program Control")) { ImGui::Button("Run Generation"); - if (ImGui::IsItemClicked() && !is_running) + if ((ImGui::IsItemClicked() || (blt::gfx::isKeyPressed(GLFW_KEY_R) && blt::gfx::keyPressedLastFrame())) && !is_running) { run_generation = true; } @@ -535,6 +627,9 @@ void update(const blt::gfx::window_data& data) ImGui::Text("Best fitness: %lf", stats.best_fitness.load()); ImGui::Text("Worst fitness: %lf", stats.worst_fitness.load()); ImGui::Text("Overall fitness: %lf", stats.overall_fitness.load()); + ImGui::Separator(); + ImGui::Text("Hovered Fitness: %lf", hovered_fitness); + ImGui::Text("Hovered Fitness Value: %lf", hovered_fitness_value); ImGui::End(); } @@ -557,37 +652,39 @@ void update(const blt::gfx::window_data& data) renderer_2d.drawRectangleInternal(blt::make_color(0.9, 0.9, 0.3), {x, y, IMAGE_SIZE + IMAGE_PADDING / 2.0f, IMAGE_SIZE + IMAGE_PADDING / 2.0f}, 10.0f); + auto& ind = program.get_current_pop().get_individuals()[i]; auto& io = ImGui::GetIO(); if (io.WantCaptureMouse) continue; - if (blt::gfx::mousePressedLastFrame()) + hovered_fitness = ind.fitness.adjusted_fitness; + + if (blt::gfx::isKeyPressed(GLFW_KEY_LEFT_SHIFT)) { - auto& ind = program.get_current_pop().get_individuals()[i]; - std::cout << "Fitness: " << ind.fitness.adjusted_fitness << " " << ind.fitness.raw_fitness << " "; - ind.tree.print(program, std::cout, false); - std::cout << std::endl; + if (blt::gfx::mousePressedLastFrame()) + { + std::cout << "Fitness: " << ind.fitness.adjusted_fitness << " " << ind.fitness.raw_fitness << " "; + ind.tree.print(program, std::cout, false); + std::cout << std::endl; + } + } else + { + if (blt::gfx::mousePressedLastFrame() || (blt::gfx::isKeyPressed(GLFW_KEY_F) && blt::gfx::keyPressedLastFrame())) + { + fitness_values[i] = last_fitness; + last_fitness += 1; + } } - -// if (blt::gfx::mousePressedLastFrame()) -// { -// if (blt::gfx::isKeyPressed(GLFW_KEY_LEFT_SHIFT)) -// { -// program_red.get_current_pop().get_individuals()[i].fitness.raw_fitness = static_cast(last_fitness); -// program_green.get_current_pop().get_individuals()[i].fitness.raw_fitness = static_cast(last_fitness); -// program_blue.get_current_pop().get_individuals()[i].fitness.raw_fitness = static_cast(last_fitness); -// if (blt::gfx::mousePressedLastFrame()) -// last_fitness++; -// } else -// selected_image = i; -// } + + hovered_fitness_value = fitness_values[i]; } - renderer_2d.drawRectangleInternal(blt::make_vec4(blt::vec3(program.get_current_pop().get_individuals()[i].fitness.adjusted_fitness), 1.0), - {x, y, IMAGE_SIZE + IMAGE_PADDING / 2.0f, IMAGE_SIZE + IMAGE_PADDING / 2.0f}, - 5.0f); + renderer_2d.drawRectangleInternal( + blt::make_vec4(blt::vec3(static_cast(program.get_current_pop().get_individuals()[i].fitness.adjusted_fitness)), 1.0), + {x, y, IMAGE_SIZE + IMAGE_PADDING / 2.0f, IMAGE_SIZE + IMAGE_PADDING / 2.0f}, + 5.0f); renderer_2d.drawRectangleInternal(blt::gfx::render_info_t::make_info(std::to_string(i)), {x, y, IMAGE_SIZE, @@ -605,6 +702,9 @@ void update(const blt::gfx::window_data& data) int main() { + // reset all fitness values. + for (auto& v : fitness_values) + v = -1; blt::gfx::init(blt::gfx::window_data{"My Sexy Window", init, update, 1440, 720}.setSyncInterval(1)); global_matrices.cleanup(); resources.cleanup();