How to resize a SVG element composed of multiple paths in JavaFX? - svg

I'm remaking the design of an old javafx app and I need to include an icon for the wifi strength. The designer sent me an svg icon for that, and I thought it was smarter to just keep one icon and use different fill colors for the 3 bars depending of the said wifi strength.
I found a nice approach for that, and it works quite well until you need to resize the wifi icon. It seems like even if I change the -fx-pref-width (or height, or min/max) the svg icon keeps its size.
I also tried to resize each svg path one by one but it only goes messy with the spaces between them. And using a unique shape to resize later is not an option, as I need at least 2 colors. FYI the goal is to apply a 5em or 3em size depending of the context.
Here's the code I currently have, where everything looks great if you don't matter the icon size :
public class WifiStrengthRegion extends Pane {
public WifiStrengthRegion() {
getStyleClass().setAll("wifi-strength");
SVGPath round = new SVGPath();
SVGPath bar1 = new SVGPath();
SVGPath bar2 = new SVGPath();
SVGPath bar3 = new SVGPath();
round.setContent("M253.5,336.5c-18.9,0-34.2,15.3-34.2,34.1c0,18.8,15.4,34.1,34.2,34.1c18.9,0,34.2-15.3,34.2-34.1 C287.7,351.8,272.4,336.5,253.5,336.5z");
bar1.setContent("M337,290.1c-22.3-22.3-51.9-34.5-83.5-34.5c-31.4,0-61,12.2-83.3,34.3c-9,9-9,23.5,0,32.5 c4.4,4.4,10.2,6.8,16.3,6.8c6.2,0,11.9-2.4,16.3-6.7c13.6-13.5,31.6-20.9,50.7-20.9c19.2,0,37.3,7.5,50.8,21 c4.4,4.4,10.2,6.8,16.3,6.8c6.2,0,11.9-2.4,16.3-6.7C346,313.6,346,299,337,290.1z");
bar2.setContent("M389.3,238c-36.3-36.2-84.5-56.1-135.8-56.1c-51.2,0-99.3,19.9-135.6,55.9c-4.4,4.3-6.8,10.1-6.8,16.3 c0,6.1,2.4,11.9,6.7,16.3c4.4,4.3,10.2,6.7,16.3,6.7c6.2,0,11.9-2.4,16.3-6.7c27.5-27.4,64.1-42.5,103-42.5 c39,0,75.6,15.1,103.1,42.6c4.4,4.4,10.2,6.8,16.3,6.8c6.2,0,12-2.4,16.3-6.7c4.4-4.3,6.8-10.1,6.8-16.3 C396,248.1,393.6,242.3,389.3,238z");
bar3.setContent("M444.3,183.2c-50.9-50.8-118.7-78.8-190.8-78.8c-72,0-139.7,27.9-190.6,78.6c-9,9-9,23.5,0,32.5 c4.4,4.3,10.2,6.7,16.3,6.7c6.2,0,12-2.4,16.3-6.7c42.2-42,98.3-65.2,158-65.2c59.7,0,115.9,23.2,158.1,65.3 c4.4,4.3,10.2,6.7,16.3,6.7c6.2,0,11.9-2.4,16.3-6.7C453.2,206.7,453.3,192.1,444.3,183.2z");
round.getStyleClass().add("wifi-base");
bar1.getStyleClass().add("wifi-bar1");
bar2.getStyleClass().add("wifi-bar2");
bar3.getStyleClass().add("wifi-bar3");
this.getChildren().addAll(round, bar1, bar2, bar3);
}
public void setWifiStrength(Integer strength) {
if (strength == null) {
setManaged(false);
setVisible(false);
} else {
setManaged(true);
setVisible(true);
getStyleClass().removeAll("wifi-excellent", "wifi-good", "wifi-fair", "wifi-weak", "wifi-off");
if (strength < 0 && strength >= -100) {
if (strength >= -50) {
getStyleClass().add("wifi-excellent");
} else if (strength >= -70) {
getStyleClass().add("wifi-good");
} else if (strength >= -80) {
getStyleClass().add("wifi-fair");
} else {
getStyleClass().add("wifi-weak");
}
} else {
getStyleClass().add("wifi-off");
}
}
}
}
(and then a css stylesheet applies -fx-fill to each .wifi-barX depending of the main element class)
And here is an example of how the svg icon looks like:
I'm a very beginner in Java (and obv JavaFX), so any constructive criticism will be appreciated!

