Search code examples
javascriptangle

Calculate random bounce angles


I'd like to make a ball bounce angle change each time it hits a wall.

It will change based on how near the middle of the wall it hits...

Right now, I'm hard coding the change in X and Y when it hits a surface... My goal is to get the degrees from current X and Y, apply a change to the degrees (Right now I'm add a random number to the degrees), then calculate the new incrementing values for X and Y. I know how to get the newX and newY, but not how to get the incrementing values.

The green is the starting x y of (5,5)... the blue is the next frame of (4,4).

enter image description here

  • So I calculated the degrees to be 45 based on that.
  • Then added a random number to the degrees.
  • Then, I want to get the new x and y coordinates. So I followed this method...

currX (5) - wallX (0) = distX (5)

currY (5) - wallY (0) = distY (5)

Take the cosine of my angle + random increment, we'll say 55 degrees, * distX

cos(55 degrees) = .5735... .5735 x distX (5) = 2.86

And sin of my angle * distY

sin(55 degrees) = .8191... .8191 x distY (5) = 4.09

newX = cos result (2.86) + originX (5) = 7.86

newY = sin result (4.09) + originY (5) = 9.09

newX, newY = (7.86, 9.09)

Okay... so I have my new coordinates...

But those don't equate to what my new incrementing value of x and y should be based on my angle in incidence.

Code snippet: You can see that I'm hard coding the x,y increments (dragger.x += 2; )

        function tick() {
            var rand = Math.floor((Math.random()*10)+1);

            console.log("ticking..." + rand);
            if (dragger.x >= 400-20) {
                dragger.xDir = "right";         
            }
            if (dragger.x < 20) {
                dragger.xDir = "left";      
            }       
            if (dragger.y >= 150-20) {
                dragger.yDir = "up";
            }
            if (dragger.y < 20) {
                dragger.yDir = "down";
            }

            var oldX = dragger.y;
            var oldY = dragger.x;

            if (dragger.xDir == "left") {
                dragger.x += 2; 
            }
            else {
                dragger.x -= 2;
            }
            if (dragger.yDir == "up") {
                dragger.y -= 2;
            }
            else {
                dragger.y += 2;
            }
            //post update...
            var newX = dragger.y;
            var newY = dragger.x;                   

            var angle = getAngle(newX, oldX, newY, oldY)
            angle+=rand;

            $('#getAngle').empty();
            $('#getAngle').append("bounce angle (degrees): " + angle);


            //console.log(xDir);
            // update the stage:
            stage.update();
        }

        function getAngle(x2, x1, y2, y1) {             
            var deltaX = Math.abs(x2-x1);
            var deltaY = Math.abs(y2-y1);
            var radians = Math.atan2(deltaX, deltaY);
            var degrees = radians * (180/Math.PI);
            return degrees;
        }

