Add software projects pages
@@ -10,6 +10,8 @@ tags:
|
|||||||
|
|
||||||
Implementation of the shortest path algorithm, invented by Dijkstra.
|
Implementation of the shortest path algorithm, invented by Dijkstra.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
```python
|
```python
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -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
|
||||||
|
---
|
||||||
|
|
||||||
|
<small>An open-source game engine.<br>
|
||||||
|
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"}.
|
||||||
|
</small>
|
||||||
|
|
||||||
|
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<Window>();
|
||||||
|
m_window->setEventCallback(NF_BIND_EVENT(Application::onEvent));
|
||||||
|
```
|
||||||
|
|
||||||
|
The event callback is stored in a std::function.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
inline void setEventCallback(const std::function<void(Event&)>& 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<MousePositionEvent>(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<typename T, typename... P>
|
||||||
|
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<typename... P> void dbgln(const char* format, P&&... parameters) { dbgln(Log::None, true, format, std::forward<P>(parameters)...); }
|
||||||
|
template<typename... P> void infoln(const char* format, P&&... parameters) { dbgln(Log::Info, true, format, std::forward<P>(parameters)...); }
|
||||||
|
template<typename... P> void warnln(const char* format, P&&... parameters) { dbgln(Log::Warn, true, format, std::forward<P>(parameters)...); }
|
||||||
|
template<typename... P> void dangerln(const char* format, P&&... parameters) { dbgln(Log::Danger, true, format, std::forward<P>(parameters)...); }
|
||||||
|
template<typename... P> void successln(const char* format, P&&... parameters) { dbgln(Log::Success, true, format, std::forward<P>(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>& shader);
|
||||||
|
std::shared_ptr<Shader> load(const std::string& name);
|
||||||
|
std::shared_ptr<Shader> 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>& shader);
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::shared_ptr<Shader>> 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<BufferElement>& 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> 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<const void*>(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<VertexArray>();
|
||||||
|
|
||||||
|
// Create vertex buffer
|
||||||
|
auto vertexBuffer = std::make_shared<VertexBuffer>(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<IndexBuffer>(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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Preview
|
||||||
|
|
||||||
|
There isn't much visual to show as of now.
|
||||||
|
|
||||||
|

|
||||||
@@ -10,6 +10,8 @@ tags:
|
|||||||
|
|
||||||
Implementation of inverting a binary tree.
|
Implementation of inverting a binary tree.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
binarytree.h
|
binarytree.h
|
||||||
```cpp
|
```cpp
|
||||||
#ifndef BINARYTREE_H
|
#ifndef BINARYTREE_H
|
||||||
|
|||||||
@@ -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
|
||||||
|
---
|
||||||
|
|
||||||
|
<small>Config file and package tracking utility.<br>
|
||||||
|
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"}.
|
||||||
|
</small>
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6">
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -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
|
||||||
|
---
|
||||||
|
|
||||||
|
<small>Config file and package tracking utility.<br>
|
||||||
|
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"}.
|
||||||
|
</small>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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"}
|
||||||
|
::
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
title: "Personal Website"
|
title: "Personal Website"
|
||||||
description: "An open-source content management system, used for this website."
|
description: "An open-source content management system, used for this website."
|
||||||
navigation: false
|
navigation: false
|
||||||
date: "2025-03-03"
|
date: "2021-03-03"
|
||||||
img: "/img/personal-website/admin-menu.png"
|
img: "/img/personal-website/admin-menu.png"
|
||||||
tags:
|
tags:
|
||||||
- PHP 7
|
- PHP 7
|
||||||
@@ -13,9 +13,9 @@ tags:
|
|||||||
|
|
||||||
<small>Open-source content management system.<br>
|
<small>Open-source content management system.<br>
|
||||||
Repository at
|
Repository at
|
||||||
[GitHub](https://github.com/riyyi/website),
|
[GitHub](https://github.com/riyyi/website){target="_blank"},
|
||||||
[GitLab](https://gitlab.com/riyyi/website) or
|
[GitLab](https://gitlab.com/riyyi/website){target="_blank"} or
|
||||||
[Gitea](https://git.riyyi.com/riyyi/website).
|
[Gitea](https://git.riyyi.com/riyyi/website){target="_blank"}.
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
This is the CMS that is used for this website! It's written in PHP 7, MySQL and
|
This is the CMS that is used for this website! It's written in PHP 7, MySQL and
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -11,6 +11,8 @@ tags:
|
|||||||
Implementation of the ancient sieve of Eratosthenes math algorithm, used for
|
Implementation of the ancient sieve of Eratosthenes math algorithm, used for
|
||||||
finding prime numbers up to a given limit.
|
finding prime numbers up to a given limit.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
```c
|
```c
|
||||||
#include <stdbool.h> // bool
|
#include <stdbool.h> // bool
|
||||||
#include <stdio.h> // printf
|
#include <stdio.h> // printf
|
||||||
|
|||||||
@@ -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
|
||||||
|
---
|
||||||
|
|
||||||
|
<small>Simple 2D RPG.<br>
|
||||||
|
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"}.
|
||||||
|
</small>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
@@ -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
|
||||||
|
---
|
||||||
|
|
||||||
|
<small>Board game.<br>
|
||||||
|
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"}.
|
||||||
|
</small>
|
||||||
|
|
||||||
|
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.
|
||||||
|

|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
|
||||||
|
Title screen
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
|
||||||
|
Setting player names.
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
|
||||||
|
Message box informing the players of the current phase.
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
|
||||||
|
Players take turns putting their space ships onto the planets.
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
|
||||||
|
Message box informing the players of the current phase.
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
|
||||||
|
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).
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -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
|
||||||
|
---
|
||||||
|
|
||||||
|
<small>Utility library.<br>
|
||||||
|
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"}.
|
||||||
|
</small>
|
||||||
|
|
||||||
|
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<SettingsProperties>();
|
||||||
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|

|
||||||
|
After Width: | Height: | Size: 582 KiB |
|
After Width: | Height: | Size: 776 KiB |
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 461 KiB |
@@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<video ref="videoRef" class="w-100" controls>
|
||||||
|
<source :src="loadedSrc" type="video/webm">
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, onUnmounted } from "vue"
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
src: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadedSrc = ref<string>();
|
||||||
|
const refinedSrc = computed(() => {
|
||||||
|
return getPublicPath(props.src);
|
||||||
|
});
|
||||||
|
|
||||||
|
const videoRef = ref<HTMLVideoElement | null>(null)
|
||||||
|
|
||||||
|
function lazyLoadVideos() {
|
||||||
|
const video = videoRef.value;
|
||||||
|
if (!video || !isInViewport(video)) return;
|
||||||
|
|
||||||
|
removeEvents();
|
||||||
|
loadedSrc.value = refinedSrc.value;
|
||||||
|
video.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeEvents() {
|
||||||
|
window.removeEventListener("scroll", lazyLoadVideos);
|
||||||
|
window.removeEventListener('resize', lazyLoadVideos);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.addEventListener("scroll", lazyLoadVideos);
|
||||||
|
window.addEventListener("resize", lazyLoadVideos);
|
||||||
|
lazyLoadVideos(); // trigger immediately
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
removeEvents();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
::VideoLazy{:src="/img/path-to-video.webm"}
|
||||||
|
::
|
||||||
|
-->
|
||||||
@@ -36,3 +36,14 @@ export const prettyDate = function (date: string | Date): string {
|
|||||||
|
|
||||||
return formatted;
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||