If you want to resize only (I hope this works):
public void resize(SVGPath svg, double width, double height) {
this.width = width;
this.height = height;
double originalWidthR = svg.prefWidth(-1);
double originalHeightR = svg.prefHeight(originalWidthR);
double scaleXr = width / originalWidthR;
double scaleYr = height / originalHeightR;
svg.setScaleX(scaleXr);
svg.setScaleY(scaleYr);
}
And if you want to set bounds use this:
public void setBounds(SVGPath svg, double width, double height, double x, double y) {
this.width = width;
this.height = height;
double originalWidthR = svg.prefWidth(-1);
double originalHeightR = svg.prefHeight(originalWidthR);
double scaleXr = width / originalWidthR;
double scaleYr = height / originalHeightR;
svg.setScaleX(scaleXr);
svg.setScaleY(scaleYr);
svg.setLayoutX((originalWidthR - width) + x);
svg.setLayoutY((originalHeightR - height) + y);
}

Related

Codenameone : Custom Component doesn't get right height

I was trying to print rectangle in Codenameone.
fun showCustomForm() {
val hi = Form("", BorderLayout())
hi.add(BorderLayout.CENTER, getGreenLine())
hi.show()
}
fun getGreenLine(): Component {
return object : Component() {
override fun paint(g: Graphics) {
println("Graphics Printing starts")
g.color = 0x00ff00
g.fillRect(x, y, width, height)
}
override fun calcPreferredSize(): Dimension {
return Dimension(1, 20)
}
}
}
As showing above, the rectangle is supposed to have a width of 1 and height of 20
The height seems to be correct but the width goes across the screen.
What is the right way to display the rectangle with the correct dimension?
I've never used Kotlin, however... in this example, try to replace BorderLayout() with BorderLayout(BorderLayout.CENTER_BEHAVIOR_CENTER) to give to the component its preferred size.
In general, the layout managers can or cannot use the preferred size, see: https://www.codenameone.com/manual/basics.html
For example, FlowLayout always gives a component its preferred size; BoxLayout.y() always gives a component its preferred height, but using the maximum available width; etc.

Placing canvas inside Div element

I am trying to create a circle in itext 7 and then place this circle anywhere in I need to in the document.
The document is laid out using divs and I have managed to create the circle using a PdfCanvas.
Below is a snippet of what I am trying to achieve and there may well be a better way to do this:
PdfDocument pdfDoc = new PdfDocument(writer);
Document doc = new Document(pdfDoc);
PdfPage pdfPage = pdfDoc.addNewPage();
Div div = new Div();
div.setBackgroundColor(Color.CYAN);
div.setHeight(10.0F);
div.add(new Paragraph(" ").setFont(PdfFontFactory.createFont(FontConstants.COURIER_BOLD)));
doc.add(div);
PdfCanvas canvas = new PdfCanvas(pdfPage);
Color white = Color.WHITE;
Color black = Color.BLACK;
canvas.setColor(white, true)
.setStrokeColor(black)
.circle(15, 800, 8)
.fillStroke();
canvas.beginText()
.setFontAndSize(PdfFontFactory.createFont(FontConstants.COURIER_BOLD), 10)
.setColor(black, true)
.moveText(15 - 3, 800 - 3)
.showText("1")
.endText();
doc.close();
If there is a correct way of wrapping some text (number) in a circle that can be positioned inside a div then I will happily change to this method if someone can point me towards a tutorial or some documentation I can follow.
If anybody is looking for an easy way to do this then you can override the draw method of Div.
public class Circle extends Div
{
#Override
public IRenderer getRenderer()
{
return new CircleRenderer(this);
}
private class CircleRenderer extends DivRenderer
{
public CircleRenderer(final Circle circle)
{
super(circle);
setPadding(PADDING);
}
#Override
public void draw(final DrawContext drawContext)
{
final PdfCanvas canvas = drawContext.getCanvas();
final Rectangle area = this.occupiedArea.getBBox();
final float x = area.getX();
final float y = area.getY();
canvas.circle(x, y, 8);
canvas.fillStroke();
super.draw(drawContext);
}
}
}
After I draw the canvas I need to do some correction of the x and y positions and same for any text placed inside the circle.

