Compare commits

..

1 commit

Author SHA1 Message Date
3dc170b6e1 Impl "Action" object for undo/redo history
TODO: Store undo/redo history for color editing, allow deleting
individual triangles and undo/redo of them.
2021-01-04 22:19:27 +09:00
35 changed files with 18414 additions and 24745 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*/
compile_commands.json
.clangd/
.cache/

12
.gitmodules vendored
View file

@ -1,6 +1,6 @@
[submodule "third_party/raygui"]
path = third_party/raygui
url = https://github.com/raysan5/raygui.git
[submodule "third_party/glm"]
path = third_party/glm
url = https://github.com/g-truc/glm.git
[submodule "third_party/imgui"]
path = third_party/imgui
url = https://github.com/ocornut/imgui.git
[submodule "third_party/imgui-sfml"]
path = third_party/imgui-sfml
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)
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/glm/glm/glm.hpp)
message(FATAL_ERROR "third_party/glm/glm/glm.hpp is missing!\n \
Please update the glm submodule by running 'git submodule init' and \
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/imgui/imgui.h)
message(FATAL_ERROR "third_party/imgui/imgui.h is missing!\n \
Please update the GameDevTools submodule by running 'git submodule init' and \
'git submodule update'!")
endif()
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/raygui/src/raygui.h)
message(FATAL_ERROR "third_party/raygui/src/raygui.h is missing!\n \
Please update the raygui submodule by running 'git submodule init' and \
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/imgui-sfml/imgui-SFML.h)
message(FATAL_ERROR "third_party/imgui-sfml/imgui-SFML.h is missing!\n \
Please update the GameDevTools submodule by running 'git submodule init' and \
'git submodule update'!")
endif()
@ -16,18 +16,34 @@ if(NOT DEFINED CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
endif()
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
src/shape.cpp
src/triangle.cpp
src/circle.cpp
src/raygui.cpp
third_party/imgui/imgui.cpp
third_party/imgui/imgui_draw.cpp
third_party/imgui/imgui_widgets.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})
if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
@ -36,51 +52,54 @@ else()
add_executable(Triangles ${Triangles_MAIN_SOURCES})
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_compile_features(TrianglesLib PUBLIC cxx_std_17)
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()
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()
if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
target_link_libraries(TrianglesLib PUBLIC
raylib
sfml-graphics sfml-window sfml-system
opengl32
)
else()
target_link_libraries(TrianglesLib PUBLIC
raylib
sfml-graphics sfml-window sfml-system
GL
)
endif()
target_include_directories(TrianglesLib PUBLIC
if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
target_include_directories(TrianglesLib PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/third_party/raygui/src
${CMAKE_CURRENT_SOURCE_DIR}/third_party/glm
)
${SFML_INCLUDE_DIR}
${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")
set(Triangles_UNIT_TEST_SOURCES
src/unittest/test_main.cpp
src/unittest/test_helpers.cpp
third_party/catch/catch_amalgamated.cpp
)
add_executable(UnitTest_Triangles ${Triangles_UNIT_TEST_SOURCES})

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020-2021,2023-2024 Stephen Seo
Copyright (c) 2020 Stephen Seo
Permission is hereby granted, free of charge, to any person obtaining a copy
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
background.
Uses [raygui](https://github.com/raysan5/raygui) for UI.
Uses [imgui](https://github.com/ocornut/imgui) for UI.
# Compiling
@ -18,19 +18,19 @@ Create a build dir, run cmake, and then make to build.
## Dependencies
Raylib should be installed on the system. Other third-party dependencies are git
submodules and should be initialized with `git submodule update --init recursive`.
SFML should be installed on the system. imgui, and imgui-sfml are git submodules
and should be initialized with `git submodule update --init --recursive`.
# Legal stuff
Uses [raylib](https://github.com/raysan5/raylib), which is licensed under the
[zlib license](https://choosealicense.com/licenses/zlib).
Uses [SFML](https://github.com/SFML/SFML), which is licensed under the
[SFML license](https://github.com/SFML/SFML/blob/master/license.md).
Uses [raygui](https://github.com/raysan5/raylib), which is licensed under the
[zlib license](https://choosealicense.com/licenses/zlib).
Uses [imgui](https://github.com/ocornut/imgui), which is licensed under the
[MIT](https://choosealicense.com/licenses/mit/) license.
Uses [glm](https://github.com/g-truc/glm), which is licensed under the [Happy
Bunny License or the MIT License](https://github.com/g-truc/glm/blob/master/copying.txt).
Uses [imgui-sfml](https://github.com/eliasdaler/imgui-sfml), which is licensed
under the [MIT](https://choosealicense.com/licenses/mit/) license.
Uses [catch](https://github.com/catchorg/Catch2), which is licensed under the
[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 <optional>
#include <cassert>
#include <raylib.h>
#include <raygui.h>
#include <imgui.h>
#include "shape.hpp"
#include "state.hpp"
#define SHOW_HELP_WIDTH (state->get_width() / 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
# include <cstdio>
#endif
@ -30,194 +20,126 @@ namespace Tri {
// so this should be called during update, not draw
inline void draw_help(Tri::State *state) {
if(state->get_flags().test(Tri::State::F_DISPLAY_HELP)) {
GuiSetAlpha(1.0f);
if(!GuiWindowBox({10.0f,
10.0f,
800.0f - 20.0f,
600.0f - 20.0f},
"Help")) {
GuiLabel(
{14.0f, 38.0f, 800.0f - 28.0f, 16.0f},
"This is the help window - Press \"H\" to toggle this window");
GuiLabel(
{14.0f, 56.0f, 800.0f - 28.0f, 16.0f},
"Click anywhere to create triangles, one point at a time");
GuiLabel(
{14.0f, 74.0f, 800.0f - 28.0f, 16.0f},
"You cannot draw when a window is open");
GuiLabel(
{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 {
ImGui::SetNextWindowPos(sf::Vector2f(10.0f, 10.0f));
ImGui::SetNextWindowSize(sf::Vector2f(
state->get_width() - 20.0f,
state->get_height() - 20.0f));
ImGui::SetNextWindowBgAlpha(0.7f);
ImGui::Begin("Help Window", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings);
ImGui::Text("This is the help window - Press \"H\" to toggle this window");
ImGui::Text("Click anywhere to create triangles, one point at a time");
ImGui::Text("You cannot draw when a window is open");
ImGui::Text("Press \"U\" to undo. Clicking will remove all future undo history");
ImGui::Text("Press \"R\" to redo.");
ImGui::Text("Press \"C\" to change colors");
ImGui::Text("Press \"B\" to change background color");
ImGui::Text("Press \"P\" to set current color to a color on screen");
ImGui::Text("Press \"S\" to save what was drawn as a png image");
if(ImGui::Button("Close")) {
state->close_help();
}
ImGui::End();
}
}
inline void draw_notification(Tri::State *state) {
float alpha = state->get_notification_alpha();
if(alpha > 0.0f) {
GuiSetAlpha(alpha);
GuiPanel({(800 - SHOW_HELP_WIDTH) / 2.0f,
(600 - SHOW_HELP_HEIGHT) / 2.0f,
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
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_HEIGHT}, nullptr);
GuiLabel({4.0f + (800 - SHOW_HELP_WIDTH) / 2.0f,
4.0f + (600 - SHOW_HELP_HEIGHT) / 2.0f,
SHOW_HELP_WIDTH - 8.0f,
SHOW_HELP_HEIGHT - 8.0f},
state->get_notification_text());
SHOW_HELP_HEIGHT));
ImGui::Begin(
"Notification Window",
nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings);
ImGui::SetWindowFontScale(3.0f);
ImGui::Text("%s", state->get_notification_text());
ImGui::End();
ImGui::PopStyleVar();
}
}
inline void draw_color_picker(Tri::State *state) {
if(state->get_flags().test(Tri::State::F_DISPLAY_COLOR_P)) {
GuiSetAlpha(1.0f);
if(!GuiWindowBox({4.0f, 4.0f, 242.0f, 328.0f}, "Tri Color Picker")) {
auto &color = state->get_color();
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 {
ImGui::Begin("Tri Color Picker");
ImGui::ColorPicker4("Tri Color", state->get_color());
if(ImGui::Button("Close")) {
state->close_color_picker();
}
ImGui::End();
}
}
inline void draw_bg_color_picker(Tri::State *state) {
if(state->get_flags().test(Tri::State::F_DISPLAY_BG_COLOR_P)) {
GuiSetAlpha(1.0f);
if(!GuiWindowBox({250.0f, 4.0f, 242.0f, 292.0f}, "BG Color Picker")) {
auto &colorArray = state->get_bg_color();
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 {
ImGui::Begin("BG Color Picker");
ImGui::ColorPicker3("BG Color", state->get_bg_color());
if(ImGui::Button("Close")) {
state->close_bg_color_picker();
}
ImGui::End();
}
}
inline void draw_save(Tri::State *state) {
if(state->get_flags().test(Tri::State::F_DISPLAY_SAVE)) {
auto *filenameBuffer = state->get_save_filename_buffer();
GuiSetAlpha(1.0f);
if(!GuiWindowBox({4.0f, 300.0f, 292.0f, 292.0f}, "Save")) {
GuiTextBox(
{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")) {
ImGui::Begin("Save");
ImGui::InputText("Filename", filenameBuffer->data(), filenameBuffer->size() - 1);
if(ImGui::Button("Save")) {
if(state->do_save()) {
state->close_save();
}
}
const std::string &string = state->failed_message();
if(!string.empty()) {
GuiLabel({8.0f, 372.0f, 284.0f, 16.0f}, string.c_str());
}
} else {
} else if(ImGui::Button("Cancel")) {
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) {
if(state->get_flags().test(Tri::State::F_DISPLAY_CHANGE_SIZE)) {
GuiSetAlpha(1.0f);
if(!GuiWindowBox({300.0f, 300.0f, 292.0f, 292.0f}, "Change Size")) {
GuiValueBox(
{384.0f, 328.0f, 80.0f, 16.0f},
"Width",
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());
ImGui::Begin("ChangeSize");
ImGui::InputInt2("Width and Height", state->get_input_width_height());
auto string_view = state->failed_message();
if(!string_view.empty()) {
ImGui::TextUnformatted(string_view.data(), string_view.data() + string_view.size());
}
if(GuiButton({304.0f, 394.0f, 70.0f, 16.0f}, "Cancel")) {
if(ImGui::Button("Cancel")) {
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()) {
state->close_input_width_height_window();
}
}
} else {
state->close_input_width_height_window();
}
ImGui::End();
}
}
inline bool is_within_shape(
const Tri::Shape &shape,
glm::vec2 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);
const sf::ConvexShape &shape,
sf::Vector2f xy) {
std::optional<bool> is_right;
for(unsigned int i = 0; i < vertices.size(); ++i) {
glm::vec2 t_a, t_b;
t_b = vertices[(i + 1) % vertices.size()] - vertices[i];
t_a = xy - vertices[i];
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
//
@ -242,53 +164,19 @@ namespace Tri {
}
return true;
}
}
inline Color invert_color(const Color &other) {
return Color{
(unsigned char)(255 - (int)other.r),
(unsigned char)(255 - (int)other.g),
(unsigned char)(255 - (int)other.b),
other.a};
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)) {
GuiSetAlpha(1.0f);
if(!GuiWindowBox({500.0f, 4.0f, 242.0f, 328.0f}, "Edit Tri Color Picker")) {
auto &color = state->get_selected_tri_color();
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")) {
ImGui::Begin("Edit Tri Color Picker");
ImGui::ColorPicker4("Tri Color", state->get_selected_tri_color());
if(ImGui::Button("Close")) {
state->close_selected_tri_mode();
}
} else {
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;
ImGui::End();
}
}
}

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"
#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,80 +4,76 @@
#include <cassert>
#include <string>
#include <cmath>
#include <cstdio>
#include <raylib.h>
#include <imgui-SFML.h>
#include "helpers.hpp"
#define STARTING_HELP_FADE_RATE 0.2f
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
Tri::State::State(int argc, char **argv) :
flags(),
width(DEFAULT_WIDTH),
height(DEFAULT_HEIGHT),
dt(1.0f/60.0f),
notificationAlpha(1.0f),
notificationText(),
tris(),
currentTri(),
#ifndef NDEBUG
# include <cstdio>
#endif
Tri::State::State(int /*argc*/, char ** /*argv*/) :
width(800),
height(600),
dt(sf::microseconds(16666)),
notification_alpha(1.0f),
window(sf::VideoMode(800, 600), "Triangles", sf::Style::Titlebar | sf::Style::Close),
currentTri_state(CurrentState::NONE),
pointCircle({7.0F, 7.0F}, 7.0F, WHITE),
colorPickerColor{255, 255, 255, 255},
bgColorPickerColor{0, 0, 0, 255},
bgColor(BLACK),
saveFilenameBuffer(),
failedMessage(),
drawCache(),
colorPickerColor{1.0f, 1.0f, 1.0f, 1.0f},
bgColorPickerColor{0.0f, 0.0f, 0.0f},
bgColor(sf::Color::Black),
inputWidthHeight{800, 600},
pi(std::acos(-1.0f)),
selectedTri(),
selectedTriColor{255, 255, 255, 255},
selectedTriBlinkTimer(),
inputWidth(800),
inputHeight(600),
history(),
history_idx(0)
{
InitWindow(width, height, "Triangles");
SetTargetFPS(60);
flags.set(F_IS_RUNNING); // is running
ImGui::SFML::Init(window);
window.setFramerateLimit(60);
set_notification_text("Press \"H\" for help");
pointCircle.setRadius(7.0f);
pointCircle.setOrigin(7.0f, 7.0f);
pointCircle.setFillColor(sf::Color::White);
pointCircle.setOutlineColor(sf::Color::Black);
pointCircle.setOutlineThickness(1.0f);
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_DIRTY);
GuiSetStyle(DEFAULT, BACKGROUND_COLOR, 0x303030);
drawCacheSprite.setTexture(drawCache.getTexture(), true);
}
}
#pragma GCC diagnostic pop
Tri::State::Action::Action() :
type(Tri::State::Action::AT_NONE),
idx(0),
color(BLACK),
color(sf::Color::Black),
data{{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}}
{}
Tri::State::Action::Action(const Type& type, IndexT idx, const Color& color, float *data) :
Tri::State::Action::Action(const Type& type, IndexT idx, const sf::Color& color, float *data) :
type(type),
idx(idx),
color(color),
data()
color(color)
{
init(data);
}
Tri::State::Action::Action(Type&& type, IndexT idx, Color&& color, float *data) :
Tri::State::Action::Action(Type&& type, IndexT idx, sf::Color&& color, float *data) :
type(type),
idx(idx),
color(color),
data()
color(color)
{
init(data);
}
@ -97,32 +93,31 @@ void Tri::State::Action::init(float *data) {
default:
type = AT_NONE;
idx = 0;
color = BLACK;
color = sf::Color::Black;
this->data = {{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}};
break;
}
}
Tri::State::~State() {
UnloadRenderTexture(drawCache);
CloseWindow();
window.close();
ImGui::SFML::Shutdown();
}
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);
}
int keyPressed = GetKeyPressed();
while(keyPressed > 0) {
} else if(event.type == sf::Event::KeyPressed) {
if(!flags.test(F_DISPLAY_SAVE)) {
switch(keyPressed) {
case KEY_H:
// TODO use a switch statement
if(event.key.code == sf::Keyboard::H) {
flags.flip(F_DISPLAY_HELP);
break;
case KEY_U:
} else if(event.key.code == sf::Keyboard::U) {
flags.set(F_DRAW_CACHE_DIRTY);
if (history_idx > 0) {
if(history_idx > 0) {
switch(history[history_idx-1].type) {
case Action::AT_TRI:
tris.erase(
@ -132,19 +127,18 @@ void Tri::State::handle_events() {
case Action::AT_TRI_DEL:
{
tris.emplace(
tris.cbegin() + history[history_idx].idx,
Triangle(
{{
{history[history_idx-1].data.tri[0],
history[history_idx-1].data.tri[1]},
{history[history_idx-1].data.tri[2],
history[history_idx-1].data.tri[3]},
{history[history_idx-1].data.tri[4],
history[history_idx-1].data.tri[5]},
}},
history[history_idx-1].color
)
);
tris.cbegin() + history[history_idx-1].idx,
sf::ConvexShape(3));
tris[history[history_idx-1].idx].setPoint(0, sf::Vector2f(
history[history_idx-1].data.tri[0],
history[history_idx-1].data.tri[1]));
tris[history[history_idx-1].idx].setPoint(1, sf::Vector2f(
history[history_idx-1].data.tri[2],
history[history_idx-1].data.tri[3]));
tris[history[history_idx-1].idx].setPoint(2, sf::Vector2f(
history[history_idx-1].data.tri[4],
history[history_idx-1].data.tri[5]));
tris[history[history_idx-1].idx].setFillColor(history[history_idx-1].color);
currentTri_state = CurrentState::NONE;
break;
}
@ -161,8 +155,7 @@ void Tri::State::handle_events() {
}
--history_idx;
}
break;
case KEY_R:
} else if(event.key.code == sf::Keyboard::R) {
flags.set(F_DRAW_CACHE_DIRTY);
if(history_idx < history.size()) {
switch(history[history_idx].type) {
@ -170,18 +163,17 @@ void Tri::State::handle_events() {
{
tris.emplace(
tris.cbegin() + history[history_idx].idx,
Triangle(
{{
{history[history_idx].data.tri[0],
history[history_idx].data.tri[1]},
{history[history_idx].data.tri[2],
history[history_idx].data.tri[3]},
{history[history_idx].data.tri[4],
history[history_idx].data.tri[5]},
}},
history[history_idx].color
)
);
sf::ConvexShape(3));
tris[history[history_idx].idx].setPoint(0, sf::Vector2f(
history[history_idx].data.tri[0],
history[history_idx].data.tri[1]));
tris[history[history_idx].idx].setPoint(1, sf::Vector2f(
history[history_idx].data.tri[2],
history[history_idx].data.tri[3]));
tris[history[history_idx].idx].setPoint(2, sf::Vector2f(
history[history_idx].data.tri[4],
history[history_idx].data.tri[5]));
tris[history[history_idx].idx].setFillColor(history[history_idx].color);
currentTri_state = CurrentState::NONE;
break;
}
@ -200,7 +192,7 @@ void Tri::State::handle_events() {
currentTri[currentTri_state].y = history[history_idx].data.point[1];
currentTri_state = static_cast<CurrentState>(
currentTri_state + 1);
pointCircle.fillColor = history[history_idx].color;
pointCircle.setFillColor(history[history_idx].color);
break;
default:
assert(!"Unreachable code");
@ -208,25 +200,21 @@ void Tri::State::handle_events() {
}
++history_idx;
}
break;
case KEY_C:
} else if(event.key.code == sf::Keyboard::C) {
if(flags.test(F_DISPLAY_COLOR_P)) {
close_color_picker();
} else {
flags.set(F_DISPLAY_COLOR_P);
}
break;
case KEY_B:
} else if(event.key.code == sf::Keyboard::B) {
if(flags.test(F_DISPLAY_BG_COLOR_P)) {
close_bg_color_picker();
} else {
flags.set(F_DISPLAY_BG_COLOR_P);
}
break;
case KEY_S:
} else if(event.key.code == sf::Keyboard::S) {
flags.flip(F_DISPLAY_SAVE);
break;
case KEY_P:
} else if(event.key.code == sf::Keyboard::P) {
flags.flip(F_COPY_COLOR_MODE);
if(flags.test(F_COPY_COLOR_MODE)) {
set_notification_text(
@ -236,18 +224,15 @@ void Tri::State::handle_events() {
"to what was\n"
"clicked on");
} else {
notificationAlpha = 0.0f;
notification_alpha = 0.0f;
}
break;
case KEY_I:
} else if(event.key.code == sf::Keyboard::I) {
flags.flip(F_DISPLAY_CHANGE_SIZE);
if(!flags.test(F_DISPLAY_CHANGE_SIZE)) {
close_input_width_height_window();
} else {
failedMessage = "Press TAB to switch between width/height";
inputWidthHeight[0] = width;
inputWidthHeight[1] = height;
}
break;
case KEY_E:
} else if(event.key.code == sf::Keyboard::E) {
if(flags.test(F_TRI_EDIT_MODE)) {
close_selected_tri_mode();
} else {
@ -256,48 +241,51 @@ void Tri::State::handle_events() {
set_notification_text("Click on a tri\nto edit it");
}
}
break;
case KEY_TAB:
flags.flip(F_TAB_TOGGLE);
break;
}
}
keyPressed = GetKeyPressed();
}
if(IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
} else if(event.type == sf::Event::MouseButtonPressed) {
if(can_draw()) {
switch(currentTri_state) {
case CurrentState::NONE: {
currentTri[0] = {GetMouseX(), GetMouseY()};
case CurrentState::NONE:
{
currentTri[0] = sf::Vector2f(event.mouseButton.x, event.mouseButton.y);
currentTri_state = CurrentState::FIRST;
if(history_idx < history.size()) {
history.resize(history_idx);
}
float points[2] = {currentTri[0].x, currentTri[0].y};
history.push_back(Action(Action::AT_POINT,
0,
pointCircle.fillColor,
pointCircle.getFillColor(),
points));
++history_idx;
break; }
case CurrentState::FIRST: {
currentTri[1] = {GetMouseX(), GetMouseY()};
break;
}
case CurrentState::FIRST:
{
currentTri[1] = sf::Vector2f(event.mouseButton.x, event.mouseButton.y);
currentTri_state = CurrentState::SECOND;
if(history_idx < history.size()) {
history.resize(history_idx);
}
float points[2] = {currentTri[1].x, currentTri[1].y};
history.push_back(Action(Action::AT_POINT,
1,
pointCircle.fillColor,
pointCircle.getFillColor(),
points));
++history_idx;
break; }
case CurrentState::SECOND: {
currentTri[2] = {GetMouseX(), GetMouseY()};
make_counter_clockwise(currentTri);
tris.emplace_back(currentTri, pointCircle.fillColor);
break;
}
case CurrentState::SECOND:
{
currentTri[2] = sf::Vector2f(event.mouseButton.x, event.mouseButton.y);
tris.emplace_back(sf::ConvexShape(3));
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;
flags.set(F_DRAW_CACHE_DIRTY);
@ -311,48 +299,37 @@ void Tri::State::handle_events() {
};
history.push_back(Action(Action::AT_TRI,
tris.size()-1,
pointCircle.fillColor,
pointCircle.getFillColor(),
points));
++history_idx;
break; }
break;
}
}
} else if(flags.test(F_COPY_COLOR_MODE)) {
check_draw_cache();
Image drawImage = LoadImageFromTexture(drawCache.texture);
Color *colors = LoadImageColors(drawImage);
int mx = GetMouseX();
int my = GetMouseY();
if(mx < 0) { mx = 0; }
else if(mx >= drawImage.width) { mx = drawImage.width - 1; }
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];
auto color = drawCache.getTexture().copyToImage()
.getPixel(event.mouseButton.x, event.mouseButton.y);
colorPickerColor[0] = color.r / 255.0f;
colorPickerColor[1] = color.g / 255.0f;
colorPickerColor[2] = color.b / 255.0f;
colorPickerColor[3] = 1.0f;
pointCircle.setFillColor(color);
flags.reset(F_COPY_COLOR_MODE);
set_notification_text("Color set");
UnloadImageColors(colors);
UnloadImage(drawImage);
} else if(flags.test(F_SELECT_TRI_MODE)) {
int mx = GetMouseX();
int my = GetMouseY();
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 (auto &tri : tris) {
if(is_within_shape(tri, {mx, my})) {
tri.outlineColor = invert_color(tri.fillColor);
sf::Vector2f mouseXY = window.mapPixelToCoords(
{event.mouseButton.x, event.mouseButton.y});
for(unsigned int i = tris.size(); 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 = tri.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;
}
}
@ -361,73 +338,44 @@ void Tri::State::handle_events() {
}
}
}
}
}
void Tri::State::update() {
dt = GetFrameTime();
ImGui::SFML::Update(window, dt);
if(notificationAlpha > 0.0f) {
notificationAlpha -= dt * STARTING_HELP_FADE_RATE;
if(notificationAlpha < 0.0f) {
notificationAlpha = 0.0f;
if(notification_alpha > 0.0f) {
notification_alpha -= dt.asSeconds() * STARTING_HELP_FADE_RATE;
if(notification_alpha < 0.0f) {
notification_alpha = 0.0f;
}
}
if(flags.test(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)) {
flags.reset(F_BG_COLOR_P_COLOR_DIRTY);
bgColor = bgColorPickerColor;
bgColor.a = 255;
bgColor.r = (unsigned char)(255 * bgColorPickerColor[0]);
bgColor.g = (unsigned char)(255 * bgColorPickerColor[1]);
bgColor.b = (unsigned char)(255 * bgColorPickerColor[2]);
}
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) {
selectedTriBlinkTimer = 1.0f;
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) {
DrawCircle(currentTri[i].x, currentTri[i].y, pointCircle.getRadius(), pointCircle.fillColor);
}
}
// 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);
@ -436,27 +384,48 @@ void Tri::State::draw() {
Tri::draw_save(this);
Tri::draw_help(this);
EndDrawing();
ImGui::EndFrame();
}
void Tri::State::draw_to_target(RenderTexture2D *target) {
if(target) {
BeginTextureMode(*target);
ClearBackground(bgColor);
// draw tris
for(unsigned int i = 0; i < tris.size(); ++i) {
tris[i].draw();
void Tri::State::draw() {
if(flags.test(F_DRAW_CACHE_INITIALIZED)) {
// draw cache initialized
if(flags.test(F_DRAW_CACHE_DIRTY)) {
// draw cache dirty
flags.reset(F_DRAW_CACHE_DIRTY);
draw_to_target(&drawCache);
drawCache.display();
}
EndTextureMode();
window.draw(drawCacheSprite);
} else {
// Expects BeginDrawing() already having been called prior to this fn
ClearBackground(bgColor);
draw_to_target(&window);
}
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
for(unsigned int i = 0; i < tris.size(); ++i) {
tris[i].draw();
}
target->draw(tris[i]);
}
}
@ -473,73 +442,66 @@ const Tri::State::BitsetType Tri::State::get_flags() const {
}
float Tri::State::get_notification_alpha() const {
return notificationAlpha;
return notification_alpha;
}
const char* Tri::State::get_notification_text() const {
return notificationText.data();
return notification_text.data();
}
void Tri::State::set_notification_text(const char *text) {
notificationText.fill(0);
std::strncpy(notificationText.data(),
notification_text.fill(0);
std::strncpy(notification_text.data(),
text,
notificationText.max_size() - 1);
notificationAlpha = 1.0f;
notification_text.max_size() - 1);
notification_alpha = 1.0f;
}
void Tri::State::append_notification_text(const char *text) {
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() {
float* Tri::State::get_color() {
flags.set(F_COLOR_P_COLOR_DIRTY);
return colorPickerColor;
}
Color& Tri::State::get_bg_color() {
float* Tri::State::get_bg_color() {
flags.set(F_BG_COLOR_P_COLOR_DIRTY);
return bgColorPickerColor;
}
std::array<char, 256>* Tri::State::get_save_filename_buffer() {
Tri::State::FilenameBufferType* Tri::State::get_save_filename_buffer() {
return &saveFilenameBuffer;
}
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);
saveTexture.display();
Image saveImage = LoadImageFromTexture(saveTexture.texture);
ImageFlipVertical(&saveImage);
UnloadRenderTexture(saveTexture);
if(ExportImage(saveImage, saveFilenameBuffer.data())) {
sf::Image saveImage = saveTexture.getTexture().copyToImage();
std::string filename = std::string(saveFilenameBuffer.data());
if(saveImage.saveToFile(filename)) {
#ifndef NDEBUG
printf("Saved to \"%s\"\n", saveFilenameBuffer.data());
printf("Saved to \"%s\"\n", filename.c_str());
#endif
failedMessage.clear();
UnloadImage(saveImage);
return true;
} else {
#ifndef NDEBUG
printf("ERROR: Failed to save \"%s\"\n", saveFilenameBuffer.data());
printf("ERROR: Failed to save \"%s\"\n", filename.c_str());
#endif
failedMessage = std::string("Failed to save (does the name end in \".png\"?)");
UnloadImage(saveImage);
return false;
}
}
const std::string& Tri::State::failed_message() const {
std::string_view Tri::State::failed_message() const {
return failedMessage;
}
@ -584,35 +546,39 @@ void Tri::State::close_bg_color_picker() {
}
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";
return false;
}
if(inputWidth < 800) {
inputWidth = 800;
if(inputWidthHeight[0] < 200) {
inputWidthHeight[0] = 200;
warnings.set(F_DISPLAY_HELP);
}
if(inputHeight < 600) {
inputHeight = 600;
if(inputWidthHeight[1] < 150) {
inputWidthHeight[1] = 150;
warnings.set(F_IS_RUNNING);
}
notificationText.fill(0);
std::array<char, 5> tempBuf = {0, 0, 0, 0, 0};
if(warnings.test(0) && warnings.test(1)) {
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 ");
snprintf(tempBuf.data(), 5, "%u", inputWidth);
append_notification_text(tempBuf.data());
this->width = inputWidthHeight[0];
this->height = inputWidthHeight[1];
append_notification_text(", Height set to ");
snprintf(tempBuf.data(), 5, "%u", inputHeight);
append_notification_text(tempBuf.data());
this->width = inputWidth;
this->height = inputHeight;
UnloadRenderTexture(drawCache);
SetWindowSize(this->width, this->height);
drawCache = LoadRenderTexture(this->width, this->height);
window.setSize({this->width, this->height});
sf::View newView(
sf::Vector2f(width / 2.0f, height / 2.0f),
sf::Vector2f(width, height));
window.setView(newView);
drawCache.create(width, height);
drawCacheSprite.setTexture(drawCache.getTexture(), true);
flags.set(F_DRAW_CACHE_DIRTY);
currentTri_state = CurrentState::NONE;
@ -620,10 +586,11 @@ bool Tri::State::change_width_height() {
return true;
}
int* Tri::State::get_input_width_height() {
return inputWidthHeight;
}
void Tri::State::close_input_width_height_window() {
failedMessage.clear();
inputWidth = width;
inputHeight = height;
flags.reset(F_DISPLAY_CHANGE_SIZE);
}
@ -631,42 +598,31 @@ float Tri::State::get_pi() const {
return pi;
}
Color& Tri::State::get_selected_tri_color() {
tris.at(selectedTri).fillColor = selectedTriColor;
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).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);
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;
}
void Tri::State::restore_points_on_tri_del(Action::IndexT end) {
assert(end < history.size()
&& "Index on history must be in range");
currentTri[2].x = history[end].data.tri[4];
currentTri[2].y = history[end].data.tri[5];
pointCircle.fillColor = history[end].color;
pointCircle.setFillColor(history[end].color);
unsigned int currentTriIdx = 1;
while(end-- > 0) {
if(history[end].type == Action::AT_POINT) {

View file

@ -7,13 +7,8 @@
#include <vector>
#include <array>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#include "glm/glm.hpp"
#pragma GCC diagnostic pop
#include "triangle.hpp"
#include "circle.hpp"
#include <SFML/System.hpp>
#include <SFML/Graphics.hpp>
namespace Tri {
class State {
@ -37,7 +32,6 @@ namespace Tri {
F_SELECT_TRI_MODE = 11,
F_TRI_EDIT_MODE = 12,
F_TRI_EDIT_DRAW_TRI = 13,
F_TAB_TOGGLE = 14,
};
private:
@ -55,16 +49,16 @@ namespace Tri {
Action();
Action(const Type& type,
IndexT idx,
const Color& color,
const sf::Color& color,
float *data);
Action(Type&& type,
IndexT idx,
Color&& color,
sf::Color&& color,
float *data);
Type type;
IndexT idx;
Color color;
sf::Color color;
union Data {
float tri[6];
float point[2];
@ -79,33 +73,38 @@ namespace Tri {
BitsetType flags;
unsigned int width;
unsigned int height;
float dt;
float notificationAlpha;
std::array<char, 256> notificationText;
const sf::Time dt;
float notification_alpha;
typedef std::array<char, 256> NotificationBufferType;
NotificationBufferType notification_text;
std::vector<Triangle> tris;
std::array<glm::vec2, 3> currentTri;
sf::RenderWindow window;
std::vector<sf::ConvexShape> tris;
sf::Vector2f currentTri[3];
CurrentState currentTri_state;
Circle pointCircle;
sf::CircleShape pointCircle;
Color colorPickerColor;
Color bgColorPickerColor;
Color bgColor;
sf::Event event;
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;
RenderTexture2D drawCache;
sf::RenderTexture drawCache;
sf::Sprite drawCacheSprite;
int inputWidthHeight[2];
const float pi;
unsigned int selectedTri;
Color selectedTriColor;
float selectedTriColor[4];
float selectedTriBlinkTimer;
int inputWidth;
int inputHeight;
std::vector<Action> history;
Action::IndexT history_idx;
@ -115,7 +114,7 @@ namespace Tri {
void draw();
private:
void draw_to_target(RenderTexture2D *target);
void draw_to_target(sf::RenderTarget *target);
public:
unsigned int get_width() const;
@ -128,15 +127,14 @@ namespace Tri {
private:
void set_notification_text(const char *text);
void append_notification_text(const char *text);
public:
Color& get_color();
Color& get_bg_color();
float* get_color();
float* get_bg_color();
std::array<char, 256>* get_save_filename_buffer();
FilenameBufferType* get_save_filename_buffer();
bool do_save();
const std::string& failed_message() const;
std::string_view failed_message() const;
void close_save();
private:
@ -149,22 +147,17 @@ namespace Tri {
void close_bg_color_picker();
bool change_width_height();
int* get_input_width_height();
void close_input_width_height_window();
float get_pi() const;
Color& get_selected_tri_color();
float* get_selected_tri_color();
void close_selected_tri_mode();
private:
bool check_draw_cache();
public:
int* get_input_width();
int* get_input_height();
private:
void restore_points_on_tri_del(Action::IndexT end);
};
}

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
#pragma GCC diagnostic ignored "-Weffc++"
#include "catch_amalgamated.hpp"
#pragma GCC diagnostic pop
#include "catch.hpp"
#include <SFML/Graphics.hpp>
#include "helpers.hpp"
#include "triangle.hpp"
TEST_CASE("Test is_within_shape", "Helpers") {
Tri::Triangle triangle({{
{0.0f, 10.0f},
{10.0f, 10.0f},
{10.0f, 0.0f}
}});
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(triangle, {2.0f, 2.0f}) == false);
CHECK(Tri::is_within_shape(triangle, {5.0f, 15.0f}) == false);
CHECK(Tri::is_within_shape(triangle, {15.0f, 5.0f}) == false);
CHECK(Tri::is_within_shape(triangle, {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);
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

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