diff --git a/lib/core/core-wrappers/include/Gl.h b/lib/core/core-wrappers/include/Gl.h index 47960c7df..2a1f039f3 100644 --- a/lib/core/core-wrappers/include/Gl.h +++ b/lib/core/core-wrappers/include/Gl.h @@ -257,4 +257,5 @@ class Gl static void viewport(GLint x, GLint y, GLsizei width, GLsizei height); static void drawArrays(GLenum mode, GLint first, GLsizei count); + static void drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instanceCount); }; diff --git a/lib/core/core-wrappers/source/Gl.cpp b/lib/core/core-wrappers/source/Gl.cpp index 298b983d7..e3477b595 100644 --- a/lib/core/core-wrappers/source/Gl.cpp +++ b/lib/core/core-wrappers/source/Gl.cpp @@ -369,6 +369,14 @@ Gl::State Gl::getState() return state; } +void Gl::drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instanceCount) +{ + glDrawArraysInstanced(mode, first, count, instanceCount); +#ifdef OPENGL_DEBUG + Gl::debugTraces(); +#endif +} + std::string Gl::Texture::channelToString(Channel channel) { if (channel == Channel::SRGB) diff --git a/lib/core/shapes/include/InstancedDrawAble.h b/lib/core/shapes/include/InstancedDrawAble.h new file mode 100644 index 000000000..c7c6a4b2a --- /dev/null +++ b/lib/core/shapes/include/InstancedDrawAble.h @@ -0,0 +1,54 @@ +// MIT License +// +// Copyright (c) 2023 Valerii Koniushenko +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include "ShaderPack.h" +#include "glm/glm.hpp" + +#include + +class Camera; + +class InstancedDrawAble +{ +public: + virtual void draw(ShaderPack& shaderProgram, Camera* camera = nullptr); + [[nodiscard]] virtual std::size_t getVerticesCount() const; + + void setPosition(const glm::vec2& newPosition); + void move(const glm::vec2& offset); + [[nodiscard]] const glm::vec2& getPosition() const; + + void setRotation(float newRotation); + void rotate(float offset); + [[nodiscard]] float getRotation() const; + virtual void update() = 0; + + [[nodiscard]] std::vector& getTransforms(); + [[nodiscard]] const std::vector& getTransforms() const; + +protected: + glm::vec2 position_{}; + float rotation_{}; + std::vector transforms_; +}; \ No newline at end of file diff --git a/lib/core/shapes/include/InstancedWidget.h b/lib/core/shapes/include/InstancedWidget.h new file mode 100644 index 000000000..d8b7fcc96 --- /dev/null +++ b/lib/core/shapes/include/InstancedWidget.h @@ -0,0 +1,119 @@ +// MIT License +// +// Copyright (c) 2023 Valerii Koniushenko +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include "Delegate.h" +#include "InstancedDrawAble.h" +#include "JsonPrintable.h" +#include "Keyboard.h" +#include "NotCopyableButMovable.h" +#include "Rect.h" +#include "Size.h" +#include "Updateable.h" +#include "Vao.h" +#include "Vbo.h" + +#include + +class Texture; + +class InstancedWidget : public InstancedDrawAble, public JsonPrintable, public Utils::CopyableAndMoveable +{ +public: + inline static constexpr glm::vec4 borderColor = {1.f, 1.f, 0.f, 1.f}; + inline static constexpr float borderWidth = 0.05f; + inline static constexpr const char* componentName = "widget"; + + InstancedWidget() = default; + + InstancedWidget(InstancedWidget&& other) noexcept; + + InstancedWidget& operator=(InstancedWidget&& other) noexcept; + + void draw(ShaderPack& shaderProgram, Camera* camera = nullptr) override; + + [[nodiscard]] std::size_t getVerticesCount() const override; + + void setTexture(Texture& texture); + + [[nodiscard]] Texture& getTexture(); + + [[nodiscard]] const Texture& getTexture() const; + + void setSize(Utils::FSize2D newSize); + + [[nodiscard]] Utils::FSize2D getSize() const; + + void setScale(Utils::FSize2D newScale); + + [[nodiscard]] Utils::FSize2D getScale() const; + + [[nodiscard]] Utils::FRect getRect() const; + + void setIsDrawBorder(bool isDraw); + + [[nodiscard]] bool isDrawBorder() const; + + virtual void prepare(ShaderPack& shader); + + void update() override; + + [[nodiscard]] virtual std::string getComponentName() const; + + LambdaMulticastDelegate onMouseHover; + LambdaMulticastDelegate onMouseUnHover; + LambdaMulticastDelegate onMouseLeftClick; + LambdaMulticastDelegate onMouseRightClick; + LambdaMulticastDelegate onMouseMiddleClick; + LambdaMulticastDelegate onMouseWheel; + LambdaMulticastDelegate onTextInput; + + [[nodiscard]] boost::property_tree::ptree toJson() const override; + + void setTextureRect(const Utils::Rect& rect); + + void calculateFitTextureSize(); + +private: + void recalculateVerticiesData(); + +private: + // clang-format off + inline static const std::vector templateVertices_ = { + 0.f, 0.f, 0.f, 1.f, 0, 0, + 0.f, -1.f, 0.f, 0.f, 0, 0, + 1.f, 0.f, 1.f, 1.f, 0, 0, + 1.f, -1.f, 1.f, 0.f, 0, 0, + }; + // clang-format on + Utils::IRect textureRect_; + Texture* texture_{}; + Vbo vbo_; + Vao vao_; + Vbo vboInstancing_; + Utils::FSize2D size_ = {.width = 100.f, .height = 100.f}; + Utils::FSize2D scale_ = {.width = 1.f, .height = 1.f}; + bool isDrawBorder_ = false; + bool wasHover_ = false; + bool isPrepared = false; +}; \ No newline at end of file diff --git a/lib/core/shapes/source/InstancedDrawAble.cpp b/lib/core/shapes/source/InstancedDrawAble.cpp new file mode 100644 index 000000000..56a9e9dcf --- /dev/null +++ b/lib/core/shapes/source/InstancedDrawAble.cpp @@ -0,0 +1,76 @@ +// MIT License +// +// Copyright (c) 2023 Valerii Koniushenko +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "InstancedDrawAble.h" +#include "Camera.h" + +#include "Gl.h" + +void InstancedDrawAble::draw(ShaderPack& shaderPack, Camera* camera/* = nullptr*/) +{ + Gl::drawArraysInstanced(GL_TRIANGLE_STRIP, 0, getVerticesCount(), transforms_.size()); +} + +std::size_t InstancedDrawAble::getVerticesCount() const +{ + return 0; +} + +void InstancedDrawAble::setPosition(const glm::vec2& newPosition) +{ + position_ = newPosition; +} + +void InstancedDrawAble::move(const glm::vec2& offset) +{ + position_ += offset; +} + +const glm::vec2& InstancedDrawAble::getPosition() const +{ + return position_; +} + +void InstancedDrawAble::setRotation(float newRotation) +{ + rotation_ = newRotation; +} + +void InstancedDrawAble::rotate(float offset) +{ + rotation_ += offset; +} + +float InstancedDrawAble::getRotation() const +{ + return rotation_; +} + +std::vector& InstancedDrawAble::getTransforms() +{ + return transforms_; +} + +const std::vector& InstancedDrawAble::getTransforms() const +{ + return transforms_; +} diff --git a/lib/core/shapes/source/InstancedWidget.cpp b/lib/core/shapes/source/InstancedWidget.cpp new file mode 100644 index 000000000..39df8df33 --- /dev/null +++ b/lib/core/shapes/source/InstancedWidget.cpp @@ -0,0 +1,360 @@ +// MIT License +// +// Copyright (c) 2023 Valerii Koniushenko +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "InstancedWidget.h" + +#include "Camera.h" +#include "Image.h" +#include "Mouse.h" +#include "Texture.h" +#include "Window.h" +#include "WorldVariables.h" + +#include +#include +#include + +void InstancedWidget::draw(ShaderPack& shaderPack, Camera* camera /* = nullptr*/) +{ + if (!isPrepared) + { + prepare(shaderPack); + if (!isPrepared) + { + throw std::runtime_error("The InstancedWidget is not prepared. Use InstancedWidget::prepare before InstancedWidget::draw to resolve it."); + } + } + + recalculateVerticiesData(); + + auto& shaderProgram = shaderPack["instansed-widget"]; + vao_.bind(); + if (texture_) + { + texture_->bind(); + } + else + { + Gl::Texture::bind(Gl::Texture::Target::Texture2D, 0); + } + + glm::mat4 cameraMatrix = glm::mat4(1.0f); + glm::vec2 windowSize = glm::vec2(static_cast(GetWindow().getSize().width) / 2.f, static_cast(GetWindow().getSize().height) / 2.f); + if (camera) + { + cameraMatrix = camera->generateMatrix(windowSize); + } + + shaderProgram.use(); + shaderProgram.uniform("uHasTexture", static_cast(texture_)); + shaderProgram.uniform("uCameraMatrix", false, cameraMatrix); + shaderProgram.uniform( + "uResolution", static_cast(GetWindow().getSize().width), static_cast(GetWindow().getSize().height)); + shaderProgram.uniform("uGamma", shaderProgram.lightning.gamma); + shaderProgram.uniform("uBrightness", shaderProgram.lightning.brightness); + shaderProgram.uniform("uContrast", shaderProgram.lightning.contrast); + shaderProgram.uniform("uSaturation", shaderProgram.lightning.saturation); + shaderProgram.uniform("uBorderColor", borderColor.r, borderColor.g, borderColor.b, borderColor.a); + shaderProgram.uniform("uBorderWidth", borderWidth); + shaderProgram.uniform("uIsDrawBorder", isDrawBorder_); + if (texture_ && texture_->getImage()) + { + shaderProgram.uniform("uAtlasSize", texture_->getImage()->getSize()); + } + + InstancedDrawAble::draw(shaderPack); +} + +std::size_t InstancedWidget::getVerticesCount() const +{ + constexpr std::size_t countOfParts = 4; + return templateVertices_.size() / countOfParts; +} + +void InstancedWidget::setTexture(Texture& texture) +{ + isPrepared = false; + texture_ = &texture; + if (texture.getImage()) + { + auto size = texture.getImage()->getSize(); + textureRect_.position = {}; + textureRect_.size = {size.x, size.y}; + } +} + +Texture& InstancedWidget::getTexture() +{ + if (!texture_) + { + throw std::runtime_error("Impossible to get NULL texture"); + } + return *texture_; +} + +void InstancedWidget::prepare(ShaderPack& shader) +{ + shader["instansed-widget"].use(); + + recalculateVerticiesData(); + + if (!vao_.isGenerated()) + { + vao_.generate(); + } + vao_.bind(); + Gl::Vao::vertexAttribPointer(0, 2, Gl::Type::Float, false, 6 * sizeof(float), nullptr); + Gl::Vao::enableVertexAttribArray(0); + + Gl::Vao::vertexAttribPointer(1, 2, Gl::Type::Float, false, 6 * sizeof(float), reinterpret_cast(2 * sizeof(float))); + Gl::Vao::enableVertexAttribArray(1); + + Gl::Vao::vertexAttribPointer(2, 2, Gl::Type::Float, false, 6 * sizeof(float), reinterpret_cast(4 * sizeof(float))); + Gl::Vao::enableVertexAttribArray(2); + + if (!vboInstancing_.isGenerated()) + { + vboInstancing_.generate(); + } + vboInstancing_.bind(); + glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * transforms_.size(), transforms_.data(), GL_STATIC_DRAW); + Gl::Vao::vertexAttribPointer(3, 2, Gl::Type::Float, false, sizeof(float) * 2, (void*)(0)); + glVertexAttribDivisor(3, 1); + Gl::requireNoErrors(); + + if (texture_) + { + texture_->bind(); + texture_->loadToGpu(); + } + + isPrepared = true; +} + +void InstancedWidget::setSize(Utils::FSize2D newSize) +{ + isPrepared = false; + size_ = newSize; +} + +Utils::FSize2D InstancedWidget::getSize() const +{ + return size_; +} + +void InstancedWidget::setScale(Utils::FSize2D newScale) +{ + isPrepared = false; + scale_ = newScale; +} + +Utils::FSize2D InstancedWidget::getScale() const +{ + return scale_; +} + +void InstancedWidget::update() +{ + if (getRect().isCollision(GetWindow().getCamera()->toGlobalCoordinates(Mouse::getPosition(GetWindow())))) + { + onMouseHover.trigger(); + + if (Mouse::isKeyPressed(Mouse::Key::Left)) + { + onMouseLeftClick.trigger(); + } + if (Mouse::isKeyPressed(Mouse::Key::Right)) + { + onMouseRightClick.trigger(); + } + if (Mouse::isKeyPressed(Mouse::Key::Middle)) + { + onMouseMiddleClick.trigger(); + } + try + { + if (get(GetWorldVariables()["mouse-wheel-y"]) != 0.) + { + onMouseWheel.trigger(get(GetWorldVariables()["mouse-wheel-y"])); + } + } + catch (...) + { + } + try + { + if (get(GetWorldVariables()["inputted-text"]) != 0.) + { + onTextInput.trigger(get(GetWorldVariables()["inputted-text"])); + } + } + catch (...) + { + } + + wasHover_ = true; + } + else if (wasHover_) + { + onMouseUnHover.trigger(); + wasHover_ = false; + } +} + +Utils::FRect InstancedWidget::getRect() const +{ + return {position_, size_}; +} + +void InstancedWidget::setIsDrawBorder(bool isDraw) +{ + isDrawBorder_ = isDraw; +} + +bool InstancedWidget::isDrawBorder() const +{ + return isDrawBorder_; +} + +boost::property_tree::ptree InstancedWidget::toJson() const +{ + boost::property_tree::ptree ptree; + ptree.put("component", getComponentName()); + ptree.put("verticies-count", templateVertices_.size()); + ptree.put("vbo", vbo_.getId()); + ptree.put("vao", vao_.getId()); + ptree.put("width", size_.width); + ptree.put("height", size_.height); + ptree.put("scale-width", scale_.width); + ptree.put("scale-height", scale_.height); + ptree.put("position-x", position_.x); + ptree.put("position-y", position_.y); + if (texture_) + { + boost::property_tree::ptree texture; + texture.put("name", texture_->getName()); + texture.put("mag-filter", Gl::Texture::magFilterToString(texture_->getMagFilter())); + texture.put("min-filter", Gl::Texture::minFilterToString(texture_->getMinFilter())); + texture.put("width", texture_->getImage()->getWidth()); + texture.put("height", texture_->getImage()->getHeight()); + texture.put("channel", Image::channelToString(texture_->getImage()->getChannel())); + texture.put("internal-channel", Gl::Texture::channelToString(texture_->getImage()->getInternalChannel())); + + ptree.add_child("texture", texture); + } + + return ptree; +} + +std::string InstancedWidget::getComponentName() const +{ + return componentName; +} + +InstancedWidget::InstancedWidget(InstancedWidget&& other) noexcept +{ + isPrepared = false; + *this = std::move(other); +} + +InstancedWidget& InstancedWidget::operator=(InstancedWidget&& other) noexcept +{ + isPrepared = false; + + textureRect_ = other.textureRect_; + texture_ = other.texture_; + vbo_ = other.vbo_; + vao_ = other.vao_; + size_ = other.size_; + scale_ = other.scale_; + isDrawBorder_ = other.isDrawBorder_; + wasHover_ = other.wasHover_; + isPrepared = other.isPrepared; + + other.textureRect_ = {}; + other.isPrepared = false; + other.texture_ = {}; + other.vbo_ = {}; + other.vao_ = {}; + other.size_ = {}; + other.scale_ = {}; + other.isDrawBorder_ = {}; + other.wasHover_ = {}; + other.isPrepared = {}; + + return *this; +} + +void InstancedWidget::setTextureRect(const Utils::Rect& rect) +{ + textureRect_ = rect; +} + +void InstancedWidget::calculateFitTextureSize() +{ + if (texture_ && texture_->getImage()) + { + auto size = texture_->getImage()->getSize(); + size_.width = size.x; + size_.height = size.y; + } +} + +void InstancedWidget::recalculateVerticiesData() +{ + if (!vbo_.isGenerated()) + { + vbo_.generate(); + } + + std::vector vertices = templateVertices_; + vertices.at(7) *= size_.height * 2.f * scale_.height; // TODO: refactor it + vertices.at(12) *= size_.width * 2.f * scale_.width; // TODO: refactor it + vertices.at(18) *= size_.width * 2.f * scale_.width; // TODO: refactor it + vertices.at(19) *= size_.height * 2.f * scale_.height; // TODO: refactor it + + vertices.at(3) *= textureRect_.size.height; + vertices.at(14) *= textureRect_.size.width; + vertices.at(15) *= textureRect_.size.height; + vertices.at(20) *= textureRect_.size.width; + + vertices.at(4) = textureRect_.position.x; + vertices.at(5) = textureRect_.position.y; + vertices.at(10) = textureRect_.position.x; + vertices.at(11) = textureRect_.position.y; + vertices.at(16) = textureRect_.position.x; + vertices.at(17) = textureRect_.position.y; + vertices.at(22) = textureRect_.position.x; + vertices.at(23) = textureRect_.position.y; + + vbo_.bind(); + vbo_.data(vertices); +} + +const Texture& InstancedWidget::getTexture() const +{ + if (texture_) + { + throw std::runtime_error("Impossible to get NULL texture"); + } + return *texture_; +}