Unity3D ScrollView scroll range

I'm trying to create a scrollView, but I don't know how to determine the maximum range of the scroll (or even better, the maximum scroll position).
This is what I have tried to do without any positive results:
void Update()
{
//get the amount of space that my text is taking
textSize = gs.CalcHeight(new GUIContent(text), (screenWidth/2));
if(Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Moved && Input.GetTouch(0).tapCount==1)
{
var touch = Input.touches[0];
//check if touch is within the scrollView area
if(touch.position.x >= (screenWidth/2) && touch.position.x <= (screenWidth) && touch.position.y >= 0 && touch.position.y <= (screenHeight))
{
if(touch.phase == TouchPhase.Moved)
{
scrollPosition[1] += touch.deltaPosition.y;
if(scrollPosition[1] < 0)
{
scrollPosition[1] = 0;
}
//I subtracted this cause i read that the scrollbars take 16px
if(scrollPosition[1] > (textSize-scrollViewRect.yMax-16)) //scrollViewRect is a Rect with the size of my scrollView
{
scrollPosition[1] = textSize-scrollViewRect.yMax-16;
}
}
}
}
}
void OnGUI()
{
screenWidth = camera.pixelWidth;
screenHeight = camera.pixelHeight;
GUILayout.BeginArea(new Rect(screenWidth/2, 0, screenWidth/2, screenHeight));
GUILayout.BeginScrollView(scrollPosition/*, GUILayout.Width (screenWidth/2), GUILayout.Height (screenHeight/2)*/, "box");
GUILayout.Label (new GUIContent(text), gs);
// End the scrollview
GUILayout.EndScrollView ();
scrollViewRect = GUILayoutUtility.GetLastRect();
GUILayout.EndArea();
}
This was done assuming that the scrollView is on the right half of the screen.
Can you guys help me out on this?
Thanks in advance.
As shown in the API documentation for GUI.BeginScrollView, you can assign a Vector2 to the declaration of BeginScrollView to track and update where the box has been scrolled to.
I'll give an example:
using UnityEngine;
using System.Collections;
public class ScrollSize : MonoBehaviour {
private int screenBoxX = Screen.width;
private int screenBoxY = Screen.height;
private int scrollBoxX = Screen.width * 2;
private int scrollBoxY = Screen.height * 3;
public Vector2 scrollPosition = Vector2.zero;
void Awake()
{
Debug.Log ("box size is " + scrollBoxX + " " + scrollBoxY);
}
void OnGUI()
{
scrollPosition = GUI.BeginScrollView(new Rect(0, 0, screenBoxX, screenBoxY),
scrollPosition, new Rect(0, 0, scrollBoxX, scrollBoxY));
GUI.EndScrollView();
// showing 4 decimal places
Debug.Log ("Box scrolled # " + scrollPosition.ToString("F4"));
}
}
For the explanation of this example, let's assume that the screen resolution is 800 by 600.
Therefore the viewable scrollbox on the screen will be 800 wide and 600 high, with the internal area of the scrollbox being 1600 by 1800.
We create the scrollbox by assigning Gui.BeginScrollView() as the value of Vector2, scrollPosition. As the scrollbox is scrolled horizontally and vertically, the Vector2 will update with the scrolled value.
If the scrollbox is scrolled to the top-leftmost position, the value of scrollPosition will be 0,0.
If the scrollbox is scrolled to the bottom-rightmost position, the value of scrollPosition will be 816, 616, the size of the box on screen plus the thickness of the scroll bars.
I know this is 3 years later but maybe it will help others too...
I found that setting the scrollPosition to a very high number will force the scrollbar to the bottom:
scrollPosition.y = 999.9f;
Then you can read the position like this:
scrollPosition = GUILayout.BeginScrollView(scrollPosition,
GUILayout.Width(370),
GUILayout.Height(135));
And it will be set to whatever the maximum is.
Hope this helps!

