Reputation: 5496
I am stuck while trying to implement a World Coordinates to Device Coordinates transformation.
Basically I want to draw in the following World Coordinates:
// --- World Coordinates
//
// (xmax,ymax)
// ┌────────────────┐
// │ │
// │ │
// │ │
// │ │
// └────────────────┘
// (xmin,ymin)
//
// (xmin,ymin) = (0, 100)
// (xmax,ymax) = (1.5, 2.5)
Following this book (page 31), I am trying to implement the affine transformations required to move from a world coordinates viewport into a device coordinates viewport.
// Introduction to Computer Graphics Using Java 2D and 3D
// Frank Klawonn, Ed. Springer 2008
// Page 31
I have prepared a test class which contains two tests, the first one (just set int test = 1
) tests the first two transformation to flip Y axis (origin in the screen in the upper left corner, instead of the bottom left).
That tests works fine, a rectangle and line are draw to show this.
However, while switching to Test 2, which includes all the expected transformations, I get an empty screen as result:
// --- Affine Transform 1 and 2
//
// T(0, h) ◦ S(1,−1)
//
// --- Affine Transform 3, 4 and 5
//
// ╭ umax − umin vmax − vmin ╮
// T(umin, vmin) ◦ S | ----------- , ----------- | ◦ T(-xmin, -ymin)
// ╰ xmax − xmin ymax − ymin ╯
//
The whole source code for the Test class is included:
package com.example.test2;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
public class Test2 extends Frame {
Graphics2D g2d;
Insets insFrame;
Dimension sizeFrame;
public Test2() {
this.setSize(660,540);
this.setUndecorated(false);
this.setVisible(true);
this.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e) {
dispose();
}
});
}
@Override
public void paint(Graphics g) {
g2d = (Graphics2D) g;
insFrame = this.getInsets();
sizeFrame = this.getSize();
//int test = 1; // Change to test 2 to test the whole transformation
int test = 2;
if ( test == 1 ) {
// AT1 & AT2 Test
this.setScale(1);
g2d.setColor(Color.ORANGE);
Line2D.Double line = new Line2D.Double(0, 0, sizeFrame.width-insFrame.left-insFrame.right-1, sizeFrame.height-insFrame.top-insFrame.bottom);
g2d.draw(line);
g2d.setColor(Color.RED);
g2d.drawRect(0, 0, sizeFrame.width-insFrame.left-insFrame.right-1, sizeFrame.height-insFrame.top-insFrame.bottom-1);
} else if (test == 2) {
// AT1, AT2, AT3, AT4 & AT5 Test
this.setScale(2);
g2d.setColor(Color.ORANGE);
Line2D.Double line = new Line2D.Double(0, 1.5, 100, 2.5);
g2d.draw(line);
g2d.setColor(Color.RED);
Rectangle2D.Double rectangle = new Rectangle2D.Double(0, 1.5, 100, 2.5);
g2d.draw(rectangle);
}
};
// Required affine transforms to move from
// World Coordinates Viewport to
// Screen Pixel Coordinates Viewport
//
// --- Reference textbook:
//
// Introduction to Computer Graphics Using Java 2D and 3D
// Frank Klawonn, Ed. Springer 2008
// Page 31
//
// --- Viewports
//
// World Coordinates Viewport (xmin,ymin) - (xmax,ymax)
// Screen Pixel Coordinates Viewport (umin, vmin) - (umax, vmax)
//
// --- World Coordinates
//
// (xmax,ymax)
// ┌────────────────┐
// │ │
// │ │
// │ │
// │ │
// └────────────────┘
// (xmin,ymin)
//
// (xmin,ymin) = (0, 100)
// (xmax,ymax) = (1.5, 2.5)
//
// --- User coordinates
//
// (umax,vmax)
// ┌────────────────┐
// │ │
// │ │
// │ │
// │ │
// └────────────────┘
// (umin,vmin)
//
// (umin,vmin) = (inset.left, heightFrame - inset.bottom)
// (umax,vmax) = (widthFrame - inset.right, inset.top)
//
// --- Affine Transform 1 and 2
//
// T(0, h) ◦ S(1,−1)
//
// --- Affine Transform 3, 4 and 5
//
// ╭ umax − umin vmax − vmin ╮
// T(umin, vmin) ◦ S | ----------- , ----------- | ◦ T(-xmin, -ymin)
// ╰ xmax − xmin ymax − ymin ╯
//
private void setScale(int test) {
// World Coordinates
// (xmin,ymin) = (0, 1.5)
// (xmax,ymax) = (100, 2.5)
Double xmin = 0.0;
Double ymin = 1.5;
Double xmax = 100.0;
Double ymax = 2.5;
// User Coordinates
// (umin,vmin) = (inset.left, heightFrame - inset.bottom)
// (umax,vmax) = (widthFrame - inset.right, inset.top)
int umin = insFrame.left;
int vmin = (int) (sizeFrame.getHeight() - insFrame.bottom);
int umax = (int) (sizeFrame.getWidth() - insFrame.right);
int vmax = insFrame.top;
if (test == 1) {
// Affine Transformation 1 and 2
// T(0, h) ◦ S(1,−1)
AffineTransform at1 = new AffineTransform();
at1.setToScale(1,-1);
AffineTransform at2 = new AffineTransform();
at2.setToTranslation(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
at1.preConcatenate(at2);
g2d.transform(at1);
} else if (test == 2) {
// Affine Transformation 1 and 2
// T(0, h) ◦ S(1,−1)
AffineTransform at1 = new AffineTransform();
at1.setToScale(1,-1);
AffineTransform at2 = new AffineTransform();
at2.setToTranslation(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
// Affine Transformation 3, 4 and 5
// ╭ umax − umin vmax − vmin ╮
// T(umin, vmin) ◦ S | ----------- , ----------- | ◦ T(-xmin, -ymin)
// ╰ xmax − xmin ymax − ymin ╯
AffineTransform at3 = new AffineTransform();
at3.setToTranslation(umin, vmin);
AffineTransform at4 = new AffineTransform();
at4.setToScale(1.0*(umax-umin)/(xmax-xmin), 1.0*(vmax-vmin)/(ymax-ymin));
AffineTransform at5 = new AffineTransform();
at5.setToTranslation(-xmin,-ymin);
at4.preConcatenate(at5);
at3.preConcatenate(at4);
at2.preConcatenate(at3);
at1.preConcatenate(at2);
g2d.transform(at1);
}
}
public static void main( String[] args ) {
Test2 window = new Test2();
}
}
Upvotes: 0
Views: 322
Reputation: 21435
I've looked into the book and I find the usage of AffineTransform.preConcatenate()
confusing. I prefer to use AffineTransform.concatenate()
, since for me this gives a more natural flow.
Let's look at the first example (transform, then swap y direction).
This is your code:
AffineTransform at1 = new AffineTransform();
at1.setToScale(1,-1);
AffineTransform at2 = new AffineTransform();
at2.setToTranslation(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
at1.preConcatenate(at2);
With using concatenate
, you could write it as:
AffineTransform at1 = new AffineTransform();
at1.setToTranslation(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
AffineTransform at2 = new AffineTransform();
at2.setToScale(1,-1);
at1.concatenate(at2);
or, even shorter:
AffineTransform at = new AffineTransform();
at.translate(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
at.scale(1,-1);
You can see the flow "translate, then scale" directly represented in the code.
For the second test you can apply the same approach:
// Affine Transformation 3, 4 and 5
// ╭ umax − umin vmax − vmin ╮
// T(umin, vmin) ◦ S | ----------- , ----------- | ◦ T(-xmin, -ymin)
// ╰ xmax − xmin ymax − ymin ╯
AffineTransform at = new AffineTransform();
at.translate(umin, vmin);
at.scale((umax-umin)/(xmax-xmin), (vmax-vmin)/(ymax-ymin));
at.translate(-xmin, -ymin);
Note this: umin, vmin
already denotes the lower left screen coordinate and umax, vmax
the upper right screen coordinate, so no additional translation or y axis flipping is necessary!
Some closing notes:
ymin
and ymax
, one scaled unit fills the complete height of your window. To not get only a filled rectangle, you should set the line width to the minimum possible value with g2d.setStroke(new BasicStroke(0.0f));
before the g2d.draw()
calls.Rectangle2D.Double()
are x, y, w, h
, so your code to create the rectangle object in the second example should probably read Rectangle2D.Double rectangle = new Rectangle2D.Double(0, 1.5, 100, 1);
(with a height of 2.5 the upper border will not show on screen).Upvotes: 1