Example Picture: 1
I try to draw a driving path. For this I draw one line behind the other and rotate the lines depending on the actual rotation by using affine transformation.
Everything works fine and I can draw the path but the path is not displayed very nice. There are a lot of white gaps. I think this is caused by the rotation of the lines.
How can I manage it to have a path without these white gaps?
public class Stage extends JPanel
{
/**
*
*/
private static final long serialVersionUID = 1L;
int pW = 2000;
int pH = 2000;
double _x = 400,_y=300;
double centerX, centerY;
double x,y,angle, angleLine, xpic,ypic;
double w = 100;
double x1,y1,x2,y2,x3,y3;
double xCnt = 0;
BufferedImage bi;
private boolean gf_bgSet;
Point2D p1 = new Point2D.Double(0,0);
Point2D p2 = new Point2D.Double(0,0);;
Point2D lastRotP = new Point2D.Double(0,0);
double degrees;
boolean gf_up = false;
boolean gf_down = false;
private double angleOld;
private double ArcAngleOld;
public Stage()
{
setLayout(null);
setBounds(0,0,pW,pH);
setBackground(Color.WHITE);
centerX = _x;
centerY = _y;
x = centerX;
y = centerY;
xpic = centerX;
ypic = centerY;
bi = new BufferedImage(pW, pH, BufferedImage.TYPE_INT_ARGB);
}
public void moveLine(KeyEvent e)
{
switch(e.getKeyCode())
{
case KeyEvent.VK_PLUS:
angle--;
angleLine--;
degrees++;
if (degrees == -360 || degrees == 360)
{
degrees = 0;
}
repaint();
break;
case KeyEvent.VK_MINUS:
angle++;
angleLine++;
degrees--;
if (degrees == -360 || degrees == 360)
{
degrees = 0;
}
repaint();
break;
case KeyEvent.VK_LEFT:
break;
case KeyEvent.VK_RIGHT:
break;
case KeyEvent.VK_UP:
gf_down = false;
gf_up = true;
xpic++;
repaint();
break;
case KeyEvent.VK_DOWN:
ypic--;
gf_down = true;
gf_up = false;
repaint();
break;
case KeyEvent.VK_Z:
repaint();
break;
}
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
double _w = 100;
//get Graphic2D objects
Graphics2D g2d = (Graphics2D) bi.createGraphics();
Graphics2D g2dMain = (Graphics2D) g;
//set Background and first blue line
if (gf_bgSet==false)
{
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, pW, pH);
g2d.setColor(Color.BLUE);
Line2D line = new Line2D.Double(_x, _y, _x+_w/2,_y);
g2d.draw(line);
gf_bgSet=true;
}
else
{
double _angle = 0;
_angle = Math.toRadians(degrees);
//Set color red transparent
g2d.setColor(Color.BLUE);
//Draw line
Line2D line = null;
line = new Line2D.Double(_x, _y, _x+(_w/2), _y);
if(gf_up==true)
{
_x=_x+(Math.sin(_angle));
_y=_y-(Math.cos(_angle));
}
else
{
_x=_x-(Math.sin(_angle));
_y=_y+(Math.cos(_angle));
}
angleOld = _angle;
double xDiff = centerX-_x;
double yDiff = centerY-_y;
AffineTransform old = g2d.getTransform();
AffineTransform atLine = AffineTransform.getRotateInstance(_angle,_x+w/2,_y);
g2d.draw(atLine.createTransformedShape(line));
g2d.setTransform(old);
//Rotate picture
AffineTransform oldtrans = g2dMain.getTransform();
AffineTransform trans = AffineTransform.getRotateInstance(-_angle, centerX-xDiff, centerY-yDiff);
g2dMain.translate(xDiff, yDiff);
g2dMain.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2dMain.drawImage(bi, trans , null);
g2dMain.setTransform(oldtrans);
}
}
}
Instead of drawing the lines one by one, try using a GeneralPath and set the BasicStroke to the size of the lines you are using at the moment. With every new point you just update your path.
Here is a good example:
https://kodejava.org/how-do-i-draw-a-generalpath-in-java-2d/
Related
Trying to draw a metro graph using JUNG.
Is it possible to draw an edge of straight line that uses 2 or more colors in parallel, using the transformer?
You could do something like this....
Copy and modify some code from the EdgeShape class (where it makes the Bowtie shape) modified to a rectangle, then set the edge fill paint, draw paint, and stroke to look like what you want
public class RectangleEdge<V,E> extends AbstractEdgeShapeTransformer<V,E> {
private static GeneralPath rectangle;
public RectangleEdge(int width) {
rectangle = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
rectangle.moveTo(0, width/2); // change points to make rectangle
rectangle.lineTo(1, width/2);
rectangle.lineTo(1, -width/2);
rectangle.lineTo(0, -width/2);
rectangle.closePath();
}
public Shape transform(Context<Graph<V,E>,E> context) {
return rectangle;
}
}
vv.getRenderContext().setEdgeShapeTransformer(new RectangleEdge<>(4));
vv.getRenderContext().setEdgeFillPaintTransformer(new ConstantTransformer(Color.red));
vv.getRenderContext().setEdgeStrokeTransformer(new ConstantTransformer(new BasicStroke(2)));
vv.getRenderContext().setEdgeDrawPaintTransformer(new ConstantTransformer(Color.blue));
Here's a picture:
ok, if you want a lot of colors, try this:
vv.getRenderer().setEdgeRenderer(
new NotSimpleEdgeRenderer(new Color[]{
Color.red, Color.blue, Color.pink, Color.green, Color.magenta,
Color.cyan, Color.black, Color.orange, Color.yellow
}));
vv.getRenderContext().setEdgeStrokeTransformer(new ConstantTransformer(new BasicStroke(2)));
public class NotSimpleEdgeRenderer<V,E> extends BasicEdgeRenderer<V,E> {
Color[] colors;
Shape[] shapes;
public NotSimpleEdgeRenderer(Color... colors) {
int count = colors.length;
this.colors = colors;
shapes = new Shape[count];
for (int i=0; i<count; i++) {
shapes[i] = new Line2D.Double(0, -count/2+(2*i), 1, -count/2+(2*i));
}
}
protected void drawSimpleEdge(RenderContext<V,E> rc, Layout<V,E> layout, E e) {
GraphicsDecorator g = rc.getGraphicsContext();
Graph<V,E> graph = layout.getGraph();
Pair<V> endpoints = graph.getEndpoints(e);
V v1 = endpoints.getFirst();
V v2 = endpoints.getSecond();
Point2D p1 = layout.transform(v1);
Point2D p2 = layout.transform(v2);
p1 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p1);
p2 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p2);
float x1 = (float) p1.getX();
float y1 = (float) p1.getY();
float x2 = (float) p2.getX();
float y2 = (float) p2.getY();
boolean edgeHit;
Rectangle deviceRectangle = null;
JComponent vv = rc.getScreenDevice();
if(vv != null) {
Dimension d = vv.getSize();
deviceRectangle = new Rectangle(0,0,d.width,d.height);
}
AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1);
float dx = x2-x1;
float dy = y2-y1;
float thetaRadians = (float) Math.atan2(dy, dx);
xform.rotate(thetaRadians);
float dist = (float) Math.sqrt(dx*dx + dy*dy);
xform.scale(dist, 1.0);
Paint oldPaint = g.getPaint();
for (int i=0; i<shapes.length; i++) {
Shape edgeShape = shapes[i];
edgeShape = xform.createTransformedShape(edgeShape);
MutableTransformer vt = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW);
edgeHit = vt.transform(edgeShape).intersects(deviceRectangle);
if(edgeHit == true) {
Paint fill_paint = colors[i];
if (fill_paint != null) {
g.setPaint(fill_paint);
g.fill(edgeShape);
}
Paint draw_paint = colors[i];
if (draw_paint != null) {
g.setPaint(draw_paint);
g.draw(edgeShape);
}
}
// restore old paint
g.setPaint(oldPaint);
}
}
}
and it looks like this:
I think you can do this with small modifications to the code samples I posted in previous answers.
I assume that you know what colors you want for each edge. Make a Map that has the mappings from each edge to the colors you want for that edge. Then modify the code I answered with to look like this:
public class NotSimpleEdgeRenderer<V,E> extends BasicEdgeRenderer<V,E> {
Map<E,Color[]> colorMap;
public NotSimpleEdgeRenderer(Map<E,Color[]> colorMap) {
this.colorMap = colorMap;
}
protected void drawSimpleEdge(RenderContext<V,E> rc, Layout<V,E> layout, E e) {
GraphicsDecorator g = rc.getGraphicsContext();
Graph<V,E> graph = layout.getGraph();
Pair<V> endpoints = graph.getEndpoints(e);
V v1 = endpoints.getFirst();
V v2 = endpoints.getSecond();
Point2D p1 = layout.transform(v1);
Point2D p2 = layout.transform(v2);
p1 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p1);
p2 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p2);
float x1 = (float) p1.getX();
float y1 = (float) p1.getY();
float x2 = (float) p2.getX();
float y2 = (float) p2.getY();
boolean edgeHit;
Rectangle deviceRectangle = null;
JComponent vv = rc.getScreenDevice();
if(vv != null) {
Dimension d = vv.getSize();
deviceRectangle = new Rectangle(0,0,d.width,d.height);
}
AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1);
float dx = x2-x1;
float dy = y2-y1;
float thetaRadians = (float) Math.atan2(dy, dx);
xform.rotate(thetaRadians);
float dist = (float) Math.sqrt(dx*dx + dy*dy);
xform.scale(dist, 1.0);
Paint oldPaint = g.getPaint();
// get the colors for this edge from the map
Color[] colors = colorMap.get(e);
int count = colors.length;
// make the Shapes for this edge here
Shape[] shapes = new Shape[count];
for (int i=0; i<count; i++) {
// this code offsets the lines enough to see the colors
shapes[i] = new Line2D.Double(0, -count/2+(2*i), 1, -count/2+(2*i));
}
// iterate over the edge shapes and draw them with the corresponding colors
for (int i=0; i<shapes.length; i++) {
Shape edgeShape = shapes[i];
edgeShape = xform.createTransformedShape(edgeShape);
MutableTransformer vt = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW);
edgeHit = vt.transform(edgeShape).intersects(deviceRectangle);
if(edgeHit == true) {
Paint fill_paint = colors[i];
if (fill_paint != null) {
g.setPaint(fill_paint);
g.fill(edgeShape);
}
Paint draw_paint = colors[i];
if (draw_paint != null) {
g.setPaint(draw_paint);
g.draw(edgeShape);
}
}
// restore old paint
g.setPaint(oldPaint);
}
}
}
I want to do a "zoomable paint", I mean a paint that I can zoom/zoom out and pan/drag the canvas and then draw on it.
I have a problem that I can't solve: when I draw while the canvas is zoomed, I retrieve the X and Y coordinate and effectively drawing it on the canvas. But these coordinates are not correct because of the zoomed canvas.
I tried to correct these (multiply by (zoomHeigh/screenHeight)) but I can't find a way to retrieve where I must draw on the original/none-zoomed screen
This is my code :
public class PaintView extends View {
public static int BRUSH_SIZE = 20;
public static final int DEFAULT_COLOR = Color.BLACK;
public static final int DEFAULT_BG_COLOR = Color.WHITE;
private static final float TOUCH_TOLERANCE = 4;
private float mX, mY;
private SerializablePath mPath;
private Paint mPaint;
private ArrayList<FingerPath> paths = new ArrayList<>();
private ArrayList<FingerPath> tempPaths = new ArrayList<>();
private int currentColor;
private int backgroundColor = DEFAULT_BG_COLOR;
private int strokeWidth;
private boolean emboss;
private boolean blur;
private boolean eraser;
private MaskFilter mEmboss;
private MaskFilter mBlur;
private Bitmap mBitmap;
private Canvas mCanvas;
private Paint mBitmapPaint = new Paint(Paint.DITHER_FLAG);
public boolean zoomViewActivated = false;
//These two constants specify the minimum and maximum zoom
private static float MIN_ZOOM = 1f;
private static float MAX_ZOOM = 5f;
private float scaleFactor = 1.f;
private ScaleGestureDetector detector;
//These constants specify the mode that we're in
private static int NONE = 0;
private static int DRAG = 1;
private static int ZOOM = 2;
private int mode;
//These two variables keep track of the X and Y coordinate of the finger when it first
//touches the screen
private float startX = 0f;
private float startY = 0f;
//These two variables keep track of the amount we need to translate the canvas along the X
//and the Y coordinate
private float translateX = 0f;
private float translateY = 0f;
//These two variables keep track of the amount we translated the X and Y coordinates, the last time we
//panned.
private float previousTranslateX = 0f;
private float previousTranslateY = 0f;
int currentPositionX = 0;
int currentPositionY = 0;
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
scaleFactor *= detector.getScaleFactor();
scaleFactor = Math.max(MIN_ZOOM, Math.min(scaleFactor, MAX_ZOOM));
return true;
}
}
public ArrayList<FingerPath> getDividedPaths(float i){
for(FingerPath p : this.paths){
Matrix scaleMatrix = new Matrix();
scaleMatrix.setScale(i, i);
p.path.transform(scaleMatrix);
}
return this.paths;
}
public ArrayList<FingerPath> getPaths() {
return paths;
}
public void dividePath(float i) {
for(FingerPath p : this.paths){
Matrix scaleMatrix = new Matrix();
scaleMatrix.setScale(i, i);
p.path.transform(scaleMatrix);
}
}
public void setPaths(ArrayList<FingerPath> paths){
this.paths = paths;
}
public void setStrokeWidth(int value){
strokeWidth = value;
}
public PaintView(Context context) {
this(context, null);
detector = new ScaleGestureDetector(getContext(), new ScaleListener());
}
public PaintView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(DEFAULT_COLOR);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setXfermode(null);
mPaint.setAlpha(0xff);
mEmboss = new EmbossMaskFilter(new float[] {1, 1, 1}, 0.4f, 6, 3.5f);
mBlur = new BlurMaskFilter(5, BlurMaskFilter.Blur.NORMAL);
detector = new ScaleGestureDetector(getContext(), new ScaleListener());
}
public void init(DisplayMetrics metrics) {
int height = metrics.heightPixels;
int width = metrics.widthPixels;
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
currentColor = DEFAULT_COLOR;
strokeWidth = BRUSH_SIZE;
}
public void normal() {
emboss = false;
blur = false;
eraser = false;
}
public void emboss() {
emboss = true;
blur = false;
eraser = false;
}
public void blur() {
emboss = false;
blur = true;
eraser = false;
}
public void eraser() {
eraser = true;
}
public void cancel(){
if(paths.size() != 0){
tempPaths.add(paths.get(paths.size()-1));
paths.remove(paths.size()-1);
invalidate();
}
}
public void redo(){
if(tempPaths.size() != 0){
paths.add(tempPaths.get(tempPaths.size()-1));
tempPaths.remove(tempPaths.size()-1);
invalidate();
}
}
public void clear() {
backgroundColor = DEFAULT_BG_COLOR;
paths.clear();
normal();
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
//We're going to scale the X and Y coordinates by the same amount
canvas.scale(scaleFactor, scaleFactor);
//If translateX times -1 is lesser than zero, let's set it to zero. This takes care of the left bound
if((translateX * -1) < 0) {
translateX = 0;
}
//This is where we take care of the right bound. We compare translateX times -1 to (scaleFactor - 1) * displayWidth.
//If translateX is greater than that value, then we know that we've gone over the bound. So we set the value of
//translateX to (1 - scaleFactor) times the display width. Notice that the terms are interchanged; it's the same
//as doing -1 * (scaleFactor - 1) * displayWidth
else if((translateX * -1) > (scaleFactor - 1) * getWidth()) {
translateX = (1 - scaleFactor) * getWidth();
}
if(translateY * -1 < 0) {
translateY = 0;
}
//We do the exact same thing for the bottom bound, except in this case we use the height of the display
else if((translateY * -1) > (scaleFactor - 1) * getHeight()) {
translateY = (1 - scaleFactor) * getHeight();
}
//We need to divide by the scale factor here, otherwise we end up with excessive panning based on our zoom level
//because the translation amount also gets scaled according to how much we've zoomed into the canvas.
canvas.translate(translateX / scaleFactor, translateY / scaleFactor);
/* The rest of your canvas-drawing code */
mCanvas.drawColor(backgroundColor);
if(paths != null){
for (FingerPath fp : paths) {
mPaint.setColor(fp.color);
mPaint.setStrokeWidth(fp.strokeWidth);
mPaint.setMaskFilter(null);
if (fp.emboss)
mPaint.setMaskFilter(mEmboss);
else if (fp.blur)
mPaint.setMaskFilter(mBlur);
if(fp.eraser) {
mPaint.setColor(DEFAULT_BG_COLOR);
}
mCanvas.drawPath(fp.path, mPaint);
}
}
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.restore();
}
private void touchStart(float x, float y) {
mPath = new SerializablePath();
FingerPath fp = new FingerPath(currentColor, emboss, blur, eraser, strokeWidth, mPath);
paths.add(fp);
tempPaths = new ArrayList<>();
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touchMove(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
private void touchUp() {
mPath.lineTo(mX, mY);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(zoomViewActivated){
boolean dragged = false;
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mode = DRAG;
//We assign the current X and Y coordinate of the finger to startX and startY minus the previously translated
//amount for each coordinates This works even when we are translating the first time because the initial
//values for these two variables is zero.
startX = event.getX() - previousTranslateX;
startY = event.getY() - previousTranslateY;
break;
case MotionEvent.ACTION_MOVE:
translateX = event.getX() - startX;
translateY = event.getY() - startY;
//We cannot use startX and startY directly because we have adjusted their values using the previous translation values.
//This is why we need to add those values to startX and startY so that we can get the actual coordinates of the finger.
double distance = Math.sqrt(Math.pow(event.getX() - (startX + previousTranslateX), 2) +
Math.pow(event.getY() - (startY + previousTranslateY), 2)
);
if(distance > 0) {
dragged = true;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = ZOOM;
break;
case MotionEvent.ACTION_UP:
mode = NONE;
dragged = false;
//All fingers went up, so let's save the value of translateX and translateY into previousTranslateX and
//previousTranslate
previousTranslateX = translateX;
previousTranslateY = translateY;
currentPositionX += previousTranslateX;
currentPositionY += previousTranslateY;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = DRAG;
//This is not strictly necessary; we save the value of translateX and translateY into previousTranslateX
//and previousTranslateY when the second finger goes up
previousTranslateX = translateX;
previousTranslateY = translateY;
break;
}
detector.onTouchEvent(event);
//We redraw the canvas only in the following cases:
//
// o The mode is ZOOM
// OR
// o The mode is DRAG and the scale factor is not equal to 1 (meaning we have zoomed) and dragged is
// set to true (meaning the finger has actually moved)
if ((mode == DRAG && scaleFactor != 1f && dragged) || mode == ZOOM) {
invalidate();
}
}else{
float x = event.getX()*(getHeight()/scaleFactor)/getHeight()+currentPositionX;
float y = event.getY()*(getWidth()/scaleFactor)/getWidth()+currentPositionY;
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN :
touchStart(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE :
touchMove(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP :
touchUp();
invalidate();
break;
}
}
return true;
}
}
As a project, I'm attempting to create an emulation of the game Asteroids. Currently, I'm trying to make it so then the spaceship appears on the opposite side of the GUI if the player ever sends himself out of bounds.
However, I've become quite confused between how AffineTransformation's rotate and createTransformedShape function interacts with the x and y points of the polygon.
Currently, I have the spaceship working; it flies in the angle the user specifies it and all the movement involved works fine. However, when I tried to get the lowest X coordinate of the spaceship to compare if it ever goes bigger than the constant WIDTH, it returns 780. This happens all the time whether or not I am on one side of the map to the other, it always returns 780. This I find really strange because shouldn't it return the smallest X coordinate of where it currently is?
Here is a screenshot of the console displaying that the "smallest x coordinate" of the polygon is 780, despite not being at 780
Can someone explain to me why the X coordinates of the polygon are not changing? I have 2 classes. One, which is the driver classes, and the other which is the ship class which extends Polygon.
public class AsteroidGame implements ActionListener, KeyListener{
public static AsteroidGame game;
public Renderer renderer;
public boolean keyDown = false;
public int playerAngle = 0;
public boolean left = false;
public boolean right = false;
public boolean go = false;
public boolean back = false;
public boolean still = true;
public double angle = 0;
public int turnRight = 5;
public int turnLeft = -5;
public Shape transformed;
public Shape transformedLine;
public Point p1;
public Point p2;
public Point center;
public Point p4;
public final int WIDTH = 1600;
public final int HEIGHT = 800;
public Ship ship;
public AffineTransform transform = new AffineTransform();
public AsteroidGame(){
JFrame jframe = new JFrame();
Timer timer = new Timer(20, this);
renderer = new Renderer();
jframe.add(renderer);
jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jframe.setSize(WIDTH, HEIGHT);
jframe.setVisible(true);
jframe.addKeyListener(this);
jframe.setResizable(false);
int xPoints[] = {800, 780, 800, 820};
int yPoints[] = {400, 460, 440, 460};
p1 = new Point(400,400);
p2 = new Point(380, 460);
center = new Point(400,440);//center
p4 = new Point(420, 460);
ship = new Ship(xPoints, yPoints, 4, 0);
transformed = transform.createTransformedShape(ship);
timer.start();
}
public void repaint(Graphics g){
g.setColor(Color.BLACK);
g.fillRect(0, 0, WIDTH, HEIGHT);
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(Color.WHITE);
g2d.draw(transformed);
/*
g2d.draw(r2);
Path2D.Double path = new Path2D.Double();
path.append(r, false);
AffineTransform t = new AffineTransform();
t.rotate(Math.toRadians(45));
path.transform(t);
g2d.draw(path);
Rectangle test = new Rectangle(WIDTH/2, HEIGHT/2, 200, 100);
Rectangle test2 = new Rectangle(WIDTH/2, HEIGHT/2, 200, 100);
g2d.draw(test2);
AffineTransform at = AffineTransform.getTranslateInstance(100, 100);
g2d.rotate(Math.toRadians(45));
g2d.draw(test);
*/
}
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
if (right){
ship.right();
transform.rotate(Math.toRadians(turnRight), ship.getCenterX(), ship.getCenterY());
System.out.println(ship.getCenterY());
}
else if (left){
ship.left();
transform.rotate(Math.toRadians(turnLeft), ship.getCenterX(), ship.getCenterY());
}
if (go){
ship.go();
//ship.x += Math.sin(Math.toRadians(angle)) * 5;
//ship.y
/*
ship.x += (int) Math.sin(Math.toRadians(angle));
ship.y += (int) Math.cos(Math.toRadians(angle));
*/
//System.out.println(Math.sin(Math.toRadians(ship.angle)) * 5 + "y" + Math.cos(Math.toRadians(ship.angle)) * 5);
}
else if (back){
ship.reverse();
}
ship.move();
//ship.decrement();
transformed = transform.createTransformedShape(ship);
if (ship.smallestX() >= WIDTH){
System.out.println("out");
}
renderer.repaint();
System.out.println("Smallest x coordinate: " + ship.smallestX());
}
public static void main(String[] args){
game = new AsteroidGame();
}
#Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if (e.getKeyCode() == KeyEvent.VK_RIGHT){
System.out.println("I am down");
right = true;
keyDown = true;
}else if (e.getKeyCode() == KeyEvent.VK_LEFT){
left = true;
System.out.println("I am down");
keyDown = true;
}
if (e.getKeyCode() == KeyEvent.VK_UP){
go = true;
}
else if (e.getKeyCode() == KeyEvent.VK_DOWN){
back = true;
}
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
if (e.getKeyCode() == KeyEvent.VK_RIGHT){
right = false;
}
if (e.getKeyCode() == KeyEvent.VK_LEFT){
left = false;
}
if (e.getKeyCode() == KeyEvent.VK_UP){
go = false;
}
if (e.getKeyCode() == KeyEvent.VK_DOWN){
back = false;
}
still = true;
keyDown = false;
System.out.println("up");
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
Ship Class:
public class Ship extends Polygon{
/**
*
*/
private double currSpeed = 0;
private static final long serialVersionUID = 1L;
public double angle;
public Ship(int[] x, int[] y, int points, double angle){
super(x, y, points);
this.angle= angle;
}
public void right(){
angle += 5;
}
public void left(){
angle -= 5;
}
public void move(){
for (int i = 0; i < super.ypoints.length; i++){
super.ypoints[i] -= currSpeed;
//System.out.println(super.ypoints[i]);
//System.out.println(super.xpoints[i]);
}
}
public void reverse(){
if (currSpeed > -15) currSpeed -= 0.2;
}
public void go(){
if (currSpeed < 25) currSpeed += 0.5;
}
public int smallestX(){
int min = super.xpoints[0];
for (int i = 0; i < super.xpoints.length; i++){
if (min > super.xpoints[i]){
min = super.xpoints[i];
}
}
return min;
}
public int smallestY(){
int min = super.ypoints[0];
for (int i = 0; i < super.ypoints.length; i++){
if (min < super.ypoints[i]){
min = super.ypoints[i];
}
}
return min;
}
public int getCenterX(){
return super.xpoints[2];
}
public int getCenterY(){
return super.ypoints[2];
}
public double getAng(){
return angle;
}
Why it doesn't work
The affine transformation doesn't actually affect the ship instance, instead it created an entirely new instance, this is why your method doesn't seem to work. To prove this, i have the following code:
int[] xPoints = {800, 780, 800, 820};
int[] yPoints = {400, 460, 440, 460};
Ship ship = new Ship(xPoints, yPoints, 4, 0);
System.out.println("old points:");
System.out.println(Arrays.toString(ship.xpoints));
System.out.println(Arrays.toString(ship.ypoints));
AffineTransform transform = new AffineTransform();
transform.translate(20, 20);
Shape transformed = transform.createTransformedShape(ship);
System.out.println("new points (unchanged):");
System.out.println(Arrays.toString(ship.xpoints));
System.out.println(Arrays.toString(ship.ypoints));
Both times, the points are the same.
Solution
What i suggest you do is instead of thinking of the point as actual points on the screen, think of them as a model, that will also make the physics part easier. We'll center the points around (0,0). And when we need to render it, tranform it to the correct position and rotation.
The points would then be something like this:
int[] xPoints = {0, -20, 0, 20};
int[] yPoints = {-40, 20, 0, 20};
So now, you don't need to edit all the points when you move the ship, only the rotation and the center. Note that you should translate first, and then rotate.
The fact that these point are around (0,0) is important, because this is the center of your rotation. This is probably why you seem to be able to move at the moment, your rotation is not centered around the center of the ship. And when you rotate and move up, you also move a bit to the right.
This solves our problem, because now you don't need to look at all the points anymore, you can just check the center of the ship. If the center goes out of bounds, move it to the other side.
Resulting in the folliwing simple code (i use 300 here because that's the width and height in my example, change it to your width and height in the actual code)
if(center_x > 300)
center_x = 0;
if(center_x < 0)
center_x = 300;
if(center_y > 300)
center_y = 0;
if(center_y < 0)
center_y = 300;
This will take the ship to the other side of the screen when needed. However, we still need to show part of the ship while the other part is on the other side. For that reason, we can just render the ship twice. I hope the following code speaks for itself.
Rectangle2D box = transformed.getBounds2D();
//wrap in x direction
if(box.getX() + box.getWidth() > 300){
AffineTransform transform2 = new AffineTransform();
transform2.translate(ship.center_x - 300, ship.center_y);
transform2.rotate(Math.toRadians(ship.angle));
Shape transformed2 = transform2.createTransformedShape(ship);
g2.draw(transformed2);
}else if(box.getX() < 0){
AffineTransform transform2 = new AffineTransform();
transform2.translate(ship.center_x + 300, ship.center_y);
transform2.rotate(Math.toRadians(ship.angle));
Shape transformed2 = transform2.createTransformedShape(ship);
g2.draw(transformed2);
}
//wrap in y direction
if(box.getY() + box.getHeight() > 300){
AffineTransform transform2 = new AffineTransform();
transform2.translate(ship.center_x, ship.center_y - 300);
transform2.rotate(Math.toRadians(ship.angle));
Shape transformed2 = transform2.createTransformedShape(ship);
g2.draw(transformed2);
}else if(box.getY() < 0){
AffineTransform transform2 = new AffineTransform();
transform2.translate(ship.center_x, ship.center_y + 300);
transform2.rotate(Math.toRadians(ship.angle));
Shape transformed2 = transform2.createTransformedShape(ship);
g2.draw(transformed2);
}
Physics
You also requested more info on the actual physics, i'll just give you the formulas (let me know if you need more help). Here, theta is the angle of the ship in radians, where if theta is 0, the ship points up. The x-axis points to the right, and the y-axis points up.
a_x = Math.sin(theta)*a;
a_y = -Math.cos(theta)*a;
v_x_new = v_x_old + a_x*timeDiff;
v_y_new = v_y_old + a_y*timeDiff;
x_new = x_old + v_x_old*timeDiff + a_x*timeDiff*timeDiff/2;
y_new = y_old + v_y_old*timeDiff + a_y*timeDiff*timeDiff/2;
x_old = x_new;
y_old = y_new;
v_x_old = v_x_new;
v_y_old = v_y_new;
Final result
I've implemented how the ship class should look, and also tested if the rendering method works.
public class AsteroidsTest {
public static void main(String[] args){
int[] xPoints = {0, -20, 0, 20};
int[] yPoints = {-40, 20, 0, 20};
Ship ship = new Ship(xPoints, yPoints, 4, 0);
ship.center_x = 100;
ship.center_y = 100;
ship.angle = 45;
ship.speed_x = 30;
ship.speed_y = -30;
System.out.println("running...");
JFrame window = new JFrame();
window.setBounds(30, 30, 300, 300);
window.getContentPane().add(new MyCanvas(ship));
window.setVisible(true);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
long time1 = System.currentTimeMillis();
while(true){
long time2 = System.currentTimeMillis();
double timeDiff = (time2-time1)/1000f;
time1 = time2;
ship.move(timeDiff);
window.getContentPane().repaint();
}
}
}
class MyCanvas extends JComponent {
private Ship ship;
public MyCanvas(Ship ship){
this.ship = ship;
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
AffineTransform transform = new AffineTransform();
transform.translate(ship.center_x, ship.center_y);
transform.rotate(Math.toRadians(ship.angle));
Shape transformed = transform.createTransformedShape(ship);
g2.draw(transformed);
Rectangle2D box = transformed.getBounds2D();
//wrap in x direction
if(box.getX() + box.getWidth() > 300){
AffineTransform transform2 = new AffineTransform();
transform2.translate(ship.center_x - 300, ship.center_y);
transform2.rotate(Math.toRadians(ship.angle));
Shape transformed2 = transform2.createTransformedShape(ship);
g2.draw(transformed2);
}else if(box.getX() < 0){
AffineTransform transform2 = new AffineTransform();
transform2.translate(ship.center_x + 300, ship.center_y);
transform2.rotate(Math.toRadians(ship.angle));
Shape transformed2 = transform2.createTransformedShape(ship);
g2.draw(transformed2);
}
//wrap in y direction
if(box.getY() + box.getHeight() > 300){
AffineTransform transform2 = new AffineTransform();
transform2.translate(ship.center_x, ship.center_y - 300);
transform2.rotate(Math.toRadians(ship.angle));
Shape transformed2 = transform2.createTransformedShape(ship);
g2.draw(transformed2);
}else if(box.getY() < 0){
AffineTransform transform2 = new AffineTransform();
transform2.translate(ship.center_x, ship.center_y + 300);
transform2.rotate(Math.toRadians(ship.angle));
Shape transformed2 = transform2.createTransformedShape(ship);
g2.draw(transformed2);
}
}
}
Ship class:
import java.awt.Polygon;
public class Ship extends Polygon{
private static final long serialVersionUID = 1L;
public double center_x = 0;
public double center_y = 0;
public double speed_x = 0;
public double speed_y = 0;
//the angle of the ship
public double angle = 0;
//the acceleration of the ship in the direction defined by angle
public double acceleration = 0;
public Ship(int[] x, int[] y, int points, double angle){
super(x, y, points);
this.angle= angle;
}
public void right(){
angle += 5;
}
public void left(){
angle -= 5;
}
public void move(double timeDiff){
double a_x = Math.sin(Math.toRadians(angle))*acceleration;
double a_y = -Math.cos(Math.toRadians(angle))*acceleration;
center_x = center_x + speed_x*timeDiff + a_x*timeDiff*timeDiff/2;
center_y = center_y + speed_y*timeDiff + a_y*timeDiff*timeDiff/2;
speed_x = speed_x + a_x*timeDiff;
speed_y = speed_y + a_y*timeDiff;
if(center_x > 300)
center_x = 0;
if(center_x < 0)
center_x = 300;
if(center_y > 300)
center_y = 0;
if(center_y < 0)
center_y = 300;
}
public void reverse(){
acceleration = -1;
}
public void go(){
acceleration = 1;
}
public void stop(){
acceleration = 0;
}
public int getCenterX(){
return (int) Math.round(center_x);
}
public int getCenterY(){
return (int) Math.round(center_y);
}
public double getAng(){
return angle;
}
}
given the following code:
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
public class DragRotation extends JPanel {
Rectangle2D.Double rect = new Rectangle2D.Double(100,75,200,160);
AffineTransform at = new AffineTransform();
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(Color.blue);
g2.draw(rect);
g2.setPaint(Color.red);
g2.draw(at.createTransformedShape(rect));
}
public static void main(String[] args) {
DragRotation test = new DragRotation();
test.addMouseListener(test.rotator);
test.addMouseMotionListener(test.rotator);
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(test);
f.setSize(400,400);
f.setLocation(200,200);
f.setVisible(true);
}
private MouseInputAdapter rotator = new MouseInputAdapter() {
Point2D.Double center = new Point2D.Double();
double thetaStart = 0;
double thetaEnd = 0;
boolean rotating = false;
public void mousePressed(MouseEvent e) {
Point p = e.getPoint();
Shape shape = at.createTransformedShape(rect);
if(shape.contains(p)) {
Rectangle r = shape.getBounds();
center.x = r.getCenterX();
center.y = r.getCenterY();
double dy = p.y - center.y;
double dx = p.x - center.x;
thetaStart = Math.atan2(dy, dx) - thetaEnd;
System.out.printf("press thetaStart = %.1f%n",
Math.toDegrees(thetaStart));
rotating = true;
}
}
public void mouseReleased(MouseEvent e) {
rotating = false;
double dy = e.getY() - center.y;
double dx = e.getX() - center.x;
thetaEnd = Math.atan2(dy, dx) - thetaStart;
System.out.printf("release thetaEnd = %.1f%n",
Math.toDegrees(thetaEnd));
}
public void mouseDragged(MouseEvent e) {
if(rotating) {
double dy = e.getY() - center.y;
double dx = e.getX() - center.x;
double theta = Math.atan2(dy, dx);
at.setToRotation(theta - thetaStart, center.x, center.y);
repaint();
}
}
};
}
If i change the line: Rectangle2D.Double rect = new Rectangle2D.Double(100,75,200,160);
to Line2D.Double rect = new Line2D.Double(100,75,200,160); in order to create a 2D line.
After that how should i modify the code,so that it is able to get the coordinates of the mouse over the line and make the whole code work for rotation of the line.
Thanks!
For determine rotation you use shape.contains(p) it's work for Rectangle, but it doesn't work for a line, because I think it is really hard to point inside a Line.
You need to specify some area for rotation flag of a line, somthing like next :
if(rect.x1 < p.x && rect.x2 > p.x
&& rect.y1 < p.y && rect.y2 > p.y){
}
instead of
if(shape.contains(p)) {
}
in your mousePressed() method.
Is there a simple library that can parse a True Type font file and give me a list of vectors/points for me to render it myself? I'm talking about something similar to freetype, but for Java.
Java can do that out of the box. Some time ago, I explored this field.
I used Processing for easy/fast graphic rendering, but the code to access font information is pure Java. So maybe my little program can be of interest for you. I just pasted it in Pastebin
/**
Get a font and display glyphs with additional information
(bounding boxes, anchor & control points...)
by Philippe Lhoste <PhiLho(a)GMX.net> http://Phi.Lho.free.fr & http://PhiLho.deviantART.com
*/
/* File/Project history:
1.01.000 -- 2008/08/28 (PL) -- Some improvements.
1.00.000 -- 2008/08/28 (PL) -- Creation.
*/
/* Copyright notice: For details, see the following file:
http://Phi.Lho.free.fr/softwares/PhiLhoSoft/PhiLhoSoftLicence.txt
This program is distributed under the zlib/libpng license.
Copyright (c) 2008 Philippe Lhoste / PhiLhoSoft
*/
import java.awt.font.*;
import java.awt.geom.*;
final static int DEMO_ID = 2;
final static String FONT_NAME = "Times New Roman";
final static String STRING = "p#";
int FONT_SIZE, SCALE;
Graphics2D g2;
AffineTransform transform;
HashMap colorList = new HashMap();
void setup()
{
size(1000, 700);
smooth();
noLoop();
background(150);
noFill();
g2 = ((PGraphicsJava2D) g).g2;
// Move drawing to a convenient place
transform = new AffineTransform();
if (DEMO_ID == 1)
{
FONT_SIZE = 1;
SCALE = 300;
transform.translate(40, 320);
transform.scale(SCALE, SCALE);
}
else if (DEMO_ID == 2)
{
FONT_SIZE = 2;
SCALE = 300;
transform.translate(50, 500);
transform.scale(SCALE, SCALE);
}
else if (DEMO_ID == 3)
{
FONT_SIZE = 2;
SCALE = 300;
transform.translate(50, 500);
transform.scale(SCALE, SCALE);
}
// g2.setTransform(transform);
// And show the origin (and scale) of drawing
SetColor(#FF0000); // Update g2
strokeWeight(5);
Line2D.Double line = new Line2D.Double(-0.1, -0.1, 0.1, 0.1);
g2.draw(transform.createTransformedShape(line));
line = new Line2D.Double(-0.1, 0.1, 0.1, -0.1);
g2.draw(transform.createTransformedShape(line));
strokeWeight(2);
// Now, we get the font
Font font = new Font(FONT_NAME, Font.PLAIN, FONT_SIZE);
g2.setFont(font);
FontRenderContext frc = g2.getFontRenderContext();
if (DEMO_ID == 1)
{
//~ String str = "%&#";
GlyphVector glyphVector = font.createGlyphVector(frc, STRING);
for (int i = 0; i < STRING.length(); i++)
{
strokeWeight(2);
SetColor(#00FF00);
Shape vbs = glyphVector.getGlyphVisualBounds(i);
g2.draw(transform.createTransformedShape(vbs));
SetColor(#005000);
Rectangle r = glyphVector.getGlyphPixelBounds(i, null, 0.0f, 0.0f);
g2.draw(transform.createTransformedShape(r));
SetColor(#A0A000);
Shape lbs = glyphVector.getGlyphLogicalBounds(i);
g2.draw(transform.createTransformedShape(lbs));
SetColor(#0050F0);
strokeWeight(5);
Shape shape = glyphVector.getGlyphOutline(i);
g2.draw(transform.createTransformedShape(shape));
}
// Draw the whole string at once
SetColor(#F0A000);
strokeWeight(1);
Shape shape = glyphVector.getOutline();
g2.draw(transform.createTransformedShape(shape));
}
else if (DEMO_ID == 2)
{
GlyphVector glyphVector = font.createGlyphVector(frc, STRING);
for (int i = 0; i < STRING.length(); i++)
{
SetColor(#0050F0);
strokeWeight(5);
Shape shape = glyphVector.getGlyphOutline(i);
g2.draw(transform.createTransformedShape(shape));
HighlightPoints(shape);
}
}
else if (DEMO_ID == 3) // To test the SEG_CUBICTO case!
{
SetColor(#0050F0);
strokeWeight(5);
GeneralPath shape = new GeneralPath();
shape.moveTo(1.5, 0.0);
shape.lineTo(1.8, 0.0);
shape.quadTo(2.0, 0.3, 1.5, 0.3);
shape.lineTo(1.1, 0.3);
shape.curveTo(0.7, 0.3, 0.5, -0.5, 1.2, -0.2);
shape.lineTo(1.5, -0.3);
shape.closePath();
g2.draw(transform.createTransformedShape(shape));
HighlightPoints(shape);
}
}
void HighlightPoints(Shape shape)
{
strokeWeight(1);
PathIterator iterator = shape.getPathIterator(null);
float[] coords = new float[6];
float px = 0, py = 0;
while (!iterator.isDone())
{
int type = iterator.currentSegment(coords);
switch (type)
{
case PathIterator.SEG_MOVETO: // One point
print("Move ");
px = coords[0];
py = coords[1];
DrawMovePoint(px, py);
break;
case PathIterator.SEG_LINETO:
print("Line ");
px = coords[0];
py = coords[1];
DrawLinePoint(px, py);
break;
case PathIterator.SEG_QUADTO: // Two points
print("Quad ");
DrawControlLine(coords[0], coords[1], px, py);
px = coords[2];
py = coords[3];
DrawControlLine(coords[0], coords[1], px, py);
DrawControlPoint(coords[0], coords[1]);
DrawQuadPoint(px, py);
break;
case PathIterator.SEG_CUBICTO: // Three points
print("Cubic "); // Not seen yet...
DrawControlLine(coords[0], coords[1], px, py); // Connect to last point
px = coords[4];
py = coords[5];
DrawControlLine(coords[2], coords[3], px, py);
DrawControlPoint(coords[0], coords[1]);
DrawControlPoint(coords[2], coords[3]);
DrawCubicPoint(px, py);
break;
case PathIterator.SEG_CLOSE: // No points
}
iterator.next();
}
}
void DrawMovePoint(float x, float y)
{
float radius = 2.5 / SCALE;
Ellipse2D.Float e = new Ellipse2D.Float(x - radius, y - radius, 2 * radius, 2 * radius);
SetColor(#FF00A0);
g2.fill(transform.createTransformedShape(e));
SetColor(#FF00FF);
g2.draw(transform.createTransformedShape(e));
}
void DrawLinePoint(float x, float y)
{
float radius = 4.5 / SCALE;
Ellipse2D.Float e = new Ellipse2D.Float(x - radius, y - radius, 2 * radius, 2 * radius);
SetColor(#FF00FF);
g2.draw(transform.createTransformedShape(e));
}
void DrawQuadPoint(float x, float y)
{
SetColor(#80FF00);
float radius = 3.0 / SCALE;
Rectangle2D.Float r = new Rectangle2D.Float(x - radius, y - radius, 2 * radius, 2 * radius);
g2.draw(transform.createTransformedShape(r));
}
void DrawCubicPoint(float x, float y)
{
SetColor(#FF8000);
float radius = 5.0 / SCALE;
Rectangle2D.Float r = new Rectangle2D.Float(x - radius, y - radius, 2 * radius, 2 * radius);
g2.draw(transform.createTransformedShape(r));
}
void DrawControlPoint(float x, float y)
{
SetColor(#D0E000);
float radius = 3.0 / SCALE;
Ellipse2D.Float e = new Ellipse2D.Float(x - radius, y - radius, 2 * radius, 2 * radius);
g2.draw(transform.createTransformedShape(e));
}
void DrawControlLine(float x1, float y1, float x2, float y2)
{
SetColor(#FFA000);
Line2D.Float l = new Line2D.Float(x1, y1, x2, y2);
g2.draw(transform.createTransformedShape(l));
}
Color GetColor(color c)
{
Integer ic = Integer.valueOf(c); // New to 1.5! Cache values
Color k = (Color) colorList.get(ic);
if (k == null)
{
k = new Color(ic);
colorList.put(ic, k);
}
return k;
}
void SetColor(color c)
{
Color k = GetColor(c);
g2.setPaint(k);
}
You can use Batik to parse a TrueType Font and generate an SVG file (SVGFont). So I bet that you could use the libraries to get your vectors out.