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'!")
endif()
set(ImGuiDemo "")
if((NOT CMAKE_BUILD_TYPE) OR (${CMAKE_BUILD_TYPE} MATCHES "Debug"))
set(ImGuiDemo "third_party/imgui/imgui_demo.cpp")
if(NOT DEFINED CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
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
)
set(Triangles_LIB_SOURCES
src/state.cpp
third_party/imgui/imgui.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_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))
find_package(SFML 2 REQUIRED
@ -43,16 +55,29 @@ else()
add_definitions(-DSFML_STATIC)
endif()
target_link_libraries(Triangles
target_link_libraries(TrianglesLib PUBLIC
sfml-graphics sfml-window sfml-system
GL
)
target_include_directories(Triangles PUBLIC
target_include_directories(TrianglesLib PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src
${SFML_INCLUDE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/build_include # imgui related headers
)
# 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\"")
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
#define TRIANGLES_IMGUI_HELPER_HPP
#include <cmath>
#include <optional>
#include <imgui.h>
#include "state.hpp"
@ -16,7 +19,7 @@ namespace Tri {
// Seems misleading, but imgui handles setting up the window during update
// so this should be called during update, not draw
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::SetNextWindowSize(sf::Vector2f(
state->get_width() - 20.0f,
@ -63,7 +66,7 @@ namespace Tri {
}
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::ColorPicker4("Tri Color", state->get_color());
if(ImGui::Button("Close")) {
@ -74,7 +77,7 @@ namespace Tri {
}
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::ColorPicker3("BG Color", state->get_bg_color());
if(ImGui::Button("Close")) {
@ -85,7 +88,7 @@ namespace Tri {
}
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();
ImGui::Begin("Save");
ImGui::InputText("Filename", filenameBuffer->data(), filenameBuffer->size() - 1);
@ -105,7 +108,7 @@ namespace Tri {
}
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::InputInt2("Width and Height", state->get_input_width_height());
auto string_view = state->failed_message();
@ -123,6 +126,59 @@ namespace Tri {
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

View file

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

View file

@ -3,10 +3,11 @@
#include <cstring>
#include <cassert>
#include <string>
#include <cmath>
#include <imgui-SFML.h>
#include "imgui_helper.hpp"
#include "helpers.hpp"
#define STARTING_HELP_FADE_RATE 0.2f
@ -26,14 +27,14 @@ currentTri_maxState(CurrentState::NONE),
colorPickerColor{1.0f, 1.0f, 1.0f, 1.0f},
bgColorPickerColor{0.0f, 0.0f, 0.0f},
bgColor(sf::Color::Black),
inputWidthHeight{800, 600}
inputWidthHeight{800, 600},
pi(std::acos(-1.0f))
{
flags.set(F_IS_RUNNING); // is running
ImGui::SFML::Init(window);
window.setFramerateLimit(60);
notification_text.fill(0);
std::strncpy(notification_text.data(), "Press \"H\" for help", notification_text.max_size() - 1);
set_notification_text("Press \"H\" for help");
pointCircle.setRadius(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) {
flags.flip(F_COPY_COLOR_MODE);
if(flags.test(F_COPY_COLOR_MODE)) {
notification_text.fill(0);
std::strncpy(notification_text.data(),
set_notification_text(
"Copy color mode\n"
"Click to change\n"
"current draw color\n"
"to what was\n"
"clicked on",
notification_text.max_size() - 1);
notification_alpha = 1.0f;
"clicked on");
} else {
notification_alpha = 0.0f;
}
@ -153,6 +151,15 @@ void Tri::State::handle_events() {
inputWidthHeight[0] = width;
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) {
@ -199,11 +206,28 @@ void Tri::State::handle_events() {
colorPickerColor[3] = 1.0f;
pointCircle.setFillColor(color);
flags.reset(F_COPY_COLOR_MODE);
notification_text.fill(0);
std::strncpy(notification_text.data(),
"Color set",
notification_text.max_size() - 1);
notification_alpha = 1.0f;
set_notification_text("Color set");
} else if(flags.test(F_SELECT_TRI_MODE)) {
sf::Vector2f mouseXY = window.mapPixelToCoords(
{event.mouseButton.x, event.mouseButton.y});
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]);
}
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
Tri::draw_notification(this);
Tri::draw_color_picker(this);
Tri::draw_bg_color_picker(this);
Tri::draw_edit_tri(this);
Tri::draw_change_size(this);
Tri::draw_save(this);
Tri::draw_help(this);
@ -260,9 +293,17 @@ void Tri::State::draw() {
draw_to_target(&window);
}
for(unsigned int i = 0; i < currentTri_state; ++i) {
pointCircle.setPosition(currentTri[i]);
window.draw(pointCircle);
if(flags.test(F_TRI_EDIT_MODE) && flags.test(F_TRI_EDIT_DRAW_TRI)) {
tris.at(selectedTri).setOutlineThickness(4.0f);
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
@ -300,6 +341,14 @@ const char* Tri::State::get_notification_text() const {
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() {
flags.set(F_COLOR_P_COLOR_DIRTY);
return colorPickerColor;
@ -358,7 +407,20 @@ bool Tri::State::can_draw() const {
&& !flags.test(F_DISPLAY_BG_COLOR_P)
&& !flags.test(F_DISPLAY_SAVE)
&& !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() {
@ -391,29 +453,11 @@ bool Tri::State::change_width_height() {
}
if(warnings.test(0) && warnings.test(1)) {
notification_alpha = 1.0f;
notification_text.fill(0);
std::strncpy(
notification_text.data(),
"Width set to 200\nHeight set to 150",
notification_text.max_size() - 1
);
set_notification_text("Width set to 200\nHeight set to 150");
} else if(warnings.test(0)) {
notification_alpha = 1.0f;
notification_text.fill(0);
std::strncpy(
notification_text.data(),
"Width set to 200",
notification_text.max_size() - 1
);
set_notification_text("Width set to 200");
} else if(warnings.test(1)) {
notification_alpha = 1.0f;
notification_text.fill(0);
std::strncpy(
notification_text.data(),
"Height set to 150",
notification_text.max_size() - 1
);
set_notification_text("Height set to 150");
}
this->width = inputWidthHeight[0];
@ -441,3 +485,26 @@ int* Tri::State::get_input_width_height() {
void Tri::State::close_input_width_height_window() {
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
#define TRIANGLES_STATE_HPP
#define TRIANGLES_EDIT_TRI_BLINK_RATE 2.0f
#include <bitset>
#include <vector>
#include <array>
@ -15,7 +17,6 @@ namespace Tri {
~State();
enum CurrentState {NONE = 0, FIRST = 1, SECOND = 2};
private:
enum FlagName {
F_DISPLAY_HELP = 0,
F_IS_RUNNING = 1,
@ -28,8 +29,12 @@ namespace Tri {
F_DRAW_CACHE_INITIALIZED = 8,
F_COPY_COLOR_MODE = 9,
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
typedef std::bitset<64> BitsetType;
BitsetType flags;
@ -63,6 +68,12 @@ namespace Tri {
int inputWidthHeight[2];
const float pi;
unsigned int selectedTri;
float selectedTriColor[4];
float selectedTriBlinkTimer;
public:
void handle_events();
void update();
@ -80,6 +91,10 @@ namespace Tri {
float get_notification_alpha() const;
const char* get_notification_text() const;
private:
void set_notification_text(const char *text);
public:
float* get_color();
float* get_bg_color();
@ -90,6 +105,7 @@ namespace Tri {
private:
bool can_draw() const;
void reset_modes();
public:
void close_help();
@ -100,6 +116,11 @@ namespace Tri {
int* get_input_width_height();
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