I'm pretty new to Affine Transformations and am finding a few things quite frustrating..
I've been working on a personal project that draws a laser (think Star Wars) on the screen. I use the RadialGradientPaint class to draw the glow for the laser, and then use scale and rotate Affine Transformations to change the oval's shape and turn the laser in a specific direction.
When I try to rotate the graphic (without the scale), it appears to shrink it, and I'm not sure why this is. I've tried looking this up to no avail. Perhaps I misinterpreted the Java Doc on this class. Why is this?
I'm also curious as to how I can get the transformed object to move to the same location as an un-transformed graphic. Both graphics move to different places even with the same coordinates. I know this might be due to an Affine Transformation shifting the whole graph, and that I might have to translate the transformed graphic to get it to the same exact place as the un-transformed graphic, but I'm not sure how to approach this.
Here is the portion of my code that draws the laser:
public void render(Graphics g) {
// If the graphic is visible on the frame, draw it
if(onScreen()) {
Graphics2D g2d = (Graphics2D) g;
// Preserves the original presets of the program's graphics
AffineTransform ogTransform = g2d.getTransform();
Shape ogClip = g2d.getClip();
Point2D center = new Point2D.Float(x, y);
float[] dist = {0.3f, 0.9f};
Color[] colors = {new Color(1.0f, 1.0f, 1.0f, 0.9f), new Color(0.0f, 1.0f, 0.0f, 0.1f)};
g2d.setPaint(new RadialGradientPaint(center, radius, dist, colors));
AffineTransform scaleTransform = AffineTransform.getScaleInstance(scaleX, scaleY);
AffineTransform rotationTransform = AffineTransform.getRotateInstance(angle, x + width/2f, y + height/2f);
// Transforms the shape to look more like a laser
rotationTransform.concatenate(scaleTransform);
g2d.setTransform(rotationTransform);
// Draws a "cut-out" of the rectangle
g2d.setClip(new Ellipse2D.Double(x - radius, y - radius, radius * 2, radius * 2));
g2d.fillRect(x - radius, y - radius, radius * 2, radius * 2);
// Default transformation and clip are set
g2d.setTransform(ogTransform);
g2d.setClip(ogClip);
// Outlines where the transformed graphic should be
g.setColor(Color.orange);
g.drawRect(x - 10, y - 10, 20, 20);
}
}
Any help is appreciated!
Related
I am programming a GUI framework in lwjgl (opengl for java). I've recently implemented rounded rectangles by rendering a couple of normal rectangles surrounded by circles. To render the circles I used GL11.GL_POINTS. I now reached the point, where I am trying to implement animations and for a window open animation, I decided to GL11.glScaled() it from small to normal. That works fine, but unfortunately my circles don't get resized.
I tried changing my GL_POINTS circle render method against a method that uses TRIANGLE_FANs and that worked fine. My problem there was, that the circles didn't look smooth and round at all and if I increase the rendered triangles it starts to lag very quick. Even though my computer isn't bad at all.
This is the code I've used to render circles with GL_POINTS.
GL11.glEnable(GL11.GL_POINT_SMOOTH);
GL11.glHint(GL11.GL_POINT_SMOOTH_HINT, GL11.GL_NICEST);
GL11.glPointSize(radius);
GL11.glBegin(GL11.GL_POINTS);
GL11.glVertex2d(x, y);
GL11.glEnd();
GL11.glDisable(GL11.GL_POINT_SMOOTH);
This is the code I've used to scale the circles
GL11.glPushMatrix();
GL11.glTranslated(x, y, 0);
GL11.glScaled(2.0f, 2.0f, 1);
GL11.glTranslated(-x, -y, 0);
render circles
GL11.glPopMatrix();
I expect the circles to scale accordingly to the number I've put into glScaled()
Currently they aren't rescaling at all, just rendered at their normal size.
Here's a demonstration of how to properly render a circle using triangle fans:
public void render() {
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();
// Coordinate system starts out as screen space coordinates
glOrtho(0, 400, 300, 0, 1, -1);
glColor3d(1, 0.5, 0.5);
renderCircle(120, 120, 100);
glColor3d(0.5, 1, 0.5);
renderCircle(300, 200, 50);
glColor3d(0.5, 0.5, 1);
renderCircle(200, 250, 30);
}
private void renderCircle(double centerX, double centerY, double radius) {
glPushMatrix();
glTranslated(centerX, centerY, 0);
glScaled(radius, radius, 1);
// Another translation here would be wrong
renderUnitCircle();
glPopMatrix();
}
private void renderUnitCircle() {
glBegin(GL_TRIANGLE_FAN);
int numVertices = 100;
double angle = 2 * Math.PI / numVertices;
for (int i = 0; i < numVertices; ++i) {
glVertex2d(Math.cos(i*angle), Math.sin(i*angle));
}
glEnd();
}
Output image:
The GL_POINT_SIZE value is actually the size of the point in pixels onscreen, not current coordinate units. For that reason your circles were unaffected by GL_SCALE. That's one reason not to use GL_POINTS to render circles. The other (arguably more important) reason being that GL_POINT_SIZE is severely deprecated and unsupported in newer OpenGL profiles.
In a small game I am developing using libgdx, I have spears as game objects.
I implemented them using Scene2D, as a subclass of Actor. The spears can be rotated by multitudes of 90° . I want to have just the "speartip" part of my image as a part that "hurts" the Player, the shaft shall be harmless. So when I construct my Spear object, I also construct 2 rectangles that cover the tip and the shaft of the spear. But when my actor gets rotated using setRotation() , the rectangles obviously don't, cause they aren't "attached" to the spear object.
Do you have some suggestions on how to handle this kind of stuff? Code below.
public TrapEntity(Texture texture, float pos_x, float pos_y, float width, float height, Vector2 center,
float rotation, Rectangle hurtZone, Rectangle specialZone) {
super(texture, pos_x, pos_y, width, height, center, rotation);
this.hurtZone = new Rectangle(hurtZone.getX(), hurtZone.getY(), hurtZone.getWidth(), hurtZone.getHeight());
this.specialZone = new Rectangle(specialZone.getX(), specialZone.getY(), specialZone.getWidth(), specialZone.getHeight());
}
And the render method in the same class. I use it to render the bounds of the "hurtZone" rectangle:
#Override
public void draw(Batch batch, float alpha){
super.draw(batch, alpha);
batch.end();
sr.setProjectionMatrix(batch.getProjectionMatrix());
sr.setTransformMatrix(batch.getTransformMatrix());
sr.begin(ShapeRenderer.ShapeType.Line);
sr.setColor(Color.RED);
sr.rect(hurtZone.getX(), hurtZone.getY(), hurtZone.getWidth(), hurtZone.getHeight());
sr.end();
batch.begin();
}
Rectangles can't be rotated, I had to do a similar thing and struggled with finding a solution. The best solution I have found yet is to use a Polygon. You would do something like this;
//Polygon for a rect is set by vertices in this order
Polygon polygon = new Polygon(new float[]{0, 0, width, 0, width, height, 0, height});
//properties to update each frame
polygon.setPosition(item.getX(), item.getY());
polygon.setOrigin(item.getOriginX(), item.getOriginY());
polygon.setRotation(item.getRotation());
Then, to check if a point is within the rotated Polygon use;
polygon.contains(x, y);
I am trying to plot a graph using the java 2d graphics library and I thought I had it. I want to plot in the coordinate system where 0,0 is in the center of the panel on the left edge. I used the following code and it seemed to give me the result I needed.
private void doDraw(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
AffineTransform saveAT = g2d.getTransform();
// get the height of the panel
int height = getHeight();
// Find the middle of the panel
double yTrans = ((double)height)/2.0;
AffineTransform tform = AffineTransform.getTranslateInstance( 0.0, yTrans);
g2d.setTransform(tform);
//draw the line for the x-axis.
g2d.drawLine(0, 0, 100, 0);
//restore the old transform
g2d.setTransform(saveAT);
}
This plotted the origin centered in the window.
The problem shows itself when I added a menu. Then the origin was offset in the y direction about twice the size of the menu higher then it should be. Do I need to account for the size of the menu and other containers that I add to the panel?
private void doDraw(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
int height = getHeight();
double yTrans = ((double)height)/2.0;
AffineTransform tform = AffineTransform.getTranslateInstance( 0.0, yTrans);
g2d.transform(tform);
//draw the line for the x-axis.
g2d.drawLine(0, 0, 100, 0);
}
works, thank you for your help
You might try the approach outlined here. Override paintComponent() to obtain a graphics context relative to the enclosing panel, rather than the enclosing frame.
To center the origin at the left edge, use
g2d.translate(0, h / 2);
To get upright, cartesian coordinates, use
g2d.scale(1, -1);
I'm quite new to graphics in java and I'm trying to create a shape that clips to the bottom of another shape. Here is an example of what I'm trying to achieve:
Where the white line at the base of the shape is the sort of clipped within the round edges.
The current way I am doing this is like so:
g2.setColor(gray);
Shape shape = getShape(); //round rectangle
g2.fill(shape);
Rectangle rect = new Rectangle(shape.getBounds().x, shape.getBounds().y, width, height - 3);
Area area = new Area(shape);
area.subtract(new Area(rect));
g2.setColor(white);
g2.fill(area);
I'm still experimenting with the clip methods but I can't seem to get it right. Is this current method ok (performance wise, since the component repaints quite often) or is there a more efficient way?
I think your original idea about using the clip methods was the right way to do it. This works for me:
static void drawShapes(Graphics2D g, int width, int height,
Shape clipShape) {
g.setPaint(Color.BLACK);
g.fillRect(0, 0, width, height);
g.clip(clipShape);
int centerX = width / 2;
g.setPaint(new GradientPaint(
centerX, 0, Color.WHITE,
centerX, height, new Color(255, 204, 0)));
g.fillRect(0, 0, width, height);
g.setPaint(Color.WHITE);
int whiteRectHeight = height * 4 / 5;
g.fillRect(0, whiteRectHeight,
width, height - whiteRectHeight);
}
Is this current method ok (performance wise, since the component repaints quite often) ..
Subtracting shapes is how I'd go about it. The objects could be a few instances or (possibly) a single instance that is transformed as needed.
A text demo., using scaling & fading.
Here's one with simple lines (..and dots, ..and it is animated).
Of course, if the image is purely additive, use a BufferedImage as the canvas & display it in a JLabel/ImageIcon combo. As in both of those examples.
Is it possible to rotate the Rectangle object to a certain degree around it's axis? Is it as easy as Rectangle rect = new Rectangle(x,y,w,h,r)?
If it is not possible to rotate the object, what would be a way I could get similar results?
Edit: for clarity here is my dilemma, I have images that rotate but when they colide with other images the collisions only work at 90 and 180 degree rotations because their hit box Rectangle objects don't rotate.
Edit: for clarity here is my dilemma, I have images that rotate but when they colide with other images the collisions only work at 90 and 180 degree rotations because their hit box Rectangle objects don't rotate.
You can rotate a Shape-derived object, such as a Rectangle2D by using the AffineTransform method, createTransformedShape(...).
Rectangle2D myRect = new Rectangle2D.Double(100, 100, 200, 200);
AffineTransform at = AffineTransform.getRotateInstance(Math.PI / 4, 150, 150);
Shape rotatedRect = at.createTransformedShape(myRect);
Note: code not compiled nor tested.
To rotate the rectangle, you give the graphics context an AffineTransform for rotation. Here's an example:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
AffineTransform oldTransform = g2d.getTransform();
g2d.setTransform(AffineTransform.getRotateInstance(radians);
g2d.draw(rectangle);
g2d.setTransform(oldTransform);
}
You can also use g2d.rotate(radians) for rotation.
Note that the angle must be in radians. To convert degrees into radians, use degrees * (Math.PI/180) for the angle.
There's also another way to do this (besides createTransformedShape) which creates fewer temporary objects, if that's desirable.
Instead of keeping a Rectangle2D for the bounding box, you can keep a Path2D and then do the transform in place, using Path2D.transform(AffineTransform):
import java.awt.geom.*;
public class Example {
private final Path2D hitBox;
public Example(Rectangle2D initialHitBox) {
this.hitBox = new Path2D.Double(initialHitBox);
}
public void transform(AffineTransform tx) {
path.transform(tx); // In-place transformation.
}
// ...
}
This is very similar to what AffineTransform actually does under the hood.