Solution

  • This is a pretty interesting problem due to it's specificity.

    Making a ball bounce in a programming language can be done quite easily. Like this example.

    But clearly, your question is not about 'making it work'; you want explicit control over the coordinates and the angles such that you can alter them for whatever purpose you had in mind.

    Because I am quite vulnerable to nerd sniping, I dusted off my geometric skills and came up with the following scrap of pseudocode (I made this from scratch to make sure I have total control):

    Intuition

    enter image description here

    enter image description here

    Pseudocode

     theta    = starting angle
     a        = current x-coordinate of ball
     b        = current y-coordinate of ball
     quadrant = quadrant-direction to which ball is moving
    
     /> Determine number between 1 and 360: theta
     /> Calculate quadrant
    
       .> 0-90 :   quadrant 1:    horizontal: 90-a   vertical: b      alpha: 90 - theta
       .> 90-180:  quadrant 4:    horizontal: 90-a   vertical: 30-b   alpha: theta - 90
       .> 180-270: quadrant 3:    horizontal: a      vertical: 30-b   alpha: 270 - theta
       .> 270-360: quadrant 2:    horizontal: a      vertical: b      alpha: theta - 270
    
     /> Calculate distance to side       |
     /> Calculate distance to top/bottom |
    
       .> to side:       n(alpha) = horizontal/cos(alpha)
       .> to top/bottom: m(alpha) = vertical  /sin(alpha)
    
     /> Determine where ball is going to hit (n = side, m = top/bottom)
    
       .> n >= m  : bounces at top/bottom
       .> m >= n  : bounces at side
    
          .> switch (quadrant)
             .> 1 : n = right side     m = top      
             .> 2 : n = left side      m = top
             .> 3 : n = left side      m = bottom
             .> 4 : n = right side     m = bottom
    
     /> Calculate coordinates of hit
    
        /> Define new angle
    
          // Normally, angle of impact = angle of reflection
          // Let's define the angle of impact with respect to the origin (0,0)
    
       .> switch (quadrant)
             .> 1 : 
                     .> n >= m (at top/bottom) : x = a + vertical*tan(alpha)   y = 0                         theta = 180-theta
                     .> m >= n (at side)       : x = 90                        y = b - horizontal*tan(alpha) theta = 270+alpha
             .> 2 : 
                     .> n >= m (at top/bottom) : x = a - vertical/tan(alpha)   y = 0                         theta = 270-alpha
                     .> m >= n (at side)       : x = 0                         y = b - horizontal*tan(alpha) theta = 90-alpha
             .> 3 : 
                     .> n >= m (at top/bottom) : x = a - vertical/tan(alpha)   y = 30                        theta = 270+alpha
                     .> m >= n (at side)       : x = 0                         y = b + horizontal*tan(alpha) theta = 90+alpha
             .> 4 : 
                     .> n >= m (at top/bottom) : x = a + vertical/tan(alpha)   y = 30                        theta = 90-alpha
                     .> m >= n (at side)       : x = 90                        y = b + horizontal*tan(alpha) theta = 270-alpha
    
     /> Define new coordinates (for reusage of function)
    
        .> a = x
        .> b = y
    
       .> (optional) if you would like the angles to differ, enter extra term here:
    
             .> extra = ...
             .> theta = theta + extra
    

    Implementing this code will allow you to work with the easiness of degrees and still be able to determine the coordinates.

    It works as follows:

    • First determine the initial position of the ball (a,b) and it's initial direction (theta)

    • Now the program will calculate:

      • Where the ball is going to hit
      • What the coordinates of the ball at impact are
      • What the new angle of reflection is (this is the part you want to change)

    And then it starts over again to calculate the new hit.

    In JavaScript, the code would look like this:

    Code

    var width = 500;
    var height = 200;
    var extra = 0;
    var a;
    var b;
    var x;
    var y;
    var angle;
    var n;
    var m;
    var quadrant;
    var horizontal;
    var vertical;
    var alpha;
    var side;
    var topbottom;
    var sides;
    var i = 1;
    
      var txt=document.getElementById("info");
      txt.innerHTML="x: "+a+"<br>y: "+b+"<br>angle: "+angle+"<br>quadrant: "+quadrant;
    
    function buttonClick()
    {
      if (i == 1)
      {
        a = 75;
        b = 75;
        //determine first angle randonmly
        angle = Math.floor((Math.random()*360)+1);;
      } else
      {
        a = xcoord();
        b = ycoord();
      }
      var oldAngle = angle;  
      angle = findNewCoordinate(a, b, angle);
    
      sides = hitWhere();
    
      var txt=document.getElementById("info");
        txt.innerHTML="x: "+a+"<br>y: "+b+"<br>horizontal: "+horizontal+"<br>vertical: "+vertical+"<br>n: "+n+"<br>m: "+m+"<br>angle: "+oldAngle+"<br>alpha: "+alpha+"<br>quadrant: "+quadrant+"<br>side: "+topbottom+side+"<br>"+sides+"<br>"+i;
        i++;
    }
    
    function findNewCoordinate(a, b, angle)
    {
        if (angle >= 0 && angle < 90) { quadrant = 1; horizontal = width-a; vertical = b; alpha = (90 - angle); }
        else if (angle >= 90 && angle < 180) { quadrant = 4; horizontal = width-a; vertical = height-b; alpha = (angle-90);  }
        else if (angle >= 180 && angle < 270) { quadrant = 3; horizontal = a; vertical = height-b; alpha = (270-angle);  }
        else if (angle >= 270 && angle <= 360) { quadrant = 2; horizontal = a; vertical = b; alpha = (angle-270);  }
    
    
           var cosa = Math.cos(alpha * Math.PI / 180);
           var sina = Math.sin(alpha * Math.PI / 180);
           var tana = Math.tan(alpha * Math.PI / 180);
    
           var tant = Math.tan(angle * Math.PI / 180);
    
           n = horizontal/cosa;
           m = vertical/sina;
    
    
        switch (quadrant)
        {
            case 1:  
                if (m >= n) //hit at side
                {
                    y = b - horizontal*tana; 
                    x = width;               
                    angle = 270+alpha;       
                } else
                {
                    y = 0;                  
                    x = a + vertical*tant;   
                    angle = 180-angle;       
                } 
                side = "right side"; topbottom = "top";
                break;
            case 2:
                if (m >= n)  //hit at side
                {
                    y = b-horizontal*tana;   
                    x = 0;                   
                    angle = 90-alpha;        
                } else
                {
                    y = 0;                   
                    x = a - vertical/tana;   
                    angle = 270-alpha;       
                } 
                side = "left side"; topbottom = "top";
                break;
            case 3: side = "left side"; topbottom = "bottom";
                if (m >= n)  //hit at side
                {
                    x = 0;                   
                    y = b + tana*horizontal; 
                    angle = 90+alpha;        
                } else
                {
                    y = height;              
                    x = a - vertical/tana;   
                    angle = 270+alpha;       
                } break;
            case 4: side = "right side"; topbottom = "bottom";
                if (m >= n)  //hit at side
                {
                    y = b+horizontal*tana; 
                    x = width;             
                    angle = 270-alpha;     
                } else
                {
                    y = height;            
                    x = a + vertical/tana; 
                    angle = 90-alpha;      
                } break;
        }
    
        //add extra degrees to the angle (optional)
        angle += extra;
    
        context.beginPath();
        context.arc(a, b, 5, 0, Math.PI*2, true); 
        context.stroke();
        context.closePath();
        context.fill();
    
        drawLine(a,b,x,y);
    
        return angle;
    }
    

    Important

    Note that there are many more ways to make a bouncing program. But, because I tackled the question geometrically and without 'shortcuts', the unique characteristics of my program make it very easy for you to alter it to your likings:

    • You can give an extra angle to the bounce angle easily (use var extra).
    • You can change the movement of the ball at any time (at bounce, after bounce etc.)
    • You have explicit access to the coordinates of the ball
    • All units are conventional (in degrees and coordinates; hence easy to understand and intuitive).

    Also note that I did not make the program very concise because this simply wasn't my goal. I wanted to create a bouncing ball program that, although lenghty, is an exact realisation of the geometric intuition behind it.

    Demo

    You can find a demo of my program in this JSFiddle. Note that the beginning angle is determined randomly. Hence restarting the program will give a different angle.

    enter image description here

    Well, that's about it.

    Good luck with building the rest of your program!