Drawing onto a Java2D Path using several Transforms

405 Views Asked by At

I have a fairly complex list of short line segments that taken together form a line.

I'd like to take this line and draw it 4 times along the 4 edges of a polygon. Obviously, for each side of the polygon I'll need to rotate the line by 90 degrees and translate it to it's final position.

After drawing this polygon with irregular sides I'd like to fill it.

I have the folowing code:

  private void drawSide(GeneralPath path, int startX, int endX, int y)
    {
    path.moveTo(startX, y);
    
    // in reality this is very complex, but for now, just draw a line
    path.lineTo(endX, y);
    }
  
  private AffineTransform getTransform(int deltaX, int deltaY, int angle)
    {
    AffineTransform rat = new AffineTransform();
    rat.translate(deltaX, deltaY);
    rat.rotate(Math.toRadians(angle));
    return rat;
    }
  
  private void test(Graphics2D g2d)
    {
    GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);

    drawSide(path, 100, 200, 100);

    path.transform(getTransform(100, 0, 90));  
    drawSide(path, 100, 200, 100);
    
    path.transform(getTransform(100, 100, 180));  
    drawSide(path, 100, 200, 100);
    
    path.transform(getTransform(0, 10, 270));  
    drawSide(path, 100, 200, 100);
    
    path.closePath();
    g2d.fill(path); 
    g2d.draw(path);
    }

I don't really understand what's wrong here. Can anyone help?

1

There are 1 best solutions below

3
MadProgrammer On

The first thing I want to point out is, I'm an idiot. So the first thing I try to do is find a workflow which my cognitive facilities can grasp.

What does this mean? There's probably a better solution, but I can't see it right now.

I did try doing a "local" context workflow, which would simply apply 90 degrees of rotation at each step, but that ended in a mess, so, I stuck with a "world context" workflow

This just means that the rotation/translations are based on the "world context" as apposed to the local context of the last change, yea for me

So, the basic idea is to start with a basic line Path, then create a new Path for each segment, appending the "base" Path to and applying appropriate transformations, for example

protected Path2D worldContext() {
    // Our "base line" line
    Path2D baseLine = new Path2D.Double();
    baseLine.moveTo(0, 0);
    baseLine.lineTo(100, 0);

    // Top line
    Path2D line1 = new Path2D.Double();
    line1.append(baseLine, true);

    Path2D line2 = new Path2D.Double();
    line2.append(baseLine, true);
    line2.transform(AffineTransform.getTranslateInstance(100, 0));
    line2.transform(AffineTransform.getRotateInstance(Math.toRadians(90), 100, 0));

    Path2D line3 = new Path2D.Double();
    line3.append(baseLine, true);
    line3.transform(AffineTransform.getTranslateInstance(100, 100));
    line3.transform(AffineTransform.getRotateInstance(Math.toRadians(180), 100, 100));

    Path2D line4 = new Path2D.Double();
    line4.append(baseLine, true);
    line4.transform(AffineTransform.getTranslateInstance(0, 100));
    line4.transform(AffineTransform.getRotateInstance(Math.toRadians(270), 0, 100));

    Path2D finalPath = new Path2D.Double();
    finalPath.append(line1, true);
    finalPath.append(line2, true);
    finalPath.append(line3, true);
    finalPath.append(line4, true);

    finalPath.closePath();

    return finalPath;
}

enter image description here

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Main {

    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setColor(Color.RED);
            Path2D path = worldContext();
            int x = (getWidth() - path.getBounds().width) / 2;
            int y = (getHeight()- path.getBounds().height) / 2;
            g2d.translate(x, y);
            g2d.draw(path);
            g2d.dispose();
        }

        protected Path2D worldContext() {
            // Our "base line" line
            Path2D baseLine = new Path2D.Double();
            baseLine.moveTo(0, 0);
            baseLine.lineTo(100, 0);

            // Top line
            Path2D line1 = new Path2D.Double();
            line1.append(baseLine, true);

            Path2D line2 = new Path2D.Double();
            line2.append(baseLine, true);
            line2.transform(AffineTransform.getTranslateInstance(100, 0));
            line2.transform(AffineTransform.getRotateInstance(Math.toRadians(90), 100, 0));

            Path2D line3 = new Path2D.Double();
            line3.append(baseLine, true);
            line3.transform(AffineTransform.getTranslateInstance(100, 100));
            line3.transform(AffineTransform.getRotateInstance(Math.toRadians(180), 100, 100));

            Path2D line4 = new Path2D.Double();
            line4.append(baseLine, true);
            line4.transform(AffineTransform.getTranslateInstance(0, 100));
            line4.transform(AffineTransform.getRotateInstance(Math.toRadians(270), 0, 100));

            Path2D finalPath = new Path2D.Double();
            finalPath.append(line1, true);
            finalPath.append(line2, true);
            finalPath.append(line3, true);
            finalPath.append(line4, true);

            finalPath.closePath();

            return finalPath;
        }

    }
}

A "possible" local context...

This basically applies a transformation to an existing Path on each step. Because it's applying it to an existing Path, you need to keep in mind that you're rotation point is actually the start point of the line, which is something that just messes with my head

protected Path2D localContext() {
    // Our "base line" line
    Path2D baseLine = new Path2D.Double();
    baseLine.moveTo(0, 0);
    baseLine.lineTo(100, 0);

    Path2D next = modify(baseLine, 0, 0, 0);

    // Top line
    Path2D path = new Path2D.Double();
    path.append(next, false);

    next = modify(next, 100, 0, 90);
    path.append(next, false);

    next = modify(next, 100, 0, 90);
    path.append(next, false);

    next = modify(next, 100, 0, 90);
    path.append(next, false);

    return path;
}

protected Path2D modify(Path2D path, double x, double y, double angle) {
    AffineTransform at = AffineTransform.getTranslateInstance(x, y);
    at.rotate(Math.toRadians(angle), 0, 0);
    Shape shape = path.createTransformedShape(at);
    Path2D newPath = new Path2D.Double(shape);
    return newPath;
}