Simple Circle/Line Physics Library

User projects written in or related to FreeBASIC.
qbworker
Posts: 73
Joined: Jan 14, 2011 2:34

Simple Circle/Line Physics Library

Postby qbworker » Feb 09, 2011 19:58

This lib requires the FB-ext lib installed to compile.

First, the main lib:

physics.bi:

Code: Select all

#Include "fbgfx.bi"
#Include Once "ext/math/line2.bi"
#Include Once "ext/math/projections.bi"
#Include Once "ext/math/vector2.bi"

Using fb

Type vector2d As ext.math.fbext_Vector2( ((Single)) )
Type line2d As ext.math.fbext_Line2( ((Single)) )

Type circle2d
    posi As vector2d
    vel As vector2d
    radius As Integer
    ccolor As Integer
    maxvelocity As Integer
    Declare Function bounce (ByRef ball As circle2d) As Integer
    Declare Function bounce Overload (ByRef wall As line2d) As Integer
    Declare Sub halt()
    Declare Sub display(ByVal xx_ As single, ByVal yy_ As Single)
    Declare Constructor(x As Single, y As Single, rad As Integer, colr As Integer, maxvel As Integer)
    Declare Constructor()
End Type

Declare Function lineNormal(ByVal l As line2d, ByVal num As Integer) As vector2d

function circle2d.bounce (byref wall as line2d) as integer
    dim as vector2d impulse,impact,xpoint,normal,normal1,normal2,blah1,blah2
   
    xpoint = ext.math.ClampProjectedPoint(wall, this.posi)
   
    dim as single d
   
    d = this.posi.distance(xpoint)
   
    if d < this.radius Then

        normal1 = lineNormal(wall, 1)
        normal2 = lineNormal(wall, 2)
        blah1 = xpoint + normal1
        blah2 = xpoint + normal2
        if this.posi.distance(blah1) < this.posi.distance(blah2) then normal = normal1
        if this.posi.distance(blah1) > this.posi.distance(blah2) then normal = normal2
       
        impact.x = -this.vel.x - this.vel.x
        impact.y = -this.vel.y - this.vel.y

        impulse = normal
       
        dim as single comp, leng, s
        comp = impact.dot(impulse)
        leng = impulse.magnitude()
        s = (comp)/leng
        impulse = impulse * s
       
        this.vel = this.vel + impulse
       
        this.posi = this.posi + normal * (this.radius - d)
       
        return 1
    end if
   
    return 0
end function

function circle2d.bounce(byref ball as circle2d) As Integer
    if this.posi.distance(ball.posi) <= this.radius + ball.radius then
        dim as vector2d impact, impulse
        dim impactspeed as double
        impact = this.vel - ball.vel
        impulse = this.posi - ball.posi
        impulse.normalize
        impactspeed = impulse.dot(impact)
        impulse = impulse * impactspeed
        this.vel = this.vel + -(impulse)
        ball.vel = ball.vel + impulse
        return 1
    end if
    return 0
end Function

Sub circle2d.halt()
   this.vel.x = 0
   this.vel.y = 0
End Sub

Sub circle2d.display(ByVal xx_ As single, ByVal yy_ As Single)
   Circle(xx_, yy_), this.radius, this.ccolor,,,,f
End Sub

Constructor circle2d(x_ As Single, y_ As Single, rad_ As Integer, colr_ As Integer, maxvel_ As Integer)
   this.posi.x = x_
   this.posi.y = y_
   this.radius = rad_
   this.ccolor = colr_
   this.maxvelocity = maxvel_
End Constructor

Constructor circle2d()
   this.posi.x = 0
   this.posi.y = 0
   this.radius = 0
   this.ccolor = 0
   this.maxvelocity = 0
End Constructor

Function lineNormal(ByVal l As line2d, ByVal num As Integer) As vector2d
   Dim As vector2d normal1, normal2
   
      normal1.x = -(l.b.y-l.a.y)
      normal1.y = (l.b.x-l.a.x)
       
     normal2.x = (l.b.y-l.a.y)
      normal2.y = -(l.b.x-l.a.x)
   
     normal1.normalize
     normal2.normalize
     
    If num = 1 Then Return normal1
    If num = 2 Then Return normal2
