java – Data-type implementation for vectors and drawing a vector field as a test client

The following is the web exercise 3.2.12. from the book Computer Science An Interdisciplinary Approach by Sedgewick & Wayne:

Write a program that draws a vector field. A vector field associates a vector with every point in a Euclidean
space. Widely used in physics to model speed and direction of a moving
object or strength and direction of a Newtonian force.

Here is my program:

public class Vector {
    private final double() coordinates;

    public Vector(double() coordinates) {
        this.coordinates = coordinates;
    }

    private int getCoordinatesLength() {
        return coordinates.length;
    }

    public double getCoordinate(int index) {
        return coordinates(index - 1);
    }

    public double getLength() {
        double sumOfCoordinatesSquared = 0;
        for (int i = 0; i < getCoordinatesLength(); i++) {
            sumOfCoordinatesSquared += getCoordinate(i + 1) * getCoordinate(i + 1);
        }
        return Math.sqrt(sumOfCoordinatesSquared);
    }

    private double getDirection2D() {
        return Math.atan(getCoordinate(2) / getCoordinate(1));
    }

    public double() getDirection() {
        if (getCoordinatesLength() != 2 && getCoordinatesLength() != 3) {
            throw new IllegalArgumentException("dimention of the vector must be either 2 or 3");
        }
        int dimention = 0;
        if (getCoordinatesLength() == 2) dimention = 1;
        else if (getCoordinatesLength() == 3) dimention = 2;
        double() angles = new double(dimention);
        if (getCoordinatesLength() == 2) {
            angles(0) = Math.atan(getCoordinate(2) / getCoordinate(1));
        } else if (getCoordinatesLength() == 3) {
            double vectorLength = getLength();
            double azimuth = Math.atan(getCoordinate(2) / getCoordinate(1));
            double zenith = Math.acos(getCoordinate(3) / vectorLength);
            angles(0) = azimuth;
            angles(1) = zenith;
        }
        return angles;
    }

    public Vector add(Vector otherVector) {
        if (getCoordinatesLength() != otherVector.getCoordinatesLength()) {
            throw new IllegalArgumentException("length of the vectors must be equal");
        }
        double() newCoordinates = new double(getCoordinatesLength());
        for (int i = 0; i < getCoordinatesLength(); i++) {
            newCoordinates(i) = getCoordinate(i + 1) + otherVector.getCoordinate(i + 1);
        }
        return new Vector(newCoordinates);
    }

    public Vector multiplyByScalar(double scalar) {
        double() newCoordinates = new double(getCoordinatesLength());
        for (int i = 0; i < getCoordinatesLength(); i++) {
            newCoordinates(i) = getCoordinate(i + 1) * scalar;
        }
        return new Vector(newCoordinates);
    }

    public Vector subtract(Vector otherVector) {
        return add(otherVector.multiplyByScalar(-1.0));
    }

    public boolean isEqual(Vector otherVector) {
        if (getCoordinatesLength() != otherVector.getCoordinatesLength()) return false;
        for (int i = 0; i < getCoordinatesLength(); i++) {
            if (getCoordinate(i + 1) != otherVector.getCoordinate(i + 1)) return false;
        }
        return true;
    }

    public double applyDotProduct(Vector otherVector) {
        if (getCoordinatesLength() != otherVector.getCoordinatesLength()) {
            throw new IllegalArgumentException("length of the vectors must be equal");
        }
        double dotProduct = 0;
        for (int i = 0; i < getCoordinatesLength(); i++) {
            dotProduct += getCoordinate(i + 1) * otherVector.getCoordinate(i + 1);
        }
        return dotProduct;
    }

    public Vector applyCrossProduct(Vector otherVector) {
        if (getCoordinatesLength() != otherVector.getCoordinatesLength()) {
            throw new IllegalArgumentException("length of the vectors must be equal");
        }
        if (getCoordinatesLength() != 3) {
            throw new IllegalArgumentException("dimention of the vector must be 3");
        }
        int x = 1;
        int y = 2;
        int z = 3;
        double newXCoordinate = getCoordinate(y) * otherVector.getCoordinate(z) - getCoordinate(z) * otherVector.getCoordinate(y);
        double newYCoordinate = getCoordinate(z) * otherVector.getCoordinate(x) - getCoordinate(x) * otherVector.getCoordinate(z);
        double newZCoordinate = getCoordinate(x) * otherVector.getCoordinate(y) - getCoordinate(y) * otherVector.getCoordinate(x);
        double() newCoordinates = {
            newXCoordinate,
            newYCoordinate,
            newZCoordinate
        };
        return new Vector(newCoordinates);
    }

