Java gives us tools which we can use to do low–level drawing/graphics. We have to
use imagination and ingenuity to figure out how to use these tools to create what we want.
You can create about anything you want this way.
Using AffineTransform to bridge any model into the view can be challenging. How you use it
depends on what you are trying to do and how you are trying to go about it. There are many
possibilities.
The whole stated idea of Java2D transforms is to have the graphics environment
transform points or Shapes between coordinate systems
It does this really well.
instead of writing custom code for each object that transforms them when drawn
Sometimes this is necessary, usually not.
and without generating unwanted intermediate clone garbage.
Sometimes it is useful to save the originals and create what we need from them as we
proceed. It just depends on what you need.
The trick is to set things up so you can write simple, easy–to–read transforms to get the
job done. This can take some experimenting.
Here's a couple of different ways to transform Polygons from a (world) model coordinate
system, as you defined it with y values increasing upward, into the java/view coordinate
system in which y values increase downward. I chose Polygons with odd numbers of sides
because they are more difficult: their center (origin of radius R to vertices) does not
coincide with the center of their bounding rectangle. You can use the Shape bounds for
everything that has a bounding box, eg, arcs, ellipses, rectangles and GeneralPath and get
good results, fairly easily.
The blue polygons are the originals (naively) drawn with their model coordinates. The red
and green polygons are the polygons transformed into the java/view coordinate system.
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
public class WorldTransform extends JPanel {
Polygon[] polygons;
boolean methodOne = true;
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
if(polygons == null) initPolygons();
for(int j = 0; j < polygons.length; j++) {
g2.setPaint(Color.blue);
g2.draw(polygons[j]);
if(methodOne) {
g2.setPaint(Color.red);
g2.draw(modelToView1(polygons[j]));
} else {
g2.setPaint(Color.green.darker());
g2.draw(modelToView2(polygons[j]));
}
}
}
/** Tranlate reflected center point of Polygon. */
private Shape modelToView1(Polygon p) {
Rectangle r = p.getBounds();
double cy = r.getCenterY() + getVerticalOffset(r.height, p.xpoints.length);
Point2D src = new Point2D.Double(r.getCenterX(), cy);
Point2D dest = new Point2D.Double();
// Move to polygon center from bottom of view [y = getHeight()].
AffineTransform at = AffineTransform.getTranslateInstance(-r.getCenterX(),
getHeight()-cy);
// Flip polygon about its center.
at.scale(1, -1);
// Transform the center point of the polygon.
at.transform(src, dest);
//System.out.printf("src=[%5.1f, %5.1f] dest=[%.1f, %6.1f]%n",
//src.getX(), src.getY(), dest.getX(), dest.getY());
// Use this transformed center point to translate the polygon
// to its location in the java/view coordinate system.
at.setToTranslation(dest.getX(), dest.getY());
return at.createTransformedShape(p);
}
/**
* Offset between center of bounds and center of polygon
* for polygons which have an odd number of sides. The offset
* for polygons which have an even number of sides is zero.
*/
private double getVerticalOffset(int height, int sides) {
// R + r = height
// r = R * cos(PI/sides)
double R = height/(1.0 + Math.cos(Math.PI/sides));
double r = R * Math.cos(Math.PI/sides);
return (R - r)/2;
}
/** Reflect polygon about abcissa and flip it about its center. */
private Shape modelToView2(Polygon p) {
// Translate to model abcissa [where y = 0], the bottom of the view.
AffineTransform at = AffineTransform.getTranslateInstance(0, getHeight());
// Reflect about model abcissa, polygon will be inverted.
at.scale(1, -1);
// Move to polygon center and flip it about its center:
// (we are located at the bottom of the view (the JPanel), looking up)
Rectangle r = p.getBounds();
double cy = r.getCenterY() + getVerticalOffset(r.height, p.xpoints.length);
// move to polygon center;
at.translate(0, cy);
// flip polygon about its center;
at.scale(1, -1);
// and move back to abcissa.
at.translate(0, -cy);
// We are now showing polygon in java/view space.
return at.createTransformedShape(p);
}
private void initPolygons() {
polygons = new Polygon[3];
int w = getWidth();
int h = getHeight();
int R = Math.min(w, h)/8;
int[][] xy = generateShapeArrays(w/4, h*3/4, R, 3);
polygons[0] = new Polygon(xy[0], xy[1], 3);
xy = generateShapeArrays(w/2, h/2, R, 5);
polygons[1] = new Polygon(xy[0], xy[1], 5);
xy = generateShapeArrays(w*3/4, h/4, R, 7);
polygons[2] = new Polygon(xy[0], xy[1], 7);
}
private int[][] generateShapeArrays(int cx, int cy, int R, int sides) {
int radInc = 0;
if(sides % 2 == 0)
radInc = 1;
int[] x = new int[sides];
int[] y = new int[sides];
for(int i = 0; i < sides; i++) {
x[i] = cx + (int)(R * Math.sin(radInc*Math.PI/sides));
y[i] = cy - (int)(R * Math.cos(radInc*Math.PI/sides));
radInc += 2;
}
// keep base of triangle level
if(sides == 3)
y[2] = y[1];
return new int[][] { x, y };
}
private JPanel getUIPanel() {
String[] ids = { "method 1", "method 2" };
ButtonGroup group = new ButtonGroup();
ActionListener l = new ActionListener() {
public void actionPerformed(ActionEvent e) {
String ac = e.getActionCommand();
methodOne = ac.equals("method 1");
repaint();
}
};
JPanel panel = new JPanel();
for(int j = 0; j < ids.length; j++) {
JRadioButton rb = new JRadioButton(ids[j], j==0);
rb.setActionCommand(ids[j]);
group.add(rb);
rb.addActionListener(l);
panel.add(rb);
}
return panel;
}
public static void main(String[] args) {
WorldTransform test = new WorldTransform();
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(test);
f.getContentPane().add(test.getUIPanel(), "Last");
f.setSize(400,400);
f.setLocation(200,200);
f.setVisible(true);
}
}
It always takes some experimenting until I can see what I'm doing with the transforms and
then it gets easier. Sometimes I'm unable to find a way to do the transform(s) and have to
give up.