From c9fadb377935359a233fc127b329cb8250e0fee9 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sun, 21 Dec 2025 19:52:41 +0100 Subject: [PATCH] rotation --- ...hics-RenderingElementScalingRotating2D.cpp | 177 ++++++++++++++++++ .../Crafter.Graphics-Window_wayland.cpp | 6 +- .../Crafter.Graphics-RenderingElement.cppm | 21 +++ interfaces/Crafter.Graphics-Types.cppm | 12 ++ project.json | 2 +- 5 files changed, 214 insertions(+), 4 deletions(-) create mode 100644 implementations/Crafter.Graphics-RenderingElementScalingRotating2D.cpp diff --git a/implementations/Crafter.Graphics-RenderingElementScalingRotating2D.cpp b/implementations/Crafter.Graphics-RenderingElementScalingRotating2D.cpp new file mode 100644 index 0000000..4f996be --- /dev/null +++ b/implementations/Crafter.Graphics-RenderingElementScalingRotating2D.cpp @@ -0,0 +1,177 @@ +/* +Crafter®.Graphics +Copyright (C) 2025 Catcrafts® +Catcrafts.net + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3.0 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +module Crafter.Graphics:RenderingElementScalingRotating2D_impl; +import :RenderingElement; +import :Window; +import :Types; +import :Font; +import std; + +using namespace Crafter; + +RenderingElementScalingRotating2D::RenderingElementScalingRotating2D(OpaqueType opaque) : RenderingElement(opaque), rotation(0) { + +} + +RenderingElementScalingRotating2D::RenderingElementScalingRotating2D(OpaqueType opaque, std::int_fast32_t anchorX, std::int_fast32_t anchorY, std::uint_fast32_t relativeWidth, std::uint_fast32_t relativeHeight, std::int_fast32_t anchorOffsetX, std::int_fast32_t anchorOffsetY, std::int_fast32_t z, bool ignoreScaling) : RenderingElement(opaque, anchorX, anchorY, relativeWidth, relativeHeight, anchorOffsetX, anchorOffsetY, z, ignoreScaling), rotation(0) { + +} + +RenderingElementScalingRotating2D::RenderingElementScalingRotating2D(OpaqueType opaque, std::uint_fast32_t bufferWidth, std::uint_fast32_t bufferHeight, std::uint_fast32_t rotation, double scaleX, double scaleY, std::int_fast32_t anchorX, std::int_fast32_t anchorY, std::uint_fast32_t relativeWidth, std::uint_fast32_t relativeHeight, std::int_fast32_t anchorOffsetX, std::int_fast32_t anchorOffsetY, std::int_fast32_t z, bool ignoreScaling) + : bufferWidth(bufferWidth), bufferHeight(bufferHeight), buffer(bufferWidth*bufferHeight), + rotation(rotation), + RenderingElement(opaque, anchorX, anchorY, relativeWidth, relativeHeight, anchorOffsetX, anchorOffsetY, z, ignoreScaling) { + +} + +void RenderingElementScalingRotating2D::ResizeBuffer(std::uint_fast32_t width, std::uint_fast32_t height) { + this->bufferWidth = width; + this->bufferHeight = height; + buffer.resize(width * height); +} + +void RenderingElementScalingRotating2D::UpdateRotation(std::uint_fast32_t rotation) { + rotationUpdated = true; + this->rotation = rotation; +} + +void RenderingElementScalingRotating2D::UpdateScaledBuffer(ScaleData& scaled) { + // Rotation + const double rad = (static_cast(rotation) / static_cast(std::numeric_limits::max())) * 2.0 * std::numbers::pi; + + std::uint_fast32_t dstWidth = scaled.width; + std::uint_fast32_t dstHeight = scaled.height; + + const double rotatedWidth = scaled.width * (std::abs(std::cos(rad)) + std::abs(std::sin(rad))); + const double rotatedHeight = scaled.height * (std::abs(std::cos(rad)) + std::abs(std::sin(rad))); + + scaled.width = static_cast(std::ceil(rotatedWidth)); + scaled.height = static_cast(std::ceil(rotatedHeight)); + + bufferScaled.resize(scaled.width * scaled.height); + + // Destination center + const double dstCx = (static_cast(scaled.width) - 1.0) * 0.5; + const double dstCy = (static_cast(scaled.height) - 1.0) * 0.5; + + // Source center + const double srcCx = (static_cast(bufferWidth) - 1.0) * 0.5; + const double srcCy = (static_cast(bufferHeight) - 1.0) * 0.5; + + const double c = std::cos(rad); + const double s = std::sin(rad); + + // Scale factors (destination → source) + const double scaleX = static_cast(bufferWidth) / dstWidth; + const double scaleY = static_cast(bufferHeight) / dstHeight; + + for (std::uint_fast32_t yB = 0; yB < scaled.height; ++yB) { + for (std::uint_fast32_t xB = 0; xB < scaled.width; ++xB) { + + // ---- Destination pixel relative to center ---- + double dx = static_cast(xB) - dstCx; + double dy = static_cast(yB) - dstCy; + + // ---- Inverse scale ---- + dx *= scaleX; + dy *= scaleY; + + // ---- Inverse rotation ---- + double sx = c * dx - s * dy; + double sy = s * dx + c * dy; + + // ---- Move into source space ---- + sx += srcCx; + sy += srcCy; + + // ---- Nearest neighbour sampling ---- + const std::int_fast32_t srcX = static_cast(std::round(sx)); + const std::int_fast32_t srcY = static_cast(std::round(sy)); + + if (srcX >= 0 && srcX < bufferWidth && srcY >= 0 && srcY < bufferHeight) { + bufferScaled[yB * scaled.width + xB] = buffer[srcY * bufferWidth + srcX]; + } else { + bufferScaled[yB * scaled.width + xB] = {0, 0, 0, 0}; + } + } + } +} + + + + + + +void RenderingElementScalingRotating2D::UpdatePosition(Window& window) { + ScaleData oldScale = scaled; + window.ScaleElement(*this); + + if(oldScale.width != scaled.width || oldScale.height != scaled.height || rotationUpdated) { + UpdateScaledBuffer(scaled); + window.AddDirtyRect(oldScale); + window.AddDirtyRect(scaled); + std::cout << scaled.width << std::endl; + rotationUpdated = false; + } else if(oldScale.x != scaled.x || oldScale.y != scaled.y) { + + const double rad = (static_cast(rotation) / static_cast(std::numeric_limits::max())) * 2.0 * std::numbers::pi; + + const double rotatedWidth = scaled.width * (std::abs(std::cos(rad)) + std::abs(std::sin(rad))); + const double rotatedHeight = scaled.height * (std::abs(std::cos(rad)) + std::abs(std::sin(rad))); + + scaled.width = static_cast(std::ceil(rotatedWidth)); + scaled.height = static_cast(std::ceil(rotatedHeight)); + + window.AddDirtyRect(oldScale); + window.AddDirtyRect(scaled); + } + + for(Transform* child : children) { + child->UpdatePosition(window, *this); + } +} + +void RenderingElementScalingRotating2D::UpdatePosition(Window& window, Transform& parent) { + ScaleData oldScale = scaled; + window.ScaleElement(*this, parent); + + if(oldScale.width != scaled.width || oldScale.height != scaled.height || rotationUpdated) { + UpdateScaledBuffer(scaled); + window.AddDirtyRect(oldScale); + window.AddDirtyRect(scaled); + rotationUpdated = false; + } else if(oldScale.x != scaled.x || oldScale.y != scaled.y) { + const double rad = (static_cast(rotation) / static_cast(std::numeric_limits::max())) * 2.0 * std::numbers::pi; + + const double rotatedWidth = scaled.width * (std::abs(std::cos(rad)) + std::abs(std::sin(rad))); + const double rotatedHeight = scaled.height * (std::abs(std::cos(rad)) + std::abs(std::sin(rad))); + + scaled.width = static_cast(std::ceil(rotatedWidth)); + scaled.height = static_cast(std::ceil(rotatedHeight)); + + window.AddDirtyRect(oldScale); + window.AddDirtyRect(scaled); + } + + for(Transform* child : children) { + child->UpdatePosition(window, *this); + } +} \ No newline at end of file diff --git a/implementations/Crafter.Graphics-Window_wayland.cpp b/implementations/Crafter.Graphics-Window_wayland.cpp index 88e59fd..9c06fcd 100644 --- a/implementations/Crafter.Graphics-Window_wayland.cpp +++ b/implementations/Crafter.Graphics-Window_wayland.cpp @@ -302,9 +302,9 @@ void WindowWayland::Render() { dirtyRects = std::move(newClip); - // for(uint_fast32_t i = 0; i < width*height; i++) { - // framebuffer[i] = {0, 0, 0, 255}; - // } + for(uint_fast32_t i = 0; i < width*height; i++) { + framebuffer[i] = {0, 0, 0, 255}; + } // std::cout << dirtyRects.size() << std::endl; // for (ClipRect rect : dirtyRects) { diff --git a/interfaces/Crafter.Graphics-RenderingElement.cppm b/interfaces/Crafter.Graphics-RenderingElement.cppm index fb8f11a..66a5672 100644 --- a/interfaces/Crafter.Graphics-RenderingElement.cppm +++ b/interfaces/Crafter.Graphics-RenderingElement.cppm @@ -70,4 +70,25 @@ export namespace Crafter { void UpdatePosition(Window& window) override; void UpdatePosition(Window& window, Transform& parent) override; }; + + class RenderingElementScalingRotating2D : public RenderingElement { + public: + std::vector buffer; + std::uint_fast32_t bufferWidth; + std::uint_fast32_t bufferHeight; + std::uint_fast32_t rotation; + bool rotationUpdated = true; + + RenderingElementScalingRotating2D(OpaqueType opaque = OpaqueType::Transparent); + RenderingElementScalingRotating2D(OpaqueType opaque, std::int_fast32_t anchorX, std::int_fast32_t anchorY, std::uint_fast32_t relativeWidth, std::uint_fast32_t relativeHeight, std::int_fast32_t anchorOffsetX, std::int_fast32_t anchorOffsetY, std::int_fast32_t z, bool ignoreScaling); + RenderingElementScalingRotating2D(OpaqueType opaque, std::uint_fast32_t bufferWidth, std::uint_fast32_t bufferHeight, std::uint_fast32_t rotationAngle = 0, double scaleX = 1.0, double scaleY = 1.0, std::int_fast32_t anchorX = FractionalToMapped(0.5), std::int_fast32_t anchorY = FractionalToMapped(0.5), std::uint_fast32_t relativeWidth = FractionalToMapped(1), std::uint_fast32_t relativeHeight = FractionalToMapped(1), std::int_fast32_t anchorOffsetX = FractionalToMapped(0.5), std::int_fast32_t anchorOffsetY = FractionalToMapped(0.5), std::int_fast32_t z = 0, bool ignoreScaling = false); + RenderingElementScalingRotating2D(RenderingElementScalingRotating2D&) = delete; + RenderingElementScalingRotating2D& operator=(RenderingElementScalingRotating2D&) = delete; + + void UpdateRotation(std::uint_fast32_t newRotation); + void ResizeBuffer(std::uint_fast32_t width, std::uint_fast32_t height); + void UpdateScaledBuffer(ScaleData& data); + void UpdatePosition(Window& window) override; + void UpdatePosition(Window& window, Transform& parent) override; + }; } \ No newline at end of file diff --git a/interfaces/Crafter.Graphics-Types.cppm b/interfaces/Crafter.Graphics-Types.cppm index a1a3aea..9b76a15 100644 --- a/interfaces/Crafter.Graphics-Types.cppm +++ b/interfaces/Crafter.Graphics-Types.cppm @@ -111,14 +111,22 @@ namespace Crafter { export constexpr std::int_fast32_t BOUND = 9; export constexpr std::int_fast32_t SCALE = std::numeric_limits::max() / BOUND; export constexpr double SCALEDOUBLE = static_cast(std::numeric_limits::max()) / BOUND; + export constexpr double SCALEDOUBLEU = static_cast(std::numeric_limits::max()) / BOUND; export constexpr std::int_fast32_t SCALEBOUNDLESS = std::numeric_limits::max(); export constexpr double SCALEDOUBLEBOUNDLESS = static_cast(std::numeric_limits::max()); + export constexpr std::int_fast32_t SCALEBOUNDLESSU = std::numeric_limits::max(); + export constexpr double SCALEDOUBLEBOUNDLESSU = static_cast(std::numeric_limits::max()); + export constexpr std::int_fast32_t FractionalToMapped(double f) { return std::int_fast32_t(f * SCALEDOUBLE); } + export constexpr std::uint_fast32_t FractionalToMappedU(double f) { + return std::uint_fast32_t(f * SCALEDOUBLEU); + } + export constexpr double MappedToFractional(std::int_fast32_t mapped) { return static_cast(mapped) / SCALEDOUBLE; } @@ -139,6 +147,10 @@ namespace Crafter { return std::int_fast32_t(f * SCALEDOUBLEBOUNDLESS); } + export constexpr std::uint_fast32_t FractionalToMappedBoundlessU(double f) { + return std::uint_fast32_t(f * SCALEDOUBLEBOUNDLESSU); + } + export constexpr double MappedToFractionalBoundless(std::int_fast32_t mapped) { return static_cast(mapped) / SCALEDOUBLEBOUNDLESS; } diff --git a/project.json b/project.json index b730015..ec72a99 100644 --- a/project.json +++ b/project.json @@ -3,7 +3,7 @@ "configurations": [ { "name": "base", - "implementations": ["implementations/Crafter.Graphics-Font", "implementations/Crafter.Graphics-Shm", "implementations/Crafter.Graphics-Window", "implementations/Crafter.Graphics-RenderingElement", "implementations/Crafter.Graphics-TextElement", "implementations/Crafter.Graphics-ImageElement", "implementations/Crafter.Graphics-MouseElement", "implementations/Crafter.Graphics-Transform", "implementations/Crafter.Graphics-GridElement"], + "implementations": ["implementations/Crafter.Graphics-Font", "implementations/Crafter.Graphics-Shm", "implementations/Crafter.Graphics-Window", "implementations/Crafter.Graphics-RenderingElement", "implementations/Crafter.Graphics-RenderingElementScalingRotating2D", "implementations/Crafter.Graphics-TextElement", "implementations/Crafter.Graphics-ImageElement", "implementations/Crafter.Graphics-MouseElement", "implementations/Crafter.Graphics-Transform", "implementations/Crafter.Graphics-GridElement"], "interfaces": ["interfaces/Crafter.Graphics-Window", "interfaces/Crafter.Graphics", "interfaces/Crafter.Graphics-Types", "interfaces/Crafter.Graphics-Font", "interfaces/Crafter.Graphics-Shm", "interfaces/Crafter.Graphics-Animation", "interfaces/Crafter.Graphics-RenderingElement", "interfaces/Crafter.Graphics-TextElement", "interfaces/Crafter.Graphics-ImageElement", "interfaces/Crafter.Graphics-MouseElement", "interfaces/Crafter.Graphics-Transform", "interfaces/Crafter.Graphics-GridElement"], "type": "library" },