    public boolean isPerpendicular(Vector otherVector) {
        if (applyDotProduct(otherVector) == 0) return true;
        else return false;
    }

    public boolean isParallel(Vector otherVector) {
        double scalingFactor = 0;
        for (int i = 0; i < getCoordinatesLength(); i++) {
            if (getCoordinate(i + 1) != 0 && otherVector.getCoordinate(i + 1) != 0) {
                scalingFactor = getCoordinate(i + 1) / otherVector.getCoordinate(i + 1);
                break;
            }
        }
        double() newCoordinates = new double(getCoordinatesLength());
        for (int i = 0; i < getCoordinatesLength(); i++) {
            newCoordinates(i) = getCoordinate(i + 1) / scalingFactor;
        }
        Vector newVector = new Vector(newCoordinates);
        if (otherVector.isEqual(newVector)) return true;
        else return false;
    }

    public String toString() {
        String printedCoordinates = "";
        for (int i = 0; i < getCoordinatesLength() - 1; i++) {
            printedCoordinates += (getCoordinate(i + 1) + ", ");
        }
        return "(" + printedCoordinates + getCoordinate(getCoordinatesLength()) + ")";
    }

    public void draw(double originX, double originY, double scaleDownFactor, double arrowHeadSize) {
        if (getCoordinatesLength() != 2) {
            throw new IllegalArgumentException("dimention of the vector must be 3");
        }
        double newX = getCoordinate(1) * scaleDownFactor;
        double newY = getCoordinate(2) * scaleDownFactor;
        double arrowHeadPointX = originX + newX;
        double arrowHeadPointY = originY + newY;
        StdDraw.line(originX, originY, arrowHeadPointX, arrowHeadPointY);
        double arrowHeadBaseX = arrowHeadSize * Math.sin(getDirection2D());
        double arrowHeadBaseY = arrowHeadSize * Math.cos(getDirection2D());
        double() arrowHeadXCoordinates = {-arrowHeadBaseX + (originX + 0.95 * newX),
                                           arrowHeadBaseX + (originX + 0.95 * newX),
                                           arrowHeadPointX
        };
        double() arrowHeadYCoordinates = {
             arrowHeadBaseY + (originY + 0.95 * newY),
            -arrowHeadBaseY + (originY + 0.95 * newY),
             arrowHeadPointY
        };
        StdDraw.filledPolygon(arrowHeadXCoordinates, arrowHeadYCoordinates);
    }

    public static void main(String() args) {
        /*
        double() coordinatesOfVectorA = {1,2};
        double() coordinatesOfVectorB = {0,1};
        Vector vectorA = new Vector(coordinatesOfVectorA);
        Vector vectorB = new Vector(coordinatesOfVectorB);
        double originX = 0.5;
        double originY = 0.5;
        double scaleDownFactor = 0.1; 
        double arrowHeadSize = 0.01;

        System.out.println("Vector A = " + vectorA.toString());
        System.out.println("Vector B = " + vectorB.toString());
        System.out.println("A plus B equals " + vectorA.add(vectorB).toString());
        System.out.println("A minus B equals " + vectorA.subtract(vectorB).toString());
        System.out.println("Dot product of A and B equals " + vectorA.applyDotProduct(vectorB));
        //System.out.println("Cross product of A and B equals " + vectorA.applyCrossProduct(vectorB).toString()); 
        System.out.println(vectorA.isParallel(vectorB));

        vectorA.draw(originX, originY, scaleDownFactor, arrowHeadSize);
        vectorB.draw(originX, originY, scaleDownFactor, arrowHeadSize);
        */
        StdDraw.setXscale(-1, 1);
        StdDraw.setYscale(-1, 1);
        for (int i = -10; i < 11; i++) {
            for (int j = -10; j < 11; j++) {
                if (i == 0 && j == 0) j++;
                double x = 1.0 * i / 10;
                double y = 1.0 * j / 10;
                double vectorXCoordinate = -y;
                double vectorYCoordinate = x;
                double() coordinates = {
                    vectorXCoordinate,
                    vectorYCoordinate
                };
                Vector vector = new Vector(coordinates);
                vector.draw(x, y, 0.1, 0.01);
            }
        }
    }
}

StdDraw is a simple API written by the authors of the book. I checked my program and it works. Here is one instance of it:

Input (taken from here):

enter image description here

Output:

enter image description here

Is there any way that I can improve my program?

Thanks for your attention.

discrete mathematics – Drawing r regions given n lines?

Thanks for contributing an answer to Mathematics Stack Exchange!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

