summaryrefslogtreecommitdiffhomepage
path: root/samples/04_physics_and_collisions/08_bouncing_on_collision/app/peg.rb
blob: 52045de01c73a181f2e2d5aecf8c1f098be29348 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
class Peg
  def initialize(x, y, block_size)
    @x = x                    # x cordinate of the LEFT side of the peg
    @y = y                    # y cordinate of the RIGHT side of the peg
    @block_size = block_size  # diameter of the peg

    @radius = @block_size/2.0 # radius of the peg
    @center = {               # cordinatees of the CENTER of the peg
      x: @x+@block_size/2.0,
      y: @y+@block_size/2.0
    }

    @r = 255 # color of the peg
    @g = 0
    @b = 0

    @velocity = {x: 2, y: 0}
  end

  def draw args
    args.outputs.sprites << [ # draw the peg according to the @x, @y, @radius, and the RGB
      @x,
      @y,
      @radius*2.0,
      @radius*2.0,
      "sprites/circle-white.png",
      0,
      255,
      @r,    #r
      @g,    #g
      @b   #b
    ]
  end


  def calc args
    if collisionWithBounce? args # if the is a collision with the bouncing ball
      collide args
      @r = 0
      @b = 0
      @g = 255
    else
    end
  end


  # do two circles (the ball and this peg) intersect
  def collisionWithBounce? args
    squareDistance = (  # the squared distance between the ball's center and this peg's center
      (args.state.ball.center.x - @center.x) ** 2.0 +
      (args.state.ball.center.y - @center.y) ** 2.0
    )
    radiusSum = (  # the sum of the radius squared of the this peg and the ball
      (args.state.ball.radius + @radius) ** 2.0
    )
    # if the squareDistance is less or equal to radiusSum, then there is a radial intersection between the ball and this peg
    return (squareDistance <= radiusSum)
  end

  # ! The following links explain the getRepelMagnitude function !
  # https://raw.githubusercontent.com/DragonRuby/dragonruby-game-toolkit-physics/master/docs/docImages/LinearCollider_4.png
  # https://raw.githubusercontent.com/DragonRuby/dragonruby-game-toolkit-physics/master/docs/docImages/LinearCollider_5.png
  # https://github.com/DragonRuby/dragonruby-game-toolkit-physics/blob/master/docs/LinearCollider.md
  def getRepelMagnitude (args, fbx, fby, vrx, vry, ballMag)
    a = fbx ; b = vrx ; c = fby
    d = vry ; e = ballMag
    if b**2 + d**2 == 0
      #unexpected
    end

    x1 = (-a*b+-c*d + (e**2 * b**2 - b**2 * c**2 + 2*a*b*c*d + e**2 + d**2 - a**2 * d**2)**0.5)/(b**2 + d**2)
    x2 = -((a*b + c*d + (e**2 * b**2 - b**2 * c**2 + 2*a*b*c*d + e**2 * d**2 - a**2 * d**2)**0.5)/(b**2 + d**2))

    err = 0.00001
    o = ((fbx + x1*vrx)**2 + (fby + x1*vry)**2 ) ** 0.5
    p = ((fbx + x2*vrx)**2 + (fby + x2*vry)**2 ) ** 0.5
    r = 0

    if (ballMag >= o-err and ballMag <= o+err)
      r = x1
    elsif (ballMag >= p-err and ballMag <= p+err)
      r = x2
    else
      #unexpected
    end

    if (args.state.ball.center.x > @center.x)
      return x2*-1
    end

    return x2

    #return r
  end

  #this sets the new velocity of the ball once it has collided with this peg
  def collide args
    normalOfRCCollision = [                                                     #this is the normal of the collision in COMPONENT FORM
      {x: @center.x, y: @center.y},                                             #see https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.mathscard.co.uk%2Fonline%2Fcircle-coordinate-geometry%2F&psig=AOvVaw2GcD-e2-nJR_IUKpw3hO98&ust=1605731315521000&source=images&cd=vfe&ved=0CAIQjRxqFwoTCMjBo7e1iu0CFQAAAAAdAAAAABAD
      {x: args.state.ball.center.x, y: args.state.ball.center.y},
    ]

    normalSlope = (                                                             #normalSlope is the slope of normalOfRCCollision
      (normalOfRCCollision[1].y - normalOfRCCollision[0].y) /
      (normalOfRCCollision[1].x - normalOfRCCollision[0].x)
    )
    slope = normalSlope**-1.0 * -1                                              # slope is the slope of the tangent
    # args.state.display_value = slope
    pointA = {                                                                  # pointA and pointB are using the var slope to tangent in COMPONENT FORM
      x: args.state.ball.center.x-1,
      y: -(slope-args.state.ball.center.y)
    }
    pointB = {
      x: args.state.ball.center.x+1,
      y: slope+args.state.ball.center.y
    }

    perpVect = {x: pointB.x - pointA.x, y:pointB.y - pointA.y}                  # perpVect is to be VECTOR of the perpendicular tangent
    mag  = (perpVect.x**2 + perpVect.y**2)**0.5                                 # find the magniude of the perpVect
    perpVect = {x: perpVect.x/(mag), y: perpVect.y/(mag)}                       # divide the perpVect by the magniude to make it a unit vector
    perpVect = {x: -perpVect.y, y: perpVect.x}                                  # swap the x and y and multiply by -1 to make the vector perpendicular
    args.state.display_value = perpVect
    if perpVect.y > 0                                                           #ensure perpVect points upward
      perpVect = {x: perpVect.x*-1, y: perpVect.y*-1}
    end

    previousPosition = {                                                        # calculate an ESTIMATE of the previousPosition of the ball
      x:args.state.ball.center.x-args.state.ball.velocity.x,
      y:args.state.ball.center.y-args.state.ball.velocity.y
    }

    yInterc = pointA.y + -slope*pointA.x
    if slope == INFINITY                                                        # the perpVect presently either points in the correct dirrection or it is 180 degrees off we need to correct this
      if previousPosition.x < pointA.x
        perpVect = {x: perpVect.x*-1, y: perpVect.y*-1}
        yInterc = -INFINITY
      end
    elsif previousPosition.y < slope*previousPosition.x + yInterc               # check if ball is bellow or above the collider to determine if perpVect is - or +
      perpVect = {x: perpVect.x*-1, y: perpVect.y*-1}
    end

    velocityMag =                                                               # the current velocity magnitude of the ball
      (args.state.ball.velocity.x**2 + args.state.ball.velocity.y**2)**0.5
    theta_ball=
      Math.atan2(args.state.ball.velocity.y,args.state.ball.velocity.x)         #the angle of the ball's velocity
    theta_repel=
      Math.atan2(args.state.ball.center.y,args.state.ball.center.x)             #the angle of the repelling force(perpVect)

    fbx = velocityMag * Math.cos(theta_ball)                                    #the x component of the ball's velocity
    fby = velocityMag * Math.sin(theta_ball)                                    #the y component of the ball's velocity
    repelMag = getRepelMagnitude(                                               # the magniude of the collision vector
      args,
      fbx,
      fby,
      perpVect.x,
      perpVect.y,
      (args.state.ball.velocity.x**2 + args.state.ball.velocity.y**2)**0.5
    )
    frx = repelMag* Math.cos(theta_repel)                                       #the x component of the repel's velocity | magnitude is set to twice of fbx
    fry = repelMag* Math.sin(theta_repel)                                       #the y component of the repel's velocity | magnitude is set to twice of fby

    fsumx = fbx+frx                            # sum of x forces
    fsumy = fby+fry                            # sum of y forces
    fr = velocityMag                           # fr is the resulting magnitude
    thetaNew = Math.atan2(fsumy, fsumx)        # thetaNew is the resulting angle
    xnew = fr*Math.cos(thetaNew)               # resulting x velocity
    ynew = fr*Math.sin(thetaNew)               # resulting y velocity
    if (args.state.ball.center.x >= @center.x) # this is necessary for the ball colliding on the right side of the peg
      xnew=xnew.abs
    end

    args.state.ball.velocity.x = xnew                                           # set the x-velocity to the new velocity
    if args.state.ball.center.y > @center.y                                     # if the ball is above the middle of the peg we need to temporarily ignore some of the gravity
      args.state.ball.velocity.y = ynew + GRAVITY * 0.01
    else
      args.state.ball.velocity.y = ynew - GRAVITY * 0.01                        # if the ball is bellow the middle of the peg we need to temporarily increase the power of the gravity
    end

    args.state.ball.center.x+= args.state.ball.velocity.x                       # update the position of the ball so it never looks like the ball is intersecting the circle
    args.state.ball.center.y+= args.state.ball.velocity.y
  end
end