How do I draw an arrowhead (in Android)? - java
I'm fairly new to Android and have been toying around with Canvas. I'm attempting to draw an arrow but I'm only having luck with drawing the shaft, none of the arrowhead is working.
I have searched a bit and found a Java example, but Android doesn't have GeneralPath or AffineTransform.
Right now my code looks like the following (the arrowhead looks nothing like an arrowhead):
public class DrawableView extends View {
Context mContext;
private int centerX;
private int centerY;
private int radius;
private double arrLength;
private double arrHeading;
private int margin = 10;
public DrawableView(Context context) {
super(context);
mContext = context;
}
#Override
protected void onDraw(Canvas canvas) {
//Paint Background
Paint background = new Paint();
background.setColor(getResources().getColor(R.color.background);
canvas.drawRect(0, 0, getWidth(), getHeight(), background);
//Set vars for Arrow Paint
Paint paint = new Paint();
paint.setColor(getResources().getColor(R.color.arrowColor);
centerX = getWidth() / 2;
centerY = getHeight() / 2;
arrLength = radius - 10;
if(centerX < centerY)
radius = centerX - margin;
else
radius = centerY - margin;
//Draw Shaft
int[] xy = findArrowPos(arrLength, arrHeading);
canvas.drawLine(centerX, centerY, xy[0], xy[1], paint);
//Draw ArrowHead
//This is where I'm confused
}
private int[] findArrowPos(double length, double angle) {
int[] points = new int[2];
double theta = Math.toRadians(angle);
points[0] = centerX + (int) (length * Math.cos(theta));
points[1] = centerY + (int) (length * Math.sin(theta));
return points;
}
}
I have taken a look at the following threads for guidance:
* http://www.java-forums.org/awt-swing/6241-how-u-rotate-arrow-mark-line-moves-accordingly.html
* How to draw a directed arrow line in Java?
How about using "Path myPath = new Path();" where you would give the x and y positions to create a triangle using lines and filling it. You can read about it, here is an example I took from somewhere.
// create and draw triangles
// use a Path object to store the 3 line segments
// use .offset to draw in many locations
// note: this triangle is not centered at 0,0
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2);
paint.setColor(Color.RED);
Path path = new Path();
path.moveTo(0, -10);
path.lineTo(5, 0);
path.lineTo(-5, 0);
path.close();
path.offset(10, 40);
canvas.drawPath(path, paint);
path.offset(50, 100);
canvas.drawPath(path, paint);
// offset is cumlative
// next draw displaces 50,100 from previous
path.offset(50, 100);
canvas.drawPath(path, paint);
My Arrow Drawing code, maybe it can be of some use for somebody:
/**
* Draw an arrow
* change internal radius and angle to change appearance
* - angle : angle in degrees of the arrows legs
* - radius : length of the arrows legs
* #author Steven Roelants 2017
*
* #param paint
* #param canvas
* #param from_x
* #param from_y
* #param to_x
* #param to_y
*/
private void drawArrow(Paint paint, Canvas canvas, float from_x, float from_y, float to_x, float to_y)
{
float angle,anglerad, radius, lineangle;
//values to change for other appearance *CHANGE THESE FOR OTHER SIZE ARROWHEADS*
radius=10;
angle=15;
//some angle calculations
anglerad= (float) (PI*angle/180.0f);
lineangle= (float) (atan2(to_y-from_y,to_x-from_x));
//tha line
canvas.drawLine(from_x,from_y,to_x,to_y,paint);
//tha triangle
Path path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
path.moveTo(to_x, to_y);
path.lineTo((float)(to_x-radius*cos(lineangle - (anglerad / 2.0))),
(float)(to_y-radius*sin(lineangle - (anglerad / 2.0))));
path.lineTo((float)(to_x-radius*cos(lineangle + (anglerad / 2.0))),
(float)(to_y-radius*sin(lineangle + (anglerad / 2.0))));
path.close();
canvas.drawPath(path, paint);
}
I try this code it has been working perfectly:
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
startPoint = new PointF(event.getX(), event.getY());
endPoint = new PointF();
invalidate();
break;
case MotionEvent.ACTION_MOVE:
float dx = Math.abs(x - mX);
System.out.println("action move");
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE)
{
// currentDrawingPath.path.quadTo(mX,mY,(x + mX)/2, (y + mY)/2);
}
mX = x;
mY = y;
endPoint.x = event.getX();
endPoint.y = event.getY();
isDrawing = true;
invalidate();
break;
case MotionEvent.ACTION_UP:
mPath.lineTo(mX, mY);
float deltaX = endPoint.x-startPoint.x;
float deltaY = endPoint.y-startPoint.y;
float frac = (float) 0.1;
float point_x_1 = startPoint.x + (float) ((1 - frac) * deltaX + frac * deltaY);
float point_y_1 = startPoint.y + (float) ((1 - frac) * deltaY - frac * deltaX);
float point_x_2 = endPoint.x;
float point_y_2 = endPoint.y;
float point_x_3 = startPoint.x + (float) ((1 - frac) * deltaX - frac * deltaY);
float point_y_3 = startPoint.y + (float) ((1 - frac) * deltaY + frac * deltaX);
mPath.moveTo(point_x_1, point_y_1);
mPath.lineTo(point_x_2, point_y_2);
mPath.lineTo(point_x_3, point_y_3);
mPath.lineTo(point_x_1, point_y_1);
mPath.lineTo(point_x_1, point_y_1);
mCanvas.drawPath(mPath, ppaint);
endPoint.x = event.getX();
endPoint.y = event.getY();
isDrawing = false;
invalidate();
break;
default:
break;
}
I've been having the same problem, I need an arrow to point in a certain direction. After playing around with drawing algorithms I decided the simplest method is to use a bitmap & simply use a Matrix to rotate it, e.g.
ImageView image = (ImageView) findViewById(R.id.bitmap_image);
Bitmap bMap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
Matrix mat = new Matrix();
mat.postRotate(90);
Bitmap bMapRotate = Bitmap.createBitmap(bMap, 0, 0, bMap.getWidth(), bMap.getHeight(), mat, true);
image.setImageBitmap(bMapRotate);
then your bitmap can be any fancy looking arrow you like.
If you are looking for the solution to draw thousands of arrows under a second, with fixed length head lines, try this function (draws only arrow heads):
private void fillArrow(Paint paint, Canvas canvas, float x0, float y0, float x1, float y1) {
paint.setStyle(Paint.Style.STROKE);
int arrowHeadLenght = 10;
int arrowHeadAngle = 45;
float[] linePts = new float[] {x1 - arrowHeadLenght, y1, x1, y1};
float[] linePts2 = new float[] {x1, y1, x1, y1 + arrowHeadLenght};
Matrix rotateMat = new Matrix();
//get the center of the line
float centerX = x1;
float centerY = y1;
//set the angle
double angle = Math.atan2(y1 - y0, x1 - x0) * 180 / Math.PI + arrowHeadAngle;
//rotate the matrix around the center
rotateMat.setRotate((float) angle, centerX, centerY);
rotateMat.mapPoints(linePts);
rotateMat.mapPoints(linePts2);
canvas.drawLine(linePts [0], linePts [1], linePts [2], linePts [3], paint);
canvas.drawLine(linePts2 [0], linePts2 [1], linePts2 [2], linePts2 [3], paint);
}
Based on https://gamedev.stackexchange.com/questions/44456/drawing-lines-on-android-with-matrix
Here is code working perfect for me draw arrow head while drawing line on canvas
package com.example.canvasexample;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
public class DrawerViewArrow extends View {
private ArrayList<Path> drawingLinePath;
private ArrayList<Path> drawingArrowPath;
private ArrayList<Paint> drawingLinePaint;
private int pathIndex = 0;
private float startX = -1, startY = -1;
private float mX = -1, mY = -1;
public int arrowLength = 80;
public int arrowWidth = 45;
public int strokeWidth = 10;
public DrawerViewArrow(Context context) {
super(context);
initPath();
}
public DrawerViewArrow(Context context, #NonNull AttributeSet attrs) {
super(context, attrs);
initPath();
}
public DrawerViewArrow(Context context, #NonNull AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPath();
}
private Paint initPaint() {
Paint mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(Color.GREEN);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(strokeWidth);
return mPaint;
}
private void initPath() {
drawingLinePath = new ArrayList<>();
drawingArrowPath = new ArrayList<>();
drawingLinePath.add(new Path());
drawingArrowPath.add(new Path());
drawingLinePaint = new ArrayList<>();
drawingLinePaint.add(initPaint());
pathIndex++;
}
private Path createPath(MotionEvent event) {
Path path = new Path();
path.moveTo(event.getX(), event.getY());
return path;
}
private void updateIndex(MotionEvent event) {
if (pathIndex == drawingLinePath.size()) {
drawingLinePath.add(createPath(event));
drawingArrowPath.add(createPath(event));
drawingLinePaint.add(initPaint());
pathIndex++;
}
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (startX > -1 && mX > -1) {
canvas.drawLine(startX, startY, mX, mY, initPaint());
drawArrow(canvas);
}
for (int index = 0; index < pathIndex; index++) {
Path path = drawingLinePath.get(index);
Path arrow_path = drawingArrowPath.get(index);
Paint paint = drawingLinePaint.get(index);
canvas.drawPath(path, paint);
canvas.drawPath(arrow_path, paint);
}
}
private void drawArrow(Canvas canvas) {
double angle = calculateAngle(startX, startY, mX, mY);
float final_angle = (float) (180 - angle);
Path arrow_path = new Path();
Matrix arrow_matrix = new Matrix();
arrow_matrix.postRotate(final_angle, mX, mY);
arrow_path.moveTo(mX, mY);
arrow_path.lineTo(mX - arrowWidth, mY + arrowLength);
arrow_path.moveTo(mX, mY);
arrow_path.lineTo(mX + arrowWidth, mY + arrowLength);
arrow_path.lineTo(mX - (arrowWidth), mY + arrowLength);
arrow_path.transform(arrow_matrix);
canvas.drawPath(arrow_path, initPaint());
}
private void saveArrow() {
if (mX == -1 || mY == -1) {
return;
}
double angle = calculateAngle(startX, startY, mX, mY);
float final_angle = (float) (180 - angle);
Path arrow_path = drawingArrowPath.get(pathIndex - 1);
Matrix arrow_matrix = new Matrix();
arrow_matrix.postRotate(final_angle, mX, mY);
arrow_path.moveTo(mX, mY);
arrow_path.lineTo(mX - arrowWidth, mY + arrowLength);
arrow_path.moveTo(mX, mY);
arrow_path.lineTo(mX + arrowWidth, mY + arrowLength);
arrow_path.lineTo(mX - (arrowWidth), mY + arrowLength);
arrow_path.transform(arrow_matrix);
}
public double calculateAngle(double x1, double y1, double x2, double y2) {
double angle = Math.toDegrees(Math.atan2(x2 - x1, y2 - y1));
angle = angle + Math.ceil(-angle / 360) * 360; //Keep angle between 0 and 360
return angle;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case ACTION_UP:
actionUp(event);
break;
case ACTION_MOVE:
actionMove(event);
break;
case ACTION_DOWN:
actionDown(event);
break;
}
invalidate();
return true;
}
private void actionDown(MotionEvent event) {
updateIndex(event);
startX = event.getX();
startY = event.getY();
}
private void actionMove(MotionEvent event) {
mX = event.getX();
mY = event.getY();
}
private void actionUp(MotionEvent event) {
drawingLinePath.get(pathIndex - 1).lineTo(event.getX(), event.getY());
saveArrow();
startX = -1;
startY = -1;
mX = -1;
mY = -1;
}
}
Use a Path as below and adjust the co-ordinates accordingly:
// Construct a wedge-shaped path
Path mPath = new Path();
mPath.moveTo(0, -50);
mPath.lineTo(-20, 60);
mPath.lineTo(0, 50);
mPath.lineTo(20, 60);
mPath.close();
Copypast from this answer https://stackoverflow.com/a/29383352/9975029
private void fillArrow(Canvas canvas, float x0, float y0, float x1, float y1) {
paint.setStyle(Paint.Style.FILL);
float deltaX = x1 - x0;
float deltaY = y1 - y0;
double distance = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY));
float frac = (float) (1 / (distance / 30));
float point_x_1 = x0 + (float) ((1 - frac) * deltaX + frac * deltaY);
float point_y_1 = y0 + (float) ((1 - frac) * deltaY - frac * deltaX);
float point_x_2 = x1;
float point_y_2 = y1;
float point_x_3 = x0 + (float) ((1 - frac) * deltaX - frac * deltaY);
float point_y_3 = y0 + (float) ((1 - frac) * deltaY + frac * deltaX);
Path path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
path.moveTo(point_x_1, point_y_1);
path.lineTo(point_x_2, point_y_2);
path.lineTo(point_x_3, point_y_3);
path.lineTo(point_x_1, point_y_1);
path.lineTo(point_x_1, point_y_1);
path.close();
canvas.drawPath(path, paint);
}
Here's my arrow drawing code without using trig functions explicitly (although the underlying math obviously uses trig)
The math makes an arrow head like half of a square (cut diagonally) where variable L is the length of the diagonal. Also, the arrow ends at point p2 which means that for small difference between p2 and p1, the arrow head will be drawn 'before' p2 for sufficient L. Also if p1 and p2 are the same, the arrow will not be drawn because the math would cause division by zero.
I suggest you use Paint.Style.FILL_AND_STROKE to draw this arrow.
I'm open for any questions.
void drawArrow(Canvas canvas, Point p1, Point p2, float L) {
float fsin, fcos;
double d;
if(p1.equals(p2))
return;
d = Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
fsin = (p2.y - p1.y)/(float)d;
fcos = (p2.x - p1.x)/(float)d;
PointF p3 = new PointF(p2.x - L/2*(fsin + fcos), p2.y + L/2*(fcos - fsin));
PointF p4 = new PointF(p2.x + L/2*(fsin - fcos), p2.y - L/2*(fsin + fcos));
canvas.drawLine(p1.x, p1.y, p2.x, p2.y, arrowPaint);
Path path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
path.moveTo(p2.x, p2.y);
path.lineTo(p3.x, p3.y);
path.lineTo(p4.x, p4.y);
path.close();
canvas.drawPath(path, arrowPaint);
}
Related
java android path animation
So I am nearly at the end. I have been researching the whole day on how to do this. Draw a path which grows(animation) from one point to another. I have tried it with Matrix, but that just ended with turning my whole paths. Here is a image of my project: my project My goal is to draw a animated path from one circle to the other. Code: public void init(#Nullable AttributeSet attr) { circle = new Paint(); circle.setColor(Color.GREEN); circle.setStyle(Paint.Style.FILL); circle.setAntiAlias(true); line = new Paint(); line.setColor(Color.GREEN); line.setStyle(Paint.Style.STROKE); line.setStrokeWidth(10); line.setAntiAlias(true); Collections.addAll(height, 100, 20, 50, 40, 70, 10, 50); // in percent System.out.println(height.size() + " this is the size"); } #Override protected void onDraw(Canvas canvas) { float y = getHeight() / 20 * 14; float x = getWidth() / 8; float radius = (canvas.getWidth() * canvas.getHeight()) / 40940; for (int c = 1; c < 8; c++) { System.out.println("at " + c); canvas.drawCircle(x * c, y - ((getHeight() / 20) * (height.get(c - 1) / 10)), radius, circle); points.add(new PointF(x * c, (y - ((getHeight() / 20) * (height.get(c - 1) / 10))))); } } Please Help, Thanks
you just need animate the path using ValueAnimator create one path object Path path = new Path(); and create animator ValueAnimator animator = new ValueAnimator(); float startX = // starting circle x co-ordinate float startY = // starting circle y co-ordinate float endX = // end circle x co-ordinate float endY = // end circle y co-ordinate PropertyValuesHolder propertyX = PropertyValuesHolder.ofFloat("x",startX,endX); PropertyValuesHolder propertyY = PropertyValuesHolder.ofFloat("y",startY,endY); valueAnimator.setValues(propertyX,propertyY); valueAnimator.setDuration(1000); // animation time valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { #Override public void onAnimationUpdate(ValueAnimator animation) { float x = (float) animation.getAnimatedValue("x"); float y = (float) animation.getAnimatedValue("y"); path.lineTo(x,y); invalidate(); } }); valueAnimator.start(); and in onDraw() draw the path canvas.drawPath(path,paint);
Trying to draw custom view How to Detect which slice is clicked
I am creating custom view, which is like screen below. I can draw views but I am unable to create separate click events for each view. How to set separate click events for each arc view?. Thanks in advance. Here is the code: ArcView.java public class ItemView extends View { Utils utils; int left, right, top, bottom; private int color; private int start; private int sweep; private boolean inner; private RectF oval; public float center_x, center_y; int divOn; public float radius; public ItemView(Context context, int color, int start, int sweep) { super(context); this.color = color; this.start = start; this.sweep = sweep; utils = Utils.getInstance(getContext()); } public ItemView(Context context, int color, int start, int sweep, boolean inner, int divOn) { super(context); this.color = color; this.start = start; this.sweep = sweep; this.inner = inner; this.divOn = divOn; utils = Utils.getInstance(getContext()); } public RectF getOval() { return oval; } #Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); } #Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //There is another flag for inner white circle if (inner) { float width = (float) getWidth(); float height = utils.getScreenHeight() - utils.getActionBarSize(); radius = (width - utils.dpTopixel(50)) / divOn; Path path = new Path(); path.addCircle((width - utils.dpTopixel(50)) / divOn, (height - utils.dpTopixel(50)) / divOn, radius, Path.Direction.CW); Paint paint = new Paint(); paint.setColor(ContextCompat.getColor(getContext(), color)); paint.setStrokeWidth(5); paint.setStyle(Paint.Style.FILL); paint.setAntiAlias(true); final RectF oval = new RectF(); paint.setStyle(Paint.Style.FILL); left = -(int) radius; right = (int) radius; top = (getHeight() / 2) - (int) radius; bottom = (getHeight() / 2) + (int) radius; oval.set(left, top, right, bottom); canvas.drawArc(oval, start, sweep, true, paint); } else { float width = (float) getWidth(); float height = utils.getScreenHeight() - utils.getActionBarSize(); float radius; radius = (width - utils.dpTopixel(50)) / divOn; Path path = new Path(); path.addCircle((width - utils.dpTopixel(50)) / 1, (width - utils.dpTopixel(50)) / 1, radius, Path.Direction.CW); Paint paint = new Paint(); paint.setColor(ContextCompat.getColor(getContext(), color)); paint.setStrokeWidth(5); paint.setStyle(Paint.Style.FILL); paint.setAntiAlias(true); oval = new RectF(); paint.setStyle(Paint.Style.FILL); TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.SUBPIXEL_TEXT_FLAG); textPaint.setColor(ContextCompat.getColor(getContext(), R.color.white)); textPaint.setTextAlign(Paint.Align.CENTER); /*Paint.FontMetrics metrics = paint.getFontMetrics(); float textheight = Math.abs(metrics.top - metrics.bottom); float x = getWidth() / 2; float y = (getHeight() / 2) + (height / 2); canvas.drawText("My Text", x, y, paint);*/ left = -(int) radius; right = (int) radius; top = (getHeight() / 2) - (int) radius; bottom = (getHeight() / 2) + (int) radius; center_x = width / 2; center_y = utils.getOffset(); int xPos = (getWidth() / 2); int yPos = (int) ((getHeight() / 2) - ((textPaint.descent() + textPaint.ascent()) / 2)) ; //((textPaint.descent() + textPaint.ascent()) / 2) is the distance from the baseline to the center. oval.set(left, top, right, bottom); canvas.drawArc(oval, start, sweep, true, paint); } } } utils class to detect height and width and convert to px i want equation to check i click which circle
Create a slice selected listener, override the onTouchEvent in your custom class, calculate which slice the touch event (with the action ACTION_UP) is on using the x,y values. You can then call the function from your slice selected listener and pass details about the selected slice.
Coordinate values arent stored in processing
I really have no idea why the x and y values wont go to the drawLines function float x, x1, x2; float y, y1, y2; float rad; //radius int lines = 30; //number of lines int colorNumber = 1; void setup() { background(#FFFFFF); size (800, 600); rad = 8; } void draw() { } This creates the three dots or vertices of the mathematical envelope void mouseClicked() { float x = mouseX; float x1 = mouseX; float x2 = mouseX; float y = mouseY; float y1 = mouseY; float y2 = mouseY; if (colorNumber == 1) { fill(#9393ff); ellipse(x, y, rad, rad); } else if (colorNumber == 2) { fill(#FF9393); ellipse(x1, y1, rad, rad); } else if (colorNumber == 3) { fill(#93ff93); ellipse(x2, y2, rad, rad); } } This is supposed to draw the envelope using the coordinates of the vertices void drawLines(int numLines) { for (int i = 0; i < numLines; i = i + 1) { float x = mouseX; float x1 = mouseX; float x2 = mouseX; float y = mouseY; float y1 = mouseY; float y2 = mouseY; float t = (float) i/(numLines-1); float startX = x + t * (x1 - x); float startY = y + t * (y1 - y); float endX = x1 + t * (x2 - x1); float endY = y1 + t * (y2 - y1); line (startX, startY, endX, endY); } } void mouseReleased() { colorNumber++; if (colorNumber == 4) { colorNumber = 1; } println(colorNumber); } void keyPressed() { if (keyPressed == true) { background(#FFFFFF); } } this last stuff just tells the code if you press a key, it will reset the backround
I understand your intention with using mouseX and mouseY to specify the coordinates of one of the 3 points of the envelope on click. The current issue is that all 3 points are being set to the same coordinate with each click. You need to introduce a variable to keep track of which coordinate to set on-click, such that only one pair is set. Then, only once all 3 coordinates are set, drawLines() can be called. I propose the following: Introduce 2 variables, one to keep track of which point is being modified; the other an array of PVectors (just to make it cleaner). int index = 0; PVector[] coords = new PVector[3]; Modify mouseClicked() to include the following: void mouseClicked() { ellipse(mouseX, mouseY, 8, 8); coords[index] = new PVector(mouseX, mouseY); index += 1; if (index == 3) { drawLines(lines); } index %= 3; } drawLines() becomes: void drawLines(int numLines) { for (int i = 0; i < numLines; i = i + 1) { x = coords[0].x; x1 = coords[1].x; x2 = coords[2].x; y = coords[0].y; y1 = coords[1].y; y2 = coords[2].y; float t = (float) i / (numLines - 1); float startX = x + t * (x1 - x); float startY = y + t * (y1 - y); float endX = x1 + t * (x2 - x1); float endY = y1 + t * (y2 - y1); line(startX, startY, endX, endY); } } Finally, since your drawing on a black background, and the default stroke colour is black, use strokeColour() to change the colour of the lines so that you can see the envelope once its drawn.
Java Canvas Rotating Points Along A Circle
I'm trying to make an Asteroids remake using Java and I'm having problems making the player (which is a triangle) rotate when I press a button. I have looked up methods and the math to make the points of the triangle rotate but I haven't figured out how to use the formulas. Your help will be greatly appreciated. Thank You! import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.event.KeyEvent; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import Main.GameObject; import Main.ID; public class Player extends GameObject { public float x1 = 16 + xPos; public float y1 = 0 + yPos; public float x2 = 0 + xPos; public float y2 = 48 + yPos; public float x3 = 32 + xPos; public float y3 = 48 + yPos; public float centerX = xPos + 16; public float centerY = yPos - 24; public Point2D.Float center = new Point2D.Float(xPos + 16, yPos - 24); public Point2D.Float p1 = new Point2D.Float(x1, y1); public Point2D.Float p2 = new Point2D.Float(x2, y2); public Point2D.Float p3 = new Point2D.Float(x3, y3); public Player(float startX, float startY, ID id) { super(startX, startY, id); this.xPos = startX; this.yPos = startY; this.id = id; } public void tick() { } public void render(Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.setColor(Color.WHITE); g2.draw(new Line2D.Float(p1, p2)); g2.draw(new Line2D.Float(p2, p3)); g2.draw(new Line2D.Float(p3, p1)); } public Rectangle getBounds() { return null; } public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); if(key == KeyEvent.VK_RIGHT) { //Rotate triangle here } } }
To rotate a single point around the origin (0,0). float rot = 2.0; // angle in radians Point2D.Float p1 = new Point2D.Float(10.0,10.0); Point2D.Float p1Rotated = new Point2D.Float(0,0); float dx = Math.cos(rot); float dy = Math.sin(rot); p1Rotated.x = p1.x * dx + p1.y * -dy; p1Rotated.y = p1.x * dy + p1.y * dx; So for a triangle that has its center at 0,0 do the above to each point then position the triangle by adding the offset to each point. float rot = 2.0; // angle in radians float x = 100.0; // space ship center at 100,100 float y = 100.0; Point2D.Float p1 = new Point2D.Float(10.0,10.0); Point2D.Float p2 = new Point2D.Float(-10.0,10.0); Point2D.Float p3 = new Point2D.Float(0.0,-10.0); Point2D.Float p1R = new Point2D.Float(0,0); Point2D.Float p2R = new Point2D.Float(0,0); Point2D.Float p3R = new Point2D.Float(0,0); float dx = Math.cos(rot); float dy = Math.sin(rot); p1R.x = p1.x * dx + p1.y * -dy + x; p1R.y = p1.x * dy + p1.y * dx + y; p2R.x = p2.x * dx + p2.y * -dy + x; p2R.y = p2.x * dy + p2.y * dx + y; p3R.x = p3.x * dx + p3.y * -dy + x; p3R.y = p3.x * dy + p3.y * dx + y;
Java Draw Arc Between 2 Points
I'm having trouble drawing the smallest arc described by 3 points: the arc center, an "anchored" end point, and a second point that gives the other end of the arc by determining a radius. I used the law of cosines to determine the length of the arc and tried using atan for the starting degree, but the starting position for the arc is off. I managed to get the arc to lock onto the anchor point (x1,y1) when it's in Quadrant 2, but that will only work when it is in Quadrant 2. Solutions I can see all have a bunch of if-statements to determine the location of the 2 points relative to each other, but I'm curious if I'm overlooking something simple. Any help would be greatly appreciated. SSCCE: import javax.swing.JComponent; import javax.swing.JFrame; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.geom.*; import java.awt.*; import java.util.*; class Canvas extends JComponent { float circleX, circleY, x1, y1, x2, y2, dx, dy, dx2, dy2, radius, radius2; Random random = new Random(); public Canvas() { //Setup. x1 = random.nextInt(250); y1 = random.nextInt(250); //Cant have x2 == circleX while (x1 == 150 || y1 == 150) { x1 = random.nextInt(250); y1 = random.nextInt(250); } circleX = 150; //circle center is always dead center. circleY = 150; //Radius between the 2 points must be equal. dx = Math.abs(circleX-x1); dy = Math.abs(circleY-y1); //c^2 = a^2 + b^2 to solve for the radius radius = (float) Math.sqrt((float)Math.pow(dx, 2) + (float)Math.pow(dy, 2)); //2nd random point x2 = random.nextInt(250); y2 = random.nextInt(250); //I need to push it out to radius length, because the radius is equal for both points. dx2 = Math.abs(circleX-x2); dy2 = Math.abs(circleY-y2); radius2 = (float) Math.sqrt((float)Math.pow(dx2, 2) + (float)Math.pow(dy2, 2)); dx2 *= radius/radius2; dy2 *= radius/radius2; y2 = circleY+dy2; x2 = circleX+dx2; //Radius now equal for both points. } public void paintComponent(Graphics g2) { Graphics2D g = (Graphics2D) g2; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); Arc2D.Float centerPoint = new Arc2D.Float(150-2,150-2,4,4, 0, 360, Arc2D.OPEN); Arc2D.Float point1 = new Arc2D.Float(x1-2, y1-2, 4, 4, 0, 360, Arc2D.OPEN); Arc2D.Float point2 = new Arc2D.Float(x2-2, y2-2, 4, 4, 0, 360, Arc2D.OPEN); //3 points drawn in black g.setColor(Color.BLACK); g.draw(centerPoint); g.draw(point1); g.draw(point2); float start = 0; float distance; //Form a right triangle to find the length of the hypotenuse. distance = (float) Math.sqrt(Math.pow(Math.abs(x2-x1),2) + Math.pow(Math.abs(y2-y1), 2)); //Law of cosines to determine the internal angle between the 2 points. distance = (float) (Math.acos(((radius*radius) + (radius*radius) - (distance*distance)) / (2*radius*radius)) * 180/Math.PI); float deltaY = circleY - y1; float deltaX = circleX - x1; float deltaY2 = circleY - y2; float deltaX2 = circleX - x2; float angleInDegrees = (float) ((float) Math.atan((float) (deltaY / deltaX)) * 180 / Math.PI); float angleInDegrees2 = (float) ((float) Math.atan((float) (deltaY2 / deltaX2)) * 180 / Math.PI); start = angleInDegrees; //Q2 works. if (x1 < circleX) { if (y1 < circleY) { start*=-1; start+=180; } else if (y2 > circleX) { start+=180; start+=distance; } } //System.out.println("Start: " + start); //Arc drawn in blue g.setColor(Color.BLUE); Arc2D.Float arc = new Arc2D.Float(circleX-radius, //Center x circleY-radius, //Center y Rotates around this point. radius*2, radius*2, start, //start degree distance, //distance to travel Arc2D.OPEN); //Type of arc. g.draw(arc); } } public class Angle implements MouseListener { Canvas view; JFrame window; public Angle() { window = new JFrame(); view = new Canvas(); view.addMouseListener(this); window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); window.setBounds(30, 30, 400, 400); window.getContentPane().add(view); window.setVisible(true); } public static void main(String[] a) { new Angle(); } #Override public void mouseClicked(MouseEvent arg0) { window.getContentPane().remove(view); view = new Canvas(); window.getContentPane().add(view); view.addMouseListener(this); view.revalidate(); view.repaint(); } #Override public void mouseEntered(MouseEvent arg0) { // TODO Auto-generated method stub } #Override public void mouseExited(MouseEvent arg0) { // TODO Auto-generated method stub } #Override public void mousePressed(MouseEvent arg0) { // TODO Auto-generated method stub } #Override public void mouseReleased(MouseEvent arg0) { // TODO Auto-generated method stub } }
Perhaps this will help. It tests with click and drag to set the two points rather than random numbers. It's considerably simpler than what you were attempting and other solutions posted so far. Notes: Math.atan2() is a friend in problems like this. Little helper functions make it easier to reason about your code. It's best practice to use instance variables for independent values only and compute the dependent values in local variables. My code fixes some Swing usage problems like calling Swing functions from the main thread. Code follows: import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import javax.swing.*; import javax.swing.event.MouseInputAdapter; class TestCanvas extends JComponent { float x0 = 150f, y0 = 150f; // Arc center. Subscript 0 used for center throughout. float xa = 200f, ya = 150f; // Arc anchor point. Subscript a for anchor. float xd = 150f, yd = 50f; // Point determining arc angle. Subscript d for determiner. // Return the distance from any point to the arc center. float dist0(float x, float y) { return (float)Math.sqrt(sqr(x - x0) + sqr(y - y0)); } // Return polar angle of any point relative to arc center. float angle0(float x, float y) { return (float)Math.toDegrees(Math.atan2(y0 - y, x - x0)); } #Override protected void paintComponent(Graphics g0) { Graphics2D g = (Graphics2D) g0; // Can always draw the center point. dot(g, x0, y0); // Get radii of anchor and det point. float ra = dist0(xa, ya); float rd = dist0(xd, yd); // If either is zero there's nothing else to draw. if (ra == 0 || rd == 0) { return; } // Get the angles from center to points. float aa = angle0(xa, ya); float ad = angle0(xd, yd); // (xb, yb) would work fine, too. // Draw the arc and other dots. g.draw(new Arc2D.Float(x0 - ra, y0 - ra, // box upper left 2 * ra, 2 * ra, // box width and height aa, angleDiff(aa, ad), // angle start, extent Arc2D.OPEN)); dot(g, xa, ya); // Use similar triangles to get the second dot location. float xb = x0 + (xd - x0) * ra / rd; float yb = y0 + (yd - y0) * ra / rd; dot(g, xb, yb); } // Some helper functions. // Draw a small dot with the current color. static void dot(Graphics2D g, float x, float y) { final int rad = 2; g.fill(new Ellipse2D.Float(x - rad, y - rad, 2 * rad, 2 * rad)); } // Return the square of a float. static float sqr(float x) { return x * x; } // Find the angular difference between a and b, -180 <= diff < 180. static float angleDiff(float a, float b) { float d = b - a; while (d >= 180f) { d -= 360f; } while (d < -180f) { d += 360f; } return d; } // Construct a test canvas with mouse handling. TestCanvas() { addMouseListener(mouseListener); addMouseMotionListener(mouseListener); } // Listener changes arc parameters with click and drag. MouseInputAdapter mouseListener = new MouseInputAdapter() { boolean mouseDown = false; // Is left mouse button down? #Override public void mousePressed(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { mouseDown = true; xa = xd = e.getX(); ya = yd = e.getY(); repaint(); } } #Override public void mouseReleased(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { mouseDown = false; } } #Override public void mouseDragged(MouseEvent e) { if (mouseDown) { xd = e.getX(); yd = e.getY(); repaint(); } } }; } public class Test extends JFrame { public Test() { setSize(400, 400); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); getContentPane().add(new TestCanvas()); } public static void main(String[] args) { // Swing code must run in the UI thread, so // must invoke setVisible rather than just calling it. SwingUtilities.invokeLater(new Runnable() { #Override public void run() { new Test().setVisible(true); } }); } }
package curve; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.geom.Ellipse2D; import java.awt.geom.Line2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.imageio.ImageIO; public class Main { /** * #param args the command line arguments */ public static void main(String[] args) throws IOException { PointF pFrom = new PointF(-10f, 30.0f); PointF pTo = new PointF(-100f, 0.0f); List<PointF> points = generateCurve(pFrom, pTo, 100f, 7f, true, true); System.out.println(points); // Calculate the bounds of the curve Rectangle2D.Float bounds = new Rectangle2D.Float(points.get(0).x, points.get(0).y, 0, 0); for (int i = 1; i < points.size(); ++i) { bounds.add(points.get(i).x, points.get(i).y); } bounds.add(pFrom.x, pFrom.y); bounds.add(pTo.x, pTo.y); BufferedImage img = new BufferedImage((int) (bounds.width - bounds.x + 50), (int) (bounds.height - bounds.y + 50), BufferedImage.TYPE_4BYTE_ABGR_PRE); Graphics2D g = img.createGraphics(); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.translate(25.0f - bounds.getX(), 25.0f - bounds.getY()); g.setStroke(new BasicStroke(1.0f)); g.setColor(Color.DARK_GRAY); g.drawLine(-1000, 0, 1000, 0); g.drawLine(0, -1000, 0, 1000); g.setColor(Color.RED); for (int i = 0; i < points.size(); ++i) { if (i > 0) { Line2D.Float f = new Line2D.Float(points.get(i - 1).x, points.get(i - 1).y, points.get(i).x, points.get(i).y); System.out.println("Dist : " + f.getP1().distance(f.getP2())); // g.draw(f); } g.fill(new Ellipse2D.Float(points.get(i).x - 0.8f, points.get(i).y - 0.8f, 1.6f, 1.6f)); } g.setColor(Color.BLUE); g.fill(new Ellipse2D.Float(pFrom.x - 1, pFrom.y - 1, 3, 3)); g.fill(new Ellipse2D.Float(pTo.x - 1, pTo.y - 1, 3, 3)); g.dispose(); ImageIO.write(img, "PNG", new File("result.png")); } static class PointF { public float x, y; public PointF(float x, float y) { this.x = x; this.y = y; } #Override public String toString() { return "(" + x + "," + y + ")"; } } private static List<PointF> generateCurve(PointF pFrom, PointF pTo, float pRadius, float pMinDistance, boolean shortest, boolean side) { List<PointF> pOutPut = new ArrayList<PointF>(); // Calculate the middle of the two given points. PointF mPoint = new PointF(pFrom.x + pTo.x, pFrom.y + pTo.y); mPoint.x /= 2.0f; mPoint.y /= 2.0f; System.out.println("Middle Between From and To = " + mPoint); // Calculate the distance between the two points float xDiff = pTo.x - pFrom.x; float yDiff = pTo.y - pFrom.y; float distance = (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff); System.out.println("Distance between From and To = " + distance); if (pRadius * 2.0f < distance) { throw new IllegalArgumentException("The radius is too small! The given points wont fall on the circle."); } // Calculate the middle of the expected curve. float factor = (float) Math.sqrt((pRadius * pRadius) / ((pTo.x - pFrom.x) * (pTo.x - pFrom.x) + (pTo.y - pFrom.y) * (pTo.y - pFrom.y)) - 0.25f); PointF circleMiddlePoint = new PointF(0, 0); if (side) { circleMiddlePoint.x = 0.5f * (pFrom.x + pTo.x) + factor * (pTo.y - pFrom.y); circleMiddlePoint.y = 0.5f * (pFrom.y + pTo.y) + factor * (pFrom.x - pTo.x); } else { circleMiddlePoint.x = 0.5f * (pFrom.x + pTo.x) - factor * (pTo.y - pFrom.y); circleMiddlePoint.y = 0.5f * (pFrom.y + pTo.y) - factor * (pFrom.x - pTo.x); } System.out.println("Middle = " + circleMiddlePoint); // Calculate the two reference angles float angle1 = (float) Math.atan2(pFrom.y - circleMiddlePoint.y, pFrom.x - circleMiddlePoint.x); float angle2 = (float) Math.atan2(pTo.y - circleMiddlePoint.y, pTo.x - circleMiddlePoint.x); // Calculate the step. float step = pMinDistance / pRadius; System.out.println("Step = " + step); // Swap them if needed if (angle1 > angle2) { float temp = angle1; angle1 = angle2; angle2 = temp; } boolean flipped = false; if (!shortest) { if (angle2 - angle1 < Math.PI) { float temp = angle1; angle1 = angle2; angle2 = temp; angle2 += Math.PI * 2.0f; flipped = true; } } for (float f = angle1; f < angle2; f += step) { PointF p = new PointF((float) Math.cos(f) * pRadius + circleMiddlePoint.x, (float) Math.sin(f) * pRadius + circleMiddlePoint.y); pOutPut.add(p); } if (flipped ^ side) { pOutPut.add(pFrom); } else { pOutPut.add(pTo); } return pOutPut; } } and the use the generateCurve method like this to have a curve between the from and to points.. generateCurve(pFrom, pTo, 100f, 7f, true, false);
Okay, here it is, testing and working. The problems were based on the fact that I don't use graphics much, so I have to remind myself that the coordinate systems are backward, and on the fact that the Javadoc description of the Arc2D constructor is atrocious. In addition to these, I found that your point creation (for the two points to be connected) was extremely inefficient given the requirements. I had assumed you actually had to receive two arbitrary points and then calculate their angles, etc., but based on what you put on Pastebin, we can define the two points however we please. This benefits us. Anyway, here's a working version, with none of that gobbledegook from before. Simplified code is simplified: import javax.swing.JComponent; import java.awt.geom.*; import java.awt.*; import java.util.*; public class Canvas extends JComponent { double circleX, circleY, x1, y1, x2, y2, dx, dy, dx2, dy2, radius, radius2; Random random = new Random(); double distance; private static double theta1; private static double theta2; private static double theta; // private static double radius; private Point2D point1; private Point2D point2; private Point2D center; private static int direction; private static final int CW = -1; private static final int CCW = 1; public Canvas() { /* * You want two random points on a circle, so let's start correctly, * by setting a random *radius*, and then two random *angles*. * * This has the added benefit of giving us the angles without having to calculate them */ radius = random.nextInt(175); //your maximum radius is higher, but we only have 200 pixels in each cardinal direction theta1 = random.nextInt(360); //angle to first point (absolute measurement) theta2 = random.nextInt(360); //angle to second point //build the points center = new Point2D.Double(200, 200); //your frame is actually 400 pixels on a side point1 = new Point2D.Double(radius * Math.cos(toRadians(theta1)) + center.getX(), center.getY() - radius * Math.sin(toRadians(theta1))); point2 = new Point2D.Double(radius * Math.cos(toRadians(theta2)) + center.getX(), center.getY() - radius * Math.sin(toRadians(theta2))); theta = Math.abs(theta1 - theta2) <= 180 ? Math.abs(theta1 - theta2) : 360 - (Math.abs(theta1 - theta2)); if ((theta1 + theta) % 360 == theta2) { direction = CCW; } else { direction = CW; } System.out.println("theta1: " + theta1 + "; theta2: " + theta2 + "; theta: " + theta + "; direction: " + (direction == CCW ? "CCW" : "CW")); System.out.println("point1: (" + (point1.getX() - center.getX()) + ", " + (center.getY() - point1.getY()) + ")"); System.out.println("point2: (" + (point2.getX() - center.getX()) + ", " + (center.getY() - point2.getY()) + ")"); // Radius now equal for both points. } public double toRadians(double angle) { return angle * Math.PI / 180; } public double toDegrees(double angle) { return angle * 180 / Math.PI; } public void paintComponent(Graphics g2) { Graphics2D g = (Graphics2D) g2; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); //centerpoint should be based on the actual center point Arc2D.Double centerPoint = new Arc2D.Double(center.getX() - 2, center.getY() - 2, 4, 4, 0, 360, Arc2D.OPEN); //likewise these points Arc2D.Double point11 = new Arc2D.Double(point1.getX() - 2, point1.getY() - 2, 4, 4, 0, 360, Arc2D.OPEN); Arc2D.Double point22 = new Arc2D.Double(point2.getX() - 2, point2.getY() - 2, 4, 4, 0, 360, Arc2D.OPEN); // 3 points drawn in black g.setColor(Color.BLACK); g.draw(centerPoint); g.draw(point11); g.draw(point22); // Arc drawn in blue g.setColor(Color.BLUE); g.draw(new Arc2D.Double(center.getX() - radius, center.getY() - radius, 2 * radius, 2 * radius, theta1, theta * direction, Arc2D.OPEN)); } }