Compare commits

..

No commits in common. "1da2ddb1250ce045925788c3a00a9c30773c38ea" and "ef19ff326a043b9c5aa8db170c24750aff0052d4" have entirely different histories.

35 changed files with 18337 additions and 24680 deletions

View file

@ -1,15 +0,0 @@
name: Run UnitTests
on:
push:
branches:
- '*'
jobs:
build-and-run-tests:
runs-on: any_archLinux
steps:
- run: git clone --depth=1 --no-single-branch https://git.seodisparate.com/stephenseo/Triangles.git Tri
- run: cd Tri && git checkout $GITHUB_REF_NAME
- run: cd Tri && git submodule update --init --recursive
- run: cd Tri && cmake -S . -B buildDebug -DCMAKE_BUILD_TYPE=Debug
- run: make -C Tri/buildDebug UnitTest_Triangles && ./Tri/buildDebug/UnitTest_Triangles

View file

@ -1,24 +0,0 @@
name: Run UnitTests
on:
push:
branches:
- '*'
jobs:
build-and-run-tests:
runs-on: ubuntu-latest
steps:
- name: Install dependencies
run: sudo /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get install cmake git patch libasound2-dev libx11-dev libxrandr-dev libxi-dev libgl1-mesa-dev libglu1-mesa-dev libxcursor-dev libxinerama-dev
- name: Fetch Raylib sources
run: git clone https://github.com/raysan5/raylib raylib && cd raylib && git checkout 5.0
- name: Patch Raylib version (version 5.0 in cmake)
run: cd raylib && git show 032cc497ca5aaca862dc926a93c2a45ed8017737 | patch -p1
- name: Build Raylib
run: cd raylib && cmake -B buildRelease -S . -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=On -DCMAKE_INSTALL_PREFIX=/usr/local && make -C buildRelease
- name: Install Raylib
run: cd raylib/buildRelease && sudo make install
- name: Fetch Triangles
run: git clone --depth=1 --no-single-branch https://github.com/Stephen-Seo/Triangles Tri && cd Tri && git checkout $GITHUB_REF_NAME && git submodule update --init --recursive
- name: Build and Run UnitTests
run: cd Tri && cmake -B buildDebug -S . -DCMAKE_BUILD_TYPE=Debug && make -C buildDebug UnitTest_Triangles && ./buildDebug/UnitTest_Triangles

1
.gitignore vendored
View file

@ -1,4 +1,3 @@
build*/ build*/
compile_commands.json compile_commands.json
.clangd/ .clangd/
.cache/

12
.gitmodules vendored
View file

@ -1,6 +1,6 @@
[submodule "third_party/raygui"] [submodule "third_party/imgui"]
path = third_party/raygui path = third_party/imgui
url = https://github.com/raysan5/raygui.git url = https://github.com/ocornut/imgui.git
[submodule "third_party/glm"] [submodule "third_party/imgui-sfml"]
path = third_party/glm path = third_party/imgui-sfml
url = https://github.com/g-truc/glm.git url = https://github.com/eliasdaler/imgui-sfml.git

View file