End Function


A simple program to test angled line collisions:

Controls:
Arrow keys move the blue circle,
Insert stops the blue circle,
space for new random line

test.bas:

Code: Select all

#Include "fbgfx.bi"
#Include "physics.bi"

Using fb

Const accel = .5
Const friction = .02

Dim ball(1 To 6) As circle2d
Dim As vector2d screenmax, blahx, blahy
Dim As line2d l, wall(1 To 4)
Dim As Integer i, j, x, y

Screen 19
Screeninfo x, y
Randomize Timer, 3

screenmax.x = x:screenmax.y = y

With wall(1)
   .a.x = 0
   .a.y = 0
   .b.x = screenmax.x
   .b.y = 0
End With

With wall(2)
   .a.x = screenmax.x
   .a.y = 0
   .b.x = screenmax.x
   .b.y = screenmax.y
End With

With wall(3)
   .a.x = screenmax.x
   .a.y = screenmax.y
   .b.x = 0
   .b.y = screenmax.y
End With

With wall(4)
   .a.x = 0
   .a.y = screenmax.y
   .b.x = 0
   .b.y = 0
End With

For i = 1 To 6
   With ball(i)
      .posi.x = CInt(Rnd * screenmax.x)
      .posi.y = CInt(Rnd * screenmax.y)
      .radius = CInt(Rnd * 20) + 10
      .ccolor = i
   End With
Next

Do
    With l
             .a.x = Rnd * screenmax.x
            .a.y = Rnd * screenmax.y
            .b.x = Rnd * screenmax.x
            .b.y = Rnd * screenmax.y
    End With
   
    Do
        Screenlock
        Cls
        With l
            Line (.a.x, .a.y)-(.b.x, .b.y), 15
        End With
        For i = 1 To 6
           ball(i).display(ball(i).posi.x, ball(i).posi.y)
        Next i
        ScreenSync
        Screenunlock
        If Multikey(SC_UP) Then ball(1).vel.y = ball(1).vel.y - accel
        If Multikey(SC_RIGHT) Then ball(1).vel.x = ball(1).vel.x + accel
        If Multikey(SC_DOWN) Then ball(1).vel.y = ball(1).vel.y + accel
        If Multikey(SC_LEFT) Then ball(1).vel.x = ball(1).vel.x - accel
        If Multikey(SC_INSERT) Then ball(1).halt
       For i = 1 To 6
          ball(i).posi.x += ball(i).vel.x
          ball(i).posi.y += ball(i).vel.y
          ball(i).vel.x *= (1-friction)
          ball(i).vel.y *= (1-friction)
          ball(i).bounce(l)
          For j = 1 To 4
             ball(i).bounce(wall(j))
          Next
          For j = 1 To 6
             ball(i).bounce(ball(j))
          Next j
       Next
    Loop Until Multikey(SC_ESCAPE) Or Multikey(SC_SPACE)
Loop Until Multikey(SC_ESCAPE)


This libby does not deal with mass, so the ball to ball collision is kinda weird. I would have added it, but I wanted to keep the code as simple as possible. If you need mass, it should be simple enough to add.

Edit:
The lib is mostly finished. The code has been updated. If there are any bugs, just post them here.

Edit:
Finished. Code updated.
Last edited by qbworker on May 04, 2011 18:21, edited 8 times in total.
qbworker
Posts: 73
Joined: Jan 14, 2011 2:34

Postby qbworker » Feb 09, 2011 20:13

Oh and one question.
You may have noticed in the code the redundancy of this:

ball(1).yvelocity = ball(1).yvelocity - accelerate

I was wondering why i just can't do this:

ball(1).yvelocity -= accelerate

It throws an error and says that I don't have a property called yvelocity and that it expects an '=' sign when I do the second option

Why can't I use that operator with properties? It would save a lot of time as you can very well see.
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Postby rolliebollocks » Feb 09, 2011 20:33

You forgot the bubble world file. The Set properties apparently can't set and decrement at the same time. I dunno. I usually use worker variables for something like that. Properties tend to be overkill in such situations.
qbworker
Posts: 73
Joined: Jan 14, 2011 2:34

