maths: calculate Reflection angle

edited May 2015 in How To...

Dear all,

I know when we have a reflection on a wall it's xAdd = xAdd * -1; etc.

but my projectile / missile has no xadd / yadd but an angle it is follwing (45°, 273° etc.)

What is the formula to get a new angle from the old angle after an reflection please?

Or how many degrees do I have to turn / rotate my missile?

Thanks!

Best, Chrisir ;-)

Answers

  • I'm really not sure what you're asking. Can you post an MCVE? (NOT your whole sketch, just a small example that demonstrates what you're talking about.)

  • no, I'm just asking for the formula. A mcve makes no sense here.

    When a missile is flying northeast, angle is 45°. When it bounces on a wall in south-north-direction, the new angle is 315° (flying Nortwest now)

    Thank you!

    ;-)

  • Well, in that case you just subtract 90 degrees, right? If you're just talking about horizontal and vertical walls, then that's all you ever do. In fact, you don't need to consider angles at all- just reverse the X or Y speed.

    If you're talking about more complicated scenes with arbitrary lines at any angle, then all you need to do is reflect the missile. Google "angle of reflection" for a bunch of results.

    If you can't get that working, then I suggest posting an MCVE. After all, you should make it as easy as possible for people to help you, right?

  • Answer ✓

    First of all your angles are wrong since on computers the positive y direction is down. So east = 0°, south = 90°, west = 180° and north = 270° using these values mean the trig functions e.g. sin(), cos() etc work correctly with the screen coordinate system.

    If you are only interested in reflections against vertical and horizontal boundaries then this works.

    // All angles measured in degrees
    //        270
    //         |
    //   180 --+-- 0
    //         |
    //        90
    
    void setup() {
      reflectNS(30); // should be 330
      reflectNS(340); // should be 20
      reflectEW(30); // should be 150
      reflectEW(340); // should be 200
    }
    
    // Calculate the reflection angle in degrees against 
    // a vertical barrier
    // parameter a = incident angle
    // returns reflected angle
    float reflectNS(float a) {
      float r = 360 - a;
      while (r < 0) r += 360;
      println("NS incident angle "+ a + " reflected angle " + r);
      return r;
    }
    
    // Calculate the reflection angle in degrees against 
    // a horizontal barrier
    // parameter a = incident angle
    // returns reflected angle
    float reflectEW(float a) {
      float r = 180 - a;
      while (r < 0) r += 360;
      println("EW incident angle "+ a + " reflected angle " + r);
      return r;
    }
    

    Personally I would avoid angles altogether and use vectors for the direction of travel and also for the start and end of the barrier. Then calculate the reflected vector.

  • edited May 2015

    @KevinWorkman: I think you are right about mcve... thank you!

    @quarks: Thank you, I will have a look at it!

    I appreciate your comments and your help, from both of you.

    Thank you!

    Best, Chrisir ;-)

  • @quark: Is there a solution when I don't know if the line my missile collides with is horizontal or vertical?

    Is it just

    // All angles measured in degrees
    newAngle = angle + 90; 
    

    Thank you...

    Chrisir ;-)

  • edited May 2015

    @Chrisir: the book link I posted in this thread may be of interest. Scroll down to 'bouncing off an angle'. The code is actionscript, but the maths is the same and it's a good explanation (and a well written book in general) ;)

    @_vk - edited again to fix links. Editing from my PC: previous attempts to link from tablet didn't go so well :/

  • Is there a solution when I don't know if the line my missile collides with is horizontal or vertical?

    The answer is no.

    To know the reflection angle requires two things
    1) The orientation of the wall
    2) the angle of incidence

    As I said before I would avoid working with angles, they are not needed. In my AI for Games library there is not an angle to be seen.

    A barrier is specified by the end points i.e. start and end

    The missile is represented by a position and a vector which defines the direction of movement and the magnitude of the vector defines its speed of movement. This vector is the velocity of the missile.

    If the velocity vector intersects the barrier (line intersection calculation)
    1) then you calculate the surface normal for the barrier
    2) Use the surface normal vector and the missile velocity vector to calculate the reflected velocity vector.

  • Interesting question. The answers from Quark seem correct as far as they go.

    I found this http://www.mightydrake.com/Articles/ricochet.htm which I think adds to the answer.

    Going beyond that;

    You may want to consider the axial rotation of your ‘missile’, and the rotation/movement of the ‘wall’ if any.

    A non-winged missile will have some amount/degree of rotation for stability. A winged missile will have a rotational bias once the wings fall off.

    Think billiards, where the ‘English’ imparted to the cue ball affects its travel after it hits the object ball.

    And just for fun, you should also consider the rotational kinetic energy of your projectile relative to the initial frame of the ‘wall’. For instance, if the projectile is flying true to the planets axis of rotation or poles, the combined rotational affect is greater flying south assuming a clockwise rotation in the projectile. Of course, all this is influenced by distance from the pole of the launch point and the target area. At the equator the planetary rotational influence is effectively zero though the distance traveled changes from east to west.

    Also, for further torment, consider the elasticity of the ’wall’. Will the wall deform in a symmetrical and radial way? Or will it deform along inherent faults in an unknowable or otherwise random way?

    If you write code to address all of this, I’d love to see it.

  • i disagree with quark

    imagine you sit in a car and hit a wall and get reflected, say 60 degree, it doesn't matter if it is vert or horiz; your relative rotation is the same

  • Either way the car would be crushed :/

    What you describe is a 3d environment with the car travelling on the z axis and a wall on the xy plane. What quark describes is a 2d calculation. I'm not awake enough to explain the maths involved but the book I linked to above has a good section on trig and maths. You should be able to read it online ;)

  • If the wall is in the XY plane and the car moves in the Z direction (i.e. like a rocket) then there is no collision unless there is a ceiling ;)

    I believe we are discussing a 2D solution.

    imagine you sit in a car and hit a wall and get reflected, say 60 degree, it doesn't matter if it is vert or horiz; your relative rotation is the same

    I have no problems with you disagreeing with me but unfortunately you are wrong. In this picture the black line is the wall the red line is the car moving and the green line is the rebound.

    bounce

    Despite the fact that the car is traveling in the same direction in both cases the ABSOLUTE and the RELATIVE angle is different in both cases.

  • @quark: maybe I was being dense this morning, but I thought the z axis was movement to/away from you; at least in the processing environment? Anyway your diagram clearly describes the logic involved :)

  • but I thought the z axis was movement to/away from you; at least in the processing environment?

    thats true but changing the z value with a 2D renderer has no visual movement

  • edited May 2015

    Anyway I thought I would do an example showing how you could use vectors to represent the direction a ball moves and vectors to represent the walls.

    You will notice that the balls can 'escape' at the corners because the reflection is so close to the end of the line the reflected position ends up outside of the shape.

    Obviously I have used classes to represent walls and balls to make the program adaptable for other scenarios.

    // Framerate independant movement based on
    //  http://www.lagers.org.uk/how-to/ht-anim-01/index.html
    // The system time for this and the previous frame (in milliseconds)
    int currTime, prevTime;
    // The elapsed time since the last frame (in seconds)
    float deltaTime;
    
    Wall[] walls;
    Ball[] balls;
    
    void setup() {
      size(400, 400);
    
      makeWalls();
      makeBalls();
    
      // Initialise the timer
      currTime = prevTime = millis();
    }
    
    void draw() {
      // get the current time
      currTime = millis();
      // calculate the elapsed time in seconds
      deltaTime = (currTime - prevTime)/1000.0;
      // rember current time for the next frame
      prevTime = currTime;
    
      background(255);
      for (Wall wall : walls)
        wall.display();
      for (Ball ball : balls) {
        ball.update(deltaTime, walls);
        ball.move(deltaTime);
        ball.display();
      }
    }
    
    class Wall {
      PVector s = new PVector(); // one end of the wall
      PVector e = new PVector(); // the other end
      PVector perp = new PVector(); // Unit vector 90 deg to the wall
    
      // Set both these to 1.0 perfectly elastic rebounds
      // Try changing these values and see what happens.
      // Sensible values are
      // elasticity >=0 and <1.1
      // friction >=0 and <1.0
      float elasticty = 1.0;
      float friction = 1.0;
    
      Wall(float sx, float sy, float ex, float ey) {
        s.set(sx, sy);
        e.set(ex, ey);
        // Calculate the normalised perpendicular once and rmember it
        perp.x = -(ey - sy);
        perp.y = ex - sx;
        perp.normalize();
      }
    
      void display() {
        noFill();
        stroke(0);
        strokeWeight(1.6);
        line(s.x, s.y, e.x, e.y);
      }
    }
    
    class Ball {
      PVector pos = new PVector(); // Position
      PVector dir = new PVector(); // Direction (keep normalised)
      float speed;
      // The nextPos vector is a tempoary variable for checking
      // a bounce against a wall it is created once to avoid
      // excessive object creation in the update method
      PVector nextPos = new PVector(); // Position
      PVector intercept = new PVector(); // 
      // These two vector are used to calculate bounce
      // Create once use many times
      PVector u = new PVector();
      PVector w = new PVector();
    
      /**
       px, py initial position
       angle = initial angle in degrees
       s = speed in pixels per second
    
       NOTE This is the only bit that uses angles. It is for 
       convenience only and the angle is used to set the 
       initial diraction as a vector.
       */
      Ball(float px, float py, float angle, float s) {
        pos.set(px, py);
        dir.set(cos(radians(angle)), cos(radians(angle)));
        dir.normalize();
        speed = s;
        nextPos.set(pos);
      }
    
      void display() {
        noStroke();
        fill(200, 0, 255);
        ellipse(pos.x, pos.y, 8, 8);
      }
    
      // Will update direction of ball if it collides with a wall
      // returns -1 if no collision else it returns the array 
      // index of the wall it collided with
      int update(float dt, Wall[] walls) {
        // Calculate the next position as if it isn't going 
        nextPos.set(pos);
        nextPos.x += dir.x * speed * dt;
        nextPos.y += dir.y * speed * dt;
        if (walls != null) {
          for (int i = 0; i < walls.length; i++) {
            Wall wall = walls[i]; // simplify the code
            // Check against each wall in turn
            PVector inter = line_line_p(pos, nextPos, wall.s, wall.e);
            if (inter == null) // not this wall so continue to next wall
                continue;
            // We have hit the wall
            float timeToHit = PVector.dist(pos, inter)/speed;
            dt -= timeToHit;
            pos.set(inter);
            nextPos.set(pos);
            // So we have a hit calculate the new direction
            u.set(wall.perp);
            u.mult(dir.dot(wall.perp));
            w.set(dir);
            w.sub(u);
            // apply elasticty and friction
            // You can delete these lines if you want perfectly
            // elastic rebounds
            u.mult(wall.elasticty);
            w.mult(wall.friction);
            // new direction
            dir.set(w);
            dir.sub(u);
            speed *= dir.mag();
            dir.normalize();
            return i;
          }
        }
        return -1;
      }
    
    
      void move(float dt) {
        pos.add(dir.x * speed * dt, dir.y * speed * dt, 0);
      }
    }
    
    // Intersection code form my cookbook page 6
    // http://www.lagers.org.uk/how-to/ht-geom-01/index.html  
    final float ACCY = 1E-9;
    
    /**
     * Find the point of intersection between two finite lines.
     * This method uses PVector objects to represent the line end points.
     * [x0, y0]-[x1, y1] start and end of the first line
     * [x2, y2]-[x3, y3] start and end of the second line
     * if the two lines are parallel then null is returned, otherwise the intercept 
     * position is returned.
     */
    public PVector line_line_p(PVector v0, PVector v1, PVector v2, PVector v3) {
      PVector intercept = null;
    
      float f1 = (v1.x - v0.x);
      float g1 = (v1.y - v0.y);
      float f2 = (v3.x - v2.x);
      float g2 = (v3.y - v2.y);
      float f1g2 = f1 * g2;
      float f2g1 = f2 * g1;
      float det = f2g1 - f1g2;
    
      if (abs(det) > ACCY) {
        float s = (f2*(v2.y - v0.y) - g2*(v2.x - v0.x))/ det;
        float t = (f1*(v2.y - v0.y) - g1*(v2.x - v0.x))/ det;
        if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
          intercept = new PVector(v0.x + f1 * s, v0.y + g1 * s);
      }
      return intercept;
    }
    
    void makeWalls() {
      walls = new Wall[] { 
        // Outer walls
        new Wall(25, 25, 100, 25), 
        new Wall(100, 25, 350, 50), 
        new Wall(350, 50, 339, 350), 
        new Wall(339, 350, 100, 375), 
        new Wall(100, 375, 25, 100), 
        new Wall(25, 100, 25, 25), 
        // Inner walls 
        new Wall(150, 150, 275, 125), 
        new Wall(275, 125, 290, 245), 
        new Wall(290, 245, 150, 150)
        };
      }
    
      void makeBalls() {
        balls = new Ball[] {
          new Ball(125, 75, 330, 177), 
          new Ball(125, 210, 299, 137), 
          new Ball(265, 300, 77, 231),
        };
      }
    
  • if I find time I make

    a mcve

  • edited May 2015

    @Chrisir, your example with the car can happen in the real world because real objects have rotation. It is possible to add rotation to these kinds of equations but then they become quite complicated

    Edit: Just realized OLAM gave the same example below with billiards... opps

    As an example of the difference, a tennis player can serve the ball by just hitting it or but moving their racquet while they are hitting it causing the ball to spin. In the first case when the ball hits the ground and it has little or no spin then it follows the equations Quark is talking about. In the second case when the ball is spinning when it hits the ground modeling its movement is more difficult

  • asimes is correct, my code does not take into account angular momentum (i.e. ball spin) although you can play with the elasticity and friction of the wall :)

  • BTW if you only have vertical and horizontal barriers and want to use angles then use my original equations. There is nothing wrong with that you just have to know which type of barrier you are rebounding off.
    In fact the vector solution maybe more complicated than you need :)

  • this is my mcve if you like

    // Turtle Graphic as in LOGO
    
    float TurtleX;  // position 
    float TurtleY;
    int direction;  // in degree 
    
    color turtleColor;  // color 
    
    // ================================================ 
    // main functions 
    
    void setup() {
    
      size(600, 600 );
      background(110);
    
      TurtleX = width / 2;
      TurtleY = height / 2;
      direction = 0;
      turtleColor = color(255, 0, 0 );
    }
    
    void draw()
    {
      background(110);
      Square(240);
    }
    
    // =============================================
    // Begin Turtle-Programs 
    
    void Square(int lengthToWalk)
    {
      for (int i = 0; i < 4; i++) 
      {   
        vw (lengthToWalk);
        ri (90); // degrees
      }
    }
    
    // ============================================
    // Begin Turtle-Basics
    
    void vw(int lengthToWalk) {
    
      // forward
      float xNew;
      float yNew;
      float directionRadians;
    
      directionRadians = radians(direction);
    
      xNew = ( TurtleX + (lengthToWalk * cos(directionRadians)));
      yNew = ( TurtleY + (lengthToWalk * sin(directionRadians)));
    
      stroke (turtleColor);
      line ( TurtleX, TurtleY, xNew, yNew);
    
      TurtleX = xNew;
      TurtleY = yNew;
    }
    
    void ri(int angle)
    {   // right
      direction += angle;
    }
    //
    
  • or see this as my mcve, it does really bounce

    // Turtle Graphic as in LOGO
    
    float TurtleX;  // position 
    float TurtleY;
    int direction;  // in degree 
    
    color turtleColor;  // color 
    
    // ================================================ 
    // main functions 
    
    void setup() {
    
      size(600, 600 );
      background(110);
      noFill(); 
      ellipseMode(CENTER);  // Set ellipseMode to CENTER
      //
      initTurtle();
    }
    
    void draw()
    {
      background(110);
      if (!(TurtleX>100))
        ri(22);  
    
      if (!(TurtleX<width-100))
        le(22);
    
      if (!(TurtleY>100&&TurtleY<width-100))
        ri(22);
    
      // WALK 
      fw(2);
    
      showTurtle();
    }
    
    void drawOLD()
    {
      background(110);
      initTurtle();
      square(240);
      showTurtle();
    }
    
    // =============================================
    // Begin Turtle-Programs 
    
    void square(int lengthToWalk)
    {
      for (int i = 0; i < 4; i++) 
      {   
        fw (lengthToWalk);
        ri (90); // degrees
      }
    }
    
    // ============================================
    // Begin Turtle-Basics
    
    void initTurtle() {
      TurtleX = width / 2;
      TurtleY = height / 2;
      direction = 0;
      turtleColor = color(255, 0, 0 );
    }
    
    void showTurtle() {
      stroke(0, 255, 0);
      ellipse (TurtleX, TurtleY, 8, 8 );
    }
    
    void fw(int lengthToWalk) {
    
      // forward
      float xNew;
      float yNew;
      float directionRadians;
    
      directionRadians = radians(direction);
    
      xNew = ( TurtleX + (lengthToWalk * cos(directionRadians)));
      yNew = ( TurtleY + (lengthToWalk * sin(directionRadians)));
    
      stroke (turtleColor);
      line ( TurtleX, TurtleY, xNew, yNew);
    
      TurtleX = xNew;
      TurtleY = yNew;
    }
    
    void ri(int angle)
    {   // right
      direction += angle;
    }
    
    void le(int angle)
    {   // left
      ri(-angle);
    }
    //
    
  • @quark could you please look at my mcve for this...

    thank you...

    ;-)

  • Just looking at your latest MCVE but I am not sure what I am looking for :-?

    If you are implementing turtle graphics (as in LOGO) surely you don't need reflection. :-?

  • well, this is part of a bigger project.

    and yes, I wanted to implement a reflection into turtle graphics (as in LOGO).

    Just as you can see from the mcve, how can the turtle be reflected from the wall as if it were a ball?

    thes means we have to use its angle and the rotation RE and LI commands.

    thank you!

    Chrisir ;-)

  • Ok but is this what you expected?

  • that's what my latest mcve is doing, right?

    No, that's not what I expect.

    That's the wrong behaviour I have now and which I want to improve.

    That's why I came here and ask this.

    The mcve just shows that I use an angle (and want to stich with it) and not PVectors or speedX,speedY and so... and that I want to make a good reflection in the frame of that mcve.

    ;-)

  • The clue was in my first post which did the reflection angles. Need to modify it because it used the incident angle rather than the direction of travel. After removing unused code we get.

    // Turtle Graphic as in LOGO
    
    float TurtleX;  // position 
    float TurtleY;
    int direction;  // in degree 
    
    color turtleColor;  // color 
    
    // ================================================ 
    // main functions 
    
    void setup() {
      size(600, 600 );
      background(110);
      noFill(); 
      ellipseMode(CENTER);  // Set ellipseMode to CENTER
      //
      initTurtle();
      noLoop();
    }
    
    void draw()
    {
      background(110);
      // WALK 
      fw(2);
    
      if (TurtleX < 100) {
        direction = horzReflect(direction);
        TurtleX = 100;
      }  
    
      if (TurtleX > width-100) {
        direction = horzReflect(direction);
        TurtleX = width - 100;
      }
    
      if (TurtleY < 100) {
        direction = vertReflect(direction);
        TurtleY = 100;
      }
      if (TurtleY > height-100) {
        direction = vertReflect(direction); 
        TurtleY = height - 100;
      } 
    
      showTurtle();
    }
    
    void mouseClicked() {
      loop();
    }
    
    // ============================================
    // Begin Turtle-Basics
    
    void initTurtle() {
      TurtleX = width / 2;
      TurtleY = height / 2;
      direction = 20;
      turtleColor = color(255, 0, 0 );
    }
    
    void showTurtle() {
      stroke(0, 255, 0);
      ellipse (TurtleX, TurtleY, 8, 8 );
    }
    
    void fw(int lengthToWalk) { // forward
      float xNew;
      float yNew;
      float directionRadians;
    
      directionRadians = radians(direction);
    
      xNew = ( TurtleX + (lengthToWalk * cos(directionRadians)));
      yNew = ( TurtleY - (lengthToWalk * sin(directionRadians)));
    
      stroke (turtleColor);
      line ( TurtleX, TurtleY, xNew, yNew);
    
      TurtleX = xNew;
      TurtleY = yNew;
    }
    
    void ri(int angle) {   // right
      direction += angle;
    }
    
    void le(int angle) {   // left
      ri(-angle);
    }
    
    int vertReflect(int a) {
      int r = 360 - a;
      while (r < 0) r += 360;
      return r;
    }
    
    int horzReflect(int a) {
      int r = 180 - a;
      while (r < 0) r += 360;
      return r;
    }
    
Sign In or Register to comment.