diff --git a/scripts/build-wxwidgets-native.sh b/scripts/build-wxwidgets-native.sh deleted file mode 100755 index 1a0d996..0000000 --- a/scripts/build-wxwidgets-native.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash -# -# Build wxWidgets for native macOS (not WASM) -# This is used for the native GAL test harness -# - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -WX_SRC="$PROJECT_ROOT/wxwidgets" -BUILD_DIR="$PROJECT_ROOT/build-native/wxwidgets" -INSTALL_DIR="$PROJECT_ROOT/build-native/wxwidgets-install" -LOG_FILE="$PROJECT_ROOT/tests/logs/wxwidgets-native-build.log" - -mkdir -p "$(dirname "$LOG_FILE")" - -echo "Building wxWidgets for native macOS..." -echo " Source: $WX_SRC" -echo " Build: $BUILD_DIR" -echo " Install: $INSTALL_DIR" -echo " Log: $LOG_FILE" - -# Create build directory -mkdir -p "$BUILD_DIR" -cd "$BUILD_DIR" - -# Set up paths for homebrew libraries -export PKG_CONFIG_PATH="/opt/homebrew/lib/pkgconfig:/opt/homebrew/opt/libpng/lib/pkgconfig:$PKG_CONFIG_PATH" -export CPPFLAGS="-I/opt/homebrew/include $CPPFLAGS" -export LDFLAGS="-L/opt/homebrew/lib $LDFLAGS" - -# Configure wxWidgets for native macOS with OpenGL support -echo "Configuring..." -"$WX_SRC/configure" \ - --prefix="$INSTALL_DIR" \ - --disable-shared \ - --with-opengl \ - --with-osx_cocoa \ - --disable-webview \ - --disable-mediactrl \ - --with-libpng=sys \ - --with-libjpeg=sys \ - --with-libtiff=sys \ - --with-zlib=sys \ - >> "$LOG_FILE" 2>&1 - -# Build -echo "Building (this may take a while)..." -make -j$(sysctl -n hw.ncpu 2>/dev/null || echo 4) >> "$LOG_FILE" 2>&1 - -# Install -echo "Installing..." -make install >> "$LOG_FILE" 2>&1 - -echo "" -echo "wxWidgets native build complete!" -echo "wx-config: $INSTALL_DIR/bin/wx-config" -echo "" -echo "To use in CMake:" -echo " set(wxWidgets_CONFIG_EXECUTABLE $INSTALL_DIR/bin/wx-config)" -echo " find_package(wxWidgets REQUIRED COMPONENTS core base gl)" diff --git a/tests/gal-regression/baseline/gal-arc-segments.png b/tests/gal-regression/baseline/gal-arc-segments.png new file mode 100644 index 0000000..d00480d Binary files /dev/null and b/tests/gal-regression/baseline/gal-arc-segments.png differ diff --git a/tests/gal-regression/baseline/gal-bezier-curves.png b/tests/gal-regression/baseline/gal-bezier-curves.png new file mode 100644 index 0000000..4bb2ef3 Binary files /dev/null and b/tests/gal-regression/baseline/gal-bezier-curves.png differ diff --git a/tests/gal-regression/baseline/gal-group-caching.png b/tests/gal-regression/baseline/gal-group-caching.png new file mode 100644 index 0000000..98dd602 Binary files /dev/null and b/tests/gal-regression/baseline/gal-group-caching.png differ diff --git a/tests/gal-regression/baseline/gal-segment-chain.png b/tests/gal-regression/baseline/gal-segment-chain.png new file mode 100644 index 0000000..3b06a55 Binary files /dev/null and b/tests/gal-regression/baseline/gal-segment-chain.png differ diff --git a/tests/gal-regression/native/CMakeLists.txt b/tests/gal-regression/native/CMakeLists.txt index 9febf2c..4a2a523 100644 --- a/tests/gal-regression/native/CMakeLists.txt +++ b/tests/gal-regression/native/CMakeLists.txt @@ -62,13 +62,23 @@ set(KICAD_GAL_SOURCES set(STUB_SOURCES ${CMAKE_SOURCE_DIR}/kicad_stubs.cpp ${CMAKE_SOURCE_DIR}/gal_test_accessor.cpp +) + +# Test scenario files +set(SCENARIO_SOURCES ${CMAKE_SOURCE_DIR}/../scenarios/gal_test_scenarios.cpp + # New modular scenarios + ${CMAKE_SOURCE_DIR}/../scenarios/scenario_bezier_curves.cpp + ${CMAKE_SOURCE_DIR}/../scenarios/scenario_arc_segments.cpp + ${CMAKE_SOURCE_DIR}/../scenarios/scenario_segment_chain.cpp + ${CMAKE_SOURCE_DIR}/../scenarios/scenario_group_caching.cpp ) # Main executable add_executable(gal_native_test gal_native_test.cpp ${STUB_SOURCES} + ${SCENARIO_SOURCES} ${KICAD_GAL_SOURCES} ${SHADER_SOURCES} ) diff --git a/tests/gal-regression/scenarios/gal_test_scenarios.cpp b/tests/gal-regression/scenarios/gal_test_scenarios.cpp index b673b5e..3771096 100644 --- a/tests/gal-regression/scenarios/gal_test_scenarios.cpp +++ b/tests/gal-regression/scenarios/gal_test_scenarios.cpp @@ -3,6 +3,13 @@ * * Uses KiCad's GAL API to render test patterns. * These serve as baselines for WEBGL_GAL visual regression testing. + * + * This file contains the original 11 scenarios (0-10). + * Additional scenarios are in separate files: + * - scenario_bezier_curves.cpp (11) + * - scenario_arc_segments.cpp (12) + * - scenario_segment_chain.cpp (13) + * - scenario_group_caching.cpp (14) */ #include "gal_test_scenarios.h" @@ -25,8 +32,15 @@ namespace GALTest { using KIGFX::COLOR4D; using KIGFX::GAL; -// Scenario names +// Forward declarations for scenarios in separate files +void RenderBezierCurves(GAL* gal, int width, int height); +void RenderArcSegments(GAL* gal, int width, int height); +void RenderSegmentChain(GAL* gal, int width, int height); +void RenderGroupCaching(GAL* gal, int width, int height); + +// Scenario names - original 11 + 4 new scenarios static const char* SCENARIO_NAMES[] = { + // Original scenarios (0-10) "basic-lines", "line-widths", "circles", @@ -37,7 +51,12 @@ static const char* SCENARIO_NAMES[] = { "transforms", "grid-cursor", "segments", - "complex-scene" + "complex-scene", + // New scenarios (11-14) - defined in separate files + "bezier-curves", + "arc-segments", + "segment-chain", + "group-caching" }; static const int SCENARIO_COUNT = sizeof(SCENARIO_NAMES) / sizeof(SCENARIO_NAMES[0]); @@ -549,6 +568,7 @@ static void RenderComplexScene(KIGFX::GAL* gal, int width, int height) { void RenderScenario(KIGFX::GAL* gal, int index, int width, int height) { switch (index) { + // Original scenarios (0-10) - defined in this file case 0: RenderBasicLines(gal, width, height); break; case 1: RenderLineWidths(gal, width, height); break; case 2: RenderCircles(gal, width, height); break; @@ -560,6 +580,11 @@ void RenderScenario(KIGFX::GAL* gal, int index, int width, int height) { case 8: RenderGridCursor(gal, width, height); break; case 9: RenderSegments(gal, width, height); break; case 10: RenderComplexScene(gal, width, height); break; + // New scenarios (11-14) - defined in separate files + case 11: RenderBezierCurves(gal, width, height); break; + case 12: RenderArcSegments(gal, width, height); break; + case 13: RenderSegmentChain(gal, width, height); break; + case 14: RenderGroupCaching(gal, width, height); break; default: break; } } diff --git a/tests/gal-regression/scenarios/scenario_arc_segments.cpp b/tests/gal-regression/scenarios/scenario_arc_segments.cpp new file mode 100644 index 0000000..b5f6a6b --- /dev/null +++ b/tests/gal-regression/scenarios/scenario_arc_segments.cpp @@ -0,0 +1,161 @@ +/** + * Arc Segments Scenario + * + * Tests GAL::DrawArcSegment() - thick arc strokes + * + * Unlike DrawArc() which draws pie slices when filled, + * DrawArcSegment() draws thick arc strokes (like a PCB trace). + */ + +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace GALTest { + +using KIGFX::COLOR4D; +using KIGFX::GAL; + +void RenderArcSegments(GAL* gal, int width, int height) { + double cx = width / 2.0; + double cy = height / 2.0; + double maxError = 0.01; // Segment approximation error + + // Thick arc segments with varying widths + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // 90-degree arcs at different widths + double widths[] = {5.0, 10.0, 20.0, 30.0}; + double radius = 80; + + for (int i = 0; i < 4; i++) { + double t = (double)i / 3.0; + gal->SetFillColor(COLOR4D(1.0 - t * 0.5, 0.3 + t * 0.4, 0.2 + t * 0.6, 1.0)); + + EDA_ANGLE startAngle(i * 90, DEGREES_T); + EDA_ANGLE arcAngle(90, DEGREES_T); + + gal->DrawArcSegment( + VECTOR2D(cx - 200, cy - 100), + radius, + startAngle, + arcAngle, + widths[i], + maxError + ); + } + + // Full ring using arc segments + gal->SetFillColor(COLOR4D(0.8, 0.6, 0.2, 1.0)); + gal->DrawArcSegment( + VECTOR2D(cx, cy - 100), + 60, + EDA_ANGLE(0, DEGREES_T), + EDA_ANGLE(360, DEGREES_T), + 15, + maxError + ); + + // Half rings + gal->SetFillColor(COLOR4D(0.2, 0.8, 0.4, 1.0)); + gal->DrawArcSegment( + VECTOR2D(cx + 180, cy - 100), + 70, + EDA_ANGLE(0, DEGREES_T), + EDA_ANGLE(180, DEGREES_T), + 12, + maxError + ); + + gal->SetFillColor(COLOR4D(0.4, 0.2, 0.8, 1.0)); + gal->DrawArcSegment( + VECTOR2D(cx + 180, cy - 100), + 70, + EDA_ANGLE(180, DEGREES_T), + EDA_ANGLE(180, DEGREES_T), + 8, + maxError + ); + + // Concentric arc segments (like a spiral) + double baseRadius = 40; + for (int i = 0; i < 5; i++) { + double t = (double)i / 4.0; + gal->SetFillColor(COLOR4D(t, 0.5, 1.0 - t, 0.9)); + + gal->DrawArcSegment( + VECTOR2D(cx - 150, cy + 150), + baseRadius + i * 25, + EDA_ANGLE(i * 30, DEGREES_T), + EDA_ANGLE(270, DEGREES_T), + 6, + maxError + ); + } + + // Thin arc segments (like copper traces) + gal->SetFillColor(COLOR4D(0.9, 0.7, 0.3, 1.0)); + for (int i = 0; i < 8; i++) { + gal->DrawArcSegment( + VECTOR2D(cx + 100, cy + 150), + 30 + i * 15, + EDA_ANGLE(45, DEGREES_T), + EDA_ANGLE(90, DEGREES_T), + 3, + maxError + ); + } + + // Small angle arc segments + gal->SetFillColor(COLOR4D(1.0, 0.4, 0.4, 1.0)); + gal->DrawArcSegment( + VECTOR2D(cx, cy + 100), + 100, + EDA_ANGLE(0, DEGREES_T), + EDA_ANGLE(30, DEGREES_T), + 20, + maxError + ); + + gal->SetFillColor(COLOR4D(0.4, 1.0, 0.4, 1.0)); + gal->DrawArcSegment( + VECTOR2D(cx, cy + 100), + 100, + EDA_ANGLE(60, DEGREES_T), + EDA_ANGLE(30, DEGREES_T), + 20, + maxError + ); + + gal->SetFillColor(COLOR4D(0.4, 0.4, 1.0, 1.0)); + gal->DrawArcSegment( + VECTOR2D(cx, cy + 100), + 100, + EDA_ANGLE(120, DEGREES_T), + EDA_ANGLE(30, DEGREES_T), + 20, + maxError + ); + + // Stroked arc segment outline + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(1.0, 1.0, 0.0, 1.0)); + + gal->DrawArcSegment( + VECTOR2D(cx + 200, cy + 100), + 50, + EDA_ANGLE(0, DEGREES_T), + EDA_ANGLE(270, DEGREES_T), + 25, + maxError + ); +} + +} // namespace GALTest diff --git a/tests/gal-regression/scenarios/scenario_bezier_curves.cpp b/tests/gal-regression/scenarios/scenario_bezier_curves.cpp new file mode 100644 index 0000000..28997b2 --- /dev/null +++ b/tests/gal-regression/scenarios/scenario_bezier_curves.cpp @@ -0,0 +1,138 @@ +/** + * Bezier Curves Scenario + * + * Tests GAL::DrawCurve() - cubic bezier splines + */ + +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace GALTest { + +using KIGFX::COLOR4D; +using KIGFX::GAL; + +void RenderBezierCurves(GAL* gal, int width, int height) { + double cx = width / 2.0; + double cy = height / 2.0; + + gal->SetIsFill(false); + gal->SetIsStroke(true); + + // Simple S-curve + gal->SetStrokeColor(COLOR4D(1.0, 0.3, 0.3, 1.0)); + gal->SetLineWidth(3.0); + gal->DrawCurve( + VECTOR2D(100, cy - 100), // start + VECTOR2D(200, cy - 200), // control 1 + VECTOR2D(300, cy), // control 2 + VECTOR2D(400, cy - 100), // end + 0.0 // filter value + ); + + // Inverse S-curve below + gal->SetStrokeColor(COLOR4D(0.3, 1.0, 0.3, 1.0)); + gal->DrawCurve( + VECTOR2D(100, cy + 100), + VECTOR2D(200, cy + 200), + VECTOR2D(300, cy), + VECTOR2D(400, cy + 100), + 0.0 + ); + + // Loop curve (tight control points) + gal->SetStrokeColor(COLOR4D(0.3, 0.3, 1.0, 1.0)); + gal->SetLineWidth(2.0); + gal->DrawCurve( + VECTOR2D(450, cy - 50), + VECTOR2D(550, cy - 150), + VECTOR2D(550, cy + 150), + VECTOR2D(450, cy + 50), + 0.0 + ); + + // Straight-ish curve (control points on line) + gal->SetStrokeColor(COLOR4D(1.0, 1.0, 0.3, 1.0)); + gal->DrawCurve( + VECTOR2D(500, 80), + VECTOR2D(550, 80), + VECTOR2D(650, 80), + VECTOR2D(700, 80), + 0.0 + ); + + // Wave pattern using multiple curves + gal->SetStrokeColor(COLOR4D(0.8, 0.5, 0.8, 1.0)); + gal->SetLineWidth(2.5); + double waveY = height - 100; + double segWidth = 100; + double amplitude = 40; + + for (int i = 0; i < 6; i++) { + double x0 = 100 + i * segWidth; + double x1 = x0 + segWidth; + double dir = (i % 2 == 0) ? 1.0 : -1.0; + + gal->DrawCurve( + VECTOR2D(x0, waveY), + VECTOR2D(x0 + segWidth * 0.33, waveY + amplitude * dir), + VECTOR2D(x0 + segWidth * 0.66, waveY + amplitude * dir), + VECTOR2D(x1, waveY), + 0.0 + ); + } + + // Heart shape using two curves + gal->SetStrokeColor(COLOR4D(1.0, 0.2, 0.4, 1.0)); + gal->SetLineWidth(3.0); + double hx = cx + 150; + double hy = cy - 50; + double hs = 50; // heart scale + + // Left half of heart + gal->DrawCurve( + VECTOR2D(hx, hy + hs * 0.5), // bottom point + VECTOR2D(hx - hs * 1.5, hy - hs), // left control + VECTOR2D(hx - hs * 0.5, hy - hs * 1.5), // top control + VECTOR2D(hx, hy - hs * 0.3), // top middle + 0.0 + ); + + // Right half of heart + gal->DrawCurve( + VECTOR2D(hx, hy - hs * 0.3), // top middle + VECTOR2D(hx + hs * 0.5, hy - hs * 1.5), // top control + VECTOR2D(hx + hs * 1.5, hy - hs), // right control + VECTOR2D(hx, hy + hs * 0.5), // bottom point + 0.0 + ); + + // Control point visualization (draw handles) + gal->SetStrokeColor(COLOR4D(0.5, 0.5, 0.5, 0.5)); + gal->SetLineWidth(1.0); + + // Show control points for the first curve + VECTOR2D p0(100, cy - 100); + VECTOR2D c0(200, cy - 200); + VECTOR2D c1(300, cy); + VECTOR2D p1(400, cy - 100); + + gal->DrawLine(p0, c0); + gal->DrawLine(p1, c1); + + // Draw control point markers + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(1.0, 0.5, 0.0, 0.8)); + gal->DrawCircle(c0, 5); + gal->DrawCircle(c1, 5); + gal->SetFillColor(COLOR4D(0.0, 0.5, 1.0, 0.8)); + gal->DrawCircle(p0, 5); + gal->DrawCircle(p1, 5); +} + +} // namespace GALTest diff --git a/tests/gal-regression/scenarios/scenario_group_caching.cpp b/tests/gal-regression/scenarios/scenario_group_caching.cpp new file mode 100644 index 0000000..f24d17e --- /dev/null +++ b/tests/gal-regression/scenarios/scenario_group_caching.cpp @@ -0,0 +1,191 @@ +/** + * Group Caching Scenario + * + * Tests GAL group/caching methods: + * - BeginGroup() / EndGroup() - define cached geometry + * - DrawGroup() - render cached geometry + * - ChangeGroupColor() - modify group color + * - ChangeGroupDepth() - modify group depth + * + * Groups allow geometry to be cached in GPU memory and redrawn + * efficiently, useful for static elements that don't change often. + */ + +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace GALTest { + +using KIGFX::COLOR4D; +using KIGFX::GAL; + +void RenderGroupCaching(GAL* gal, int width, int height) { + double cx = width / 2.0; + double cy = height / 2.0; + + // Create a group with a simple pattern + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(0.8, 0.4, 0.2, 1.0)); + + int group1 = gal->BeginGroup(); + { + // Draw a simple component symbol + gal->DrawRectangle(VECTOR2D(-30, -20), VECTOR2D(30, 20)); + gal->SetFillColor(COLOR4D(0.3, 0.3, 0.3, 1.0)); + gal->DrawCircle(VECTOR2D(-15, 0), 8); + gal->DrawCircle(VECTOR2D(15, 0), 8); + } + gal->EndGroup(); + + // Create another group with different geometry + gal->SetFillColor(COLOR4D(0.2, 0.6, 0.8, 1.0)); + + int group2 = gal->BeginGroup(); + { + // Draw a triangle marker + std::deque tri = { + VECTOR2D(0, -25), + VECTOR2D(-20, 15), + VECTOR2D(20, 15) + }; + gal->DrawPolygon(tri); + } + gal->EndGroup(); + + // Create a more complex group + gal->SetFillColor(COLOR4D(0.6, 0.2, 0.6, 1.0)); + gal->SetIsStroke(true); + gal->SetIsFill(false); + gal->SetLineWidth(2.0); + gal->SetStrokeColor(COLOR4D(0.8, 0.8, 0.2, 1.0)); + + int group3 = gal->BeginGroup(); + { + // Draw a star outline + for (int i = 0; i < 5; i++) { + double angle1 = -M_PI / 2 + i * 2 * M_PI / 5; + double angle2 = -M_PI / 2 + ((i + 2) % 5) * 2 * M_PI / 5; + gal->DrawLine( + VECTOR2D(cos(angle1) * 30, sin(angle1) * 30), + VECTOR2D(cos(angle2) * 30, sin(angle2) * 30) + ); + } + } + gal->EndGroup(); + + // Now draw the groups at different positions + + // Draw group1 multiple times + gal->Save(); + gal->Translate(VECTOR2D(100, 100)); + gal->DrawGroup(group1); + gal->Restore(); + + gal->Save(); + gal->Translate(VECTOR2D(250, 100)); + gal->DrawGroup(group1); + gal->Restore(); + + gal->Save(); + gal->Translate(VECTOR2D(400, 100)); + gal->Rotate(45.0 * M_PI / 180.0); + gal->DrawGroup(group1); + gal->Restore(); + + // Draw group2 multiple times + gal->Save(); + gal->Translate(VECTOR2D(100, 250)); + gal->DrawGroup(group2); + gal->Restore(); + + gal->Save(); + gal->Translate(VECTOR2D(200, 250)); + gal->Scale(VECTOR2D(1.5, 1.5)); + gal->DrawGroup(group2); + gal->Restore(); + + gal->Save(); + gal->Translate(VECTOR2D(320, 250)); + gal->Rotate(180.0 * M_PI / 180.0); + gal->DrawGroup(group2); + gal->Restore(); + + // Draw group3 multiple times + gal->Save(); + gal->Translate(VECTOR2D(500, 200)); + gal->DrawGroup(group3); + gal->Restore(); + + gal->Save(); + gal->Translate(VECTOR2D(600, 200)); + gal->Scale(VECTOR2D(1.3, 1.3)); + gal->DrawGroup(group3); + gal->Restore(); + + gal->Save(); + gal->Translate(VECTOR2D(700, 200)); + gal->Rotate(36.0 * M_PI / 180.0); + gal->DrawGroup(group3); + gal->Restore(); + + // Test ChangeGroupColor - redraw with different colors + gal->ChangeGroupColor(group1, COLOR4D(0.2, 0.8, 0.4, 1.0)); + gal->Save(); + gal->Translate(VECTOR2D(100, 400)); + gal->DrawGroup(group1); + gal->Restore(); + + gal->ChangeGroupColor(group1, COLOR4D(0.4, 0.4, 0.9, 1.0)); + gal->Save(); + gal->Translate(VECTOR2D(250, 400)); + gal->DrawGroup(group1); + gal->Restore(); + + // Test ChangeGroupDepth + gal->SetLayerDepth(50); + gal->ChangeGroupDepth(group2, 30); + gal->Save(); + gal->Translate(VECTOR2D(450, 400)); + gal->DrawGroup(group2); + gal->Restore(); + + // Draw a rectangle to show depth ordering + gal->SetLayerDepth(40); + gal->SetIsFill(true); + gal->SetIsStroke(false); + gal->SetFillColor(COLOR4D(1.0, 0.5, 0.5, 0.7)); + gal->DrawRectangle(VECTOR2D(430, 385), VECTOR2D(470, 430)); + + // Labels using simple geometry (no text) + gal->SetLayerDepth(10); + gal->SetIsFill(false); + gal->SetIsStroke(true); + gal->SetLineWidth(1.0); + gal->SetStrokeColor(COLOR4D(0.5, 0.5, 0.5, 1.0)); + + // "Group 1" marker + gal->DrawRectangle(VECTOR2D(80, 60), VECTOR2D(180, 75)); + + // "Group 2" marker + gal->DrawRectangle(VECTOR2D(80, 210), VECTOR2D(180, 225)); + + // "Group 3" marker + gal->DrawRectangle(VECTOR2D(480, 160), VECTOR2D(580, 175)); + + // Grid pattern to show group reuse efficiency + gal->SetStrokeColor(COLOR4D(0.3, 0.3, 0.3, 0.3)); + for (int x = 0; x < width; x += 50) { + gal->DrawLine(VECTOR2D(x, 0), VECTOR2D(x, height)); + } + for (int y = 0; y < height; y += 50) { + gal->DrawLine(VECTOR2D(0, y), VECTOR2D(width, y)); + } +} + +} // namespace GALTest diff --git a/tests/gal-regression/scenarios/scenario_segment_chain.cpp b/tests/gal-regression/scenarios/scenario_segment_chain.cpp new file mode 100644 index 0000000..79a08ae --- /dev/null +++ b/tests/gal-regression/scenarios/scenario_segment_chain.cpp @@ -0,0 +1,142 @@ +/** + * Segment Chain Scenario + * + * Tests GAL::DrawSegmentChain() - connected thick segments with round joins + * + * This is used for drawing thick polylines like PCB traces with proper + * join handling between segments. + */ + +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace GALTest { + +using KIGFX::COLOR4D; +using KIGFX::GAL; + +void RenderSegmentChain(GAL* gal, int width, int height) { + double cx = width / 2.0; + double cy = height / 2.0; + + gal->SetIsFill(true); + gal->SetIsStroke(false); + + // Simple zigzag chain + gal->SetFillColor(COLOR4D(0.9, 0.6, 0.2, 1.0)); + std::vector zigzag = { + VECTOR2D(80, 80), + VECTOR2D(180, 150), + VECTOR2D(280, 80), + VECTOR2D(380, 150), + VECTOR2D(480, 80) + }; + gal->DrawSegmentChain(zigzag, 12.0); + + // Smooth wave chain + gal->SetFillColor(COLOR4D(0.2, 0.7, 0.9, 1.0)); + std::vector wave; + for (int i = 0; i <= 20; i++) { + double t = (double)i / 20.0; + double x = 80 + t * 400; + double y = 220 + sin(t * M_PI * 3) * 40; + wave.push_back(VECTOR2D(x, y)); + } + gal->DrawSegmentChain(wave, 8.0); + + // Sharp corners (tests join handling) + gal->SetFillColor(COLOR4D(0.8, 0.3, 0.5, 1.0)); + std::vector sharp = { + VECTOR2D(550, 80), + VECTOR2D(650, 80), + VECTOR2D(650, 180), + VECTOR2D(550, 180), + VECTOR2D(550, 130), + VECTOR2D(700, 130) + }; + gal->DrawSegmentChain(sharp, 10.0); + + // Spiral chain + gal->SetFillColor(COLOR4D(0.5, 0.8, 0.3, 1.0)); + std::vector spiral; + double spiralCx = cx - 150; + double spiralCy = cy + 100; + for (int i = 0; i <= 30; i++) { + double angle = i * M_PI / 6; + double radius = 20 + i * 3; + spiral.push_back(VECTOR2D( + spiralCx + cos(angle) * radius, + spiralCy + sin(angle) * radius + )); + } + gal->DrawSegmentChain(spiral, 6.0); + + // Varying width segments (multiple chains) + double widths[] = {3.0, 6.0, 10.0, 15.0, 20.0}; + for (int w = 0; w < 5; w++) { + double t = (double)w / 4.0; + gal->SetFillColor(COLOR4D(1.0 - t * 0.5, 0.3 + t * 0.4, 0.3 + t * 0.5, 1.0)); + + std::vector line = { + VECTOR2D(80, 320 + w * 45), + VECTOR2D(200, 340 + w * 45), + VECTOR2D(280, 310 + w * 45) + }; + gal->DrawSegmentChain(line, widths[w]); + } + + // Acute angle chain (stress test for joins) + gal->SetFillColor(COLOR4D(1.0, 0.5, 0.2, 1.0)); + std::vector acute = { + VECTOR2D(400, 350), + VECTOR2D(500, 350), + VECTOR2D(420, 380), // Very acute angle back + VECTOR2D(520, 380), + VECTOR2D(440, 410), + VECTOR2D(540, 410) + }; + gal->DrawSegmentChain(acute, 8.0); + + // Star pattern chain + gal->SetFillColor(COLOR4D(0.9, 0.9, 0.2, 1.0)); + std::vector star; + double starCx = cx + 180; + double starCy = cy + 120; + double outerR = 60; + double innerR = 25; + for (int i = 0; i <= 10; i++) { + double angle = i * M_PI / 5 - M_PI / 2; + double r = (i % 2 == 0) ? outerR : innerR; + star.push_back(VECTOR2D( + starCx + cos(angle) * r, + starCy + sin(angle) * r + )); + } + gal->DrawSegmentChain(star, 5.0); + + // Single segment (degenerate case) + gal->SetFillColor(COLOR4D(0.6, 0.4, 0.8, 1.0)); + std::vector single = { + VECTOR2D(600, 280), + VECTOR2D(720, 320) + }; + gal->DrawSegmentChain(single, 15.0); + + // Very thin chain + gal->SetFillColor(COLOR4D(0.3, 0.3, 0.3, 1.0)); + std::vector thin; + for (int i = 0; i <= 15; i++) { + double t = (double)i / 15.0; + double x = 550 + t * 180; + double y = 450 + sin(t * M_PI * 4) * 20; + thin.push_back(VECTOR2D(x, y)); + } + gal->DrawSegmentChain(thin, 2.0); +} + +} // namespace GALTest