Task: Sutherland-Hodgman polygon clipping


C++: #include <iostream>
#include <span>
#include <vector>

struct vec2 {
    float x = 0.0f, y = 0.0f;

    constexpr vec2 operator+(vec2 other) const {
        return vec2{x + other.x, y + other.y};
    }

    constexpr vec2 operator-(vec2 other) const {
        return vec2{x - other.x, y - other.y};
    }
};

constexpr vec2 operator*(vec2 a, float b) { return vec2{a.x * b, a.y * b}; }

constexpr float dot(vec2 a, vec2 b) { return a.x * b.x + a.y * b.y; }

constexpr float cross(vec2 a, vec2 b) { return a.x * b.y - b.x * a.y; }


constexpr bool is_inside(vec2 point, vec2 a, vec2 b) {
    return (cross(a - b, point) + cross(b, a)) < 0.0f;
}


constexpr vec2 intersection(vec2 a1, vec2 a2, vec2 b1, vec2 b2) {
    return ((b1 - b2) * cross(a1, a2) - (a1 - a2) * cross(b1, b2)) *
           (1.0f / cross(a1 - a2, b1 - b2));
}


std::vector<vec2> suther_land_hodgman(
    std::span<vec2 const> subject_polygon, std::span<vec2 const> clip_polygon) {
    if (clip_polygon.empty() || subject_polygon.empty()) {
        return {};
    }

    std::vector<vec2> ring{subject_polygon.begin(), subject_polygon.end()};

    vec2 p1 = clip_polygon[clip_polygon.size() - 1];

    std::vector<vec2> input;

    for (vec2 p2 : clip_polygon) {
        input.clear();
        input.insert(input.end(), ring.begin(), ring.end());
        vec2 s = input[input.size() - 1];

        ring.clear();

        for (vec2 e : input) {
            if (is_inside(e, p1, p2)) {
                if (!is_inside(s, p1, p2)) {
                    ring.push_back(intersection(p1, p2, s, e));
                }

                ring.push_back(e);
            } else if (is_inside(s, p1, p2)) {
                ring.push_back(intersection(p1, p2, s, e));
            }

            s = e;
        }

        p1 = p2;
    }

    return ring;
}

int main(int argc, char **argv) {
    
    vec2 subject_polygon[] = {{50, 150},  {200, 50},  {350, 150},
                              {350, 300}, {250, 300}, {200, 250},
                              {150, 350}, {100, 250}, {100, 200}};

    
    vec2 clip_polygon[] = {{100, 100}, {300, 100}, {300, 300}, {100, 300}};

    
    std::vector<vec2> clipped_polygon =
        suther_land_hodgman(subject_polygon, clip_polygon);

    
    std::cout << "Clipped polygon points:" << std::endl;
    for (vec2 p : clipped_polygon) {
        std::cout << "(" << p.x << ", " << p.y << ")" << std::endl;
    }

    return EXIT_SUCCESS;
}




Java: import java.awt.*;
import java.awt.geom.Line2D;
import java.util.*;
import java.util.List;
import javax.swing.*;

public class SutherlandHodgman extends JFrame {

    SutherlandHodgmanPanel panel;

    public static void main(String[] args) {
        JFrame f = new SutherlandHodgman();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setVisible(true);
    }

    public SutherlandHodgman() {
        Container content = getContentPane();
        content.setLayout(new BorderLayout());
        panel = new SutherlandHodgmanPanel();
        content.add(panel, BorderLayout.CENTER);
        setTitle("SutherlandHodgman");
        pack();
        setLocationRelativeTo(null);
    }
}

class SutherlandHodgmanPanel extends JPanel {
    List<double[]> subject, clipper, result;

    public SutherlandHodgmanPanel() {
        setPreferredSize(new Dimension(600, 500));

        
        double[][] subjPoints = {{50, 150}, {200, 50}, {350, 150}, {350, 300},
        {250, 300}, {200, 250}, {150, 350}, {100, 250}, {100, 200}};

        double[][] clipPoints = {{100, 100}, {300, 100}, {300, 300}, {100, 300}};

        subject = new ArrayList<>(Arrays.asList(subjPoints));
        result  = new ArrayList<>(subject);
        clipper = new ArrayList<>(Arrays.asList(clipPoints));

        clipPolygon();
    }

    private void clipPolygon() {
        int len = clipper.size();
        for (int i = 0; i < len; i++) {

            int len2 = result.size();
            List<double[]> input = result;
            result = new ArrayList<>(len2);

            double[] A = clipper.get((i + len - 1) % len);
            double[] B = clipper.get(i);

            for (int j = 0; j < len2; j++) {

                double[] P = input.get((j + len2 - 1) % len2);
                double[] Q = input.get(j);

                if (isInside(A, B, Q)) {
                    if (!isInside(A, B, P))
                        result.add(intersection(A, B, P, Q));
                    result.add(Q);
                } else if (isInside(A, B, P))
                    result.add(intersection(A, B, P, Q));
            }
        }
    }

    private boolean isInside(double[] a, double[] b, double[] c) {
        return (a[0] - c[0]) * (b[1] - c[1]) > (a[1] - c[1]) * (b[0] - c[0]);
    }

    private double[] intersection(double[] a, double[] b, double[] p, double[] q) {
        double A1 = b[1] - a[1];
        double B1 = a[0] - b[0];
        double C1 = A1 * a[0] + B1 * a[1];

        double A2 = q[1] - p[1];
        double B2 = p[0] - q[0];
        double C2 = A2 * p[0] + B2 * p[1];

        double det = A1 * B2 - A2 * B1;
        double x = (B2 * C1 - B1 * C2) / det;
        double y = (A1 * C2 - A2 * C1) / det;

        return new double[]{x, y};
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.translate(80, 60);
        g2.setStroke(new BasicStroke(3));
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        drawPolygon(g2, subject, Color.blue);
        drawPolygon(g2, clipper, Color.red);
        drawPolygon(g2, result, Color.green);
    }

    private void drawPolygon(Graphics2D g2, List<double[]> points, Color color) {
        g2.setColor(color);
        int len = points.size();
        Line2D line = new Line2D.Double();
        for (int i = 0; i < len; i++) {
            double[] p1 = points.get(i);
            double[] p2 = points.get((i + 1) % len);
            line.setLine(p1[0], p1[1], p2[0], p2[1]);
            g2.draw(line);
        }
    }
}