Postby qbworker » Feb 09, 2011 20:37

Sorry I renamed project but forgot to rename the include. it should be:

Code: Select all

#include "circle.bi"


fixed first post.

If you 'dunno' then maybe it should be added in version 0.21.2 or 0.22, yes?
Last edited by qbworker on Feb 10, 2011 16:20, edited 1 time in total.
TJF
Posts: 3604
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Postby TJF » Feb 10, 2011 6:35

qbworker wrote:Why can't I use that operator with properties? It would save a lot of time as you can very well see.

You can replace the PROPERTY keyword by the SUB command. This will make clear why the operator doesn't (and shouldn't) work.

To save time you shouldn't use a SUB for just setting a variable. Calling a SUB means a lot of overhead to handle the stack issues.
qbworker
Posts: 73
Joined: Jan 14, 2011 2:34

Postby qbworker » Feb 10, 2011 16:18

I guess so, but it would sure save a lot of time like when you want to automatically check that the input value hasn't gone out of bounds.
Also, there's the issue of notation. For instance here's how the set property code looks like:

foo.x = 1000

And here's how the sub code would look like:

foo.x(1000)

The whole idea for a property is so that you can act as if you were changing the value directly, but underhandedly preforming any and all necessary checks and/or modifications. A sub would work, but it wouldn't be quite the same. You have a point there though, if you don't need to perform repeated out-of-bound checks when you set the value, then there is absolutely no reason for a private variable with public get/set properties. It would be a tutorial in overkills!

Anyway, I'm still working on the circle thingy, and I'll be releasing a version where you get to join in the fun(and all the other circles too!) soon.
TJF
Posts: 3604
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Postby TJF » Feb 10, 2011 18:56

qbworker wrote:A sub would work, but it wouldn't be quite the same.

Why? What is the difference?
qbworker
Posts: 73
Joined: Jan 14, 2011 2:34

Postby qbworker » Feb 10, 2011 20:14

The difference is:

(quote from my last post)
property code:

foo.x = 1000

And here's how the sub code would look like:

foo.x(1000)



I'm being redundant for the sake of clarity.
TJF
Posts: 3604
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Postby TJF » Feb 11, 2011 6:44

qbworker wrote:The difference is:

(quote from my last post)
property code:

foo.x = 1000

And here's how the sub code would look like:

foo.x(1000)



I'm being redundant for the sake of clarity.

You're right, sorry. I overred that you overloaded the PROPERTY:

Code: Select all

        Declare Property x(ByVal xx_ As Double)
        Declare Property x() As Double

From my point of view your code isn't well aranged yet.
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Postby rolliebollocks » Feb 11, 2011 13:04

Code: Select all

Function distance(ByVal pointa As point2d, ByVal pointb As point2d) As Double
        Return Int(Sqr((pointb.x - pointa.x) ^ 2 + (pointb.y - pointa.y) ^ 2))
End Function


Couple points of concern (no pun) - firstly you are using INT but returning a double. You can cut the INT and just return an integer or just cut the INT, but either way, it's slowing you down. Also, I tend to send UDT's byref because it tends to be faster that passing large chunks of data to a procedure like that. Those are two things there I would consider fixing, and also, because it is the default manner in which they're passed. For things like integers/doubles/singles/pointers, I send them byval because there you get a speed boost.
qbworker
Posts: 73
Joined: Jan 14, 2011 2:34

Postby qbworker » Feb 11, 2011 16:15

Yea I just started on a collision detect algo for this thing to add walls and I ran into that. So I'll be fixing that in the first post. As for the byval/byref stuff, When this thing gets so slow that it moves like a seven year itch, I might consider looking into that stuff. I do that because I tend to make the kind of mistakes that it takes all/half a day to solve and when i'm through it's because I accidentally forgot which by*(ref/val) thing I had up there. Thanks for the tip though!

About the collision algo, I was wondering, Is it mathematically possible to know the height of a triangle if you know the distance of the sides? If so, that would help a lot. Otherwise I would like some pointers to any algos for this besides the kind where you have all the points of the line in an array and you check for each of those.
Prime Productions
Posts: 147
Joined: May 24, 2009 23:13
Location: Texas, United States, Planet Earth
Contact:

