diff --git a/content/articles/dijkstras-shortest-path.md b/content/articles/dijkstras-shortest-path.md index b66694e..9884933 100644 --- a/content/articles/dijkstras-shortest-path.md +++ b/content/articles/dijkstras-shortest-path.md @@ -10,6 +10,8 @@ tags: Implementation of the shortest path algorithm, invented by Dijkstra. +![dijkstra's shortest path](/img/algorithms/dijkstras-shortest-path.png "dijkstra's shortest path") + ```python #!/usr/bin/python diff --git a/content/articles/first.md b/content/articles/first.md deleted file mode 100644 index 0a5c0a2..0000000 --- a/content/articles/first.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: "My First Blog Post" -description: "This is a test article." -navigation: true -date: "2025-03-01" -img: "/img/personal-website/login.png" -tags: - - C++20 - - GLSL - - Lua - - Software ---- - -Foobarbazbuz. - -## Component Rendering - -::ExampleComponent -#namedslot -This is a Named Slot -#default -This is the Default Slot -:: - -### Testing Some Markdown Features - -A link: [website-vue](https://github.com/riyyi/website-vue) - -A codeblock: -```js [file.js]{2} meta-info=val -export default () => { - console.log('Code block') -} -``` - -```cpp -int i = 0; -``` - -```php -protected static $router; -$path = parse_url($_SERVER['REQUEST_URI'])['path']; -``` - -Inline `hightlight` looks like so. - -Inline highlight with language `const code: string = 'highlighted code inline'`{lang="ts"} like this. - -- An -- Unordered -- List - -## Heading With No Subheading - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum a tempor -dolor. Nullam mattis sapien vel finibus dignissim. Etiam et diam ultrices, -aliquam enim nec, commodo sem. Cras ut faucibus risus. Suspendisse vel faucibus -ipsum. Duis vel orci nec arcu porttitor fermentum eu quis est. Phasellus elit -odio, elementum ac placerat at, feugiat sit amet sapien. - -Vestibulum dapibus pharetra metus. Integer volutpat lacus nec enim euismod, id -dignissim felis rhoncus. Cras commodo tempus turpis, eu vehicula mi lacinia -eget. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per -inceptos himenaeos. Integer rhoncus dolor ut dolor gravida vestibulum. Ut -dignissim orci a ornare ullamcorper. Nam facilisis mauris sit amet nunc -fermentum pretium. Nullam in nisi at risus luctus viverra. Mauris non congue -dolor, vel finibus lectus. Nam quis leo pretium, sodales augue et, dictum sem. - -Curabitur velit ante, imperdiet in eros eu, iaculis gravida risus. Nullam ut -feugiat eros, viverra vehicula sem. Aliquam finibus mi magna, eu fringilla -tellus ullamcorper vel. Fusce eget auctor mi. Mauris venenatis pellentesque -arcu. Nam ac diam sem. Nulla suscipit sed risus non vehicula. Cras molestie -lectus et tincidunt tempor. Ut tempus lorem id augue semper convallis. Aliquam -vel dui quis dolor cursus faucibus eget a mi. Curabitur tempus justo diam, a -facilisis ligula tincidunt viverra. Donec sodales quis dolor at dignissim. -Nullam placerat vitae urna quis bibendum. - -Integer a magna a velit bibendum mollis. Donec lobortis molestie diam at rutrum. -Nunc viverra gravida metus at facilisis. Nulla sit amet erat sodales, commodo -elit sed, ultrices magna. Aliquam vel purus fringilla, gravida enim quis, -aliquam augue. Duis interdum et erat nec feugiat. Praesent vitae lacinia leo, -non suscipit purus. - -Aenean massa magna, imperdiet id ex quis, mollis auctor lectus. Cras velit -nulla, volutpat eu neque id, semper venenatis urna. Integer in blandit ex, non -scelerisque nisi. Ut sagittis tincidunt enim at volutpat. Sed hendrerit metus ac -interdum laoreet. In rutrum turpis in nulla vulputate suscipit. Nunc dictum nisl -id magna laoreet dapibus. diff --git a/content/articles/inferno.md b/content/articles/inferno.md new file mode 100644 index 0000000..06a12c0 --- /dev/null +++ b/content/articles/inferno.md @@ -0,0 +1,432 @@ +--- +title: "Inferno" +description: "An open-source game engine." +navigation: true +date: "2019-12-10" +img: "/img/inferno/preview.png" +tags: + - C++20 + - GLSL + - Lua + - CMake + - Software +--- + +An open-source game engine.
+Repository at +[GitHub](https://github.com/riyyi/inferno){target="_blank"}, +[GitLab](https://gitlab.com/riyyi/inferno){target="_blank"} or +[Gitea](https://git.riyyi.com/riyyi/inferno){target="_blank"}. +
+ +Video games have always been a hobby of mine, but what if I could combine my +profession with this hobby? Then you get this project, a game engine! + +This open-source game engine is written in C++20, GLSL and Lua with the +libraries EnTT, glad, GLFW, GLM, sol3 and stb, using the build tool CMake. + +## Abstractions + +Making a game engine is not an easy task, if the project has any chance of +success it needs a solid foundation. Therefor, the main focus of the project +thus far has been to work on solid, easy-to-use abstractions. The additional +benefit of this approach is that I learned a lot about software design in the +process. + +### Events + +The library used for window and context creation and receiving input is GLFW, +this is abstracted away in a "Window" wrapper class. This "Window" is created in +the "Application" class, which also binds the callback function for event +handling. + +```cpp +// Make it more readable +#define NF_BIND_EVENT(f) std::bind(&f, this, std::placeholders::_1) + +m_window = std::make_unique(); +m_window->setEventCallback(NF_BIND_EVENT(Application::onEvent)); +``` + +The event callback is stored in a std::function. + +```cpp +inline void setEventCallback(const std::function& callback) { m_eventCallback = callback; } +``` + +Inside the "Window" wrapper, we first associate the wrapper to the internal "GLFWwindow" object. +```cpp +glfwSetWindowUserPointer(m_window, this); +``` + +Then, it can set the appropriate callback functions. In this example, we set the +position of the mouse. It gets the "Window" wrapper from the association, then +creates an event object, which is passed on to the callback function. + +```cpp +// Mouse position callback +glfwSetCursorPosCallback(m_window, [](GLFWwindow* window, double xPos, double yPos) { + Window& w = *(Window*)glfwGetWindowUserPointer(window); + + MousePositionEvent event(xPos, yPos); + w.m_eventCallback(event); +}); +``` + +All we have to do to handle these incoming events is create an event dispatcher +and call dispatch. The dispatcher simply checks if the current event is of the +same type as the provided event and calls the function if this is the case. + +```cpp +void Application::onEvent(Event& e) +{ + EventDispatcher dispatcher(e); + dispatcher.dispatch(NF_BIND_EVENT(Application::onMousePosition)); +} +``` + +### Logging + +I wanted logging to work with both `printf` and `std::cout` style logging. To +make logging fast and easy to use, it also has to be compatible with +user-defined types. The class hierarchy looks like the following: + +``` +. +└── LogStream + └── BufferedLogStream + ├── DebugLogStream + └── StringLogStream +``` + +"LogStream" is an abstract class with a pure virtual function named "write", to +be used as the interface. We need both variants of the write function in order +to also print extended ASCII characters. + +```cpp +class LogStream { +public: + virtual void write(const char* characters, int length) const = 0; + virtual void write(const unsigned char* characters, int length) const = 0; +}; +``` + +To extend the functionality of the "LogStream" class, we override the bitwise +left shift `<<` operator. Support for user-defined types is added in the same way. + +```cpp +const LogStream& operator<<(const LogStream& stream, const std::string& value) +{ + stream.write(value.c_str(), value.length()); + return stream; +} + +const LogStream& operator<<(const LogStream& stream, bool value) +{ + return stream << (value ? "true": "false"); +} +``` + +In order to keep I/O performance high, logging is done in a buffered manner. +When a "BufferedLogStream" is created it allocates an array of 128 bytes in +length on the stack, which you can then write to. If the buffer size is +exceeded, the buffer grows automatically to the required size in chunks of 128, +this time on the heap. + +```cpp +// Append to buffer +memcpy(buffer() + m_count, characters, length); + +// Buffer is increased in chunks of 128 bytes +size_t newCapacity = (m_count + bytes + BUFFER_SIZE - 1) & ~(BUFFER_SIZE - 1); +``` + +The "DebugLogStream" class is used to display messages. It has two variables to +configure this behavior, "newline" and "type", to indicate if a newline should +be printed and the color of the message. When the object goes out of scope, it +will print everything that was added to the buffer in one go. + +```cpp +fwrite(buffer(), 1, count(), stdout); +``` + +Now we get to the actual usage of the system. Using helper functions, printing +colored and formatted messages is easy. Each helper function also has a +non-newline variant as newlines are optional. They simply create a +"DebugLogStream" and return it. + +```cpp +DebugLogStream dbg(bool newline); +DebugLogStream info(bool newline); +DebugLogStream warn(bool newline); +DebugLogStream danger(bool newline); +DebugLogStream success(bool newline); + +dbg() << "Hello World! " << 2.5 << " " << true; +info() << "This will be printed in blue."; +``` + +The printf style logging is more complicated than the previous use case. This is +because the function needs to be able to process any number of arguments and of +any type, this is accomplished using a template parameter pack. We use recursion +to loop through all the parameters in this pack, with an overload for when there +is no parameter expansion. Because the "DebugLogStream" is used for printing, +all the type overloads are available to us, so we don't have to specify the type +like usual with `printf`. + +```cpp +void dbgln(Log type, bool newline); +void dbgln(Log type, bool newline, const char* format); + +template +void dbgln(Log type, bool newline, const char* format, T value, P&&... parameters) +{ + std::string_view view { format }; + + for(uint32_t i = 0; format[i] != '\0'; i++) { + + if (format[i] == '{' && format[i + 1] == '}') { + DebugLogStream(type, false) << view.substr(0, i) << value; + dbgln(type, newline, format + i + 2, parameters...); + return; + } + } +} +``` + +This style of logging also has a bunch of helper functions to make using them +quick and easy. + +```cpp +template void dbgln(const char* format, P&&... parameters) { dbgln(Log::None, true, format, std::forward

(parameters)...); } +template void infoln(const char* format, P&&... parameters) { dbgln(Log::Info, true, format, std::forward

(parameters)...); } +template void warnln(const char* format, P&&... parameters) { dbgln(Log::Warn, true, format, std::forward

(parameters)...); } +template void dangerln(const char* format, P&&... parameters) { dbgln(Log::Danger, true, format, std::forward

(parameters)...); } +template void successln(const char* format, P&&... parameters) { dbgln(Log::Success, true, format, std::forward

(parameters)...); } + +dbgln("Hello {}, print anything! {} {}", "World", 2.5, true); +dangerln("This will print in {}.", "red"); +``` + +Finally, "StringLogStream" is used to convert any supported type into a +std::string. This is achieved by simply converting the buffer in +"BufferedLogStream" to a string and setting the provided string to it when the +object goes out of scope. + +```cpp +StringLogStream str(std::string* fill); + +std::string result; +str(&result) << "Add " << "anything " << 2.5 << " " << false; +``` + +### Shaders + +"Shader" functionality is split into two classes, "Shader" and "ShaderManager". +The manager does exactly what the name would suggest, it manages the resources. +Using convenient functions you can `add`, `load`, `check existence`, `remove` +shaders. The shaders get stored in a hash table (hash map), with the key being +its name and the value a `std::shared_ptr` to the shader object. Adding anything +to the manager that has already been loaded will simply return the existing +instance, to prevent duplication. The other pairs "Texture/TextureManager", +"Font/FontManager" and "Gltf/GltfManager" are structured similarly. + +```cpp +void add(const std::string& name, const std::shared_ptr& shader); +std::shared_ptr load(const std::string& name); +std::shared_ptr load(const std::string& vertexSource, + const std::string& fragmentSource); +bool exists(const std::string& name); + +void remove(const std::string& name); +void remove(const std::shared_ptr& shader); + +std::unordered_map> m_shaderList; +``` + +To construct a "Shader", only a name needs to be provided. It will then load, +compile and link both the vertex and fragment shader files. Any errors like +files not existing or GLSL syntax errors will be printed to the console. + +```cpp +// Get file contents +std::string vertexSrc = File::read(name + ".vert"); +std::string fragmentSrc = File::read(name + ".frag"); + +// Compile shaders +uint32_t vertexID = compileShader(GL_VERTEX_SHADER, vertexSrc.c_str()); +uint32_t fragmentID = compileShader(GL_FRAGMENT_SHADER, fragmentSrc.c_str()); + +// Link shaders +if (vertexID > 0 && fragmentID > 0) { + m_id = linkShader(vertexID, fragmentID); +} +``` + +An uniform is a global "Shader" variable, to set uniform data, there are +functions for the most common used data types. ASSERT is a wrapper for `dbgln`, +which can print any registered data types, explained in the +[Logging](#logging) +section. + +```cpp +int32_t findUniform(const std::string& name) const; + +void setInt(const std::string& name, int value); +void setInt(const std::string& name, int* values, uint32_t count); +void setFloat(const std::string& name, float value) const; +void setFloat(const std::string& name, float v1, float v2, float v3, float v4) const; +void setFloat(const std::string& name, glm::vec2 value) const; +void setFloat(const std::string& name, glm::vec3 value) const; +void setFloat(const std::string& name, glm::vec4 value) const; +void setFloat(const std::string& name, glm::mat3 matrix) const; +void setFloat(const std::string& name, glm::mat4 matrix) const; + +void bind() const; +void unbind() const; + +int32_t Shader::findUniform(const std::string& name) const +{ + int32_t location = glGetUniformLocation(m_id, name.c_str()); + ASSERT(location != -1, "Shader could not find uniform '{}'", name); + return location; +} + +void Shader::setInt(const std::string& name, int value) +{ + // Set unifrom int + glUniform1i(findUniform(name), value); +} +``` + +In the renderer we only need to do the following. The `the` function in +"ShaderManager" is a static function that gets the instance of the singleton. + +```cpp +m_shader = ShaderManager::the().load("assets/glsl/batch-quad"); + +m_shader->bind(); +m_shader->setFloat("u_projectionView", cameraProjectionView); +m_shader->unbind(); +``` + +### Buffers + +Rendering in OpenGL is done using a set of two buffers, the vertex buffer and +the index buffer. The vertex buffer is used to store geometry data, in the +format of points. From these points, you can construct triangles, which can +actually be rendered. The constructed triangles are stored as indexes to points +in the index buffer, the simplest form of an index buffer of a single triangle +looks like the following: `[0, 1, 2]`. When you have these two buffers set up +correctly, you can draw a triangle. + +```cpp +void RenderCommand::drawIndexed(const VertexArray& vertexArray, uint32_t indexCount) +{ + uint32_t count = indexCount ? indexCount : vertexArray.getIndexBuffer()->getCount(); + glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, nullptr); +} +``` + +To create a vertex buffer a lot of manual setup is needed. This includes offset +calculation, which is very prone to errors, so these steps are abstracted away +in the "VertexBuffer" class. A "VertexBuffer" object stores a "BufferLayout", +which stores a `std::vector` of "BufferElements". "BufferElements" are what +OpenGL calls vertex attributes, which allows us to specify any input we want, +because of this we also have to specify how the data should be interpreted. The +"BufferElement" objects hold the variables `type`, the types `size`, `offset` in +the buffer and if the data is normalized, these can be accessed via getter +functions. Now for the fun part of vertex buffers, the "BufferLayout", via a +`std::initializer_list` they can be constructed very easily. + +```cpp +BufferLayout::BufferLayout(const std::initializer_list& elements) + : m_elements(elements) +{ + calculateOffsetsAndStride(); +} + +void BufferLayout::calculateOffsetsAndStride() +{ + m_stride = 0; + for (auto& element : m_elements) { + element.setOffset(m_stride); + m_stride += element.getSize(); + } +} +``` + +Because of OpenGL's C-like API, rendering requires manual binding of the vertex +attribute configurations and vertex buffers, which is a lot of boilerplate. In +order to simplify this, a new concept was added called a vertex array object +(also known as VAO), which is actually required in OpenGL's core profile. VAOs +store the vertex attribute configuration and the associated vertex buffers, they +are abstracted away in the "VertexArray" class which stores the "VertexBuffers" +and an "IndexBuffer". When adding a "VertexBuffer" to a "VertexArray", it will +set up the vertex attribute configuration. + +```cpp +void VertexArray::addVertexBuffer(std::shared_ptr vertexBuffer) +{ + const auto& layout = vertexBuffer->getLayout(); + ASSERT(layout.getElements().size(), "VertexBuffer has no layout"); + + bind(); + vertexBuffer->bind(); + + uint32_t index = 0; + for (const auto& element : layout) { + glEnableVertexAttribArray(index); + glVertexAttribPointer( + index, + element.getTypeCount(), + element.getTypeGL(), + element.getNormalized() ? GL_TRUE : GL_FALSE, + layout.getStride(), + reinterpret_cast(element.getOffset())); + + index++; + } + m_vertexBuffers.push_back(std::move(vertexBuffer)); + + unbind(); + vertexBuffer->unbind(); +} +``` + +The final usage looks like the following, notice how the layout is created +easily using the `std::initializer_list` in the `setLayout` function. + +```cpp +// Create vertex array +m_vertexArray = std::make_shared(); + +// Create vertex buffer +auto vertexBuffer = std::make_shared(sizeof(QuadVertex) * vertexCount); +vertexBuffer->setLayout({ + { BufferElementType::Vec3, "a_position" }, + { BufferElementType::Vec4, "a_color" }, + { BufferElementType::Vec2, "a_textureCoordinates" }, + { BufferElementType::Float, "a_textureIndex" }, +}); +m_vertexArray->addVertexBuffer(vertexBuffer); + +uint32_t indices[] { 0, 1, 2, 2, 3, 0 }; + +// Create index buffer +auto indexBuffer = std::make_shared(indices, sizeof(uint32_t) * indexCount); +m_vertexArray->setIndexBuffer(indexBuffer); +``` + +## Design Structure + +Pictured below are all the classes currently in the codebase and the +relationships between them. + +![design](/img/inferno/design.png "design") + +## Preview + +There isn't much visual to show as of now. + +![preview](/img/inferno/preview.png "preview") diff --git a/content/articles/invert-binary-tree.md b/content/articles/invert-binary-tree.md index ec4c555..0b246e7 100644 --- a/content/articles/invert-binary-tree.md +++ b/content/articles/invert-binary-tree.md @@ -10,6 +10,8 @@ tags: Implementation of inverting a binary tree. +![invert binary tree](/img/algorithms/invert-binary-tree.png "invert binary tree") + binarytree.h ```cpp #ifndef BINARYTREE_H diff --git a/content/articles/manafiles.md b/content/articles/manafiles.md new file mode 100644 index 0000000..9748310 --- /dev/null +++ b/content/articles/manafiles.md @@ -0,0 +1,63 @@ +--- +title: "Manafiles" +description: "Config file and package tracking utility." +navigation: false +date: "2021-09-04" +img: "/img/manafiles-unit-test.png" +tags: + - C++20 + - CMake + - Software +--- + +Config file and package tracking utility.
+Repository at +[GitHub](https://github.com/riyyi/manafiles){target="_blank"}, +[GitLab](https://gitlab.com/riyyi/manafiles){target="_blank"} or +[Gitea](https://git.riyyi.com/riyyi/manafiles){target="_blank"}. +
+ +Written in C++20, using the build tool CMake. + +The goal of this project is simple reproducibility of a Linux system, without +using symlinks. This is achieved by managing configuration files and keeping +track of what packages were installed. + +In order to use the same bundle of files for multiple systems, the program +allows to specify variables inside of the configuration files. These +configuration lines will then get commented or uncommented when pushing the +configuration to the system, depending on the value of the variables. The +variables that are supported are the `distribution` the `hostname`, the +`username`, and the display `session`, which is either X.Org or Wayland. + +Below an example of a variable block, where I set the amount of jobs the +compiler will use, depending on the hostname, because my desktop has more cores +than my laptop. + +``` +# >>> hostname=arch-desktop +MAKEFLAGS="-j8" +# <<< +# >>> hostname=arch-laptop +# MAKEFLAGS="-j4" +# <<< +``` + +List of features: + +- Manage dotfiles and system config files. +- Selectively comment and uncomment depending on machine configuration. +- Store a list of all installed packages. +- Install packages from a stored list. +- Pattern matching in the config file and cli arguments. +- Test suite with unit tests, using my own macros. + +Pictured below is the output of running the test suite: + +

+
+ +![manafiles unit test output](/img/manafiles-unit-test.png "manafiles unit test output") + +
+
diff --git a/content/articles/opengl-test.md b/content/articles/opengl-test.md new file mode 100644 index 0000000..5c4d6f7 --- /dev/null +++ b/content/articles/opengl-test.md @@ -0,0 +1,36 @@ +--- +title: "OpenGL Test" +description: "OpenGL test." +navigation: false +date: "2021-02-20" +img: "/img/opengl-test-preview.png" +tags: + - C++14 + - GLSL + - CMake + - Software +--- + +Config file and package tracking utility.
+Repository at +[GitHub](https://github.com/riyyi/opengl-test){target="_blank"}, +[GitLab](https://gitlab.com/riyyi/opengl-test){target="_blank"} or +[Gitea](https://git.riyyi.com/riyyi/opengl-test){target="_blank"}. +
+ +Written in C++ with glad, glfw, glm and stb, using the build tool CMake. +Created for the Computer Graphics course at Hogeschool Rotterdam. + +Design structure. + +![opengl test design](/img/opengl-test-design.png "opengl test design") + +Preview. + +![opengl test preview](/img/opengl-test-preview.png "opengl test preview") + +Demo showcasing translation, rotation, scaling and textures working. You can +also fly around, which is not shown in the video. + +::VideoLazy{:src="https://riyyi.com/media/opengl-test-demo.webm"} +:: diff --git a/content/articles/personal-website.md b/content/articles/personal-website.md index 1ad3abb..41bce02 100644 --- a/content/articles/personal-website.md +++ b/content/articles/personal-website.md @@ -2,7 +2,7 @@ title: "Personal Website" description: "An open-source content management system, used for this website." navigation: false -date: "2025-03-03" +date: "2021-03-03" img: "/img/personal-website/admin-menu.png" tags: - PHP 7 @@ -13,9 +13,9 @@ tags: Open-source content management system.
Repository at -[GitHub](https://github.com/riyyi/website), -[GitLab](https://gitlab.com/riyyi/website) or -[Gitea](https://git.riyyi.com/riyyi/website). +[GitHub](https://github.com/riyyi/website){target="_blank"}, +[GitLab](https://gitlab.com/riyyi/website){target="_blank"} or +[Gitea](https://git.riyyi.com/riyyi/website){target="_blank"}.
This is the CMS that is used for this website! It's written in PHP 7, MySQL and diff --git a/content/articles/second.md b/content/articles/second.md deleted file mode 100644 index 1cd1c32..0000000 --- a/content/articles/second.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "My Second Blog Post" -description: "This is another article." -navigation: false -date: "2025-03-02" -img: "/img/personal-website/reset-password.png" -tags: - - Bash - - Hardware ---- - -This is another article. diff --git a/content/articles/sieve-of-eratosthenes.md b/content/articles/sieve-of-eratosthenes.md index aef98f7..8521fdc 100644 --- a/content/articles/sieve-of-eratosthenes.md +++ b/content/articles/sieve-of-eratosthenes.md @@ -11,6 +11,8 @@ tags: Implementation of the ancient sieve of Eratosthenes math algorithm, used for finding prime numbers up to a given limit. +![sieve of eratosthenes](/img/algorithms/sieve-of-eratosthenes.gif "sieve of eratosthenes") + ```c #include // bool #include // printf diff --git a/content/articles/simple-2d-rpg.md b/content/articles/simple-2d-rpg.md new file mode 100644 index 0000000..508cff1 --- /dev/null +++ b/content/articles/simple-2d-rpg.md @@ -0,0 +1,28 @@ +--- +title: "Simple 2D RPG" +description: "Simple 2D RPG." +navigation: false +date: "2015-03-09" +img: "/img/simple-2d-rpg.png" +tags: + - C++11 + - CMake + - Software +--- + +Simple 2D RPG.
+Repository at +[GitHub](https://github.com/riyyi/rpg){target="_blank"}, +[GitLab](https://gitlab.com/riyyi/rpg){target="_blank"} or +[Gitea](https://git.riyyi.com/riyyi/rpg){target="_blank"}. +
+ +Written in C++ with SFML and RapidJSON, using the build tool CMake. + +This is a very old project, the only things that are implemented are walking and +tile collision. Mainly used for exploring the SFML library and Tiled tool for +level design. + +Preview. + +![preview](/img/simple-2d-rpg.png "preview") diff --git a/content/articles/space-walk.md b/content/articles/space-walk.md new file mode 100644 index 0000000..91dc98f --- /dev/null +++ b/content/articles/space-walk.md @@ -0,0 +1,65 @@ +--- +title: "Space Walk" +description: "Space Walk board game." +navigation: false +date: "2021-02-18" +img: "/img/space-walk/phase-1.png" +tags: + - C++11 + - Software +--- + +Board game.
+Repository at +[GitHub](https://github.com/riyyi/space-walk){target="_blank"}, +[GitLab](https://gitlab.com/riyyi/space-walk){target="_blank"} or +[Gitea](https://git.riyyi.com/riyyi/space-walk){target="_blank"}. +
+ +This is an implementation of the [Space +Walk](https://mancala.fandom.com/wiki/Space_Walk){target="_blank"} board game, +written in C++ with ncurses for the UI, built using GNU Make. Created for the +C++ course at Hogeschool Rotterdam. + +UML design. +![UML design](/img/space-walk/uml.png "UML design") + +
+
+ +Title screen +![title screen](/img/space-walk/title-screen.png "title screen") + +
+
+ +Setting player names. +![setting up players](/img/space-walk/player-name.png "setting up players") + +
+
+ +Message box informing the players of the current phase. +![message box](/img/space-walk/phase-1.png "message box") + +
+
+ +Players take turns putting their space ships onto the planets. +![player turn langing space ship](/img/space-walk/place-ship.png "player turn langing space ship") + +
+
+ +Message box informing the players of the current phase. +![message box](/img/space-walk/phase-2.png "message box") + +
+
+ +Players take turns evacuating the planets, where ships that pass the edges of +the board will get sucked into black holes (top and bottom square). +![player turn for evacuating](/img/space-walk/evacuate.png "player turn for evacuating") + +
+
diff --git a/content/articles/utility-library.md b/content/articles/utility-library.md new file mode 100644 index 0000000..4574b42 --- /dev/null +++ b/content/articles/utility-library.md @@ -0,0 +1,200 @@ +--- +title: "Utility Library" +description: "Utility library." +navigation: true +date: "2022-08-17" +img: "/img/ruc-example-unit-test.png" +tags: + - C++20 + - CMake + - Software +--- + +Utility library.
+Repository at +[GitHub](https://github.com/riyyi/ruc){target="_blank"}, +[GitLab](https://gitlab.com/riyyi/ruc){target="_blank"} or +[Gitea](https://git.riyyi.com/riyyi/ruc){target="_blank"}. +
+ +C++20 utility library without any dependencies, using build tool CMake. + +This is an attempt at deduplicating all the commonly used functionality across +my projects and create one cohesive style. + +## Argument parsing + +placeholder + +## Formatting library + +placeholder + +## JSON parsing + +This is a full implementation of the JSON +[RFC 7159](https://datatracker.ietf.org/doc/html/rfc7159) +specification. Created mostly +for fun, but also for the convenient API. + +First, lets specify some JSON that we want to parse. + +```js +{ + "window": { + "title": "Inferno", + "width": 1280 + "height": 720, + "fullscreen": "windowed", + "vsync": false, + } +} +``` + +Then, define the structs the JSON will get serialized into. +```cpp +struct WindowProperties { + std::string title { "Inferno" }; + uint32_t width { 1280 }; + uint32_t height { 720 }; + std::string fullscreen { "borderless" }; + bool vsync { true }; +}; + +struct SettingsProperties { + WindowProperties window; +}; +``` + +To deserialize the JSON into the struct defined above, we first need to read the +contents of the JSON file. Then we call the parse function on it, which will +return an instance of the base class of the JSON library. Calling the get +function on this instance will try to convert it to the specified type, using +user-declared conversion functions, more on this later. + +```cpp +ruc::Json object = ruc::Json::parse(ruc::File("assets/settings.json").data()); + +if (object.type() != ruc::Json::Type::Object) { + ruc::warn("Settings invalid formatting"); + return false; +} + +SettingsProperties properties = object.get(); +``` + +Serializing back into JSON is simple: just create an instance of the JSON base +class, whose constructor takes in any type. After, call the dump function on it. + +```cpp +ruc::Json object = properties; + +auto file = ruc::File("assets/settings.json"); +file.clear(); +file.append(object.dump(1, '\t')); +file.append("\n"); +file.flush(); +``` + +So how does this work, how are the JSON objects mapped to the C++ instances? The +library is using a [clever +trick](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html){target="_blank"} +with Argument-Dependent Lookup (ADL). "Lookup" refers to name lookup, which is +the process a C++ compiler uses to resolve identifiers to their declarations. We +let the compiler search for a function of a specific shape, anywhere in the +project, that allows the library to find functions declared by the user of that +library! What this effectively means is that all a user has to do is declare a +`to` and `from` conversion function anywhere in his project and these will get +used automatically by the library. + +Below the implementation of the window settings. + +```cpp +void fromJson(const ruc::Json& object, WindowProperties& window) +{ + VERIFY(object.type() == ruc::Json::Type::Object); + + if (object.exists("title")) + object.at("title").getTo(window.title); + if (object.exists("width")) + object.at("width").getTo(window.width); + if (object.exists("height")) + object.at("height").getTo(window.height); + if (object.exists("fullscreen")) + object.at("fullscreen").getTo(window.fullscreen); + if (object.exists("vsync")) + object.at("vsync").getTo(window.vsync); +} + +void toJson(ruc::Json& object, const WindowProperties& window) +{ + object = ruc::Json { + { "title", window.title }, + { "width", window.width }, + { "height", window.height }, + { "fullscreen", window.fullscreen }, + { "vsync", window.vsync }, + }; +} + +void fromJson(const ruc::Json& object, SettingsProperties& settings) +{ + VERIFY(object.type() == ruc::Json::Type::Object); + + if (object.exists("window")) + object.at("window").getTo(settings.window); +} + +void toJson(ruc::Json& object, const SettingsProperties& settings) +{ + object = ruc::Json { + { "window", settings.window } + }; +} +``` + +## Unit test macros + +These are some macros to quickly setup a unit test. The usage is made very +simple, as there is no need to setup a main function entrypoint. + +To accomplish this, the macro has a trick to declare the unit test inside of a +struct and that also has a static variable of that struct type. Because its +static it will get allocated on program startup and in the constructor of the +struct it will register the unit test function in the `TestSuite` class. + +What this effectively means, is that after the CMake configuration is setup, all +a user has to do is create a `.cpp` file and put a test inside of it with the +`TEST_CASE` macro. + +```cpp +#include "macro.h" +#include "testcase.h" +#include "testsuite.h" + +TEST_CASE(ExampleUnitTest) +{ + // Test a boolean value + EXPECT(true); +} + +TEST_CASE(ExampleUnitTestTrue) +{ + // Test 2 values, true + int leftside = 3; + int rightside = 3; + EXPECT_EQ(leftside, rightside); +} + +TEST_CASE(ExampleUnitTestFalse) +{ + // Test 2 values, false + int leftside = 3; + int rightside = 5; + EXPECT_EQ(leftside, rightside); +} +``` + +Output of the testcases above: + +![example unit test](/img/ruc-example-unit-test.png "example unit test") diff --git a/public/img/inferno/design.png b/public/img/inferno/design.png new file mode 100644 index 0000000..a92b4f3 Binary files /dev/null and b/public/img/inferno/design.png differ diff --git a/public/img/inferno/preview.png b/public/img/inferno/preview.png new file mode 100644 index 0000000..6a7241c Binary files /dev/null and b/public/img/inferno/preview.png differ diff --git a/public/img/manafiles-unit-test.png b/public/img/manafiles-unit-test.png new file mode 100644 index 0000000..7de695d Binary files /dev/null and b/public/img/manafiles-unit-test.png differ diff --git a/public/img/opengl-test-design.png b/public/img/opengl-test-design.png new file mode 100644 index 0000000..030b467 Binary files /dev/null and b/public/img/opengl-test-design.png differ diff --git a/public/img/opengl-test-preview.png b/public/img/opengl-test-preview.png new file mode 100644 index 0000000..895db80 Binary files /dev/null and b/public/img/opengl-test-preview.png differ diff --git a/public/img/ruc-example-unit-test.png b/public/img/ruc-example-unit-test.png new file mode 100644 index 0000000..3fe0df0 Binary files /dev/null and b/public/img/ruc-example-unit-test.png differ diff --git a/public/img/simple-2d-rpg.png b/public/img/simple-2d-rpg.png new file mode 100644 index 0000000..67fbb0e Binary files /dev/null and b/public/img/simple-2d-rpg.png differ diff --git a/public/img/space-walk/evacuate.png b/public/img/space-walk/evacuate.png new file mode 100644 index 0000000..b063fe3 Binary files /dev/null and b/public/img/space-walk/evacuate.png differ diff --git a/public/img/space-walk/phase-1.png b/public/img/space-walk/phase-1.png new file mode 100644 index 0000000..5685893 Binary files /dev/null and b/public/img/space-walk/phase-1.png differ diff --git a/public/img/space-walk/phase-2.png b/public/img/space-walk/phase-2.png new file mode 100644 index 0000000..3a2f402 Binary files /dev/null and b/public/img/space-walk/phase-2.png differ diff --git a/public/img/space-walk/place-ship.png b/public/img/space-walk/place-ship.png new file mode 100644 index 0000000..f3ab422 Binary files /dev/null and b/public/img/space-walk/place-ship.png differ diff --git a/public/img/space-walk/player-name.png b/public/img/space-walk/player-name.png new file mode 100644 index 0000000..16f55d2 Binary files /dev/null and b/public/img/space-walk/player-name.png differ diff --git a/public/img/space-walk/title-screen.png b/public/img/space-walk/title-screen.png new file mode 100644 index 0000000..e6c7907 Binary files /dev/null and b/public/img/space-walk/title-screen.png differ diff --git a/public/img/space-walk/uml.png b/public/img/space-walk/uml.png new file mode 100644 index 0000000..526b8e2 Binary files /dev/null and b/public/img/space-walk/uml.png differ diff --git a/src/components/video-lazy.vue b/src/components/video-lazy.vue new file mode 100644 index 0000000..11fd0ef --- /dev/null +++ b/src/components/video-lazy.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/src/utils/index.ts b/src/utils/index.ts index e20a999..2f97f97 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -36,3 +36,14 @@ export const prettyDate = function (date: string | Date): string { return formatted; } + +/** + * Check if DOM element is inside the current viewport + */ +export const isInViewport = function(el: HTMLElement): boolean { + const rect = el.getBoundingClientRect(); + return ( + rect.bottom > 0 && + rect.top < (window.innerHeight || document.documentElement.clientHeight) + ); +}