Impl edit tri color, and more

Added way to select and edit colors of placed Tris.
Added third_party catch unit test framework that builds the UnitTest in
Debug builds.
Refactor internal use of notification_text.
Rename src/imgui_helper.hpp to src/helpers.hpp
Added some helper functions, including "is_within_shape" (used for
selecting a Tri).
Fixed use of flags in helpers not using enum values.
This commit is contained in:
Stephen Seo 2020-08-04 21:13:17 +09:00
parent 72e1675f5f
commit d30a792aca
8 changed files with 18042 additions and 55 deletions

View file

@ -12,13 +12,21 @@ Please update the GameDevTools submodule by running 'git submodule init' and \
'git submodule update'!") 'git submodule update'!")
endif() endif()
set(ImGuiDemo "") if(NOT DEFINED CMAKE_BUILD_TYPE)
if((NOT CMAKE_BUILD_TYPE) OR (${CMAKE_BUILD_TYPE} MATCHES "Debug")) set(CMAKE_BUILD_TYPE "Debug")
set(ImGuiDemo "third_party/imgui/imgui_demo.cpp")
endif() endif()
set(Triangles_SOURCES if(CMAKE_BUILD_TYPE MATCHES "Debug")
set(ImGuiDemo "third_party/imgui/imgui_demo.cpp")
else()
set(ImGuiDemo "")
endif()
set(Triangles_MAIN_SOURCES
src/main.cpp src/main.cpp
)
set(Triangles_LIB_SOURCES
src/state.cpp src/state.cpp
third_party/imgui/imgui.cpp third_party/imgui/imgui.cpp
third_party/imgui/imgui_draw.cpp third_party/imgui/imgui_draw.cpp
@ -30,9 +38,13 @@ set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wpedantic -Wsuggest-override")
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g") set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -D NDEBUG") set(CMAKE_CXX_FLAGS_RELEASE "-O3 -D NDEBUG")
add_executable(Triangles ${Triangles_SOURCES}) add_library(TrianglesLib STATIC ${Triangles_LIB_SOURCES})
target_compile_features(Triangles PUBLIC cxx_std_17) add_executable(Triangles ${Triangles_MAIN_SOURCES})
target_link_libraries(Triangles PUBLIC TrianglesLib)
target_compile_features(TrianglesLib PUBLIC cxx_std_17)
if(BUILD_SHARED_LIBS OR (UNIX AND NOT CYGWIN)) if(BUILD_SHARED_LIBS OR (UNIX AND NOT CYGWIN))
find_package(SFML 2 REQUIRED find_package(SFML 2 REQUIRED
@ -43,16 +55,29 @@ else()
add_definitions(-DSFML_STATIC) add_definitions(-DSFML_STATIC)
endif() endif()
target_link_libraries(Triangles target_link_libraries(TrianglesLib PUBLIC
sfml-graphics sfml-window sfml-system sfml-graphics sfml-window sfml-system
GL GL
) )
target_include_directories(Triangles PUBLIC target_include_directories(TrianglesLib PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/src
${SFML_INCLUDE_DIR} ${SFML_INCLUDE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/build_include # imgui related headers ${CMAKE_CURRENT_SOURCE_DIR}/build_include # imgui related headers
) )
# Use macro to override imgui config header # Use macro to override imgui config header
target_compile_definitions(Triangles PRIVATE target_compile_definitions(TrianglesLib PRIVATE
"IMGUI_USER_CONFIG=\"${CMAKE_CURRENT_SOURCE_DIR}/third_party/imgui-sfml/imconfig-SFML.h\"") "IMGUI_USER_CONFIG=\"${CMAKE_CURRENT_SOURCE_DIR}/third_party/imgui-sfml/imconfig-SFML.h\"")
if(CMAKE_BUILD_TYPE MATCHES "Debug")
set(Triangles_UNIT_TEST_SOURCES
src/unittest/test_main.cpp
src/unittest/test_helpers.cpp
)
add_executable(UnitTest_Triangles ${Triangles_UNIT_TEST_SOURCES})
target_link_libraries(UnitTest_Triangles TrianglesLib)
target_include_directories(UnitTest_Triangles PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/third_party/catch
)
endif()

View file

@ -1,6 +1,9 @@
#ifndef TRIANGLES_IMGUI_HELPER_HPP #ifndef TRIANGLES_IMGUI_HELPER_HPP
#define TRIANGLES_IMGUI_HELPER_HPP #define TRIANGLES_IMGUI_HELPER_HPP
#include <cmath>
#include <optional>
#include <imgui.h> #include <imgui.h>
#include "state.hpp" #include "state.hpp"
@ -16,7 +19,7 @@ namespace Tri {
// Seems misleading, but imgui handles setting up the window during update // Seems misleading, but imgui handles setting up the window during update
// so this should be called during update, not draw // so this should be called during update, not draw
inline void draw_help(Tri::State *state) { inline void draw_help(Tri::State *state) {
if(state->get_flags().test(0)) { if(state->get_flags().test(Tri::State::F_DISPLAY_HELP)) {
ImGui::SetNextWindowPos(sf::Vector2f(10.0f, 10.0f)); ImGui::SetNextWindowPos(sf::Vector2f(10.0f, 10.0f));
ImGui::SetNextWindowSize(sf::Vector2f( ImGui::SetNextWindowSize(sf::Vector2f(
state->get_width() - 20.0f, state->get_width() - 20.0f,
@ -63,7 +66,7 @@ namespace Tri {
} }
inline void draw_color_picker(Tri::State *state) { inline void draw_color_picker(Tri::State *state) {
if(state->get_flags().test(2)) { if(state->get_flags().test(Tri::State::F_DISPLAY_COLOR_P)) {
ImGui::Begin("Tri Color Picker"); ImGui::Begin("Tri Color Picker");
ImGui::ColorPicker4("Tri Color", state->get_color()); ImGui::ColorPicker4("Tri Color", state->get_color());
if(ImGui::Button("Close")) { if(ImGui::Button("Close")) {
@ -74,7 +77,7 @@ namespace Tri {
} }
inline void draw_bg_color_picker(Tri::State *state) { inline void draw_bg_color_picker(Tri::State *state) {
if(state->get_flags().test(5)) { if(state->get_flags().test(Tri::State::F_DISPLAY_BG_COLOR_P)) {
ImGui::Begin("BG Color Picker"); ImGui::Begin("BG Color Picker");
ImGui::ColorPicker3("BG Color", state->get_bg_color()); ImGui::ColorPicker3("BG Color", state->get_bg_color());
if(ImGui::Button("Close")) { if(ImGui::Button("Close")) {
@ -85,7 +88,7 @@ namespace Tri {
} }
inline void draw_save(Tri::State *state) { inline void draw_save(Tri::State *state) {
if(state->get_flags().test(6)) { if(state->get_flags().test(Tri::State::F_DISPLAY_SAVE)) {
auto *filenameBuffer = state->get_save_filename_buffer(); auto *filenameBuffer = state->get_save_filename_buffer();
ImGui::Begin("Save"); ImGui::Begin("Save");
ImGui::InputText("Filename", filenameBuffer->data(), filenameBuffer->size() - 1); ImGui::InputText("Filename", filenameBuffer->data(), filenameBuffer->size() - 1);
@ -105,7 +108,7 @@ namespace Tri {
} }
inline void draw_change_size(Tri::State *state) { inline void draw_change_size(Tri::State *state) {
if(state->get_flags().test(10)) { if(state->get_flags().test(Tri::State::F_DISPLAY_CHANGE_SIZE)) {
ImGui::Begin("ChangeSize"); ImGui::Begin("ChangeSize");
ImGui::InputInt2("Width and Height", state->get_input_width_height()); ImGui::InputInt2("Width and Height", state->get_input_width_height());
auto string_view = state->failed_message(); auto string_view = state->failed_message();
@ -123,6 +126,59 @@ namespace Tri {
ImGui::End(); ImGui::End();
} }
} }
inline bool is_within_shape(
const sf::ConvexShape &shape,
sf::Vector2f xy) {
std::optional<bool> is_right;
sf::Transform t = shape.getTransform();
for(unsigned int i = 0; i < shape.getPointCount(); ++i) {
sf::Vector2f t_a = t.transformPoint(shape.getPoint(i));
sf::Vector2f t_b = t.transformPoint(shape.getPoint((i + 1) % shape.getPointCount()));
t_b = t_b - t_a;
t_a = xy - t_a;
// TODO
// cross product, where z coordinate is 0
// Use sign of z value to determine if line is to right or left
//
// a x b = c
// a_1 b_1 0
// a_2 b_2 0
// 0 0 a_1 * b_2 - a_2 * b_1
//
// in this case "a" is "t_b"
float z = t_b.x * t_a.y - t_b.y * t_a.x;
if(is_right.has_value()) {
if(is_right.value()) {
if(z >= 0.0f) {
return false;
}
} else if(z < 0.0f) {
return false;
}
} else {
is_right = z < 0.0f;
}
}
return true;
}
inline sf::Color invert_color(const sf::Color &other) {
return sf::Color(255 - other.r, 255 - other.g, 255 - other.b);
}
inline void draw_edit_tri(Tri::State *state) {
if(state->get_flags().test(Tri::State::F_TRI_EDIT_MODE)) {
ImGui::Begin("Edit Tri Color Picker");
ImGui::ColorPicker4("Tri Color", state->get_selected_tri_color());
if(ImGui::Button("Close")) {
state->close_selected_tri_mode();
}
ImGui::End();
}
}
} }
#endif #endif

View file

@ -6,7 +6,6 @@
#include <imgui-SFML.h> #include <imgui-SFML.h>
#include "state.hpp" #include "state.hpp"
#include "imgui_helper.hpp"
int main(int argc, char **argv) { int main(int argc, char **argv) {
// init // init

View file

@ -3,10 +3,11 @@
#include <cstring> #include <cstring>
#include <cassert> #include <cassert>
#include <string> #include <string>
#include <cmath>
#include <imgui-SFML.h> #include <imgui-SFML.h>
#include "imgui_helper.hpp" #include "helpers.hpp"
#define STARTING_HELP_FADE_RATE 0.2f #define STARTING_HELP_FADE_RATE 0.2f
@ -26,14 +27,14 @@ currentTri_maxState(CurrentState::NONE),
colorPickerColor{1.0f, 1.0f, 1.0f, 1.0f}, colorPickerColor{1.0f, 1.0f, 1.0f, 1.0f},
bgColorPickerColor{0.0f, 0.0f, 0.0f}, bgColorPickerColor{0.0f, 0.0f, 0.0f},
bgColor(sf::Color::Black), bgColor(sf::Color::Black),
inputWidthHeight{800, 600} inputWidthHeight{800, 600},
pi(std::acos(-1.0f))
{ {
flags.set(F_IS_RUNNING); // is running flags.set(F_IS_RUNNING); // is running
ImGui::SFML::Init(window); ImGui::SFML::Init(window);
window.setFramerateLimit(60); window.setFramerateLimit(60);
notification_text.fill(0); set_notification_text("Press \"H\" for help");
std::strncpy(notification_text.data(), "Press \"H\" for help", notification_text.max_size() - 1);
pointCircle.setRadius(7.0f); pointCircle.setRadius(7.0f);
pointCircle.setOrigin(7.0f, 7.0f); pointCircle.setOrigin(7.0f, 7.0f);
@ -135,15 +136,12 @@ void Tri::State::handle_events() {
} else if(event.key.code == sf::Keyboard::P) { } else if(event.key.code == sf::Keyboard::P) {
flags.flip(F_COPY_COLOR_MODE); flags.flip(F_COPY_COLOR_MODE);
if(flags.test(F_COPY_COLOR_MODE)) { if(flags.test(F_COPY_COLOR_MODE)) {
notification_text.fill(0); set_notification_text(
std::strncpy(notification_text.data(),
"Copy color mode\n" "Copy color mode\n"
"Click to change\n" "Click to change\n"
"current draw color\n" "current draw color\n"
"to what was\n" "to what was\n"
"clicked on", "clicked on");
notification_text.max_size() - 1);
notification_alpha = 1.0f;
} else { } else {
notification_alpha = 0.0f; notification_alpha = 0.0f;
} }
@ -153,6 +151,15 @@ void Tri::State::handle_events() {
inputWidthHeight[0] = width; inputWidthHeight[0] = width;
inputWidthHeight[1] = height; inputWidthHeight[1] = height;
} }
} else if(event.key.code == sf::Keyboard::E) {
if(flags.test(F_TRI_EDIT_MODE)) {
close_selected_tri_mode();
} else {
flags.flip(F_SELECT_TRI_MODE);
if(flags.test(F_SELECT_TRI_MODE)) {
set_notification_text("Click on a tri\nto edit it");
}
}
} }
} }
} else if(event.type == sf::Event::MouseButtonPressed) { } else if(event.type == sf::Event::MouseButtonPressed) {
@ -199,11 +206,28 @@ void Tri::State::handle_events() {
colorPickerColor[3] = 1.0f; colorPickerColor[3] = 1.0f;
pointCircle.setFillColor(color); pointCircle.setFillColor(color);
flags.reset(F_COPY_COLOR_MODE); flags.reset(F_COPY_COLOR_MODE);
notification_text.fill(0); set_notification_text("Color set");
std::strncpy(notification_text.data(), } else if(flags.test(F_SELECT_TRI_MODE)) {
"Color set", sf::Vector2f mouseXY = window.mapPixelToCoords(
notification_text.max_size() - 1); {event.mouseButton.x, event.mouseButton.y});
notification_alpha = 1.0f; for(unsigned int i = trisIndex; i-- > 0; ) {
if(is_within_shape(tris.at(i), mouseXY)) {
selectedTri = i;
tris[i].setOutlineColor(invert_color(tris[i].getFillColor()));
flags.reset(F_SELECT_TRI_MODE);
flags.set(F_TRI_EDIT_MODE);
flags.set(F_TRI_EDIT_DRAW_TRI);
selectedTriBlinkTimer = 1.0f;
selectedTriColor[0] = tris[i].getFillColor().r / 255.0f;
selectedTriColor[1] = tris[i].getFillColor().g / 255.0f;
selectedTriColor[2] = tris[i].getFillColor().b / 255.0f;
selectedTriColor[3] = tris[i].getFillColor().a / 255.0f;
break;
}
}
if(!flags.test(F_TRI_EDIT_MODE)) {
set_notification_text("Did not select\nanything");
}
} }
} }
} }
@ -235,10 +259,19 @@ void Tri::State::update() {
bgColor.b = (unsigned char)(255 * bgColorPickerColor[2]); bgColor.b = (unsigned char)(255 * bgColorPickerColor[2]);
} }
if(flags.test(F_TRI_EDIT_MODE)) {
selectedTriBlinkTimer -= dt.asSeconds() * TRIANGLES_EDIT_TRI_BLINK_RATE;
if(selectedTriBlinkTimer <= 0.0f) {
selectedTriBlinkTimer = 1.0f;
flags.flip(F_TRI_EDIT_DRAW_TRI);
}
}
// Seems misleading, but imgui handles setting up the window during update // Seems misleading, but imgui handles setting up the window during update
Tri::draw_notification(this); Tri::draw_notification(this);
Tri::draw_color_picker(this); Tri::draw_color_picker(this);
Tri::draw_bg_color_picker(this); Tri::draw_bg_color_picker(this);
Tri::draw_edit_tri(this);
Tri::draw_change_size(this); Tri::draw_change_size(this);
Tri::draw_save(this); Tri::draw_save(this);
Tri::draw_help(this); Tri::draw_help(this);
@ -260,9 +293,17 @@ void Tri::State::draw() {
draw_to_target(&window); draw_to_target(&window);
} }
for(unsigned int i = 0; i < currentTri_state; ++i) { if(flags.test(F_TRI_EDIT_MODE) && flags.test(F_TRI_EDIT_DRAW_TRI)) {
pointCircle.setPosition(currentTri[i]); tris.at(selectedTri).setOutlineThickness(4.0f);
window.draw(pointCircle); window.draw(tris[selectedTri]);
tris.at(selectedTri).setOutlineThickness(0.0f);
}
if(can_draw()) {
for(unsigned int i = 0; i < currentTri_state; ++i) {
pointCircle.setPosition(currentTri[i]);
window.draw(pointCircle);
}
} }
// draw gui stuff // draw gui stuff
@ -300,6 +341,14 @@ const char* Tri::State::get_notification_text() const {
return notification_text.data(); return notification_text.data();
} }
void Tri::State::set_notification_text(const char *text) {
notification_text.fill(0);
std::strncpy(notification_text.data(),
text,
notification_text.max_size() - 1);
notification_alpha = 1.0f;
}
float* Tri::State::get_color() { float* Tri::State::get_color() {
flags.set(F_COLOR_P_COLOR_DIRTY); flags.set(F_COLOR_P_COLOR_DIRTY);
return colorPickerColor; return colorPickerColor;
@ -358,7 +407,20 @@ bool Tri::State::can_draw() const {
&& !flags.test(F_DISPLAY_BG_COLOR_P) && !flags.test(F_DISPLAY_BG_COLOR_P)
&& !flags.test(F_DISPLAY_SAVE) && !flags.test(F_DISPLAY_SAVE)
&& !flags.test(F_COPY_COLOR_MODE) && !flags.test(F_COPY_COLOR_MODE)
&& !flags.test(F_DISPLAY_CHANGE_SIZE); && !flags.test(F_DISPLAY_CHANGE_SIZE)
&& !flags.test(F_SELECT_TRI_MODE)
&& !flags.test(F_TRI_EDIT_MODE);
}
void Tri::State::reset_modes() {
flags.reset(F_DISPLAY_HELP);
flags.reset(F_DISPLAY_COLOR_P);
flags.reset(F_DISPLAY_BG_COLOR_P);
flags.reset(F_DISPLAY_SAVE);
flags.reset(F_COPY_COLOR_MODE);
flags.reset(F_DISPLAY_CHANGE_SIZE);
flags.reset(F_SELECT_TRI_MODE);
flags.reset(F_TRI_EDIT_MODE);
} }
void Tri::State::close_help() { void Tri::State::close_help() {
@ -391,29 +453,11 @@ bool Tri::State::change_width_height() {
} }
if(warnings.test(0) && warnings.test(1)) { if(warnings.test(0) && warnings.test(1)) {
notification_alpha = 1.0f; set_notification_text("Width set to 200\nHeight set to 150");
notification_text.fill(0);
std::strncpy(
notification_text.data(),
"Width set to 200\nHeight set to 150",
notification_text.max_size() - 1
);
} else if(warnings.test(0)) { } else if(warnings.test(0)) {
notification_alpha = 1.0f; set_notification_text("Width set to 200");
notification_text.fill(0);
std::strncpy(
notification_text.data(),
"Width set to 200",
notification_text.max_size() - 1
);
} else if(warnings.test(1)) { } else if(warnings.test(1)) {
notification_alpha = 1.0f; set_notification_text("Height set to 150");
notification_text.fill(0);
std::strncpy(
notification_text.data(),
"Height set to 150",
notification_text.max_size() - 1
);
} }
this->width = inputWidthHeight[0]; this->width = inputWidthHeight[0];
@ -441,3 +485,26 @@ int* Tri::State::get_input_width_height() {
void Tri::State::close_input_width_height_window() { void Tri::State::close_input_width_height_window() {
flags.reset(F_DISPLAY_CHANGE_SIZE); flags.reset(F_DISPLAY_CHANGE_SIZE);
} }
float Tri::State::get_pi() const {
return pi;
}
float* Tri::State::get_selected_tri_color() {
tris.at(selectedTri).setFillColor(sf::Color(
(unsigned char)(255.0f * selectedTriColor[0]),
(unsigned char)(255.0f * selectedTriColor[1]),
(unsigned char)(255.0f * selectedTriColor[2]),
(unsigned char)(255.0f * selectedTriColor[3])));
return selectedTriColor;
}
void Tri::State::close_selected_tri_mode() {
tris.at(selectedTri).setFillColor(sf::Color(
(unsigned char)(255.0f * selectedTriColor[0]),
(unsigned char)(255.0f * selectedTriColor[1]),
(unsigned char)(255.0f * selectedTriColor[2]),
(unsigned char)(255.0f * selectedTriColor[3])));
flags.set(F_DRAW_CACHE_DIRTY);
reset_modes();
}

View file

@ -1,6 +1,8 @@
#ifndef TRIANGLES_STATE_HPP #ifndef TRIANGLES_STATE_HPP
#define TRIANGLES_STATE_HPP #define TRIANGLES_STATE_HPP
#define TRIANGLES_EDIT_TRI_BLINK_RATE 2.0f
#include <bitset> #include <bitset>
#include <vector> #include <vector>
#include <array> #include <array>
@ -15,7 +17,6 @@ namespace Tri {
~State(); ~State();
enum CurrentState {NONE = 0, FIRST = 1, SECOND = 2}; enum CurrentState {NONE = 0, FIRST = 1, SECOND = 2};
private:
enum FlagName { enum FlagName {
F_DISPLAY_HELP = 0, F_DISPLAY_HELP = 0,
F_IS_RUNNING = 1, F_IS_RUNNING = 1,
@ -28,8 +29,12 @@ namespace Tri {
F_DRAW_CACHE_INITIALIZED = 8, F_DRAW_CACHE_INITIALIZED = 8,
F_COPY_COLOR_MODE = 9, F_COPY_COLOR_MODE = 9,
F_DISPLAY_CHANGE_SIZE = 10, F_DISPLAY_CHANGE_SIZE = 10,
F_SELECT_TRI_MODE = 11,
F_TRI_EDIT_MODE = 12,
F_TRI_EDIT_DRAW_TRI = 13,
}; };
private:
// use enum FlagName // use enum FlagName
typedef std::bitset<64> BitsetType; typedef std::bitset<64> BitsetType;
BitsetType flags; BitsetType flags;
@ -63,6 +68,12 @@ namespace Tri {
int inputWidthHeight[2]; int inputWidthHeight[2];
const float pi;
unsigned int selectedTri;
float selectedTriColor[4];
float selectedTriBlinkTimer;
public: public:
void handle_events(); void handle_events();
void update(); void update();
@ -80,6 +91,10 @@ namespace Tri {
float get_notification_alpha() const; float get_notification_alpha() const;
const char* get_notification_text() const; const char* get_notification_text() const;
private:
void set_notification_text(const char *text);
public:
float* get_color(); float* get_color();
float* get_bg_color(); float* get_bg_color();
@ -90,6 +105,7 @@ namespace Tri {
private: private:
bool can_draw() const; bool can_draw() const;
void reset_modes();
public: public:
void close_help(); void close_help();
@ -100,6 +116,11 @@ namespace Tri {
int* get_input_width_height(); int* get_input_width_height();
void close_input_width_height_window(); void close_input_width_height_window();
float get_pi() const;
float* get_selected_tri_color();
void close_selected_tri_mode();
}; };
} }

View file

@ -0,0 +1,18 @@
#include "catch.hpp"
#include <SFML/Graphics.hpp>
#include "helpers.hpp"
TEST_CASE("Test is_within_shape", "[Triangles]") {
sf::ConvexShape shape;
shape.setPointCount(3);
shape.setPoint(0, {0.0f, 10.0f});
shape.setPoint(1, {10.0f, 10.0f});
shape.setPoint(2, {10.0f, 0.0f});
CHECK(Tri::is_within_shape(shape, {2.0f, 2.0f}) == false);
CHECK(Tri::is_within_shape(shape, {5.0f, 15.0f}) == false);
CHECK(Tri::is_within_shape(shape, {15.0f, 5.0f}) == false);
CHECK(Tri::is_within_shape(shape, {7.0f, 7.0f}) == true);
}

View file

@ -0,0 +1,2 @@
#define CATCH_CONFIG_MAIN
#include "catch.hpp"

17799
third_party/catch/catch.hpp vendored Normal file

File diff suppressed because it is too large Load diff