Postby Prime Productions » Feb 11, 2011 17:28

qbworker wrote: About the collision algo, I was wondering, Is it mathematically possible to know the height of a triangle if you know the distance of the sides? If so, that would help a lot.


Yes, with Heron's formula:

You first need to find the area of the triangle without knowing it's height, which means that we can't use the common Area = 1/2bh.

So we use Heron's formula, which is: Area = SQRT(s(s-a)(s-b)(s-c))

(A, B, and C are the sides of the triangle.)

S stands for semiperimeter, whish is half of the trangles perimeter. Obviously, if we know the triangles sides, than we know the perimeter. Finding the semiperimeter is simply dividing by 2 so: S = (a + b + c) / 2

Then once we know S we plug that into Heron's formula and get the area of the triangle. After that, we can rearrange the Area = 1/2bh formula to get:

Height = 2 * Area / Base

(There is another way to do it to get rid of the slow square root, but you'll have to use a sine (also slow) which will require that you know an angle.)

The following is the FreeBASIC code to apply all of this:

Code: Select all

DIM S AS SINGLE
DIM A AS SINGLE
DIM B AS SINGLE
DIM C AS SINGLE
DIM Area AS SINGLE
DIM Height AS SINGLE

'The following values are what the known sides of your triangle are:
A = 4
B = 5
C = 6

'Calculate the semiperimeter:

S = (A + B + C) / 2

'Apply Heron's formula to get the area:

Area = SQR(S * (S - A) * (S - B) * (S - C))

'Now rearrange the formula Area = 1/2 Base * Height and get the Height

Height = 2 * Area / A 'Divide by whichever side is the base of the triangle.

Print Height

Sleep 'You now got the height!


You might have already found an answer to this, but if not I hope this helps, and good luck!

David
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Postby rolliebollocks » Feb 11, 2011 17:31

That's not enough information unless the triangles are right triangles, then you can know two sides and use the Pythagorean theorem/distance formula to calculate the last. Assuming you know one side and the hypotenuse:

hyp^2 = s1^2 + s2^2

Otherwise you would have to have at least one of the angles and then use sin/cos to calculate the shadow/height.

I wrote this reference awhile back here:

http://imortisoft.no-ip.org/b2b/?Issue_%231:Triangles,_Circles_and_Vectors:_A_Brief_Reference_for_Programmers

It explains everything about that kind of thing.
Prime Productions
Posts: 147
Joined: May 24, 2009 23:13
Location: Texas, United States, Planet Earth
Contact:

Postby Prime Productions » Feb 11, 2011 17:35

rolliebollocks wrote:That's not enough information unless the triangles are right triangles, then you can know two sides and use the Pythagorean theorem/distance formula to calculate the last. Assuming you know one side and the hypotenuse:

hyp^2 = s1^2 + s2^2

Otherwise you would have to have at least one of the angles and then use sin/cos to calculate the shadow/height.

I wrote this reference awhile back here:

http://imortisoft.no-ip.org/b2b/?Issue_%231:Triangles,_Circles_and_Vectors:_A_Brief_Reference_for_Programmers

It explains everything about that kind of thing.


Yes it is enough info rolliebollocks, check my previous post. (Don't listen to rollie qbworker, lol)
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Postby rolliebollocks » Feb 11, 2011 18:50

Well, a triangle can have multiple heights for the same perimeter. That's why I figured it wasn't possible. I didn't know about Heron's formula. That's a good one.

The frivolity of this method is the fact that he's going to be calculating the distance between each of the vertices via distance formula, then performing Heron's formula on the perimiter, just to get the height.

The fastest way to do it, is by sorting the vertices, and then subtracting the upper bound from the lower bound:

Code: Select all

Property Triangle2d.Height () as units
    dim as integer lby=p(0).y, uby=p(0).y
   
    for i as integer = 1 to 2
        if p(i).y > uby then uby = p(i).y
        if p(i).y < lby then lby = p(i).y
    next
   
    return uby-lby
end Property


Heron's formula would not be viable in a loop... Way too many Sqare Roots... Listen to me, Prime Production's method is not a viable solution...

Return to “Projects”

Who is online

Users browsing this forum: No registered users and 13 guests