Use MathJax to format equations. MathJax reference.

To learn more, see our tips on writing great answers.

graphics – Drawing two altitudes of a triangle

I am trying to construct a triangle with 2 altitudes. It worked for ‘All’ vertices.

a = {5, 0};
b = {0, 0};
c = {3, 6};
tri = {a, b, c};
alt = TriangleConstruct[tri, {"Altitude", All}];
Graphics[{Style[Triangle[tri], Opacity[0.2]], alt}]

But when I tried to specify two vertices,

alt = TriangleConstruct[tri, {"Altitude", {a, b}}]

The following message came out:

TriangleConstruct is not a Graphics primitive or directive.

How do I specify two vertices to draw the altitudes?


I have also tried the following:

RandomInstance[GeometricScene[{a,b,c,d}, {d == TriangleConstruct[{a,b,c}, {"Altitude", a}]}]]

But the output was only:

RandomInstance[GeometricScene[{{a, b, c, d}, {}}, {d ==TriangleConstruct[{a, b, c}, {"Altitude", a}]}, {}]]

What went wrong?

java – Drawing dragon curves using Turtle graphics

This is exercise 3.2.23. from the book Computer Science An Interdisciplinary Approach by Sedgewick & Wayne:

Write a recursive Turtle client that draws dragon fractal.

The following is the data-type implementation for Turtle graphics from the book which I beautified:

public class Turtle {
    private double x;
    private double y;
    private double angle;

    public Turtle(double x, double y, double angle) {
        this.x = x;
        this.y = y;
        this.angle = angle;
    }
    public void turnLeft(double delta) {
        angle += delta;
    }
    public void goForward(double step) {
        double oldX = x, oldY = y;
        x += step * Math.cos(Math.toRadians(angle));
        y += step * Math.sin(Math.toRadians(angle));
        StdDraw.line(oldX, oldY, x, y);
    }
}

StdDraw is a simple API written by the authors of the book.

Here is my program:

public class Dragon {
    public static void drawDragonCurve(int n, double step, Turtle turtle) {
        if (n == 0) {
            turtle.goForward(step);
            return;
        }
        drawDragonCurve(n - 1, step, turtle);
        turtle.turnLeft(90);
        drawNodragCurve(n - 1, step, turtle);

    }
    public static void drawNodragCurve(int n, double step, Turtle turtle) {
        if (n == 0) {
            turtle.goForward(step);
            return;
        }
        drawDragonCurve(n - 1, step, turtle);
        turtle.turnLeft(-90);
        drawNodragCurve(n - 1, step, turtle);
    }
    public static void main(String() args) {
        int n = Integer.parseInt(args(0));
        double step = Double.parseDouble(args(1));
        Turtle turtle = new Turtle(0.67, 0.5, 0);
        drawDragonCurve(n, step, turtle);
    }
}

I checked my program and it works. Here is one instance of it:

Input: 12 0.007

Output:

enter image description here

Is there any way that I can improve my program?

Thanks for your attention.

packages – Drawing the wheel graph

Is there a fast way to add a central vertex connected to all others after drawing the cycle graph using the graph library of the tikz package?

This is what the code currently looks like:

usepackage{tikz}
usetikzlibrary{graphs,graphs.standard}

begin{tikzpicture}[scale=0.8,every node/.style={scale=0.8}]
    graph  [nodes={circle,fill=black!25}, edges={black!60, semithick}, clockwise, radius=8em,
    n=9, p=0.3] 
        { subgraph C_n [n=6,m=3,clockwise,radius=2cm] };
end{tikzpicture}

Why is my camera drawing labyrinths on my photos?

On the photos where the lens flare is present, my Nikon D7000 is drawing strange labyrinth-looking artifacts. Here’s an example.

The first image is a screenshot of a little part of the original photo zoomed 2:1 in Lightroom.

The second image is the same photo with Lightroom’s contrast, clarity and sharpening set to the maximum to make the labyrinth pattern more visible.

enter image description here

enter image description here

This affects only some of the photos presenting lens flare. Here, for instance, the lens flare is present and light conditions are very close to the first photo, but there is no labyrinth pattern, only usual noise (screenshot of a part of a photo at 1:1 with contrast, clarity and sharpening set to the maximum):

enter image description here

Notes:

  • I can see this pattern only on some of the photos when the sun is in the frame or nearly in the frame.

  • It is present with any lens I tested.

  • The actual photo was shot at f/8, ISO 200, 1/640 s. I’ve seen this pattern in photos shot at ISO 100 as well.

  • The photo is in RAW format, so this is not a JPEG artifact.