Programmatically measure text string in pixels for Silverlight

In WPF there is the FormattedText in the System.Windows.Media namespace MSDN FormattedText that I can use like so:
private static Size GetTextSize(string txt, string font, int size, bool isBold)
{
Typeface tf = new Typeface(new System.Windows.Media.FontFamily(font),
FontStyles.Normal,
(isBold) ? FontWeights.Bold : FontWeights.Normal,
FontStretches.Normal);
FormattedText ft = new FormattedText(txt, new CultureInfo("en-us"), System.Windows.FlowDirection.LeftToRight, tf, (double)size, System.Windows.Media.Brushes.Black, null, TextFormattingMode.Display);
return new Size { Width = ft.WidthIncludingTrailingWhitespace, Height = ft.Height };
}
Is there a good approach in Silverlight to getting the width in pixels (at the moment height isn't important) besides making a call to the server?
An approach that I've seen used, that may not work in your particular instance, is to throw the text into an unstyled TextBlock, and then get the width of that control, like so:
private double GetTextWidth(string text, int fontSize)
{
TextBlock txtMeasure = new TextBlock();
txtMeasure.FontSize = fontSize;
txtMeasure.Text = text;
double width = txtMeasure.ActualWidth;
return width;
}
It's a hack, no doubt.

How to handle ball to ball collision with trigonometry instead of vectors?

I am making a simplified version of the ball physics app found at this SO question. I gained a lot from reading through the code and links provided at that question, but in my program I do not use vectors, I just update the coordinates of my balls (each of which has a random angle and speed) with trigonometry functions when they hit the walls.
There is info all over the place for how to handle ball to ball collisions without trigonometry, but I have found none that explain how it can be done with trigonometry.
--EDIT 9/13/2010--
Success... kind of... I got done what I wanted to do, but I was unable to do it how I wanted. If there is a way to calculate ball to ball collisions without the use of vectors it has eluded me. Even so, vectors do seem to be an easier way to handle collisions of all types... I just wish I would have known that when I started my program... would have saved me two or three days of work :) All the code for my (complete?) program is below. I added some neat features like shadows and a decreasing ball radius which really lets you see the difference in the mass of two balls when a big ball hits a small ball. In total there are five class files, AddLogic.java, Ball.java, BallBuilder.java, MouseEventHandler.java, and Vector2D.java.
AddLogic.java
import java.awt.*;
import java.awt.Graphics2D;
import java.awt.image.BufferStrategy;
import java.util.ArrayList;
public class AddLogic implements Runnable {//Make AddLogic a runnable task.
private BallBuilder ballBuilder;
private BufferStrategy strategy;
private static ArrayList objectsToDraw = new ArrayList();
private int floorHeight = 33;
public AddLogic(BallBuilder ballBuilder, BufferStrategy strategy) {
this.ballBuilder = ballBuilder;
this.strategy = strategy;
}
private void logic(BallBuilder ballBuilder, BufferStrategy strategy) {
this.ballBuilder = ballBuilder;
this.strategy = strategy;
while (true) {//Main loop. Draws all objects on screen and calls update methods.
Graphics2D g = (Graphics2D) strategy.getDrawGraphics();//Creates the Graphics2D object g and uses it with the double buffer.
g.setColor(Color.gray);
g.fillRect(0, 0, ballBuilder.getWidth(), ballBuilder.getHeight());//Draw the wall.
g.setColor(Color.lightGray);
g.fillRect(0, ballBuilder.getHeight() - floorHeight, ballBuilder.getWidth(), floorHeight);//Draw the floor.
g.setColor(Color.black);
g.drawLine(0, ballBuilder.getHeight() - floorHeight, ballBuilder.getWidth(), ballBuilder.getHeight() - floorHeight);//Draw the line between the wall and floor.
if (objectsToDrawIsEmpty() == true) {//If no balls have been made display message telling users how to make new ball.
g.setColor(Color.red);
g.drawString("Click Mouse For New Ball", (ballBuilder.getWidth() / 2) - 70, ballBuilder.getHeight() / 2);
}
for (int i = 0; i < objectsToDraw.size(); i++) {//Draw shadows for all balls.
Ball ball = (Ball) objectsToDraw.get(i);
g.setColor(Color.darkGray);
g.fillOval(
(int) ball.ballPosition.getX() - (int) ((ball.ballPosition.getY() / (350 / ball.getBallRadius())) / 2),
ballBuilder.getHeight() - (floorHeight / 2) - (int) ((ball.ballPosition.getY() / (1250 / ball.getBallRadius())) / 2),
(int) ball.ballPosition.getY() / (350 / ball.getBallRadius()),
(int) ball.ballPosition.getY() / (1250 / ball.getBallRadius()));
}
for (int i = 0; i < objectsToDraw.size(); i++) {//Draw all balls by looping through them and checking for any vector or collision updates that need to be made.
Ball ball = (Ball) objectsToDraw.get(i);
g.setColor(ball.getBallColor());
g.fillOval(
(int) ball.ballPosition.getX() - ball.getBallRadius(),
(int) ball.ballPosition.getY() - ball.getBallRadius(),
ball.getBallRadius() * 2,
ball.getBallRadius() * 2);
vectorUpdate(ball);//Update ball vector coordinates.
collisionCheck(ball);//Check ball to ball and ball to wall collisions.
}
if (MouseEventHandler.mouseEventCheck() == true) {// Creates a new ball when mouse is clicked.
Ball ball = new Ball(ballBuilder);
objectsToDraw.add(ball); //Adds the new ball to the array list.
MouseEventHandler.mouseEventUpdate(); //Resets the mouse click event to false.
}
g.dispose();//To aid Java in garbage collection.
strategy.show();//Show all graphics drawn on the buffer.
try {//Try to make thread sleep for 5ms. Results in a frame rate of 200FPS.
Thread.sleep(5);
}
catch (Exception e) {//Catch any exceptions if try fails.
}
}
}
private void vectorUpdate(Ball ball) {//Update the ball vector based upon the ball's current position and its velocity.
ball.ballPosition.setX(ball.ballPosition.getX() + ball.ballVelocity.getX());
ball.ballPosition.setY(ball.ballPosition.getY() + ball.ballVelocity.getY());
}
private void collisionCheck(Ball ball) {//Check for ball to wall collisions. Call check for ball to ball collisions at end of method.
if (ball.ballPosition.getX() - ball.getBallRadius() < 0) {//Check for ball to left wall collision.
ball.ballPosition.setX(ball.getBallRadius());
ball.ballVelocity.setX(-(ball.ballVelocity.getX()));
ball.decreaseBallRadius(ball);//Decrease ball radius by one pixel. Called on left, top, and right walls, but not bottom because it looks weird watching shadow get smaller during bottom bounce.
}
else if (ball.ballPosition.getX() + ball.getBallRadius() > ballBuilder.getWidth()) {//Check for ball to right wall collision.
ball.ballPosition.setX(ballBuilder.getWidth() - ball.getBallRadius());
ball.ballVelocity.setX(-(ball.ballVelocity.getX()));
ball.decreaseBallRadius(ball);//Decrease ball radius by one pixel. Called on left, top, and right walls, but not bottom because it looks weird watching shadow get smaller during bottom bounce.
}
if (ball.ballPosition.getY() - ball.getBallRadius() < 0) {//Check for ball to top wall collision.
ball.ballPosition.setY(ball.getBallRadius());
ball.ballVelocity.setY(-(ball.ballVelocity.getY()));
ball.decreaseBallRadius(ball);//Decrease ball radius by one pixel. Called on left, top, and right walls, but not bottom because it looks weird watching shadow get smaller during bottom bounce.
}
else if (ball.ballPosition.getY() + ball.getBallRadius() + (floorHeight / 2) > ballBuilder.getHeight()) {//Check for ball to bottom wall collision. Floor height is accounted for to give the appearance that ball is bouncing in the center of the floor strip.
ball.ballPosition.setY(ballBuilder.getHeight() - ball.getBallRadius() - (floorHeight / 2));
ball.ballVelocity.setY(-(ball.ballVelocity.getY()));
}
for (int i = 0; i < objectsToDraw.size(); i++) {//Check to see if a ball is touching any other balls by looping through all balls and checking their positions.
Ball otherBall = (Ball) objectsToDraw.get(i);
if (ball != otherBall && Math.sqrt(Math.pow(ball.ballPosition.getX() - otherBall.ballPosition.getX(), 2.0) + Math.pow(ball.ballPosition.getY() - otherBall.ballPosition.getY(), 2.0)) < ball.getBallRadius() + otherBall.getBallRadius()) {
resolveBallToBallCollision(ball, otherBall);//If the ball is hitting another ball calculate the new vectors based on the variables of the balls involved.
}
}
}
private void resolveBallToBallCollision(Ball ball, Ball otherBall) {//Calculate the new vectors after ball to ball collisions.
Vector2D delta = (ball.ballPosition.subtract(otherBall.ballPosition));//Difference between the position of the two balls involved in the collision.
float deltaLength = delta.getLength();//The (x, y) of the delta squared.
Vector2D minimumTranslationDistance = delta.multiply(((ball.getBallRadius() + otherBall.getBallRadius()) - deltaLength) / deltaLength);//The minimum distance the balls should move apart once they.
float ballInverseMass = 1 / ball.getBallMass();//half the ball mass.
float otherBallInverseMass = 1 / otherBall.getBallMass();//half the other ball mass.
ball.ballPosition = ball.ballPosition.add(minimumTranslationDistance.multiply(ballInverseMass / (ballInverseMass + otherBallInverseMass)));//Calculate the new position of the ball.
otherBall.ballPosition = otherBall.ballPosition.subtract(minimumTranslationDistance.multiply(otherBallInverseMass / (ballInverseMass + otherBallInverseMass)));//Calculate the new position of the other ball.
Vector2D impactVelocity = (ball.ballVelocity.subtract(otherBall.ballVelocity));//Find the veloicity of the impact based upon the velocities of the two balls involved.
float normalizedImpactVelocity = impactVelocity.dot(minimumTranslationDistance.normalize());//
if (normalizedImpactVelocity > 0.0f) {//Returns control to calling object if ball and other ball are intersecting, but moving away from each other.
return;
}
float restitution = 2.0f;//The constraint representing friction. A value of 2.0 is 0 friction, a value smaller than 2.0 is more friction, and a value over 2.0 is negative friction.
float i = (-(restitution) * normalizedImpactVelocity) / (ballInverseMass + otherBallInverseMass);
Vector2D impulse = minimumTranslationDistance.multiply(i);
ball.ballVelocity = ball.ballVelocity.add(impulse.multiply(ballInverseMass));//Change the velocity of the ball based upon its mass.
otherBall.ballVelocity = otherBall.ballVelocity.subtract(impulse.multiply(otherBallInverseMass));//Change the velocity of the other ball based upon its mass.
}
public static boolean objectsToDrawIsEmpty() {//Checks to see if there are any balls to draw.
boolean empty = false;
if (objectsToDraw.isEmpty()) {
empty = true;
}
return empty;
}
public void run() {//Runs the AddLogic instance logic in a new thread.
logic(ballBuilder, strategy);
}
}
Ball.java
import java.awt.*;
public class Ball {
private int ballRadius;
private float ballMass;
public Vector2D ballPosition = new Vector2D();
public Vector2D ballVelocity = new Vector2D();
private Color ballColor;
public Ball(BallBuilder ballBuilder) {//Construct a new ball.
this.ballRadius = 75;//When ball is created make its radius 75 pixels.
this.ballMass = ((float)(4 / 3 * Math.PI * Math.pow(ballRadius, 3.0)));//When ball is created make its mass that the volume of a sphere the same size.
this.ballPosition.set(ballRadius, ballBuilder.getHeight() - ballRadius);//When ball is created make its starting coordinates the bottom left hand corner of the screen.
this.ballVelocity.set(randomVelocity(), randomVelocity());//When ball is created make its (x, y) velocity a random value between 0 and 2.
if (AddLogic.objectsToDrawIsEmpty() == true) {//If the ball being created is the first ball, make it blue, otherwise pick a random color.
this.ballColor = Color.blue;
} else {
this.ballColor = randomColor();
}
}
public void decreaseBallRadius(Ball ball){//Decrease the ball radius.
if(ball.getBallRadius() <= 15){//If the ball radius is less than or equal to 15 return control to calling object, else continue.
return;
}
ball.setBallRadius(ball.getBallRadius() - 1);//Decrease the ball radius by 1 pixel.
ball.setBallMass((float)(4 / 3 * Math.PI * Math.pow(ballRadius, 3.0)));//Recalcualte the mass based on the new radius.
}
public int getBallRadius() {
return ballRadius;
}
public float getBallMass(){
return ballMass;
}
public Color getBallColor() {
return ballColor;
}
private void setBallRadius(int newBallRadius) {
this.ballRadius = newBallRadius;
}
private void setBallMass(float newBallMass){
this.ballMass = newBallMass;
}
private float randomVelocity() {//Generate a random number between 0 and 2 for the (x, y) velocity of a ball.
float speed = (float)(Math.random() * 2);
return speed;
}
private Color randomColor() {//Generate a random color for a new ball based on the generation of a random red, green, and blue value.
int red = (int) (Math.random() * 255);
int green = (int) (Math.random() * 255);
int blue = (int) (Math.random() * 255);
ballColor = new Color(red, green, blue);
return ballColor;
}
}
BallBuilder.java
import java.awt.*;
import java.awt.image.*;
import java.util.concurrent.*;
import javax.swing.*;
public class BallBuilder extends Canvas{
private int frameHeight = 600;
private int frameWidth = 800;
private static BufferStrategy strategy;//Create a buffer strategy named strategy.
public BallBuilder(){
setIgnoreRepaint(true);//Tell OS that we will handle any repainting manually.
setBounds(0,0,frameWidth,frameHeight);
JFrame frame = new JFrame("Bouncing Balls");
JPanel panel = new JPanel();
panel.setPreferredSize(new Dimension(frameWidth, frameHeight));
panel.add(this);
frame.setContentPane(panel);
frame.pack();
frame.setResizable(false);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
addMouseListener(new MouseEventHandler());
createBufferStrategy(2);//Create a double buffer for smooth graphics.
strategy = getBufferStrategy();//Apply the double buffer to the buffer strategy named strategy.
}
public static void main(String[] args) {
BallBuilder ballBuilder = new BallBuilder(); // Creates a new ball builder.
ExecutorService executor = Executors.newSingleThreadExecutor();//Creates a thread executor that uses a single thread.
executor.execute(new AddLogic(ballBuilder, strategy));//Executes the runnable task AddLogic on the previously created thread.
}
}
MouseEventHandler.java
import java.awt.event.*;
public class MouseEventHandler extends MouseAdapter{
private static boolean mouseClicked = false;
public void mousePressed(MouseEvent e){//If either of the mouse buttons is pressed the mouse clicked variable is set to true.
mouseClicked = true;
}
public static void mouseEventUpdate(){//When called, sets the mouse clicked variable back to false.
mouseClicked = false;
}
public static boolean mouseEventCheck(){//Returns the state of the mouse clicked variable.
if(mouseClicked){
return true;
}
else{
return false;
}
}
}
Vector2D
public class Vector2D {//A class that takes care of ball position and speed vectors.
private float x;
private float y;
public Vector2D() {
this.setX(0);
this.setY(0);
}
public Vector2D(float x, float y) {
this.setX(x);
this.setY(y);
}
public void set(float x, float y) {
this.setX(x);
this.setY(y);
}
public void setX(float x) {
this.x = x;
}
public void setY(float y) {
this.y = y;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
public float dot(Vector2D v2) {//Speciality method used during calculations of ball to ball collisions.
float result = 0.0f;
result = this.getX() * v2.getX() + this.getY() * v2.getY();
return result;
}
public float getLength() {
return (float) Math.sqrt(getX() * getX() + getY() * getY());
}
public Vector2D add(Vector2D v2) {
Vector2D result = new Vector2D();
result.setX(getX() + v2.getX());
result.setY(getY() + v2.getY());
return result;
}
public Vector2D subtract(Vector2D v2) {
Vector2D result = new Vector2D();
result.setX(this.getX() - v2.getX());
result.setY(this.getY() - v2.getY());
return result;
}
public Vector2D multiply(float scaleFactor) {
Vector2D result = new Vector2D();
result.setX(this.getX() * scaleFactor);
result.setY(this.getY() * scaleFactor);
return result;
}
public Vector2D normalize() {//Speciality method used during calculations of ball to ball collisions.
float length = getLength();
if (length != 0.0f) {
this.setX(this.getX() / length);
this.setY(this.getY() / length);
} else {
this.setX(0.0f);
this.setY(0.0f);
}
return this;
}
}
The speed angle changes when the ball bounces, you should change it after each bounce.
Also, as you'll see later, the speed VALUE (called "modulus") also changes when two balls collide.
EDIT:::
It seems to me that you are accelerating the balls
The
int x = (int) (ball.getBallTempX() +
(ball.getBallPathLength() * Math.cos(ball.getBallAngle())));
Seems to correspond to
int x = (int) (ball.getBallTempX() +
(ball.getBallSpeed() * Math.cos(ball.getBallAngle())));
And the same for "y"
Am I right?
Edit 2::
In fact, you don't need the TempX,TempY,PreviousCenterX and PreviousCenterY either.
Try these methods
private int xCoordinateUpdate(Ball ball) {
int x = (int) (ball.getBallCenterX()+ (ball.Speed()
* Math.cos(ball.getBallAngle())));
return x;
(the same for Y)
Edit 3 ::
One more ... :)
What you need is to code is this formula (any other will fail when you attempt to collide balls):
Pseudocode >
BallCenterX = BallCenterX + BallSpeed * Math.cos(angle)
BallCenterY = BallCenterY + BallSpeed * Math.sin(angle)
Forget any "temp" and "old" values. They will impair your ball colliding feature.
I'll start this answer and keep editing it step by step until we are done. I'll try to guide you to a "semi-vectorized" version of the program, trying to minimize the effort.
Please keep updating the code as you progress, and commenting on my suggestions.
First, a few things:
In
private double randomBallAngle(){
ballAngle = Math.toRadians((Math.random()*90));
return ballAngle;
}
You are working in radians, but in yCoordinateUpdate and yCoordinateUpdate it seems that you are using the same angle in grads (because you are comparing with 90).
Using radians is easier for all the math involved.
Also, the vars GoingUp, etc are not needed since the speed angle will take care of that.
You may set the initial random angle in the interval (0 ... 2 Pi).
You should change (in fact reflect) the speed angle after each border collission (and afterwards, when we are done, after each inter-ball caollission too).
For this reflection, the laws are:
Upper or lower wall: New Angle = - OldAngle
For Right or left wall: New Angle = Pi - OldAngle
We'll keep reflections on the vertex for later ...

Resources