I'm working on a java applet where i want move or rotate a wheel..so I take a circle and 3 lines which are placed at 60 degrees to each other on the circle...and the length of the lines are the diameter of the circle..
So I use a Line constructor which has initial positions of the lines and a degree with respect to x-axis i.e. after a certain time (here 1sec) it will rotate 5 degree.
This code works fine for line1, which is horizontal to x-axis..but cant work for line2 and line3..need some suggestion
import java.awt.*;
import java.applet.*;
import java.lang.*;
class Line
{
int x1, x2, y1, y2, xChange=0, yChange=0;
double degree;
Line(int x1, int y1, int x2, int y2, double degree)
{
this.x1=x1;
this.y1=y1;
this.x2=x2;
this.y2=y2;
this.degree=degree;
}
public void rotate(float radius)
{
degree+=5;
double rads = Math.toRadians(degree);
x1+=5;
x2+=5;
if(degree==0)
degree=360;
xChange=(int) (radius - ((Math.cos(rads)) * radius));
yChange=(int) ((Math.sin(rads)) * radius);
}
}
public class Wheeler extends Applet implements Runnable
{
Thread t1;
int xCircle=40, yCircle=100, diameter=100;
Line line1, line2, line3;
int degree;
float radius=diameter/2;
public void init()
{
xCircle=40; yCircle=100; diameter=100;
line1=new Line(xCircle, yCircle+(diameter/2), xCircle+diameter, yCircle+(diameter/2), 0);
line2=new Line(xCircle+(int) (radius - ((Math.cos(Math.toRadians(60)) * radius))), yCircle+(diameter/2)+(int) ((Math.sin(Math.toRadians(60)) * radius)), xCircle+diameter-(int) (radius - ((Math.cos(Math.toRadians(60)) * radius))), yCircle+(diameter/2)-(int) ((Math.sin(Math.toRadians(60)) * radius)), 60);
line3=new Line(xCircle+(int) (radius - ((Math.cos(Math.toRadians(120)) * radius))), yCircle+(diameter/2)+(int) ((Math.sin(Math.toRadians(120)) * radius)), xCircle+diameter-(int) (radius - ((Math.cos(Math.toRadians(120)) * radius))), yCircle+(diameter/2)-(int) ((Math.sin(Math.toRadians(120)) * radius)), 120);
t1 = new Thread(this);
t1.start();
}
public void run ()
{
while(true)
{
try
{
repaint();
Thread.sleep(1000);
line1.rotate(radius);
line2.rotate(radius);
line3.rotate(radius);
xCircle+=5;
if(xCircle==1000)
{
xCircle=40;
line1.x1=xCircle;
line1.x2=xCircle+diameter;
line2.x1=xCircle+(int) (radius - ((Math.cos(Math.toRadians(60))) * radius));
line2.x2=xCircle+diameter-(int) (radius - ((Math.cos(Math.toRadians(60)) * radius)));
line3.x1=xCircle+(int) (radius - ((Math.cos(Math.toRadians(120)) * radius)));
line3.x2=xCircle+diameter-(int) (radius - ((Math.cos(Math.toRadians(120)) * radius)));
}
}
catch(InterruptedException e) {}
}
}
public void paint(Graphics g)
{
g.setColor(Color.GREEN);
g.drawOval(xCircle, yCircle, diameter, diameter);
g.setColor(Color.RED);
g.drawLine(line1.x1+line1.xChange, line1.y1-line1.yChange, line1.x2-line1.xChange, line1.y2+line1.yChange);
g.setColor(Color.RED);
g.drawLine(line2.x1+line2.xChange, line2.y1-line2.yChange, line2.x2-line2.xChange, line2.y2+line2.yChange);
g.setColor(Color.RED);
g.drawLine(line3.x1+line3.xChange, line3.y1-line3.yChange, line3.x2-line3.xChange, line3.y2+line3.yChange);
}
}
Related
As you can see on the image, I have a p1 and p2 objects with (x,y) coordinates which I know the values, and I know radius of all these circle objects.
However, I want to calculate new position x,y which would be p3 center point. Basically, as you can see it's p2 position + radius.
I am doing this for java game which is based on libgdx. I would appreciate any math or java language directions/examples.
See code comments for explanation.
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import javax.swing.*;
class CenteredCircle extends Ellipse2D.Double {
CenteredCircle(Point2D.Double p, double radius) {
super(p.x - radius, p.y - radius, 2 * radius, 2 * radius);
}
}
public class CircleDemo extends JFrame {
public CircleDemo() {
int width = 640; int height = 480;
setSize(new Dimension(width, height));
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
JPanel p = new JPanel() {
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
// center p1
Point2D.Double p1 = new Point2D.Double(getSize().width/2, getSize().height/2);
double radius = 130.0;
// big circle
Shape circle2 = new CenteredCircle(p1, radius);
g2d.draw(circle2);
// 12 small circles
for (int angle = 0; angle < 360; angle += 30) {
// this is the magic part
// a polar co-ordinate has a length and an angle
// by changing the angle we rotate
// the transformed co-ordinate is the center of the small circle
Point2D.Double newCenter = polarToCartesian(radius, angle);
// draw line just for visualization
Line2D line = new Line2D.Double(p1.x, p1.y, p1.x + newCenter.x, p1.y+ newCenter.y);
g2d.draw(line);
// draw the small circle
Shape circle = new CenteredCircle(
new Point2D.Double(p1.x + newCenter.x, p1.y + newCenter.y),
radius/4);
g2d.draw(circle);
}
}
};
setTitle("Circle Demo");
getContentPane().add(p);
}
public static void main(String arg[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new CircleDemo();
}
});
}
static Point2D.Double polarToCartesian(double r, double theta) {
theta = (theta * Math.PI) / 180.0; // multiply first, then divide to keep error small
return new Point2D.Double(r * Math.cos(theta), r * Math.sin(theta));
}
// not needed, just for completeness
public static Point2D.Double cartesianToPolar(double x, double y) {
return new Point2D.Double(Math.sqrt(x * x + y * y), (Math.atan2(y, x) * 180) / Math.PI);
}
}
Now using libgdx for the graphics. Thus no need for polar co-ordinates, on the outside.
I am not doing frame rate relative animation. Therefore, this is no perfect match to your code.
Using the following calculation (if (theta >= 360) { theta = 0.0f; }) at the end of the render method will let the animation restart with its original value.
package org.demo;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.ScreenUtils;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
public class CircleDemo extends ApplicationAdapter {
ShapeRenderer shapeRenderer;
float theta = 0.0f;
#Override
public void create () {
shapeRenderer = new ShapeRenderer();
}
#Override
public void render () {
ScreenUtils.clear(0, 0.4f, 0.4f, 1);
Vector2 p1 = new Vector2( Gdx.graphics.getWidth() / 2.0f , Gdx.graphics.getHeight() / 2.0f);
Vector2 smallCircleCenter = new Vector2(150.0f, 0.0f);
smallCircleCenter.add(p1); // translate center by p1
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
// static lines and circles
for (int angle = 0; angle < 360; angle += 30) {
Vector2 lineEnd = new Vector2(smallCircleCenter);
lineEnd.rotateAroundDeg(p1, angle);
shapeRenderer.line(p1, lineEnd);
shapeRenderer.circle(lineEnd.x, lineEnd.y, 20);
}
// animated line and circle in red
shapeRenderer.setColor(0.75f, 0, 0, 1);
Vector2 movingCircleCenter = new Vector2(smallCircleCenter);
movingCircleCenter.rotateAroundDeg(p1, theta);
shapeRenderer.line(p1, movingCircleCenter);
shapeRenderer.circle(movingCircleCenter.x, movingCircleCenter.y, 20);
shapeRenderer.setColor(1, 1, 1, 1);
shapeRenderer.end();
theta++;
// for the screenshot stop at 90 degrees
if (theta >= 90) {
theta = 90.0f;
}
}
#Override
public void dispose () {
shapeRenderer.dispose();
}
}
So I wrote a test in my project, based on your approach:
package com.bigbang.test.impl;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.bigbang.Game;
import com.bigbang.graphics.g2d.shapes.impl.Ellipse;
import com.bigbang.graphics.g2d.shapes.impl.Line;
import com.bigbang.graphics.gl.Color;
import com.bigbang.math.BBMath;
public class PolarToCartesianTest extends AbstractTest {
private Array<GraphicalObject> graphicalObjectArray;
private GraphicalObject dynamicGraphicalObject;
private float radius, smallCircleRadius;
private float centerX, centerY;
public PolarToCartesianTest(Game game) {
super(game);
}
#Override
public void create() {
radius = 200f;
centerX = game.getScreenController().getScreenWidth() / 2;
centerY = game.getScreenController().getScreenHeight() / 2;
smallCircleRadius = radius / 4;
graphicalObjectArray = new Array<>();
for (int angle = 0; angle < 360; angle += 30) {
GraphicalObject graphicalObject = new GraphicalObject();
graphicalObject.angle = angle;
graphicalObjectArray.add(graphicalObject);
}
dynamicGraphicalObject = new GraphicalObject();
game.getCameraController().getCamera().position.x = game.getScreenController().getScreenWidth() / 2;
game.getCameraController().getCamera().position.y = game.getScreenController().getScreenHeight() / 2;
}
#Override
public void update(float deltaTime) {
for (GraphicalObject graphicalObject : graphicalObjectArray) {
Vector2 polarToCartesianPosition = BBMath.polarToCartesian(radius, graphicalObject.angle);
graphicalObject.line.x1 = centerX + 0;
graphicalObject.line.y1 = centerY + 0;
graphicalObject.line.x2 = centerX + polarToCartesianPosition.x;
graphicalObject.line.y2 = centerY + polarToCartesianPosition.y;
graphicalObject.line.color = Color.WHITE_COLOR;
graphicalObject.ellipse.x = centerX + polarToCartesianPosition.x;
graphicalObject.ellipse.y = centerY + polarToCartesianPosition.y;
graphicalObject.ellipse.width = 2 * smallCircleRadius;
graphicalObject.ellipse.height = 2 * smallCircleRadius;
graphicalObject.ellipse.color = Color.WHITE_COLOR;
}
float shift = 0;
float theta = (shift * smallCircleRadius) * (centerY / centerX);
Vector2 pos = BBMath.polarToCartesian(radius, theta);
dynamicGraphicalObject.line.color = new Color(Color.RED);
dynamicGraphicalObject.line.x1 = centerX + 0;
dynamicGraphicalObject.line.y1 = centerY + 0;
dynamicGraphicalObject.line.x2 = centerX + pos.x;
dynamicGraphicalObject.line.y2 = centerY + pos.y;
dynamicGraphicalObject.ellipse.x = centerX + pos.x;
dynamicGraphicalObject.ellipse.y = centerY + pos.y;
dynamicGraphicalObject.ellipse.width = 2 * smallCircleRadius;
dynamicGraphicalObject.ellipse.height = 2 * smallCircleRadius;
dynamicGraphicalObject.ellipse.color = new Color(Color.RED);
}
#Override
public void draw() {
game.getShapeRenderer().begin(ShapeRenderer.ShapeType.Line);
for (GraphicalObject graphicalObject : graphicalObjectArray) {
graphicalObject.line.draw();
graphicalObject.ellipse.draw();
}
dynamicGraphicalObject.line.draw();
dynamicGraphicalObject.ellipse.draw();
game.getShapeRenderer().end();
}
class GraphicalObject {
Ellipse ellipse;
Line line;
float angle;
public GraphicalObject() {
this.ellipse = new Ellipse(game);
this.line = new Line(game);
}
}
}
Which is same math like in your example, with some modifications:
However, you can notice I have this dynamicGraphicalObject (red circle), which I want to shift position around circle by using theta value calculated as (shift * smallCircleRadius) * (centerY / centerX);. This works perfect for shift=0 value. It's properly positioned/overlapping white. But if I would change shift variable to 1, 2, 3, or 11, you can see that it's not precisely aligned with white circles. Is this floating point issue or am I missing something in calculation of theta ?
shift values used: 2,6 and 11 in order by images
--
SOLUTION:
float fixPrecision = 1.1f;
float theta = (shift * fixPrecision) + ((shift * smallCircleRadius) * (centerY / centerX));
How can I make this tree's angle and depth to be random by using 'random number'?
In the below code JFrame is been used. The intent behind the question is to get the idea of randomizing the angles and the depth, which is passed in the paint method.
public class DrawTreeFrame extends JFrame {
public DrawTreeFrame() {
setSize(800, 700);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
private void drawTree(Graphics g, int x1, int y1, double angle, int depth) {
if(depth==0)
return;
int x2 = x1 + (int) (Math.cos(Math.toRadians(angle)) * depth * 10.0);
int y2 = y1 + (int) (Math.sin(Math.toRadians(angle)) * depth * 10.0);
g.drawLine(x1, y1, x2, y2);
drawTree(g, x2, y2, angle-20, depth-1);
drawTree(g, x2, y2, angle+20, depth-1);
}
#Override
public void paint(Graphics g) {
g.setColor(Color.BLACK);
drawTree(g, 400, 600, -90, 10);
}
public static void main(String[] args) {
new DrawTreeFrame();
}
}
You can use Math.random(). The following code method will give you random numbers in a range;
public int getRandomNumber(int min, int max) {
return (int) ((Math.random() * (max - min)) + min);
}
Ultimately your code should look like this:-
class DrawTreeFrame extends JFrame {
public DrawTreeFrame() {
setSize(800, 700);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
private void drawTree(Graphics g, int x1, int y1, double angle, int depth) {
if(depth==0)
return;
int x2 = x1 + (int) (Math.cos(Math.toRadians(angle)) * depth * 10.0);
int y2 = y1 + (int) (Math.sin(Math.toRadians(angle)) * depth * 10.0);
g.drawLine(x1, y1, x2, y2);
drawTree(g, x2, y2, angle-20, depth-1);
drawTree(g, x2, y2, angle+20, depth-1);
}
#Override
public void paint(Graphics g) {
g.setColor(Color.BLACK);
int x1 = getRandomNumber(100, 400);
int y1 = getRandomNumber(400, 800);
double angle = getRandomNumber(-10, -100);
int depth = getRandomNumber(5, 20);
drawTree(g, x1, y1, angle, depth);
}
public int getRandomNumber(int min, int max) {
return (int) ((Math.random() * (max - min)) + min);
}
public static void main(String[] args) {
new DrawTreeFrame();
}
}
I found a nice code on the web for drawing a binary tree. I created the binary search tree algorithm for it, now if I pass the root node for the function, it draws out my tree. I want to make it draw step by step, by running it in a thread, and making it sleep after inserting an element. Now the problem is, I guess, the JPanel itself won't be returned until the drawing is complete, so even if it runs on a different thread, it won't be added to my split pane ( I have many sorting algorithms implemented, and already being painted, and all can be accessed from the same tabbed pane. Every tabbed pane contains a split pane and every split pane contains the controlling buttons, and a jpanel, where I draw the algorithms, but the others do not extend from JPanel! ). Could you please help me find a way to run this in a totally different thread then the main program, and be able to see how it draws the tree?
Please do not judge me on this code, I just put it together fast, trying to make it work, this is not the way I code.
public class BinaryTree extends JPanel implements Runnable {
private int radius = 20;
private int vGap = 45, hGap = 150;
private int x = 350, y = 25;
private GraphNode root;
private BinarySearchTreeSort binaryTreeSort;
private int latency = 270;
private boolean isManual = false;
//Stores the sorting algorithm's instance
public BinaryTree(BinarySearchTreeSort binaryTreeSort) {
this.binaryTreeSort = binaryTreeSort;
}
#Override
public void run() {
//Maybe i should put here the paintComponent somehow???
root = binaryTreeSort.binaryTreeSort();
}
#Override
public void paintComponent(final Graphics g) {
displayTree(g, root, x, y, hGap);
}
private void displayTree(Graphics g, GraphNode root, int x, int y, int hGap) {
// Display the root
while (!isStarted) {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Logger.getLogger(BinaryTreeFrame.class.getName()).log(Level.SEVERE, null, ex);
}
}
g.drawOval(x - radius, y - radius, 2 * radius, 2 * radius);
if (root.getValue() != 0) {
g.drawString(root.getValue() + "", x - 13, y + 4);
}
sleep();
if (root.getLeftChild() != null) {
// Draw a line to the left node
g.setColor(Color.black);
leftChild(g, x - hGap, y + vGap, x, y);
//Draw the left subtree
displayTree(g, root.getLeftChild(), x - hGap, y + vGap, hGap / 2);
}
if (root.getRightChild() != null) {
// Draw a line to the right node
g.setColor(Color.black);
rightChild(g, x + hGap, y + vGap, x, y);
// Draw the right subtree recursively
displayTree(g, root.getRightChild(), x + hGap, y + vGap, hGap / 2);
}
}
/* Draw left child */
private void leftChild(Graphics g, int x1, int y1, int x2, int y2) {
double d = Math.sqrt(vGap * vGap + (x2 - x1) * (x2 - x1));
int x11 = (int) (x1 + radius * (x2 - x1) / d);
int y11 = (int) (y1 - radius * vGap / d);
int x21 = (int) (x2 - radius * (x2 - x1) / d);
int y21 = (int) (y2 + radius * vGap / d);
g.drawLine(x11, y11, x21, y21);
//g.drawString("0", ((x21 + x11) - 20) / 2, (y21 + y11) / 2);
}
/*Draw right child */
private void rightChild(Graphics g, int x1, int y1, int x2, int y2) {
double d = Math.sqrt(vGap * vGap + (x2 - x1) * (x2 - x1));
int x11 = (int) (x1 - radius * (x1 - x2) / d);
int y11 = (int) (y1 - radius * vGap / d);
int x21 = (int) (x2 + radius * (x1 - x2) / d);
int y21 = (int) (y2 + radius * vGap / d);
g.drawLine(x11, y11, x21, y21);
//g.drawString("1", (x21 + x11) / 2, (y21 + y11) / 2);
}
public void sleep() {
if (!isManual) {
try {
Thread.sleep(latency);
} catch (InterruptedException ex) {
Logger.getLogger(BinaryTree.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
I'm trying to draw to filled circles, centered at random locations, with a line connecting the circles. The distance between the to centers is displayed on the line and whenever your resize the frame, the circles are redisplayed in new random locations.
I'm stuck in how to display the distance?
Any help is appreciated and thx for advance.
This's the code (what i managed to do):
public class Test extends JFrame {
public Test() {
add(new LineConnectingTheTwoCircles());
}
// Panel class
class LineConnectingTheTwoCircles extends JPanel {
//Default constructor
LineConnectingTheTwoCircles() {
}
/* Override paintComponent (getting access to the panel's Graphics
class) */
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int radius = 15;
// getting coordinates of circle 1
int x1 = (int) (Math.random() * (getWidth()));
int y1 = (int) (Math.random() * (getHeight()));
// getting coordinates of circle 2
int x2 = (int) (Math.random() * (getWidth()));
int y2 = (int) (Math.random() * (getWidth()));
// Setting color and drawing a filled circles (1 & 2)
g.setColor(Color.BLUE);
g.fillOval(x1 - radius, y1 - radius, 2 * radius, 2 * radius);
g.drawString("1", x1 - 25, y1);
g.setColor(Color.RED);
g.fillOval(x2 - radius, y2 - radius, 2 * radius, 2 * radius);
g.drawString("2", x2 - 25, y2);
connectingTheTwoCircles(g, x1, y1, x2, y2);
}
// Connecting the two circles from the center
private void connectingTheTwoCircles(Graphics g, int x1, int y1,
int x2, int y2) {
//Distance between the circles centered
double D = Math.sqrt((Math.pow((y2 - y1), 2))
+ (Math.pow((x2 - x1), 2)));
//Getting the coordinates for the line l
int x11 = x1;
int y11 = y1;
int x21 = x2;
int y21 = y2;
g.setColor(Color.BLACK);
g.drawLine(x11, y11, x21, y21);
}
public double getDistance(double x1, double y1, double x2, double y2) {
return Math.sqrt((Math.pow((y2 - y1), 2))
+ (Math.pow((x2 - x1), 2)));
}
}
public static void main(String[] args) {
// Frame declaration
Test frame = new Test();
/*
* Invoking some methods, to set a title on the title bar, to specifier
* the size of the frame to centre it on the screen, to tell the program
* to terminate when the frame is closed and finally to display it
*/
frame.setTitle("This is a test");
frame.setSize(300, 300);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Try next code draws distance at line center
double distance = getDistance(x11, y11, x21, y21);
g.drawString(distance+" ",
x11> x21 ? x21 + (x11-x21)/2 : x11 + (x21 - x11)/2 ,
y11> y21 ? y21 + (y11-y21)/2 : y11 + (y21 - y11)/2 );
Can anyone guide me how to code the arrow line in different direction.
wa and wl is positive the rectangle will be on top of the x-axis. Below example shown if wl is negative and wa is positive. The code below shown how i code the rectangle shape. x1 is the varaible to state where to start at x axis. e1 is the length of the shape, wa1 and wl1 is the height. wsign to determine the height wa1 or wl1 should display at negative side or positive side.
if (Math.abs(wl1) > Math.abs(wa1)) {
y_scale = (load_y0 - 40) / (double) Math.abs(wl1);
} else {
y_scale = (load_y0 - 40) / (double) Math.abs(wa1);
}
g.drawLine((int) ((double) x0 + x1 * x_scale), (int) (load_y),
(int) ((double) x0 + x1 * x_scale),
(int) (load_y + (wa1 * y_scale) * -1));
g.drawLine((int) ((double) x0 + (x1 + e1) * x_scale),
(int) (load_y), (int) ((double) x0 + (x1 + e1)
* x_scale), (int) (load_y + (wl1 * y_scale)
* -1));
g.drawLine((int) ((double) x0 + x1 * x_scale),
(int) (load_y + (wa1 * y_scale * -1)),
(int) ((double) x0 + (x1 + e1) * x_scale),
(int) (load_y + (wl1 * y_scale) * -1));
Here is a simple routine (adopted from here) for drawing arbitrary arrows:
import static java.awt.geom.AffineTransform.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import javax.swing.*;
public class Main {
public static void main(String args[]) {
JFrame t = new JFrame();
t.add(new JComponent() {
private final int ARR_SIZE = 4;
void drawArrow(Graphics g1, int x1, int y1, int x2, int y2) {
Graphics2D g = (Graphics2D) g1.create();
double dx = x2 - x1, dy = y2 - y1;
double angle = Math.atan2(dy, dx);
int len = (int) Math.sqrt(dx*dx + dy*dy);
AffineTransform at = AffineTransform.getTranslateInstance(x1, y1);
at.concatenate(AffineTransform.getRotateInstance(angle));
g.transform(at);
// Draw horizontal arrow starting in (0, 0)
g.drawLine(0, 0, len, 0);
g.fillPolygon(new int[] {len, len-ARR_SIZE, len-ARR_SIZE, len},
new int[] {0, -ARR_SIZE, ARR_SIZE, 0}, 4);
}
public void paintComponent(Graphics g) {
for (int x = 15; x < 200; x += 16)
drawArrow(g, x, x, x, 150);
drawArrow(g, 30, 300, 300, 190);
}
});
t.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
t.setSize(400, 400);
t.setVisible(true);
}
}
Result:
Simple arrow sample
/**
* #param fromPoint end of the arrow
* #param rotationDeg rotation angle of line
* #param length arrow length
* #param wingsAngleDeg wingspan of arrow
* #return Path2D arrow shape
*/
public static Path2D createArrowForLine(
Point2D fromPoint,
double rotationDeg,
double length,
double wingsAngleDeg) {
double ax = fromPoint.getX();
double ay = fromPoint.getY();
double radB = Math.toRadians(-rotationDeg + wingsAngleDeg);
double radC = Math.toRadians(-rotationDeg - wingsAngleDeg);
Path2D resultPath = new Path2D.Double();
resultPath.moveTo(length * Math.cos(radB) + ax, length * Math.sin(radB) + ay);
resultPath.lineTo(ax, ay);
resultPath.lineTo(length * Math.cos(radC) + ax, length * Math.sin(radC) + ay);
return resultPath;
}