What is that? How do I avoid it?

plotting – Three-Set Venn Diagram with Varying Radii not Drawing Full Circles

Here is the code that I have:

a = Disk({0, 1}, 0.7);
b = Disk({-0.5, 0}, 1.3);
c = Disk({0.5, 0});

subsets = Subsets({a, b, c}, {1, 3});

subsetscolors = Map(
   Function(
    {c},
    Blend(
     Flatten(
      Map(
       Table(
         Map(
          Append(#, 1.5/Length(c)) &,
          c
          ), 2
         ) &,
       c
       )
      )
     )
    ),
   Subsets({RGBColor("#f839ff"), RGBColor("#fff839"), 
     RGBColor("#40ff39")}, {1, 4})
   );

RegionPlot(
 Evaluate(
  DiscretizeRegion(RegionDifference(
      BooleanRegion(And, #),
      BooleanRegion(Or, 
       Complement({a, b, c, EmptyRegion(2)}, #)))) & /@ subsets
  ),
 PlotLabels -> Callout(
   (Apply(
     StringJoin, {{"a"}, {"b"}, {"c"}, {"d"}, {"e"}, {"f"}, {"g"}}, 
{1})),
   Center
   ),
 Sequence(
  PlotStyle -> subsetscolors,
  BoundaryStyle -> Directive(Thickness(0.01), Black),
  Frame -> True,
  LabelStyle -> {20},
  PerformanceGoal -> "Quality",
  ImageSize -> 400
  )
 )

Producing this output:

enter image description here

Because I vary the radii, not all of the circles are being drawn in full.

Sometimes (but I have been unable to reproduce this for StackExchange) varying the radii for the three disks will change the areas where the disks are not fully rendered.

I am guessing my issue is with the maybe to do with PerformanceGoal ->, but as I have this set to "Quality" I do not know what the problem is.

rendering – Drawing a line between 2 vectors

I was trying to implement a simple mechanic by drawing a line between the sprite and the mouse, but it’s not working that well:

Screenshot showing red line emanating from a blue sprite

extends KinematicBody2D
onready var player = $CollisionShape2D
var pos_two 
var pos
func _physics_process(delta):
    
    var vel = Vector2()
    pos_two = player.get_position()
    
    pos = get_global_mouse_position()
    look_at(pos)
    if Input.is_action_pressed("movethere"):
        vel = Vector2(400 , 0).rotated(rotation)
    vel = move_and_slide(vel)
func _draw():
    draw_line( pos_two ,pos, Color(255 , 0 , 0))

javascript – JS + CANVAS Projectile Motion, problem drawing rotated images + improvements & sugestions

now just as the title says, i’ve got some JS code.

So basically it is a projectile motion thingy, i’m quite proud that i’ve actually got it working!

Basically you’ve got a canvas, and you can click it to launch a ball in a cerain angle and speed, everything is working fine, er.. except for the arrow tip of the vector thingy, can’t get that sucker to draw correctly.

So, how should i approach the arrow tip thing? i mean, how to draw it at the correct position and angle (so basically the TRANSLATE and ROTATE functions). That’s the thing that i want to solve right now.

And also, if you do take your time to go throguh the code, i’m open to suggestions and improvements, i do want to learn more about canvas and it’s best practices.

So hyped to hear your two cents on this! thx :p

First off, the HTML:

<html lang="es">
    <head>
        <meta charset="utf-8">
        <title>Projectile Motion</title>
        <link rel="stylesheet" href="index.css">
    </head>
    
    <body>
        
        <h3 id="fpsIndicator">FPS: 0</h3>
        
        <h3 id="mouse">X:0  Y:0</h3>
              
        <canvas id="area" height=500 width=1700></canvas>
        
        <p id="collision">collision indicator</p>
        <p id="ballCount">NºBalls: 0</p>
        
        <br>
        <br>
               
        <div id="infoarea">
           
            <!-- triangle-->
            <div style="border: 1px solid black; width: 220px; padding: 3px;">
                <h2 style="text-align: center;">TRIANGLE</h2>

                <h3 id="hypotenuse">hypotenuse: 0</h3>

                <h3 id="adj">adjacent: 0</h3>
                <h3 id="opp">opposite: 0</h3>
                <h3 id="theta">&theta;: 0.00º</h3>
                <h3 id="currentRads"></h3>
            </div>
            
            <!-- motion input -->
            <div style="border: 1px solid black; width: 220px; padding: 3px;">
                <h2 style="text-align: center;">MOTION INPUT</h2>

                
                <div>
                    <p>Launch Velocity (m/s)</p>
                    <input type="number" id="velocity">
                </div>
                
                <div>
                    <p>Launch Angle (degrees)</p>
                    <input type="number" id="launchAngle">
                </div>
                
                <div>
                    <p>Vertical Acceleration (gravity)</p>
                    <select name="" id="acceleration">
                        <option value="-3.7">Mercury (3.7 m/s)</option>
                        <option value="-8.87">Venus (8.87 m/s)</option>
                        <option selected value="-9.807">Earth (9.807 m/s)</option>
                        <option value="-3.711">Mars (3.711 m/s)</option>
                        <option value="-24.79">Jupiter (24.79 m/s)</option>
                        <option value="-10.44">Saturn (10.44 m/s)</option>
                        <option value="-8.87">Uranus (8.87 m/s)</option>
                        <option value="-11.15">Neptune (11.15 m/s)</option>
                        <option value="-0.62">Pluto (0.62 m/s)</option>
                        <option value="-5000">STRONK GRAVITY (5000m/s)</option>
                    </select>
                </div>
                
            </div>
            
            <!-- motion data -->
            <div style="border: 1px solid black; padding: 3px;">
                <h2 style="text-align: center;">PREDICTED MOTION DATA</h2>

                <div id="container">
                  
                   <!-- fields -->
                   <div class="subcontainer">
                        <div>
                            <p>Vertical Velocity (m/s)</p>
                            <input type="number" id="Yvel">
                        </div>

                        <div>
                            <p>Horizontal Velocity (m/s)</p>
                            <input type="number" id="Xvel">
                        </div>

                        <div>
                            <p>Flight Time (seconds)</p>
                            <input type="number" id="time">
                        </div>
                    </div>
                    
                    <!-- fields 2 -->
                    <div class="subcontainer">
                        <div>
                            <p>Max Height achieved (m)</p>
                            <input type="number" id="mHeight">
                        </div>

                        <div>
                            <p>X displacement (aprox.range)</p>
                            <input type="number" id="Xdisplacement">
                        </div>

                        <div>
                            <p>Y Acceleration (gravity)</p>
                            <input type="number" id="yAcceleration">
                        </div>
                    </div>
                    
                    
                    
                    
                </div>    
                
            </div>
            
            <!-- manual input-->
            <div style="border: 1px solid black; padding: 3px;">
               
                <h2 style="text-align: center;">MANUAL INPUT</h2>
                
                <div id="container">
                  
                   <!-- fields -->
                   <div class="subcontainer">
                       
                        <div>
                            <p>Launch Angle (degrees)</p>
                            <input type="number" id="mAngle">
                        </div>

                        <div>
                            <p>Launch Velocity (m/s)</p>
                            <input type="number" id="mVelocity">
                        </div>

                       <div style="display: flex; justify-content: center; margin-top: 3px;">
                            <button id="launchManual">
                                Launch!
                            </button>
                        </div>
                        
                    </div>
                </div>
                
                
            </div>
            
        </div>    
        
        
        <img src="https://codereview.stackexchange.com/theta.png" id="angle" alt="" style="display: none;">
        <img src="arrowTip.png" id="tip" alt="" style="display: none;">
    </body>
    
    <footer>
        <script src="ball.js" type="text/javascript"></script>
        <script src="index.js" type="text/javascript"></script>
    </footer>
    
</html>

Now the CSS (not much here LOL)

* {
    -webkit-user-select: none; /* Safari */        
    -moz-user-select: none; /* Firefox */
    -ms-user-select: none; /* IE10+/Edge */
    user-select: none; /* Standard */
}

canvas {

    height: 500px;
    width: 1700px;
    display: block;
    margin-left: auto;
    margin-right: auto;
    border: 1px solid red;
}

p {
    margin: 0;
    margin-top: 8px;
}

#infoarea {
    display: flex;
    justify-content: flex-start;
}

#container {
    display: flex;
    justify-content: space-around:
}

.subcontainer{
    margin: 4px;
    padding: 4px;
    box-shadow: 2px 2px 2px gray;
}

Now the Ball class

class Ball{
    
    
    constructor(positionP, velocityP, accelerationP, radiusP, contextP, colorP){
        
        this.radius = 15; /*radiusP;*/
        this.context = contextP;
        this.color = colorP; 
        
        this.friction = 0.97;
        this.colliding = false;
        this.collidingColor = "yellow";
        
        this.position = {
            x: positionP.x,
            y: positionP.y
        }
        
        this.velocity = {
            x: velocityP.x,
            y: velocityP.y
        }
        
        this.acceleration = {
            x : 0,
            y : accelerationP/60
        }
    }
    
    draw(){
        
        if(this.colliding) this.context.fillStyle = this.collidingColor;
        else this.context.fillStyle = this.color; 
            
        this.context.beginPath();
        this.context.arc(this.position.x, this.position.y, this.radius, 0, 2*Math.PI);
        this.context.fill();
    }
    
    move(){
               
        this.velocity.x += this.acceleration.x;
        this.velocity.y = this.velocity.y + this.acceleration.y;


        this.position.x += this.velocity.x;
        this.position.y -= this.velocity.y;
        
    }
}

And finally, the BIG one:

//screen labels
let fpsInd = document.getElementById("fpsIndicator");
let mouseInd = document.getElementById("mouse");
let hypIndicator = document.getElementById("hypotenuse");
let oppIndicator = document.getElementById("opp");
let adjIndicator = document.getElementById("adj");
let thetaIndicator = document.getElementById("theta");


let manualLauncherBtn = document.getElementById("launchManual");
let manualVelocity = document.getElementById("mVelocity");
let manualAngle = document.getElementById("mAngle");

var strThetaChar = thetaIndicator.innerHTML.split(" ")(0);
var currentThetaRadians;

var CANVAS_CLICKED = false;
let canvas = document.getElementById("area");
let ctx = canvas.getContext("2d");

var frames = 0;
var fps = 0;
var lastCallTime;

let Balls = ();

/////////////////////////motion variables//////////
let velocity_initial;
let angle;
let time;


//VERTICAL 
let y_velocity_original = 0;
let y_velocity_final = 0;
let y_original = 0;
let y_final = 0;
let y_acceleration = 0;
let max_height = 0;


//HORIZONTAL 
let x_velocity_original = 0;
let x_velocity_final = 0;
let x_original = 0;
let x_final = 0;
let x_acceleration = 0;




canvas.addEventListener('mousemove', (event)=>{
    updateMouse(event);
    solveTriangle();
});

canvas.addEventListener('click', () => {
   CANVAS_CLICKED = true;    
   MotionMain(ctx); 
});

manualLauncherBtn.addEventListener('click', () => {
   performManuaLaunch(); 
});

var origin = {
    x: 0,
    y: canvas.height
}

var originBalls = {
    x: 0,
    y: canvas.height-1
}

var mousePosition = {
    x: 0,
    y: 0
}




window.requestAnimationFrame(loop);
function loop() {
      
  frames ++;
  getFPS();
    
  console.log(CANVAS_CLICKED);    
    
  if(frames % 3 == 0) 
    fpsInd.innerHTML = "FPS: "+fps;      

  clearScreen();    
  workNextFrame();    
    

  CANVAS_CLICKED = false;    
    
  window.requestAnimationFrame(loop);  
}

//motion functions
function MotionMain(ctx){      
        
    y_acceleration = parseFloat(y_acceleration);  
    solveProblem();
    showLaunchData();
    
    newBall();
}

function performManuaLaunch(){    
    let launchData = createManualBall();
    let {ball, velocities, maxHeight, s, t} = launchData;
           
    showManualData(velocities, t, maxHeight, s);  
    Balls.push(ball);
}

function showManualData(velocities, t, maxHeight, s){
    let a = document.getElementById("acceleration").value;
    
    document.getElementById('velocity').value = manualVelocity.value;
    document.getElementById('launchAngle').value = manualAngle.value;
    document.getElementById('Yvel').value = velocities.y.toFixed(2);
    document.getElementById('Xvel').value = velocities.x.toFixed(2);
    document.getElementById('time').value = parseFloat(t).toFixed(2);
    document.getElementById('mHeight').value = parseFloat(maxHeight).toFixed(2);
    document.getElementById('Xdisplacement').value = parseFloat(s).toFixed(2);
    document.getElementById('yAcceleration').value = a;
}

function createManualBall(){
    let mTime, mDisplacementX, mMaxHeight, mBall;
    
    let m_angle = parseFloat(manualAngle.value);
    let m_vel = parseFloat(manualVelocity.value);
    
    let velY = Math.sin( (m_angle * Math.PI) / 180) * m_vel;
    let velX = Math.cos( (m_angle * Math.PI) / 180) * m_vel;
    
        
    let accelerationY = parseFloat(document.getElementById("acceleration").value);
    
    // 1- get the time -> Vf=Vo+at --> t=Vo-Vf/a
    mTime = Math.abs( velY / accelerationY )*2;
    mTime = mTime.toFixed(3);
    
    // 2- get the range -> Xf=Vo*t
    mDisplacementX = (velX * mTime).toFixed(0);
    
    // 3- get the maximum height -> Vo*Vo/2*a
    mMaxHeight = Math.abs( (velY*velY) / ( 2 * accelerationY ) ).toFixed(2);
    
    mBall = new Ball(originBalls, {
        x: velX,
        y: velY
    }, accelerationY,7, ctx, "yellow");
    
    return {
        t: mTime,
        s: mDisplacementX,
        maxHeight: mMaxHeight,
        velocities: {x: velX, y: velY},
        ball: mBall
    }
}


function solveProblem(){
    
    time = 0;
    x_final = 0;
    max_height = 0;
        
    // 1- get the time -> Vf=Vo+at --> t=Vo-Vf/a
    time = Math.abs( (y_velocity_original - y_velocity_final) / y_acceleration )*2;
    time = time.toFixed(3);
    
    // 2- get the range -> Xf=Vo*t
    x_final = (x_velocity_original * time).toFixed(0);
    
    // 3- get the maximum height -> Vo*Vo/2*a
    max_height = Math.abs( (y_velocity_original*y_velocity_original) / (2*y_acceleration) ).toFixed(2);  
    
    //alert("time: "+time+"s range: "+x_final+"m Max.Height: "+max_height+"m");
}

function showLaunchData(){
    document.getElementById('velocity').value = velocity_initial;
    document.getElementById('launchAngle').value = angle;
    document.getElementById('Yvel').value = y_velocity_original;
    document.getElementById('Xvel').value = x_velocity_original;
    document.getElementById('time').value = time;
    document.getElementById('mHeight').value = max_height;
    document.getElementById('Xdisplacement').value = x_final;
    document.getElementById('yAcceleration').value = y_acceleration;
}

function newBall(){
    
    if(!CANVAS_CLICKED) return;
    
    let c;
    
    if(angle <= 30) c = "red";
    else if(angle <= 45 && angle > 30) c = "green";
    else if(angle <= 60 && angle > 45) c = "blue";
    else if(angle <= 90 && angle > 60) c = "black";
    
      
    var ball = new Ball( originBalls, {
        x: x_velocity_original,
        y: y_velocity_original
    }, y_acceleration, 5, ctx, c);
    
    
    Balls.push(ball);
}

function randomInteger(min, max){
    return Math.floor(Math.random() * (max-min+1) + min);
}
/////////////////////////////////////////

function workNextFrame(){
    //drawVectorTip(); <- this thing right here, darn
    drawVectorLine(); 
    
    drawAngleIcon();
    drawAngleArc();
    
    
    updateBalls();
    cleanBalls();
    
    checkCollisions();
}

function updateBalls(){
    for(let i = 0; i < Balls.length; i++){       
        Balls(i).draw();
        Balls(i).move();
    }
}

function cleanBalls(){
    for(let i = Balls.length-1; i >= 0; i--){       
        let ball = Balls(i);
        if(ball.position.y > canvas.height) Balls.splice(i,1);
        document.getElementById("ballCount").innerHTML = "NºBalls: "+Balls.length;
    }
}

function drawVectorTip(){
    
    var img = document.getElementById("tip");
    
    ctx.save();
    
    ctx.translate(origin.x/2, origin.y/2);
    ctx.rotate(-currentThetaRadians);
    
    ctx.drawImage(img, mousePosition.x, mousePosition.y, 40, 40);
    
    ctx.restore();
}



function drawAngleArc(){
    
    ctx.beginPath();
    ctx.arc(origin.x, origin.y, 30, 0, -currentThetaRadians, true);
    ctx.stroke(); 
    
    //document.getElementById("currentRads").innerHTML = currentThetaRadians.toFixed(3);
}

function solveTriangle(){
    var opp = Math.floor(canvas.height - mousePosition.y);
    var adj = Math.floor(mousePosition.x);
    var hyp = Math.floor(Math.sqrt( opp*opp + adj*adj ));
    
    var thetaRadians = Math.atan(opp/adj);
    var thetaDegrees = (thetaRadians*180)/Math.PI;
    
    currentThetaRadians = thetaRadians;
    
    oppIndicator.innerHTML = `Opposite: ${opp}`;
    adjIndicator.innerHTML = `Adjacent: ${adj}`;
    hypIndicator.innerHTML = `Hypotenuse: ${hyp}`;
    
    thetaIndicator.innerHTML = strThetaChar+" "+thetaDegrees.toFixed(2)+"º";
    
    
    passMotionData(opp, adj, hyp, thetaDegrees);  
}

function passMotionData(opp, adj, hyp, thetaDegrees){
    
    velocity_initial = hyp;
    angle = thetaDegrees.toFixed(2);
    time = null; 
    
    y_velocity_original = opp;
    y_velocity_final = 0;
    y_original = canvas.height;
    y_final = canvas.height;
    y_acceleration = document.getElementById("acceleration").value;
    max_height = null;
    
    x_velocity_original = adj;
    x_velocity_final = 0;
    x_original = 0;
    x_final = null;
    x_acceleration = 0;     
}

function drawVectorLine(){
    
    ctx.beginPath();
    ctx.moveTo(origin.x, origin.y);
    ctx.lineTo(mousePosition.x, mousePosition.y);
    ctx.stroke();
    
}

function drawAngleIcon(){
  var img = document.getElementById("angle");
  ctx.drawImage(img, origin.x+5, origin.y-14, 10, 10);
}

function clearScreen(){
    ctx.clearRect(0, 0, canvas.width, canvas.height);
}

function updateMouse(event){
  var rect = event.target.getBoundingClientRect();
     
  mousePosition.x = event.clientX - rect.left; //x position within the element.
  mousePosition.y = event.clientY - rect.top;  //y position within the element.
    
  mouseInd.innerHTML = `X: ${mousePosition.x.toFixed(0)}  Y: ${mousePosition.y.toFixed(0)}`;
}


function checkCollisions(){
    
    let first, second;
    
    for(let i = 0; i < Balls.length; i++){        
        first = Balls(i);
        
        for(let p = 0; p < Balls.length; p++){        
            second = Balls(p);
            
            if(i != p){
               if(distance(first, second) <= first.radius + second.radius) first.colliding = true;
               else first.colliding = false; 
            }            
        }
    }
}

function distance(first, second){
    let diffX = Math.abs(first.position.x - second.position.x);
    let diffY = Math.abs(first.position.y - second.position.y);
    let distance = Math.sqrt(diffX*diffX + diffY*diffY);
    
    if(distance <= first.radius + second.radius) document.getElementById("collision").innerHTML = "colliding";
    else document.getElementById("collision").innerHTML = "not colliding";
    
    return distance;
}

///funcions xungues de colisions
function rotate(velocity, angle){
    const rotatedVelocities = {
        x: velocity.X * Math.cos(angle) - velocity.Y * Math.sin(angle),   
        y: velocity.X * Math.sin(angle) + velocity.Y * Math.cos(angle)
    };
    
    return rotatedVelocities;
}


function resolveCollision(bubble, otherBubble){
    const xVelocityDiff = bubble.velocity.X - otherBubble.velocity.X;
    const yVelocityDiff = bubble.velocity.Y - otherBubble.velocity.Y;
    
    const xDist = otherBubble.x - bubble.x;
    const yDist = otherBubble.y - bubble.y;
    
    //prevent accidental overlap of bubbles
    if(xVelocityDiff * xDist + yVelocityDiff * yDist >= 0){
        
        
        //grab angle between the two colliding bubbles
        const angle = -Math.atan2(otherBubble.y - bubble.y, otherBubble.x - bubble.x);
        
        //store mass in var for better readability in collision equation
        const m1 = bubble.mass;
        const m2 = otherBubble.mass;
        
        //velocity before equation
        const u1 = rotate(bubble.velocity, angle);
        const u2 = rotate(otherBubble.velocity, angle);
        
        //velocity after 1 dimension collision equation
        const v1 = {X: u1.x * (m1 - m2) / (m1 + m2) + u2.x * 2 * m2 / (m1 + m2), Y: u1.y };
        const v2 = {X: u2.x * (m1 - m2) / (m1 + m2) + u1.x * 2 * m2 / (m1 + m2), Y: u2.y };
        
        //final velocity after rotating axis back to original location
        const vFinal1 = rotate(v1, -angle);
        const vFinal2 = rotate(v2, -angle);
        
        //swap bubbles velocities for realistic bounce effect
        bubble.velocity.X = vFinal1.x;
        bubble.velocity.Y = vFinal1.y;
        
        otherBubble.velocity.X = vFinal2.x;
        otherBubble.velocity.Y = vFinal2.y;
    }
}
////

function getFPS(){
    
    let delta;
    
    if(!lastCallTime){
        lastCallTime = Date.now();
        fps = 0;
        return;
    }
    
    delta = (Date.now() - lastCallTime) / 1000;
    lastCallTime = Date.now();
    fps = Math.floor(1/delta);
}