Compare commits
3
Commits
c607ebcc72
...
fd8973d10d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd8973d10d | ||
|
|
bc3f2c9db5 | ||
|
|
0dd7a05d46 |
@@ -48,3 +48,5 @@ $ cmake .. && make
|
|||||||
- [[https://www.glfw.org/docs/latest/build_guide.html#build_link_cmake_source][Build GLFW using CMake]]
|
- [[https://www.glfw.org/docs/latest/build_guide.html#build_link_cmake_source][Build GLFW using CMake]]
|
||||||
- [[https://learnopengl.com][Learn OpenGL]]
|
- [[https://learnopengl.com][Learn OpenGL]]
|
||||||
- [[https://www.youtube.com/playlist?list=PLlrATfBNZ98dC-V-N3m0Go4deliWHPFwT][Game Engine]] by The Cherno
|
- [[https://www.youtube.com/playlist?list=PLlrATfBNZ98dC-V-N3m0Go4deliWHPFwT][Game Engine]] by The Cherno
|
||||||
|
- [[https://www.youtube.com/watch?v=mnIQEQoHHCU][OpenGL 3D Game Tutorial 32: Font Rendering]]
|
||||||
|
- [[https://youtu.be/d8cfgcJR9Tk][OpenGL 3D Game Tutorial 33: Distance Field Text Rendering]]
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
@@ -0,0 +1,49 @@
|
|||||||
|
#version 450 core
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 color;
|
||||||
|
|
||||||
|
in vec4 v_color;
|
||||||
|
in vec3 v_textureCoordinates;
|
||||||
|
in flat float v_textureIndex;
|
||||||
|
|
||||||
|
uniform samplerCube u_textures[32];
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 textureColor = v_color;
|
||||||
|
switch(int(v_textureIndex)) {
|
||||||
|
case 0: break; // Texture unit 0 is reserved for no texture
|
||||||
|
case 1: textureColor *= texture(u_textures[1], v_textureCoordinates); break;
|
||||||
|
case 2: textureColor *= texture(u_textures[2], v_textureCoordinates); break;
|
||||||
|
case 3: textureColor *= texture(u_textures[3], v_textureCoordinates); break;
|
||||||
|
case 4: textureColor *= texture(u_textures[4], v_textureCoordinates); break;
|
||||||
|
case 5: textureColor *= texture(u_textures[5], v_textureCoordinates); break;
|
||||||
|
case 6: textureColor *= texture(u_textures[6], v_textureCoordinates); break;
|
||||||
|
case 7: textureColor *= texture(u_textures[7], v_textureCoordinates); break;
|
||||||
|
case 8: textureColor *= texture(u_textures[8], v_textureCoordinates); break;
|
||||||
|
case 9: textureColor *= texture(u_textures[9], v_textureCoordinates); break;
|
||||||
|
case 10: textureColor *= texture(u_textures[10], v_textureCoordinates); break;
|
||||||
|
case 11: textureColor *= texture(u_textures[11], v_textureCoordinates); break;
|
||||||
|
case 12: textureColor *= texture(u_textures[12], v_textureCoordinates); break;
|
||||||
|
case 13: textureColor *= texture(u_textures[13], v_textureCoordinates); break;
|
||||||
|
case 14: textureColor *= texture(u_textures[14], v_textureCoordinates); break;
|
||||||
|
case 15: textureColor *= texture(u_textures[15], v_textureCoordinates); break;
|
||||||
|
case 16: textureColor *= texture(u_textures[16], v_textureCoordinates); break;
|
||||||
|
case 17: textureColor *= texture(u_textures[17], v_textureCoordinates); break;
|
||||||
|
case 18: textureColor *= texture(u_textures[18], v_textureCoordinates); break;
|
||||||
|
case 19: textureColor *= texture(u_textures[19], v_textureCoordinates); break;
|
||||||
|
case 20: textureColor *= texture(u_textures[20], v_textureCoordinates); break;
|
||||||
|
case 21: textureColor *= texture(u_textures[21], v_textureCoordinates); break;
|
||||||
|
case 22: textureColor *= texture(u_textures[22], v_textureCoordinates); break;
|
||||||
|
case 23: textureColor *= texture(u_textures[23], v_textureCoordinates); break;
|
||||||
|
case 24: textureColor *= texture(u_textures[24], v_textureCoordinates); break;
|
||||||
|
case 25: textureColor *= texture(u_textures[25], v_textureCoordinates); break;
|
||||||
|
case 26: textureColor *= texture(u_textures[26], v_textureCoordinates); break;
|
||||||
|
case 27: textureColor *= texture(u_textures[27], v_textureCoordinates); break;
|
||||||
|
case 28: textureColor *= texture(u_textures[28], v_textureCoordinates); break;
|
||||||
|
case 29: textureColor *= texture(u_textures[29], v_textureCoordinates); break;
|
||||||
|
case 30: textureColor *= texture(u_textures[30], v_textureCoordinates); break;
|
||||||
|
case 31: textureColor *= texture(u_textures[31], v_textureCoordinates); break;
|
||||||
|
}
|
||||||
|
color = textureColor;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#version 450 core
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 a_position;
|
||||||
|
layout(location = 1) in vec4 a_color;
|
||||||
|
layout(location = 2) in float a_textureIndex;
|
||||||
|
|
||||||
|
out vec4 v_color;
|
||||||
|
out vec3 v_textureCoordinates;
|
||||||
|
out flat float v_textureIndex;
|
||||||
|
|
||||||
|
uniform mat4 u_projectionView;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
v_color = a_color;
|
||||||
|
v_textureCoordinates = a_position;
|
||||||
|
v_textureIndex = a_textureIndex;
|
||||||
|
// Vclip = Camera projection * Camera view * Model transform * Vlocal
|
||||||
|
gl_Position = u_projectionView * vec4(a_position, 1.0f);
|
||||||
|
}
|
||||||
+81
-20
@@ -1,50 +1,111 @@
|
|||||||
{
|
{
|
||||||
"init": "assets/lua/scene1-init.lua",
|
"init": "assets/lua/scene1-init.lua",
|
||||||
"camera": {
|
"entities": [
|
||||||
"name": "Camera",
|
{
|
||||||
"translate": [ 0.0, 0.0, 1.0 ],
|
"id": { "id": 12312312 },
|
||||||
"rotate": [ 0.0, 0.0, -1.0 ],
|
"tag": { "tag": "Camera" },
|
||||||
"scale": [ 1.0, 1.0, 1.0 ],
|
"transform" : {
|
||||||
"type": "perspective",
|
"translate": [0.0, 0.0, 1.0],
|
||||||
"script": {
|
"rotate": [0.0, 0.0, -1.0],
|
||||||
"type": "lua",
|
"scale": [1.0, 1.0, 1.0]
|
||||||
"name": "assets/lua/cameracontroller.lua"
|
},
|
||||||
|
"camera": { "type": "perspective" },
|
||||||
|
"lua-scripts": [
|
||||||
|
{ "path": "assets/lua/cameracontroller.lua" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": { "id": 212563732 },
|
||||||
|
"tag": { "tag": "Skybox" },
|
||||||
|
"transform" : {
|
||||||
|
"translate": [0.0, 0.0, 0.0],
|
||||||
|
"rotate": [0.0, 0.0, 0.0],
|
||||||
|
"scale": [100.0, 100.0, 100.0]
|
||||||
|
},
|
||||||
|
"cubemap": {
|
||||||
|
"color": [ 1.0, 1.0, 1.0, 1.0 ],
|
||||||
|
"texture": "assets/gfx/skybox.jpg"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"quad": [
|
|
||||||
{
|
{
|
||||||
"name": "Quad",
|
"id": { "id": 564564564 },
|
||||||
|
"tag": { "tag": "Quad" },
|
||||||
|
"transform" : {
|
||||||
"translate": [ 0.0, 0.0, 0.0 ],
|
"translate": [ 0.0, 0.0, 0.0 ],
|
||||||
"rotate": [ 0.0, 0.0, 0.0 ],
|
"rotate": [ 0.0, 0.0, 0.0 ],
|
||||||
"scale": [ 1.0, 1.0, 1.0 ],
|
"scale": [ 1.0, 1.0, 1.0 ]
|
||||||
|
},
|
||||||
|
"sprite": {
|
||||||
"color": [ 1.0, 1.0, 1.0, 1.0 ],
|
"color": [ 1.0, 1.0, 1.0, 1.0 ],
|
||||||
"texture": "assets/gfx/test.png"
|
"texture": "assets/gfx/test.png"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Quad 2",
|
"id": { "id": 97897897 },
|
||||||
|
"tag": { "tag": "Quad 2" },
|
||||||
|
"transform" : {
|
||||||
"translate": [ 1.1, 0.0, 0.0 ],
|
"translate": [ 1.1, 0.0, 0.0 ],
|
||||||
"rotate": [ 0.0, 0.0, 0.0 ],
|
"rotate": [ 0.0, 0.0, 0.0 ],
|
||||||
"scale": [ 1.0, 1.0, 1.0 ],
|
"scale": [ 1.0, 1.0, 1.0 ]
|
||||||
|
},
|
||||||
|
"sprite": {
|
||||||
"color": [ 0.5, 0.6, 0.8, 1.0 ],
|
"color": [ 0.5, 0.6, 0.8, 1.0 ],
|
||||||
"texture": "assets/gfx/test.png"
|
"texture": "assets/gfx/test.png"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Quad 3",
|
"id": { "id": 3424242 },
|
||||||
"translate": [ 2.2, 0.0, 0.0 ],
|
"tag": { "tag": "Quad 3" },
|
||||||
|
"transform" : {
|
||||||
|
"translate": [ 2.2, 1.0, 0.0 ],
|
||||||
|
"rotate": [ 0.0, 0.0, -20.0 ],
|
||||||
|
"scale": [ 1.0, 1.0, 1.0 ]
|
||||||
|
},
|
||||||
|
"sprite": {
|
||||||
|
"color": [ 1.0, 1.0, 1.0, 1.0 ],
|
||||||
|
"texture": "assets/gfx/test-inverted.png"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": { "id": 4345472 },
|
||||||
|
"tag": { "tag": "Quad 4" },
|
||||||
|
"transform" : {
|
||||||
|
"translate": [ 0.85, 0.0, 0.0 ],
|
||||||
"rotate": [ 0.0, 0.0, 0.0 ],
|
"rotate": [ 0.0, 0.0, 0.0 ],
|
||||||
"scale": [ 1.0, 1.0, 1.0 ],
|
"scale": [ 0.5, 0.5, 1.0 ]
|
||||||
|
},
|
||||||
|
"sprite": {
|
||||||
|
"color": [ 1.0, 1.0, 1.0, 1.0 ],
|
||||||
|
"texture": "assets/gfx/test-inverted.png"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": { "id": 5234723 },
|
||||||
|
"tag": { "tag": "Quad 5" },
|
||||||
|
"transform" : {
|
||||||
|
"translate": [ 1.0, 0.0, 0.0 ],
|
||||||
|
"rotate": [ 0.0, 0.0, -20.0 ],
|
||||||
|
"scale": [ 0.5, 0.5, 1.0 ]
|
||||||
|
},
|
||||||
|
"sprite": {
|
||||||
"color": [ 1.0, 1.0, 1.0, 1.0 ],
|
"color": [ 1.0, 1.0, 1.0, 1.0 ],
|
||||||
"texture": "assets/gfx/test-inverted.png"
|
"texture": "assets/gfx/test-inverted.png"
|
||||||
}
|
}
|
||||||
],
|
}
|
||||||
"text": [
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Text",
|
"id": { "id": 675754 },
|
||||||
|
"tag": { "tag": "Text" },
|
||||||
|
"text": {
|
||||||
"content": "Hello World!",
|
"content": "Hello World!",
|
||||||
"font": "assets/fnt/open-sans",
|
"font": "assets/fnt/open-sans",
|
||||||
"font-size": 24,
|
"font-size": 24,
|
||||||
"line-spacing": 1.0,
|
"line-spacing": 1.0,
|
||||||
"width": 150
|
"width": 150
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022,2024 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <utility> // std::pair
|
||||||
|
|
||||||
#include "glm/gtc/type_ptr.hpp" // glm::make_mat4
|
#include "glm/gtc/type_ptr.hpp" // glm::make_mat4
|
||||||
#include "ruc/format/log.h"
|
#include "ruc/format/log.h"
|
||||||
#include "ruc/meta/assert.h"
|
#include "ruc/meta/assert.h"
|
||||||
@@ -78,6 +80,7 @@ Application::~Application()
|
|||||||
FontManager::destroy();
|
FontManager::destroy();
|
||||||
RendererCharacter::destroy();
|
RendererCharacter::destroy();
|
||||||
Renderer2D::destroy();
|
Renderer2D::destroy();
|
||||||
|
RendererCubemap::destroy();
|
||||||
RenderCommand::destroy();
|
RenderCommand::destroy();
|
||||||
TextureManager::destroy();
|
TextureManager::destroy();
|
||||||
ShaderManager::destroy();
|
ShaderManager::destroy();
|
||||||
@@ -164,12 +167,15 @@ int Application::run()
|
|||||||
RenderCommand::clearColor({ 0.2f, 0.3f, 0.3f, 1.0f });
|
RenderCommand::clearColor({ 0.2f, 0.3f, 0.3f, 1.0f });
|
||||||
RenderCommand::clear();
|
RenderCommand::clear();
|
||||||
|
|
||||||
Renderer2D::the().beginScene(m_scene->cameraProjectionView()); // camera, lights, environment
|
std::pair<glm::mat4, glm::mat4> projectionView = m_scene->cameraProjectionView();
|
||||||
|
RendererCubemap::the().beginScene(projectionView.first, projectionView.second); // camera, lights, environment
|
||||||
|
Renderer2D::the().beginScene(projectionView.first, projectionView.second); // camera, lights, environment
|
||||||
RendererCharacter::the().beginScene();
|
RendererCharacter::the().beginScene();
|
||||||
|
|
||||||
m_scene->render();
|
m_scene->render();
|
||||||
// RendererCharacter::the().drawCharacter(character, f->texture());
|
// RendererCharacter::the().drawCharacter(character, f->texture());
|
||||||
|
|
||||||
|
RendererCubemap::the().endScene();
|
||||||
Renderer2D::the().endScene();
|
Renderer2D::the().endScene();
|
||||||
RendererCharacter::the().endScene();
|
RendererCharacter::the().endScene();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Riyyi
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ruc/format/print.h"
|
||||||
|
#include "ruc/json/json.h"
|
||||||
|
|
||||||
|
#include "inferno/component/cameracomponent.h"
|
||||||
|
|
||||||
|
namespace Inferno {
|
||||||
|
|
||||||
|
void fromJson(const ruc::Json& json, CameraComponent& value)
|
||||||
|
{
|
||||||
|
VERIFY(json.type() == ruc::Json::Type::Object);
|
||||||
|
|
||||||
|
if (json.exists("type")) {
|
||||||
|
value.type = json.at("type").get<std::string>() == "orthographic" ? CameraType::Orthographic : CameraType::Perspective;
|
||||||
|
}
|
||||||
|
if (json.exists("zoom-level")) {
|
||||||
|
json.at("zoom-level").getTo(value.zoomLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Inferno
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include "glm/ext/matrix_float4x4.hpp" // glm::mat4
|
#include "glm/ext/matrix_float4x4.hpp" // glm::mat4
|
||||||
#include "glm/ext/vector_float3.hpp" // glm::vec3
|
#include "glm/ext/vector_float3.hpp" // glm::vec3
|
||||||
|
#include "ruc/json/json.h"
|
||||||
|
|
||||||
namespace Inferno {
|
namespace Inferno {
|
||||||
|
|
||||||
@@ -32,4 +33,6 @@ struct CameraComponent {
|
|||||||
glm::mat4 projection { 1.0f }; // Identity matrix
|
glm::mat4 projection { 1.0f }; // Identity matrix
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void fromJson(const ruc::Json& json, CameraComponent& value);
|
||||||
|
|
||||||
} // namespace Inferno
|
} // namespace Inferno
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Riyyi
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ruc/json/json.h"
|
||||||
|
|
||||||
|
#include "inferno/component/cubemap-component.h"
|
||||||
|
#include "inferno/component/spritecomponent.h" // TODO: Move glm::x toJson/fromJson to separate file
|
||||||
|
#include "inferno/render/texture.h"
|
||||||
|
|
||||||
|
namespace Inferno {
|
||||||
|
|
||||||
|
void fromJson(const ruc::Json& json, CubemapComponent& value)
|
||||||
|
{
|
||||||
|
VERIFY(json.type() == ruc::Json::Type::Object);
|
||||||
|
|
||||||
|
if (json.exists("color")) {
|
||||||
|
json.at("color").getTo(value.color);
|
||||||
|
}
|
||||||
|
if (json.exists("texture") && json.at("texture").type() == ruc::Json::Type::String) {
|
||||||
|
value.texture = TextureManager::the().load(json.at("texture").asString(), Texture::Type::Cubemap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Inferno
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Riyyi
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory> // std::shared_ptr
|
||||||
|
|
||||||
|
#include "glm/ext/vector_float4.hpp" // glm::vec4
|
||||||
|
#include "ruc/json/json.h"
|
||||||
|
|
||||||
|
#include "inferno/render/texture.h"
|
||||||
|
|
||||||
|
namespace Inferno {
|
||||||
|
|
||||||
|
struct CubemapComponent {
|
||||||
|
glm::vec4 color { 1.0f };
|
||||||
|
std::shared_ptr<Texture> texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
void fromJson(const ruc::Json& json, CubemapComponent& value);
|
||||||
|
|
||||||
|
} // namespace Inferno
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Riyyi
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ruc/json/json.h"
|
||||||
|
|
||||||
|
#include "inferno/component/id-component.h"
|
||||||
|
|
||||||
|
namespace Inferno {
|
||||||
|
|
||||||
|
void fromJson(const ruc::Json& json, IDComponent& value)
|
||||||
|
{
|
||||||
|
VERIFY(json.type() == ruc::Json::Type::Object);
|
||||||
|
|
||||||
|
if (json.exists("id")) {
|
||||||
|
json.at("id").getTo(value.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Inferno
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Riyyi
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ruc/json/json.h"
|
||||||
|
|
||||||
|
#include "inferno/uid.h"
|
||||||
|
|
||||||
|
namespace Inferno {
|
||||||
|
|
||||||
|
struct IDComponent {
|
||||||
|
UID id;
|
||||||
|
|
||||||
|
IDComponent() = default;
|
||||||
|
IDComponent(UID id)
|
||||||
|
: id(id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
IDComponent(const IDComponent&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
void fromJson(const ruc::Json& json, IDComponent& value);
|
||||||
|
|
||||||
|
} // namespace Inferno
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Riyyi
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ruc/json/json.h"
|
||||||
|
|
||||||
|
#include "inferno/component/luascriptcomponent.h"
|
||||||
|
|
||||||
|
namespace Inferno {
|
||||||
|
|
||||||
|
void fromJson(const ruc::Json& json, LuaScriptComponent& value)
|
||||||
|
{
|
||||||
|
VERIFY(json.type() == ruc::Json::Type::Object);
|
||||||
|
|
||||||
|
VERIFY(json.exists("path"), "path not found");
|
||||||
|
json.at("path").getTo(value.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Inferno
|
||||||
@@ -6,9 +6,11 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string> // std::string
|
#include <string>
|
||||||
#include <utility> // std::move
|
#include <utility> // std::move
|
||||||
|
|
||||||
|
#include "ruc/json/json.h"
|
||||||
|
|
||||||
namespace Inferno {
|
namespace Inferno {
|
||||||
|
|
||||||
class LuaScript;
|
class LuaScript;
|
||||||
@@ -24,4 +26,7 @@ struct LuaScriptComponent {
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void fromJson(const ruc::Json& json, LuaScriptComponent& value);
|
||||||
|
|
||||||
} // namespace Inferno
|
} // namespace Inferno
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Riyyi
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ruc/json/json.h"
|
||||||
|
|
||||||
|
#include "inferno/component/nativescriptcomponent.h"
|
||||||
|
|
||||||
|
namespace Inferno {
|
||||||
|
|
||||||
|
void fromJson(const ruc::Json& json, NativeScriptComponent& value)
|
||||||
|
{
|
||||||
|
VERIFY(json.type() == ruc::Json::Type::Object);
|
||||||
|
|
||||||
|
VERIFY(json.exists("name"), "name not found");
|
||||||
|
json.at("name").getTo(value.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Inferno
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility> // std::move
|
||||||
|
|
||||||
#include "inferno/script/nativescript.h"
|
#include "inferno/script/nativescript.h"
|
||||||
|
|
||||||
@@ -14,17 +15,29 @@ namespace Inferno {
|
|||||||
|
|
||||||
struct NativeScriptComponent {
|
struct NativeScriptComponent {
|
||||||
NativeScript* instance { nullptr };
|
NativeScript* instance { nullptr };
|
||||||
|
std::string name;
|
||||||
|
|
||||||
NativeScript::InitializeFunction initialize { nullptr };
|
NativeScript::InitializeFunction initialize { nullptr };
|
||||||
NativeScript::DestroyFunction destroy { nullptr };
|
NativeScript::DestroyFunction destroy { nullptr };
|
||||||
|
|
||||||
// Dont allow manually setting instance during construction
|
// Dont allow manually setting instance during construction
|
||||||
NativeScriptComponent() {}
|
NativeScriptComponent() {}
|
||||||
NativeScriptComponent(const std::string& binding)
|
NativeScriptComponent(const std::string& name)
|
||||||
|
: name(std::move(name))
|
||||||
{
|
{
|
||||||
initialize = NativeScriptBinding::the().initializeBinding(binding);
|
bind();
|
||||||
destroy = NativeScriptBinding::the().destroyBinding(binding);
|
}
|
||||||
|
|
||||||
|
void bind()
|
||||||
|
{
|
||||||
|
VERIFY(initialize == nullptr && destroy == nullptr, "NativeScript already bound");
|
||||||
|
|
||||||
|
VERIFY(name != "", "name not set");
|
||||||
|
initialize = NativeScriptBinding::the().initializeBinding(name);
|
||||||
|
destroy = NativeScriptBinding::the().destroyBinding(name);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void fromJson(const ruc::Json& json, NativeScriptComponent& value);
|
||||||
|
|
||||||
} // namespace Inferno
|
} // namespace Inferno
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Riyyi
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ruc/json/json.h"
|
||||||
|
|
||||||
|
#include "inferno/component/tagcomponent.h"
|
||||||
|
|
||||||
|
namespace Inferno {
|
||||||
|
|
||||||
|
void fromJson(const ruc::Json& json, TagComponent& value)
|
||||||
|
{
|
||||||
|
VERIFY(json.type() == ruc::Json::Type::Object);
|
||||||
|
|
||||||
|
if (json.exists("tag")) {
|
||||||
|
json.at("tag").getTo(value.tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Inferno
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
#include <string> // std::string
|
#include <string> // std::string
|
||||||
#include <utility> // std::move
|
#include <utility> // std::move
|
||||||
|
|
||||||
|
#include "ruc/json/json.h"
|
||||||
|
|
||||||
namespace Inferno {
|
namespace Inferno {
|
||||||
|
|
||||||
struct TagComponent {
|
struct TagComponent {
|
||||||
@@ -23,4 +25,6 @@ struct TagComponent {
|
|||||||
operator const std::string&() const { return tag; }
|
operator const std::string&() const { return tag; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void fromJson(const ruc::Json& json, TagComponent& value);
|
||||||
|
|
||||||
} // namespace Inferno
|
} // namespace Inferno
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022-2024 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
@@ -18,7 +18,7 @@ namespace Inferno {
|
|||||||
struct TextAreaComponent {
|
struct TextAreaComponent {
|
||||||
std::string content;
|
std::string content;
|
||||||
std::string font;
|
std::string font;
|
||||||
unsigned char fontSize { 0 };
|
unsigned char fontSize { 12 };
|
||||||
float lineSpacing { 1.0f };
|
float lineSpacing { 1.0f };
|
||||||
uint32_t width { 0 };
|
uint32_t width { 0 };
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022,2024 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint> // uint32_t
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "entt/entity/entity.hpp" // entt::null
|
||||||
|
#include "entt/entity/fwd.hpp" // entt::entity
|
||||||
#include "glm/ext/matrix_float4x4.hpp" // glm::mat4
|
#include "glm/ext/matrix_float4x4.hpp" // glm::mat4
|
||||||
#include "glm/ext/vector_float3.hpp" // glm::vec3
|
#include "glm/ext/vector_float3.hpp" // glm::vec3
|
||||||
#include "ruc/format/format.h"
|
#include "ruc/format/format.h"
|
||||||
@@ -17,6 +22,8 @@ struct TransformComponent {
|
|||||||
glm::vec3 translate { 0.0f, 0.0f, 0.0f };
|
glm::vec3 translate { 0.0f, 0.0f, 0.0f };
|
||||||
glm::vec3 rotate { 0.0f, 0.0f, 0.0f };
|
glm::vec3 rotate { 0.0f, 0.0f, 0.0f };
|
||||||
glm::vec3 scale { 1.0f, 1.0f, 1.0f };
|
glm::vec3 scale { 1.0f, 1.0f, 1.0f };
|
||||||
|
entt::entity parent { entt::null };
|
||||||
|
|
||||||
glm::mat4 transform { 1.0f }; // Identity matrix
|
glm::mat4 transform { 1.0f }; // Identity matrix
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ Font::Font(const std::string& name)
|
|||||||
std::string font = ruc::File(path).data();
|
std::string font = ruc::File(path).data();
|
||||||
parseFont(font);
|
parseFont(font);
|
||||||
|
|
||||||
m_texture = std::make_shared<Texture>(image);
|
m_texture = Texture2D::create(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move this to ruc
|
// TODO: Move this to ruc
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022,2024 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
@@ -216,10 +216,10 @@ Renderer2D::~Renderer2D()
|
|||||||
Renderer::destroy();
|
Renderer::destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer2D::beginScene(glm::mat4 cameraProjectionView)
|
void Renderer2D::beginScene(glm::mat4 cameraProjection, glm::mat4 cameraView)
|
||||||
{
|
{
|
||||||
m_shader->bind();
|
m_shader->bind();
|
||||||
m_shader->setFloat("u_projectionView", cameraProjectionView);
|
m_shader->setFloat("u_projectionView", cameraProjection * cameraView);
|
||||||
m_shader->unbind();
|
m_shader->unbind();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,6 +311,184 @@ void Renderer2D::nextBatch()
|
|||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
|
RendererCubemap::RendererCubemap(s)
|
||||||
|
{
|
||||||
|
Renderer::initialize();
|
||||||
|
|
||||||
|
// CPU
|
||||||
|
// ---------------------------------
|
||||||
|
|
||||||
|
// Create array for storing quads vertices
|
||||||
|
m_vertexBufferBase = std::make_unique<CubemapVertex[]>(vertexCount);
|
||||||
|
m_vertexBufferPtr = m_vertexBufferBase.get();
|
||||||
|
|
||||||
|
// Set default cubemap vertex positions
|
||||||
|
|
||||||
|
// Back face - v
|
||||||
|
m_vertexPositions[0] = { 0.5f, -0.5f, 0.5f, 1.0f };
|
||||||
|
m_vertexPositions[1] = { -0.5f, -0.5f, 0.5f, 1.0f };
|
||||||
|
m_vertexPositions[2] = { -0.5f, 0.5f, 0.5f, 1.0f };
|
||||||
|
m_vertexPositions[3] = { 0.5f, 0.5f, 0.5f, 1.0f };
|
||||||
|
|
||||||
|
// Left face - v
|
||||||
|
m_vertexPositions[7] = { -0.5f, 0.5f, 0.5f, 1.0f };
|
||||||
|
m_vertexPositions[6] = { -0.5f, 0.5f, -0.5f, 1.0f };
|
||||||
|
m_vertexPositions[5] = { -0.5f, -0.5f, -0.5f, 1.0f };
|
||||||
|
m_vertexPositions[4] = { -0.5f, -0.5f, 0.5f, 1.0f };
|
||||||
|
|
||||||
|
// Right face - v
|
||||||
|
m_vertexPositions[8] = { 0.5f, -0.5f, -0.5f, 1.0f };
|
||||||
|
m_vertexPositions[9] = { 0.5f, -0.5f, 0.5f, 1.0f };
|
||||||
|
m_vertexPositions[10] = { 0.5f, 0.5f, 0.5f, 1.0f };
|
||||||
|
m_vertexPositions[11] = { 0.5f, 0.5f, -0.5f, 1.0f };
|
||||||
|
|
||||||
|
// Front face - v
|
||||||
|
m_vertexPositions[12] = { -0.5f, -0.5f, -0.5f, 1.0f };
|
||||||
|
m_vertexPositions[13] = { 0.5f, -0.5f, -0.5f, 1.0f };
|
||||||
|
m_vertexPositions[14] = { 0.5f, 0.5f, -0.5f, 1.0f };
|
||||||
|
m_vertexPositions[15] = { -0.5f, 0.5f, -0.5f, 1.0f };
|
||||||
|
|
||||||
|
// Top face
|
||||||
|
m_vertexPositions[16] = { -0.5f, 0.5f, -0.5f, 1.0f };
|
||||||
|
m_vertexPositions[17] = { 0.5f, 0.5f, -0.5f, 1.0f };
|
||||||
|
m_vertexPositions[18] = { 0.5f, 0.5f, 0.5f, 1.0f };
|
||||||
|
m_vertexPositions[19] = { -0.5f, 0.5f, 0.5f, 1.0f };
|
||||||
|
|
||||||
|
// Bottom face
|
||||||
|
m_vertexPositions[20] = { -0.5f, -0.5f, -0.5f, 1.0f };
|
||||||
|
m_vertexPositions[21] = { -0.5f, -0.5f, 0.5f, 1.0f };
|
||||||
|
m_vertexPositions[22] = { 0.5f, -0.5f, 0.5f, 1.0f };
|
||||||
|
m_vertexPositions[23] = { 0.5f, -0.5f, -0.5f, 1.0f };
|
||||||
|
|
||||||
|
// Generate indices
|
||||||
|
|
||||||
|
uint32_t* indices = new uint32_t[indexCount];
|
||||||
|
|
||||||
|
uint32_t offset = 0;
|
||||||
|
for (uint32_t i = 0; i < indexCount; i += indexPerQuad) {
|
||||||
|
indices[i + 0] = offset + 0;
|
||||||
|
indices[i + 1] = offset + 1;
|
||||||
|
indices[i + 2] = offset + 2;
|
||||||
|
indices[i + 3] = offset + 2;
|
||||||
|
indices[i + 4] = offset + 3;
|
||||||
|
indices[i + 5] = offset + 0;
|
||||||
|
|
||||||
|
offset += vertexPerQuad;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GPU
|
||||||
|
// ---------------------------------
|
||||||
|
|
||||||
|
// Create vertex buffer
|
||||||
|
auto vertexBuffer = std::make_shared<VertexBuffer>(sizeof(CubemapVertex) * vertexCount);
|
||||||
|
vertexBuffer->setLayout({
|
||||||
|
{ BufferElementType::Vec3, "a_position" },
|
||||||
|
{ BufferElementType::Vec4, "a_color" },
|
||||||
|
{ BufferElementType::Float, "a_textureIndex" },
|
||||||
|
});
|
||||||
|
m_vertexArray->addVertexBuffer(vertexBuffer);
|
||||||
|
|
||||||
|
// Create index buffer
|
||||||
|
auto indexBuffer = std::make_shared<IndexBuffer>(indices, sizeof(uint32_t) * indexCount);
|
||||||
|
m_vertexArray->setIndexBuffer(indexBuffer);
|
||||||
|
delete[] indices;
|
||||||
|
|
||||||
|
ruc::info("RendererCubemap initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
RendererCubemap::~RendererCubemap()
|
||||||
|
{
|
||||||
|
Renderer::destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RendererCubemap::beginScene(glm::mat4 cameraProjection, glm::mat4 cameraView)
|
||||||
|
{
|
||||||
|
// We want the skybox fixed in position, so only retain the rotation and scale.
|
||||||
|
// Set the translation of the camera's view matrix to 0, meaning:
|
||||||
|
// x x x 0
|
||||||
|
// x x x 0
|
||||||
|
// x x x 0
|
||||||
|
// 0 0 0 1
|
||||||
|
cameraView = glm::mat4(glm::mat3(cameraView));
|
||||||
|
|
||||||
|
m_shader->bind();
|
||||||
|
m_shader->setFloat("u_projectionView", cameraProjection * cameraView);
|
||||||
|
m_shader->unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RendererCubemap::endScene()
|
||||||
|
{
|
||||||
|
nextBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RendererCubemap::drawCubemap(const TransformComponent& transform, glm::vec4 color, std::shared_ptr<Texture> texture)
|
||||||
|
{
|
||||||
|
drawCubemap(transform, glm::mat4(color, color, color, color), texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RendererCubemap::drawCubemap(const TransformComponent& transform, glm::mat4 color, std::shared_ptr<Texture> texture)
|
||||||
|
{
|
||||||
|
// Create a new batch if the quad limit has been reached
|
||||||
|
if (m_quadIndex >= quadCount) {
|
||||||
|
nextBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t textureUnitIndex = addTextureUnit(texture);
|
||||||
|
|
||||||
|
// Add the quads 4 vertices
|
||||||
|
for (uint32_t i = 0; i < vertexPerQuad * quadPerCube; i++) {
|
||||||
|
m_vertexBufferPtr->position = transform.transform * m_vertexPositions[i];
|
||||||
|
m_vertexBufferPtr->color = color[i % 4];
|
||||||
|
m_vertexBufferPtr->textureIndex = (float)textureUnitIndex;
|
||||||
|
m_vertexBufferPtr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_quadIndex += quadPerCube;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RendererCubemap::loadShader()
|
||||||
|
{
|
||||||
|
m_shader = ShaderManager::the().load("assets/glsl/batch-cubemap");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RendererCubemap::flush()
|
||||||
|
{
|
||||||
|
if (m_quadIndex == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload vertex data to GPU
|
||||||
|
m_vertexArray->getVertexBuffers().at(0)->uploadData(
|
||||||
|
m_vertexBufferBase.get(),
|
||||||
|
m_quadIndex * vertexPerQuad * sizeof(CubemapVertex));
|
||||||
|
|
||||||
|
bind();
|
||||||
|
|
||||||
|
// Render
|
||||||
|
bool depthTest = RenderCommand::depthTest();
|
||||||
|
RenderCommand::setDepthTest(false);
|
||||||
|
RenderCommand::drawIndexed(*m_vertexArray, m_quadIndex * indexPerQuad);
|
||||||
|
RenderCommand::setDepthTest(depthTest);
|
||||||
|
|
||||||
|
unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RendererCubemap::startBatch()
|
||||||
|
{
|
||||||
|
m_quadIndex = 0;
|
||||||
|
m_vertexBufferPtr = m_vertexBufferBase.get();
|
||||||
|
|
||||||
|
m_textureUnitIndex = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RendererCubemap::nextBatch()
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
startBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
RendererCharacter::RendererCharacter(s)
|
RendererCharacter::RendererCharacter(s)
|
||||||
{
|
{
|
||||||
Renderer::initialize();
|
Renderer::initialize();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022,2024 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
@@ -27,7 +27,13 @@ struct QuadVertex {
|
|||||||
glm::vec3 position { 0.0f, 0.0f, 0.0f };
|
glm::vec3 position { 0.0f, 0.0f, 0.0f };
|
||||||
glm::vec4 color { 1.0f, 1.0f, 1.0f, 1.0f };
|
glm::vec4 color { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||||
glm::vec2 textureCoordinates { 0.0f, 0.0f };
|
glm::vec2 textureCoordinates { 0.0f, 0.0f };
|
||||||
float textureIndex = 0; // @Todo get int to pass to fragment correctly
|
float textureIndex = 0; // TODO: get int to pass to fragment correctly
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CubemapVertex {
|
||||||
|
glm::vec3 position { 0.0f, 0.0f, 0.0f };
|
||||||
|
glm::vec4 color { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||||
|
float textureIndex = 0; // TODO: get int to pass to fragment correctly
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CharacterVertex {
|
struct CharacterVertex {
|
||||||
@@ -66,9 +72,10 @@ public:
|
|||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
public:
|
public:
|
||||||
static const uint32_t vertexPerQuad = 4;
|
static constexpr const uint32_t vertexPerQuad = 4;
|
||||||
static const uint32_t indexPerQuad = 6;
|
static constexpr const uint32_t indexPerQuad = 6;
|
||||||
static const uint32_t textureUnitPerBatch = 32;
|
static constexpr const uint32_t quadPerCube = 6;
|
||||||
|
static constexpr const uint32_t textureUnitPerBatch = 32;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Renderer() {}
|
Renderer() {}
|
||||||
@@ -108,11 +115,12 @@ public:
|
|||||||
|
|
||||||
using Singleton<Renderer2D>::destroy;
|
using Singleton<Renderer2D>::destroy;
|
||||||
|
|
||||||
static const uint32_t quadCount = 1000;
|
// When to start a new batch
|
||||||
static const uint32_t vertexCount = quadCount * vertexPerQuad;
|
static constexpr const uint32_t quadCount = 1000;
|
||||||
static const uint32_t indexCount = quadCount * indexPerQuad;
|
static constexpr const uint32_t vertexCount = quadCount * vertexPerQuad;
|
||||||
|
static constexpr const uint32_t indexCount = quadCount * indexPerQuad;
|
||||||
|
|
||||||
void beginScene(glm::mat4 cameraProjectionView);
|
void beginScene(glm::mat4 cameraProjectionView, glm::mat4 cameraView);
|
||||||
void endScene();
|
void endScene();
|
||||||
|
|
||||||
void drawQuad(const TransformComponent& transform, glm::vec4 color);
|
void drawQuad(const TransformComponent& transform, glm::vec4 color);
|
||||||
@@ -133,6 +141,42 @@ private:
|
|||||||
// Default quad vertex positions
|
// Default quad vertex positions
|
||||||
glm::vec4 m_vertexPositions[vertexPerQuad];
|
glm::vec4 m_vertexPositions[vertexPerQuad];
|
||||||
};
|
};
|
||||||
|
// -------------------------------------
|
||||||
|
|
||||||
|
class RendererCubemap final
|
||||||
|
: public Renderer
|
||||||
|
, public ruc::Singleton<RendererCubemap> {
|
||||||
|
public:
|
||||||
|
RendererCubemap(s);
|
||||||
|
virtual ~RendererCubemap();
|
||||||
|
|
||||||
|
using Singleton<RendererCubemap>::destroy;
|
||||||
|
|
||||||
|
// When to start a new batch
|
||||||
|
static constexpr const uint32_t cubemapCount = 166;
|
||||||
|
static constexpr const uint32_t quadCount = cubemapCount * quadPerCube;
|
||||||
|
static constexpr const uint32_t vertexCount = quadCount * vertexPerQuad;
|
||||||
|
static constexpr const uint32_t indexCount = quadCount * indexPerQuad;
|
||||||
|
|
||||||
|
void beginScene(glm::mat4 cameraProjectionView, glm::mat4 cameraView);
|
||||||
|
void endScene();
|
||||||
|
|
||||||
|
void drawCubemap(const TransformComponent& transform, glm::vec4 color, std::shared_ptr<Texture> texture);
|
||||||
|
void drawCubemap(const TransformComponent& transform, glm::mat4 color, std::shared_ptr<Texture> texture);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void loadShader() override;
|
||||||
|
void flush() override;
|
||||||
|
void startBatch() override;
|
||||||
|
void nextBatch() override;
|
||||||
|
|
||||||
|
// CPU quad vertices
|
||||||
|
std::unique_ptr<CubemapVertex[]> m_vertexBufferBase;
|
||||||
|
CubemapVertex* m_vertexBufferPtr { nullptr };
|
||||||
|
|
||||||
|
// Default cubemap vertex positions
|
||||||
|
glm::vec4 m_vertexPositions[vertexPerQuad * quadPerCube];
|
||||||
|
};
|
||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
|
|
||||||
|
|||||||
+123
-42
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022,2024 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
@@ -19,36 +19,47 @@
|
|||||||
|
|
||||||
namespace Inferno {
|
namespace Inferno {
|
||||||
|
|
||||||
Texture::Texture(const std::string& path)
|
Texture::~Texture()
|
||||||
: m_path(std::move(path))
|
|
||||||
{
|
{
|
||||||
unsigned char* data = nullptr;
|
glDeleteTextures(1, &m_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture::init(uint32_t width, uint32_t height, uint8_t channels)
|
||||||
|
{
|
||||||
|
m_width = width;
|
||||||
|
m_height = height;
|
||||||
|
|
||||||
|
m_internalFormat = (channels == 3) ? GL_RGB8 : GL_RGBA8;
|
||||||
|
m_dataFormat = (channels == 3) ? GL_RGB : GL_RGBA;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
std::shared_ptr<Texture> Texture2D::create(const std::string& path)
|
||||||
|
{
|
||||||
|
auto result = std::shared_ptr<Texture2D>(new Texture2D);
|
||||||
|
result->m_path = path;
|
||||||
|
|
||||||
int width = 0;
|
int width = 0;
|
||||||
int height = 0;
|
int height = 0;
|
||||||
int channels = 0;
|
int channels = 0;
|
||||||
|
unsigned char* data = nullptr;
|
||||||
|
|
||||||
// Load image data
|
// Load image data
|
||||||
stbi_set_flip_vertically_on_load(1);
|
stbi_set_flip_vertically_on_load(1);
|
||||||
data = stbi_load(path.c_str(), &width, &height, &channels, STBI_default);
|
data = stbi_load(path.c_str(), &width, &height, &channels, STBI_default);
|
||||||
VERIFY(data, "failed to load image: '{}'", path);
|
VERIFY(data, "failed to load image: '{}'", path);
|
||||||
|
|
||||||
init(data, width, height, channels);
|
result->init(width, height, channels);
|
||||||
|
result->create(data);
|
||||||
|
|
||||||
// Clean resources
|
// Clean resources
|
||||||
stbi_image_free(data);
|
stbi_image_free(data);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Texture::Texture(unsigned char* data, uint32_t width, uint32_t height, uint8_t channels)
|
void Texture2D::bind(uint32_t unit) const
|
||||||
{
|
|
||||||
init(data, width, height, channels);
|
|
||||||
}
|
|
||||||
|
|
||||||
Texture::~Texture()
|
|
||||||
{
|
|
||||||
glDeleteTextures(1, &m_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Texture::bind(uint32_t unit) const
|
|
||||||
{
|
{
|
||||||
// Set active unit
|
// Set active unit
|
||||||
glActiveTexture(GL_TEXTURE0 + unit);
|
glActiveTexture(GL_TEXTURE0 + unit);
|
||||||
@@ -59,31 +70,12 @@ void Texture::bind(uint32_t unit) const
|
|||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Texture::unbind() const
|
void Texture2D::unbind() const
|
||||||
{
|
{
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------
|
void Texture2D::create(unsigned char* data)
|
||||||
|
|
||||||
void Texture::init(unsigned char* data, uint32_t width, uint32_t height, uint8_t channels)
|
|
||||||
{
|
|
||||||
m_width = width;
|
|
||||||
m_height = height;
|
|
||||||
|
|
||||||
if (channels == 4) {
|
|
||||||
m_internalFormat = GL_RGBA8;
|
|
||||||
m_dataFormat = GL_RGBA;
|
|
||||||
}
|
|
||||||
else if (channels == 3) {
|
|
||||||
m_internalFormat = GL_RGB8;
|
|
||||||
m_dataFormat = GL_RGB;
|
|
||||||
}
|
|
||||||
|
|
||||||
create(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Texture::create(unsigned char* data)
|
|
||||||
{
|
{
|
||||||
m_id = UINT_MAX;
|
m_id = UINT_MAX;
|
||||||
|
|
||||||
@@ -109,10 +101,10 @@ void Texture::create(unsigned char* data)
|
|||||||
data); // Image data
|
data); // Image data
|
||||||
|
|
||||||
// Set the texture wrapping / filtering options
|
// Set the texture wrapping / filtering options
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Magnify
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Minify
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // X
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // X
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Y
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Y
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Minify
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Magnify
|
|
||||||
|
|
||||||
// Automatically generate all mipmap levels
|
// Automatically generate all mipmap levels
|
||||||
glGenerateMipmap(GL_TEXTURE_2D);
|
glGenerateMipmap(GL_TEXTURE_2D);
|
||||||
@@ -123,6 +115,94 @@ void Texture::create(unsigned char* data)
|
|||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
|
std::shared_ptr<Texture> TextureCubemap::create(const std::string& path)
|
||||||
|
{
|
||||||
|
auto result = std::shared_ptr<TextureCubemap>(new TextureCubemap);
|
||||||
|
result->m_path = path;
|
||||||
|
|
||||||
|
result->create();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureCubemap::bind(uint32_t unit) const
|
||||||
|
{
|
||||||
|
// Set active unit
|
||||||
|
glActiveTexture(GL_TEXTURE0 + unit);
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_CUBE_MAP, m_id);
|
||||||
|
|
||||||
|
// Reset unit
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureCubemap::unbind() const
|
||||||
|
{
|
||||||
|
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureCubemap::create()
|
||||||
|
{
|
||||||
|
m_id = UINT_MAX;
|
||||||
|
|
||||||
|
// Create texture object
|
||||||
|
glGenTextures(1, &m_id);
|
||||||
|
|
||||||
|
// Bind texture object
|
||||||
|
glBindTexture(GL_TEXTURE_CUBE_MAP, m_id);
|
||||||
|
|
||||||
|
// Set unpacking of pixel data to byte-alignment,
|
||||||
|
// this prevents alignment issues when using a single byte for color
|
||||||
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
|
||||||
|
stbi_set_flip_vertically_on_load(0);
|
||||||
|
|
||||||
|
static constexpr const char* cubemapPaths[6] { "-px", "-nx", "-py", "-ny", "-pz", "-nz" };
|
||||||
|
size_t dotIndex = m_path.find_last_of('.');
|
||||||
|
std::string path = m_path.substr(0, dotIndex);
|
||||||
|
std::string extension = m_path.substr(dotIndex);
|
||||||
|
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
int channels = 0;
|
||||||
|
unsigned char* data = nullptr;
|
||||||
|
for (size_t i = 0; i < 6; ++i) {
|
||||||
|
std::string facePath = path + cubemapPaths[i] + extension;
|
||||||
|
|
||||||
|
// Load image data
|
||||||
|
data = stbi_load(facePath.c_str(), &width, &height, &channels, STBI_default);
|
||||||
|
VERIFY(data, "failed to load image: '{}'", facePath.c_str());
|
||||||
|
|
||||||
|
init(width, height, channels);
|
||||||
|
|
||||||
|
// Generate texture face
|
||||||
|
glTexImage2D(
|
||||||
|
GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, // Texture target
|
||||||
|
0, // Midmap level, base starts at level 0
|
||||||
|
m_internalFormat, // Internal format
|
||||||
|
m_width, m_height, // Image width/height
|
||||||
|
0, // Always 0 (legacy)
|
||||||
|
m_dataFormat, // Texture source format
|
||||||
|
GL_UNSIGNED_BYTE, // Texture source datatype
|
||||||
|
data); // Image data
|
||||||
|
|
||||||
|
// Clean resources
|
||||||
|
stbi_image_free(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the texture wrapping / filtering options
|
||||||
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Magnify
|
||||||
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // Minify
|
||||||
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // X
|
||||||
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Y
|
||||||
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Z
|
||||||
|
|
||||||
|
// Unbind texture object
|
||||||
|
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
TextureManager::TextureManager(s)
|
TextureManager::TextureManager(s)
|
||||||
{
|
{
|
||||||
ruc::info("TextureManager initialized");
|
ruc::info("TextureManager initialized");
|
||||||
@@ -138,15 +218,16 @@ void TextureManager::add(const std::string& path, std::shared_ptr<Texture> textu
|
|||||||
m_textureList.emplace(std::move(path), std::move(texture));
|
m_textureList.emplace(std::move(path), std::move(texture));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Texture> TextureManager::load(const std::string& path)
|
std::shared_ptr<Texture> TextureManager::load(const std::string& path, Texture::Type type)
|
||||||
{
|
{
|
||||||
if (exists(path)) {
|
if (exists(path)) {
|
||||||
return get(path);
|
return get(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Texture> texture = std::make_shared<Texture>(path);
|
auto texture = (type == Texture::TwoDimensional) ? Texture2D::create(path) : TextureCubemap::create(path);
|
||||||
add(path, texture);
|
add(path, texture);
|
||||||
return get(path);
|
|
||||||
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Texture> TextureManager::get(const std::string& path)
|
std::shared_ptr<Texture> TextureManager::get(const std::string& path)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022,2024 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
@@ -15,33 +15,88 @@
|
|||||||
|
|
||||||
namespace Inferno {
|
namespace Inferno {
|
||||||
|
|
||||||
|
class Texture2D;
|
||||||
|
class TextureCubemap;
|
||||||
|
|
||||||
class Texture {
|
class Texture {
|
||||||
public:
|
public:
|
||||||
Texture(const std::string& path);
|
|
||||||
Texture(unsigned char* data, uint32_t width, uint32_t height, uint8_t channels = 3);
|
|
||||||
virtual ~Texture();
|
virtual ~Texture();
|
||||||
|
|
||||||
void bind(uint32_t unit = 0) const;
|
enum Type : uint8_t {
|
||||||
void unbind() const;
|
TwoDimensional = 0,
|
||||||
|
Cubemap,
|
||||||
|
};
|
||||||
|
|
||||||
inline std::string path() const { return m_path; }
|
void init(uint32_t width, uint32_t height, uint8_t channels);
|
||||||
inline uint32_t width() const { return m_width; }
|
|
||||||
inline uint32_t height() const { return m_height; }
|
virtual void bind(uint32_t unit = 0) const = 0;
|
||||||
inline uint32_t id() const { return m_id; }
|
virtual void unbind() const = 0;
|
||||||
inline uint32_t internalFormat() const { return m_internalFormat; }
|
|
||||||
inline uint32_t dataFormat() const { return m_dataFormat; }
|
std::string path() const { return m_path; }
|
||||||
|
uint32_t width() const { return m_width; }
|
||||||
|
uint32_t height() const { return m_height; }
|
||||||
|
uint32_t id() const { return m_id; }
|
||||||
|
uint32_t internalFormat() const { return m_internalFormat; }
|
||||||
|
uint32_t dataFormat() const { return m_dataFormat; }
|
||||||
|
|
||||||
|
virtual bool is2D() const { return false; }
|
||||||
|
virtual bool isCubeMap() const { return false; }
|
||||||
|
|
||||||
|
friend Texture2D;
|
||||||
|
friend TextureCubemap;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void init(unsigned char* data, uint32_t width, uint32_t height, uint8_t channels);
|
Texture() {}
|
||||||
void create(unsigned char* data);
|
|
||||||
|
protected:
|
||||||
|
std::string m_path;
|
||||||
|
uint32_t m_width { 0 };
|
||||||
|
uint32_t m_height { 0 };
|
||||||
|
uint32_t m_id { 0 };
|
||||||
|
uint32_t m_internalFormat { 0 };
|
||||||
|
uint32_t m_dataFormat { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
// -------------------------------------
|
||||||
|
|
||||||
|
class Texture2D final : public Texture {
|
||||||
|
public:
|
||||||
|
virtual ~Texture2D() = default;
|
||||||
|
|
||||||
|
// Factory function
|
||||||
|
static std::shared_ptr<Texture> create(const std::string& path);
|
||||||
|
|
||||||
|
virtual void bind(uint32_t unit = 0) const override;
|
||||||
|
virtual void unbind() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string m_path;
|
Texture2D() {}
|
||||||
uint32_t m_width;
|
|
||||||
uint32_t m_height;
|
virtual bool is2D() const override { return true; }
|
||||||
uint32_t m_id;
|
|
||||||
uint32_t m_internalFormat;
|
private:
|
||||||
uint32_t m_dataFormat;
|
void create(unsigned char* data);
|
||||||
|
};
|
||||||
|
|
||||||
|
// -------------------------------------
|
||||||
|
|
||||||
|
class TextureCubemap final : public Texture {
|
||||||
|
public:
|
||||||
|
virtual ~TextureCubemap() = default;
|
||||||
|
|
||||||
|
// Factory function
|
||||||
|
static std::shared_ptr<Texture> create(const std::string& path);
|
||||||
|
|
||||||
|
virtual void bind(uint32_t unit = 0) const override;
|
||||||
|
virtual void unbind() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
TextureCubemap() {};
|
||||||
|
|
||||||
|
virtual bool isCubeMap() const override { return true; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void create();
|
||||||
};
|
};
|
||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
@@ -52,7 +107,7 @@ public:
|
|||||||
~TextureManager();
|
~TextureManager();
|
||||||
|
|
||||||
void add(const std::string& path, std::shared_ptr<Texture> texture);
|
void add(const std::string& path, std::shared_ptr<Texture> texture);
|
||||||
std::shared_ptr<Texture> load(const std::string& path);
|
std::shared_ptr<Texture> load(const std::string& path, Texture::Type type = Texture::Type::TwoDimensional);
|
||||||
std::shared_ptr<Texture> get(const std::string& path);
|
std::shared_ptr<Texture> get(const std::string& path);
|
||||||
bool exists(const std::string& path);
|
bool exists(const std::string& path);
|
||||||
|
|
||||||
|
|||||||
+88
-52
@@ -1,18 +1,23 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022-2024 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint32_t
|
#include <cstdint> // uint32_t
|
||||||
#include <limits> // std::numeric_limits
|
#include <limits> // std::numeric_limits
|
||||||
|
#include <utility> // std::pair
|
||||||
|
|
||||||
|
#include "entt/entity/entity.hpp" // ent::entity
|
||||||
#include "ruc/file.h"
|
#include "ruc/file.h"
|
||||||
#include "ruc/format/log.h"
|
#include "ruc/format/log.h"
|
||||||
#include "ruc/json/json.h"
|
#include "ruc/json/json.h"
|
||||||
#include "ruc/meta/assert.h"
|
#include "ruc/meta/assert.h"
|
||||||
|
|
||||||
#include "inferno/component/cameracomponent.h"
|
#include "inferno/component/cameracomponent.h"
|
||||||
|
#include "inferno/component/cubemap-component.h"
|
||||||
|
#include "inferno/component/id-component.h"
|
||||||
#include "inferno/component/luascriptcomponent.h"
|
#include "inferno/component/luascriptcomponent.h"
|
||||||
#include "inferno/component/nativescriptcomponent.h"
|
#include "inferno/component/nativescriptcomponent.h"
|
||||||
#include "inferno/component/spritecomponent.h"
|
#include "inferno/component/spritecomponent.h"
|
||||||
@@ -26,6 +31,7 @@
|
|||||||
#include "inferno/system/scriptsystem.h"
|
#include "inferno/system/scriptsystem.h"
|
||||||
#include "inferno/system/textareasystem.h"
|
#include "inferno/system/textareasystem.h"
|
||||||
#include "inferno/system/transformsystem.h"
|
#include "inferno/system/transformsystem.h"
|
||||||
|
#include "inferno/uid.h"
|
||||||
|
|
||||||
namespace Inferno {
|
namespace Inferno {
|
||||||
|
|
||||||
@@ -47,54 +53,19 @@ void Scene::initialize()
|
|||||||
|
|
||||||
auto sceneJson = ruc::Json::parse(ruc::File("assets/scene/scene1.json").data());
|
auto sceneJson = ruc::Json::parse(ruc::File("assets/scene/scene1.json").data());
|
||||||
|
|
||||||
// Camera
|
if (sceneJson.exists("init")) {
|
||||||
|
// TODO: load either NativeScript or LuaScript?
|
||||||
VERIFY(sceneJson.exists("camera"), "scene doesnt contain a camera");
|
|
||||||
auto& cameraJson = sceneJson.at("camera");
|
|
||||||
uint32_t camera = loadEntity(cameraJson);
|
|
||||||
|
|
||||||
auto& cameraComponent = addComponent<CameraComponent>(camera);
|
|
||||||
if (cameraJson.exists("type") && cameraJson.at("type").get<std::string>() == "orthographic") {
|
|
||||||
cameraComponent.type = CameraType::Orthographic;
|
|
||||||
}
|
|
||||||
if (cameraJson.exists("zoom-level") && cameraJson.at("zoom-level").type() == ruc::Json::Type::Number) {
|
|
||||||
cameraComponent.zoomLevel = cameraJson.at("zoom-level").asDouble();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cameraJson.exists("script")) {
|
// Entities
|
||||||
auto& cameraScript = cameraJson.at("script");
|
// -------------------------------------
|
||||||
if (cameraScript.exists("type") && cameraScript.exists("name")) {
|
|
||||||
auto name = cameraScript.at("name").get<std::string>();
|
|
||||||
if (cameraScript.at("type").get<std::string>() == "lua") {
|
|
||||||
addComponent<LuaScriptComponent>(camera, name);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
addComponent<NativeScriptComponent>(camera, name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Quads
|
if (sceneJson.exists("entities")) {
|
||||||
|
const auto& entityJson = sceneJson.at("entities");
|
||||||
if (sceneJson.exists("quad") && sceneJson.at("quad").type() == ruc::Json::Type::Array) {
|
VERIFY(entityJson.type() == ruc::Json::Type::Array);
|
||||||
auto& quads = sceneJson.at("quad").asArray().elements();
|
const auto& entities = entityJson.asArray();
|
||||||
for (const auto& quad : quads) {
|
for (size_t i = 0; i < entities.size(); ++i) {
|
||||||
uint32_t quadEntity = loadEntity(quad);
|
loadEntity(entities.at(i));
|
||||||
addComponent<SpriteComponent>(quadEntity);
|
|
||||||
auto& spriteComponent = getComponent<SpriteComponent>(quadEntity);
|
|
||||||
quad.getTo(spriteComponent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Text
|
|
||||||
|
|
||||||
if (sceneJson.exists("text") && sceneJson.at("text").type() == ruc::Json::Type::Array) {
|
|
||||||
auto& texts = sceneJson.at("text").asArray().elements();
|
|
||||||
for (const auto& text : texts) {
|
|
||||||
uint32_t textEntity = loadEntity(text);
|
|
||||||
addComponent<TextAreaComponent>(textEntity);
|
|
||||||
auto& textAreaComponent = getComponent<TextAreaComponent>(textEntity);
|
|
||||||
text.getTo(textAreaComponent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,22 +94,85 @@ void Scene::destroy()
|
|||||||
TransformSystem::destroy();
|
TransformSystem::destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
uint32_t Scene::createEntity(const std::string& name)
|
uint32_t Scene::createEntity(const std::string& name)
|
||||||
|
{
|
||||||
|
return createEntityWithUID(UID(), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Scene::createEntityWithUID(UID id, const std::string& name)
|
||||||
{
|
{
|
||||||
uint32_t entity = static_cast<uint32_t>(m_registry->create());
|
uint32_t entity = static_cast<uint32_t>(m_registry->create());
|
||||||
|
addComponent<IDComponent>(entity, id);
|
||||||
addComponent<TagComponent>(entity, name.empty() ? "Unnamed Entity" : name);
|
addComponent<TagComponent>(entity, name.empty() ? "Unnamed Entity" : name);
|
||||||
addComponent<TransformComponent>(entity);
|
addComponent<TransformComponent>(entity);
|
||||||
|
|
||||||
|
TransformSystem::the().add(entity);
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Scene::loadEntity(ruc::Json json)
|
uint32_t Scene::loadEntity(ruc::Json components, uint32_t parentEntity)
|
||||||
{
|
{
|
||||||
uint32_t entity = createEntity((json.exists("name"))
|
VERIFY(components.type() == ruc::Json::Type::Object);
|
||||||
? json.at("name").get<std::string>()
|
|
||||||
: "");
|
uint32_t entity = createEntity();
|
||||||
|
|
||||||
|
// At minimum, ID is required
|
||||||
|
VERIFY(components.exists("id"), "id not found");
|
||||||
|
auto& id = getComponent<IDComponent>(entity);
|
||||||
|
components.at("id").getTo(id);
|
||||||
|
|
||||||
|
if (components.exists("tag")) {
|
||||||
|
auto& tag = getComponent<TagComponent>(entity);
|
||||||
|
components.at("tag").getTo(tag);
|
||||||
|
}
|
||||||
|
if (components.exists("transform")) {
|
||||||
auto& transform = getComponent<TransformComponent>(entity);
|
auto& transform = getComponent<TransformComponent>(entity);
|
||||||
json.getTo(transform);
|
components.at("transform").getTo(transform);
|
||||||
|
transform.parent = static_cast<entt::entity>(parentEntity);
|
||||||
|
}
|
||||||
|
if (components.exists("camera")) {
|
||||||
|
auto& camera = addComponent<CameraComponent>(entity);
|
||||||
|
components.at("camera").getTo(camera);
|
||||||
|
}
|
||||||
|
if (components.exists("lua-scripts")) {
|
||||||
|
VERIFY(components.at("lua-scripts").type() == ruc::Json::Type::Array);
|
||||||
|
const auto& scripts = components.at("lua-scripts").asArray();
|
||||||
|
for (size_t i = 0; i < scripts.size(); ++i) {
|
||||||
|
auto& script = addComponent<LuaScriptComponent>(entity);
|
||||||
|
scripts.at(i).getTo(script);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (components.exists("native-scripts")) {
|
||||||
|
VERIFY(components.at("native-scripts").type() == ruc::Json::Type::Array);
|
||||||
|
const auto& scripts = components.at("native-scripts").asArray();
|
||||||
|
for (size_t i = 0; i < scripts.size(); ++i) {
|
||||||
|
auto& script = addComponent<NativeScriptComponent>(entity);
|
||||||
|
scripts.at(i).getTo(script);
|
||||||
|
script.bind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (components.exists("sprite")) {
|
||||||
|
auto& sprite = addComponent<SpriteComponent>(entity);
|
||||||
|
components.at("sprite").getTo(sprite);
|
||||||
|
}
|
||||||
|
if (components.exists("cubemap")) {
|
||||||
|
auto& cubemap = addComponent<CubemapComponent>(entity);
|
||||||
|
components.at("cubemap").getTo(cubemap);
|
||||||
|
}
|
||||||
|
if (components.exists("text")) {
|
||||||
|
auto& text = addComponent<TextAreaComponent>(entity);
|
||||||
|
components.at("text").getTo(text);
|
||||||
|
}
|
||||||
|
if (components.exists("children")) {
|
||||||
|
VERIFY(components.at("children").type() == ruc::Json::Type::Array);
|
||||||
|
const auto& children = components.at("children").asArray();
|
||||||
|
for (size_t i = 0; i < children.size(); ++i) {
|
||||||
|
loadEntity(components.at("children")[i], entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
@@ -162,7 +196,9 @@ void Scene::destroyEntity(uint32_t entity)
|
|||||||
m_registry->destroy(entt::entity { entity });
|
m_registry->destroy(entt::entity { entity });
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::mat4 Scene::cameraProjectionView()
|
// -----------------------------------------
|
||||||
|
|
||||||
|
std::pair<glm::mat4, glm::mat4> Scene::cameraProjectionView()
|
||||||
{
|
{
|
||||||
return CameraSystem::the().projectionView();
|
return CameraSystem::the().projectionView();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022,2024 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint32_t
|
#include <cstdint> // uint32_t
|
||||||
#include <memory> // std::shared_ptr
|
#include <memory> // std::shared_ptr
|
||||||
|
#include <utility> // std::pair
|
||||||
|
|
||||||
#include "entt/entity/registry.hpp" // entt::entity, entt::registry
|
#include "entt/entity/registry.hpp" // entt::entity, entt::registry
|
||||||
#include "glm/ext/matrix_float4x4.hpp" // glm::mat4
|
#include "glm/ext/matrix_float4x4.hpp" // glm::mat4
|
||||||
#include "ruc/format/format.h"
|
#include "ruc/format/format.h"
|
||||||
#include "ruc/json/json.h"
|
#include "ruc/json/json.h"
|
||||||
|
|
||||||
|
#include "inferno/uid.h"
|
||||||
|
|
||||||
namespace Inferno {
|
namespace Inferno {
|
||||||
|
|
||||||
class Camera;
|
class Camera;
|
||||||
@@ -27,11 +31,15 @@ public:
|
|||||||
void destroy();
|
void destroy();
|
||||||
|
|
||||||
uint32_t createEntity(const std::string& name = "");
|
uint32_t createEntity(const std::string& name = "");
|
||||||
uint32_t loadEntity(ruc::Json json);
|
uint32_t createEntityWithUID(UID id, const std::string& name = "");
|
||||||
|
uint32_t loadEntity(ruc::Json components, uint32_t parentEntity = entt::null);
|
||||||
uint32_t findEntity(std::string_view name);
|
uint32_t findEntity(std::string_view name);
|
||||||
void destroyEntity(uint32_t entity);
|
void destroyEntity(uint32_t entity);
|
||||||
|
|
||||||
glm::mat4 cameraProjectionView();
|
/**
|
||||||
|
* @brief Return a pair from the camera component: { projection, view }
|
||||||
|
*/
|
||||||
|
std::pair<glm::mat4, glm::mat4> cameraProjectionView();
|
||||||
|
|
||||||
void validEntity(uint32_t entity) const;
|
void validEntity(uint32_t entity) const;
|
||||||
|
|
||||||
@@ -49,7 +57,8 @@ public:
|
|||||||
return m_registry->any<T...>(entt::entity { entity });
|
return m_registry->any<T...>(entt::entity { entity });
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Todo Should replace be allowed? could trigger memory leaks with nativescript
|
// TODO: Should replace be allowed? could trigger memory leaks with nativescript
|
||||||
|
// TODO: Replace will make it so an entity cant have multiple scripts
|
||||||
template<typename T, typename... P>
|
template<typename T, typename... P>
|
||||||
T& addComponent(uint32_t entity, P&&... parameters) const
|
T& addComponent(uint32_t entity, P&&... parameters) const
|
||||||
{
|
{
|
||||||
@@ -64,7 +73,7 @@ public:
|
|||||||
return m_registry->remove_if_exists<T>(entt::entity { entity });
|
return m_registry->remove_if_exists<T>(entt::entity { entity });
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Todo Should replace be allowed? could trigger memory leaks with nativescript
|
// TODO: Should replace be allowed? could trigger memory leaks with nativescript
|
||||||
template<typename T, typename... P>
|
template<typename T, typename... P>
|
||||||
T& getComponent(uint32_t entity, P&&... parameters) const
|
T& getComponent(uint32_t entity, P&&... parameters) const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ public:
|
|||||||
m_detroyBindings.emplace(binding, destroy);
|
m_detroyBindings.emplace(binding, destroy);
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeScript::InitializeFunction initializeBinding(const std::string& binding) { return m_initializeBindings[binding]; }
|
NativeScript::InitializeFunction initializeBinding(const std::string& name) { return m_initializeBindings[name]; }
|
||||||
NativeScript::DestroyFunction destroyBinding(const std::string& binding) { return m_detroyBindings[binding]; }
|
NativeScript::DestroyFunction destroyBinding(const std::string& name) { return m_detroyBindings[name]; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string, NativeScript::InitializeFunction> m_initializeBindings;
|
std::unordered_map<std::string, NativeScript::InitializeFunction> m_initializeBindings;
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022,2024 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <utility> // std::pair
|
||||||
|
|
||||||
#include "glm/ext/matrix_clip_space.hpp" // glm::perspective, glm::ortho
|
#include "glm/ext/matrix_clip_space.hpp" // glm::perspective, glm::ortho
|
||||||
#include "glm/ext/matrix_transform.hpp" // glm::radians, glm::lookAt
|
#include "glm/ext/matrix_transform.hpp" // glm::radians, glm::lookAt
|
||||||
#include "ruc/format/log.h"
|
#include "ruc/format/log.h"
|
||||||
@@ -42,17 +44,17 @@ void CameraSystem::update()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::mat4 CameraSystem::projectionView()
|
std::pair<glm::mat4, glm::mat4> CameraSystem::projectionView()
|
||||||
{
|
{
|
||||||
auto view = m_registry->view<TransformComponent, CameraComponent>();
|
auto view = m_registry->view<TransformComponent, CameraComponent>();
|
||||||
|
|
||||||
for (auto [entity, transform, camera] : view.each()) {
|
for (auto [entity, transform, camera] : view.each()) {
|
||||||
return camera.projection * transform.transform;
|
return { camera.projection, transform.transform };
|
||||||
}
|
}
|
||||||
|
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
|
|
||||||
return glm::mat4 { 1.0f };
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void CameraSystem::updateOrthographic(TransformComponent& transform, CameraComponent& camera)
|
void CameraSystem::updateOrthographic(TransformComponent& transform, CameraComponent& camera)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022,2024 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory> //std::shared_ptr
|
#include <memory> //std::shared_ptr
|
||||||
|
#include <utility> // std::pair
|
||||||
|
|
||||||
#include "entt/entity/registry.hpp" // entt::entity, entt::registry
|
#include "entt/entity/registry.hpp" // entt::entity, entt::registry
|
||||||
#include "glm/ext/matrix_float4x4.hpp" // glm::mat4
|
#include "glm/ext/matrix_float4x4.hpp" // glm::mat4
|
||||||
@@ -27,7 +28,10 @@ public:
|
|||||||
|
|
||||||
void update();
|
void update();
|
||||||
|
|
||||||
glm::mat4 projectionView();
|
/**
|
||||||
|
* @brief Return a pair from the camera component: { projection, view }
|
||||||
|
*/
|
||||||
|
std::pair<glm::mat4, glm::mat4> projectionView();
|
||||||
|
|
||||||
void setRegistry(std::shared_ptr<entt::registry> registry) { m_registry = registry; };
|
void setRegistry(std::shared_ptr<entt::registry> registry) { m_registry = registry; };
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022,2024 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "glm/ext/matrix_transform.hpp" // glm::translate, glm::rotate, glm::scale, glm::radians
|
#include "glm/ext/matrix_transform.hpp" // glm::translate, glm::rotate, glm::scale, glm::radians
|
||||||
#include "ruc/format/log.h"
|
#include "ruc/format/log.h"
|
||||||
|
|
||||||
|
#include "inferno/component/cubemap-component.h"
|
||||||
#include "inferno/component/spritecomponent.h"
|
#include "inferno/component/spritecomponent.h"
|
||||||
#include "inferno/component/transformcomponent.h"
|
#include "inferno/component/transformcomponent.h"
|
||||||
#include "inferno/render/renderer.h"
|
#include "inferno/render/renderer.h"
|
||||||
@@ -25,11 +26,17 @@ RenderSystem::~RenderSystem()
|
|||||||
|
|
||||||
void RenderSystem::render()
|
void RenderSystem::render()
|
||||||
{
|
{
|
||||||
auto group = m_registry->group<TransformComponent, SpriteComponent>();
|
auto quadView = m_registry->view<TransformComponent, SpriteComponent>();
|
||||||
|
|
||||||
for (auto [entity, transform, sprite] : group.each()) {
|
for (auto [entity, transform, sprite] : quadView.each()) {
|
||||||
Renderer2D::the().drawQuad(transform, sprite.color, sprite.texture);
|
Renderer2D::the().drawQuad(transform, sprite.color, sprite.texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto cubemapView = m_registry->view<TransformComponent, CubemapComponent>();
|
||||||
|
|
||||||
|
for (auto [entity, transform, cubemap] : cubemapView.each()) {
|
||||||
|
RendererCubemap::the().drawCubemap(transform, cubemap.color, cubemap.texture);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Inferno
|
} // namespace Inferno
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022,2024 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <algorithm> // std::sort
|
||||||
|
#include <cstdint> // uint32_t
|
||||||
|
|
||||||
|
#include "entt/entity/fwd.hpp" // entt:entity
|
||||||
#include "glm/ext/matrix_transform.hpp" // glm::translate, glm::rotate, glm::scale, glm::radians
|
#include "glm/ext/matrix_transform.hpp" // glm::translate, glm::rotate, glm::scale, glm::radians
|
||||||
#include "ruc/format/log.h"
|
#include "ruc/format/log.h"
|
||||||
|
|
||||||
@@ -25,7 +29,7 @@ void TransformSystem::update()
|
|||||||
{
|
{
|
||||||
auto view = m_registry->view<TransformComponent>();
|
auto view = m_registry->view<TransformComponent>();
|
||||||
|
|
||||||
for (auto entity : view) {
|
for (auto entity : m_hierarchy) {
|
||||||
|
|
||||||
auto& component = view.get<TransformComponent>(entity);
|
auto& component = view.get<TransformComponent>(entity);
|
||||||
|
|
||||||
@@ -42,7 +46,29 @@ void TransformSystem::update()
|
|||||||
|
|
||||||
// Scale
|
// Scale
|
||||||
component.transform = glm::scale(component.transform, component.scale);
|
component.transform = glm::scale(component.transform, component.scale);
|
||||||
|
|
||||||
|
// Apply the parent transform to the child transform
|
||||||
|
if (component.parent != entt::null) {
|
||||||
|
auto& parent = view.get<TransformComponent>(component.parent);
|
||||||
|
component.transform = parent.transform * component.transform;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TransformSystem::add(uint32_t entity)
|
||||||
|
{
|
||||||
|
m_hierarchy.push_back(static_cast<entt::entity>(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransformSystem::sort()
|
||||||
|
{
|
||||||
|
std::sort(m_hierarchy.begin(), m_hierarchy.end(),
|
||||||
|
[](entt::entity a, entt::entity b) {
|
||||||
|
return (a == entt::null && b == entt::null) ? false
|
||||||
|
: (a == entt::null) ? true
|
||||||
|
: (b == entt::null) ? false
|
||||||
|
: a < b;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Inferno
|
} // namespace Inferno
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022,2024 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint> // uint32_t
|
||||||
#include <memory> // std::shared_ptr
|
#include <memory> // std::shared_ptr
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "entt/entity/registry.hpp" // entt::entity, entt::registry
|
#include "entt/entity/registry.hpp" // entt::entity, entt::registry
|
||||||
#include "ruc/singleton.h"
|
#include "ruc/singleton.h"
|
||||||
@@ -20,9 +22,14 @@ public:
|
|||||||
|
|
||||||
void update();
|
void update();
|
||||||
|
|
||||||
|
void add(uint32_t entity);
|
||||||
|
void sort();
|
||||||
|
|
||||||
void setRegistry(std::shared_ptr<entt::registry> registry) { m_registry = registry; };
|
void setRegistry(std::shared_ptr<entt::registry> registry) { m_registry = registry; };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::vector<entt::entity> m_hierarchy;
|
||||||
|
|
||||||
std::shared_ptr<entt::registry> m_registry;
|
std::shared_ptr<entt::registry> m_registry;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Riyyi
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#include "ruc/format/log.h"
|
||||||
|
#include "ruc/json/json.h"
|
||||||
|
|
||||||
|
#include "inferno/uid.h"
|
||||||
|
|
||||||
|
namespace Inferno {
|
||||||
|
|
||||||
|
static std::random_device s_seed;
|
||||||
|
static std::mt19937_64 s_engine(s_seed());
|
||||||
|
static std::uniform_int_distribution<uint64_t> s_distribution;
|
||||||
|
|
||||||
|
UID::UID()
|
||||||
|
: m_uid(s_distribution(s_engine))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
UID::UID(uint64_t uid)
|
||||||
|
: m_uid(uid)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void fromJson(const ruc::Json& json, UID& value)
|
||||||
|
{
|
||||||
|
VERIFY(json.type() == ruc::Json::Type::Number);
|
||||||
|
|
||||||
|
value = UID((int64_t)json.asDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Inferno
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Riyyi
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint> // uint64_t
|
||||||
|
|
||||||
|
#include "ruc/json/json.h"
|
||||||
|
|
||||||
|
namespace Inferno {
|
||||||
|
|
||||||
|
class UID {
|
||||||
|
public:
|
||||||
|
UID();
|
||||||
|
UID(uint64_t uid);
|
||||||
|
|
||||||
|
// Comparison operator for std::map
|
||||||
|
bool operator<(const UID& other) const
|
||||||
|
{
|
||||||
|
return m_uid < other.m_uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator uint64_t() const { return m_uid; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint64_t m_uid;
|
||||||
|
};
|
||||||
|
|
||||||
|
void fromJson(const ruc::Json& json, UID& value);
|
||||||
|
|
||||||
|
} // namespace Inferno
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
|
||||||
|
// Hash function for std::unordered_map
|
||||||
|
template<>
|
||||||
|
struct hash<Inferno::UID> {
|
||||||
|
size_t operator()(const Inferno::UID& uid) const
|
||||||
|
{
|
||||||
|
return hash<uint64_t>()(static_cast<uint64_t>(uid));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace std
|
||||||
Reference in New Issue
Block a user