@ -1,14 +1,14 @@
cmake_minimum_required(VERSION 3.8.2) cmake_minimum_required(VERSION 3.0)
project(Triangles LANGUAGES CXX VERSION 1.0) project(Triangles LANGUAGES CXX VERSION 1.0)
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/glm/glm/glm.hpp) if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/imgui/imgui.h)
message(FATAL_ERROR "third_party/glm/glm/glm.hpp is missing!\n \ message(FATAL_ERROR "third_party/imgui/imgui.h is missing!\n \
Please update the glm submodule by running 'git submodule init' and \ Please update the GameDevTools submodule by running 'git submodule init' and \
'git submodule update'!") 'git submodule update'!")
endif() endif()
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/raygui/src/raygui.h) if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/imgui-sfml/imgui-SFML.h)
message(FATAL_ERROR "third_party/raygui/src/raygui.h is missing!\n \ message(FATAL_ERROR "third_party/imgui-sfml/imgui-SFML.h is missing!\n \
Please update the raygui submodule by running 'git submodule init' and \ Please update the GameDevTools submodule by running 'git submodule init' and \
'git submodule update'!") 'git submodule update'!")
endif() endif()
@ -16,18 +16,34 @@ if(NOT DEFINED CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug") set(CMAKE_BUILD_TYPE "Debug")
endif() endif()
if(CMAKE_BUILD_TYPE MATCHES "Debug")
set(ImGuiDemo "third_party/imgui/imgui_demo.cpp")
else()
set(ImGuiDemo "")
endif()
set(Triangles_MAIN_SOURCES set(Triangles_MAIN_SOURCES
src/main.cpp src/main.cpp
) )
set(Triangles_LIB_SOURCES set(Triangles_LIB_SOURCES
src/state.cpp src/state.cpp
src/shape.cpp third_party/imgui/imgui.cpp
src/triangle.cpp third_party/imgui/imgui_draw.cpp
src/circle.cpp third_party/imgui/imgui_widgets.cpp
src/raygui.cpp third_party/imgui-sfml/imgui-SFML.cpp
) )
if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS_DEBUG "/Od /Zi")
set(CMAKE_CXX_FLAGS_RELEASE "/O2 /DNDEBUG")
else()
set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wpedantic -Wsuggest-override")
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -D NDEBUG")
endif()
add_library(TrianglesLib STATIC ${Triangles_LIB_SOURCES}) add_library(TrianglesLib STATIC ${Triangles_LIB_SOURCES})
if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
@ -36,51 +52,54 @@ else()
add_executable(Triangles ${Triangles_MAIN_SOURCES}) add_executable(Triangles ${Triangles_MAIN_SOURCES})
endif() endif()
target_compile_options(TrianglesLib PRIVATE
-Wall -Wextra -Wpedantic
-Wsuggest-override
$<$<COMPILE_LANGUAGE:CXX>:-Weffc++>
$<$<CONFIG:DEBUG>:-Og>
)
target_compile_options(Triangles PRIVATE
-Wall -Wextra -Wpedantic
-Wsuggest-override
$<$<COMPILE_LANGUAGE:CXX>:-Weffc++>
$<$<CONFIG:DEBUG>:-Og>
)
target_link_libraries(Triangles PUBLIC TrianglesLib) target_link_libraries(Triangles PUBLIC TrianglesLib)
target_compile_features(TrianglesLib PUBLIC cxx_std_17) target_compile_features(TrianglesLib PUBLIC cxx_std_17)
if(BUILD_SHARED_LIBS OR (UNIX AND NOT CYGWIN) OR (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")) if(BUILD_SHARED_LIBS OR (UNIX AND NOT CYGWIN) OR (CMAKE_CXX_COMPILER_ID MATCHES "MSVC"))
find_package(raylib 5.0 REQUIRED) find_package(SFML 2 REQUIRED
COMPONENTS audio network graphics window system)
else() else()
find_package(raylib 5.0 REQUIRED) find_package(SFML 2 REQUIRED
COMPONENTS audio-s network-s graphics-s window-s system-s)
add_definitions(-DSFML_STATIC)
endif() endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
target_link_libraries(TrianglesLib PUBLIC target_link_libraries(TrianglesLib PUBLIC
raylib sfml-graphics sfml-window sfml-system
opengl32 opengl32
) )
else() else()
target_link_libraries(TrianglesLib PUBLIC target_link_libraries(TrianglesLib PUBLIC
raylib sfml-graphics sfml-window sfml-system
GL GL
) )
endif() endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
target_include_directories(TrianglesLib PUBLIC target_include_directories(TrianglesLib PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/third_party/raygui/src ${SFML_INCLUDE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/third_party/glm ${CMAKE_CURRENT_SOURCE_DIR}/third_party/imgui # imgui related headers
${CMAKE_CURRENT_SOURCE_DIR}/third_party/imgui-sfml # imgui-sfml related headers
) )
else()
target_include_directories(TrianglesLib PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src
${SFML_INCLUDE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/build_include # imgui related headers
)
endif()
# Use macro to override imgui config header
target_compile_definitions(TrianglesLib PUBLIC
"IMGUI_USER_CONFIG=\"${CMAKE_CURRENT_SOURCE_DIR}/third_party/imgui-sfml/imconfig-SFML.h\"")
if(CMAKE_BUILD_TYPE MATCHES "Debug") if(CMAKE_BUILD_TYPE MATCHES "Debug")
set(Triangles_UNIT_TEST_SOURCES set(Triangles_UNIT_TEST_SOURCES
src/unittest/test_main.cpp
src/unittest/test_helpers.cpp src/unittest/test_helpers.cpp
third_party/catch/catch_amalgamated.cpp
) )
add_executable(UnitTest_Triangles ${Triangles_UNIT_TEST_SOURCES}) add_executable(UnitTest_Triangles ${Triangles_UNIT_TEST_SOURCES})

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2020-2021,2023-2024 Stephen Seo Copyright (c) 2020-2021 Stephen Seo
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -5,7 +5,7 @@ THIS PROJECT IS STILL A WORK IN PROGRESS!
A gui application that lets you draw triangles of different colors on a colored A gui application that lets you draw triangles of different colors on a colored
background. background.
Uses [raygui](https://github.com/raysan5/raygui) for UI. Uses [imgui](https://github.com/ocornut/imgui) for UI.
# Compiling # Compiling
@ -18,19 +18,19 @@ Create a build dir, run cmake, and then make to build.
## Dependencies ## Dependencies
Raylib should be installed on the system. Other third-party dependencies are git SFML should be installed on the system. imgui, and imgui-sfml are git submodules
submodules and should be initialized with `git submodule update --init recursive`. and should be initialized with `git submodule update --init --recursive`.
# Legal stuff # Legal stuff
Uses [raylib](https://github.com/raysan5/raylib), which is licensed under the Uses [SFML](https://github.com/SFML/SFML), which is licensed under the
[zlib license](https://choosealicense.com/licenses/zlib). [SFML license](https://github.com/SFML/SFML/blob/master/license.md).
Uses [raygui](https://github.com/raysan5/raylib), which is licensed under the Uses [imgui](https://github.com/ocornut/imgui), which is licensed under the
[zlib license](https://choosealicense.com/licenses/zlib). [MIT](https://choosealicense.com/licenses/mit/) license.
Uses [glm](https://github.com/g-truc/glm), which is licensed under the [Happy Uses [imgui-sfml](https://github.com/eliasdaler/imgui-sfml), which is licensed
Bunny License or the MIT License](https://github.com/g-truc/glm/blob/master/copying.txt). under the [MIT](https://choosealicense.com/licenses/mit/) license.
Uses [catch](https://github.com/catchorg/Catch2), which is licensed under the Uses [catch](https://github.com/catchorg/Catch2), which is licensed under the
[Boost Software](https://choosealicense.com/licenses/bsl-1.0/) license. [Boost Software](https://choosealicense.com/licenses/bsl-1.0/) license.

1
build_include/imconfig.h Symbolic link
View file

@ -0,0 +1 @@
../third_party/imgui-sfml/imconfig-SFML.h

1
build_include/imgui-SFML.h Symbolic link
View file

@ -0,0 +1 @@
../third_party/imgui-sfml/imgui-SFML.h

View file

@ -0,0 +1 @@
../third_party/imgui-sfml/imgui-SFML_export.h

1
build_include/imgui.h Symbolic link
View file

@ -0,0 +1 @@
../third_party/imgui/imgui.h

View file

@ -0,0 +1 @@
../third_party/imgui/imgui_internal.h

View file

@ -0,0 +1 @@
../third_party/imgui/imstb_rectpack.h

View file

@ -0,0 +1 @@
../third_party/imgui/imstb_textedit.h

View file

@ -0,0 +1 @@
../third_party/imgui/imstb_truetype.h

View file

@ -1,60 +0,0 @@
#include "circle.hpp"
#include <cmath>
Tri::Circle::Circle() :
Shape(),
position{0.0f, 0.0f},
radius(4.0f)
{}
Tri::Circle::Circle(glm::vec2 pos) :
Shape(),
position(pos),
radius(4.0f)
{}
Tri::Circle::Circle(glm::vec2 pos, float radius) :
Shape(),
position(pos),
radius(radius)
{}
Tri::Circle::Circle(glm::vec2 pos, float radius, Color fillColor) :
Shape(fillColor),
position(pos),
radius(radius)
{}
Tri::Circle::Circle(glm::vec2 pos, float radius, Color fillColor, Color outlineColor) :
Shape(fillColor, outlineColor),
position(pos),
radius(radius)
{}
Tri::Shape& Tri::Circle::draw() {
glm::vec2 transformed = transform * glm::vec3(position.x, position.y, 1.0f);
transformed.x = std::roundf(transformed.x);
transformed.y = std::roundf(transformed.y);
DrawCircle(transformed.x, transformed.y, radius, fillColor);
DrawCircleLines(transformed.x, transformed.y, radius, outlineColor);
return *this;
}
void Tri::Circle::getVertices(std::vector<glm::vec2> &verticesOut) const {
verticesOut.clear();
verticesOut.push_back(position);
}
void Tri::Circle::getTransformedVertices(std::vector<glm::vec2> &verticesOut) const {
verticesOut.clear();
verticesOut.push_back(transform * glm::vec3{position.x, position.y, 1.0f});
}
float Tri::Circle::getRadius() const {
return radius;
}
void Tri::Circle::setRadius(float radius) {
this->radius = radius;
}

View file

@ -1,29 +0,0 @@
#ifndef TRIANGLES_CIRCLE_HPP
#define TRIANGLES_CIRCLE_HPP
#include "shape.hpp"
namespace Tri {
struct Circle : public Shape {
Circle();
Circle(glm::vec2 pos);
Circle(glm::vec2 pos, float radius);
Circle(glm::vec2 pos, float radius, Color fillColor);
Circle(glm::vec2 pos, float radius, Color fillColor, Color outlineColor);
glm::vec2 position;
float radius;
virtual Shape& draw() override;
virtual void getVertices(std::vector<glm::vec2> &verticesOut) const override;
virtual void getTransformedVertices(std::vector<glm::vec2> &verticesOut) const override;
virtual float getRadius() const override;
virtual void setRadius(float radius) override;
}; // struct Circle
} // namespace Tri
#endif

View file

@ -3,24 +3,14 @@
#include <cmath> #include <cmath>
#include <optional> #include <optional>
#include <cassert>
#include <raylib.h> #include <imgui.h>
#include <raygui.h>
#include "shape.hpp"
#include "state.hpp" #include "state.hpp"
#define SHOW_HELP_WIDTH (state->get_width() / 2.0f) #define SHOW_HELP_WIDTH (state->get_width() / 2.0f)
#define SHOW_HELP_HEIGHT (state->get_height() / 2.0f) #define SHOW_HELP_HEIGHT (state->get_height() / 2.0f)
#define DEFAULT_WIDTH 800
#define DEFAULT_HEIGHT 600
#define CHANGE_SIZE_MIN_X 800
#define CHANGE_SIZE_MAX_X 1920
#define CHANGE_SIZE_MIN_Y 600
#define CHANGE_SIZE_MAX_Y 1080
#ifndef NDEBUG #ifndef NDEBUG
# include <cstdio> # include <cstdio>
#endif #endif
@ -30,194 +20,126 @@ namespace Tri {
// 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(Tri::State::F_DISPLAY_HELP)) { if(state->get_flags().test(Tri::State::F_DISPLAY_HELP)) {
GuiSetAlpha(1.0f); ImGui::SetNextWindowPos(sf::Vector2f(10.0f, 10.0f));
if(!GuiWindowBox({10.0f, ImGui::SetNextWindowSize(sf::Vector2f(
10.0f, state->get_width() - 20.0f,
800.0f - 20.0f, state->get_height() - 20.0f));
600.0f - 20.0f}, ImGui::SetNextWindowBgAlpha(0.7f);
"Help")) { ImGui::Begin("Help Window", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings);
GuiLabel( ImGui::Text("This is the help window - Press \"H\" to toggle this window");
{14.0f, 38.0f, 800.0f - 28.0f, 16.0f}, ImGui::Text("Click anywhere to create triangles, one point at a time");
"This is the help window - Press \"H\" to toggle this window"); ImGui::Text("You cannot draw when a window is open");
GuiLabel( ImGui::Text("Press \"U\" to undo. Clicking will remove all future undo history");
{14.0f, 56.0f, 800.0f - 28.0f, 16.0f}, ImGui::Text("Press \"R\" to redo.");
"Click anywhere to create triangles, one point at a time"); ImGui::Text("Press \"C\" to change colors");
GuiLabel( ImGui::Text("Press \"B\" to change background color");
{14.0f, 74.0f, 800.0f - 28.0f, 16.0f}, ImGui::Text("Press \"P\" to set current color to a color on screen");
"You cannot draw when a window is open"); ImGui::Text("Press \"S\" to save what was drawn as a png image");
GuiLabel( if(ImGui::Button("Close")) {
{14.0f, 92.0f, 800.0f - 28.0f, 16.0f},
"Press \"U\" to undo. Clicking will remove all future undo history");
GuiLabel(
{14.0f, 110.0f, 800.0f - 28.0f, 16.0f},
"Press \"R\" to redo.");
GuiLabel(
{14.0f, 128.0f, 800.0f - 28.0f, 16.0f},
"Press \"C\" to change colors");
GuiLabel(
{14.0f, 146.0f, 800.0f - 28.0f, 16.0f},
"Press \"B\" to change background color");
GuiLabel(
{14.0f, 164.0f, 800.0f - 28.0f, 16.0f},
"Press \"P\" to set current color to a color on screen");
GuiLabel(
{14.0f, 182.0f, 800.0f - 28.0f, 16.0f},
"Press \"S\" to save what was drawn as a png image");
GuiLabel(
{14.0f, 200.0f, 800.0f - 28.0f, 16.0f},
"Press \"I\" to resize the canvas");
GuiLabel(
{14.0f, 218.0f, 800.0f - 28.0f, 16.0f},
"Press \"E\" to edit the selected tri");
if(GuiButton({14.0f, 238.0f, 100.0f, 16.0f}, "Close")) {
state->close_help();
}
} else {
state->close_help(); state->close_help();
} }
ImGui::End();
} }
} }
inline void draw_notification(Tri::State *state) { inline void draw_notification(Tri::State *state) {
float alpha = state->get_notification_alpha(); float alpha = state->get_notification_alpha();
if(alpha > 0.0f) { if(alpha > 0.0f) {
GuiSetAlpha(alpha); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
GuiPanel({(800 - SHOW_HELP_WIDTH) / 2.0f,
(600 - SHOW_HELP_HEIGHT) / 2.0f, ImGui::SetNextWindowPos(sf::Vector2f(
(state->get_width() - SHOW_HELP_WIDTH) / 2.0f,
(state->get_height() - SHOW_HELP_HEIGHT) / 2.0f));
ImGui::SetNextWindowSize(sf::Vector2f(
SHOW_HELP_WIDTH, SHOW_HELP_WIDTH,
SHOW_HELP_HEIGHT}, nullptr); SHOW_HELP_HEIGHT));
GuiLabel({4.0f + (800 - SHOW_HELP_WIDTH) / 2.0f, ImGui::Begin(
4.0f + (600 - SHOW_HELP_HEIGHT) / 2.0f, "Notification Window",
SHOW_HELP_WIDTH - 8.0f, nullptr,
SHOW_HELP_HEIGHT - 8.0f}, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings);
state->get_notification_text()); ImGui::SetWindowFontScale(3.0f);
ImGui::Text("%s", state->get_notification_text());
ImGui::End();
ImGui::PopStyleVar();
} }
} }
inline void draw_color_picker(Tri::State *state) { inline void draw_color_picker(Tri::State *state) {
if(state->get_flags().test(Tri::State::F_DISPLAY_COLOR_P)) { if(state->get_flags().test(Tri::State::F_DISPLAY_COLOR_P)) {
GuiSetAlpha(1.0f); ImGui::Begin("Tri Color Picker");
if(!GuiWindowBox({4.0f, 4.0f, 242.0f, 328.0f}, "Tri Color Picker")) { ImGui::ColorPicker4("Tri Color", state->get_color());
auto &color = state->get_color(); if(ImGui::Button("Close")) {
GuiColorPicker(
{8.0f, 32.0f, 206.0f, 240.0f},
nullptr,
&color);
float alpha = ((float)color.a) / 255.0F;
GuiColorBarAlpha(
{8.0F, 280.0F, 206.0F, 20.0F},
nullptr,
&alpha
);
color.a = alpha * 255.0F;
if(GuiButton({8.0f, 308.0f, 234.0f, 16.0f}, "Close")) {
state->close_color_picker();
}
} else {
state->close_color_picker(); state->close_color_picker();
} }
ImGui::End();
} }
} }
inline void draw_bg_color_picker(Tri::State *state) { inline void draw_bg_color_picker(Tri::State *state) {
if(state->get_flags().test(Tri::State::F_DISPLAY_BG_COLOR_P)) { if(state->get_flags().test(Tri::State::F_DISPLAY_BG_COLOR_P)) {
GuiSetAlpha(1.0f); ImGui::Begin("BG Color Picker");
if(!GuiWindowBox({250.0f, 4.0f, 242.0f, 292.0f}, "BG Color Picker")) { ImGui::ColorPicker3("BG Color", state->get_bg_color());
auto &colorArray = state->get_bg_color(); if(ImGui::Button("Close")) {
GuiColorPicker(
{254.0f, 32.0f, 206.0f, 240.0f},
nullptr,
&colorArray);
if(GuiButton({254.0f, 274.0f, 234.0f, 16.0f}, "Close")) {
state->close_bg_color_picker();
}
} else {
state->close_bg_color_picker(); state->close_bg_color_picker();
} }
ImGui::End();
} }
} }
inline void draw_save(Tri::State *state) { inline void draw_save(Tri::State *state) {
if(state->get_flags().test(Tri::State::F_DISPLAY_SAVE)) { if(state->get_flags().test(Tri::State::F_DISPLAY_SAVE)) {
auto *filenameBuffer = state->get_save_filename_buffer(); auto *filenameBuffer = state->get_save_filename_buffer();
GuiSetAlpha(1.0f); ImGui::Begin("Save");
if(!GuiWindowBox({4.0f, 300.0f, 292.0f, 292.0f}, "Save")) { ImGui::InputText("Filename", filenameBuffer->data(), filenameBuffer->size() - 1);
GuiTextBox( if(ImGui::Button("Save")) {
{8.0f, 328.0f, 284.0f, 20.0f},
filenameBuffer->data(),
filenameBuffer->size() - 1,
true);
if(GuiButton({8.0f, 352.0f, 50.0f, 16.0f}, "Save")) {
if(state->do_save()) { if(state->do_save()) {
state->close_save(); state->close_save();
} }
} } else if(ImGui::Button("Cancel")) {
const std::string &string = state->failed_message();
if(!string.empty()) {
GuiLabel({8.0f, 372.0f, 284.0f, 16.0f}, string.c_str());
}
} else {
state->close_save(); state->close_save();
} }
auto string_view = state->failed_message();
if(!string_view.empty()) {
ImGui::TextUnformatted(string_view.data(), string_view.data() + string_view.size());
}
ImGui::End();
} }
} }
inline void draw_change_size(Tri::State *state) { inline void draw_change_size(Tri::State *state) {
if(state->get_flags().test(Tri::State::F_DISPLAY_CHANGE_SIZE)) { if(state->get_flags().test(Tri::State::F_DISPLAY_CHANGE_SIZE)) {
GuiSetAlpha(1.0f); ImGui::Begin("ChangeSize");
if(!GuiWindowBox({300.0f, 300.0f, 292.0f, 292.0f}, "Change Size")) { ImGui::InputInt2("Width and Height", state->get_input_width_height());
GuiValueBox( auto string_view = state->failed_message();
{384.0f, 328.0f, 80.0f, 16.0f}, if(!string_view.empty()) {
"Width", ImGui::TextUnformatted(string_view.data(), string_view.data() + string_view.size());
state->get_input_width(),
CHANGE_SIZE_MIN_X,
CHANGE_SIZE_MAX_X,
!state->get_flags().test(State::F_TAB_TOGGLE));
GuiValueBox(
{384.0f, 348.0f, 80.0f, 16.0f},
"Height",
state->get_input_height(),
CHANGE_SIZE_MIN_Y,
CHANGE_SIZE_MAX_Y,
state->get_flags().test(State::F_TAB_TOGGLE));
const std::string &failMessage = state->failed_message();
if(!failMessage.empty()) {
GuiLabel({304.0f, 368.0f, 284.0f, 16.0f}, failMessage.c_str());
} }
if(GuiButton({304.0f, 394.0f, 70.0f, 16.0f}, "Cancel")) { if(ImGui::Button("Cancel")) {
state->close_input_width_height_window(); state->close_input_width_height_window();
} }
if(GuiButton({378.0f, 394.0f, 50.0f, 16.0f}, "Set")) { if(ImGui::Button("Set")) {
if(state->change_width_height()) { if(state->change_width_height()) {
state->close_input_width_height_window(); state->close_input_width_height_window();
} }
} }
} else { ImGui::End();
state->close_input_width_height_window();
}
} }
} }
inline bool is_within_shape( inline bool is_within_shape(
const Tri::Shape &shape, const sf::ConvexShape &shape,
glm::vec2 xy) { sf::Vector2f xy) {
float radius = shape.getRadius();
std::vector<glm::vec2> vertices;
shape.getTransformedVertices(vertices);
if(radius > 0.0f) {
assert(vertices.size() == 1);
xy = xy - vertices[0];
return std::sqrt(xy.x * xy.x + xy.y * xy.y) <= radius;
} else {
assert(vertices.size() > 2);
std::optional<bool> is_right; std::optional<bool> is_right;
for(unsigned int i = 0; i < vertices.size(); ++i) { sf::Transform t = shape.getTransform();
glm::vec2 t_a, t_b; for(unsigned int i = 0; i < shape.getPointCount(); ++i) {
t_b = vertices[(i + 1) % vertices.size()] - vertices[i]; sf::Vector2f t_a = t.transformPoint(shape.getPoint(i));
t_a = xy - vertices[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 // cross product, where z coordinate is 0
// Use sign of z value to determine if line is to right or left // Use sign of z value to determine if line is to right or left
// //
@ -242,53 +164,19 @@ namespace Tri {
} }
return true; return true;
} }
}
inline Color invert_color(const Color &other) { inline sf::Color invert_color(const sf::Color &other) {
return Color{ return sf::Color(255 - other.r, 255 - other.g, 255 - other.b);
(unsigned char)(255 - (int)other.r),
(unsigned char)(255 - (int)other.g),
(unsigned char)(255 - (int)other.b),
other.a};
} }
inline void draw_edit_tri(Tri::State *state) { inline void draw_edit_tri(Tri::State *state) {
if(state->get_flags().test(Tri::State::F_TRI_EDIT_MODE)) { if(state->get_flags().test(Tri::State::F_TRI_EDIT_MODE)) {
GuiSetAlpha(1.0f); ImGui::Begin("Edit Tri Color Picker");
if(!GuiWindowBox({500.0f, 4.0f, 242.0f, 328.0f}, "Edit Tri Color Picker")) { ImGui::ColorPicker4("Tri Color", state->get_selected_tri_color());
auto &color = state->get_selected_tri_color(); if(ImGui::Button("Close")) {
GuiColorPicker(
{504.0f, 32.0f, 206.0f, 240.0f},
nullptr,
&color);
float alpha = ((float)color.a) / 255.0F;
GuiColorBarAlpha(
{504.0F, 280.0F, 206.0F, 20.0F},
nullptr,
&alpha
);
color.a = alpha * 255.0F;
if(GuiButton({504.0f, 308.0f, 234.0f, 16.0f}, "Close")) {
state->close_selected_tri_mode(); state->close_selected_tri_mode();
} }
} else { ImGui::End();
state->close_selected_tri_mode();
}
}
}
inline void make_counter_clockwise(std::array<glm::vec2, 3> &tri) {
// 2D cross product to get the sign to determine if clockwise
glm::vec2 t_a, t_b;
t_b = tri[1] - tri[0];
t_a = tri[2] - tri[0];
float z = t_b.x * t_a.y - t_b.y * t_a.x;
if(z >= 0.0f) {
// is clockwise, swap first with last
t_a = tri[0];
tri[0] = tri[2];
tri[2] = t_a;
} }
} }
} }

View file

@ -1,3 +1,10 @@
#include <SFML/System.hpp>
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>
#include <imgui.h>
#include <imgui-SFML.h>
#include "state.hpp" #include "state.hpp"
#ifdef _MSC_VER #ifdef _MSC_VER

View file

@ -1,14 +0,0 @@
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wall"
#pragma GCC diagnostic ignored "-Wextra"
#pragma GCC diagnostic ignored "-Wpedantic"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#pragma GCC diagnostic ignored "-Wenum-compare"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Weffc++"
#define RAYGUI_IMPLEMENTATION
#include <raygui.h>
#pragma GCC diagnostic pop

View file

@ -1,46 +0,0 @@
#include "shape.hpp"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#include <glm/ext/matrix_transform.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/matrix_transform_2d.hpp>
#pragma GCC diagnostic pop
Tri::Shape::Shape() :
transform(glm::identity<glm::mat3>()),
fillColor(RAYWHITE),
outlineColor(BLACK)
{}
Tri::Shape::Shape(Color fillColor) :
transform(glm::identity<glm::mat3>()),
fillColor(fillColor),
outlineColor(BLACK)
{}
Tri::Shape::Shape(Color fillColor, Color outlineColor) :
transform(glm::identity<glm::mat3>()),
fillColor(fillColor),
outlineColor(outlineColor)
{}
Tri::Shape& Tri::Shape::resetTransform() {
transform = glm::identity<glm::mat3>();
return *this;
}
Tri::Shape& Tri::Shape::translate(const glm::vec2 move) {
transform = glm::translate(transform, move);
return *this;
}
Tri::Shape& Tri::Shape::rotate(const float angle) {
transform = glm::rotate(transform, angle);
return *this;
}
Tri::Shape& Tri::Shape::scale(const glm::vec2 scale) {
transform = glm::scale(transform, scale);
return *this;
}

View file

@ -1,40 +0,0 @@
#ifndef TRIANGLES_SHAPE_HPP
#define TRIANGLES_SHAPE_HPP
#include <vector>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#include <raylib.h>
#include <glm/glm.hpp>
#pragma GCC diagnostic pop
namespace Tri {
struct Shape {
Shape();
Shape(Color fillColor);
Shape(Color fillColor, Color outlineColor);
virtual ~Shape() {}
glm::mat3 transform;
Color fillColor;
Color outlineColor;
Shape& resetTransform();
Shape& translate(const glm::vec2 move);
Shape& rotate(const float angle);
Shape& scale(const glm::vec2 scale);
virtual Shape& draw() = 0;
virtual void getVertices(std::vector<glm::vec2> &verticesOut) const = 0;
virtual void getTransformedVertices(std::vector<glm::vec2> &verticesOut) const = 0;
virtual float getRadius() const = 0;
virtual void setRadius(float radius) = 0;
}; // struct Shape
} // namespace Tri
#endif

View file

@ -4,82 +4,75 @@
#include <cassert> #include <cassert>
#include <string> #include <string>
#include <cmath> #include <cmath>
#include <cstdio>
#include <raylib.h> #include <imgui-SFML.h>
#include "helpers.hpp" #include "helpers.hpp"
#define STARTING_HELP_FADE_RATE 0.2f #define STARTING_HELP_FADE_RATE 0.2f
#pragma GCC diagnostic push #ifndef NDEBUG
#pragma GCC diagnostic ignored "-Wunused-parameter" # include <cstdio>
#endif
Tri::State::State(int argc, char **argv) : Tri::State::State(int argc, char **argv) :
flags(), width(800),
width(DEFAULT_WIDTH), height(600),
height(DEFAULT_HEIGHT), dt(sf::microseconds(16666)),
dt(1.0f/60.0f), notification_alpha(1.0f),
notificationAlpha(1.0f), window(sf::VideoMode(800, 600), "Triangles", sf::Style::Titlebar | sf::Style::Close),
notificationText(),
tris(),
trisIndex(0), trisIndex(0),
currentTri(),
currentTri_state(CurrentState::NONE), currentTri_state(CurrentState::NONE),
currentTri_maxState(CurrentState::NONE), currentTri_maxState(CurrentState::NONE),
pointCircle(), colorPickerColor{1.0f, 1.0f, 1.0f, 1.0f},
colorPickerColor{255, 255, 255, 255}, bgColorPickerColor{0.0f, 0.0f, 0.0f},
bgColorPickerColor{0, 0, 0, 255}, bgColor(sf::Color::Black),
bgColor(BLACK), inputWidthHeight{800, 600},
saveFilenameBuffer(), pi(std::acos(-1.0f))
failedMessage(),
drawCache(),
pi(std::acos(-1.0f)),
selectedTri(),
selectedTriColor{255, 255, 255, 255},
selectedTriBlinkTimer(),
inputWidth(800),
inputHeight(600)
{ {
InitWindow(width, height, "Triangles");
SetTargetFPS(60);
flags.set(F_IS_RUNNING); // is running flags.set(F_IS_RUNNING); // is running
ImGui::SFML::Init(window);
window.setFramerateLimit(60);
set_notification_text("Press \"H\" for help"); set_notification_text("Press \"H\" for help");
pointCircle.setRadius(7.0f); pointCircle.setRadius(7.0f);
pointCircle.translate({7.0f, 7.0f}); pointCircle.setOrigin(7.0f, 7.0f);
pointCircle.fillColor = WHITE; pointCircle.setFillColor(sf::Color::White);
pointCircle.outlineColor = BLACK; pointCircle.setOutlineColor(sf::Color::Black);
pointCircle.setOutlineThickness(1.0f);
saveFilenameBuffer.fill(0); saveFilenameBuffer.fill(0);
drawCache = LoadRenderTexture(width, height); if(!drawCache.create(800, 600)) {
#ifndef NDEBUG
puts("ERROR: Failed to initialize RenderTexture (draw cache)");
#endif
flags.reset(F_DRAW_CACHE_INITIALIZED);
} else {
flags.set(F_DRAW_CACHE_INITIALIZED); flags.set(F_DRAW_CACHE_INITIALIZED);
flags.set(F_DRAW_CACHE_DIRTY); flags.set(F_DRAW_CACHE_DIRTY);
drawCacheSprite.setTexture(drawCache.getTexture(), true);
GuiSetStyle(DEFAULT, BACKGROUND_COLOR, 0x303030); }
} }
#pragma GCC diagnostic pop
Tri::State::~State() { Tri::State::~State() {
UnloadRenderTexture(drawCache); window.close();
CloseWindow(); ImGui::SFML::Shutdown();
} }
void Tri::State::handle_events() { void Tri::State::handle_events() {
if(WindowShouldClose()) { while(window.pollEvent(event)) {
ImGui::SFML::ProcessEvent(event);
if(event.type == sf::Event::Closed) {
window.close();
flags.reset(F_IS_RUNNING); flags.reset(F_IS_RUNNING);
} } else if(event.type == sf::Event::KeyPressed) {
int keyPressed = GetKeyPressed();
while(keyPressed > 0) {
if(!flags.test(F_DISPLAY_SAVE)) { if(!flags.test(F_DISPLAY_SAVE)) {
switch(keyPressed) { // TODO use a switch statement
case KEY_H: if(event.key.code == sf::Keyboard::H) {
flags.flip(F_DISPLAY_HELP); flags.flip(F_DISPLAY_HELP);
break; } else if(event.key.code == sf::Keyboard::U) {
case KEY_U:
flags.set(F_DRAW_CACHE_DIRTY); flags.set(F_DRAW_CACHE_DIRTY);
if(currentTri_state > 0) { if(currentTri_state > 0) {
switch(currentTri_state) { switch(currentTri_state) {
@ -96,8 +89,7 @@ void Tri::State::handle_events() {
} else if(trisIndex > 0) { } else if(trisIndex > 0) {
--trisIndex; --trisIndex;
} }
break; } else if(event.key.code == sf::Keyboard::R) {
case KEY_R:
flags.set(F_DRAW_CACHE_DIRTY); flags.set(F_DRAW_CACHE_DIRTY);
if(currentTri_state != CurrentState::NONE if(currentTri_state != CurrentState::NONE
&& currentTri_state < currentTri_maxState) { && currentTri_state < currentTri_maxState) {
@ -127,25 +119,21 @@ void Tri::State::handle_events() {
break; break;
} }
} }
break; } else if(event.key.code == sf::Keyboard::C) {
case KEY_C:
if(flags.test(F_DISPLAY_COLOR_P)) { if(flags.test(F_DISPLAY_COLOR_P)) {
close_color_picker(); close_color_picker();
} else { } else {
flags.set(F_DISPLAY_COLOR_P); flags.set(F_DISPLAY_COLOR_P);
} }
break; } else if(event.key.code == sf::Keyboard::B) {
case KEY_B:
if(flags.test(F_DISPLAY_BG_COLOR_P)) { if(flags.test(F_DISPLAY_BG_COLOR_P)) {
close_bg_color_picker(); close_bg_color_picker();
} else { } else {
flags.set(F_DISPLAY_BG_COLOR_P); flags.set(F_DISPLAY_BG_COLOR_P);
} }
break; } else if(event.key.code == sf::Keyboard::S) {
case KEY_S:
flags.flip(F_DISPLAY_SAVE); flags.flip(F_DISPLAY_SAVE);
break; } else if(event.key.code == sf::Keyboard::P) {
case KEY_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)) {
set_notification_text( set_notification_text(
@ -155,18 +143,15 @@ void Tri::State::handle_events() {
"to what was\n" "to what was\n"
"clicked on"); "clicked on");
} else { } else {
notificationAlpha = 0.0f; notification_alpha = 0.0f;
} }
break; } else if(event.key.code == sf::Keyboard::I) {
case KEY_I:
flags.flip(F_DISPLAY_CHANGE_SIZE); flags.flip(F_DISPLAY_CHANGE_SIZE);
if(!flags.test(F_DISPLAY_CHANGE_SIZE)) { if(!flags.test(F_DISPLAY_CHANGE_SIZE)) {
close_input_width_height_window(); inputWidthHeight[0] = width;
} else { inputWidthHeight[1] = height;
failedMessage = "Press TAB to switch between width/height";
} }
break; } else if(event.key.code == sf::Keyboard::E) {
case KEY_E:
if(flags.test(F_TRI_EDIT_MODE)) { if(flags.test(F_TRI_EDIT_MODE)) {
close_selected_tri_mode(); close_selected_tri_mode();
} else { } else {
@ -175,20 +160,13 @@ void Tri::State::handle_events() {
set_notification_text("Click on a tri\nto edit it"); set_notification_text("Click on a tri\nto edit it");
} }
} }
break;
case KEY_TAB:
flags.flip(F_TAB_TOGGLE);
break;
} }
} }
keyPressed = GetKeyPressed(); } else if(event.type == sf::Event::MouseButtonPressed) {
}
if(IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
if(can_draw()) { if(can_draw()) {
switch(currentTri_state) { switch(currentTri_state) {
case CurrentState::NONE: case CurrentState::NONE:
currentTri[0] = {GetMouseX(), GetMouseY()}; currentTri[0] = sf::Vector2f(event.mouseButton.x, event.mouseButton.y);
if(trisIndex < tris.size()) { if(trisIndex < tris.size()) {
tris.resize(trisIndex); tris.resize(trisIndex);
} }
@ -196,7 +174,7 @@ void Tri::State::handle_events() {
currentTri_maxState = CurrentState::FIRST; currentTri_maxState = CurrentState::FIRST;
break; break;
case CurrentState::FIRST: case CurrentState::FIRST:
currentTri[1] = {GetMouseX(), GetMouseY()}; currentTri[1] = sf::Vector2f(event.mouseButton.x, event.mouseButton.y);
if(trisIndex < tris.size()) { if(trisIndex < tris.size()) {
tris.resize(trisIndex); tris.resize(trisIndex);
} }
@ -204,56 +182,46 @@ void Tri::State::handle_events() {
currentTri_maxState = CurrentState::SECOND; currentTri_maxState = CurrentState::SECOND;
break; break;
case CurrentState::SECOND: case CurrentState::SECOND:
currentTri[2] = {GetMouseX(), GetMouseY()}; currentTri[2] = sf::Vector2f(event.mouseButton.x, event.mouseButton.y);
if(trisIndex < tris.size()) { if(trisIndex < tris.size()) {
tris.resize(trisIndex); tris.resize(trisIndex);
} }
++trisIndex; ++trisIndex;
make_counter_clockwise(currentTri); tris.emplace_back(sf::ConvexShape(3));
tris.emplace_back(currentTri, pointCircle.fillColor); tris.back().setPoint(0, currentTri[0]);
tris.back().setPoint(1, currentTri[1]);
tris.back().setPoint(2, currentTri[2]);
tris.back().setFillColor(pointCircle.getFillColor());
currentTri_state = CurrentState::NONE; currentTri_state = CurrentState::NONE;
currentTri_maxState = CurrentState::NONE; currentTri_maxState = CurrentState::NONE;
flags.set(F_DRAW_CACHE_DIRTY); flags.set(F_DRAW_CACHE_DIRTY);
break; break;
} }
} else if(flags.test(F_COPY_COLOR_MODE)) { } else if(flags.test(F_COPY_COLOR_MODE)) {
check_draw_cache(); auto color = drawCache.getTexture().copyToImage()
Image drawImage = LoadImageFromTexture(drawCache.texture); .getPixel(event.mouseButton.x, event.mouseButton.y);
Color *colors = LoadImageColors(drawImage); colorPickerColor[0] = color.r / 255.0f;
int mx = GetMouseX(); colorPickerColor[1] = color.g / 255.0f;
int my = GetMouseY(); colorPickerColor[2] = color.b / 255.0f;
if(mx < 0) { mx = 0; } colorPickerColor[3] = 1.0f;
else if(mx >= drawImage.width) { mx = drawImage.width - 1; } pointCircle.setFillColor(color);
if(my < 0) { my = 0; }
else if(my >= drawImage.height) { my = drawImage.height - 1; }
my = drawImage.height - my;
colorPickerColor = colors[mx + my * drawImage.width];
pointCircle.fillColor = colors[mx + my * drawImage.width];
flags.reset(F_COPY_COLOR_MODE); flags.reset(F_COPY_COLOR_MODE);
set_notification_text("Color set"); set_notification_text("Color set");
UnloadImageColors(colors);
UnloadImage(drawImage);
} else if(flags.test(F_SELECT_TRI_MODE)) { } else if(flags.test(F_SELECT_TRI_MODE)) {
int mx = GetMouseX(); sf::Vector2f mouseXY = window.mapPixelToCoords(
int my = GetMouseY(); {event.mouseButton.x, event.mouseButton.y});
if(mx < 0) { mx = 0; }
else if(mx >= (int)width) { mx = width - 1; }
if(my < 0) { my = 0; }
else if(my >= (int)height) { my = height - 1; }
for(unsigned int i = trisIndex; i-- > 0; ) { for(unsigned int i = trisIndex; i-- > 0; ) {
if(is_within_shape(tris.at(i), {mx, my})) { if(is_within_shape(tris.at(i), mouseXY)) {
selectedTri = i; selectedTri = i;
tris[i].outlineColor = invert_color(tris[i].fillColor); tris[i].setOutlineColor(invert_color(tris[i].getFillColor()));
flags.reset(F_SELECT_TRI_MODE); flags.reset(F_SELECT_TRI_MODE);
flags.set(F_TRI_EDIT_MODE); flags.set(F_TRI_EDIT_MODE);
flags.set(F_TRI_EDIT_DRAW_TRI); flags.set(F_TRI_EDIT_DRAW_TRI);
selectedTriBlinkTimer = 1.0f; selectedTriBlinkTimer = 1.0f;
selectedTriColor = tris[i].fillColor; 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; break;
} }
} }
@ -263,74 +231,43 @@ void Tri::State::handle_events() {
} }
} }
} }
}
void Tri::State::update() { void Tri::State::update() {
dt = GetFrameTime(); ImGui::SFML::Update(window, dt);
if(notificationAlpha > 0.0f) { if(notification_alpha > 0.0f) {
notificationAlpha -= dt * STARTING_HELP_FADE_RATE; notification_alpha -= dt.asSeconds() * STARTING_HELP_FADE_RATE;
if(notificationAlpha < 0.0f) { if(notification_alpha < 0.0f) {
notificationAlpha = 0.0f; notification_alpha = 0.0f;
} }
} }
if(flags.test(F_COLOR_P_COLOR_DIRTY)) { if(flags.test(F_COLOR_P_COLOR_DIRTY)) {
flags.reset(F_COLOR_P_COLOR_DIRTY); flags.reset(F_COLOR_P_COLOR_DIRTY);
pointCircle.fillColor = colorPickerColor; pointCircle.setFillColor(sf::Color(
(unsigned char)(255 * colorPickerColor[0]),
(unsigned char)(255 * colorPickerColor[1]),
(unsigned char)(255 * colorPickerColor[2]),
(unsigned char)(255 * colorPickerColor[3])));
} }
if(flags.test(F_BG_COLOR_P_COLOR_DIRTY)) { if(flags.test(F_BG_COLOR_P_COLOR_DIRTY)) {
flags.reset(F_BG_COLOR_P_COLOR_DIRTY); flags.reset(F_BG_COLOR_P_COLOR_DIRTY);
bgColor = bgColorPickerColor; bgColor.r = (unsigned char)(255 * bgColorPickerColor[0]);
bgColor.a = 255; bgColor.g = (unsigned char)(255 * bgColorPickerColor[1]);
bgColor.b = (unsigned char)(255 * bgColorPickerColor[2]);
} }
if(flags.test(F_TRI_EDIT_MODE)) { if(flags.test(F_TRI_EDIT_MODE)) {
selectedTriBlinkTimer -= dt * TRIANGLES_EDIT_TRI_BLINK_RATE; selectedTriBlinkTimer -= dt.asSeconds() * TRIANGLES_EDIT_TRI_BLINK_RATE;
if(selectedTriBlinkTimer <= 0.0f) { if(selectedTriBlinkTimer <= 0.0f) {
selectedTriBlinkTimer = 1.0f; selectedTriBlinkTimer = 1.0f;
flags.flip(F_TRI_EDIT_DRAW_TRI); flags.flip(F_TRI_EDIT_DRAW_TRI);
} }
} }
}
void Tri::State::draw() {
// Should be able to directly draw a texture held by the RenderTexture2D
if(flags.test(F_DRAW_CACHE_INITIALIZED)) {
// draw cache initialized
check_draw_cache();
BeginDrawing();
// hack to flip in the y direction, since RenderTexture2D's texture
// is flipped
DrawTextureRec(
drawCache.texture,
{
0.0f,
0.0f,
(float)drawCache.texture.width,
(float)-drawCache.texture.height
},
{0.0f, 0.0f},
WHITE);
} else {
BeginDrawing();
draw_to_target(nullptr);
}
if(flags.test(F_TRI_EDIT_MODE) && flags.test(F_TRI_EDIT_DRAW_TRI)) {
// tris.at(selectedTri).setOutlineThickness(4.0f);
tris[selectedTri].draw();
// tris.at(selectedTri).setOutlineThickness(0.0f);
}
if(can_draw()) {
for(unsigned int i = 0; i < currentTri_state; ++i) {
pointCircle.resetTransform();
pointCircle.translate(currentTri[i]);
pointCircle.draw();
}
}
// 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);
@ -339,27 +276,48 @@ void Tri::State::draw() {
Tri::draw_save(this); Tri::draw_save(this);
Tri::draw_help(this); Tri::draw_help(this);
EndDrawing(); ImGui::EndFrame();
} }
void Tri::State::draw_to_target(RenderTexture2D *target) { void Tri::State::draw() {
if(target) { if(flags.test(F_DRAW_CACHE_INITIALIZED)) {
BeginTextureMode(*target); // draw cache initialized
ClearBackground(bgColor); if(flags.test(F_DRAW_CACHE_DIRTY)) {
// draw cache dirty
// draw tris flags.reset(F_DRAW_CACHE_DIRTY);
for(unsigned int i = 0; i < trisIndex; ++i) { draw_to_target(&drawCache);
tris[i].draw(); drawCache.display();
} }
EndTextureMode(); window.draw(drawCacheSprite);
} else { } else {
// Expects BeginDrawing() already having been called prior to this fn draw_to_target(&window);
ClearBackground(bgColor); }
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
ImGui::SFML::Render(window);
window.display();
}
void Tri::State::draw_to_target(sf::RenderTarget *target) {
target->clear(bgColor);
// draw tris // draw tris
for(unsigned int i = 0; i < trisIndex; ++i) { for(unsigned int i = 0; i < trisIndex; ++i) {
tris[i].draw(); target->draw(tris[i]);
}
} }
} }
@ -376,73 +334,66 @@ const Tri::State::BitsetType Tri::State::get_flags() const {
} }
float Tri::State::get_notification_alpha() const { float Tri::State::get_notification_alpha() const {
return notificationAlpha; return notification_alpha;
} }
const char* Tri::State::get_notification_text() const { const char* Tri::State::get_notification_text() const {
return notificationText.data(); return notification_text.data();
} }
void Tri::State::set_notification_text(const char *text) { void Tri::State::set_notification_text(const char *text) {
notificationText.fill(0); notification_text.fill(0);
std::strncpy(notificationText.data(), std::strncpy(notification_text.data(),
text, text,
notificationText.max_size() - 1); notification_text.max_size() - 1);
notificationAlpha = 1.0f; notification_alpha = 1.0f;
} }
void Tri::State::append_notification_text(const char *text) { float* Tri::State::get_color() {
auto length = std::strlen(notificationText.data());
if(length + 1 >= notificationText.max_size()) {
return;
}
std::strncpy(
notificationText.data() + length,
text,
notificationText.max_size() - length - 1);
notificationAlpha = 1.0f;
}
Color& Tri::State::get_color() {
flags.set(F_COLOR_P_COLOR_DIRTY); flags.set(F_COLOR_P_COLOR_DIRTY);
return colorPickerColor; return colorPickerColor;
} }
Color& Tri::State::get_bg_color() { float* Tri::State::get_bg_color() {
flags.set(F_BG_COLOR_P_COLOR_DIRTY); flags.set(F_BG_COLOR_P_COLOR_DIRTY);
return bgColorPickerColor; return bgColorPickerColor;
} }
std::array<char, 256>* Tri::State::get_save_filename_buffer() { Tri::State::FilenameBufferType* Tri::State::get_save_filename_buffer() {
return &saveFilenameBuffer; return &saveFilenameBuffer;
} }
bool Tri::State::do_save() { bool Tri::State::do_save() {
RenderTexture2D saveTexture = LoadRenderTexture(width, height); sf::RenderTexture saveTexture;
if(!saveTexture.create(width, height)) {
#ifndef NDEBUG
puts("ERROR: Failed to create texture for saving");
#endif
failedMessage = std::string("Failed to create texture for saving");
return false;
}
draw_to_target(&saveTexture); draw_to_target(&saveTexture);
saveTexture.display();
Image saveImage = LoadImageFromTexture(saveTexture.texture); sf::Image saveImage = saveTexture.getTexture().copyToImage();
ImageFlipVertical(&saveImage); std::string filename = std::string(saveFilenameBuffer.data());
UnloadRenderTexture(saveTexture); if(saveImage.saveToFile(filename)) {
if(ExportImage(saveImage, saveFilenameBuffer.data())) {
#ifndef NDEBUG #ifndef NDEBUG
printf("Saved to \"%s\"\n", saveFilenameBuffer.data()); printf("Saved to \"%s\"\n", filename.c_str());
#endif #endif
failedMessage.clear(); failedMessage.clear();
UnloadImage(saveImage);
return true; return true;
} else { } else {
#ifndef NDEBUG #ifndef NDEBUG
printf("ERROR: Failed to save \"%s\"\n", saveFilenameBuffer.data()); printf("ERROR: Failed to save \"%s\"\n", filename.c_str());
#endif #endif
failedMessage = std::string("Failed to save (does the name end in \".png\"?)"); failedMessage = std::string("Failed to save (does the name end in \".png\"?)");
UnloadImage(saveImage);
return false; return false;
} }
} }
const std::string& Tri::State::failed_message() const { std::string_view Tri::State::failed_message() const {
return failedMessage; return failedMessage;
} }
@ -487,35 +438,39 @@ void Tri::State::close_bg_color_picker() {
} }
bool Tri::State::change_width_height() { bool Tri::State::change_width_height() {
if(inputWidth < 0 || inputHeight < 0) { std::bitset<2> warnings;
if(inputWidthHeight[0] < 0 || inputWidthHeight[1] < 0) {
failedMessage = "Width or Height cannot be less than 0"; failedMessage = "Width or Height cannot be less than 0";
return false; return false;
} }
if(inputWidth < 800) { if(inputWidthHeight[0] < 200) {
inputWidth = 800; inputWidthHeight[0] = 200;
warnings.set(F_DISPLAY_HELP);
} }
if(inputHeight < 600) { if(inputWidthHeight[1] < 150) {
inputHeight = 600; inputWidthHeight[1] = 150;
warnings.set(F_IS_RUNNING);
} }
notificationText.fill(0); if(warnings.test(0) && warnings.test(1)) {
std::array<char, 5> tempBuf = {0, 0, 0, 0, 0}; set_notification_text("Width set to 200\nHeight set to 150");
} else if(warnings.test(0)) {
set_notification_text("Width set to 200");
} else if(warnings.test(1)) {
set_notification_text("Height set to 150");
}
append_notification_text("Width set to "); this->width = inputWidthHeight[0];
snprintf(tempBuf.data(), 5, "%u", inputWidth); this->height = inputWidthHeight[1];
append_notification_text(tempBuf.data());
append_notification_text(", Height set to "); window.setSize({this->width, this->height});
snprintf(tempBuf.data(), 5, "%u", inputHeight); sf::View newView(
append_notification_text(tempBuf.data()); sf::Vector2f(width / 2.0f, height / 2.0f),
sf::Vector2f(width, height));
this->width = inputWidth; window.setView(newView);
this->height = inputHeight;
UnloadRenderTexture(drawCache);
SetWindowSize(this->width, this->height);
drawCache = LoadRenderTexture(this->width, this->height);
drawCache.create(width, height);
drawCacheSprite.setTexture(drawCache.getTexture(), true);
flags.set(F_DRAW_CACHE_DIRTY); flags.set(F_DRAW_CACHE_DIRTY);
currentTri_state = CurrentState::NONE; currentTri_state = CurrentState::NONE;
@ -523,10 +478,11 @@ bool Tri::State::change_width_height() {
return true; return true;
} }
int* Tri::State::get_input_width_height() {
return inputWidthHeight;
}
void Tri::State::close_input_width_height_window() { void Tri::State::close_input_width_height_window() {
failedMessage.clear();
inputWidth = width;
inputHeight = height;
flags.reset(F_DISPLAY_CHANGE_SIZE); flags.reset(F_DISPLAY_CHANGE_SIZE);
} }
@ -534,32 +490,21 @@ float Tri::State::get_pi() const {
return pi; return pi;
} }
Color& Tri::State::get_selected_tri_color() { float* Tri::State::get_selected_tri_color() {
tris.at(selectedTri).fillColor = selectedTriColor; 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; return selectedTriColor;
} }
void Tri::State::close_selected_tri_mode() { void Tri::State::close_selected_tri_mode() {
tris.at(selectedTri).fillColor = selectedTriColor; 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); flags.set(F_DRAW_CACHE_DIRTY);
reset_modes(); reset_modes();
} }
bool Tri::State::check_draw_cache() {
if(flags.test(F_DRAW_CACHE_INITIALIZED) && flags.test(F_DRAW_CACHE_DIRTY)) {
// draw cache initialized and dirty
flags.reset(F_DRAW_CACHE_DIRTY);
draw_to_target(&drawCache);
return true;
} else {
return false;
}
}
int* Tri::State::get_input_width() {
return &inputWidth;
}
int* Tri::State::get_input_height() {
return &inputHeight;
}

View file

@ -7,13 +7,8 @@
#include <vector> #include <vector>
#include <array> #include <array>
#pragma GCC diagnostic push #include <SFML/System.hpp>
#pragma GCC diagnostic ignored "-Weffc++" #include <SFML/Graphics.hpp>
#include "glm/glm.hpp"
#pragma GCC diagnostic pop
#include "triangle.hpp"
#include "circle.hpp"
namespace Tri { namespace Tri {
class State { class State {
@ -37,7 +32,6 @@ namespace Tri {
F_SELECT_TRI_MODE = 11, F_SELECT_TRI_MODE = 11,
F_TRI_EDIT_MODE = 12, F_TRI_EDIT_MODE = 12,
F_TRI_EDIT_DRAW_TRI = 13, F_TRI_EDIT_DRAW_TRI = 13,
F_TAB_TOGGLE = 14,
}; };
private: private:
@ -46,42 +40,47 @@ namespace Tri {
BitsetType flags; BitsetType flags;
unsigned int width; unsigned int width;
unsigned int height; unsigned int height;
float dt; const sf::Time dt;
float notificationAlpha; float notification_alpha;
std::array<char, 256> notificationText; typedef std::array<char, 256> NotificationBufferType;
NotificationBufferType notification_text;
std::vector<Triangle> tris; sf::RenderWindow window;
std::vector<sf::ConvexShape> tris;
unsigned int trisIndex; unsigned int trisIndex;
std::array<glm::vec2, 3> currentTri; sf::Vector2f currentTri[3];
CurrentState currentTri_state; CurrentState currentTri_state;
CurrentState currentTri_maxState; CurrentState currentTri_maxState;
Circle pointCircle; sf::CircleShape pointCircle;
Color colorPickerColor; sf::Event event;
Color bgColorPickerColor;
Color bgColor;
std::array<char, 256> saveFilenameBuffer; float colorPickerColor[4];
float bgColorPickerColor[3];
sf::Color bgColor;
typedef std::array<char, 256> FilenameBufferType;
FilenameBufferType saveFilenameBuffer;
std::string failedMessage; std::string failedMessage;
RenderTexture2D drawCache; sf::RenderTexture drawCache;
sf::Sprite drawCacheSprite;
int inputWidthHeight[2];
const float pi; const float pi;
unsigned int selectedTri; unsigned int selectedTri;
Color selectedTriColor; float selectedTriColor[4];
float selectedTriBlinkTimer; float selectedTriBlinkTimer;
int inputWidth;
int inputHeight;
public: public:
void handle_events(); void handle_events();
void update(); void update();
void draw(); void draw();
private: private:
void draw_to_target(RenderTexture2D *target); void draw_to_target(sf::RenderTarget *target);
public: public:
unsigned int get_width() const; unsigned int get_width() const;
@ -94,15 +93,14 @@ namespace Tri {
private: private:
void set_notification_text(const char *text); void set_notification_text(const char *text);
void append_notification_text(const char *text);
public: public:
Color& get_color(); float* get_color();
Color& get_bg_color(); float* get_bg_color();
std::array<char, 256>* get_save_filename_buffer(); FilenameBufferType* get_save_filename_buffer();
bool do_save(); bool do_save();
const std::string& failed_message() const; std::string_view failed_message() const;
void close_save(); void close_save();
private: private:
@ -115,20 +113,14 @@ namespace Tri {
void close_bg_color_picker(); void close_bg_color_picker();
bool change_width_height(); bool change_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_pi() const;
Color& get_selected_tri_color(); float* get_selected_tri_color();
void close_selected_tri_mode(); void close_selected_tri_mode();
private:
bool check_draw_cache();
public:
int* get_input_width();
int* get_input_height();
}; };
} }

View file

@ -1,63 +0,0 @@
#include "triangle.hpp"
Tri::Triangle::Triangle() :
Shape(),
vertices{
glm::vec2{0.0f, 0.0f},
glm::vec2{1.0f, 0.0f},
glm::vec2{0.0f, 1.0f}
}
{}
Tri::Triangle::Triangle(std::array<glm::vec2, 3> vertices) :
Shape(),
vertices(vertices)
{}
Tri::Triangle::Triangle(std::array<glm::vec2, 3> vertices, Color fillColor) :
Shape(fillColor),
vertices(vertices)
{}
Tri::Triangle::Triangle(std::array<glm::vec2, 3> vertices, Color fillColor, Color outlineColor) :
Shape(fillColor, outlineColor),
vertices(vertices)
{}
Tri::Shape& Tri::Triangle::draw() {
std::array<glm::vec2, 3> transformed = {
transform * glm::vec3(vertices[0].x, vertices[0].y, 1.0f),
transform * glm::vec3(vertices[1].x, vertices[1].y, 1.0f),
transform * glm::vec3(vertices[2].x, vertices[2].y, 1.0f)
};
DrawTriangle(
{transformed[0].x, transformed[0].y},
{transformed[1].x, transformed[1].y},
{transformed[2].x, transformed[2].y},
fillColor);
// DrawTriangleLines(
// {transformed[0].x, transformed[0].y},
// {transformed[1].x, transformed[1].y},
// {transformed[2].x, transformed[2].y},
// outlineColor);
return *this;
}
void Tri::Triangle::getVertices(std::vector<glm::vec2> &verticesOut) const {
verticesOut.clear();
for(const glm::vec2 &vertex : vertices) {
verticesOut.push_back(vertex);
}
}
void Tri::Triangle::getTransformedVertices(std::vector<glm::vec2> &verticesOut) const {
verticesOut.clear();
for(glm::vec2 vertex : vertices) {
vertex = transform * glm::vec3(vertex.x, vertex.y, 1.0f);
verticesOut.push_back(vertex);
}
}
float Tri::Triangle::getRadius() const {
return 0.0f;
}

View file

@ -1,29 +0,0 @@
#ifndef TRIANGLES_TRIANGLE_HPP
#define TRIANGLES_TRIANGLE_HPP
#include <array>
#include "shape.hpp"
namespace Tri {
struct Triangle : public Shape {
Triangle();
Triangle(std::array<glm::vec2, 3> vertices);
Triangle(std::array<glm::vec2, 3> vertices, Color fillColor);
Triangle(std::array<glm::vec2, 3> vertices, Color fillColor, Color outlineColor);
std::array<glm::vec2, 3> vertices;
virtual Shape& draw() override;
virtual void getVertices(std::vector<glm::vec2> &verticesOut) const override;
virtual void getTransformedVertices(std::vector<glm::vec2> &verticesOut) const override;
virtual float getRadius() const override;
virtual void setRadius(float /* radius */) override {};
}; // struct Triangle
} // namespace Tri
#endif

View file

@ -1,73 +1,18 @@
#pragma GCC diagnostic push #include "catch.hpp"
#pragma GCC diagnostic ignored "-Weffc++"
#include "catch_amalgamated.hpp" #include <SFML/Graphics.hpp>
#pragma GCC diagnostic pop
#include "helpers.hpp" #include "helpers.hpp"
#include "triangle.hpp"
TEST_CASE("Test is_within_shape", "Helpers") { TEST_CASE("Test is_within_shape", "[Triangles]") {
Tri::Triangle triangle({{ sf::ConvexShape shape;
{0.0f, 10.0f}, shape.setPointCount(3);
{10.0f, 10.0f}, shape.setPoint(0, {0.0f, 10.0f});
{10.0f, 0.0f} shape.setPoint(1, {10.0f, 10.0f});
}}); shape.setPoint(2, {10.0f, 0.0f});
CHECK(Tri::is_within_shape(triangle, {2.0f, 2.0f}) == false); CHECK(Tri::is_within_shape(shape, {2.0f, 2.0f}) == false);
CHECK(Tri::is_within_shape(triangle, {5.0f, 15.0f}) == false); CHECK(Tri::is_within_shape(shape, {5.0f, 15.0f}) == false);
CHECK(Tri::is_within_shape(triangle, {15.0f, 5.0f}) == false); CHECK(Tri::is_within_shape(shape, {15.0f, 5.0f}) == false);
CHECK(Tri::is_within_shape(triangle, {7.0f, 7.0f}) == true); CHECK(Tri::is_within_shape(shape, {7.0f, 7.0f}) == true);
}
TEST_CASE("Test make_counter_clockwise", "Helpers") {
// Note that +x is right and +y is down.
{
// Clockwise triangle.
std::array<glm::vec2, 3> tri{{
{0.0F, 0.0F}, {1.0F, 0.0F}, {2.0F, 2.0F}
}};
Tri::make_counter_clockwise(tri);
CHECK(tri.at(0).x == 2.0F);
CHECK(tri.at(0).y == 2.0F);
CHECK(tri.at(1).x == 1.0F);
CHECK(tri.at(1).y == 0.0F);
CHECK(tri.at(2).x == 0.0F);
CHECK(tri.at(2).y == 0.0F);
}
{
// Counter-Clockwise triangle.
std::array<glm::vec2, 3> tri{{
{2.0F, 0.0F}, {3.0F, 3.0F}, {4.0F, 1.0F}
}};
Tri::make_counter_clockwise(tri);
CHECK(tri.at(0).x == 2.0F);
CHECK(tri.at(0).y == 0.0F);
CHECK(tri.at(1).x == 3.0F);
CHECK(tri.at(1).y == 3.0F);
CHECK(tri.at(2).x == 4.0F);
CHECK(tri.at(2).y == 1.0F);
}
}
TEST_CASE("Test invert_color", "Helpers") {
Color c{
255, 255, 255, 255
};
c = Tri::invert_color(c);
CHECK(c.r == 0);
CHECK(c.g == 0);
CHECK(c.b == 0);
CHECK(c.a == 255);
} }

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1
third_party/glm vendored

@ -1 +0,0 @@
Subproject commit 673a963a0f1eb82f5fcef00b7b873371555e5814

1
third_party/imgui vendored Submodule

@ -0,0 +1 @@
Subproject commit 9418dcb69355558f70de260483424412c5ca2fce

1
third_party/imgui-sfml vendored Submodule

@ -0,0 +1 @@
Subproject commit 488c321155547cb499697dac155aa6269d53c21f

1
third_party/raygui vendored

@ -1 +0,0 @@
Subproject commit 25c8c65a6e5f0f4d4b564a0343861898c6f2778b