From 6b78788d049c90dab3aec01d7bb7193f3927dc75 Mon Sep 17 00:00:00 2001 From: Yuqian Yang Date: Sun, 21 Sep 2025 23:40:30 +0800 Subject: HALF WORK! --- include/cru/platform/GraphicsBase.h | 10 +++++++ include/cru/platform/graphics/Geometry.h | 12 ++++++++ include/cru/platform/graphics/quartz/Geometry.h | 2 ++ src/platform/graphics/Geometry.cpp | 37 +++++++++++++++++++++++++ src/platform/graphics/quartz/Geometry.cpp | 14 ++++++++++ test/platform/CMakeLists.txt | 2 ++ test/platform/graphics/CMakeLists.txt | 6 ++++ test/platform/graphics/GeometryTest.cpp | 32 +++++++++++++++++++++ 8 files changed, 115 insertions(+) create mode 100644 test/platform/graphics/CMakeLists.txt create mode 100644 test/platform/graphics/GeometryTest.cpp diff --git a/include/cru/platform/GraphicsBase.h b/include/cru/platform/GraphicsBase.h index b0f653ef..d9057beb 100644 --- a/include/cru/platform/GraphicsBase.h +++ b/include/cru/platform/GraphicsBase.h @@ -5,6 +5,7 @@ #include "cru/base/Range.h" #include "cru/base/String.h" +#include #include #include @@ -24,6 +25,11 @@ struct Point final { constexpr Point Negate() const { return Point(-x, -y); } + static constexpr float Distance(const Point& p1, const Point& p2) { + auto dx = p1.x - p2.x, dy = p1.y - p2.y; + return std::sqrt(dx * dx + dy * dy); + } + float x = 0; float y = 0; }; @@ -36,6 +42,10 @@ constexpr Point operator-(const Point& left, const Point& right) { return Point(left.x - right.x, left.y - right.y); } +constexpr Point operator/(const Point& point, float scale) { + return Point(point.x / scale, point.y / scale); +} + constexpr bool operator==(const Point& left, const Point& right) { return left.x == right.x && left.y == right.y; } diff --git a/include/cru/platform/graphics/Geometry.h b/include/cru/platform/graphics/Geometry.h index b4b05194..6550a8ce 100644 --- a/include/cru/platform/graphics/Geometry.h +++ b/include/cru/platform/graphics/Geometry.h @@ -63,6 +63,18 @@ struct CRU_PLATFORM_GRAPHICS_API IGeometry : virtual IGraphicsResource { * virtual so it can override them. */ struct CRU_PLATFORM_GRAPHICS_API IGeometryBuilder : virtual IGraphicsResource { + struct ArcInfo { + Point center; + // In radian. + float start_angle; + // In radian. + float end_angle; + }; + + static ArcInfo CalculateArcInfo(const Point& start_point, const Point& radius, + float angle, bool is_large_arc, bool is_clockwise, + const Point& end_point); + virtual Point GetCurrentPosition() = 0; virtual void MoveTo(const Point& point) = 0; diff --git a/include/cru/platform/graphics/quartz/Geometry.h b/include/cru/platform/graphics/quartz/Geometry.h index 18e2e25e..14d06deb 100644 --- a/include/cru/platform/graphics/quartz/Geometry.h +++ b/include/cru/platform/graphics/quartz/Geometry.h @@ -47,6 +47,8 @@ class QuartzGeometryBuilder : public OsxQuartzResource, const Point &end_point) override; void QuadraticBezierTo(const Point &control_point, const Point &end_point) override; + void ArcTo(const Point &radius, float angle, bool is_large_arc, + bool is_clockwise, const Point &end_point) override; void CloseFigure(bool close) override; std::unique_ptr Build() override; diff --git a/src/platform/graphics/Geometry.cpp b/src/platform/graphics/Geometry.cpp index b9503c4e..25e6808b 100644 --- a/src/platform/graphics/Geometry.cpp +++ b/src/platform/graphics/Geometry.cpp @@ -2,9 +2,12 @@ #include "cru/base/Exception.h" #include "cru/platform/Exception.h" +#include "cru/platform/GraphicsBase.h" +#include "cru/platform/Matrix.h" #include "cru/platform/graphics/Factory.h" #include +#include #include namespace cru::platform::graphics { @@ -20,6 +23,40 @@ std::unique_ptr IGeometry::CreateStrokeGeometry( u"not supported on this platform."); } +IGeometryBuilder::ArcInfo IGeometryBuilder::CalculateArcInfo( + const Point& start_point, const Point& radius, float angle, + bool is_large_arc, bool is_clockwise, const Point& end_point) { + auto matrix = + Matrix::Rotation(-angle) * Matrix::Scale(1 / radius.x, 1 / radius.y); + auto s1 = matrix.TransformPoint(start_point), + s2 = matrix.TransformPoint(end_point); + if (!is_clockwise) { + using std::swap; + swap(s1, s2); + } + + auto mid = (s1 + s2) / 2; + auto d = Point::Distance(s1, s2) / 2; + auto dc = std::sqrt(1 - d * d); + auto a = std::atan2(s1.x - s2.x, s2.y - s1.y); + Point center(mid.x - dc * std::cos(a), mid.y - dc * std::sin(a)); + + if (std::abs(center.x) < 0.000001) { + center.x = 0.f; + } + if (std::abs(center.y) < 0.000001) { + center.y = 0.f; + } + + auto start_angle = std::atan2(s1.y - center.y, s1.x - center.x); + auto end_angle = std::atan2(s2.y - center.y, s2.x - center.x); + if (end_angle < 0) { + end_angle += std::numbers::pi_v * 2; + } + + return {matrix.Inverted()->TransformPoint(center), start_angle, end_angle}; +} + void IGeometryBuilder::RelativeMoveTo(const Point& offset) { MoveTo(GetCurrentPosition() + offset); } diff --git a/src/platform/graphics/quartz/Geometry.cpp b/src/platform/graphics/quartz/Geometry.cpp index e6558fbb..db0581a7 100644 --- a/src/platform/graphics/quartz/Geometry.cpp +++ b/src/platform/graphics/quartz/Geometry.cpp @@ -68,6 +68,20 @@ void QuartzGeometryBuilder::QuadraticBezierTo(const Point &control_point, control_point.y, end_point.x, end_point.y); } +void QuartzGeometryBuilder::ArcTo(const Point &radius, float angle, + bool is_large_arc, bool is_clockwise, + const Point &end_point) { + auto info = CalculateArcInfo(GetCurrentPosition(), radius, angle, + is_large_arc, is_clockwise, end_point); + + auto matrix = + Matrix::Translation(info.center) * Matrix::Scale(radius.x, radius.y); + CGAffineTransform transform = Convert(matrix); + + CGPathAddArc(cg_mutable_path_, &transform, 0, 0, 1, info.start_angle, + info.end_angle, true); +} + void QuartzGeometryBuilder::CloseFigure(bool close) { if (close) CGPathCloseSubpath(cg_mutable_path_); } diff --git a/test/platform/CMakeLists.txt b/test/platform/CMakeLists.txt index 394aa247..6a00ff21 100644 --- a/test/platform/CMakeLists.txt +++ b/test/platform/CMakeLists.txt @@ -4,6 +4,8 @@ add_executable(CruPlatformBaseTest ) target_link_libraries(CruPlatformBaseTest PRIVATE CruPlatformBase CruTestBase) +add_subdirectory(graphics) + if (WIN32) add_subdirectory(graphics/direct2d) endif() diff --git a/test/platform/graphics/CMakeLists.txt b/test/platform/graphics/CMakeLists.txt new file mode 100644 index 00000000..22be21e6 --- /dev/null +++ b/test/platform/graphics/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(CruPlatformGraphicsTest + GeometryTest.cpp +) +target_link_libraries(CruPlatformGraphicsTest PRIVATE CruPlatformGraphics CruTestBase) + +cru_catch_discover_tests(CruPlatformGraphicsTest) diff --git a/test/platform/graphics/GeometryTest.cpp b/test/platform/graphics/GeometryTest.cpp new file mode 100644 index 00000000..8a48ddde --- /dev/null +++ b/test/platform/graphics/GeometryTest.cpp @@ -0,0 +1,32 @@ +#include "cru/platform/graphics/Geometry.h" + +#include +#include +#include "catch2/catch_approx.hpp" + +using Catch::Approx; +using cru::platform::graphics::IGeometryBuilder; + +constexpr float margin = 0.000001; + +TEST_CASE("IGeometryBuilder CalculateArcInfo", "[graphics][geometry]") { + auto test = [](float start_x, float start_y, float radius_x, float radius_y, + float angle, bool is_large_arc, bool is_clockwise, float end_x, + float end_y, float expect_center_x, float expect_center_y, + float expect_start_angle, float expect_end_angle) { + auto info = IGeometryBuilder::CalculateArcInfo( + {start_x, start_y}, {radius_x, radius_y}, angle, is_large_arc, + is_clockwise, {end_x, end_y}); + REQUIRE(info.center.x == Approx(expect_center_x).margin(margin)); + REQUIRE(info.center.y == Approx(expect_center_y).margin(margin)); + REQUIRE(info.start_angle == Approx(expect_start_angle).margin(margin)); + REQUIRE(info.end_angle == Approx(expect_end_angle).margin(margin)); + }; + + test(1, 0, 1, 1, 0, false, true, 0, 1, 0, 0, 0, std::numbers::pi / 2); + test(0, 1, 1, 1, 0, false, true, -1, 0, 0, 0, std::numbers::pi / 2, + std::numbers::pi); + test(-1, 0, 1, 1, 0, false, true, 0, -1, 0, 0, std::numbers::pi, + -std::numbers::pi / 2); + test(0, -1, 1, 1, 0, false, true, 1, 0, 0, 0, -std::numbers::pi / 2, 0); +} -- cgit v1.2.3