Compare commits
27 Commits
dronuilleo
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8158a61f10 | ||
|
|
87d310b55c | ||
|
|
b9bd185968 | ||
|
|
8a806b8c65 | ||
|
|
05927da1d4 | ||
|
|
4433d34e32 | ||
|
|
0bba09a055 | ||
|
|
762c994e5c | ||
|
|
55c15cef4e | ||
|
|
dc24036efb | ||
|
|
5ffdc17d79 | ||
|
|
e85882df04 | ||
|
|
a9e53cc4cb | ||
|
|
479722185f | ||
|
|
bc865c969d | ||
|
|
dc8e5a5483 | ||
|
|
b2de337c43 | ||
|
|
7f189b2ed6 | ||
|
|
9d9d0044ee | ||
|
|
f95c48bac6 | ||
|
|
346038a8f7 | ||
|
|
ff771ba127 | ||
|
|
f2e78fbf76 | ||
|
|
87fbb1c603 | ||
|
|
dfd39257c0 | ||
|
|
c9a6f11a7a | ||
|
|
1ce25b26bd |
3
.gitignore
vendored
3
.gitignore
vendored
@ -15,6 +15,9 @@
|
|||||||
*.userprefs
|
*.userprefs
|
||||||
|
|
||||||
|
|
||||||
|
# compiled shaders
|
||||||
|
assets/shaders/*.spv
|
||||||
|
|
||||||
# Mono auto generated files
|
# Mono auto generated files
|
||||||
mono_crash.*
|
mono_crash.*
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 20)
|
|||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(CMAKE_CXX_FLAGS "-D_DEBUG")
|
set(CMAKE_CXX_FLAGS "")
|
||||||
else()
|
else()
|
||||||
set(CMAKE_CXX_FLAGS "-D_DEBUG -Wall")
|
set(CMAKE_CXX_FLAGS "-D_DEBUG -Wall")
|
||||||
endif()
|
endif()
|
||||||
@ -25,20 +25,25 @@ add_executable (pleascach ${SOURCES})
|
|||||||
|
|
||||||
# shader compilation
|
# shader compilation
|
||||||
file(GLOB SHADER_SOURCE_FILES
|
file(GLOB SHADER_SOURCE_FILES
|
||||||
assets/shaders/*.frag
|
assets/shaders/**/*.frag
|
||||||
assets/shaders/*.vert
|
assets/shaders/**/*.vert
|
||||||
assets/shaders/*.geom
|
assets/shaders/**/*.geom
|
||||||
assets/shaders/*.tese
|
assets/shaders/**/*.tese
|
||||||
assets/shaders/*.tesc
|
assets/shaders/**/*.tesc
|
||||||
|
assets/shaders/*/*/*.frag
|
||||||
|
assets/shaders/*/*/*.vert
|
||||||
|
assets/shaders/*/*/*.geom
|
||||||
|
assets/shaders/*/*/*.tese
|
||||||
|
assets/shaders/*/*/*.tesc
|
||||||
)
|
)
|
||||||
|
|
||||||
foreach(SHADER_SOURCE ${SHADER_SOURCE_FILES})
|
foreach(SHADER_SOURCE ${SHADER_SOURCE_FILES})
|
||||||
message("Processing shader file")
|
message("Processing shader file")
|
||||||
get_filename_component(FILE_NAME ${SHADER_SOURCE} NAME)
|
get_filename_component(FILE_NAME ${SHADER_SOURCE} NAME)
|
||||||
set(SPIRV "${CMAKE_SOURCE_DIR}/assets/shaders/${FILE_NAME}.spv")
|
set(SPIRV "${CMAKE_SOURCE_DIR}/assets/shaders/bin/${FILE_NAME}.spv")
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT ${SPIRV}
|
OUTPUT ${SPIRV}
|
||||||
COMMAND ${Vulkan_GLSLC_EXECUTABLE} -o ${SPIRV} ${SHADER_SOURCE}
|
COMMAND glslc -o ${SPIRV} ${SHADER_SOURCE}
|
||||||
DEPENDS ${SHADER_SOURCE}
|
DEPENDS ${SHADER_SOURCE}
|
||||||
)
|
)
|
||||||
list(APPEND SPIRV_BIN_FILES ${SPIRV})
|
list(APPEND SPIRV_BIN_FILES ${SPIRV})
|
||||||
|
|||||||
@ -74,16 +74,18 @@ void Input::setCursor(bool enabled) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Input::handleMovementKeys(Renderer& ren) {
|
void Input::handleMovementKeys(Renderer& ren) {
|
||||||
if (ImGui::GetIO().WantCaptureKeyboard)
|
if (ImGui::GetIO().WantCaptureKeyboard && ren.in_menu)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
glm::vec3 forward;
|
glm::vec3 forward;
|
||||||
if (ren.flycam)
|
if (ren.flycam)
|
||||||
forward = glm::vec3(glm::sin(ren.cam.theta)*glm::cos(ren.cam.phi), glm::cos(ren.cam.theta), glm::sin(ren.cam.theta)*glm::sin(ren.cam.phi));
|
forward = glm::normalize(glm::vec3(glm::sin(ren.cam.theta)*glm::cos(ren.cam.phi), glm::cos(ren.cam.theta), glm::sin(ren.cam.theta)*glm::sin(ren.cam.phi)));
|
||||||
else
|
else {
|
||||||
forward = glm::vec3(glm::cos(ren.cam.phi), 0.0, glm::sin(ren.cam.phi));
|
forward = glm::normalize(glm::vec3(glm::cos(ren.cam.phi), 0.0, glm::sin(ren.cam.phi)));
|
||||||
const auto right = glm::cross(forward, glm::vec3(0.0, 1.0, 0.0));
|
}
|
||||||
const auto speed = glfwGetKey(in, GLFW_KEY_LEFT_SHIFT)? 2.0f : 1.0f;
|
const auto right = glm::normalize(glm::cross(forward, glm::vec3(0.0, 1.0, 0.0)));
|
||||||
|
auto speed = glfwGetKey(in, GLFW_KEY_LEFT_SHIFT)? 2.0f : 1.0f;
|
||||||
|
speed *= ren.speed * ren.frametime / 8.0;
|
||||||
|
|
||||||
if(glfwGetKey(in, GLFW_KEY_UP)) {
|
if(glfwGetKey(in, GLFW_KEY_UP)) {
|
||||||
ren.cam.theta -= 0.01;
|
ren.cam.theta -= 0.01;
|
||||||
@ -105,28 +107,28 @@ void Input::handleMovementKeys(Renderer& ren) {
|
|||||||
}
|
}
|
||||||
/* move "forward" or "backward" */
|
/* move "forward" or "backward" */
|
||||||
if (glfwGetKey(in, GLFW_KEY_W)) {
|
if (glfwGetKey(in, GLFW_KEY_W)) {
|
||||||
ren.cam.pos += forward * 0.1f * speed;
|
ren.cam.pos += forward * 1.0f * speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (glfwGetKey(in, GLFW_KEY_S)) {
|
if (glfwGetKey(in, GLFW_KEY_S)) {
|
||||||
ren.cam.pos += forward * -0.1f * speed;
|
ren.cam.pos += forward * -1.0f * speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* move "left" or "right" */
|
/* move "left" or "right" */
|
||||||
if (glfwGetKey(in, GLFW_KEY_A)) {
|
if (glfwGetKey(in, GLFW_KEY_A)) {
|
||||||
ren.cam.pos -= right * 0.1f * speed;
|
ren.cam.pos -= right * 1.0f * speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (glfwGetKey(in, GLFW_KEY_D)) {
|
if (glfwGetKey(in, GLFW_KEY_D)) {
|
||||||
ren.cam.pos += right * 0.1f * speed;
|
ren.cam.pos += right * 1.0f * speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(glfwGetKey(in, GLFW_KEY_SPACE)) {
|
if(glfwGetKey(in, GLFW_KEY_SPACE)) {
|
||||||
ren.cam.pos.y += 0.1;
|
ren.cam.pos.y += 1.0 * speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(glfwGetKey(in, GLFW_KEY_LEFT_CONTROL)) {
|
if(glfwGetKey(in, GLFW_KEY_LEFT_CONTROL)) {
|
||||||
ren.cam.pos.y -= 0.1;
|
ren.cam.pos.y -= 1.0 * speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
ren.cam.theta = glm::clamp(ren.cam.theta, 0.01f, glm::pi<float>() - 0.01f);
|
ren.cam.theta = glm::clamp(ren.cam.theta, 0.01f, glm::pi<float>() - 0.01f);
|
||||||
@ -139,10 +141,10 @@ void Input::handleCursorMovement(Renderer& ren, double x, double y) {
|
|||||||
int rel_mouse_y = static_cast<int>(y) - last_mouse.y;
|
int rel_mouse_y = static_cast<int>(y) - last_mouse.y;
|
||||||
|
|
||||||
auto& io = ImGui::GetIO();
|
auto& io = ImGui::GetIO();
|
||||||
if (io.WantCaptureMouse)
|
if (io.WantCaptureMouse && ren.in_menu)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!ren.capture_mouse) {
|
if (ren.in_menu) {
|
||||||
io.AddMousePosEvent(x, y);
|
io.AddMousePosEvent(x, y);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,26 +58,26 @@ void Model::initVertices(Node* node, const tinygltf::Primitive& prim) {
|
|||||||
|
|
||||||
auto loc = prim.attributes.find("POSITION");
|
auto loc = prim.attributes.find("POSITION");
|
||||||
if(loc != prim.attributes.end()) {
|
if(loc != prim.attributes.end()) {
|
||||||
auto accessor = model->accessors[loc->second];
|
auto& accessor = model->accessors[loc->second];
|
||||||
const auto& view = model->bufferViews[accessor.bufferView];
|
const auto& view = model->bufferViews[accessor.bufferView];
|
||||||
pos_buff = reinterpret_cast<const float*>(&model->buffers[view.buffer].data[accessor.byteOffset+view.byteOffset]);
|
pos_buff = reinterpret_cast<const float*>(&model->buffers[view.buffer].data[accessor.byteOffset+view.byteOffset]);
|
||||||
vertex_count = accessor.count;
|
vertex_count = accessor.count;
|
||||||
}
|
}
|
||||||
loc = prim.attributes.find("NORMAL");
|
loc = prim.attributes.find("NORMAL");
|
||||||
if(loc != prim.attributes.end()) {
|
if(loc != prim.attributes.end()) {
|
||||||
auto accessor = model->accessors[loc->second];
|
auto& accessor = model->accessors[loc->second];
|
||||||
const auto& view = model->bufferViews[accessor.bufferView];
|
const auto& view = model->bufferViews[accessor.bufferView];
|
||||||
norm_buff = reinterpret_cast<const float*>(&model->buffers[view.buffer].data[accessor.byteOffset+view.byteOffset]);
|
norm_buff = reinterpret_cast<const float*>(&model->buffers[view.buffer].data[accessor.byteOffset+view.byteOffset]);
|
||||||
}
|
}
|
||||||
loc = prim.attributes.find("TEXCOORD_0");
|
loc = prim.attributes.find("TEXCOORD_0");
|
||||||
if(loc != prim.attributes.end()) {
|
if(loc != prim.attributes.end()) {
|
||||||
auto accessor = model->accessors[loc->second];
|
auto& accessor = model->accessors[loc->second];
|
||||||
const auto& view = model->bufferViews[accessor.bufferView];
|
const auto& view = model->bufferViews[accessor.bufferView];
|
||||||
uv_buff = reinterpret_cast<const float*>(&model->buffers[view.buffer].data[accessor.byteOffset+view.byteOffset]);
|
uv_buff = reinterpret_cast<const float*>(&model->buffers[view.buffer].data[accessor.byteOffset+view.byteOffset]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(size_t i = 0; i < vertex_count; i++) {
|
for(size_t i = 0; i < vertex_count; i++) {
|
||||||
vertices.push_back(Vertex {
|
vertices.push_back(glTFVertex {
|
||||||
.pos = pos_buff? glm::make_vec3(pos_buff+i*3) : glm::vec3(0.0),
|
.pos = pos_buff? glm::make_vec3(pos_buff+i*3) : glm::vec3(0.0),
|
||||||
.norm = norm_buff? glm::normalize(glm::make_vec3(norm_buff+i*3)) : glm::vec3(0.0),
|
.norm = norm_buff? glm::normalize(glm::make_vec3(norm_buff+i*3)) : glm::vec3(0.0),
|
||||||
.uv = uv_buff? glm::make_vec2(uv_buff+i*2) : glm::vec2(0.0),
|
.uv = uv_buff? glm::make_vec2(uv_buff+i*2) : glm::vec2(0.0),
|
||||||
|
|||||||
@ -36,7 +36,7 @@ struct Model {
|
|||||||
|
|
||||||
std::unique_ptr<VertexBuffer> vertex_buffer;
|
std::unique_ptr<VertexBuffer> vertex_buffer;
|
||||||
std::unique_ptr<Buffer> index_buffer;
|
std::unique_ptr<Buffer> index_buffer;
|
||||||
std::vector<Vertex> vertices;
|
std::vector<glTFVertex> vertices;
|
||||||
std::vector<uint16_t> indices;
|
std::vector<uint16_t> indices;
|
||||||
|
|
||||||
/* recusively initialize nodes with an accumulative vertex and index buffer collector */
|
/* recusively initialize nodes with an accumulative vertex and index buffer collector */
|
||||||
|
|||||||
19
README.md
19
README.md
@ -1,5 +1,24 @@
|
|||||||
# Pléascach
|
# Pléascach
|
||||||
|
|
||||||
|
###### Check out the raymarching branch (pléascach-dronuilleog)!
|
||||||
|
|
||||||
|
Small Vulkan 3D renderer.
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
#### List of 3rd party code included in this repository:
|
||||||
|
- Imgui (and Imgui-console): for debugging UI
|
||||||
|
- STB: for image loading
|
||||||
|
- GLM: for math
|
||||||
|
- GLFW: for cross-platform windowing
|
||||||
|
- TinyglTF: for glTF parsing
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- glTF Model loading
|
||||||
|
- QuakeIII BSP Map Loading
|
||||||
|
- Tessellation-controlled heightmap terrains with dynamic normal calculation
|
||||||
|
- Working lighting!
|
||||||
|
|
||||||
## Short Term Changes
|
## Short Term Changes
|
||||||
- Make index buffer device-local instead of host-coherent
|
- Make index buffer device-local instead of host-coherent
|
||||||
- Possibly restructure Buffer class to use templates to change
|
- Possibly restructure Buffer class to use templates to change
|
||||||
|
|||||||
@ -65,18 +65,13 @@ void CommandBuffer::copy(Buffer& src, Image& dst, vk::ImageLayout layout) {
|
|||||||
|
|
||||||
void CommandBuffer::bind(const GraphicsPipeline& pipeline) {
|
void CommandBuffer::bind(const GraphicsPipeline& pipeline) {
|
||||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.pipeline);
|
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.pipeline);
|
||||||
|
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline.layout, 0, pipeline.desc_set, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandBuffer::bind(vk::PipelineLayout layout, vk::ArrayProxy<vk::DescriptorSet> desc_sets) {
|
void CommandBuffer::bind(vk::PipelineLayout layout, vk::ArrayProxy<vk::DescriptorSet> desc_sets) {
|
||||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, desc_sets, nullptr);
|
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, desc_sets, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandBuffer::bind(const VertexBuffer& vertex_buffer, uint32_t binding) {
|
|
||||||
const std::array<vk::DeviceSize, 1> offsets = {0};
|
|
||||||
|
|
||||||
command_buffer.bindVertexBuffers(binding, vertex_buffer.buffer->buffer, offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CommandBuffer::bind(std::shared_ptr<Model> model) {
|
void CommandBuffer::bind(std::shared_ptr<Model> model) {
|
||||||
bind(*model->vertex_buffer);
|
bind(*model->vertex_buffer);
|
||||||
command_buffer.bindIndexBuffer(*model->index_buffer, 0, vk::IndexType::eUint16);
|
command_buffer.bindIndexBuffer(*model->index_buffer, 0, vk::IndexType::eUint16);
|
||||||
@ -87,6 +82,13 @@ void CommandBuffer::bind(Terrain* terrain) {
|
|||||||
command_buffer.bindIndexBuffer(*terrain->index_buffer, 0, vk::IndexType::eUint32);
|
command_buffer.bindIndexBuffer(*terrain->index_buffer, 0, vk::IndexType::eUint32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CommandBuffer::bind(HLBSP::BSP* bsp) {
|
||||||
|
bind(*bsp->pipeline);
|
||||||
|
bind(*bsp->vertex_buffer);
|
||||||
|
bind(bsp->pipeline->layout, bsp->pipeline->desc_set);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void CommandBuffer::draw(uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex, uint32_t first_instance) {
|
void CommandBuffer::draw(uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex, uint32_t first_instance) {
|
||||||
command_buffer.draw(vertex_count, instance_count, first_vertex, first_instance);
|
command_buffer.draw(vertex_count, instance_count, first_vertex, first_instance);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,11 +7,14 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include <Renderer/VertexBuffer.hpp>
|
||||||
|
|
||||||
|
#include <Scene/BSP.hpp>
|
||||||
|
|
||||||
struct Buffer;
|
struct Buffer;
|
||||||
struct Image;
|
struct Image;
|
||||||
struct GraphicsPipeline;
|
struct GraphicsPipeline;
|
||||||
struct ComputePipeline;
|
struct ComputePipeline;
|
||||||
struct VertexBuffer;
|
|
||||||
struct Model;
|
struct Model;
|
||||||
struct Terrain;
|
struct Terrain;
|
||||||
|
|
||||||
@ -30,9 +33,17 @@ struct CommandBuffer {
|
|||||||
|
|
||||||
void bind(const GraphicsPipeline& pipeline);
|
void bind(const GraphicsPipeline& pipeline);
|
||||||
void bind(vk::PipelineLayout layout, vk::ArrayProxy<vk::DescriptorSet> desc_sets);
|
void bind(vk::PipelineLayout layout, vk::ArrayProxy<vk::DescriptorSet> desc_sets);
|
||||||
void bind(const VertexBuffer& vertex_buffer, uint32_t binding = 0);
|
template <typename Vertex>
|
||||||
|
|
||||||
|
void bind(const GeneralVertexBuffer<Vertex>& vertex_buffer, uint32_t binding = 0) {
|
||||||
|
const std::array<vk::DeviceSize, 1> offsets = { 0 };
|
||||||
|
|
||||||
|
command_buffer.bindVertexBuffers(binding, vertex_buffer.buffer->buffer, offsets);
|
||||||
|
}
|
||||||
|
|
||||||
void bind(std::shared_ptr<Model> model);
|
void bind(std::shared_ptr<Model> model);
|
||||||
void bind(Terrain* terrain);
|
void bind(Terrain* terrain);
|
||||||
|
void bind(HLBSP::BSP* bsp);
|
||||||
|
|
||||||
void draw(uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex = 0, uint32_t first_instance = 0);
|
void draw(uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex = 0, uint32_t first_instance = 0);
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,9 @@
|
|||||||
|
|
||||||
#include <util/log.hpp>
|
#include <util/log.hpp>
|
||||||
|
|
||||||
GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& shaders, const vk::Extent2D& extent, const RenderPass& render_pass, vk::ArrayProxy<vk::DescriptorSetLayoutBinding> bindings, const VertexBuffer& vertex_buffer, enum Type type) : dev(dev) {
|
|
||||||
|
GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& shaders, const vk::Extent2D& extent, const RenderPass& render_pass, vk::ArrayProxy<vk::DescriptorSetLayoutBinding> bindings, const vk::VertexInputBindingDescription& vertex_binding, const std::vector<vk::VertexInputAttributeDescription>& vertex_attrs, enum Type type, bool wireframe, bool culling)
|
||||||
|
: dev(dev), shaders(shaders), extent(extent), render_pass(render_pass), bindings(bindings), vertex_binding(vertex_binding), vertex_attrs(vertex_attrs), type(type) {
|
||||||
/* create layout
|
/* create layout
|
||||||
* Eventually add a graphicspipline constructor that allows specification of layouts etc
|
* Eventually add a graphicspipline constructor that allows specification of layouts etc
|
||||||
* kinda like how Image::Image has all those versions
|
* kinda like how Image::Image has all those versions
|
||||||
@ -55,34 +57,44 @@ GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& sh
|
|||||||
})[0];
|
})[0];
|
||||||
|
|
||||||
/* shader setup */
|
/* shader setup */
|
||||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_info;
|
|
||||||
shader_info.reserve(shaders.size());
|
shader_info.reserve(shaders.size());
|
||||||
for (const auto& shader : shaders)
|
for (const auto& shader : shaders)
|
||||||
shader_info.push_back(shader.info());
|
shader_info.push_back(shader.info());
|
||||||
|
|
||||||
/* vertex input setup */
|
/* vertex input setup */
|
||||||
const std::vector<vk::VertexInputBindingDescription> vertex_bindings = {
|
vertex_bindings = {
|
||||||
vertex_buffer.binding(0),
|
vertex_binding,
|
||||||
};
|
};
|
||||||
|
|
||||||
auto attrs = vertex_buffer.attrs(0);
|
vertex_input_info = vk::PipelineVertexInputStateCreateInfo {
|
||||||
|
|
||||||
const auto vertex_input_info = vk::PipelineVertexInputStateCreateInfo{
|
|
||||||
.vertexBindingDescriptionCount = static_cast<uint32_t>(vertex_bindings.size()),
|
.vertexBindingDescriptionCount = static_cast<uint32_t>(vertex_bindings.size()),
|
||||||
.pVertexBindingDescriptions = vertex_bindings.data(),
|
.pVertexBindingDescriptions = vertex_bindings.data(),
|
||||||
.vertexAttributeDescriptionCount = static_cast<uint32_t>(attrs.size()),
|
.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_attrs.size()),
|
||||||
.pVertexAttributeDescriptions = attrs.data(),
|
.pVertexAttributeDescriptions = vertex_attrs.data(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto input_asm_info = vk::PipelineInputAssemblyStateCreateInfo{
|
input_asm_info = vk::PipelineInputAssemblyStateCreateInfo {
|
||||||
.topology = type == Type::eGLTF ? vk::PrimitiveTopology::eTriangleList : vk::PrimitiveTopology::ePatchList,
|
|
||||||
/* matters later if we use strip primitives */
|
/* matters later if we use strip primitives */
|
||||||
.primitiveRestartEnable = vk::False,
|
.primitiveRestartEnable = vk::False,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case eVERTEX:
|
||||||
|
case eGLTF:
|
||||||
|
case eBSP:
|
||||||
|
input_asm_info.topology = vk::PrimitiveTopology::eTriangleList;
|
||||||
|
break;
|
||||||
|
case eTERRAIN:
|
||||||
|
input_asm_info.topology = vk::PrimitiveTopology::ePatchList;
|
||||||
|
break;
|
||||||
|
case eBOX:
|
||||||
|
input_asm_info.topology = vk::PrimitiveTopology::ePointList;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
const vk::PipelineTessellationStateCreateInfo* ptesselation_info = nullptr;
|
const vk::PipelineTessellationStateCreateInfo* ptesselation_info = nullptr;
|
||||||
|
|
||||||
const auto tess_info = vk::PipelineTessellationStateCreateInfo {
|
tess_info = vk::PipelineTessellationStateCreateInfo {
|
||||||
/* quads*/
|
/* quads*/
|
||||||
.patchControlPoints = 4,
|
.patchControlPoints = 4,
|
||||||
};
|
};
|
||||||
@ -91,38 +103,47 @@ GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& sh
|
|||||||
ptesselation_info = &tess_info;
|
ptesselation_info = &tess_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto raster_info = vk::PipelineRasterizationStateCreateInfo {
|
raster_info = vk::PipelineRasterizationStateCreateInfo {
|
||||||
.depthClampEnable = vk::False,
|
.depthClampEnable = vk::False,
|
||||||
.polygonMode = type == Type::eGLTF? vk::PolygonMode::eFill : vk::PolygonMode::eFill,
|
.polygonMode = (type == eBOX || wireframe) ? vk::PolygonMode::eLine : vk::PolygonMode::eFill,
|
||||||
.cullMode = vk::CullModeFlagBits::eNone,
|
.cullMode = (type == eBOX || !culling) ? vk::CullModeFlagBits::eNone : vk::CullModeFlagBits::eBack,
|
||||||
.frontFace = Type::eGLTF ? vk::FrontFace::eClockwise : vk::FrontFace::eCounterClockwise,
|
.frontFace = vk::FrontFace::eCounterClockwise,
|
||||||
.depthBiasEnable = vk::False,
|
.depthBiasEnable = type == eBOX,
|
||||||
|
.depthBiasConstantFactor = 0.01,
|
||||||
.lineWidth = 1.0,
|
.lineWidth = 1.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto multisample_info = vk::PipelineMultisampleStateCreateInfo {
|
multisample_info = vk::PipelineMultisampleStateCreateInfo {
|
||||||
.rasterizationSamples = vk::SampleCountFlagBits::e1,
|
.rasterizationSamples = vk::SampleCountFlagBits::e1,
|
||||||
.sampleShadingEnable = vk::False,
|
.sampleShadingEnable = vk::False,
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto depth_stencil_info = vk::PipelineDepthStencilStateCreateInfo{
|
depth_stencil_info = vk::PipelineDepthStencilStateCreateInfo{
|
||||||
.depthTestEnable = vk::True,
|
.depthTestEnable = vk::True,
|
||||||
.depthWriteEnable = vk::True,
|
.depthWriteEnable = type != eBOX,
|
||||||
.depthCompareOp = vk::CompareOp::eLessOrEqual,
|
.depthCompareOp = vk::CompareOp::eLess,
|
||||||
.depthBoundsTestEnable = vk::False,
|
.depthBoundsTestEnable = vk::False,
|
||||||
.stencilTestEnable = vk::False,
|
.stencilTestEnable = vk::False,
|
||||||
.minDepthBounds = 0.0,
|
.minDepthBounds = 0.0,
|
||||||
.maxDepthBounds = 1.0,
|
.maxDepthBounds = 1.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto color_blend_attachment = vk::PipelineColorBlendAttachmentState{
|
color_blend_attachment = vk::PipelineColorBlendAttachmentState {
|
||||||
.blendEnable = vk::False,
|
/* only the box has blending */
|
||||||
|
.blendEnable = type == eBOX,
|
||||||
|
.srcColorBlendFactor = vk::BlendFactor::eSrcAlpha,
|
||||||
|
.dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha,
|
||||||
|
.colorBlendOp = vk::BlendOp::eMax,
|
||||||
|
.srcAlphaBlendFactor = vk::BlendFactor::eOne,
|
||||||
|
.dstAlphaBlendFactor = vk::BlendFactor::eZero,
|
||||||
|
.alphaBlendOp = vk::BlendOp::eAdd,
|
||||||
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eB
|
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eB
|
||||||
| vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eA,
|
| vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eA,
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::array<float, 4> blend_constants = {0.0f, 0.0f, 0.0f, 0.0f};
|
blend_constants = {0.0f, 0.0f, 0.0f, 0.0f };
|
||||||
const auto color_blend_info = vk::PipelineColorBlendStateCreateInfo{
|
color_blend_info = vk::PipelineColorBlendStateCreateInfo{
|
||||||
.logicOpEnable = vk::False,
|
.logicOpEnable = vk::False,
|
||||||
.logicOp = vk::LogicOp::eCopy,
|
.logicOp = vk::LogicOp::eCopy,
|
||||||
.attachmentCount = 1,
|
.attachmentCount = 1,
|
||||||
@ -131,36 +152,34 @@ GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& sh
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* temporary viewport and scissor, since it is a dynamic state due to the existence of window resizing */
|
/* temporary viewport and scissor, since it is a dynamic state due to the existence of window resizing */
|
||||||
const auto viewport = vk::Viewport{
|
viewport = vk::Viewport{
|
||||||
.x = 0.0,
|
.x = 0.0,
|
||||||
.y = static_cast<float>(extent.height),
|
.y = static_cast<float>(extent.height),
|
||||||
.width = static_cast<float>(extent.width),
|
.width = static_cast<float>(extent.width),
|
||||||
.height = -static_cast<float>(extent.height),
|
.height = -static_cast<float>(extent.height),
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto scissor = vk::Rect2D {
|
scissor = vk::Rect2D {
|
||||||
.offset = 0,
|
.offset = 0,
|
||||||
.extent = extent,
|
.extent = extent,
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto viewport_info = vk::PipelineViewportStateCreateInfo {
|
viewport_info = vk::PipelineViewportStateCreateInfo {
|
||||||
.viewportCount = 1,
|
.viewportCount = 1,
|
||||||
.pViewports = &viewport,
|
.pViewports = &viewport,
|
||||||
.scissorCount = 1,
|
.scissorCount = 1,
|
||||||
.pScissors = &scissor,
|
.pScissors = &scissor,
|
||||||
};
|
};
|
||||||
|
|
||||||
const vk::DynamicState dyn_states[] = {
|
dyn_states[0] = vk::DynamicState::eScissor;
|
||||||
vk::DynamicState::eScissor,
|
dyn_states[1] = vk::DynamicState::eViewport;
|
||||||
vk::DynamicState::eViewport,
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto dyn_info = vk::PipelineDynamicStateCreateInfo{
|
dyn_info = vk::PipelineDynamicStateCreateInfo {
|
||||||
.dynamicStateCount = std::size(dyn_states),
|
.dynamicStateCount = (uint32_t)std::size(dyn_states),
|
||||||
.pDynamicStates = dyn_states,
|
.pDynamicStates = dyn_states,
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto pipeline_info = vk::GraphicsPipelineCreateInfo {
|
pipeline_info = vk::GraphicsPipelineCreateInfo {
|
||||||
.stageCount = static_cast<uint32_t>(shaders.size()),
|
.stageCount = static_cast<uint32_t>(shaders.size()),
|
||||||
.pStages = shader_info.data(),
|
.pStages = shader_info.data(),
|
||||||
.pVertexInputState = &vertex_input_info,
|
.pVertexInputState = &vertex_input_info,
|
||||||
@ -216,7 +235,30 @@ void GraphicsPipeline::update(uint32_t binding, const Texture& tex) {
|
|||||||
}, nullptr);
|
}, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GraphicsPipeline::rebuild(bool wireframe, bool culling) {
|
||||||
|
vertex_input_info = vk::PipelineVertexInputStateCreateInfo {
|
||||||
|
.vertexBindingDescriptionCount = static_cast<uint32_t>(vertex_bindings.size()),
|
||||||
|
.pVertexBindingDescriptions = vertex_bindings.data(),
|
||||||
|
.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_attrs.size()),
|
||||||
|
.pVertexAttributeDescriptions = vertex_attrs.data(),
|
||||||
|
};
|
||||||
|
|
||||||
|
raster_info.cullMode = culling ? vk::CullModeFlagBits::eBack : vk::CullModeFlagBits::eNone;
|
||||||
|
raster_info.polygonMode = wireframe ? vk::PolygonMode::eLine : vk::PolygonMode::eFill;
|
||||||
|
auto res = dev.createGraphicsPipeline(nullptr, pipeline_info);
|
||||||
|
if (res.result != vk::Result::eSuccess) {
|
||||||
|
Log::error("Failed to create pipeline: (Vulkan error code: %d)\n", res.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
defunct_pipelines.push_back(pipeline);
|
||||||
|
|
||||||
|
pipeline = res.value;
|
||||||
|
}
|
||||||
|
|
||||||
GraphicsPipeline::~GraphicsPipeline() {
|
GraphicsPipeline::~GraphicsPipeline() {
|
||||||
|
for(auto& p : defunct_pipelines) {
|
||||||
|
dev.destroyPipeline(p);
|
||||||
|
}
|
||||||
dev.destroyDescriptorSetLayout(desc_layout);
|
dev.destroyDescriptorSetLayout(desc_layout);
|
||||||
dev.destroyPipelineLayout(layout);
|
dev.destroyPipelineLayout(layout);
|
||||||
dev.destroyDescriptorPool(desc_pool);
|
dev.destroyDescriptorPool(desc_pool);
|
||||||
|
|||||||
@ -3,29 +3,70 @@
|
|||||||
#define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS
|
#define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
#include <util/log.hpp>
|
||||||
|
|
||||||
|
#include <Renderer/VertexBuffer.hpp>
|
||||||
|
|
||||||
struct Shader;
|
struct Shader;
|
||||||
struct UniformBuffer;
|
struct UniformBuffer;
|
||||||
struct VertexBuffer;
|
|
||||||
struct RenderPass;
|
struct RenderPass;
|
||||||
struct Texture;
|
struct Texture;
|
||||||
|
|
||||||
struct GraphicsPipeline {
|
struct GraphicsPipeline {
|
||||||
enum Type {
|
enum Type {
|
||||||
|
eVERTEX,
|
||||||
eGLTF,
|
eGLTF,
|
||||||
|
eBSP,
|
||||||
eTERRAIN,
|
eTERRAIN,
|
||||||
|
eBOX,
|
||||||
};
|
};
|
||||||
|
|
||||||
GraphicsPipeline(vk::Device dev, const std::vector<Shader>& shaders,
|
GraphicsPipeline(vk::Device dev, const std::vector<Shader>& shaders,
|
||||||
const vk::Extent2D& extent, const RenderPass& render_pass,
|
const vk::Extent2D& extent, const RenderPass& render_pass,
|
||||||
vk::ArrayProxy<vk::DescriptorSetLayoutBinding> bindings,
|
vk::ArrayProxy<vk::DescriptorSetLayoutBinding> bindings,
|
||||||
const VertexBuffer& vertex_buffer, enum Type type = Type::eGLTF);
|
const vk::VertexInputBindingDescription& vertex_binding,
|
||||||
|
const std::vector<vk::VertexInputAttributeDescription>& vertex_attrs,
|
||||||
|
enum Type type = Type::eVERTEX, bool wireframe = false, bool culling = true);
|
||||||
|
|
||||||
|
/* everything needed for recreation */
|
||||||
vk::Device dev;
|
vk::Device dev;
|
||||||
|
const std::vector<Shader> shaders;
|
||||||
|
const vk::Extent2D extent;
|
||||||
|
const RenderPass& render_pass;
|
||||||
|
const vk::ArrayProxy<vk::DescriptorSetLayoutBinding> bindings;
|
||||||
|
const vk::VertexInputBindingDescription vertex_binding;
|
||||||
|
const std::vector<vk::VertexInputAttributeDescription> vertex_attrs;
|
||||||
|
const Type type;
|
||||||
|
|
||||||
|
|
||||||
vk::Pipeline pipeline;
|
vk::Pipeline pipeline;
|
||||||
vk::PipelineLayout layout;
|
vk::PipelineLayout layout;
|
||||||
vk::DescriptorSetLayout desc_layout;
|
vk::DescriptorSetLayout desc_layout;
|
||||||
vk::DescriptorPool desc_pool;
|
vk::DescriptorPool desc_pool;
|
||||||
vk::DescriptorSet desc_set;
|
vk::DescriptorSet desc_set;
|
||||||
|
|
||||||
|
/* pipeline creation info cached for rebuilding */
|
||||||
|
std::vector<vk::PipelineShaderStageCreateInfo> shader_info;
|
||||||
|
std::vector<vk::VertexInputBindingDescription> vertex_bindings;
|
||||||
|
vk::PipelineVertexInputStateCreateInfo vertex_input_info;
|
||||||
|
vk::PipelineInputAssemblyStateCreateInfo input_asm_info;
|
||||||
|
vk::PipelineTessellationStateCreateInfo tess_info;
|
||||||
|
vk::PipelineRasterizationStateCreateInfo raster_info;
|
||||||
|
vk::PipelineMultisampleStateCreateInfo multisample_info;
|
||||||
|
vk::PipelineDepthStencilStateCreateInfo depth_stencil_info;
|
||||||
|
vk::PipelineColorBlendAttachmentState color_blend_attachment;
|
||||||
|
std::array<float, 4> blend_constants;
|
||||||
|
vk::PipelineColorBlendStateCreateInfo color_blend_info;
|
||||||
|
vk::Viewport viewport;
|
||||||
|
vk::Rect2D scissor;
|
||||||
|
vk::PipelineViewportStateCreateInfo viewport_info;
|
||||||
|
vk::DynamicState dyn_states[2];
|
||||||
|
vk::PipelineDynamicStateCreateInfo dyn_info;
|
||||||
|
vk::GraphicsPipelineCreateInfo pipeline_info;
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<vk::Pipeline> defunct_pipelines;
|
||||||
|
|
||||||
inline operator vk::Pipeline&() {
|
inline operator vk::Pipeline&() {
|
||||||
return pipeline;
|
return pipeline;
|
||||||
}
|
}
|
||||||
@ -34,6 +75,9 @@ struct GraphicsPipeline {
|
|||||||
void update(uint32_t binding, const UniformBuffer& uni);
|
void update(uint32_t binding, const UniformBuffer& uni);
|
||||||
void update(uint32_t binding, const Texture& tex);
|
void update(uint32_t binding, const Texture& tex);
|
||||||
|
|
||||||
|
|
||||||
|
void rebuild(bool wireframe, bool culling);
|
||||||
|
|
||||||
~GraphicsPipeline();
|
~GraphicsPipeline();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -43,7 +43,7 @@ RenderPass::RenderPass(vk::Device dev, vk::Format image_format, vk::Format depth
|
|||||||
|
|
||||||
|
|
||||||
/* designates producer and consumer of the image to position subpass */
|
/* designates producer and consumer of the image to position subpass */
|
||||||
auto color_dep = vk::SubpassDependency{
|
auto color_dep = vk::SubpassDependency {
|
||||||
.srcSubpass = vk::SubpassExternal,
|
.srcSubpass = vk::SubpassExternal,
|
||||||
.dstSubpass = 0,
|
.dstSubpass = 0,
|
||||||
.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||||
|
|||||||
@ -194,54 +194,40 @@ Renderer::Renderer(Window& win) : win(win) {
|
|||||||
|
|
||||||
uniform_buffer = std::make_unique<UniformBuffer>(phys_dev, dev);
|
uniform_buffer = std::make_unique<UniformBuffer>(phys_dev, dev);
|
||||||
|
|
||||||
textures = createResources({
|
|
||||||
"assets/textures/oil.jpg",
|
|
||||||
"assets/textures/eire.png",
|
|
||||||
});
|
|
||||||
|
|
||||||
std::vector<Shader> shaders = {
|
|
||||||
{dev, "assets/shaders/fraglight.vert.spv", vk::ShaderStageFlagBits::eVertex },
|
|
||||||
{ dev, "assets/shaders/fraglight.geom.spv", vk::ShaderStageFlagBits::eGeometry },
|
|
||||||
{ dev, "assets/shaders/lambert.frag.spv", vk::ShaderStageFlagBits::eFragment },
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<vk::DescriptorSetLayoutBinding> bindings = {
|
std::vector<vk::DescriptorSetLayoutBinding> bindings = {
|
||||||
uniform_buffer->binding(0),
|
uniform_buffer->binding(0),
|
||||||
textures[0].binding(1),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* initialize models */
|
/* BSP loader */
|
||||||
Timer model_timer;
|
bsp = std::make_unique<HLBSP::BSP>(phys_dev, dev, "assets/maps/dmc.bsp");
|
||||||
models.push_back(std::make_shared<Model>(phys_dev, dev, "assets/models/dragon.gltf"));
|
bsp_shaders = {
|
||||||
auto t = model_timer.stop();
|
{ dev, "assets/shaders/bin/bsp.vert.spv", vk::ShaderStageFlagBits::eVertex },
|
||||||
|
{ dev, "assets/shaders/bin/bsp.frag.spv", vk::ShaderStageFlagBits::eFragment },
|
||||||
Log::debug("Models loaded in %lf milliseconds\n", model_timer.read());
|
|
||||||
|
|
||||||
pipeline = std::make_unique<GraphicsPipeline>(dev, shaders, swapchain->extent, *render_pass, bindings, *models[0]->vertex_buffer);
|
|
||||||
|
|
||||||
pipeline->update(0, *uniform_buffer);
|
|
||||||
pipeline->update(1, textures[1]);
|
|
||||||
|
|
||||||
|
|
||||||
/* create Terrain */
|
|
||||||
terrain = std::make_unique<Terrain>(phys_dev, dev, textures[1]);
|
|
||||||
|
|
||||||
std::vector<Shader> terrain_shaders = {
|
|
||||||
{ dev, "assets/shaders/terrain.vert.spv", vk::ShaderStageFlagBits::eVertex },
|
|
||||||
{ dev, "assets/shaders/terrain.tesc.spv", vk::ShaderStageFlagBits::eTessellationControl },
|
|
||||||
{ dev, "assets/shaders/terrain.tese.spv", vk::ShaderStageFlagBits::eTessellationEvaluation },
|
|
||||||
{ dev, "assets/shaders/terrain.frag.spv", vk::ShaderStageFlagBits::eFragment },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
terrain_pipeline = std::make_unique<GraphicsPipeline>(dev, terrain_shaders, swapchain->extent, *render_pass, bindings, *terrain->vertex_buffer, GraphicsPipeline::eTERRAIN);
|
bsp->pipeline = std::make_unique<GraphicsPipeline>(dev, bsp_shaders, swapchain->extent, *render_pass, bindings, bsp->vertex_buffer->binding(0), bsp->vertex_buffer->attrs(0));
|
||||||
|
bsp->pipeline->update(0, *uniform_buffer);
|
||||||
|
|
||||||
terrain_pipeline->update(0, *uniform_buffer);
|
/* bounding and hitboxs */
|
||||||
terrain_pipeline->update(1, textures[1]);
|
box_shaders = {
|
||||||
|
{ dev, "assets/shaders/bin/box.vert.spv", vk::ShaderStageFlagBits::eVertex },
|
||||||
|
{ dev, "assets/shaders/bin/box.geom.spv", vk::ShaderStageFlagBits::eGeometry },
|
||||||
|
{ dev, "assets/shaders/bin/box.frag.spv", vk::ShaderStageFlagBits::eFragment },
|
||||||
|
};
|
||||||
|
|
||||||
for (auto& shader : shaders)
|
std::vector<BoxVertex> boxes;
|
||||||
shader.cleanup();
|
boxes.reserve(bsp->leaves.size());
|
||||||
for (auto& shader : terrain_shaders)
|
for (auto& leaf : bsp->leaves) {
|
||||||
shader.cleanup();
|
boxes.push_back(BoxVertex{
|
||||||
|
.mins = leaf.bb_mins,
|
||||||
|
.maxes = leaf.bb_maxes,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
box_buffer = std::make_unique<GeneralVertexBuffer<BoxVertex>>(phys_dev, dev, boxes.size());
|
||||||
|
box_buffer->upload(boxes);
|
||||||
|
|
||||||
|
box_pipeline = std::make_unique<GraphicsPipeline>(dev, box_shaders, swapchain->extent, *render_pass, bindings, box_buffer->binding(0), box_buffer->attrs(0), GraphicsPipeline::Type::eBOX);
|
||||||
|
box_pipeline->update(0, *uniform_buffer);
|
||||||
|
|
||||||
ui = std::make_unique<UI>(this);
|
ui = std::make_unique<UI>(this);
|
||||||
}
|
}
|
||||||
@ -331,7 +317,7 @@ void Renderer::draw() {
|
|||||||
|
|
||||||
auto scissor = vk::Rect2D {
|
auto scissor = vk::Rect2D {
|
||||||
.offset = {0, 0},
|
.offset = {0, 0},
|
||||||
.extent = swapchain->extent,
|
.extent = win.getDimensions(),
|
||||||
};
|
};
|
||||||
|
|
||||||
/* no secondary command buffers (yet), so contents are passed inline */
|
/* no secondary command buffers (yet), so contents are passed inline */
|
||||||
@ -339,7 +325,7 @@ void Renderer::draw() {
|
|||||||
|
|
||||||
auto sz = win.getDimensions();
|
auto sz = win.getDimensions();
|
||||||
|
|
||||||
const auto p = glm::perspective(glm::radians(90.0f), static_cast<float>(sz.width) / static_cast<float>(sz.height), 0.01f, 2000.0f);
|
const auto p = glm::perspective(glm::radians(90.0f), static_cast<float>(sz.width) / static_cast<float>(sz.height), near_plane, far_plane);
|
||||||
|
|
||||||
auto uni = UniformData{
|
auto uni = UniformData{
|
||||||
.view = cam.view(),
|
.view = cam.view(),
|
||||||
@ -356,13 +342,21 @@ void Renderer::draw() {
|
|||||||
|
|
||||||
uniform_buffer->upload(uni);
|
uniform_buffer->upload(uni);
|
||||||
|
|
||||||
command_buffer->bind(*terrain_pipeline);
|
|
||||||
command_buffer->command_buffer.setViewport(0, viewport);
|
command_buffer->command_buffer.setViewport(0, viewport);
|
||||||
command_buffer->command_buffer.setScissor(0, scissor);
|
command_buffer->command_buffer.setScissor(0, scissor);
|
||||||
|
|
||||||
command_buffer->bind(terrain_pipeline->layout, terrain_pipeline->desc_set);
|
bsp->load_vertices(cam.pos, visibility_testing, p * uni.view);
|
||||||
command_buffer->bind(terrain.get());
|
command_buffer->bind(bsp.get());
|
||||||
command_buffer->command_buffer.drawIndexed(terrain->indices.size(), 1, 0, 0, 0);
|
/*command_buffer->draw(bsp->vertices.size(), 1);*/
|
||||||
|
command_buffer->draw(bsp->textured_vertices.size(), 1);
|
||||||
|
|
||||||
|
n_indices = bsp->textured_vertices.size();
|
||||||
|
|
||||||
|
if (show_bboxes) {
|
||||||
|
command_buffer->bind(*box_pipeline);
|
||||||
|
command_buffer->bind(*box_buffer);
|
||||||
|
command_buffer->draw(box_buffer->buffer->size / sizeof(BoxVertex), 1);
|
||||||
|
}
|
||||||
|
|
||||||
/* draw User Interface stuff */
|
/* draw User Interface stuff */
|
||||||
ui->newFrame();
|
ui->newFrame();
|
||||||
@ -373,7 +367,6 @@ void Renderer::draw() {
|
|||||||
|
|
||||||
command_buffer->end();
|
command_buffer->end();
|
||||||
|
|
||||||
|
|
||||||
vk::PipelineStageFlags stage_flags = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
vk::PipelineStageFlags stage_flags = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
||||||
|
|
||||||
/* submit our command buffer to the queue */
|
/* submit our command buffer to the queue */
|
||||||
@ -413,24 +406,23 @@ void Renderer::present() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
frame++;
|
frame++;
|
||||||
time += 0.0167f * speed * static_cast<float>(running);
|
time += frametime / 1000.0 * speed * static_cast<float>(!paused);
|
||||||
}
|
}
|
||||||
|
|
||||||
Renderer::~Renderer() {
|
Renderer::~Renderer() {
|
||||||
dev.waitIdle();
|
dev.waitIdle();
|
||||||
|
|
||||||
ui.reset();
|
ui.reset();
|
||||||
|
bsp.reset();
|
||||||
|
|
||||||
for(auto& model : models)
|
box_buffer.reset();
|
||||||
model.reset();
|
box_pipeline.reset();
|
||||||
|
|
||||||
terrain.reset();
|
|
||||||
|
|
||||||
uniform_buffer.reset();
|
uniform_buffer.reset();
|
||||||
vertex_buffer.reset();
|
|
||||||
terrain_pipeline.reset();
|
|
||||||
pipeline.reset();
|
|
||||||
|
|
||||||
|
for (auto& shader : box_shaders)
|
||||||
|
shader.cleanup();
|
||||||
|
for (auto& shader : bsp_shaders)
|
||||||
|
shader.cleanup();
|
||||||
|
|
||||||
for (auto& tex : textures) {
|
for (auto& tex : textures) {
|
||||||
tex.cleanup();
|
tex.cleanup();
|
||||||
|
|||||||
@ -9,16 +9,19 @@
|
|||||||
#include <Renderer/CommandBuffer.hpp>
|
#include <Renderer/CommandBuffer.hpp>
|
||||||
#include <Renderer/RenderPass.hpp>
|
#include <Renderer/RenderPass.hpp>
|
||||||
|
|
||||||
|
#include <Renderer/VertexBuffer.hpp>
|
||||||
|
|
||||||
#include <Scene/Camera.hpp>
|
#include <Scene/Camera.hpp>
|
||||||
|
#include <Scene/BSP.hpp>
|
||||||
#include <Scene/Terrain.hpp>
|
#include <Scene/Terrain.hpp>
|
||||||
|
|
||||||
#include <Model/Model.hpp>
|
#include <Model/Model.hpp>
|
||||||
|
|
||||||
#include <UI/UI.hpp>
|
#include <UI/UI.hpp>
|
||||||
|
|
||||||
|
|
||||||
struct Window;
|
struct Window;
|
||||||
struct UniformBuffer;
|
struct UniformBuffer;
|
||||||
struct VertexBuffer;
|
|
||||||
struct Texture;
|
struct Texture;
|
||||||
|
|
||||||
/* Contains all of the Vulkan objects involved in rendering the scene */
|
/* Contains all of the Vulkan objects involved in rendering the scene */
|
||||||
@ -48,31 +51,45 @@ struct Renderer {
|
|||||||
std::unique_ptr<CommandBuffer> command_buffer;
|
std::unique_ptr<CommandBuffer> command_buffer;
|
||||||
std::unique_ptr<RenderPass> render_pass;
|
std::unique_ptr<RenderPass> render_pass;
|
||||||
|
|
||||||
|
std::vector<Shader> box_shaders;
|
||||||
std::unique_ptr<GraphicsPipeline> pipeline;
|
std::unique_ptr<GraphicsPipeline> box_pipeline;
|
||||||
std::unique_ptr<GraphicsPipeline> terrain_pipeline;
|
std::unique_ptr<GeneralVertexBuffer<BoxVertex>> box_buffer;
|
||||||
std::unique_ptr<VertexBuffer> vertex_buffer;
|
|
||||||
std::unique_ptr<UniformBuffer> uniform_buffer;
|
std::unique_ptr<UniformBuffer> uniform_buffer;
|
||||||
|
|
||||||
std::unique_ptr<Terrain> terrain;
|
std::vector<Shader> bsp_shaders;
|
||||||
|
std::unique_ptr<HLBSP::BSP> bsp;
|
||||||
|
|
||||||
std::vector<Texture> textures;
|
std::vector<Texture> textures;
|
||||||
|
|
||||||
uint32_t current_image_idx;
|
uint32_t current_image_idx;
|
||||||
uint64_t frame = 0;
|
uint64_t frame = 0;
|
||||||
|
|
||||||
std::vector<std::shared_ptr<Model>> models;
|
|
||||||
std::unique_ptr<UI> ui;
|
std::unique_ptr<UI> ui;
|
||||||
|
|
||||||
Camera cam{ .pos = glm::vec3(0.0, 5.0, 0.0), };
|
Camera cam{ .pos = glm::vec3(0.0, 5.0, 0.0), };
|
||||||
|
|
||||||
bool capture_mouse = false;
|
|
||||||
|
float fps;
|
||||||
|
bool in_menu = false;
|
||||||
bool flycam = false;
|
bool flycam = false;
|
||||||
/* time speed */
|
/* time speed */
|
||||||
float time = 0.0;
|
float time = 0.0;
|
||||||
float speed = 1.0;
|
float speed = 1.0;
|
||||||
bool running = true;
|
bool paused = false;
|
||||||
|
bool visibility_testing = false;
|
||||||
|
bool show_bboxes = false;
|
||||||
|
bool should_close = false;
|
||||||
|
bool wireframe_mode = false;
|
||||||
|
bool backface_culling = true;
|
||||||
|
|
||||||
|
size_t n_indices;
|
||||||
|
|
||||||
|
float near_plane = 2.0f;
|
||||||
|
float far_plane = 10000.0f;
|
||||||
|
|
||||||
float tess_factor = 1.8f;
|
float tess_factor = 1.8f;
|
||||||
float tess_edge_size = 20.0f;
|
float tess_edge_size = 20.0f;
|
||||||
|
|
||||||
|
float frametime = 0.0;
|
||||||
|
float max_fps = 120.0;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -60,7 +60,7 @@ void Swapchain::create(vk::SwapchainKHR old_swapchain) {
|
|||||||
/* see if this allows see through windows on Wayland */
|
/* see if this allows see through windows on Wayland */
|
||||||
.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque,
|
.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque,
|
||||||
/* waits for refresh (V-Sync), consider playing with relaxed fifo later on*/
|
/* waits for refresh (V-Sync), consider playing with relaxed fifo later on*/
|
||||||
// .presentMode = vk::PresentModeKHR::eFifo,
|
// .presentMode = vk::PresentModeKHR::eFifoRelaxed,
|
||||||
.presentMode = vk::PresentModeKHR::eMailbox,
|
.presentMode = vk::PresentModeKHR::eMailbox,
|
||||||
.clipped = VK_TRUE,
|
.clipped = VK_TRUE,
|
||||||
.oldSwapchain = old_swapchain,
|
.oldSwapchain = old_swapchain,
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
#include <Renderer/VertexBuffer.hpp>
|
|
||||||
|
|
||||||
#include <Memory/Buffer.hpp>
|
|
||||||
|
|
||||||
#include <tinygltf/tiny_gltf.h>
|
|
||||||
|
|
||||||
VertexBuffer::VertexBuffer(vk::PhysicalDevice phys_dev, vk::Device dev, size_t n_vertices) {
|
|
||||||
buffer = std::make_unique<Buffer>(phys_dev, dev, n_vertices * sizeof(Vertex),
|
|
||||||
vk::BufferUsageFlagBits::eVertexBuffer,
|
|
||||||
vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VertexBuffer::upload(const std::vector<Vertex>& data) {
|
|
||||||
buffer->upload(reinterpret_cast<const uint8_t*>(data.data()), sizeof(Vertex)*data.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
void VertexBuffer::upload(const tinygltf::Buffer& buff, const tinygltf::BufferView& view) {
|
|
||||||
std::memcpy(buffer->p, buff.data.data() + view.byteOffset, view.byteLength);
|
|
||||||
}
|
|
||||||
@ -8,21 +8,91 @@
|
|||||||
|
|
||||||
#include <tinygltf/tiny_gltf.h>
|
#include <tinygltf/tiny_gltf.h>
|
||||||
|
|
||||||
struct Vertex {
|
struct glTFVertex {
|
||||||
glm::vec3 pos;
|
glm::vec3 pos;
|
||||||
glm::vec3 norm;
|
glm::vec3 norm;
|
||||||
glm::vec2 uv;
|
glm::vec2 uv;
|
||||||
glm::vec3 color;
|
glm::vec3 color;
|
||||||
|
|
||||||
|
static inline std::vector<vk::VertexInputAttributeDescription> attrs(uint32_t binding) {
|
||||||
|
return std::vector<vk::VertexInputAttributeDescription> {
|
||||||
|
{
|
||||||
|
.location = 0,
|
||||||
|
.binding = binding,
|
||||||
|
.format = vk::Format::eR32G32B32Sfloat,
|
||||||
|
.offset = offsetof(glTFVertex, pos),
|
||||||
|
}, {
|
||||||
|
.location = 1,
|
||||||
|
.binding = binding,
|
||||||
|
.format = vk::Format::eR32G32B32Sfloat,
|
||||||
|
.offset = offsetof(glTFVertex, norm),
|
||||||
|
}, {
|
||||||
|
.location = 2,
|
||||||
|
.binding = binding,
|
||||||
|
.format = vk::Format::eR32G32Sfloat,
|
||||||
|
.offset = offsetof(glTFVertex, uv),
|
||||||
|
}, {
|
||||||
|
.location = 3,
|
||||||
|
.binding = binding,
|
||||||
|
.format = vk::Format::eR32G32B32Sfloat,
|
||||||
|
.offset = offsetof(glTFVertex, color),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexBuffer {
|
|
||||||
|
enum BoxType {
|
||||||
|
eBounding,
|
||||||
|
eEnemyHit,
|
||||||
|
};
|
||||||
|
/* for hitboxes and bounding boxes */
|
||||||
|
struct BoxVertex {
|
||||||
|
glm::vec3 mins;
|
||||||
|
glm::vec3 maxes;
|
||||||
|
unsigned int id;
|
||||||
|
|
||||||
|
static inline std::vector<vk::VertexInputAttributeDescription> attrs(uint32_t binding) {
|
||||||
|
return std::vector<vk::VertexInputAttributeDescription> {
|
||||||
|
{
|
||||||
|
.location = 0,
|
||||||
|
.binding = binding,
|
||||||
|
.format = vk::Format::eR32G32B32Sfloat,
|
||||||
|
.offset = offsetof(BoxVertex, mins),
|
||||||
|
}, {
|
||||||
|
.location = 1,
|
||||||
|
.binding = binding,
|
||||||
|
.format = vk::Format::eR32G32B32Sfloat,
|
||||||
|
.offset = offsetof(BoxVertex, maxes),
|
||||||
|
}, {
|
||||||
|
.location = 2,
|
||||||
|
.binding = binding,
|
||||||
|
.format = vk::Format::eR32Uint,
|
||||||
|
.offset = offsetof(BoxVertex, id),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template<typename Vertex>
|
||||||
|
struct GeneralVertexBuffer {
|
||||||
std::unique_ptr<Buffer> buffer;
|
std::unique_ptr<Buffer> buffer;
|
||||||
|
|
||||||
|
GeneralVertexBuffer(vk::PhysicalDevice phys_dev, vk::Device dev, size_t n_vertices) {
|
||||||
|
buffer = std::make_unique<Buffer>(phys_dev, dev, n_vertices * sizeof(Vertex),
|
||||||
|
vk::BufferUsageFlagBits::eVertexBuffer,
|
||||||
|
vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible);
|
||||||
|
}
|
||||||
|
|
||||||
VertexBuffer(vk::PhysicalDevice phys_dev, vk::Device dev, size_t n_vertices);
|
void upload(const std::vector<Vertex>& data) {
|
||||||
|
buffer->upload(reinterpret_cast<const uint8_t*>(data.data()), sizeof(Vertex) * data.size());
|
||||||
void upload(const std::vector<Vertex>& data);
|
}
|
||||||
void upload(const tinygltf::Buffer& buff, const tinygltf::BufferView& view);
|
void upload(const tinygltf::Buffer& buff, const tinygltf::BufferView& view) {
|
||||||
|
std::memcpy(buffer->p, buff.data.data() + view.byteOffset, view.byteLength);
|
||||||
|
}
|
||||||
|
|
||||||
inline vk::VertexInputBindingDescription binding(uint32_t binding, vk::ShaderStageFlags stages = vk::ShaderStageFlagBits::eVertex) const {
|
inline vk::VertexInputBindingDescription binding(uint32_t binding, vk::ShaderStageFlags stages = vk::ShaderStageFlagBits::eVertex) const {
|
||||||
return vk::VertexInputBindingDescription {
|
return vk::VertexInputBindingDescription {
|
||||||
@ -33,28 +103,9 @@ struct VertexBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline std::vector<vk::VertexInputAttributeDescription> attrs(uint32_t binding) const {
|
inline std::vector<vk::VertexInputAttributeDescription> attrs(uint32_t binding) const {
|
||||||
return std::vector<vk::VertexInputAttributeDescription> {
|
return Vertex::attrs(binding);
|
||||||
{
|
|
||||||
.location = 0,
|
|
||||||
.binding = binding,
|
|
||||||
.format = vk::Format::eR32G32B32Sfloat,
|
|
||||||
.offset = offsetof(Vertex, pos),
|
|
||||||
}, {
|
|
||||||
.location = 1,
|
|
||||||
.binding = binding,
|
|
||||||
.format = vk::Format::eR32G32B32Sfloat,
|
|
||||||
.offset = offsetof(Vertex, norm),
|
|
||||||
},{
|
|
||||||
.location = 2,
|
|
||||||
.binding = binding,
|
|
||||||
.format = vk::Format::eR32G32Sfloat,
|
|
||||||
.offset = offsetof(Vertex, uv),
|
|
||||||
},{
|
|
||||||
.location = 3,
|
|
||||||
.binding = binding,
|
|
||||||
.format = vk::Format::eR32G32B32Sfloat,
|
|
||||||
.offset = offsetof(Vertex, color),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using VertexBuffer = GeneralVertexBuffer<glTFVertex>;
|
||||||
|
|
||||||
|
|||||||
257
Scene/BSP.cpp
Normal file
257
Scene/BSP.cpp
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
#include <Scene/BSP.hpp>
|
||||||
|
|
||||||
|
#include <Renderer/Pipeline.hpp>
|
||||||
|
|
||||||
|
#include <util/geo.hpp>
|
||||||
|
#include <util/file.hpp>
|
||||||
|
#include <util/log.hpp>
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
/* changes handedness by swapping z and y */
|
||||||
|
template<typename T>
|
||||||
|
static inline void change_swizzle(T& v) {
|
||||||
|
auto tmp = v.y;
|
||||||
|
v.y = v.z;
|
||||||
|
v.z = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace HLBSP;
|
||||||
|
|
||||||
|
static inline void copy_data(void* file_data, std::string& dst, Lump& lump) {
|
||||||
|
dst.resize(lump.len);
|
||||||
|
std::memcpy(dst.data(), (u8*)file_data + (size_t)lump.offset, lump.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static inline void copy_data(void* file_data, std::vector<T>& dst, Lump& lump) {
|
||||||
|
Log::debug("%zu items\n", lump.len / sizeof(T));
|
||||||
|
dst.resize(lump.len / sizeof(T));
|
||||||
|
std::memcpy(dst.data(), ((u8*)file_data) + lump.offset, lump.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline glm::vec2 calc_tex_coords(glm::vec3 v, const TexInfo& t) {
|
||||||
|
change_swizzle(v);
|
||||||
|
return glm::vec2(
|
||||||
|
t.shift_s + glm::dot(v, t.shift_s_dir),
|
||||||
|
t.shift_t + glm::dot(v, t.shift_t_dir)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BSP::load_vertices(const glm::vec3& cam_pos, bool visibility_test, const glm::mat4& view) {
|
||||||
|
std::set<int> present_faces;
|
||||||
|
std::vector<Face> visible_faces;
|
||||||
|
if (visibility_test) {
|
||||||
|
auto leaf_idx = determine_leaf(cam_pos);
|
||||||
|
|
||||||
|
auto fr_planes = frustum(view);
|
||||||
|
|
||||||
|
if (leaf_idx == last_leaf)
|
||||||
|
return;
|
||||||
|
|
||||||
|
last_leaf = leaf_idx;
|
||||||
|
auto& cam_leaf = leaves[leaf_idx];
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<Leaf> visible_leafs;
|
||||||
|
for (auto& leaf : leaves) {
|
||||||
|
|
||||||
|
const auto min = leaf.bb_mins;
|
||||||
|
const auto max = leaf.bb_maxes;
|
||||||
|
|
||||||
|
const glm::vec3 bounding_planes[8] = {
|
||||||
|
{ min.x, min.y, min.z },
|
||||||
|
{ max.x, min.y, min.z },
|
||||||
|
{ max.x, max.y, min.z },
|
||||||
|
{ min.x, max.y, min.z },
|
||||||
|
{ min.x, min.y, max.z },
|
||||||
|
{ max.x, min.y, max.z },
|
||||||
|
{ max.x, max.y, max.z },
|
||||||
|
{ min.x, max.y, max.z },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (determine_visibility(cam_leaf, leaf, fr_planes, bounding_planes))
|
||||||
|
visible_leafs.push_back(leaf);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& leaf : visible_leafs) {
|
||||||
|
for (size_t i = 0; i < leaf.n_mark_surfaces; i++) {
|
||||||
|
auto idx = mark_surfaces[leaf.first_mark_surface_idx + i];
|
||||||
|
if (present_faces.contains(idx))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
present_faces.insert(idx);
|
||||||
|
visible_faces.push_back(faces[idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
visible_faces = faces;
|
||||||
|
}
|
||||||
|
|
||||||
|
textured_vertices.clear();
|
||||||
|
|
||||||
|
for (auto& face : visible_faces) {
|
||||||
|
auto& tex_info = tex_infos[face.tex_info_idx];
|
||||||
|
|
||||||
|
for (i16 i = 1, j = 2; j < face.n_surf_edges; i++, j++) {
|
||||||
|
textured_vertices.push_back(Vertex{
|
||||||
|
.pos = processed_vertices[face.first_surf_edge_idx],
|
||||||
|
.uv = calc_tex_coords(processed_vertices[face.first_surf_edge_idx], tex_info),
|
||||||
|
});
|
||||||
|
textured_vertices.push_back(Vertex{
|
||||||
|
.pos = processed_vertices[face.first_surf_edge_idx+i],
|
||||||
|
.uv = calc_tex_coords(processed_vertices[face.first_surf_edge_idx+i], tex_info),
|
||||||
|
});
|
||||||
|
textured_vertices.push_back(Vertex{
|
||||||
|
.pos = processed_vertices[face.first_surf_edge_idx+j],
|
||||||
|
.uv = calc_tex_coords(processed_vertices[face.first_surf_edge_idx+j], tex_info),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vertex_buffer->upload(textured_vertices);
|
||||||
|
}
|
||||||
|
|
||||||
|
int BSP::get_index_from_surfedge(int surfedge) {
|
||||||
|
int surf = surfedges[surfedge];
|
||||||
|
if(surf >= 0) {
|
||||||
|
return edges[surf].vertex_indices[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return edges[-surf].vertex_indices[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int BSP::determine_leaf(glm::vec3 cam_pos) {
|
||||||
|
/* use SDF of planes to determine relative position with respect to partitioning planes */
|
||||||
|
int idx = 0;
|
||||||
|
/* positive values are node indices, negative values are leaf indices */
|
||||||
|
while (idx >= 0) {
|
||||||
|
const auto& plane = planes[nodes[idx].plane];
|
||||||
|
const auto dist = glm::dot(plane.norm, cam_pos) - plane.dist;
|
||||||
|
|
||||||
|
if (dist >= 0)
|
||||||
|
idx = nodes[idx].children[0];
|
||||||
|
else
|
||||||
|
idx = nodes[idx].children[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return -idx - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool BSP::determine_visibility(const Leaf& cam_leaf, const Leaf& leaf, const std::array<glm::vec4, 6>& frustum, const glm::vec3 box_verts[8]) {
|
||||||
|
/* perform fustrum culling */
|
||||||
|
return box_in_frustum(frustum, box_verts);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::map<std::string, std::string>> load_entities(const std::string& in) {
|
||||||
|
/* TODO */
|
||||||
|
return {
|
||||||
|
{{"test", "this"}},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<MipTexture> load_mip_textures(const u8* data, u32 offset) {
|
||||||
|
const TextureLump* lump = reinterpret_cast<const TextureLump*>(data + offset);
|
||||||
|
std::vector<MipTexture> ret;
|
||||||
|
ret.resize(lump->n_mip_textures);
|
||||||
|
|
||||||
|
for(size_t i = 0; i < ret.size(); i++) {
|
||||||
|
ret[i] = *reinterpret_cast<const MipTexture*>(data + lump->offsets[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
BSP::BSP(vk::PhysicalDevice phys_dev, vk::Device dev, const std::string& fname) : dev(dev), filename(fname) {
|
||||||
|
file_data = file::slurpb(fname);
|
||||||
|
Log::debug("BSP file size: %zu\n", file_data.size());
|
||||||
|
header = reinterpret_cast<Header*>(file_data.data());
|
||||||
|
|
||||||
|
Log::info("Loading BSP: %s\n", fname.c_str());
|
||||||
|
|
||||||
|
if(header->version != 30) {
|
||||||
|
Log::error("BSP file not expected version (Half Life has version 30)!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::debug("Loading entities\n");
|
||||||
|
std::string entities_buff;
|
||||||
|
copy_data(file_data.data(), entities_buff, header->entities);
|
||||||
|
entities = load_entities(entities_buff);
|
||||||
|
|
||||||
|
Log::debug("Loading planes\n");
|
||||||
|
copy_data(file_data.data(), planes, header->planes);
|
||||||
|
/* change swizzle */
|
||||||
|
for (auto& plane : planes) {
|
||||||
|
change_swizzle(plane.norm);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::debug("Loading textures\n");
|
||||||
|
textures = load_mip_textures(file_data.data(), header->textures.offset);
|
||||||
|
|
||||||
|
Log::debug("Loading vertices\n");
|
||||||
|
copy_data(file_data.data(), vertices, header->vertices);
|
||||||
|
for (auto& vertex : vertices) {
|
||||||
|
change_swizzle(vertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::debug("Loading nodes\n");
|
||||||
|
copy_data(file_data.data(), nodes, header->nodes);
|
||||||
|
for (auto& node : nodes) {
|
||||||
|
change_swizzle(node.bb_mins);
|
||||||
|
change_swizzle(node.bb_maxes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::debug("Loading texinfo\n");
|
||||||
|
copy_data(file_data.data(), tex_infos, header->texinfo);
|
||||||
|
|
||||||
|
Log::debug("Loading faces\n");
|
||||||
|
copy_data(file_data.data(), faces, header->faces);
|
||||||
|
|
||||||
|
Log::debug("Loading lightmap\n");
|
||||||
|
lightmap.lights = reinterpret_cast<rgb*>(file_data.data()+header->lighting.offset);
|
||||||
|
|
||||||
|
Log::debug("Loading clip nodes\n");
|
||||||
|
copy_data(file_data.data(), clip_nodes, header->clip_nodes);
|
||||||
|
|
||||||
|
Log::debug("Loading leaves\n");
|
||||||
|
copy_data(file_data.data(), leaves, header->leaves);
|
||||||
|
for (auto& leaf : leaves) {
|
||||||
|
change_swizzle(leaf.bb_mins);
|
||||||
|
change_swizzle(leaf.bb_maxes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::debug("Loading mark surfaces\n");
|
||||||
|
copy_data(file_data.data(), mark_surfaces, header->mark_surfaces);
|
||||||
|
|
||||||
|
Log::debug("Loading edges\n");
|
||||||
|
copy_data(file_data.data(), edges, header->edges);
|
||||||
|
|
||||||
|
Log::debug("Loading surfedges\n");
|
||||||
|
copy_data(file_data.data(), surfedges, header->surf_edges);
|
||||||
|
processed_vertices.reserve(surfedges.size());
|
||||||
|
/* use this to build our processed_vertices, idea thanks to gzalo's HalfMapper */
|
||||||
|
for(const auto& s : surfedges) {
|
||||||
|
processed_vertices.push_back(vertices[edges[s > 0? s : -s].vertex_indices[s<=0]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::debug("Loading models\n");
|
||||||
|
copy_data(file_data.data(), models, header->models);
|
||||||
|
for (auto& model : models) {
|
||||||
|
change_swizzle(model.bb_mins);
|
||||||
|
change_swizzle(model.bb_maxes);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t max_vertex_count = 0;
|
||||||
|
|
||||||
|
for (const auto& face : faces) {
|
||||||
|
max_vertex_count += (face.n_surf_edges - 2) * 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::debug("Creating vertex buffer of size %zu\n", max_vertex_count);
|
||||||
|
vertex_buffer = std::make_unique<GeneralVertexBuffer<Vertex>>(phys_dev, dev, max_vertex_count);
|
||||||
|
textured_vertices.reserve(max_vertex_count);
|
||||||
|
}
|
||||||
239
Scene/BSP.hpp
Normal file
239
Scene/BSP.hpp
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <util/int.hpp>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
#include <Renderer/VertexBuffer.hpp>
|
||||||
|
#include <Renderer/Pipeline.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
#define MAX_TEXTURE_NAME 16
|
||||||
|
#define MIP_LEVELS 4
|
||||||
|
|
||||||
|
#define MAX_MAP_HULLS 4
|
||||||
|
/* contains loading functions for Half Life BSPs */
|
||||||
|
namespace HLBSP {
|
||||||
|
|
||||||
|
struct Lump {
|
||||||
|
u32 offset;
|
||||||
|
u32 len;
|
||||||
|
};
|
||||||
|
using rgb = glm::u8vec3;
|
||||||
|
using rgba = glm::u8vec4;
|
||||||
|
|
||||||
|
using vec3 = glm::vec3;
|
||||||
|
using vec2 = glm::vec2;
|
||||||
|
using ivec3 = glm::vec<3, i16>;
|
||||||
|
|
||||||
|
struct Header {
|
||||||
|
u32 version;
|
||||||
|
|
||||||
|
union {
|
||||||
|
Lump lumps[15];
|
||||||
|
struct {
|
||||||
|
Lump entities,
|
||||||
|
planes,
|
||||||
|
textures,
|
||||||
|
vertices,
|
||||||
|
visibility,
|
||||||
|
nodes,
|
||||||
|
texinfo,
|
||||||
|
faces,
|
||||||
|
lighting,
|
||||||
|
clip_nodes,
|
||||||
|
leaves,
|
||||||
|
mark_surfaces,
|
||||||
|
edges,
|
||||||
|
surf_edges,
|
||||||
|
models;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Plane {
|
||||||
|
vec3 norm;
|
||||||
|
float dist;
|
||||||
|
/* exists for certain optimizations (swaped y and z) */
|
||||||
|
enum PlaneType {
|
||||||
|
eX,
|
||||||
|
eZ,
|
||||||
|
eY,
|
||||||
|
eAnyX,
|
||||||
|
eAnyZ,
|
||||||
|
eAnyY,
|
||||||
|
} type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TextureLump {
|
||||||
|
u32 n_mip_textures;
|
||||||
|
i32 offsets[];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MipTexture {
|
||||||
|
char name[MAX_TEXTURE_NAME];
|
||||||
|
u32 width, height;
|
||||||
|
/* is 0 if stored in WAD, otherwise, offset is from beginning of this struct */
|
||||||
|
u32 mip_offsets[MIP_LEVELS];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Vertex {
|
||||||
|
vec3 pos;
|
||||||
|
vec2 uv;
|
||||||
|
|
||||||
|
static inline std::vector<vk::VertexInputAttributeDescription> attrs(uint32_t binding) {
|
||||||
|
return std::vector<vk::VertexInputAttributeDescription> {
|
||||||
|
{
|
||||||
|
.location = 0,
|
||||||
|
.binding = binding,
|
||||||
|
.format = vk::Format::eR32G32B32Sfloat,
|
||||||
|
.offset = offsetof(Vertex, pos),
|
||||||
|
}, {
|
||||||
|
.location = 1,
|
||||||
|
.binding = binding,
|
||||||
|
.format = vk::Format::eR32G32Sfloat,
|
||||||
|
.offset = offsetof(Vertex, uv),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Vis {};
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
i32 plane;
|
||||||
|
/* negative numbers are leaf indices */
|
||||||
|
i16 children[2];
|
||||||
|
|
||||||
|
/* bounding box coords (integer) */
|
||||||
|
ivec3 bb_mins;
|
||||||
|
ivec3 bb_maxes;
|
||||||
|
|
||||||
|
i16 first_face_idx;
|
||||||
|
i16 n_faces;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TexInfo {
|
||||||
|
vec3 shift_s_dir;
|
||||||
|
float shift_s;
|
||||||
|
vec3 shift_t_dir;
|
||||||
|
float shift_t;
|
||||||
|
u32 mip_tex_idx;
|
||||||
|
/* seems to always be 0 */
|
||||||
|
u32 flags;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Face {
|
||||||
|
u16 plane_idx;
|
||||||
|
/* set if different normals orientation */
|
||||||
|
u16 plane_side;
|
||||||
|
u32 first_surf_edge_idx;
|
||||||
|
i16 n_surf_edges;
|
||||||
|
i16 tex_info_idx;
|
||||||
|
u8 lighting_styles[4];
|
||||||
|
u32 lightmap_offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Lightmap {
|
||||||
|
rgb* lights;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ClipNode {
|
||||||
|
i32 plane_idx;
|
||||||
|
/* negative numbers are contents */
|
||||||
|
i16 children[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Leaf {
|
||||||
|
enum {
|
||||||
|
eEmpty = -1,
|
||||||
|
eSolid = -2,
|
||||||
|
eWater = -3,
|
||||||
|
eSlime = -4,
|
||||||
|
eLava = -5,
|
||||||
|
eSky = -6,
|
||||||
|
eOrigin = -7,
|
||||||
|
eClip = -8,
|
||||||
|
eCurrent0 = -9,
|
||||||
|
eCurrent90 = -10,
|
||||||
|
eCurrent180 = -11,
|
||||||
|
eCurrent270 = -12,
|
||||||
|
eCurrentUp = -13,
|
||||||
|
eCurrentDown = -14,
|
||||||
|
eTranslucent = -15,
|
||||||
|
} contents;
|
||||||
|
/* if this is -1, no VIS data */
|
||||||
|
i32 vis_offset;
|
||||||
|
ivec3 bb_mins;
|
||||||
|
ivec3 bb_maxes;
|
||||||
|
u16 first_mark_surface_idx;
|
||||||
|
u16 n_mark_surfaces;
|
||||||
|
u8 ambient_sound_levels[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef u16 MarkSurface;
|
||||||
|
|
||||||
|
struct Edge {
|
||||||
|
u16 vertex_indices[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef i32 Surfedge;
|
||||||
|
|
||||||
|
struct Model {
|
||||||
|
vec3 bb_mins;
|
||||||
|
vec3 bb_maxes;
|
||||||
|
vec3 origin;
|
||||||
|
i32 head_node_indices[MAX_MAP_HULLS];
|
||||||
|
i32 vis_leafs;
|
||||||
|
i32 first_face_idx;
|
||||||
|
i32 n_faces;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BSP {
|
||||||
|
BSP(vk::PhysicalDevice phys_dev, vk::Device dev, const std::string& fname);
|
||||||
|
void load_vertices(const vec3& cam_pos, bool visibility_testing, const glm::mat4& view);
|
||||||
|
int determine_leaf(vec3 cam_pos);
|
||||||
|
bool determine_visibility(const Leaf& cam_leaf, const Leaf& leaf, const std::array<glm::vec4, 6>& frustum, const vec3 box_verts[8]);
|
||||||
|
int get_index_from_surfedge(int surfedge);
|
||||||
|
|
||||||
|
|
||||||
|
vk::Device dev;
|
||||||
|
Header* header;
|
||||||
|
std::string filename;
|
||||||
|
std::vector<u8> file_data;
|
||||||
|
|
||||||
|
std::vector<::std::map<::std::string, std::string>> entities;
|
||||||
|
std::vector<Plane> planes;
|
||||||
|
std::vector<MipTexture> textures;
|
||||||
|
std::vector<glm::vec3> vertices;
|
||||||
|
std::vector<glm::vec3> processed_vertices;
|
||||||
|
/* skipping vis for now */
|
||||||
|
std::vector<Node> nodes;
|
||||||
|
std::vector<TexInfo> tex_infos;
|
||||||
|
std::vector<Face> faces;
|
||||||
|
Lightmap lightmap;
|
||||||
|
std::vector<ClipNode> clip_nodes;
|
||||||
|
std::vector<Leaf> leaves;
|
||||||
|
std::vector<MarkSurface> mark_surfaces;
|
||||||
|
std::vector<Edge> edges;
|
||||||
|
std::vector<Surfedge> surfedges;
|
||||||
|
std::vector<Model> models;
|
||||||
|
|
||||||
|
std::vector<Vertex> textured_vertices;
|
||||||
|
std::unique_ptr<GeneralVertexBuffer<Vertex>> vertex_buffer;
|
||||||
|
std::unique_ptr<GraphicsPipeline> pipeline;
|
||||||
|
/* to eliminate needless re-loading*/
|
||||||
|
int last_leaf = -0x1337;
|
||||||
|
|
||||||
|
~BSP() {
|
||||||
|
vertex_buffer.reset();
|
||||||
|
pipeline.reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
#include <util/int.hpp>
|
#include <util/int.hpp>
|
||||||
|
|
||||||
|
|
||||||
float Terrain::getHeight(int32_t x, int32_t y) {
|
float Terrain::getHeight(int32_t x, int32_t y) {
|
||||||
if (x < 0)
|
if (x < 0)
|
||||||
x += 64;
|
x += 64;
|
||||||
@ -20,7 +19,7 @@ float Terrain::getHeight(int32_t x, int32_t y) {
|
|||||||
yf /= 64.0;
|
yf /= 64.0;
|
||||||
yf *= heightmap_tex->extent.height;
|
yf *= heightmap_tex->extent.height;
|
||||||
|
|
||||||
return static_cast<float>(heightmap_tex->image_data[static_cast<int>(static_cast<int>(yf) * heightmap_tex->extent.width + xf) * 4]) / 256.0f;
|
return static_cast<float>(heightmap_tex->image_data[static_cast<int>(static_cast<int>(yf) * heightmap_tex->extent.width + xf) * 4]) / 256.0f * 15.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
Terrain::Terrain(vk::PhysicalDevice phys_dev, vk::Device dev, Texture& tex) : phys_dev(phys_dev), dev(dev) {
|
Terrain::Terrain(vk::PhysicalDevice phys_dev, vk::Device dev, Texture& tex) : phys_dev(phys_dev), dev(dev) {
|
||||||
@ -35,7 +34,7 @@ Terrain::Terrain(vk::PhysicalDevice phys_dev, vk::Device dev, Texture& tex) : ph
|
|||||||
|
|
||||||
for (size_t x = 0; x < patch_size; x++)
|
for (size_t x = 0; x < patch_size; x++)
|
||||||
for (size_t y = 0; y < patch_size; y++)
|
for (size_t y = 0; y < patch_size; y++)
|
||||||
vertices[x + y*patch_size] = (Vertex {
|
vertices[x + y*patch_size] = (glTFVertex {
|
||||||
.pos = glm::vec3(
|
.pos = glm::vec3(
|
||||||
2.0f * x + 1.0f - patch_size,
|
2.0f * x + 1.0f - patch_size,
|
||||||
0.0f,
|
0.0f,
|
||||||
@ -70,20 +69,26 @@ Terrain::Terrain(vk::PhysicalDevice phys_dev, vk::Device dev, Texture& tex) : ph
|
|||||||
{ getHeight(x + 1, y - 1), getHeight(x + 1, y), getHeight(x + 1, y + 1) },
|
{ getHeight(x + 1, y - 1), getHeight(x + 1, y), getHeight(x + 1, y + 1) },
|
||||||
};
|
};
|
||||||
|
|
||||||
auto normal = glm::vec3(
|
// auto normal = glm::vec3(
|
||||||
/* x gets X sobel filter */
|
// /* x gets X sobel filter */
|
||||||
moores_heights[0][0] + 2.0f * moores_heights[0][1] + moores_heights[0][2]
|
// moores_heights[0][0] + 2.0f * moores_heights[0][1] + moores_heights[0][2]
|
||||||
- moores_heights[2][0] - 2.0f * moores_heights[2][1] - moores_heights[2][2],
|
// - moores_heights[2][0] - 2.0f * moores_heights[2][1] - moores_heights[2][2],
|
||||||
0.0,
|
// 0.0,
|
||||||
/* z gets Y sobel filter */
|
// /* z gets Y sobel filter */
|
||||||
moores_heights[0][0] + 2.0f * moores_heights[1][0] + moores_heights[2][0]
|
// moores_heights[0][0] + 2.0f * moores_heights[1][0] + moores_heights[2][0]
|
||||||
- moores_heights[0][2] - 2.0f * moores_heights[1][2] - moores_heights[2][2]
|
// - moores_heights[0][2] - 2.0f * moores_heights[1][2] - moores_heights[2][2]
|
||||||
);
|
// );
|
||||||
|
|
||||||
|
auto relx = glm::vec3(2.0, getHeight(x + 1, y)-getHeight(x, y), 0.0);
|
||||||
|
auto relz = glm::vec3(0.0, getHeight(x, y + 1)-getHeight(x, y), 2.0);
|
||||||
|
auto normal = glm::normalize(glm::cross(relz, relx));
|
||||||
|
|
||||||
|
|
||||||
/* fill in missing component, first scalar scales bump */
|
/* fill in missing component, first scalar scales bump */
|
||||||
normal.y = 0.25 * glm::sqrt(glm::abs(1.0 - normal.x*normal.x - normal.z*normal.z));
|
//normal.y = 0.25 * glm::sqrt(glm::abs(1.0 - normal.x*normal.x - normal.z*normal.z));
|
||||||
|
|
||||||
//vertices[x + y * patch_size].norm = glm::vec3(getHeight(x, y));
|
//vertices[x + y * patch_size].norm = glm::vec3(getHeight(x, y));
|
||||||
vertices[x + y * patch_size].norm = glm::normalize(normal * glm::vec3(2.0f, 1.0f, 2.0f));
|
vertices[x + y * patch_size].norm = normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
vertex_buffer = std::make_unique<VertexBuffer>(phys_dev, dev, vertices.size());
|
vertex_buffer = std::make_unique<VertexBuffer>(phys_dev, dev, vertices.size());
|
||||||
|
|||||||
@ -16,7 +16,7 @@ struct Terrain {
|
|||||||
|
|
||||||
std::unique_ptr<VertexBuffer> vertex_buffer;
|
std::unique_ptr<VertexBuffer> vertex_buffer;
|
||||||
std::unique_ptr<Buffer> index_buffer;
|
std::unique_ptr<Buffer> index_buffer;
|
||||||
std::vector<Vertex> vertices;
|
std::vector<glTFVertex> vertices;
|
||||||
std::vector<uint32_t> indices;
|
std::vector<uint32_t> indices;
|
||||||
|
|
||||||
Terrain(vk::PhysicalDevice phys_dev, vk::Device dev, Texture& hieghtmap);
|
Terrain(vk::PhysicalDevice phys_dev, vk::Device dev, Texture& hieghtmap);
|
||||||
|
|||||||
85
UI/UI.cpp
85
UI/UI.cpp
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <imgui/imgui_impl_glfw.h>
|
#include <imgui/imgui_impl_glfw.h>
|
||||||
#include <imgui/imgui_impl_vulkan.h>
|
#include <imgui/imgui_impl_vulkan.h>
|
||||||
|
#include <imgui/imgui_console.h>
|
||||||
|
|
||||||
#include <Window/Window.hpp>
|
#include <Window/Window.hpp>
|
||||||
|
|
||||||
@ -11,7 +12,27 @@
|
|||||||
|
|
||||||
#include <Scene/Camera.hpp>
|
#include <Scene/Camera.hpp>
|
||||||
|
|
||||||
UI::UI(Renderer* ren) : info{ .flycam = ren->flycam, .time = ren->time, .cam = ren->cam, .tess_factor = ren->tess_factor, .tess_edge_size = ren->tess_edge_size }, dev(ren->dev) {
|
/* this pains me to do, but its the only way :( */
|
||||||
|
Renderer* __ren;
|
||||||
|
|
||||||
|
static csys::ItemLog& operator<<(csys::ItemLog& log, ImVector<float>& vec) {
|
||||||
|
if (!vec.size())
|
||||||
|
return log << "vector<f32> {}";
|
||||||
|
log << "vector<f32> { ";
|
||||||
|
for (int i = 0; i < vec.size() - 1; i++)
|
||||||
|
log << vec[i] << ", ";
|
||||||
|
return log << vec[vec.size() - 1] << " }";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pipeline_setter(bool& v, bool in) {
|
||||||
|
if(v == in)
|
||||||
|
return;
|
||||||
|
v = in;
|
||||||
|
__ren->bsp->pipeline->rebuild(__ren->wireframe_mode, __ren->backface_culling);
|
||||||
|
}
|
||||||
|
|
||||||
|
UI::UI(Renderer* ren) : ren(ren), dev(ren->dev) {
|
||||||
|
__ren = ren;
|
||||||
IMGUI_CHECKVERSION();
|
IMGUI_CHECKVERSION();
|
||||||
ImGui::CreateContext();
|
ImGui::CreateContext();
|
||||||
|
|
||||||
@ -79,6 +100,47 @@ UI::UI(Renderer* ren) : info{ .flycam = ren->flycam, .time = ren->time, .cam =
|
|||||||
|
|
||||||
ImGui_ImplVulkan_DestroyFontUploadObjects();
|
ImGui_ImplVulkan_DestroyFontUploadObjects();
|
||||||
ImGui::StyleColorsDark();
|
ImGui::StyleColorsDark();
|
||||||
|
|
||||||
|
/* set up input so we can use the keyboard */
|
||||||
|
auto& io = ImGui::GetIO();
|
||||||
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||||
|
|
||||||
|
console = std::make_unique<ImGuiConsole>("developer console");
|
||||||
|
console->System().RegisterCommand("pause", "Pauses or unpauses the engine", [this]() {
|
||||||
|
this->ren->paused = !this->ren->paused;
|
||||||
|
console->System().Log(csys::ItemType::eINFO) << "Paused: " << (this->ren->paused? "True" : "False") << csys::endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
console->System().RegisterCommand("quit", "Quits the engine", [this]() {
|
||||||
|
this->ren->should_close = true;
|
||||||
|
console->System().Log(csys::ItemType::eINFO) << "Quitting..." << csys::endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
console->System().RegisterCommand("list-vars", "List variables accessible from developer console", [this]() {
|
||||||
|
const std::vector<std::string> names = {
|
||||||
|
"show_bboxes",
|
||||||
|
"visibility_testing",
|
||||||
|
"flycam",
|
||||||
|
"speed",
|
||||||
|
"max_fps",
|
||||||
|
"wireframe",
|
||||||
|
"backface_culling",
|
||||||
|
};
|
||||||
|
|
||||||
|
for(const auto& name : names)
|
||||||
|
console->System().Log(csys::ItemType::eINFO) << name << csys::endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
console->System().RegisterVariable("show_bboxes", ren->show_bboxes, csys::Arg<bool>("value"));
|
||||||
|
console->System().RegisterVariable("visibility_testing", ren->visibility_testing, csys::Arg<bool>("value"));
|
||||||
|
console->System().RegisterVariable("flycam", ren->flycam, csys::Arg<bool>("value"));
|
||||||
|
console->System().RegisterVariable("speed", ren->speed, csys::Arg<float>("value"));
|
||||||
|
console->System().RegisterVariable("max_fps", ren->max_fps, csys::Arg<float>("value"));
|
||||||
|
|
||||||
|
console->System().RegisterVariable("wireframe", ren->wireframe_mode, pipeline_setter);
|
||||||
|
console->System().RegisterVariable("backface_culling", ren->backface_culling, pipeline_setter);
|
||||||
|
|
||||||
|
console->System().Log(csys::ItemType::eINFO) << "Welcome to Pleascach!" << csys::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UI::newFrame() {
|
void UI::newFrame() {
|
||||||
@ -87,13 +149,15 @@ void UI::newFrame() {
|
|||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
|
|
||||||
ImGui::SetNextWindowBgAlpha(0.5f);
|
ImGui::SetNextWindowBgAlpha(0.5f);
|
||||||
ImGui::Begin("Rendering Info", nullptr);
|
ImGui::Begin("Rendering Info", nullptr, ImGuiWindowFlags_::ImGuiWindowFlags_NoFocusOnAppearing);
|
||||||
|
|
||||||
ImGui::Text("FPS: %f", info.fps);
|
ImGui::Text("# of Indices: %zu", ren->n_indices);
|
||||||
ImGui::Text("Time: %f", info.time);
|
ImGui::Text("FPS: %f", ren->fps);
|
||||||
ImGui::Checkbox("Fly Camera", &info.flycam);
|
ImGui::Text("Time: %f", ren->time);
|
||||||
ImGui::SliderFloat("Tessellation Factor", &info.tess_factor, 0.1, 10.0);
|
ImGui::Text("Speed: %f", ren->speed * ren->frametime / 8.0);
|
||||||
ImGui::SliderFloat("Edge Size", &info.tess_edge_size, 0.0, 40.0);
|
|
||||||
|
if(ren->in_menu)
|
||||||
|
console->Draw();
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
@ -104,9 +168,12 @@ void UI::render(vk::CommandBuffer cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UI::~UI() {
|
UI::~UI() {
|
||||||
|
console.reset();
|
||||||
dev.destroyDescriptorPool(desc_pool);
|
dev.destroyDescriptorPool(desc_pool);
|
||||||
|
|
||||||
ImGui_ImplVulkan_Shutdown();
|
ImGui_ImplVulkan_Shutdown();
|
||||||
ImGui_ImplGlfw_Shutdown();
|
ImGui_ImplGlfw_Shutdown();
|
||||||
ImGui::DestroyContext();
|
|
||||||
|
// for whatever reason, this segfaults with a read to -1 when it tries saving characteristics.
|
||||||
|
// TOOD: Debug
|
||||||
|
// ImGui::DestroyContext();
|
||||||
}
|
}
|
||||||
17
UI/UI.hpp
17
UI/UI.hpp
@ -5,25 +5,22 @@
|
|||||||
|
|
||||||
#include <Renderer/CommandBuffer.hpp>
|
#include <Renderer/CommandBuffer.hpp>
|
||||||
|
|
||||||
|
#include <imgui/imgui_console.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
struct Renderer;
|
struct Renderer;
|
||||||
struct Camera;
|
struct Camera;
|
||||||
|
|
||||||
struct UI {
|
struct UI {
|
||||||
struct UI_Info {
|
Renderer* ren;
|
||||||
float fps = 0.0;
|
|
||||||
bool& flycam;
|
|
||||||
float& time;
|
|
||||||
/* camera stuff */
|
|
||||||
Camera& cam;
|
|
||||||
float& tess_factor;
|
|
||||||
float& tess_edge_size;
|
|
||||||
} info;
|
|
||||||
|
|
||||||
vk::Device dev;
|
vk::Device dev;
|
||||||
vk::DescriptorPool desc_pool;
|
vk::DescriptorPool desc_pool;
|
||||||
|
|
||||||
UI(Renderer* ren);
|
UI(Renderer* ren);
|
||||||
|
|
||||||
|
|
||||||
|
std::unique_ptr<ImGuiConsole> console;
|
||||||
void newFrame();
|
void newFrame();
|
||||||
void render(vk::CommandBuffer cmd);
|
void render(vk::CommandBuffer cmd);
|
||||||
|
|
||||||
|
|||||||
BIN
assets/maps/dmc.bsp
Executable file
BIN
assets/maps/dmc.bsp
Executable file
Binary file not shown.
BIN
assets/maps/git.bsp
Normal file
BIN
assets/maps/git.bsp
Normal file
Binary file not shown.
BIN
assets/maps/hl1.bsp
Normal file
BIN
assets/maps/hl1.bsp
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
assets/shaders/bsp.frag.spv
Normal file
BIN
assets/shaders/bsp.frag.spv
Normal file
Binary file not shown.
BIN
assets/shaders/bsp.vert.spv
Normal file
BIN
assets/shaders/bsp.vert.spv
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,44 +0,0 @@
|
|||||||
#version 450 core
|
|
||||||
|
|
||||||
layout (triangles) in;
|
|
||||||
layout (triangle_strip) out;
|
|
||||||
layout (max_vertices = 6) out;
|
|
||||||
|
|
||||||
layout (location = 0) in vec3 norm[];
|
|
||||||
layout (location = 1) in vec2 texCoord[];
|
|
||||||
|
|
||||||
layout (location = 0) out vec3 _norm;
|
|
||||||
layout (location = 1) out vec2 _texCoord;
|
|
||||||
|
|
||||||
layout (set = 0, binding = 0) uniform Matrices {
|
|
||||||
mat4 mvp;
|
|
||||||
float time;
|
|
||||||
};
|
|
||||||
|
|
||||||
vec4 explode(vec4 pos, vec3 n) {
|
|
||||||
float mag = 2.0;
|
|
||||||
vec3 dir = n * (sin(time/10.0)-3.0)/10.0 * mag;
|
|
||||||
return pos + vec4(dir, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void main(void) {
|
|
||||||
|
|
||||||
if (time < 3.0) {
|
|
||||||
for(int i = 0; i < gl_in.length(); i++) {
|
|
||||||
gl_Position = mvp * gl_in[i].gl_Position;
|
|
||||||
_norm = norm[i];
|
|
||||||
_texCoord = texCoord[i];
|
|
||||||
EmitVertex();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vec3 n = norm[0] + norm[1] + norm[2];
|
|
||||||
n/=3;
|
|
||||||
for(int i = 0; i < gl_in.length(); i++) {
|
|
||||||
gl_Position = mvp * explode(gl_in[i].gl_Position*abs(cos(time)), n);
|
|
||||||
_texCoord = texCoord[i];
|
|
||||||
_norm = n;
|
|
||||||
EmitVertex();
|
|
||||||
}
|
|
||||||
EndPrimitive();
|
|
||||||
}
|
|
||||||
Binary file not shown.
@ -13,14 +13,21 @@ layout (location = 1) out vec2 _texCoord;
|
|||||||
layout (location = 2) out vec3 _pos;
|
layout (location = 2) out vec3 _pos;
|
||||||
|
|
||||||
layout (set = 0, binding = 0) uniform Matrices {
|
layout (set = 0, binding = 0) uniform Matrices {
|
||||||
mat4 mvp;
|
mat4 view;
|
||||||
|
mat4 proj;
|
||||||
float time;
|
float time;
|
||||||
|
vec3 cam_pos;
|
||||||
|
vec3 cam_dir;
|
||||||
|
vec4 frustum[6];
|
||||||
|
vec2 viewport;
|
||||||
|
float tess_factor;
|
||||||
|
float tess_edge_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
for(int i = 0; i < gl_in.length(); i++) {
|
for(int i = 0; i < gl_in.length(); i++) {
|
||||||
gl_Position = mvp * gl_in[i].gl_Position;
|
gl_Position = proj * view * gl_in[i].gl_Position;
|
||||||
_norm = norm[i];
|
_norm = norm[i];
|
||||||
_texCoord = texCoord[i];
|
_texCoord = texCoord[i];
|
||||||
_pos = pos[i];
|
_pos = pos[i];
|
||||||
BIN
assets/shaders/lighting/fraglight.geom.spv
Normal file
BIN
assets/shaders/lighting/fraglight.geom.spv
Normal file
Binary file not shown.
@ -8,8 +8,15 @@ layout (location = 1) out vec2 texCoord;
|
|||||||
layout (location = 2) out vec3 pos;
|
layout (location = 2) out vec3 pos;
|
||||||
|
|
||||||
layout (set = 0, binding = 0) uniform Matrices {
|
layout (set = 0, binding = 0) uniform Matrices {
|
||||||
mat4 mvp;
|
mat4 view;
|
||||||
|
mat4 proj;
|
||||||
float time;
|
float time;
|
||||||
|
vec3 cam_pos;
|
||||||
|
vec3 cam_dir;
|
||||||
|
vec4 frustum[6];
|
||||||
|
vec2 viewport;
|
||||||
|
float tess_factor;
|
||||||
|
float tess_edge_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
BIN
assets/shaders/lighting/fraglight.vert.spv
Normal file
BIN
assets/shaders/lighting/fraglight.vert.spv
Normal file
Binary file not shown.
BIN
assets/shaders/lighting/gooch.frag.spv
Normal file
BIN
assets/shaders/lighting/gooch.frag.spv
Normal file
Binary file not shown.
BIN
assets/shaders/lighting/lambert.frag.spv
Normal file
BIN
assets/shaders/lighting/lambert.frag.spv
Normal file
Binary file not shown.
BIN
assets/shaders/lighting/trace.frag.spv
Normal file
BIN
assets/shaders/lighting/trace.frag.spv
Normal file
Binary file not shown.
34
assets/shaders/map/bsp.frag
Normal file
34
assets/shaders/map/bsp.frag
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#version 460 core
|
||||||
|
|
||||||
|
layout (location = 0) in vec4 color;
|
||||||
|
layout (location = 1) in vec2 texCoord;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 FragColor;
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform Matrices {
|
||||||
|
mat4 view;
|
||||||
|
mat4 proj;
|
||||||
|
float time;
|
||||||
|
vec3 cam_pos;
|
||||||
|
vec3 cam_dir;
|
||||||
|
vec4 frustum[6];
|
||||||
|
vec2 viewport;
|
||||||
|
float tess_factor;
|
||||||
|
float tess_edge_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout (set = 0, binding = 1) uniform sampler2D tex;
|
||||||
|
|
||||||
|
vec3 tex_map(vec2 coords) {
|
||||||
|
return vec3(
|
||||||
|
1-abs(cos(normalize(texCoord.xxy)))
|
||||||
|
// 0.0,
|
||||||
|
// sin(texCoord.y)
|
||||||
|
// sin(texCoord.x/texCoord.y)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
FragColor.xyz = tex_map(texCoord);
|
||||||
|
FragColor.w = 1.0;
|
||||||
|
}
|
||||||
BIN
assets/shaders/map/bsp.frag.spv
Normal file
BIN
assets/shaders/map/bsp.frag.spv
Normal file
Binary file not shown.
35
assets/shaders/map/bsp.vert
Normal file
35
assets/shaders/map/bsp.vert
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#version 460 core
|
||||||
|
layout (location = 0) in vec3 aPos;
|
||||||
|
layout (location = 1) in vec2 aTexCoord;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 color;
|
||||||
|
layout (location = 1) out vec2 texCoord;
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform Matrices {
|
||||||
|
mat4 view;
|
||||||
|
mat4 proj;
|
||||||
|
float time;
|
||||||
|
vec3 cam_pos;
|
||||||
|
vec3 cam_dir;
|
||||||
|
vec4 frustum[6];
|
||||||
|
vec2 viewport;
|
||||||
|
float tess_factor;
|
||||||
|
float tess_edge_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
vec4 unpackABGR(uint packedABGR) {
|
||||||
|
float scale = 1.0 / 255.0;
|
||||||
|
float a = float((packedABGR >> 24) & 0xFF) * scale;
|
||||||
|
float b = float((packedABGR >> 16) & 0xFF) * scale;
|
||||||
|
float g = float((packedABGR >> 8) & 0xFF) * scale;
|
||||||
|
float r = float(packedABGR & 0xFF) * scale;
|
||||||
|
return vec4(r,g,b,a);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
gl_Position = proj * view * vec4(aPos, 1.0);
|
||||||
|
|
||||||
|
color = vec4(0.0);
|
||||||
|
texCoord = aTexCoord;
|
||||||
|
}
|
||||||
BIN
assets/shaders/map/bsp.vert.spv
Normal file
BIN
assets/shaders/map/bsp.vert.spv
Normal file
Binary file not shown.
@ -5,8 +5,15 @@ layout (location = 1) in vec2 texCoord;
|
|||||||
layout (location = 0) out vec4 FragColor;
|
layout (location = 0) out vec4 FragColor;
|
||||||
|
|
||||||
layout (set = 0, binding = 0) uniform Matrices {
|
layout (set = 0, binding = 0) uniform Matrices {
|
||||||
mat4 mvp;
|
mat4 view;
|
||||||
|
mat4 proj;
|
||||||
float time;
|
float time;
|
||||||
|
vec3 cam_pos;
|
||||||
|
vec3 cam_dir;
|
||||||
|
vec4 frustum[6];
|
||||||
|
vec2 viewport;
|
||||||
|
float tess_factor;
|
||||||
|
float tess_edge_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
layout (set = 0, binding = 1) uniform sampler2D tex;
|
layout (set = 0, binding = 1) uniform sampler2D tex;
|
||||||
BIN
assets/shaders/mesh/basic.frag.spv
Normal file
BIN
assets/shaders/mesh/basic.frag.spv
Normal file
Binary file not shown.
@ -11,14 +11,21 @@ layout (location = 0) out vec3 _norm;
|
|||||||
layout (location = 1) out vec2 _texCoord;
|
layout (location = 1) out vec2 _texCoord;
|
||||||
|
|
||||||
layout (set = 0, binding = 0) uniform Matrices {
|
layout (set = 0, binding = 0) uniform Matrices {
|
||||||
mat4 mvp;
|
mat4 view;
|
||||||
|
mat4 proj;
|
||||||
float time;
|
float time;
|
||||||
|
vec3 cam_pos;
|
||||||
|
vec3 cam_dir;
|
||||||
|
vec4 frustum[6];
|
||||||
|
vec2 viewport;
|
||||||
|
float tess_factor;
|
||||||
|
float tess_edge_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
for(int i = 0; i < gl_in.length(); i++) {
|
for(int i = 0; i < gl_in.length(); i++) {
|
||||||
gl_Position = mvp * gl_in[i].gl_Position;
|
gl_Position = proj * view * gl_in[i].gl_Position;
|
||||||
_norm = norm[i];
|
_norm = norm[i];
|
||||||
_texCoord = texCoord[i];
|
_texCoord = texCoord[i];
|
||||||
EmitVertex();
|
EmitVertex();
|
||||||
BIN
assets/shaders/mesh/basic.geom.spv
Normal file
BIN
assets/shaders/mesh/basic.geom.spv
Normal file
Binary file not shown.
@ -7,12 +7,19 @@ layout (location = 0) out vec3 norm;
|
|||||||
layout (location = 1) out vec2 texCoord;
|
layout (location = 1) out vec2 texCoord;
|
||||||
|
|
||||||
layout (set = 0, binding = 0) uniform Matrices {
|
layout (set = 0, binding = 0) uniform Matrices {
|
||||||
mat4 mvp;
|
mat4 view;
|
||||||
|
mat4 proj;
|
||||||
float time;
|
float time;
|
||||||
|
vec3 cam_pos;
|
||||||
|
vec3 cam_dir;
|
||||||
|
vec4 frustum[6];
|
||||||
|
vec2 viewport;
|
||||||
|
float tess_factor;
|
||||||
|
float tess_edge_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = vec4(aPos + (vec3(10.0) * gl_InstanceIndex), 1.0);
|
gl_Position = proj * view * vec4(aPos, 1.0);
|
||||||
texCoord = aTexCoord;
|
texCoord = aTexCoord;
|
||||||
norm = aNorm;
|
norm = aNorm;
|
||||||
}
|
}
|
||||||
BIN
assets/shaders/mesh/basic.vert.spv
Normal file
BIN
assets/shaders/mesh/basic.vert.spv
Normal file
Binary file not shown.
@ -11,8 +11,15 @@ layout (location = 0) out vec3 _norm;
|
|||||||
layout (location = 1) out vec2 _texCoord;
|
layout (location = 1) out vec2 _texCoord;
|
||||||
|
|
||||||
layout (set = 0, binding = 0) uniform Matrices {
|
layout (set = 0, binding = 0) uniform Matrices {
|
||||||
mat4 mvp;
|
mat4 view;
|
||||||
|
mat4 proj;
|
||||||
float time;
|
float time;
|
||||||
|
vec3 cam_pos;
|
||||||
|
vec3 cam_dir;
|
||||||
|
vec4 frustum[6];
|
||||||
|
vec2 viewport;
|
||||||
|
float tess_factor;
|
||||||
|
float tess_edge_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
vec4 explode(vec4 pos, vec3 n) {
|
vec4 explode(vec4 pos, vec3 n) {
|
||||||
@ -24,7 +31,7 @@ vec4 explode(vec4 pos, vec3 n) {
|
|||||||
void main(void) {
|
void main(void) {
|
||||||
if (time < 3.0) {
|
if (time < 3.0) {
|
||||||
for(int i = 0; i < gl_in.length(); i++) {
|
for(int i = 0; i < gl_in.length(); i++) {
|
||||||
gl_Position = mvp * gl_in[i].gl_Position;
|
gl_Position = proj * view * gl_in[i].gl_Position;
|
||||||
_norm = norm[i];
|
_norm = norm[i];
|
||||||
_texCoord = texCoord[i];
|
_texCoord = texCoord[i];
|
||||||
EmitVertex();
|
EmitVertex();
|
||||||
@ -35,7 +42,7 @@ void main(void) {
|
|||||||
vec3 n = norm[0] + norm[1] + norm[2];
|
vec3 n = norm[0] + norm[1] + norm[2];
|
||||||
n/=3;
|
n/=3;
|
||||||
for(int i = 0; i < gl_in.length(); i++) {
|
for(int i = 0; i < gl_in.length(); i++) {
|
||||||
gl_Position = mvp * explode(gl_in[i].gl_Position, n);
|
gl_Position = proj * view * explode(gl_in[i].gl_Position, n);
|
||||||
_texCoord = texCoord[i];
|
_texCoord = texCoord[i];
|
||||||
_norm = n;
|
_norm = n;
|
||||||
EmitVertex();
|
EmitVertex();
|
||||||
BIN
assets/shaders/mesh/explode.geom.spv
Normal file
BIN
assets/shaders/mesh/explode.geom.spv
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -23,7 +23,7 @@ void main() {
|
|||||||
/* extract L (light direction) from view matrix */
|
/* extract L (light direction) from view matrix */
|
||||||
vec3 L = -cam_dir;
|
vec3 L = -cam_dir;
|
||||||
float r = length(cam_pos-pos);
|
float r = length(cam_pos-pos);
|
||||||
float t = clamp(dot(L, norm), 0.0, 1.0) * 20.0/(r*r);
|
float t = clamp(dot(L, norm), 0.0, 1.0) * 200.0/(r*r);
|
||||||
|
|
||||||
FragColor = vec4(t*norm, 1.0);
|
FragColor = vec4(vec3(t), 1.0);
|
||||||
}
|
}
|
||||||
BIN
assets/shaders/terrain/terrain.frag.spv
Normal file
BIN
assets/shaders/terrain/terrain.frag.spv
Normal file
Binary file not shown.
BIN
assets/shaders/terrain/terrain.tesc.spv
Normal file
BIN
assets/shaders/terrain/terrain.tesc.spv
Normal file
Binary file not shown.
@ -42,7 +42,7 @@ void main() {
|
|||||||
vec4 pos2 = mix(gl_in[3].gl_Position, gl_in[2].gl_Position, gl_TessCoord.x);
|
vec4 pos2 = mix(gl_in[3].gl_Position, gl_in[2].gl_Position, gl_TessCoord.x);
|
||||||
|
|
||||||
vec4 fpos = mix(pos1, pos2, gl_TessCoord.y);
|
vec4 fpos = mix(pos1, pos2, gl_TessCoord.y);
|
||||||
fpos.y += 15.0 * texture(heightmap, _texCoord).r;
|
fpos.y = 15.0 * texture(heightmap, _texCoord).r;
|
||||||
|
|
||||||
_pos = fpos.xyz;
|
_pos = fpos.xyz;
|
||||||
gl_Position = proj * view * fpos;
|
gl_Position = proj * view * fpos;
|
||||||
BIN
assets/shaders/terrain/terrain.tese.spv
Normal file
BIN
assets/shaders/terrain/terrain.tese.spv
Normal file
Binary file not shown.
BIN
assets/shaders/terrain/terrain.vert.spv
Normal file
BIN
assets/shaders/terrain/terrain.vert.spv
Normal file
Binary file not shown.
14
assets/shaders/utils/box/box.frag
Normal file
14
assets/shaders/utils/box/box.frag
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#version 460 core
|
||||||
|
layout (location = 0) flat in uint id;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 FragColor;
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform Matrices {
|
||||||
|
mat4 view;
|
||||||
|
mat4 proj;
|
||||||
|
float time;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
FragColor = vec4(0.0, 0.0, 1.0, 0.2);
|
||||||
|
}
|
||||||
42
assets/shaders/utils/box/box.geom
Normal file
42
assets/shaders/utils/box/box.geom
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#version 450 core
|
||||||
|
|
||||||
|
layout (points) in;
|
||||||
|
layout (triangle_strip, max_vertices = 14) out;
|
||||||
|
|
||||||
|
layout (location = 0) in vec3 mins[];
|
||||||
|
layout (location = 1) in vec3 maxes[];
|
||||||
|
layout (location = 2) in uint id[];
|
||||||
|
|
||||||
|
layout (location = 0) out uint _id;
|
||||||
|
|
||||||
|
layout (set = 0, binding = 0) uniform Matrices {
|
||||||
|
mat4 view;
|
||||||
|
mat4 proj;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
int cube_vertices[14] = {
|
||||||
|
0, 1, 2, 3, 7, 6, 5, 4, 0, 1, 5, 6, 2, 3
|
||||||
|
};
|
||||||
|
|
||||||
|
for(int i = 0; i < gl_in.length(); i++) {
|
||||||
|
vec3 vertices[] = {
|
||||||
|
vec3(mins[i].x, mins[i].y, mins[i].z),
|
||||||
|
vec3(mins[i].x, mins[i].y, maxes[i].z),
|
||||||
|
vec3(mins[i].x, maxes[i].y, mins[i].z),
|
||||||
|
vec3(mins[i].x, maxes[i].y, maxes[i].z),
|
||||||
|
vec3(maxes[i].x, mins[i].y, mins[i].z),
|
||||||
|
vec3(maxes[i].x, mins[i].y, maxes[i].z),
|
||||||
|
vec3(maxes[i].x, maxes[i].y, mins[i].z),
|
||||||
|
vec3(maxes[i].x, maxes[i].y, maxes[i].z),
|
||||||
|
};
|
||||||
|
|
||||||
|
_id = id[i];
|
||||||
|
for(int j = 0; j < 14; j++) {
|
||||||
|
gl_Position = proj*view*vec4(vertices[cube_vertices[j]], 1.0);
|
||||||
|
EmitVertex();
|
||||||
|
}
|
||||||
|
EndPrimitive();
|
||||||
|
}
|
||||||
|
}
|
||||||
16
assets/shaders/utils/box/box.vert
Normal file
16
assets/shaders/utils/box/box.vert
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#version 460 core
|
||||||
|
/* simple passthrough */
|
||||||
|
|
||||||
|
layout (location = 0) in vec3 aMins;
|
||||||
|
layout (location = 1) in vec3 aMaxes;
|
||||||
|
layout (location = 2) in uint aId;
|
||||||
|
|
||||||
|
layout (location = 0) out vec3 mins;
|
||||||
|
layout (location = 1) out vec3 maxes;
|
||||||
|
layout (location = 2) out uint id;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
mins = aMins;
|
||||||
|
maxes = aMaxes;
|
||||||
|
id = aId;
|
||||||
|
}
|
||||||
BIN
bsp_capture.png
Normal file
BIN
bsp_capture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 547 KiB |
69
include/csys/api.h
Normal file
69
include/csys/api.h
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#ifndef CSYS_API_H
|
||||||
|
#define CSYS_API_H
|
||||||
|
|
||||||
|
#ifdef CSYS_COMPILED_LIB
|
||||||
|
# undef CSYS_HEADER_ONLY
|
||||||
|
# define CSYS_INLINE
|
||||||
|
|
||||||
|
# ifdef CSYS_SHARED_LIB
|
||||||
|
// Windows Shared Library.
|
||||||
|
# if defined(_WIN32)
|
||||||
|
# ifdef csys_EXPORTS
|
||||||
|
# define CSYS_API __declspec(dllexport)
|
||||||
|
# else
|
||||||
|
# define CSYS_API __declspec(dllimport)
|
||||||
|
# endif
|
||||||
|
// Linux shared library.
|
||||||
|
# else
|
||||||
|
# ifdef csys_EXPORTS
|
||||||
|
# define CSYS_API __attribute__((visibility("default")))
|
||||||
|
# else
|
||||||
|
# define CSYS_API __attribute__((visibility("default")))
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
|
# else
|
||||||
|
# define CSYS_API
|
||||||
|
# endif
|
||||||
|
|
||||||
|
// No export.
|
||||||
|
# ifndef CSYS_NO_EXPORT
|
||||||
|
# if defined(_WIN32)
|
||||||
|
# define CSYS_NO_EXPORT
|
||||||
|
# else
|
||||||
|
# define CSYS_NO_EXPORT __attribute__((visibility("hidden")))
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
|
|
||||||
|
#else
|
||||||
|
# define CSYS_API
|
||||||
|
# define CSYS_NO_EXPORT
|
||||||
|
# define CSYS_HEADER_ONLY
|
||||||
|
# define CSYS_INLINE inline
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
#define CSYS_DEPRECATED __attribute__((deprecated))
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
#define CSYS_DEPRECATED __declspec(deprecated)
|
||||||
|
#else
|
||||||
|
#define CSYS_DEPRECATED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CSYS_DEPRECATED_EXPORT
|
||||||
|
# define CSYS_DEPRECATED_EXPORT CSYS_API CSYS_DEPRECATED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CSYS_DEPRECATED_NO_EXPORT
|
||||||
|
# define CSYS_DEPRECATED_NO_EXPORT CSYS_NO_EXPORT CSYS_DEPRECATED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if 0 /* DEFINE_NO_DEPRECATED */
|
||||||
|
# ifndef CSYS_NO_DEPRECATED
|
||||||
|
# define CSYS_NO_DEPRECATED
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* CSYS_API_H */
|
||||||
516
include/csys/argument_parser.h
Normal file
516
include/csys/argument_parser.h
Normal file
@ -0,0 +1,516 @@
|
|||||||
|
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#ifndef CSYS_ARGUMENT_PARSER_H
|
||||||
|
#define CSYS_ARGUMENT_PARSER_H
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "csys/api.h"
|
||||||
|
#include "csys/string.h"
|
||||||
|
#include "csys/exceptions.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace csys
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
inline const std::string_view s_Reserved("\\[]\""); //!< All the reserved chars
|
||||||
|
inline constexpr char s_ErrMsgReserved[] = "Reserved chars '\\, [, ], \"' must be escaped with \\"; //!< Error message for reserved chars
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Helper class to check for parsing arguments that involve checking for reserved chars
|
||||||
|
*/
|
||||||
|
struct CSYS_API Reserved
|
||||||
|
{
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Checks if the current char 'c' is the escaping char
|
||||||
|
* \param c
|
||||||
|
* Char to compare against with the escaping char
|
||||||
|
* \return
|
||||||
|
* Returns true if the char 'c' is the escaping char
|
||||||
|
*/
|
||||||
|
static inline bool IsEscapeChar(char c)
|
||||||
|
{ return c == '\\'; }
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Checks if the current char is reserved
|
||||||
|
* \param c
|
||||||
|
* Char to check
|
||||||
|
* \return
|
||||||
|
* Returns true if the current char 'c' is reserved
|
||||||
|
*/
|
||||||
|
static inline bool IsReservedChar(char c)
|
||||||
|
{
|
||||||
|
for (auto rc : s_Reserved) if (c == rc) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Checks if a char is escaping another
|
||||||
|
* \param input
|
||||||
|
* Chars to check for it escaping
|
||||||
|
* \param pos
|
||||||
|
* Char that is escaping
|
||||||
|
* \return
|
||||||
|
* Returns true if the current char is the escaping char and is escaping
|
||||||
|
*/
|
||||||
|
static inline bool IsEscaping(std::string &input, size_t pos)
|
||||||
|
{
|
||||||
|
return pos < input.size() - 1 && IsEscapeChar(input[pos]) && IsReservedChar(input[pos + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Checks if a certain char is being escaped
|
||||||
|
* \param input
|
||||||
|
* Chars to check
|
||||||
|
* \param pos
|
||||||
|
* The current char to check if its being escaped
|
||||||
|
* \return
|
||||||
|
* Returns true if the current char at 'pos' is being escaped
|
||||||
|
*/
|
||||||
|
static inline bool IsEscaped(std::string &input, size_t pos)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
// Go through checking if the prev char is getting escaped and toggle between true and false
|
||||||
|
// Edge case is escaping the escaping char before another
|
||||||
|
for (size_t i = pos; i > 0; --i)
|
||||||
|
if (IsReservedChar(input[i]) && IsEscapeChar(input[i - 1]))
|
||||||
|
result = !result;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete unwanted operations
|
||||||
|
Reserved() = delete;
|
||||||
|
~Reserved() = delete;
|
||||||
|
Reserved(Reserved&&) = delete;
|
||||||
|
Reserved(const Reserved&) = delete;
|
||||||
|
Reserved& operator=(Reserved&&) = delete;
|
||||||
|
Reserved& operator=(const Reserved&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Used for parsing arguments of different types
|
||||||
|
* \tparam T
|
||||||
|
* Argument type
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
struct CSYS_API ArgumentParser
|
||||||
|
{
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Constructor that parses the argument and sets value T
|
||||||
|
* \param input
|
||||||
|
* Command line set of arguments to be parsed
|
||||||
|
* \param start
|
||||||
|
* Start in 'input' to where this argument should be parsed from
|
||||||
|
*/
|
||||||
|
inline ArgumentParser(String &input, size_t &start);
|
||||||
|
|
||||||
|
T m_Value; //!< Value of parsed argument
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Macro for template specialization, used for cleaner code reuse
|
||||||
|
*/
|
||||||
|
#define ARG_PARSE_BASE_SPEC(TYPE) \
|
||||||
|
template<> \
|
||||||
|
struct CSYS_API ArgumentParser<TYPE> \
|
||||||
|
{ \
|
||||||
|
inline ArgumentParser(String &input, size_t &start); \
|
||||||
|
TYPE m_Value = 0; \
|
||||||
|
}; \
|
||||||
|
inline ArgumentParser<TYPE>::ArgumentParser(String &input, size_t &start)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Macro for getting the sub-string within a range, used for readability
|
||||||
|
*/
|
||||||
|
#define ARG_PARSE_SUBSTR(range) input.m_String.substr(range.first, range.second - range.first)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Macro for build-int types that already have functions in the stl for parsing them from a string
|
||||||
|
*/
|
||||||
|
#define ARG_PARSE_GENERAL_SPEC(TYPE, TYPE_NAME, FUNCTION) \
|
||||||
|
ARG_PARSE_BASE_SPEC(TYPE) \
|
||||||
|
{ \
|
||||||
|
auto range = input.NextPoi(start); \
|
||||||
|
try \
|
||||||
|
{ \
|
||||||
|
m_Value = (TYPE)FUNCTION(ARG_PARSE_SUBSTR(range), &range.first); \
|
||||||
|
} \
|
||||||
|
catch (const std::out_of_range&) \
|
||||||
|
{ \
|
||||||
|
throw Exception(std::string("Argument too large for ") + TYPE_NAME, \
|
||||||
|
input.m_String.substr(range.first, range.second)); \
|
||||||
|
} \
|
||||||
|
catch (const std::invalid_argument&) \
|
||||||
|
{ \
|
||||||
|
throw Exception(std::string("Missing or invalid ") + TYPE_NAME + " argument", \
|
||||||
|
input.m_String.substr(range.first, range.second)); } \
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for string argument parsing
|
||||||
|
*/
|
||||||
|
ARG_PARSE_BASE_SPEC(csys::String)
|
||||||
|
{
|
||||||
|
m_Value.m_String.clear(); // Empty string before using
|
||||||
|
|
||||||
|
// Lambda for getting a word from the string and checking for reserved chars
|
||||||
|
static auto GetWord = [](std::string &str, size_t start, size_t end)
|
||||||
|
{
|
||||||
|
// For issues with reserved chars
|
||||||
|
static std::string invalid_chars;
|
||||||
|
invalid_chars.clear();
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
|
||||||
|
// Go through the str from start to end
|
||||||
|
for (size_t i = start; i < end; ++i)
|
||||||
|
// general case, not reserved char
|
||||||
|
if (!Reserved::IsReservedChar(str[i]))
|
||||||
|
result.push_back(str[i]);
|
||||||
|
// is a reserved char
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// check for \ char and if its escaping
|
||||||
|
if (Reserved::IsEscapeChar(str[i]) && Reserved::IsEscaping(str, i))
|
||||||
|
result.push_back(str[++i]);
|
||||||
|
// reserved char but not being escaped
|
||||||
|
else
|
||||||
|
throw Exception(s_ErrMsgReserved, str.substr(start, end - start));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Go to the start of the string argument
|
||||||
|
auto range = input.NextPoi(start);
|
||||||
|
|
||||||
|
// If its a single string
|
||||||
|
if (input.m_String[range.first] != '"')
|
||||||
|
m_Value = GetWord(input.m_String, range.first, range.second);
|
||||||
|
// Multi word string
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++range.first; // move past the first "
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// Get the next non-escaped "
|
||||||
|
range.second = input.m_String.find('"', range.first);
|
||||||
|
while (range.second != std::string::npos && Reserved::IsEscaped(input.m_String, range.second))
|
||||||
|
range.second = input.m_String.find('"', range.second + 1);
|
||||||
|
|
||||||
|
// Check for closing "
|
||||||
|
if (range.second == std::string::npos)
|
||||||
|
{
|
||||||
|
range.second = input.m_String.size();
|
||||||
|
throw Exception("Could not find closing '\"'", ARG_PARSE_SUBSTR(range));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add word to already existing string
|
||||||
|
m_Value.m_String += GetWord(input.m_String, range.first, range.second);
|
||||||
|
|
||||||
|
// Go to next word
|
||||||
|
range.first = range.second + 1;
|
||||||
|
|
||||||
|
// End of string check
|
||||||
|
if (range.first < input.m_String.size() && !std::isspace(input.m_String[range.first]))
|
||||||
|
{
|
||||||
|
// joining two strings together
|
||||||
|
if (input.m_String[range.first] == '"')
|
||||||
|
++range.first;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// End of input
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finished parsing
|
||||||
|
start = range.second + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for boolean argument parsing
|
||||||
|
*/
|
||||||
|
ARG_PARSE_BASE_SPEC(bool)
|
||||||
|
{
|
||||||
|
// Error messages
|
||||||
|
static const char *s_err_msg = "Missing or invalid boolean argument";
|
||||||
|
static const char *s_false = "false";
|
||||||
|
static const char *s_true = "true";
|
||||||
|
|
||||||
|
// Get argument
|
||||||
|
auto range = input.NextPoi(start);
|
||||||
|
|
||||||
|
// check if the length is between the len of "true" and "false"
|
||||||
|
input.m_String[range.first] = char(std::tolower(input.m_String[range.first]));
|
||||||
|
|
||||||
|
// true branch
|
||||||
|
if (range.second - range.first == 4 && input.m_String[range.first] == 't')
|
||||||
|
{
|
||||||
|
// Go through comparing grabbed arg to "true" char by char, bail if not the same
|
||||||
|
for (size_t i = range.first + 1; i < range.second; ++i)
|
||||||
|
if ((input.m_String[i] = char(std::tolower(input.m_String[i]))) != s_true[i - range.first])
|
||||||
|
throw Exception(s_err_msg + std::string(", expected true"), ARG_PARSE_SUBSTR(range));
|
||||||
|
m_Value = true;
|
||||||
|
}
|
||||||
|
// false branch
|
||||||
|
else if (range.second - range.first == 5 && input.m_String[range.first] == 'f')
|
||||||
|
{
|
||||||
|
// Go through comparing grabbed arg to "false" char by char, bail if not the same
|
||||||
|
for (size_t i = range.first + 1; i < range.second; ++i)
|
||||||
|
if ((input.m_String[i] = char(std::tolower(input.m_String[i]))) != s_false[i - range.first])
|
||||||
|
throw Exception(s_err_msg + std::string(", expected false"), ARG_PARSE_SUBSTR(range));
|
||||||
|
m_Value = false;
|
||||||
|
}
|
||||||
|
// anything else, not true or false
|
||||||
|
else
|
||||||
|
throw Exception(s_err_msg, ARG_PARSE_SUBSTR(range));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for char argument parsing
|
||||||
|
*/
|
||||||
|
ARG_PARSE_BASE_SPEC(char)
|
||||||
|
{
|
||||||
|
// Grab the argument
|
||||||
|
auto range = input.NextPoi(start);
|
||||||
|
size_t len = range.second - range.first;
|
||||||
|
|
||||||
|
// Check if its 3 or more letters
|
||||||
|
if (len > 2 || len <= 0)
|
||||||
|
throw Exception("Too many or no chars were given", ARG_PARSE_SUBSTR(range));
|
||||||
|
// potential reserved char
|
||||||
|
else if (len == 2)
|
||||||
|
{
|
||||||
|
// Check if the first char is \ and the second is a reserved char
|
||||||
|
if (!Reserved::IsEscaping(input.m_String, range.first))
|
||||||
|
throw Exception("Too many chars were given", ARG_PARSE_SUBSTR(range));
|
||||||
|
|
||||||
|
// is correct
|
||||||
|
m_Value = input.m_String[range.first + 1];
|
||||||
|
}
|
||||||
|
// if its one char and reserved
|
||||||
|
else if (Reserved::IsReservedChar(input.m_String[range.first]))
|
||||||
|
throw Exception(s_ErrMsgReserved, ARG_PARSE_SUBSTR(range));
|
||||||
|
// one char, not reserved
|
||||||
|
else
|
||||||
|
m_Value = input.m_String[range.first];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for unsigned char argument parsing
|
||||||
|
*/
|
||||||
|
ARG_PARSE_BASE_SPEC(unsigned char)
|
||||||
|
{
|
||||||
|
// Grab the argument
|
||||||
|
auto range = input.NextPoi(start);
|
||||||
|
size_t len = range.second - range.first;
|
||||||
|
|
||||||
|
// Check if its 3 or more letters
|
||||||
|
if (len > 2 || len <= 0)
|
||||||
|
throw Exception("Too many or no chars were given", ARG_PARSE_SUBSTR(range));
|
||||||
|
// potential reserved char
|
||||||
|
else if (len == 2)
|
||||||
|
{
|
||||||
|
// Check if the first char is \ and the second is a reserved char
|
||||||
|
if (!Reserved::IsEscaping(input.m_String, range.first))
|
||||||
|
throw Exception("Too many chars were given", ARG_PARSE_SUBSTR(range));
|
||||||
|
|
||||||
|
// is correct
|
||||||
|
m_Value = static_cast<unsigned char>(input.m_String[range.first + 1]);
|
||||||
|
}
|
||||||
|
// if its one char and reserved
|
||||||
|
else if (Reserved::IsReservedChar(input.m_String[range.first]))
|
||||||
|
throw Exception(s_ErrMsgReserved, ARG_PARSE_SUBSTR(range));
|
||||||
|
// one char, not reserved
|
||||||
|
else
|
||||||
|
m_Value = static_cast<unsigned char>(input.m_String[range.first]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for short argument parsing
|
||||||
|
*/
|
||||||
|
ARG_PARSE_GENERAL_SPEC(short, "signed short", std::stoi)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for unsigned short argument parsing
|
||||||
|
*/
|
||||||
|
ARG_PARSE_GENERAL_SPEC(unsigned short, "unsigned short", std::stoul)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for int argument parsing
|
||||||
|
*/
|
||||||
|
ARG_PARSE_GENERAL_SPEC(int, "signed int", std::stoi)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for unsigned int argument parsing
|
||||||
|
*/
|
||||||
|
ARG_PARSE_GENERAL_SPEC(unsigned int, "unsigned int", std::stoul)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for long argument parsing
|
||||||
|
*/
|
||||||
|
ARG_PARSE_GENERAL_SPEC(long, "long", std::stol)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for unsigned long argument parsing
|
||||||
|
*/
|
||||||
|
ARG_PARSE_GENERAL_SPEC(unsigned long, "unsigned long", std::stoul)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for long long argument parsing
|
||||||
|
*/
|
||||||
|
ARG_PARSE_GENERAL_SPEC(long long, "long long", std::stoll)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for unsigned long long argument parsing
|
||||||
|
*/
|
||||||
|
ARG_PARSE_GENERAL_SPEC(unsigned long long, "unsigned long long", std::stoull)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for float argument parsing
|
||||||
|
*/
|
||||||
|
ARG_PARSE_GENERAL_SPEC(float, "float", std::stof)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for double argument parsing
|
||||||
|
*/
|
||||||
|
ARG_PARSE_GENERAL_SPEC(double, "double", std::stod)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for long double argument parsing
|
||||||
|
*/
|
||||||
|
ARG_PARSE_GENERAL_SPEC(long double, "long double", std::stold)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for vector argument parsing
|
||||||
|
* \tparam T
|
||||||
|
* Type that the vector holds
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
struct CSYS_API ArgumentParser<std::vector<T>>
|
||||||
|
{
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Grabs a vector argument of type T from 'input' starting from 'start'
|
||||||
|
* \param input
|
||||||
|
* Input to the command for this class to parse its argument
|
||||||
|
* \param start
|
||||||
|
* Start of this argument
|
||||||
|
*/
|
||||||
|
ArgumentParser(String &input, size_t &start);
|
||||||
|
|
||||||
|
std::vector<T> m_Value; //!< Vector of data parsed
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Grabs a vector argument of type T from 'input' starting from 'start'
|
||||||
|
* \tparam T
|
||||||
|
* Type of vector
|
||||||
|
* \param input
|
||||||
|
* Input to the command for this class to parse its argument
|
||||||
|
* \param start
|
||||||
|
* Start of this argument
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
ArgumentParser<std::vector<T>>::ArgumentParser(String &input, size_t &start)
|
||||||
|
{
|
||||||
|
// Clean out vector before use
|
||||||
|
m_Value.clear();
|
||||||
|
|
||||||
|
// Grab the start of the vector argument
|
||||||
|
auto range = input.NextPoi(start);
|
||||||
|
|
||||||
|
// Empty
|
||||||
|
if (range.first == input.End()) return;
|
||||||
|
|
||||||
|
// Not starting with [
|
||||||
|
if (input.m_String[range.first] != '[')
|
||||||
|
throw Exception("Invalid vector argument missing opening [", ARG_PARSE_SUBSTR(range));
|
||||||
|
|
||||||
|
// Erase [
|
||||||
|
input.m_String[range.first] = ' ';
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// Get next argument in vector
|
||||||
|
range = input.NextPoi(range.first);
|
||||||
|
|
||||||
|
// No more, empty vector
|
||||||
|
if (range.first == input.End()) return;
|
||||||
|
|
||||||
|
// Is a nested vector, go deeper
|
||||||
|
else if (input.m_String[range.first] == '[')
|
||||||
|
m_Value.push_back(ArgumentParser<T>(input, range.first).m_Value);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Find first non-escaped ]
|
||||||
|
range.second = input.m_String.find(']', range.first);
|
||||||
|
while (range.second != std::string::npos && Reserved::IsEscaped(input.m_String, range.second))
|
||||||
|
range.second = input.m_String.find(']', range.second + 1);
|
||||||
|
|
||||||
|
// Check for closing ]
|
||||||
|
if (range.second == std::string::npos)
|
||||||
|
{
|
||||||
|
range.second = input.m_String.size();
|
||||||
|
throw Exception("Invalid vector argument missing closing ]", ARG_PARSE_SUBSTR(range));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase ]
|
||||||
|
input.m_String[range.second] = ' ';
|
||||||
|
start = range.first;
|
||||||
|
|
||||||
|
// Parse all arguments contained within the vector
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// If end of parsing, get out
|
||||||
|
if ((range.first = input.NextPoi(range.first).first) >= range.second)
|
||||||
|
{
|
||||||
|
start = range.first;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse argument and go to next
|
||||||
|
m_Value.push_back(ArgumentParser<T>(input, start).m_Value);
|
||||||
|
range.first = start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //CSYS_ARGUMENT_PARSER_H
|
||||||
208
include/csys/arguments.h
Normal file
208
include/csys/arguments.h
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#ifndef CSYS_ARGUMENTS_H
|
||||||
|
#define CSYS_ARGUMENTS_H
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "csys/api.h"
|
||||||
|
#include "csys/string.h"
|
||||||
|
#include "csys/exceptions.h"
|
||||||
|
#include "csys/argument_parser.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace csys
|
||||||
|
{
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Macro for supporting trivial types
|
||||||
|
*/
|
||||||
|
#define SUPPORT_TYPE(TYPE, TYPE_NAME)\
|
||||||
|
template<> struct is_supported_type<TYPE> { static constexpr bool value = true; }; \
|
||||||
|
template<> \
|
||||||
|
struct CSYS_API ArgData<TYPE> \
|
||||||
|
{ \
|
||||||
|
explicit ArgData(String name) : m_Name(std::move(name)), m_Value() {} \
|
||||||
|
const String m_Name; \
|
||||||
|
String m_TypeName = TYPE_NAME; \
|
||||||
|
TYPE m_Value; \
|
||||||
|
};
|
||||||
|
|
||||||
|
using NULL_ARGUMENT = void (*)(); //!< Null argument typedef
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Base case struct where a type is not supported
|
||||||
|
*/
|
||||||
|
template<typename> struct is_supported_type { static constexpr bool value = false; };
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Wrapper around a given data type to name it
|
||||||
|
* \tparam T
|
||||||
|
* Type of data that must have a default constructor
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
struct CSYS_API ArgData
|
||||||
|
{
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Non-default constructor
|
||||||
|
* \param name
|
||||||
|
* Name of the argument
|
||||||
|
*/
|
||||||
|
explicit ArgData(String name) : m_Name(std::move(name)), m_Value()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
const String m_Name = ""; //!< Name of argument
|
||||||
|
String m_TypeName = "Unsupported Type"; //!< Name of type
|
||||||
|
T m_Value; //!< Actual value
|
||||||
|
};
|
||||||
|
|
||||||
|
//! Supported types
|
||||||
|
SUPPORT_TYPE(String, "String")
|
||||||
|
|
||||||
|
SUPPORT_TYPE(bool, "Boolean")
|
||||||
|
|
||||||
|
SUPPORT_TYPE(char, "Char")
|
||||||
|
|
||||||
|
SUPPORT_TYPE(unsigned char, "Unsigned_Char")
|
||||||
|
|
||||||
|
SUPPORT_TYPE(short, "Signed_Short")
|
||||||
|
|
||||||
|
SUPPORT_TYPE(unsigned short, "Unsigned_Short")
|
||||||
|
|
||||||
|
SUPPORT_TYPE(int, "Signed_Int")
|
||||||
|
|
||||||
|
SUPPORT_TYPE(unsigned int, "Unsigned_Int")
|
||||||
|
|
||||||
|
SUPPORT_TYPE(long, "Signed_Long")
|
||||||
|
|
||||||
|
SUPPORT_TYPE(unsigned long, "Unsigned_Long")
|
||||||
|
|
||||||
|
SUPPORT_TYPE(long long, "Signed_Long_Long")
|
||||||
|
|
||||||
|
SUPPORT_TYPE(unsigned long long, "Unsigned_Long_Long")
|
||||||
|
|
||||||
|
SUPPORT_TYPE(float, "Float")
|
||||||
|
|
||||||
|
SUPPORT_TYPE(double, "Double")
|
||||||
|
|
||||||
|
SUPPORT_TYPE(long double, "Long_Double")
|
||||||
|
|
||||||
|
//! Supported containers
|
||||||
|
template<typename U> struct is_supported_type<std::vector<U>> { static constexpr bool value = is_supported_type<U>::value; };
|
||||||
|
template<typename T>
|
||||||
|
struct CSYS_API ArgData<std::vector<T>>
|
||||||
|
{
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Constructor for a vector argument
|
||||||
|
* \param name
|
||||||
|
* Name for argument
|
||||||
|
*/
|
||||||
|
explicit ArgData(String name) : m_Name(std::move(name))
|
||||||
|
{}
|
||||||
|
|
||||||
|
const String m_Name; //!< Name of argument
|
||||||
|
String m_TypeName = std::string("Vector_Of_") + ArgData<T>("").m_TypeName.m_String; //!< Type name
|
||||||
|
std::vector<T> m_Value; //!< Vector of data
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Wrapper around an argument for use of parsing a command line
|
||||||
|
* \tparam T
|
||||||
|
* Data type
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
struct CSYS_API Arg
|
||||||
|
{
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Is true if type of U is a supported type
|
||||||
|
* \tparam U
|
||||||
|
* Type to check if it is supported
|
||||||
|
*/
|
||||||
|
template<typename U>
|
||||||
|
static constexpr bool is_supported_type_v = is_supported_type<U>::value;
|
||||||
|
public:
|
||||||
|
|
||||||
|
using ValueType = std::remove_cv_t<std::remove_reference_t<T>>; //!< Type of this argument
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Constructor for an argument for naming
|
||||||
|
* \param name
|
||||||
|
* Name of the argument
|
||||||
|
*/
|
||||||
|
explicit Arg(const String &name) : m_Arg(name)
|
||||||
|
{
|
||||||
|
static_assert(is_supported_type_v<ValueType>,
|
||||||
|
"ValueType 'T' is not supported, see 'Supported types' for more help");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Grabs its own argument from the command line and sets its value
|
||||||
|
* \param input
|
||||||
|
* Command line argument list
|
||||||
|
* \param start
|
||||||
|
* Start of its argument
|
||||||
|
* \return
|
||||||
|
* Returns this
|
||||||
|
*/
|
||||||
|
Arg<T> &Parse(String &input, size_t &start)
|
||||||
|
{
|
||||||
|
size_t index = start;
|
||||||
|
|
||||||
|
// Check if there are more arguments to be read in
|
||||||
|
if (input.NextPoi(index).first == input.End())
|
||||||
|
throw Exception("Not enough arguments were given", input.m_String);
|
||||||
|
// Set value grabbed from input aka command line argument
|
||||||
|
m_Arg.m_Value = ArgumentParser<ValueType>(input, start).m_Value;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Gets the info of the argument in the form of [name:type]
|
||||||
|
* \return
|
||||||
|
* Returns a string containing the arugment's info
|
||||||
|
*/
|
||||||
|
std::string Info()
|
||||||
|
{
|
||||||
|
return std::string(" [") + m_Arg.m_Name.m_String + ":" + m_Arg.m_TypeName.m_String + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgData<ValueType> m_Arg; //!< Data relating to this argument
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for a null argument that gets appended to a command's argument list to check if more
|
||||||
|
* than the required number of arguments
|
||||||
|
*/
|
||||||
|
template<>
|
||||||
|
struct CSYS_API Arg<NULL_ARGUMENT>
|
||||||
|
{
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Checks if the input starting from param 'start' is all whitespace or not
|
||||||
|
* \param input
|
||||||
|
* Command line argument list
|
||||||
|
* \param start
|
||||||
|
* Start of its argument
|
||||||
|
* \return
|
||||||
|
* Returns this
|
||||||
|
*/
|
||||||
|
Arg<NULL_ARGUMENT> &Parse(String &input, size_t &start)
|
||||||
|
{
|
||||||
|
if (input.NextPoi(start).first != input.End())
|
||||||
|
throw Exception("Too many arguments were given", input.m_String);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //CSYS_ARGUMENTS_H
|
||||||
386
include/csys/autocomplete.h
Normal file
386
include/csys/autocomplete.h
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#ifndef CSYS_AUTOCOMPLETE_H
|
||||||
|
#define CSYS_AUTOCOMPLETE_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "csys/api.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace csys
|
||||||
|
{
|
||||||
|
// TODO: Check how to add support for UTF Encoding.
|
||||||
|
// TODO: Todo add max word suggestion depth.
|
||||||
|
// TODO: Only use "const char *" or "std::string" in csys. (On stl containers use iterators - SLOW). (Need to add std::string version)
|
||||||
|
|
||||||
|
//!< Auto complete ternary search tree.
|
||||||
|
class CSYS_API AutoComplete
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Type definitions.
|
||||||
|
using r_sVector = std::vector<std::string> &;
|
||||||
|
using sVector = std::vector<std::string>;
|
||||||
|
|
||||||
|
//!< Autocomplete node.
|
||||||
|
struct ACNode
|
||||||
|
{
|
||||||
|
explicit ACNode(const char data, bool isWord = false) : m_Data(data), m_IsWord(isWord), m_Less(nullptr), m_Equal(nullptr), m_Greater(nullptr)
|
||||||
|
{};
|
||||||
|
|
||||||
|
explicit ACNode(const char &&data, bool isWord = false) : m_Data(data), m_IsWord(isWord), m_Less(nullptr), m_Equal(nullptr), m_Greater(nullptr)
|
||||||
|
{};
|
||||||
|
|
||||||
|
~ACNode()
|
||||||
|
{
|
||||||
|
delete m_Less;
|
||||||
|
delete m_Equal;
|
||||||
|
delete m_Greater;
|
||||||
|
};
|
||||||
|
|
||||||
|
char m_Data; //!< Node data.
|
||||||
|
bool m_IsWord; //!< Flag to determine if node is the end of a word.
|
||||||
|
ACNode *m_Less; //!< Left pointer.
|
||||||
|
ACNode *m_Equal; //!< Middle pointer.
|
||||||
|
ACNode *m_Greater; //!< Right pointer.
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Default Constructor
|
||||||
|
*/
|
||||||
|
AutoComplete() = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Copy constructor
|
||||||
|
* \param tree
|
||||||
|
* Tree to be copied
|
||||||
|
*/
|
||||||
|
AutoComplete(const AutoComplete &tree);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Move constructor
|
||||||
|
* \param rhs
|
||||||
|
* Tree to be copied
|
||||||
|
*/
|
||||||
|
AutoComplete(AutoComplete &&rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Assignment operator
|
||||||
|
* \param rhs
|
||||||
|
* Source tree
|
||||||
|
* \return
|
||||||
|
* Self
|
||||||
|
*/
|
||||||
|
AutoComplete &operator=(const AutoComplete &rhs);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Move assignment operator
|
||||||
|
* \param rhs
|
||||||
|
* Source tree
|
||||||
|
* \return
|
||||||
|
* Self
|
||||||
|
*/
|
||||||
|
AutoComplete& operator=(AutoComplete&& rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
*
|
||||||
|
* \tparam inputType
|
||||||
|
* String input type
|
||||||
|
* \param[in] il
|
||||||
|
* List of string from which TST will be constructed
|
||||||
|
*/
|
||||||
|
template<typename inputType>
|
||||||
|
AutoComplete(std::initializer_list<inputType> il)
|
||||||
|
{
|
||||||
|
for (const auto &item : il)
|
||||||
|
{
|
||||||
|
Insert(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
*
|
||||||
|
* \tparam T
|
||||||
|
* Container type
|
||||||
|
* \param[in] items
|
||||||
|
* Arbitrary container of strings
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
explicit AutoComplete(const T &items)
|
||||||
|
{
|
||||||
|
for (const auto &item : items)
|
||||||
|
{
|
||||||
|
Insert(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* /brief
|
||||||
|
* Destructor
|
||||||
|
*/
|
||||||
|
~AutoComplete();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Get tree node count
|
||||||
|
* \return
|
||||||
|
* Tree node count
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_t Size() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Get tree word count
|
||||||
|
* \return
|
||||||
|
* Word count
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_t Count() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Search if the given word is in the tree
|
||||||
|
* \param[in] word
|
||||||
|
* Word to search
|
||||||
|
* \return
|
||||||
|
* Found word
|
||||||
|
*/
|
||||||
|
bool Search(const char *word);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Insert word into tree
|
||||||
|
* \param[in] word
|
||||||
|
* Word to be inserted
|
||||||
|
*/
|
||||||
|
void Insert(const char *word);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Insert word into tree
|
||||||
|
* \param[in] word
|
||||||
|
* Word to be inserted
|
||||||
|
*/
|
||||||
|
void Insert(const std::string &word);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Insert word into tree
|
||||||
|
* \tparam strType
|
||||||
|
* String type to be inserted
|
||||||
|
* \param[in] word
|
||||||
|
* Word to be inserted
|
||||||
|
*/
|
||||||
|
template<typename strType>
|
||||||
|
void Insert(const strType &word)
|
||||||
|
{
|
||||||
|
ACNode **ptr = &m_Root;
|
||||||
|
++m_Count;
|
||||||
|
|
||||||
|
while (*word != '\0')
|
||||||
|
{
|
||||||
|
// Insert char into tree.
|
||||||
|
if (*ptr == nullptr)
|
||||||
|
{
|
||||||
|
*ptr = new ACNode(*word);
|
||||||
|
++m_Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse tree.
|
||||||
|
if (*word < (*ptr)->m_Data)
|
||||||
|
{
|
||||||
|
ptr = &(*ptr)->m_Less;
|
||||||
|
}
|
||||||
|
else if (*word == (*ptr)->m_Data)
|
||||||
|
{
|
||||||
|
// String is already in tree, therefore only mark as word.
|
||||||
|
if (*(word + 1) == '\0')
|
||||||
|
{
|
||||||
|
if ((*ptr)->m_IsWord)
|
||||||
|
--m_Count;
|
||||||
|
|
||||||
|
(*ptr)->m_IsWord = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance.
|
||||||
|
ptr = &(*ptr)->m_Equal;
|
||||||
|
++word;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ptr = &(*ptr)->m_Greater;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Removes a word from the tree if found
|
||||||
|
* \param[in] word
|
||||||
|
* String to be removed
|
||||||
|
*/
|
||||||
|
void Remove(const std::string &word);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Retrieve suggestions that match the given prefix
|
||||||
|
* \tparam strType
|
||||||
|
* Prefix string type
|
||||||
|
* \param[in] prefix
|
||||||
|
* Prefix to use for suggestion lookup
|
||||||
|
* \param[out] ac_options
|
||||||
|
* Vector of found suggestions
|
||||||
|
*/
|
||||||
|
template<typename strType>
|
||||||
|
void Suggestions(const strType &prefix, r_sVector ac_options)
|
||||||
|
{
|
||||||
|
ACNode *ptr = m_Root;
|
||||||
|
auto temp = prefix;
|
||||||
|
|
||||||
|
// Traverse tree and check if prefix exists.
|
||||||
|
while (ptr)
|
||||||
|
{
|
||||||
|
if (*prefix < ptr->m_Data)
|
||||||
|
{
|
||||||
|
ptr = ptr->m_Less;
|
||||||
|
}
|
||||||
|
else if (*prefix == ptr->m_Data)
|
||||||
|
{
|
||||||
|
// Prefix exists in tree.
|
||||||
|
if (*(prefix + 1) == '\0')
|
||||||
|
break;
|
||||||
|
|
||||||
|
ptr = ptr->m_Equal;
|
||||||
|
++prefix;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ptr = ptr->m_Greater;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already a word. (No need to auto complete).
|
||||||
|
if (ptr && ptr->m_IsWord) return;
|
||||||
|
|
||||||
|
// Prefix is not in tree.
|
||||||
|
if (!ptr) return;
|
||||||
|
|
||||||
|
// Retrieve auto complete options.
|
||||||
|
SuggestionsAux(ptr->m_Equal, ac_options, temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Retrieve suggestions that match the given prefix
|
||||||
|
* \param[in] prefix
|
||||||
|
* Prefix to use for suggestion lookup
|
||||||
|
* \param[out] ac_options
|
||||||
|
* Vector of found suggestions
|
||||||
|
*/
|
||||||
|
void Suggestions(const char *prefix, r_sVector ac_options);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Store suggestions that match prefix in ac_options and return partially completed prefix if possible.
|
||||||
|
* \param[in] prefix
|
||||||
|
* Prefix to use for suggestion lookup
|
||||||
|
* \param[out] ac_options
|
||||||
|
* Vector of found suggestions
|
||||||
|
* \return
|
||||||
|
* Partially completed prefix
|
||||||
|
*/
|
||||||
|
std::string Suggestions(const std::string &prefix, r_sVector ac_options);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Retrieve suggestions that match the given prefix
|
||||||
|
* \param[in/out] prefix
|
||||||
|
* Prefix to use for suggestion lookup, will be partially completed if flag partial_complete is on
|
||||||
|
* \param[out] ac_options
|
||||||
|
* Vector of found suggestions
|
||||||
|
* \param[in] partial_complete
|
||||||
|
* Flag to determine if prefix string will be partially completed
|
||||||
|
*/
|
||||||
|
void Suggestions(std::string &prefix, r_sVector ac_options, bool partial_complete);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Retrieve suggestions that match the given prefix
|
||||||
|
* \tparam strType
|
||||||
|
* Prefix string type
|
||||||
|
* \param[in] prefix
|
||||||
|
* Prefix to use for suggestion lookup
|
||||||
|
* \return
|
||||||
|
* Vector of found suggestions
|
||||||
|
*/
|
||||||
|
template<typename strType>
|
||||||
|
std::unique_ptr<sVector> Suggestions(const strType &prefix)
|
||||||
|
{
|
||||||
|
auto temp = std::make_unique<sVector>();
|
||||||
|
Suggestions(prefix, *temp);
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Retrieve suggestions that match the given prefix
|
||||||
|
* \param[in] prefix
|
||||||
|
* Prefix to use for suggestion lookup
|
||||||
|
* \return
|
||||||
|
* Vector of found suggestions
|
||||||
|
*/
|
||||||
|
std::unique_ptr<sVector> Suggestions(const char *prefix);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \param[in] root
|
||||||
|
* Permutation root
|
||||||
|
* \param[out] ac_options
|
||||||
|
* Vector of found suggestions
|
||||||
|
* \param[in] buffer
|
||||||
|
* Prefix buffer
|
||||||
|
*/
|
||||||
|
void SuggestionsAux(ACNode *root, r_sVector ac_options, std::string buffer);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Remove word auxiliary function
|
||||||
|
* \param[in] root
|
||||||
|
* Current node to process
|
||||||
|
* \param[in] word
|
||||||
|
* String to look for and remove
|
||||||
|
* \return
|
||||||
|
* If node is word
|
||||||
|
*/
|
||||||
|
bool RemoveAux(ACNode *root, const char *word);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Clone acNode and all its children
|
||||||
|
* \param src
|
||||||
|
* Node to copy from
|
||||||
|
* \param dest
|
||||||
|
* Where new node will be stored
|
||||||
|
*/
|
||||||
|
void DeepClone(ACNode *src, ACNode *&dest);
|
||||||
|
|
||||||
|
ACNode *m_Root = nullptr; //!< Ternary Search Tree Root node
|
||||||
|
size_t m_Size = 0; //!< Node count
|
||||||
|
size_t m_Count = 0; //!< Word count
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CSYS_HEADER_ONLY
|
||||||
|
#include "csys/autocomplete.inl"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif //CSYS_AUTOCOMPLETE_H
|
||||||
318
include/csys/autocomplete.inl
Normal file
318
include/csys/autocomplete.inl
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CSYS_HEADER_ONLY
|
||||||
|
|
||||||
|
#include "csys/autocomplete.h"
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace csys
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Constructor/Destructors ////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
CSYS_INLINE AutoComplete::~AutoComplete()
|
||||||
|
{
|
||||||
|
delete m_Root;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE AutoComplete::AutoComplete(const AutoComplete &tree) : m_Size(tree.m_Size), m_Count(tree.m_Count)
|
||||||
|
{
|
||||||
|
DeepClone(tree.m_Root, m_Root);
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE AutoComplete &AutoComplete::operator=(const AutoComplete &rhs)
|
||||||
|
{
|
||||||
|
// Prevent self assignment.
|
||||||
|
if (&rhs == this) return *this;
|
||||||
|
|
||||||
|
// Clean.
|
||||||
|
delete m_Root;
|
||||||
|
|
||||||
|
// Copy from source tree.
|
||||||
|
DeepClone(rhs.m_Root, m_Root);
|
||||||
|
m_Size = rhs.m_Size;
|
||||||
|
m_Count = rhs.m_Count;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Public methods /////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
CSYS_INLINE size_t AutoComplete::Size() const
|
||||||
|
{
|
||||||
|
return m_Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE size_t AutoComplete::Count() const
|
||||||
|
{
|
||||||
|
return m_Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE bool AutoComplete::Search(const char *word)
|
||||||
|
{
|
||||||
|
ACNode *ptr = m_Root;
|
||||||
|
|
||||||
|
// Traverse tree in look for the given string.
|
||||||
|
while (ptr)
|
||||||
|
{
|
||||||
|
if (*word < ptr->m_Data)
|
||||||
|
{
|
||||||
|
ptr = ptr->m_Less;
|
||||||
|
} else if (*word == ptr->m_Data)
|
||||||
|
{
|
||||||
|
// Word was found.
|
||||||
|
if (*(word + 1) == '\0' && ptr->m_IsWord)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
ptr = ptr->m_Equal;
|
||||||
|
++word;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
ptr = ptr->m_Greater;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void AutoComplete::Insert(const char *word)
|
||||||
|
{
|
||||||
|
ACNode **ptr = &m_Root;
|
||||||
|
++m_Count;
|
||||||
|
|
||||||
|
while (*word != '\0')
|
||||||
|
{
|
||||||
|
// Insert char into tree.
|
||||||
|
if (*ptr == nullptr)
|
||||||
|
{
|
||||||
|
*ptr = new ACNode(*word);
|
||||||
|
++m_Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse tree.
|
||||||
|
if (*word < (*ptr)->m_Data)
|
||||||
|
{
|
||||||
|
ptr = &(*ptr)->m_Less;
|
||||||
|
} else if (*word == (*ptr)->m_Data)
|
||||||
|
{
|
||||||
|
// String is already in tree, therefore only mark as word.
|
||||||
|
if (*(word + 1) == '\0')
|
||||||
|
{
|
||||||
|
if ((*ptr)->m_IsWord)
|
||||||
|
--m_Count;
|
||||||
|
|
||||||
|
(*ptr)->m_IsWord = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance.
|
||||||
|
ptr = &(*ptr)->m_Equal;
|
||||||
|
++word;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
ptr = &(*ptr)->m_Greater;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void AutoComplete::Insert(const std::string &word)
|
||||||
|
{
|
||||||
|
Insert(word.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void AutoComplete::Remove(const std::string &word)
|
||||||
|
{
|
||||||
|
RemoveAux(m_Root, word.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void AutoComplete::Suggestions(const char *prefix, std::vector<std::string> &ac_options)
|
||||||
|
{
|
||||||
|
ACNode *ptr = m_Root;
|
||||||
|
auto temp = prefix;
|
||||||
|
|
||||||
|
// Traverse tree and check if prefix exists.
|
||||||
|
while (ptr)
|
||||||
|
{
|
||||||
|
if (*prefix < ptr->m_Data)
|
||||||
|
{
|
||||||
|
ptr = ptr->m_Less;
|
||||||
|
} else if (*prefix == ptr->m_Data)
|
||||||
|
{
|
||||||
|
// Prefix exists in tree.
|
||||||
|
if (*(prefix + 1) == '\0')
|
||||||
|
break;
|
||||||
|
|
||||||
|
ptr = ptr->m_Equal;
|
||||||
|
++prefix;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
ptr = ptr->m_Greater;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already a word. (No need to auto complete).
|
||||||
|
if (ptr && ptr->m_IsWord) return;
|
||||||
|
|
||||||
|
// Prefix is not in tree.
|
||||||
|
if (!ptr) return;
|
||||||
|
|
||||||
|
// Retrieve auto complete options.
|
||||||
|
SuggestionsAux(ptr->m_Equal, ac_options, temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE std::string AutoComplete::Suggestions(const std::string &prefix, r_sVector &ac_options)
|
||||||
|
{
|
||||||
|
std::string temp = prefix;
|
||||||
|
Suggestions(temp, ac_options, true);
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void AutoComplete::Suggestions(std::string &prefix, r_sVector ac_options, bool partial_complete)
|
||||||
|
{
|
||||||
|
ACNode *ptr = m_Root;
|
||||||
|
const char *temp = prefix.data();
|
||||||
|
size_t prefix_end = prefix.size();
|
||||||
|
|
||||||
|
// Traverse tree and check if prefix exists.
|
||||||
|
while (ptr)
|
||||||
|
{
|
||||||
|
if (*temp < ptr->m_Data)
|
||||||
|
{
|
||||||
|
ptr = ptr->m_Less;
|
||||||
|
} else if (*temp == ptr->m_Data)
|
||||||
|
{
|
||||||
|
// Prefix exists in tree.
|
||||||
|
if (*(temp + 1) == '\0')
|
||||||
|
{
|
||||||
|
if (partial_complete)
|
||||||
|
{
|
||||||
|
ACNode *pc_ptr = ptr->m_Equal;
|
||||||
|
|
||||||
|
// Get partially completed string.
|
||||||
|
while (pc_ptr)
|
||||||
|
{
|
||||||
|
if (pc_ptr->m_Equal && !pc_ptr->m_Less && !pc_ptr->m_Greater)
|
||||||
|
prefix.push_back(pc_ptr->m_Data);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
|
||||||
|
pc_ptr = pc_ptr->m_Equal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr = ptr->m_Equal;
|
||||||
|
++temp;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
ptr = ptr->m_Greater;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already a word. (No need to auto complete).
|
||||||
|
if (ptr && ptr->m_IsWord) return;
|
||||||
|
|
||||||
|
// Prefix is not in tree.
|
||||||
|
if (!ptr) return;
|
||||||
|
|
||||||
|
// Retrieve auto complete options.
|
||||||
|
SuggestionsAux(ptr->m_Equal, ac_options, prefix.substr(0, prefix_end));
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE std::unique_ptr<AutoComplete::sVector> AutoComplete::Suggestions(const char *prefix)
|
||||||
|
{
|
||||||
|
auto temp = std::make_unique<sVector>();
|
||||||
|
Suggestions(prefix, *temp);
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Private methods ////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
CSYS_INLINE void AutoComplete::SuggestionsAux(AutoComplete::ACNode *root, r_sVector ac_options, std::string buffer)
|
||||||
|
{
|
||||||
|
if (!root) return;
|
||||||
|
|
||||||
|
// Continue looking in left branch.
|
||||||
|
if (root->m_Less) SuggestionsAux(root->m_Less, ac_options, buffer);
|
||||||
|
|
||||||
|
// Word was found, push into autocomplete options.
|
||||||
|
if (root->m_IsWord)
|
||||||
|
{
|
||||||
|
ac_options.push_back(buffer.append(1, root->m_Data));
|
||||||
|
buffer.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue in middle branch, and push character.
|
||||||
|
if (root->m_Equal)
|
||||||
|
{
|
||||||
|
SuggestionsAux(root->m_Equal, ac_options, buffer.append(1, root->m_Data));
|
||||||
|
buffer.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue looking in right branch.
|
||||||
|
if (root->m_Greater)
|
||||||
|
{
|
||||||
|
SuggestionsAux(root->m_Greater, ac_options, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE bool AutoComplete::RemoveAux(AutoComplete::ACNode *root, const char *word)
|
||||||
|
{
|
||||||
|
if (!root) return false;
|
||||||
|
|
||||||
|
// String is in TST.
|
||||||
|
if (*(word + 1) == '\0' && root->m_Data == *word)
|
||||||
|
{
|
||||||
|
// Un-mark word node.
|
||||||
|
if (root->m_IsWord)
|
||||||
|
{
|
||||||
|
root->m_IsWord = false;
|
||||||
|
return (!root->m_Equal && !root->m_Less && !root->m_Greater);
|
||||||
|
}
|
||||||
|
// String is a prefix.
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
// String is a prefix.
|
||||||
|
if (*word < root->m_Data)
|
||||||
|
RemoveAux(root->m_Less, word);
|
||||||
|
else if (*word > root->m_Data)
|
||||||
|
RemoveAux(root->m_Greater, word);
|
||||||
|
|
||||||
|
// String is in TST.
|
||||||
|
else if (*word == root->m_Data)
|
||||||
|
{
|
||||||
|
// Char is unique.
|
||||||
|
if (RemoveAux(root->m_Equal, word + 1))
|
||||||
|
{
|
||||||
|
delete root->m_Equal;
|
||||||
|
root->m_Equal = nullptr;
|
||||||
|
return !root->m_IsWord && (!root->m_Equal && !root->m_Less && !root->m_Greater);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void AutoComplete::DeepClone(AutoComplete::ACNode *src, AutoComplete::ACNode *&dest)
|
||||||
|
{
|
||||||
|
if (!src) return;
|
||||||
|
|
||||||
|
dest = new ACNode(*src);
|
||||||
|
DeepClone(src->m_Less, dest->m_Less);
|
||||||
|
DeepClone(src->m_Equal, dest->m_Equal);
|
||||||
|
DeepClone(src->m_Greater, dest->m_Greater);
|
||||||
|
}
|
||||||
|
}
|
||||||
292
include/csys/command.h
Normal file
292
include/csys/command.h
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#ifndef CSYS_COMMAND_H
|
||||||
|
#define CSYS_COMMAND_H
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include "csys/arguments.h"
|
||||||
|
#include "csys/exceptions.h"
|
||||||
|
#include "csys/item.h"
|
||||||
|
|
||||||
|
namespace csys
|
||||||
|
{
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Non-templated class that allows for the storage of commands as well as accessing certain functionality of
|
||||||
|
* said commands.
|
||||||
|
*/
|
||||||
|
struct CommandBase
|
||||||
|
{
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Default virtual destructor
|
||||||
|
*/
|
||||||
|
virtual ~CommandBase() = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Parses and runs the function held within the child class
|
||||||
|
* \param input
|
||||||
|
* String of arguments for the command to parse and pass to the function
|
||||||
|
* \return
|
||||||
|
* Returns item error if the parsing in someway was messed up, and none if there was no issue
|
||||||
|
*/
|
||||||
|
virtual Item operator()(String &input) = 0;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Gets info about the command and usage
|
||||||
|
* \return
|
||||||
|
* String containing info about the command
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual std::string Help() = 0;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Getter for the number of arguments the command takes
|
||||||
|
* \return
|
||||||
|
* Returns the number of arguments taken by the command
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual size_t ArgumentCount() const = 0;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Deep copies a command
|
||||||
|
* \return
|
||||||
|
* Pointer to newly copied command
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual CommandBase* Clone() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Base template for a command that takes N amount of arguments
|
||||||
|
* \tparam Fn
|
||||||
|
* Decltype of function to be called when command is parsed and ran
|
||||||
|
* \tparam Args
|
||||||
|
* Argument type list that is proportional to the argument list of the function Fn
|
||||||
|
*/
|
||||||
|
template<typename Fn, typename ...Args>
|
||||||
|
class CSYS_API Command : public CommandBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Constructor that sets the name, description, function and arguments as well as add a null argument
|
||||||
|
* for parsing
|
||||||
|
* \param name
|
||||||
|
* Name of the command to call by
|
||||||
|
* \param description
|
||||||
|
* Info about the command
|
||||||
|
* \param function
|
||||||
|
* Function to run when command is called
|
||||||
|
* \param args
|
||||||
|
* Arguments to be used for parsing and passing into the function. Must be of type "Arg<T>"
|
||||||
|
*/
|
||||||
|
Command(String name, String description, Fn function, Args... args) : m_Name(std::move(name)),
|
||||||
|
m_Description(std::move(description)),
|
||||||
|
m_Function(function),
|
||||||
|
m_Arguments(args..., Arg<NULL_ARGUMENT>())
|
||||||
|
{}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Parses and runs the function m_Function
|
||||||
|
* \param input
|
||||||
|
* String of arguments for the command to parse and pass to the function
|
||||||
|
* \return
|
||||||
|
* Returns item error if the parsing in someway was messed up, and none if there was no issue
|
||||||
|
*/
|
||||||
|
Item operator()(String &input) final
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Try to parse and call the function
|
||||||
|
constexpr int argumentSize = sizeof... (Args);
|
||||||
|
Call(input, std::make_index_sequence<argumentSize + 1>{}, std::make_index_sequence<argumentSize>{});
|
||||||
|
}
|
||||||
|
catch (Exception &ae)
|
||||||
|
{
|
||||||
|
// Error happened with parsing
|
||||||
|
return Item(eERROR) << (m_Name.m_String + ": " + ae.what());
|
||||||
|
}
|
||||||
|
return Item(eNONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Gets info about the command and usage
|
||||||
|
* \return
|
||||||
|
* String containing info about the command
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::string Help() final
|
||||||
|
{
|
||||||
|
return m_Name.m_String + DisplayArguments(std::make_index_sequence<sizeof ...(Args)>{}) + "\n\t\t- " +
|
||||||
|
m_Description.m_String + "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Getter for the number of arguments the command takes
|
||||||
|
* \return
|
||||||
|
* Returns the number of arguments taken by the command
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_t ArgumentCount() const final
|
||||||
|
{
|
||||||
|
return sizeof... (Args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Deep copies a command
|
||||||
|
* \return
|
||||||
|
* Pointer to newly copied command
|
||||||
|
*/
|
||||||
|
[[nodiscard]] CommandBase* Clone() const final
|
||||||
|
{
|
||||||
|
return new Command<Fn, Args...>(*this);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Parses arguments and passes them into the command to be ran
|
||||||
|
* \tparam Is_p
|
||||||
|
* Index sequence from 0 to Argument Count + 1, used for parsing
|
||||||
|
* \tparam Is_c
|
||||||
|
* Index sequence from 0 to Argument Count, used for passing into the function
|
||||||
|
* \param input
|
||||||
|
* String of arguments to be parsed
|
||||||
|
*/
|
||||||
|
template<size_t... Is_p, size_t... Is_c>
|
||||||
|
void Call(String &input, const std::index_sequence<Is_p...> &, const std::index_sequence<Is_c...> &)
|
||||||
|
{
|
||||||
|
size_t start = 0;
|
||||||
|
|
||||||
|
// Parse arguments
|
||||||
|
int _[]{0, (void(std::get<Is_p>(m_Arguments).Parse(input, start)), 0)...};
|
||||||
|
(void) (_);
|
||||||
|
|
||||||
|
// Call function with unpacked tuple
|
||||||
|
m_Function((std::get<Is_c>(m_Arguments).m_Arg.m_Value)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Displays the usage for running the command successfully
|
||||||
|
* \tparam Is
|
||||||
|
* Index sequence from 0 to Argument CountH
|
||||||
|
* \return
|
||||||
|
* Returns a string containing the usage of the command
|
||||||
|
*/
|
||||||
|
template<size_t ...Is>
|
||||||
|
std::string DisplayArguments(const std::index_sequence<Is...> &)
|
||||||
|
{
|
||||||
|
return (std::get<Is>(m_Arguments).Info() + ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
const String m_Name; //!< Name of command
|
||||||
|
const String m_Description; //!< Description of the command
|
||||||
|
std::function<void(typename Args::ValueType...)> m_Function; //!< Function to be invoked as command
|
||||||
|
std::tuple<Args..., Arg<NULL_ARGUMENT>> m_Arguments; //!< Arguments to be passed into m_Function
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Template specialization for a command that doesn't take any arguments
|
||||||
|
* \tparam Fn
|
||||||
|
* Decltype of function to be called when the command is invoked
|
||||||
|
*/
|
||||||
|
template<typename Fn>
|
||||||
|
class CSYS_API Command<Fn> : public CommandBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Constructor that sets the name, description and function. A null argument will be added to the arguments
|
||||||
|
* for parsing
|
||||||
|
* \param name
|
||||||
|
* Name of the command to call by
|
||||||
|
* \param description
|
||||||
|
* Info about the command
|
||||||
|
* \param function
|
||||||
|
* Function to run when command is called
|
||||||
|
*/
|
||||||
|
Command(String name, String description, Fn function) : m_Name(std::move(name)),
|
||||||
|
m_Description(std::move(description)),
|
||||||
|
m_Function(function), m_Arguments(Arg<NULL_ARGUMENT>())
|
||||||
|
{}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Parses and runs the function m_Function
|
||||||
|
* \param input
|
||||||
|
* String of arguments for the command to parse and pass to the function. This should be empty
|
||||||
|
* \return
|
||||||
|
* Returns item error if the parsing in someway was messed up, and none if there was no issue
|
||||||
|
*/
|
||||||
|
Item operator()(String &input) final
|
||||||
|
{
|
||||||
|
// call the function
|
||||||
|
size_t start = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Check to see if input is all whitespace
|
||||||
|
std::get<0>(m_Arguments).Parse(input, start);
|
||||||
|
}
|
||||||
|
catch (Exception &ae)
|
||||||
|
{
|
||||||
|
// Command had something passed into it
|
||||||
|
return Item(eERROR) << (m_Name.m_String + ": " + ae.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call function
|
||||||
|
m_Function();
|
||||||
|
return Item(eNONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Gets info about the command and usage
|
||||||
|
* \return
|
||||||
|
* String containing info about the command
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::string Help() final
|
||||||
|
{
|
||||||
|
return m_Name.m_String + "\n\t\t- " + m_Description.m_String + "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Getter for the number of arguments the command takes
|
||||||
|
* \return
|
||||||
|
* 0
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_t ArgumentCount() const final
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Deep copies a command
|
||||||
|
* \return
|
||||||
|
* Pointer to newly copied command
|
||||||
|
*/
|
||||||
|
[[nodiscard]] CommandBase* Clone() const final
|
||||||
|
{
|
||||||
|
return new Command<Fn>(*this);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
|
||||||
|
const String m_Name; //!< Name of command
|
||||||
|
const String m_Description; //!< Description of the command
|
||||||
|
std::function<void(void)> m_Function; //!< Function to be invoked as command
|
||||||
|
std::tuple<Arg<NULL_ARGUMENT>> m_Arguments; //!< Arguments to be passed into m_Function
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //CSYS_COMMAND_H
|
||||||
11
include/csys/csys.h
Normal file
11
include/csys/csys.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#ifndef CSYS_H
|
||||||
|
#define CSYS_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "csys/system.h"
|
||||||
|
|
||||||
|
#endif
|
||||||
64
include/csys/exceptions.h
Normal file
64
include/csys/exceptions.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#ifndef CSYS_EXCEPTIONS_H
|
||||||
|
#define CSYS_EXCEPTIONS_H
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <exception>
|
||||||
|
#include <utility>
|
||||||
|
#include "csys/api.h"
|
||||||
|
|
||||||
|
namespace csys
|
||||||
|
{
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Csys exception extended from the stl for any issues
|
||||||
|
*/
|
||||||
|
class CSYS_API Exception : public std::exception
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Basic constructor that makes a message with an arg
|
||||||
|
* \param message
|
||||||
|
* Message describing what went wrong
|
||||||
|
* \param arg
|
||||||
|
* What went wrong (will get put between ' ')
|
||||||
|
*/
|
||||||
|
explicit Exception(const std::string &message, const std::string &arg) : m_Msg(message + ": '" + arg + "'")
|
||||||
|
{}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Constructor for one string
|
||||||
|
* \param message
|
||||||
|
* Message describing what went wrong
|
||||||
|
*/
|
||||||
|
explicit Exception(std::string message) : m_Msg(std::move(message))
|
||||||
|
{}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Default destructor
|
||||||
|
*/
|
||||||
|
~Exception() override = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Gets what happened
|
||||||
|
* \return
|
||||||
|
* Returns a c-style string of what happened
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const char *what() const noexcept override
|
||||||
|
{
|
||||||
|
return m_Msg.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::string m_Msg; //!< Message of what happened
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //CSYS_CSYS_EXCEPTIONS_H
|
||||||
152
include/csys/history.h
Normal file
152
include/csys/history.h
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#ifndef CSYS_HISTORY_H
|
||||||
|
#define CSYS_HISTORY_H
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "csys/api.h"
|
||||||
|
|
||||||
|
namespace csys
|
||||||
|
{
|
||||||
|
class CSYS_API CommandHistory
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Command history constructor.
|
||||||
|
* \param maxRecord
|
||||||
|
* Maximum amount of command strings to keep track at once.
|
||||||
|
*/
|
||||||
|
explicit CommandHistory(unsigned int maxRecord = 100);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Move constructor
|
||||||
|
* \param rhs
|
||||||
|
* History to be copied.
|
||||||
|
*/
|
||||||
|
CommandHistory(CommandHistory &&rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Copy constructor
|
||||||
|
* \param rhs
|
||||||
|
* History to be copied.
|
||||||
|
*/
|
||||||
|
CommandHistory(const CommandHistory &rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Move assignment operator
|
||||||
|
* \param rhs
|
||||||
|
* History to be copied.
|
||||||
|
*/
|
||||||
|
CommandHistory &operator=(CommandHistory &&rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Copy assigment operator.
|
||||||
|
* \param rhs
|
||||||
|
* History to be copied.
|
||||||
|
*/
|
||||||
|
CommandHistory &operator=(const CommandHistory &rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Record command string. (Start at the beginning once end is reached).
|
||||||
|
* \param line
|
||||||
|
* Command string to be recorded.
|
||||||
|
*/
|
||||||
|
void PushBack(const std::string &line);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Get newest register command entry index
|
||||||
|
* \return
|
||||||
|
* Newest command entry index
|
||||||
|
*/
|
||||||
|
[[nodiscard]] unsigned int GetNewIndex() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Get newest register command entry
|
||||||
|
* \return
|
||||||
|
* Newest command entry
|
||||||
|
*/
|
||||||
|
const std::string &GetNew();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Get oldest register command entry index
|
||||||
|
* \return
|
||||||
|
* Oldest command entry index
|
||||||
|
*/
|
||||||
|
[[nodiscard]] unsigned int GetOldIndex() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Get oldest register command entry
|
||||||
|
* \return
|
||||||
|
* Oldest command entry string
|
||||||
|
*/
|
||||||
|
const std::string &GetOld();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Clear command history
|
||||||
|
*/
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Retrieve command history at given index
|
||||||
|
* \param index
|
||||||
|
* Position to lookup in command history vector
|
||||||
|
* \return
|
||||||
|
* Command at given index
|
||||||
|
*
|
||||||
|
* \note
|
||||||
|
* No bound checking is performed when accessing with these index operator.
|
||||||
|
* Use the *index()* method for safe history vector indexing.
|
||||||
|
*/
|
||||||
|
const std::string &operator[](size_t index);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Output available command history.
|
||||||
|
* \param os
|
||||||
|
* Output stream
|
||||||
|
* \param history
|
||||||
|
* Reference to history to be printed
|
||||||
|
* \return
|
||||||
|
* Reference to history to be printed
|
||||||
|
*/
|
||||||
|
friend std::ostream &operator<<(std::ostream &os, const CommandHistory &history);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \return
|
||||||
|
* Number of registered commands.
|
||||||
|
*/
|
||||||
|
size_t Size();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \return
|
||||||
|
* Maximum commands that are able to be recorded
|
||||||
|
*/
|
||||||
|
size_t Capacity();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
unsigned int m_Record; //!< Amount of commands recorded
|
||||||
|
unsigned int m_MaxRecord; //!< Maximum command record to keep track of
|
||||||
|
std::vector<std::string> m_History; //!< Console command history
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CSYS_HEADER_ONLY
|
||||||
|
#include "csys/history.inl"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif //CSYS_HISTORY_H
|
||||||
83
include/csys/history.inl
Normal file
83
include/csys/history.inl
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CSYS_HEADER_ONLY
|
||||||
|
|
||||||
|
#include "csys/history.h"
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace csys
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Public methods /////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
CSYS_INLINE CommandHistory::CommandHistory(unsigned int maxRecord) : m_Record(0), m_MaxRecord(maxRecord), m_History(maxRecord)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void CommandHistory::PushBack(const std::string &line)
|
||||||
|
{
|
||||||
|
m_History[m_Record++ % m_MaxRecord] = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE unsigned int CommandHistory::GetNewIndex() const
|
||||||
|
{
|
||||||
|
return (m_Record - 1) % m_MaxRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE const std::string &CommandHistory::GetNew()
|
||||||
|
{
|
||||||
|
return m_History[(m_Record - 1) % m_MaxRecord];
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE unsigned int CommandHistory::GetOldIndex() const
|
||||||
|
{
|
||||||
|
if (m_Record <= m_MaxRecord)
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
return m_Record % m_MaxRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE const std::string &CommandHistory::GetOld()
|
||||||
|
{
|
||||||
|
if (m_Record <= m_MaxRecord)
|
||||||
|
return m_History.front();
|
||||||
|
else
|
||||||
|
return m_History[m_Record % m_MaxRecord];
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void CommandHistory::Clear()
|
||||||
|
{
|
||||||
|
m_Record = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE const std::string &CommandHistory::operator[](size_t index)
|
||||||
|
{
|
||||||
|
return m_History[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE std::ostream &operator<<(std::ostream &os, const CommandHistory &history)
|
||||||
|
{
|
||||||
|
os << "History: " << '\n';
|
||||||
|
for (unsigned int i = 0; i < history.m_Record && history.m_Record <= history.m_MaxRecord; ++i)
|
||||||
|
std::cout << history.m_History[i] << '\n';
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE size_t CommandHistory::Size()
|
||||||
|
{
|
||||||
|
return m_Record < m_MaxRecord ? m_Record : m_MaxRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE size_t CommandHistory::Capacity()
|
||||||
|
{
|
||||||
|
return m_History.capacity();
|
||||||
|
}
|
||||||
|
}
|
||||||
197
include/csys/item.h
Normal file
197
include/csys/item.h
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#ifndef CSYS_ITEM_H
|
||||||
|
#define CSYS_ITEM_H
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include "csys/api.h"
|
||||||
|
|
||||||
|
namespace csys
|
||||||
|
{
|
||||||
|
static const char endl = '\n';
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Console item type:
|
||||||
|
* - Command: Only used for commands.
|
||||||
|
* - Log: Used to log information inside a command.
|
||||||
|
* - Warning: Warn client through console.
|
||||||
|
* - Error: Display error to client through console.
|
||||||
|
* - Info: Any information wished to display through console.
|
||||||
|
* - None: Empty console item.
|
||||||
|
*/
|
||||||
|
enum ItemType
|
||||||
|
{
|
||||||
|
eCOMMAND = 0,
|
||||||
|
eLOG,
|
||||||
|
eWARNING,
|
||||||
|
eERROR,
|
||||||
|
eINFO,
|
||||||
|
eNONE
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CSYS_API Item
|
||||||
|
{
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Create console item type
|
||||||
|
* \param type
|
||||||
|
* Item type to be stored
|
||||||
|
*/
|
||||||
|
explicit Item(ItemType type = ItemType::eLOG);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Move constructor
|
||||||
|
* \param rhs
|
||||||
|
* Item to be copied.
|
||||||
|
*/
|
||||||
|
Item(Item &&rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Copy constructor
|
||||||
|
* \param rhs
|
||||||
|
* Item to be copied.
|
||||||
|
*/
|
||||||
|
Item(const Item &rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Move assignment operator
|
||||||
|
* \param rhs
|
||||||
|
* Item to be copied.
|
||||||
|
*/
|
||||||
|
Item &operator=(Item &&rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Copy assigment operator.
|
||||||
|
* \param rhs
|
||||||
|
* Item to be copied.
|
||||||
|
*/
|
||||||
|
Item &operator=(const Item &rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Log data to console item
|
||||||
|
* \param str
|
||||||
|
* Append string to item data
|
||||||
|
* \return
|
||||||
|
* Self (To allow for fluent logging)
|
||||||
|
*/
|
||||||
|
Item &operator<<(std::string_view str);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Get final/styled string of the item
|
||||||
|
* \return
|
||||||
|
* Stylized item string
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::string Get() const;
|
||||||
|
|
||||||
|
ItemType m_Type; //!< Console item type
|
||||||
|
std::string m_Data; //!< Item string data
|
||||||
|
unsigned int m_TimeStamp; //!< Record timestamp
|
||||||
|
};
|
||||||
|
|
||||||
|
#define LOG_BASIC_TYPE_DECL(type) ItemLog& operator<<(type data)
|
||||||
|
|
||||||
|
class CSYS_API ItemLog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Log console item
|
||||||
|
* \param type
|
||||||
|
* Type of item to log
|
||||||
|
* \return
|
||||||
|
* Self (To allow for fluent logging)
|
||||||
|
*/
|
||||||
|
ItemLog &log(ItemType type);
|
||||||
|
|
||||||
|
ItemLog() = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Move constructor
|
||||||
|
* \param rhs
|
||||||
|
* ItemLog to be copied.
|
||||||
|
*/
|
||||||
|
ItemLog(ItemLog &&rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Copy constructor
|
||||||
|
* \param rhs
|
||||||
|
* ItemLog to be copied.
|
||||||
|
*/
|
||||||
|
ItemLog(const ItemLog &rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Move assignment operator
|
||||||
|
* \param rhs
|
||||||
|
* ItemLog to be copied.
|
||||||
|
*/
|
||||||
|
ItemLog &operator=(ItemLog &&rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Copy assigment operator.
|
||||||
|
* \param rhs
|
||||||
|
* ItemLog to be copied.
|
||||||
|
*/
|
||||||
|
ItemLog &operator=(const ItemLog &rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Get logged console items
|
||||||
|
* \return
|
||||||
|
* Console log
|
||||||
|
*/
|
||||||
|
std::vector<Item> &Items();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Delete console item log history
|
||||||
|
*/
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DECL(int);
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DECL(long);
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DECL(float);
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DECL(double);
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DECL(long long);
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DECL(long double);
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DECL(unsigned int);
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DECL(unsigned long);
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DECL(unsigned long long);
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DECL(std::string_view);
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DECL(char);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<Item> m_Items;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CSYS_HEADER_ONLY
|
||||||
|
|
||||||
|
#include "csys/item.inl"
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
116
include/csys/item.inl
Normal file
116
include/csys/item.inl
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CSYS_HEADER_ONLY
|
||||||
|
|
||||||
|
#include "csys/item.h"
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
namespace csys
|
||||||
|
{
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Console Item ///////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
CSYS_INLINE static const std::string_view s_Command = "> ";
|
||||||
|
CSYS_INLINE static const std::string_view s_Warning = "\t[WARNING]: ";
|
||||||
|
CSYS_INLINE static const std::string_view s_Error = "[ERROR]: ";
|
||||||
|
CSYS_INLINE static const auto s_TimeBegin = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
CSYS_INLINE Item::Item(ItemType type) : m_Type(type)
|
||||||
|
{
|
||||||
|
auto timeNow = std::chrono::steady_clock::now();
|
||||||
|
m_TimeStamp = static_cast<unsigned int>(std::chrono::duration_cast<std::chrono::milliseconds>(timeNow - s_TimeBegin).count());
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE Item &Item::operator<<(const std::string_view str)
|
||||||
|
{
|
||||||
|
m_Data.append(str);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE std::string Item::Get() const
|
||||||
|
{
|
||||||
|
switch (m_Type)
|
||||||
|
{
|
||||||
|
case eCOMMAND:
|
||||||
|
return s_Command.data() + m_Data;
|
||||||
|
case eLOG:
|
||||||
|
return '\t' + m_Data;
|
||||||
|
case eWARNING:
|
||||||
|
return s_Warning.data() + m_Data;
|
||||||
|
case eERROR:
|
||||||
|
return s_Error.data() + m_Data;
|
||||||
|
case eINFO:
|
||||||
|
return m_Data;
|
||||||
|
case eNONE:
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Console Item Log ///////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#define LOG_BASIC_TYPE_DEF(type)\
|
||||||
|
CSYS_INLINE ItemLog& ItemLog::operator<<(type data)\
|
||||||
|
{\
|
||||||
|
m_Items.back() << std::to_string(data);\
|
||||||
|
return *this;\
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE ItemLog &ItemLog::log(ItemType type)
|
||||||
|
{
|
||||||
|
// New item.
|
||||||
|
m_Items.emplace_back(type);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE std::vector<Item> &ItemLog::Items()
|
||||||
|
{
|
||||||
|
return m_Items;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void ItemLog::Clear()
|
||||||
|
{
|
||||||
|
m_Items.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE ItemLog &ItemLog::operator<<(const std::string_view data)
|
||||||
|
{
|
||||||
|
m_Items.back() << data;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE ItemLog &ItemLog::operator<<(const char data)
|
||||||
|
{
|
||||||
|
m_Items.back().m_Data.append(1, data);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic type operator definitions.
|
||||||
|
LOG_BASIC_TYPE_DEF(int)
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DEF(long)
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DEF(float)
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DEF(double)
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DEF(long long)
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DEF(long double)
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DEF(unsigned int)
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DEF(unsigned long)
|
||||||
|
|
||||||
|
LOG_BASIC_TYPE_DEF(unsigned long long)
|
||||||
|
}
|
||||||
120
include/csys/script.h
Normal file
120
include/csys/script.h
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#ifndef CSYS_SCRIPT_H
|
||||||
|
#define CSYS_SCRIPT_H
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "csys/api.h"
|
||||||
|
|
||||||
|
namespace csys
|
||||||
|
{
|
||||||
|
class CSYS_API Script
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Create script object from file
|
||||||
|
* \param path
|
||||||
|
* Path of script file
|
||||||
|
* \param load_on_init
|
||||||
|
* Load script when object is created
|
||||||
|
*/
|
||||||
|
explicit Script(std::string path, bool load_on_init = true);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Create script object from file
|
||||||
|
* \param path
|
||||||
|
* Path of script file
|
||||||
|
* \param load_on_init
|
||||||
|
* Load script when object is created
|
||||||
|
*/
|
||||||
|
explicit Script(const char *path, bool load_on_init = true);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Create script object from file already in memory
|
||||||
|
* \param data
|
||||||
|
* Script file memory
|
||||||
|
*/
|
||||||
|
explicit Script(std::vector<std::string> data);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Move constructor
|
||||||
|
* \param rhs
|
||||||
|
* Script to be copied.
|
||||||
|
*/
|
||||||
|
Script(Script &&rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Copy constructor
|
||||||
|
* \param rhs
|
||||||
|
* Script to be copied.
|
||||||
|
*/
|
||||||
|
Script(const Script &rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Move assignment operator
|
||||||
|
* \param rhs
|
||||||
|
* Script to be copied.
|
||||||
|
*/
|
||||||
|
Script &operator=(Script &&rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Copy assigment operator.
|
||||||
|
* \param rhs
|
||||||
|
* Script to be copied.
|
||||||
|
*/
|
||||||
|
Script &operator=(const Script &rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Load script file
|
||||||
|
*/
|
||||||
|
void Load();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Reload script file (Unload & Load)
|
||||||
|
*/
|
||||||
|
void Reload();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* /brief Clear script data
|
||||||
|
*/
|
||||||
|
void Unload();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Set script file path (Will be used when laoding)
|
||||||
|
* \param path
|
||||||
|
* Script file path
|
||||||
|
*/
|
||||||
|
void SetPath(std::string path);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Retrieve script data (Commands)
|
||||||
|
* \return
|
||||||
|
* List of commands in script
|
||||||
|
*/
|
||||||
|
const std::vector<std::string> &Data();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<std::string> m_Data; //!< Commands in script
|
||||||
|
std::string m_Path; //!< Path of script file
|
||||||
|
bool m_FromMemory; //!< Flag to specify if script was loaded from file or memory
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CSYS_HEADER_ONLY
|
||||||
|
#include "csys/script.inl"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
83
include/csys/script.inl
Normal file
83
include/csys/script.inl
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CSYS_HEADER_ONLY
|
||||||
|
|
||||||
|
#include "csys/script.h"
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <utility>
|
||||||
|
#include "csys/exceptions.h"
|
||||||
|
|
||||||
|
namespace csys
|
||||||
|
{
|
||||||
|
|
||||||
|
CSYS_INLINE Script::Script(std::string path, bool load_on_init) : m_Path(std::move(path)), m_FromMemory(false)
|
||||||
|
{
|
||||||
|
// Load file.
|
||||||
|
if (load_on_init)
|
||||||
|
Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE Script::Script(const char *path, bool load_on_init) : m_Path(path), m_FromMemory(false)
|
||||||
|
{
|
||||||
|
// Load file.
|
||||||
|
if (load_on_init)
|
||||||
|
Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE Script::Script(std::vector<std::string> data) : m_Data(std::move(data)), m_FromMemory(true)
|
||||||
|
{}
|
||||||
|
|
||||||
|
CSYS_INLINE void Script::Load()
|
||||||
|
{
|
||||||
|
std::ifstream script_fstream(m_Path);
|
||||||
|
|
||||||
|
// Error check.
|
||||||
|
if (!script_fstream.good())
|
||||||
|
throw csys::Exception("Failed to load script", m_Path);
|
||||||
|
|
||||||
|
// Check and open file.
|
||||||
|
if (script_fstream.good() && script_fstream.is_open())
|
||||||
|
{
|
||||||
|
// Line buffer.
|
||||||
|
std::string line_buf;
|
||||||
|
|
||||||
|
// Read commands.
|
||||||
|
while (std::getline(script_fstream, line_buf))
|
||||||
|
{
|
||||||
|
m_Data.emplace_back(line_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close file.
|
||||||
|
script_fstream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void Script::Reload()
|
||||||
|
{
|
||||||
|
if (m_FromMemory) return;
|
||||||
|
|
||||||
|
Unload();
|
||||||
|
Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void Script::Unload()
|
||||||
|
{
|
||||||
|
m_Data.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void Script::SetPath(std::string path)
|
||||||
|
{
|
||||||
|
m_Path = std::move(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE const std::vector<std::string> &Script::Data()
|
||||||
|
{
|
||||||
|
return m_Data;
|
||||||
|
}
|
||||||
|
}
|
||||||
110
include/csys/string.h
Normal file
110
include/csys/string.h
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#ifndef CSYS_STRING_H
|
||||||
|
#define CSYS_STRING_H
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <cctype>
|
||||||
|
#include "csys/api.h"
|
||||||
|
|
||||||
|
namespace csys
|
||||||
|
{
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Wrapper around std::string to be used with parsing a string argument
|
||||||
|
*/
|
||||||
|
struct CSYS_API String
|
||||||
|
{
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Default constructor
|
||||||
|
*/
|
||||||
|
String() = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Conversion constructor from c-style string. A deep copy is made
|
||||||
|
* \param str
|
||||||
|
* String to be converted
|
||||||
|
*/
|
||||||
|
String(const char *str [[maybe_unused]]) : m_String(str ? str : "")
|
||||||
|
{}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Conversion constructor from an std::string to csys::String. A deep copy is made
|
||||||
|
* \param str
|
||||||
|
* String to be converted
|
||||||
|
*/
|
||||||
|
String(std::string str) : m_String(std::move(str))
|
||||||
|
{}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Conversion constructor from csys::String to a c-style string
|
||||||
|
* \return
|
||||||
|
* Returns a pointer to the string contained within this string
|
||||||
|
*/
|
||||||
|
operator const char *()
|
||||||
|
{ return m_String.c_str(); }
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Conversion constructor from csys::String to an std::string
|
||||||
|
* \return
|
||||||
|
* Returns a copy of this string
|
||||||
|
*/
|
||||||
|
operator std::string()
|
||||||
|
{ return m_String; }
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Moves until first non-whitespace char, and continues until the end of the string or a whitespace has is
|
||||||
|
* found
|
||||||
|
* \param start
|
||||||
|
* Where to start scanning from. Will be set to pair.second
|
||||||
|
* \return
|
||||||
|
* Returns the first element and one passed the end of non-whitespace. In other words [first, second)
|
||||||
|
*/
|
||||||
|
std::pair<size_t, size_t> NextPoi(size_t &start) const
|
||||||
|
{
|
||||||
|
size_t end = m_String.size();
|
||||||
|
std::pair<size_t, size_t> range(end + 1, end);
|
||||||
|
size_t pos = start;
|
||||||
|
|
||||||
|
// Go to the first non-whitespace char
|
||||||
|
for (; pos < end; ++pos)
|
||||||
|
if (!std::isspace(m_String[pos]))
|
||||||
|
{
|
||||||
|
range.first = pos;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go to the first whitespace char
|
||||||
|
for (; pos < end; ++pos)
|
||||||
|
if (std::isspace(m_String[pos]))
|
||||||
|
{
|
||||||
|
range.second = pos;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
start = range.second;
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Returns a value to be compared with the .first of the pair returned from NextPoi
|
||||||
|
* \return
|
||||||
|
* Returns size of string + 1
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_t End() const
|
||||||
|
{ return m_String.size() + 1; }
|
||||||
|
|
||||||
|
std::string m_String; //!< String data member
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //CSYS_CSYS_STRING_H
|
||||||
356
include/csys/system.h
Normal file
356
include/csys/system.h
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#ifndef CSYS_SYSTEM_H
|
||||||
|
#define CSYS_SYSTEM_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "csys/command.h"
|
||||||
|
#include "csys/autocomplete.h"
|
||||||
|
#include "csys/history.h"
|
||||||
|
#include "csys/item.h"
|
||||||
|
#include "csys/script.h"
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace csys
|
||||||
|
{
|
||||||
|
class CSYS_API System
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Initialize system object
|
||||||
|
*/
|
||||||
|
System();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Move constructor
|
||||||
|
* \param rhs
|
||||||
|
* System to be copied.
|
||||||
|
*/
|
||||||
|
System(System &&rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Copy constructor
|
||||||
|
* \param rhs
|
||||||
|
* System to be copied.
|
||||||
|
*/
|
||||||
|
System(const System &rhs);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Move assignment operator
|
||||||
|
* \param rhs
|
||||||
|
* System to be copied.
|
||||||
|
*/
|
||||||
|
System &operator=(System &&rhs) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Copy assigment operator.
|
||||||
|
* \param rhs
|
||||||
|
* System to be copied.
|
||||||
|
*/
|
||||||
|
System &operator=(const System &rhs);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Parse given command line input and run it
|
||||||
|
* \param line
|
||||||
|
* Command line string
|
||||||
|
*/
|
||||||
|
void RunCommand(const std::string &line);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Get console registered command autocomplete tree
|
||||||
|
* \return
|
||||||
|
* Autocomplete Ternary Search Tree
|
||||||
|
*/
|
||||||
|
AutoComplete &CmdAutocomplete();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Get console registered variables autocomplete tree
|
||||||
|
* \return
|
||||||
|
* Autocomplete Ternary Search Tree
|
||||||
|
*/
|
||||||
|
AutoComplete &VarAutocomplete();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Get command history container
|
||||||
|
* \return
|
||||||
|
* Command history vector
|
||||||
|
*/
|
||||||
|
CommandHistory &History();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Get console items
|
||||||
|
* \return
|
||||||
|
* Console items container
|
||||||
|
*/
|
||||||
|
std::vector<Item> &Items();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Creates a new item entry to log information
|
||||||
|
* \param type
|
||||||
|
* Log type (COMMAND, LOG, WARNING, ERROR)
|
||||||
|
* \return
|
||||||
|
* Reference to console items obj
|
||||||
|
*/
|
||||||
|
ItemLog &Log(ItemType type = ItemType::eLOG);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Run the given script
|
||||||
|
* \param script_name
|
||||||
|
* Script to be executed
|
||||||
|
*
|
||||||
|
* \note
|
||||||
|
* If script exists but its not loaded, this methods will load the script and proceed to run it.
|
||||||
|
*/
|
||||||
|
void RunScript(const std::string &script_name);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Get registered command container
|
||||||
|
* \return
|
||||||
|
* Commands container
|
||||||
|
*/
|
||||||
|
std::unordered_map<std::string, std::unique_ptr<CommandBase>> &Commands();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Get registered scripts container
|
||||||
|
* \return
|
||||||
|
* Scripts container
|
||||||
|
*/
|
||||||
|
std::unordered_map<std::string, std::unique_ptr<Script>> &Scripts();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Registers a command within the system to be invokable
|
||||||
|
* \tparam Fn
|
||||||
|
* Decltype of the function to invoke when command is ran
|
||||||
|
* \tparam Args
|
||||||
|
* List of arguments that match that of the argument list within the function Fn of type csys::Arg<T>
|
||||||
|
* \param name
|
||||||
|
* Non-whitespace separating name of the command. Whitespace will be dropped
|
||||||
|
* \param description
|
||||||
|
* Description describing what the command does
|
||||||
|
* \param function
|
||||||
|
* A non-member function to run when command is called
|
||||||
|
* \param args
|
||||||
|
* List of csys::Arg<T>s that matches that of the argument list of 'function'
|
||||||
|
*/
|
||||||
|
template<typename Fn, typename ...Args>
|
||||||
|
void RegisterCommand(const String &name, const String &description, Fn function, Args... args)
|
||||||
|
{
|
||||||
|
// Check if function can be called with the given arguments and is not part of a class
|
||||||
|
static_assert(std::is_invocable_v<Fn, typename Args::ValueType...>, "Arguments specified do not match that of the function");
|
||||||
|
static_assert(!std::is_member_function_pointer_v<Fn>, "Non-static member functions are not allowed");
|
||||||
|
|
||||||
|
// Move to command
|
||||||
|
size_t name_index = 0;
|
||||||
|
auto range = name.NextPoi(name_index);
|
||||||
|
|
||||||
|
// Command already registered
|
||||||
|
if (m_Commands.find(name.m_String) != m_Commands.end())
|
||||||
|
throw csys::Exception("ERROR: Command already exists");
|
||||||
|
|
||||||
|
// Check if command has a name
|
||||||
|
else if (range.first == name.End())
|
||||||
|
{
|
||||||
|
Log(eERROR) << "Empty command name given" << csys::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get command name
|
||||||
|
std::string command_name = name.m_String.substr(range.first, range.second - range.first);
|
||||||
|
|
||||||
|
// Command contains more than one word
|
||||||
|
if (name.NextPoi(name_index).first != name.End())
|
||||||
|
throw csys::Exception("ERROR: Whitespace separated command names are forbidden");
|
||||||
|
|
||||||
|
// Register for autocomplete.
|
||||||
|
if (m_RegisterCommandSuggestion)
|
||||||
|
{
|
||||||
|
m_CommandSuggestionTree.Insert(command_name);
|
||||||
|
m_VariableSuggestionTree.Insert(command_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add commands to system
|
||||||
|
m_Commands[name.m_String] = std::make_unique<Command<Fn, Args...>>(name, description, function, args...);
|
||||||
|
|
||||||
|
// Make help command for command just added
|
||||||
|
auto help = [this, command_name]() {
|
||||||
|
Log(eLOG) << m_Commands[command_name]->Help() << csys::endl;
|
||||||
|
};
|
||||||
|
|
||||||
|
m_Commands["help " + command_name] = std::make_unique<Command<decltype(help)>>("help " + command_name,
|
||||||
|
"Displays help info about command " +
|
||||||
|
command_name, help);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Register's a variable within the system
|
||||||
|
* \tparam T
|
||||||
|
* Type of the variable
|
||||||
|
* \tparam Types
|
||||||
|
* Type of arguments that type T can be constructed with
|
||||||
|
* \param name
|
||||||
|
* Name of the variable
|
||||||
|
* \param var
|
||||||
|
* The variable to register
|
||||||
|
* \param args
|
||||||
|
* List of csys::Arg to be used for the construction of type T
|
||||||
|
* \note
|
||||||
|
* Type T requires an assignment operator, and constructor that takes type 'Types...'
|
||||||
|
* Param 'var' is assumed to have a valid life-time up until it is unregistered or the program ends
|
||||||
|
*/
|
||||||
|
template<typename T, typename ...Types>
|
||||||
|
void RegisterVariable(const String &name, T &var, Arg<Types>... args)
|
||||||
|
{
|
||||||
|
static_assert(std::is_constructible_v<T, Types...>, "Type of var 'T' can not be constructed with types of 'Types'");
|
||||||
|
static_assert(sizeof... (Types) != 0, "Empty variadic list");
|
||||||
|
|
||||||
|
// Register get command
|
||||||
|
auto var_name = RegisterVariableAux(name, var);
|
||||||
|
|
||||||
|
// Register set command
|
||||||
|
auto setter = [&var](Types... params){ var = T(params...); };
|
||||||
|
m_Commands["set " + var_name] = std::make_unique<Command<decltype(setter), Arg<Types>...>>("set " + var_name,
|
||||||
|
"Sets the variable " + var_name,
|
||||||
|
setter, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Register's a variable within the system
|
||||||
|
* \tparam T
|
||||||
|
* Type of the variable
|
||||||
|
* \tparam Types
|
||||||
|
* Type of arguments that type T can be constructed with
|
||||||
|
* \param name
|
||||||
|
* Name of the variable
|
||||||
|
* \param var
|
||||||
|
* The variable to register
|
||||||
|
* \param setter
|
||||||
|
* Custom setter that runs when command set 'name' is invoked
|
||||||
|
* \note
|
||||||
|
* The setter must have dceltype of void(decltype(var)&, Types...)
|
||||||
|
* Param 'var' is assumed to have a valid life-time up until it is unregistered or the program ends
|
||||||
|
*/
|
||||||
|
template<typename T, typename ...Types>
|
||||||
|
void RegisterVariable(const String &name, T &var, void(*setter)(T&, Types...))
|
||||||
|
{
|
||||||
|
// Register get command
|
||||||
|
auto var_name = RegisterVariableAux(name, var);
|
||||||
|
|
||||||
|
// Register set command
|
||||||
|
auto setter_l = [&var, setter](Types... args){ setter(var, args...); };
|
||||||
|
m_Commands["set " + var_name] = std::make_unique<Command<decltype(setter_l), Arg<Types>...>>("set " + var_name,
|
||||||
|
"Sets the variable " + var_name,
|
||||||
|
setter_l, Arg<Types>("")...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Register script into console system
|
||||||
|
* \param name
|
||||||
|
* Script name
|
||||||
|
* \param path
|
||||||
|
* Scrip path
|
||||||
|
*/
|
||||||
|
void RegisterScript(const std::string &name, const std::string &path);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Unregister command from console system
|
||||||
|
* \param cmd_name
|
||||||
|
* Command to unregister
|
||||||
|
*/
|
||||||
|
void UnregisterCommand(const std::string &cmd_name);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Unregister variable from console system
|
||||||
|
* \param var_name
|
||||||
|
* Variable to unregister
|
||||||
|
*/
|
||||||
|
void UnregisterVariable(const std::string &var_name);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief
|
||||||
|
* Unregister script from console system
|
||||||
|
* \param script_name
|
||||||
|
* Script to unregister
|
||||||
|
*/
|
||||||
|
void UnregisterScript(const std::string &script_name);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template<typename T>
|
||||||
|
std::string RegisterVariableAux(const String &name, T &var)
|
||||||
|
{
|
||||||
|
// Disable.
|
||||||
|
m_RegisterCommandSuggestion = false;
|
||||||
|
|
||||||
|
// Make sure only one word was passed in
|
||||||
|
size_t name_index = 0;
|
||||||
|
auto range = name.NextPoi(name_index);
|
||||||
|
if (name.NextPoi(name_index).first != name.End())
|
||||||
|
throw csys::Exception("ERROR: Whitespace separated variable names are forbidden");
|
||||||
|
|
||||||
|
// Get variable name
|
||||||
|
std::string var_name = name.m_String.substr(range.first, range.second - range.first);
|
||||||
|
|
||||||
|
// Get Command
|
||||||
|
const auto GetFunction = [this, &var]() {
|
||||||
|
m_ItemLog.log(eLOG) << var << endl;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register get command
|
||||||
|
m_Commands["get " + var_name] = std::make_unique<Command<decltype(GetFunction)>>("get " + var_name,
|
||||||
|
"Gets the variable " +
|
||||||
|
var_name, GetFunction);
|
||||||
|
|
||||||
|
// Enable again.
|
||||||
|
m_RegisterCommandSuggestion = true;
|
||||||
|
|
||||||
|
// Register variable
|
||||||
|
m_VariableSuggestionTree.Insert(var_name);
|
||||||
|
|
||||||
|
return var_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParseCommandLine(const String &line); //!< Parse command line and execute command
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::unique_ptr<CommandBase>> m_Commands; //!< Registered command container
|
||||||
|
AutoComplete m_CommandSuggestionTree; //!< Autocomplete Ternary Search Tree for commands
|
||||||
|
AutoComplete m_VariableSuggestionTree; //!< Autocomplete Ternary Search Tree for registered variables
|
||||||
|
CommandHistory m_CommandHistory; //!< History of executed commands
|
||||||
|
ItemLog m_ItemLog; //!< Console Items (Logging)
|
||||||
|
std::unordered_map<std::string, std::unique_ptr<Script>> m_Scripts; //!< Scripts
|
||||||
|
bool m_RegisterCommandSuggestion = true; //!< Flag that determines if commands will be registered for autocomplete.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CSYS_HEADER_ONLY
|
||||||
|
#include "csys/system.inl"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// void (T&, int, float...);
|
||||||
|
|
||||||
|
// registaerVariable("name", var, Arg<int>...); //c
|
||||||
|
// registaerVariable("name", var, setter); //d
|
||||||
300
include/csys/system.inl
Normal file
300
include/csys/system.inl
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CSYS_HEADER_ONLY
|
||||||
|
|
||||||
|
#include "csys/system.h"
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace csys
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Public methods /////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Commands-Error-Warning strings.
|
||||||
|
static const std::string_view s_Set = "set";
|
||||||
|
static const std::string_view s_Get = "get";
|
||||||
|
static const std::string_view s_Help = "help";
|
||||||
|
static const std::string_view s_ErrorNoVar = "No variable provided";
|
||||||
|
static const std::string_view s_ErrorSetGetNotFound = "Command doesn't exist and/or variable is not registered";
|
||||||
|
|
||||||
|
CSYS_INLINE System::System()
|
||||||
|
{
|
||||||
|
// Register help command.
|
||||||
|
RegisterCommand(s_Help.data(), "Display commands information", [this]()
|
||||||
|
{
|
||||||
|
// Custom command information display
|
||||||
|
Log() << "help [command_name:String] (Optional)\n\t\t- Display command(s) information\n" << csys::endl;
|
||||||
|
Log() << "set [variable_name:String] [data]\n\t\t- Assign data to given variable\n" << csys::endl;
|
||||||
|
Log() << "get [variable_name:String]\n\t\t- Display data of given variable\n" << csys::endl;
|
||||||
|
|
||||||
|
for (const auto &tuple : Commands())
|
||||||
|
{
|
||||||
|
// Filter set and get.
|
||||||
|
if (tuple.first.size() >= 5 && (tuple.first[3] == ' ' || tuple.first[4] == ' '))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Skip help command.
|
||||||
|
if (tuple.first.size() == 4 && (tuple.first == "help"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Print the rest of commands
|
||||||
|
Log() << tuple.second->Help();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register pre-defined commands.
|
||||||
|
m_CommandSuggestionTree.Insert(s_Set.data());
|
||||||
|
m_CommandSuggestionTree.Insert(s_Get.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE System::System(const System &rhs) : m_CommandSuggestionTree(rhs.m_CommandSuggestionTree),
|
||||||
|
m_VariableSuggestionTree(rhs.m_VariableSuggestionTree),
|
||||||
|
m_CommandHistory(rhs.m_CommandHistory),
|
||||||
|
m_ItemLog(rhs.m_ItemLog),
|
||||||
|
m_RegisterCommandSuggestion(rhs.m_RegisterCommandSuggestion)
|
||||||
|
{
|
||||||
|
// Copy commands.
|
||||||
|
for (const auto &pair : rhs.m_Commands)
|
||||||
|
{
|
||||||
|
m_Commands[pair.first] = std::unique_ptr<CommandBase>(pair.second->Clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy scripts.
|
||||||
|
for (const auto &pair: rhs.m_Scripts)
|
||||||
|
{
|
||||||
|
m_Scripts[pair.first] = std::make_unique<Script>(*pair.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE System &System::operator=(const System &rhs)
|
||||||
|
{
|
||||||
|
if (this == &rhs)
|
||||||
|
return *this;
|
||||||
|
|
||||||
|
// Copy commands.
|
||||||
|
for (const auto &pair : rhs.m_Commands)
|
||||||
|
{
|
||||||
|
m_Commands[pair.first] = std::unique_ptr<CommandBase>(pair.second->Clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other data.
|
||||||
|
m_CommandSuggestionTree = rhs.m_CommandSuggestionTree;
|
||||||
|
m_VariableSuggestionTree = rhs.m_VariableSuggestionTree;
|
||||||
|
m_CommandHistory = rhs.m_CommandHistory;
|
||||||
|
m_ItemLog = rhs.m_ItemLog;
|
||||||
|
|
||||||
|
// Copy scripts.
|
||||||
|
for (const auto &pair: rhs.m_Scripts)
|
||||||
|
{
|
||||||
|
m_Scripts[pair.first] = std::make_unique<Script>(*pair.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rest of data.
|
||||||
|
m_RegisterCommandSuggestion = rhs.m_RegisterCommandSuggestion;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void System::RunCommand(const std::string &line)
|
||||||
|
{
|
||||||
|
// Error checking.
|
||||||
|
if (line.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Log command.
|
||||||
|
Log(csys::ItemType::eCOMMAND) << line << csys::endl;
|
||||||
|
|
||||||
|
// Parse command line.
|
||||||
|
ParseCommandLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void System::RunScript(const std::string &script_name)
|
||||||
|
{
|
||||||
|
// Attempt to find script.
|
||||||
|
auto script_pair = m_Scripts.find(script_name);
|
||||||
|
|
||||||
|
// Exit if not found.
|
||||||
|
if (script_pair == m_Scripts.end())
|
||||||
|
{
|
||||||
|
m_ItemLog.log(eERROR) << "Script \"" << script_name << "\" not found" << csys::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// About to run script.
|
||||||
|
m_ItemLog.log(eINFO) << "Running \"" << script_name << "\"" << csys::endl;
|
||||||
|
|
||||||
|
// Load if script is empty.
|
||||||
|
if (script_pair->second->Data().empty())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
script_pair->second->Load();
|
||||||
|
}
|
||||||
|
catch (csys::Exception &e)
|
||||||
|
{
|
||||||
|
Log(eERROR) << e.what() << csys::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run script.
|
||||||
|
for (const auto &cmd : script_pair->second->Data())
|
||||||
|
{
|
||||||
|
// Parse command.
|
||||||
|
RunCommand(cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void System::RegisterScript(const std::string &name, const std::string &path)
|
||||||
|
{
|
||||||
|
// Attempt to find scripts.
|
||||||
|
auto script = m_Scripts.find(name);
|
||||||
|
|
||||||
|
// Don't register if script already exists.
|
||||||
|
if (script == m_Scripts.end())
|
||||||
|
{
|
||||||
|
m_Scripts[name] = std::make_unique<Script>(path, true);
|
||||||
|
m_VariableSuggestionTree.Insert(name);
|
||||||
|
} else
|
||||||
|
throw csys::Exception("ERROR: Script \'" + name + "\' already registered");
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void System::UnregisterCommand(const std::string &cmd_name)
|
||||||
|
{
|
||||||
|
// Exit if non existent.
|
||||||
|
if (cmd_name.empty()) return;
|
||||||
|
|
||||||
|
// Get command.
|
||||||
|
auto command_it = m_Commands.find(cmd_name);
|
||||||
|
auto help_command_it = m_Commands.find("help " + cmd_name);
|
||||||
|
|
||||||
|
// Erase if found.
|
||||||
|
if (command_it != m_Commands.end() && help_command_it != m_Commands.end())
|
||||||
|
{
|
||||||
|
m_CommandSuggestionTree.Remove(cmd_name);
|
||||||
|
m_VariableSuggestionTree.Remove(cmd_name);
|
||||||
|
|
||||||
|
m_Commands.erase(command_it);
|
||||||
|
m_Commands.erase(help_command_it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void System::UnregisterVariable(const std::string &var_name)
|
||||||
|
{
|
||||||
|
// Exit if non existent.
|
||||||
|
if (var_name.empty()) return;
|
||||||
|
|
||||||
|
// Get command.
|
||||||
|
auto s_it = m_Commands.find("set " + var_name);
|
||||||
|
auto g_it = m_Commands.find("get " + var_name);
|
||||||
|
|
||||||
|
// Erase if found.
|
||||||
|
if (s_it != m_Commands.end() && g_it != m_Commands.end())
|
||||||
|
{
|
||||||
|
m_VariableSuggestionTree.Remove(var_name);
|
||||||
|
m_Commands.erase(s_it);
|
||||||
|
m_Commands.erase(g_it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CSYS_INLINE void System::UnregisterScript(const std::string &script_name)
|
||||||
|
{
|
||||||
|
// Exit if non existent.
|
||||||
|
if (script_name.empty()) return;
|
||||||
|
|
||||||
|
// Get command.
|
||||||
|
auto it = m_Scripts.find(script_name);
|
||||||
|
|
||||||
|
// Erase if found.
|
||||||
|
if (it != m_Scripts.end())
|
||||||
|
{
|
||||||
|
m_VariableSuggestionTree.Remove(script_name);
|
||||||
|
m_Scripts.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters ////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
CSYS_INLINE AutoComplete &System::CmdAutocomplete() { return m_CommandSuggestionTree; }
|
||||||
|
|
||||||
|
CSYS_INLINE AutoComplete &System::VarAutocomplete() { return m_VariableSuggestionTree; }
|
||||||
|
|
||||||
|
CSYS_INLINE CommandHistory &System::History() { return m_CommandHistory; }
|
||||||
|
|
||||||
|
CSYS_INLINE std::vector<Item> &System::Items() { return m_ItemLog.Items(); }
|
||||||
|
|
||||||
|
CSYS_INLINE ItemLog &System::Log(ItemType type) { return m_ItemLog.log(type); }
|
||||||
|
|
||||||
|
CSYS_INLINE std::unordered_map<std::string, std::unique_ptr<CommandBase>> &System::Commands() { return m_Commands; }
|
||||||
|
|
||||||
|
CSYS_INLINE std::unordered_map<std::string, std::unique_ptr<Script>> &System::Scripts() { return m_Scripts; }
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Private methods ////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
CSYS_INLINE void System::ParseCommandLine(const String &line)
|
||||||
|
{
|
||||||
|
// Get first non-whitespace char.
|
||||||
|
size_t line_index = 0;
|
||||||
|
std::pair<size_t, size_t> range = line.NextPoi(line_index);
|
||||||
|
|
||||||
|
// Just whitespace was passed in. Don't log as command.
|
||||||
|
if (range.first == line.End())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Push to history.
|
||||||
|
m_CommandHistory.PushBack(line.m_String);
|
||||||
|
|
||||||
|
// Get name of command.
|
||||||
|
std::string command_name = line.m_String.substr(range.first, range.second - range.first);
|
||||||
|
|
||||||
|
// Set or get
|
||||||
|
bool is_cmd_set = command_name == s_Set;
|
||||||
|
bool is_cmd_get = command_name == s_Get;
|
||||||
|
bool is_cmd_help = !(is_cmd_set || is_cmd_get) ? command_name == s_Help : false;
|
||||||
|
|
||||||
|
// Edge case for if user is just runs "help" command
|
||||||
|
if (is_cmd_help)
|
||||||
|
{
|
||||||
|
range = line.NextPoi(line_index);
|
||||||
|
if (range.first != line.End())
|
||||||
|
command_name += " " + line.m_String.substr(range.first, range.second - range.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Its a set or get command
|
||||||
|
else if (is_cmd_set || is_cmd_get)
|
||||||
|
{
|
||||||
|
// Try to get variable name
|
||||||
|
if ((range = line.NextPoi(line_index)).first == line.End())
|
||||||
|
{
|
||||||
|
Log(eERROR) << s_ErrorNoVar << endl;
|
||||||
|
return;
|
||||||
|
} else
|
||||||
|
// Append variable name.
|
||||||
|
command_name += " " + line.m_String.substr(range.first, range.second - range.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get runnable command
|
||||||
|
auto command = m_Commands.find(command_name);
|
||||||
|
if (command == m_Commands.end())
|
||||||
|
Log(eERROR) << s_ErrorSetGetNotFound << endl;
|
||||||
|
// Run the command
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Get the arguments.
|
||||||
|
String arguments = line.m_String.substr(range.second, line.m_String.size() - range.first);
|
||||||
|
|
||||||
|
// Execute command.
|
||||||
|
auto cmd_out = (*command->second)(arguments);
|
||||||
|
|
||||||
|
// Log output.
|
||||||
|
if (cmd_out.m_Type != eNONE)
|
||||||
|
m_ItemLog.Items().emplace_back(cmd_out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
656
include/imgui/imgui_console.cpp
Normal file
656
include/imgui/imgui_console.cpp
Normal file
@ -0,0 +1,656 @@
|
|||||||
|
// Copyright (c) 2020 - present, Roland Munguia
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
//#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "imgui_console.h"
|
||||||
|
#include "imgui_internal.h"
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
// The following three functions (InputTextCallback_UserData, InputTextCallback, InputText) are obtained from misc/cpp/imgui_stdlib.h
|
||||||
|
// Which are licensed under MIT License (https://github.com/ocornut/imgui/blob/master/LICENSE.txt)
|
||||||
|
namespace ImGui {
|
||||||
|
struct InputTextCallback_UserData
|
||||||
|
{
|
||||||
|
std::string* Str;
|
||||||
|
ImGuiInputTextCallback ChainCallback;
|
||||||
|
void* ChainCallbackUserData;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int InputTextCallback(ImGuiInputTextCallbackData* data)
|
||||||
|
{
|
||||||
|
auto* user_data = (InputTextCallback_UserData*)data->UserData;
|
||||||
|
if (data->EventFlag == ImGuiInputTextFlags_CallbackResize)
|
||||||
|
{
|
||||||
|
// Resize string callback
|
||||||
|
// If for some reason we refuse the new length (BufTextLen) and/or capacity (BufSize) we need to set them back to what we want.
|
||||||
|
std::string* str = user_data->Str;
|
||||||
|
IM_ASSERT(data->Buf == str->c_str());
|
||||||
|
str->resize(data->BufTextLen);
|
||||||
|
data->Buf = (char*)str->c_str();
|
||||||
|
} else if (user_data->ChainCallback)
|
||||||
|
{
|
||||||
|
// Forward to user callback, if any
|
||||||
|
data->UserData = user_data->ChainCallbackUserData;
|
||||||
|
return user_data->ChainCallback(data);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputText(const char* label, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
|
||||||
|
{
|
||||||
|
IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
|
||||||
|
flags |= ImGuiInputTextFlags_CallbackResize;
|
||||||
|
|
||||||
|
InputTextCallback_UserData cb_user_data;
|
||||||
|
cb_user_data.Str = str;
|
||||||
|
cb_user_data.ChainCallback = callback;
|
||||||
|
cb_user_data.ChainCallbackUserData = user_data;
|
||||||
|
return InputText(label, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiConsole::ImGuiConsole(std::string c_name, size_t inputBufferSize) : m_ConsoleName(std::move(c_name))
|
||||||
|
{
|
||||||
|
// Set input buffer size.
|
||||||
|
m_Buffer.resize(inputBufferSize);
|
||||||
|
m_HistoryIndex = std::numeric_limits<size_t>::min();
|
||||||
|
|
||||||
|
// Specify custom data to be store/loaded from imgui.ini
|
||||||
|
InitIniSettings();
|
||||||
|
|
||||||
|
// Set Console ImGui default settings
|
||||||
|
if (!m_LoadedFromIni)
|
||||||
|
{
|
||||||
|
DefaultSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom functions.
|
||||||
|
RegisterConsoleCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiConsole::Draw()
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Window and Settings ////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Begin Console Window.
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_WindowAlpha);
|
||||||
|
if (!ImGui::Begin(m_ConsoleName.data(), nullptr, ImGuiWindowFlags_MenuBar))
|
||||||
|
{
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
|
||||||
|
///////////////
|
||||||
|
// Menu bar //
|
||||||
|
///////////////
|
||||||
|
MenuBar();
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// Filter bar //
|
||||||
|
////////////////
|
||||||
|
if (m_FilterBar)
|
||||||
|
{
|
||||||
|
FilterBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////
|
||||||
|
// Console Logs //
|
||||||
|
//////////////////
|
||||||
|
LogWindow();
|
||||||
|
|
||||||
|
// Section off.
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Command-line ///////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
InputBar();
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
csys::System& ImGuiConsole::System()
|
||||||
|
{
|
||||||
|
return m_ConsoleSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiConsole::InitIniSettings()
|
||||||
|
{
|
||||||
|
ImGuiContext& g = *ImGui::GetCurrentContext();
|
||||||
|
|
||||||
|
// Load from .ini
|
||||||
|
if (g.Initialized && !g.SettingsLoaded && !m_LoadedFromIni)
|
||||||
|
{
|
||||||
|
ImGuiSettingsHandler console_ini_handler;
|
||||||
|
console_ini_handler.TypeName = "imgui-console";
|
||||||
|
console_ini_handler.TypeHash = ImHashStr("imgui-console");
|
||||||
|
console_ini_handler.ClearAllFn = SettingsHandler_ClearALl;
|
||||||
|
console_ini_handler.ApplyAllFn = SettingsHandler_ApplyAll;
|
||||||
|
console_ini_handler.ReadInitFn = SettingsHandler_ReadInit;
|
||||||
|
console_ini_handler.ReadOpenFn = SettingsHandler_ReadOpen;
|
||||||
|
console_ini_handler.ReadLineFn = SettingsHandler_ReadLine;
|
||||||
|
console_ini_handler.WriteAllFn = SettingsHandler_WriteAll;
|
||||||
|
console_ini_handler.UserData = this;
|
||||||
|
g.SettingsHandlers.push_back(console_ini_handler);
|
||||||
|
}
|
||||||
|
// else Ini settings already loaded!
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiConsole::DefaultSettings()
|
||||||
|
{
|
||||||
|
// Settings
|
||||||
|
m_AutoScroll = true;
|
||||||
|
m_ScrollToBottom = false;
|
||||||
|
m_ColoredOutput = true;
|
||||||
|
m_FilterBar = true;
|
||||||
|
m_TimeStamps = true;
|
||||||
|
|
||||||
|
// Style
|
||||||
|
m_WindowAlpha = 1;
|
||||||
|
m_ColorPalette[COL_COMMAND] = ImVec4(1.f, 1.f, 1.f, 1.f);
|
||||||
|
m_ColorPalette[COL_LOG] = ImVec4(1.f, 1.f, 1.f, 0.5f);
|
||||||
|
m_ColorPalette[COL_WARNING] = ImVec4(1.0f, 0.87f, 0.37f, 1.f);
|
||||||
|
m_ColorPalette[COL_ERROR] = ImVec4(1.f, 0.365f, 0.365f, 1.f);
|
||||||
|
m_ColorPalette[COL_INFO] = ImVec4(0.46f, 0.96f, 0.46f, 1.f);
|
||||||
|
m_ColorPalette[COL_TIMESTAMP] = ImVec4(1.f, 1.f, 1.f, 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiConsole::RegisterConsoleCommands()
|
||||||
|
{
|
||||||
|
m_ConsoleSystem.RegisterCommand("clear", "Clear console log", [this]()
|
||||||
|
{
|
||||||
|
m_ConsoleSystem.Items().clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
m_ConsoleSystem.RegisterCommand("filter", "Set screen filter", [this](const csys::String& filter)
|
||||||
|
{
|
||||||
|
// Reset filter buffer.
|
||||||
|
std::memset(m_TextFilter.InputBuf, '\0', 256);
|
||||||
|
|
||||||
|
// Copy filter input buffer from client.
|
||||||
|
std::copy(filter.m_String.c_str(), filter.m_String.c_str() + std::min(static_cast<int>(filter.m_String.length()), 255), m_TextFilter.InputBuf);
|
||||||
|
|
||||||
|
// Build text filter.
|
||||||
|
m_TextFilter.Build();
|
||||||
|
|
||||||
|
}, csys::Arg<csys::String>("filter_str"));
|
||||||
|
|
||||||
|
m_ConsoleSystem.RegisterCommand("run", "Run given script", [this](const csys::String& filter)
|
||||||
|
{
|
||||||
|
// Logs command.
|
||||||
|
m_ConsoleSystem.RunScript(filter.m_String);
|
||||||
|
}, csys::Arg<csys::String>("script_name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiConsole::FilterBar()
|
||||||
|
{
|
||||||
|
m_TextFilter.Draw("Filter", ImGui::GetWindowWidth() * 0.25f);
|
||||||
|
ImGui::Separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiConsole::LogWindow()
|
||||||
|
{
|
||||||
|
const float footerHeightToReserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
|
||||||
|
if (ImGui::BeginChild("ScrollRegion##", ImVec2(0, -footerHeightToReserve), false, 0))
|
||||||
|
{
|
||||||
|
// Display colored command output.
|
||||||
|
static const float timestamp_width = ImGui::CalcTextSize("00:00:00:0000").x; // Timestamp.
|
||||||
|
int count = 0; // Item count.
|
||||||
|
|
||||||
|
// Wrap items.
|
||||||
|
ImGui::PushTextWrapPos();
|
||||||
|
|
||||||
|
// Display items.
|
||||||
|
for (const auto& item : m_ConsoleSystem.Items())
|
||||||
|
{
|
||||||
|
// Exit if word is filtered.
|
||||||
|
if (!m_TextFilter.PassFilter(item.Get().c_str()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Spacing between commands.
|
||||||
|
if (item.m_Type == csys::eCOMMAND)
|
||||||
|
{
|
||||||
|
if (m_TimeStamps) ImGui::PushTextWrapPos(ImGui::GetColumnWidth() - timestamp_width); // Wrap before timestamps start.
|
||||||
|
if (count++ != 0) ImGui::Dummy(ImVec2(-1, ImGui::GetFontSize())); // No space for the first command.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Items.
|
||||||
|
if (m_ColoredOutput)
|
||||||
|
{
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, m_ColorPalette[item.m_Type]);
|
||||||
|
ImGui::TextUnformatted(item.Get().data());
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
ImGui::TextUnformatted(item.Get().data());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Time stamp.
|
||||||
|
if (item.m_Type == csys::eCOMMAND && m_TimeStamps)
|
||||||
|
{
|
||||||
|
// No wrap for timestamps
|
||||||
|
ImGui::PopTextWrapPos();
|
||||||
|
|
||||||
|
// Right align.
|
||||||
|
ImGui::SameLine(ImGui::GetColumnWidth(-1) - timestamp_width);
|
||||||
|
|
||||||
|
// Draw time stamp.
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, m_ColorPalette[COL_TIMESTAMP]);
|
||||||
|
ImGui::Text("%02d:%02d:%02d:%04d", ((item.m_TimeStamp / 1000 / 3600) % 24), ((item.m_TimeStamp / 1000 / 60) % 60),
|
||||||
|
((item.m_TimeStamp / 1000) % 60), item.m_TimeStamp % 1000);
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop wrapping since we are done displaying console items.
|
||||||
|
ImGui::PopTextWrapPos();
|
||||||
|
|
||||||
|
// Auto-scroll logs.
|
||||||
|
if ((m_ScrollToBottom && (ImGui::GetScrollY() >= ImGui::GetScrollMaxY() || m_AutoScroll)))
|
||||||
|
ImGui::SetScrollHereY(1.0f);
|
||||||
|
m_ScrollToBottom = false;
|
||||||
|
|
||||||
|
// Loop through command string vector.
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiConsole::InputBar()
|
||||||
|
{
|
||||||
|
// Variables.
|
||||||
|
ImGuiInputTextFlags inputTextFlags =
|
||||||
|
ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackCharFilter | ImGuiInputTextFlags_CallbackCompletion |
|
||||||
|
ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackAlways;
|
||||||
|
|
||||||
|
// Only reclaim after enter key is pressed!
|
||||||
|
bool reclaimFocus = false;
|
||||||
|
|
||||||
|
// Input widget. (Width an always fixed width)
|
||||||
|
ImGui::PushItemWidth(-ImGui::GetStyle().ItemSpacing.x * 7);
|
||||||
|
if (ImGui::InputText("Input", &m_Buffer, inputTextFlags, InputCallback, this))
|
||||||
|
{
|
||||||
|
// Validate.
|
||||||
|
if (!m_Buffer.empty())
|
||||||
|
{
|
||||||
|
// Run command line input.
|
||||||
|
m_ConsoleSystem.RunCommand(m_Buffer);
|
||||||
|
|
||||||
|
// Scroll to bottom after its ran.
|
||||||
|
m_ScrollToBottom = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep focus.
|
||||||
|
reclaimFocus = true;
|
||||||
|
|
||||||
|
// Clear command line.
|
||||||
|
m_Buffer.clear();
|
||||||
|
}
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
|
||||||
|
// Reset suggestions when client provides char input.
|
||||||
|
if (ImGui::IsItemEdited() && !m_WasPrevFrameTabCompletion)
|
||||||
|
{
|
||||||
|
m_CmdSuggestions.clear();
|
||||||
|
}
|
||||||
|
m_WasPrevFrameTabCompletion = false;
|
||||||
|
|
||||||
|
// Auto-focus on window apparition
|
||||||
|
ImGui::SetItemDefaultFocus();
|
||||||
|
if (reclaimFocus)
|
||||||
|
ImGui::SetKeyboardFocusHere(-1); // Focus on command line after clearing.
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiConsole::MenuBar()
|
||||||
|
{
|
||||||
|
if (ImGui::BeginMenuBar())
|
||||||
|
{
|
||||||
|
// Settings menu.
|
||||||
|
if (ImGui::BeginMenu("Settings"))
|
||||||
|
{
|
||||||
|
// Colored output
|
||||||
|
ImGui::Checkbox("Colored Output", &m_ColoredOutput);
|
||||||
|
ImGui::SameLine();
|
||||||
|
HelpMaker("Enable colored command output");
|
||||||
|
|
||||||
|
// Auto Scroll
|
||||||
|
ImGui::Checkbox("Auto Scroll", &m_AutoScroll);
|
||||||
|
ImGui::SameLine();
|
||||||
|
HelpMaker("Automatically scroll to bottom of console log");
|
||||||
|
|
||||||
|
// Filter bar
|
||||||
|
ImGui::Checkbox("Filter Bar", &m_FilterBar);
|
||||||
|
ImGui::SameLine();
|
||||||
|
HelpMaker("Enable console filter bar");
|
||||||
|
|
||||||
|
// Time stamp
|
||||||
|
ImGui::Checkbox("Time Stamps", &m_TimeStamps);
|
||||||
|
ImGui::SameLine();
|
||||||
|
HelpMaker("Display command execution timestamps");
|
||||||
|
|
||||||
|
// Reset to default settings
|
||||||
|
if (ImGui::Button("Reset settings", ImVec2(ImGui::GetColumnWidth(), 0)))
|
||||||
|
ImGui::OpenPopup("Reset Settings?");
|
||||||
|
|
||||||
|
// Confirmation
|
||||||
|
if (ImGui::BeginPopupModal("Reset Settings?", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
|
||||||
|
{
|
||||||
|
ImGui::Text("All settings will be reset to default.\nThis operation cannot be undone!\n\n");
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
if (ImGui::Button("Reset", ImVec2(120, 0)))
|
||||||
|
{
|
||||||
|
DefaultSettings();
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SetItemDefaultFocus();
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Cancel", ImVec2(120, 0)))
|
||||||
|
{
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
// View settings.
|
||||||
|
if (ImGui::BeginMenu("Appearance"))
|
||||||
|
{
|
||||||
|
// Logging Colors
|
||||||
|
ImGuiColorEditFlags flags =
|
||||||
|
ImGuiColorEditFlags_Float | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaBar;
|
||||||
|
|
||||||
|
ImGui::TextUnformatted("Color Palette");
|
||||||
|
ImGui::Indent();
|
||||||
|
ImGui::ColorEdit4("Command##", (float*)&m_ColorPalette[COL_COMMAND], flags);
|
||||||
|
ImGui::ColorEdit4("Log##", (float*)&m_ColorPalette[COL_LOG], flags);
|
||||||
|
ImGui::ColorEdit4("Warning##", (float*)&m_ColorPalette[COL_WARNING], flags);
|
||||||
|
ImGui::ColorEdit4("Error##", (float*)&m_ColorPalette[COL_ERROR], flags);
|
||||||
|
ImGui::ColorEdit4("Info##", (float*)&m_ColorPalette[COL_INFO], flags);
|
||||||
|
ImGui::ColorEdit4("Time Stamp##", (float*)&m_ColorPalette[COL_TIMESTAMP], flags);
|
||||||
|
ImGui::Unindent();
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Window transparency.
|
||||||
|
ImGui::TextUnformatted("Background");
|
||||||
|
ImGui::SliderFloat("Transparency##", &m_WindowAlpha, 0.1f, 1.f);
|
||||||
|
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
// All scripts.
|
||||||
|
if (ImGui::BeginMenu("Scripts"))
|
||||||
|
{
|
||||||
|
// Show registered scripts.
|
||||||
|
for (const auto& scr_pair : m_ConsoleSystem.Scripts())
|
||||||
|
{
|
||||||
|
if (ImGui::MenuItem(scr_pair.first.c_str()))
|
||||||
|
{
|
||||||
|
m_ConsoleSystem.RunScript(scr_pair.first);
|
||||||
|
m_ScrollToBottom = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload scripts.
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::Button("Reload Scripts", ImVec2(ImGui::GetColumnWidth(), 0)))
|
||||||
|
{
|
||||||
|
for (const auto& scr_pair : m_ConsoleSystem.Scripts())
|
||||||
|
{
|
||||||
|
scr_pair.second->Reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndMenuBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// From imgui_demo.cpp
|
||||||
|
void ImGuiConsole::HelpMaker(const char* desc)
|
||||||
|
{
|
||||||
|
ImGui::TextDisabled("(?)");
|
||||||
|
if (ImGui::IsItemHovered())
|
||||||
|
{
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
|
||||||
|
ImGui::TextUnformatted(desc);
|
||||||
|
ImGui::PopTextWrapPos();
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int ImGuiConsole::InputCallback(ImGuiInputTextCallbackData* data)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Exit if no buffer.
|
||||||
|
if (data->BufTextLen == 0 && (data->EventFlag != ImGuiInputTextFlags_CallbackHistory))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// Get input string and console.
|
||||||
|
std::string input_str = data->Buf;
|
||||||
|
std::string trim_str;
|
||||||
|
auto console = static_cast<ImGuiConsole*>(data->UserData);
|
||||||
|
|
||||||
|
// Optimize by only using positions.
|
||||||
|
// Trim start and end spaces.
|
||||||
|
size_t startPos = console->m_Buffer.find_first_not_of(' ');
|
||||||
|
size_t endPos = console->m_Buffer.find_last_not_of(' ');
|
||||||
|
|
||||||
|
// Get trimmed string.
|
||||||
|
if (startPos != std::string::npos && endPos != std::string::npos)
|
||||||
|
trim_str = console->m_Buffer.substr(startPos, endPos + 1);
|
||||||
|
else
|
||||||
|
trim_str = console->m_Buffer;
|
||||||
|
|
||||||
|
switch (data->EventFlag)
|
||||||
|
{
|
||||||
|
case ImGuiInputTextFlags_CallbackCompletion:
|
||||||
|
{
|
||||||
|
// Find last word.
|
||||||
|
size_t startSubtrPos = trim_str.find_last_of(' ');
|
||||||
|
csys::AutoComplete* console_autocomplete;
|
||||||
|
|
||||||
|
// Command line is an entire word/string (No whitespace)
|
||||||
|
// Determine which autocomplete tree to use.
|
||||||
|
if (startSubtrPos == std::string::npos)
|
||||||
|
{
|
||||||
|
startSubtrPos = 0;
|
||||||
|
console_autocomplete = &console->m_ConsoleSystem.CmdAutocomplete();
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
startSubtrPos += 1;
|
||||||
|
console_autocomplete = &console->m_ConsoleSystem.VarAutocomplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate str
|
||||||
|
if (!trim_str.empty())
|
||||||
|
{
|
||||||
|
// Display suggestions on console.
|
||||||
|
if (!console->m_CmdSuggestions.empty())
|
||||||
|
{
|
||||||
|
console->m_ConsoleSystem.Log(csys::eCOMMAND) << "Suggestions: " << csys::endl;
|
||||||
|
|
||||||
|
for (const auto& suggestion : console->m_CmdSuggestions)
|
||||||
|
console->m_ConsoleSystem.Log(csys::eLOG) << suggestion << csys::endl;
|
||||||
|
|
||||||
|
console->m_CmdSuggestions.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get partial completion and suggestions.
|
||||||
|
std::string partial = console_autocomplete->Suggestions(trim_str.substr(startSubtrPos, endPos + 1), console->m_CmdSuggestions);
|
||||||
|
|
||||||
|
// Autocomplete only when one work is available.
|
||||||
|
if (!console->m_CmdSuggestions.empty() && console->m_CmdSuggestions.size() == 1)
|
||||||
|
{
|
||||||
|
data->DeleteChars(static_cast<int>(startSubtrPos), static_cast<int>(data->BufTextLen - startSubtrPos));
|
||||||
|
data->InsertChars(static_cast<int>(startSubtrPos), console->m_CmdSuggestions[0].data());
|
||||||
|
console->m_CmdSuggestions.clear();
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
// Partially complete word.
|
||||||
|
if (!partial.empty())
|
||||||
|
{
|
||||||
|
data->DeleteChars(static_cast<int>(startSubtrPos), static_cast<int>(data->BufTextLen - startSubtrPos));
|
||||||
|
data->InsertChars(static_cast<int>(startSubtrPos), partial.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have performed the completion event.
|
||||||
|
console->m_WasPrevFrameTabCompletion = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ImGuiInputTextFlags_CallbackHistory:
|
||||||
|
{
|
||||||
|
// Clear buffer.
|
||||||
|
data->DeleteChars(0, data->BufTextLen);
|
||||||
|
|
||||||
|
// Init history index
|
||||||
|
if (console->m_HistoryIndex == std::numeric_limits<size_t>::min())
|
||||||
|
console->m_HistoryIndex = console->m_ConsoleSystem.History().GetNewIndex();
|
||||||
|
|
||||||
|
// Traverse history.
|
||||||
|
if (data->EventKey == ImGuiKey_UpArrow)
|
||||||
|
{
|
||||||
|
if (console->m_HistoryIndex) --(console->m_HistoryIndex);
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
if (console->m_HistoryIndex < console->m_ConsoleSystem.History().Size()) ++(console->m_HistoryIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get history.
|
||||||
|
std::string prevCommand = console->m_ConsoleSystem.History()[console->m_HistoryIndex];
|
||||||
|
|
||||||
|
// Insert commands.
|
||||||
|
data->InsertChars(data->CursorPos, prevCommand.data());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ImGuiInputTextFlags_CallbackCharFilter:
|
||||||
|
case ImGuiInputTextFlags_CallbackAlways:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiConsole::SettingsHandler_ClearALl(ImGuiContext* ctx, ImGuiSettingsHandler* handler)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiConsole::SettingsHandler_ReadInit(ImGuiContext* ctx, ImGuiSettingsHandler* handler)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void* ImGuiConsole::SettingsHandler_ReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler, const char* name)
|
||||||
|
{
|
||||||
|
if (!handler->UserData)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto console = static_cast<ImGuiConsole*>(handler->UserData);
|
||||||
|
|
||||||
|
if (strcmp(name, console->m_ConsoleName.c_str()) != 0)
|
||||||
|
return nullptr;
|
||||||
|
return (void*)1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiConsole::SettingsHandler_ReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line)
|
||||||
|
{
|
||||||
|
if (!handler->UserData)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Get console.
|
||||||
|
auto console = static_cast<ImGuiConsole*>(handler->UserData);
|
||||||
|
|
||||||
|
// Ensure console doesn't reset variables.
|
||||||
|
console->m_LoadedFromIni = true;
|
||||||
|
|
||||||
|
// Disable warning regarding sscanf when using MVSC
|
||||||
|
#pragma warning( push )
|
||||||
|
#pragma warning( disable:4996 )
|
||||||
|
|
||||||
|
#define INI_CONSOLE_LOAD_COLOR(type) (std::sscanf(line, #type"=%i,%i,%i,%i", &r, &g, &b, &a) == 4) { console->m_ColorPalette[type] = ImColor(r, g, b, a); }
|
||||||
|
#define INI_CONSOLE_LOAD_FLOAT(var) (std::sscanf(line, #var"=%f", &f) == 1) { console->var = f; }
|
||||||
|
#define INI_CONSOLE_LOAD_BOOL(var) (std::sscanf(line, #var"=%i", &b) == 1) {console->var = b == 1;}
|
||||||
|
|
||||||
|
float f;
|
||||||
|
int r, g, b, a;
|
||||||
|
|
||||||
|
// Window style/visuals
|
||||||
|
if INI_CONSOLE_LOAD_COLOR(COL_COMMAND)
|
||||||
|
else if INI_CONSOLE_LOAD_COLOR(COL_LOG)
|
||||||
|
else if INI_CONSOLE_LOAD_COLOR(COL_WARNING)
|
||||||
|
else if INI_CONSOLE_LOAD_COLOR(COL_ERROR)
|
||||||
|
else if INI_CONSOLE_LOAD_COLOR(COL_INFO)
|
||||||
|
else if INI_CONSOLE_LOAD_COLOR(COL_TIMESTAMP)
|
||||||
|
else if INI_CONSOLE_LOAD_FLOAT(m_WindowAlpha)
|
||||||
|
|
||||||
|
// Window settings
|
||||||
|
else if INI_CONSOLE_LOAD_BOOL(m_AutoScroll)
|
||||||
|
else if INI_CONSOLE_LOAD_BOOL(m_ScrollToBottom)
|
||||||
|
else if INI_CONSOLE_LOAD_BOOL(m_ColoredOutput)
|
||||||
|
else if INI_CONSOLE_LOAD_BOOL(m_FilterBar)
|
||||||
|
else if INI_CONSOLE_LOAD_BOOL(m_TimeStamps)
|
||||||
|
|
||||||
|
#pragma warning( pop )
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiConsole::SettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler)
|
||||||
|
{
|
||||||
|
if (!handler->UserData)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiConsole::SettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
|
||||||
|
{
|
||||||
|
if (!handler->UserData)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Get console.
|
||||||
|
auto console = static_cast<ImGuiConsole*>(handler->UserData);
|
||||||
|
|
||||||
|
#define INI_CONSOLE_SAVE_COLOR(type) buf->appendf(#type"=%i,%i,%i,%i\n", (int)(console->m_ColorPalette[type].x * 255),\
|
||||||
|
(int)(console->m_ColorPalette[type].y * 255),\
|
||||||
|
(int)(console->m_ColorPalette[type].z * 255),\
|
||||||
|
(int)(console->m_ColorPalette[type].w * 255))
|
||||||
|
|
||||||
|
#define INI_CONSOLE_SAVE_FLOAT(var) buf->appendf(#var"=%.3f\n", console->var)
|
||||||
|
#define INI_CONSOLE_SAVE_BOOL(var) buf->appendf(#var"=%i\n", console->var)
|
||||||
|
|
||||||
|
// Set header for CONSOLE Console.
|
||||||
|
buf->appendf("[%s][%s]\n", handler->TypeName, console->m_ConsoleName.data());
|
||||||
|
|
||||||
|
// Window settings.
|
||||||
|
INI_CONSOLE_SAVE_BOOL(m_AutoScroll);
|
||||||
|
INI_CONSOLE_SAVE_BOOL(m_ScrollToBottom);
|
||||||
|
INI_CONSOLE_SAVE_BOOL(m_ColoredOutput);
|
||||||
|
INI_CONSOLE_SAVE_BOOL(m_FilterBar);
|
||||||
|
INI_CONSOLE_SAVE_BOOL(m_TimeStamps);
|
||||||
|
|
||||||
|
// Window style/visuals
|
||||||
|
INI_CONSOLE_SAVE_FLOAT(m_WindowAlpha);
|
||||||
|
INI_CONSOLE_SAVE_COLOR(COL_COMMAND);
|
||||||
|
INI_CONSOLE_SAVE_COLOR(COL_LOG);
|
||||||
|
INI_CONSOLE_SAVE_COLOR(COL_WARNING);
|
||||||
|
INI_CONSOLE_SAVE_COLOR(COL_ERROR);
|
||||||
|
INI_CONSOLE_SAVE_COLOR(COL_INFO);
|
||||||
|
INI_CONSOLE_SAVE_COLOR(COL_TIMESTAMP);
|
||||||
|
|
||||||
|
// End saving.
|
||||||
|
buf->append("\n");
|
||||||
|
}
|
||||||
112
include/imgui/imgui_console.h
Normal file
112
include/imgui/imgui_console.h
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
// Copyright (c) 2020 - present, Roland Munguia
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#ifndef IMGUI_CONSOLE_H
|
||||||
|
#define IMGUI_CONSOLE_H
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "csys/system.h"
|
||||||
|
#include "imgui.h"
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
struct ImGuiSettingsHandler;
|
||||||
|
class ImGuiConsole
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Construct an imgui console
|
||||||
|
* \param c_name Name of the console
|
||||||
|
* \param inputBufferSize Maximum input buffer size
|
||||||
|
*/
|
||||||
|
explicit ImGuiConsole(std::string c_name = "imgui-console", size_t inputBufferSize = 256);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Render the Dear ImGui Console
|
||||||
|
*/
|
||||||
|
void Draw();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Console system which handles the console functionality (Logging, Commands, History, Scripts, etc).
|
||||||
|
* \return System Obj
|
||||||
|
*/
|
||||||
|
csys::System &System();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
// Console ////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
csys::System m_ConsoleSystem; //!< Main console system.
|
||||||
|
size_t m_HistoryIndex; //!< Command history index.
|
||||||
|
|
||||||
|
// Dear ImGui ////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Main
|
||||||
|
|
||||||
|
std::string m_Buffer; //!< Input buffer.
|
||||||
|
std::string m_ConsoleName; //!< Console name string buffer.
|
||||||
|
ImGuiTextFilter m_TextFilter; //!< Logging filer.
|
||||||
|
bool m_AutoScroll; //!< Auto scroll flag.
|
||||||
|
bool m_ColoredOutput; //!< Colored output flag.
|
||||||
|
bool m_ScrollToBottom; //!< Scroll to bottom after is command is ran
|
||||||
|
bool m_FilterBar; //!< Filter bar flag.
|
||||||
|
bool m_TimeStamps; //!< Display time stamps flag
|
||||||
|
|
||||||
|
void InitIniSettings(); //!< Initialize Ini Settings handler
|
||||||
|
void DefaultSettings(); //!< Restore console default settings
|
||||||
|
void RegisterConsoleCommands(); //!< Register built-in console commands
|
||||||
|
|
||||||
|
void MenuBar(); //!< Console menu bar
|
||||||
|
void FilterBar(); //!< Console filter bar
|
||||||
|
void InputBar(); //!< Console input bar
|
||||||
|
void LogWindow(); //!< Console log
|
||||||
|
|
||||||
|
static void HelpMaker(const char *desc);
|
||||||
|
|
||||||
|
// Window appearance.
|
||||||
|
|
||||||
|
float m_WindowAlpha; //!< Window transparency
|
||||||
|
|
||||||
|
enum COLOR_PALETTE
|
||||||
|
{
|
||||||
|
// This four have to match the csys item type enum.
|
||||||
|
|
||||||
|
COL_COMMAND = 0, //!< Color for command logs
|
||||||
|
COL_LOG, //!< Color for in-command logs
|
||||||
|
COL_WARNING, //!< Color for warnings logs
|
||||||
|
COL_ERROR, //!< Color for error logs
|
||||||
|
COL_INFO, //!< Color for info logs
|
||||||
|
|
||||||
|
COL_TIMESTAMP, //!< Color for timestamps
|
||||||
|
|
||||||
|
COL_COUNT //!< For bookkeeping purposes
|
||||||
|
};
|
||||||
|
|
||||||
|
std::array<ImVec4, COL_COUNT> m_ColorPalette; //!< Container for all available colors
|
||||||
|
|
||||||
|
// ImGui Console Window.
|
||||||
|
|
||||||
|
static int InputCallback(ImGuiInputTextCallbackData *data); //!< Console input callback
|
||||||
|
bool m_WasPrevFrameTabCompletion = false; //!< Flag to determine if previous input was a tab completion
|
||||||
|
std::vector<std::string> m_CmdSuggestions; //!< Holds command suggestions from partial completion
|
||||||
|
|
||||||
|
// Save data inside .ini
|
||||||
|
|
||||||
|
bool m_LoadedFromIni = false;
|
||||||
|
|
||||||
|
static void SettingsHandler_ClearALl(ImGuiContext *ctx, ImGuiSettingsHandler *handler);
|
||||||
|
|
||||||
|
static void SettingsHandler_ReadInit(ImGuiContext *ctx, ImGuiSettingsHandler *handler);
|
||||||
|
|
||||||
|
static void *SettingsHandler_ReadOpen(ImGuiContext *ctx, ImGuiSettingsHandler *handler, const char *name);
|
||||||
|
|
||||||
|
static void SettingsHandler_ReadLine(ImGuiContext *ctx, ImGuiSettingsHandler *handler, void *entry, const char *line);
|
||||||
|
|
||||||
|
static void SettingsHandler_ApplyAll(ImGuiContext *ctx, ImGuiSettingsHandler *handler);
|
||||||
|
|
||||||
|
static void SettingsHandler_WriteAll(ImGuiContext *ctx, ImGuiSettingsHandler *handler, ImGuiTextBuffer *buf);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //IMGUI_CONSOLE_H
|
||||||
@ -14,21 +14,24 @@ int main(int argc, char* argv[]) {
|
|||||||
try {
|
try {
|
||||||
Window win(argv[0], 256, 512);
|
Window win(argv[0], 256, 512);
|
||||||
|
|
||||||
|
// for fullscreen
|
||||||
auto mon = glfwGetPrimaryMonitor();
|
auto mon = glfwGetPrimaryMonitor();
|
||||||
auto v = glfwGetVideoMode(mon);
|
auto v = glfwGetVideoMode(mon);
|
||||||
glfwSetWindowMonitor(win.win, mon, 0, 0, v->width, v->height, v->refreshRate);
|
glfwSetWindowMonitor(win.win, mon, 0, 0, v->width, v->height, v->refreshRate);
|
||||||
|
|
||||||
auto in = win.getInput();
|
auto in = win.getInput();
|
||||||
Renderer ren(win);
|
Renderer ren(win);
|
||||||
|
in->setCursor(false);
|
||||||
|
|
||||||
while (!in->shouldClose()) {
|
|
||||||
|
while (!in->shouldClose() && !ren.should_close) {
|
||||||
Timer frame_timer;
|
Timer frame_timer;
|
||||||
in->poll();
|
in->poll();
|
||||||
in->handleMovementKeys(ren);
|
in->handleMovementKeys(ren);
|
||||||
|
|
||||||
while (in->queue.size()) {
|
while (in->queue.size()) {
|
||||||
/* take event from front of queue, then process it */
|
/* take event from front of queue, then process it */
|
||||||
const auto event = in->queue.front();
|
const InputEvent event = in->queue.front();
|
||||||
in->queue.pop();
|
in->queue.pop();
|
||||||
switch (event.tag) {
|
switch (event.tag) {
|
||||||
case InputEvent::Tag::eRESIZE:
|
case InputEvent::Tag::eRESIZE:
|
||||||
@ -56,21 +59,13 @@ int main(int argc, char* argv[]) {
|
|||||||
case InputEvent::Tag::eBUTTON:
|
case InputEvent::Tag::eBUTTON:
|
||||||
break;
|
break;
|
||||||
case InputEvent::Tag::eKEY:
|
case InputEvent::Tag::eKEY:
|
||||||
if (event.key.key == GLFW_KEY_Q) {
|
if (event.key.key == GLFW_KEY_ESCAPE && event.key.state == GLFW_PRESS) {
|
||||||
return 0;
|
ren.in_menu = !ren.in_menu;
|
||||||
} else if (event.key.key == GLFW_KEY_ESCAPE && event.key.state == GLFW_PRESS) {
|
in->setCursor(ren.in_menu);
|
||||||
ren.capture_mouse = !ren.capture_mouse;
|
} else if (event.key.key == GLFW_KEY_Q && event.key.state == GLFW_PRESS) {
|
||||||
in->setCursor(!ren.capture_mouse);
|
if (!ren.in_menu) {
|
||||||
} else if (event.key.key == GLFW_KEY_R && event.key.state == GLFW_PRESS) {
|
ren.should_close = true;
|
||||||
ren.time = 0;
|
}
|
||||||
} else if (event.key.key == GLFW_KEY_C && event.key.state == GLFW_PRESS) {
|
|
||||||
ren.flycam = !ren.flycam;
|
|
||||||
} else if (event.key.key == GLFW_KEY_P && event.key.state == GLFW_PRESS) {
|
|
||||||
ren.running = !ren.running;
|
|
||||||
} else if (event.key.key == GLFW_KEY_T && event.key.state == GLFW_PRESS) {
|
|
||||||
ren.speed *= 10.0;
|
|
||||||
} else if (event.key.key == GLFW_KEY_Y && event.key.state == GLFW_PRESS) {
|
|
||||||
ren.speed /= 10.0;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -78,14 +73,18 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
ren.draw();
|
ren.draw();
|
||||||
ren.present();
|
ren.present();
|
||||||
const auto t = frame_timer.read();
|
ren.frametime = frame_timer.read();
|
||||||
ren.ui->info.fps = 1000.0f / t;
|
ren.fps = 1000.0f / ren.frametime;
|
||||||
|
|
||||||
while (frame_timer.read() < 16.60)
|
while (frame_timer.read() < 1000.0 / ren.max_fps)
|
||||||
;
|
;
|
||||||
|
|
||||||
|
ren.frametime = frame_timer.read();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (const std::string& e) {
|
} catch (const std::string& e) {
|
||||||
std::cerr << "Exception: " << e << std::endl;
|
std::cerr << "Exception: " << e << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log::info("Quitting\n");
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
terrain_capture.png
Normal file
BIN
terrain_capture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 805 KiB |
17
util/geo.hpp
17
util/geo.hpp
@ -1,8 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/matrix_access.hpp>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* extracts frustum from projection matrix */
|
/* extracts frustum from projection matrix */
|
||||||
static std::array<glm::vec4, 6> frustum(const glm::mat4& mat) {
|
static std::array<glm::vec4, 6> frustum(const glm::mat4& mat) {
|
||||||
/* Left, Right, Top, Bottom, Back, Front */
|
/* Left, Right, Top, Bottom, Back, Front */
|
||||||
@ -15,3 +18,17 @@ static std::array<glm::vec4, 6> frustum(const glm::mat4& mat) {
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool box_in_frustum(const std::array<glm::vec4, 6>& frustum, const glm::vec3 box_verts[8]) {
|
||||||
|
for (const auto& plane : frustum) {
|
||||||
|
for (size_t i = 0; i < 8; i++) {
|
||||||
|
const auto& vert = box_verts[i];
|
||||||
|
if (glm::dot(glm::vec4(vert, 1.0), plane) > 0)
|
||||||
|
goto double_continue;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
double_continue:;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user