## 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

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 fbType 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 TypeDeclare Function lineNormal(ByVal l As line2d, ByVal num As Integer) As vector2dfunction 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 0end functionfunction 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 0end FunctionSub circle2d.halt()   this.vel.x = 0   this.vel.y = 0End SubSub circle2d.display(ByVal xx_ As single, ByVal yy_ As Single)   Circle(xx_, yy_), this.radius, this.ccolor,,,,fEnd SubConstructor 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 ConstructorConstructor circle2d()   this.posi.x = 0   this.posi.y = 0   this.radius = 0   this.ccolor = 0   this.maxvelocity = 0End ConstructorFunction 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 normal2End 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 fbConst accel = .5Const friction = .02Dim ball(1 To 6) As circle2dDim As vector2d screenmax, blahx, blahyDim As line2d l, wall(1 To 4)Dim As Integer i, j, x, yScreen 19Screeninfo x, yRandomize Timer, 3screenmax.x = x:screenmax.y = yWith wall(1)   .a.x = 0   .a.y = 0   .b.x = screenmax.x   .b.y = 0End WithWith wall(2)   .a.x = screenmax.x   .a.y = 0   .b.x = screenmax.x   .b.y = screenmax.yEnd WithWith wall(3)   .a.x = screenmax.x   .a.y = screenmax.y   .b.x = 0   .b.y = screenmax.yEnd WithWith wall(4)   .a.x = 0   .a.y = screenmax.y   .b.x = 0   .b.y = 0End WithFor 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 WithNextDo    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
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
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
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:
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
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:
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
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:
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

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
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:
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 SINGLEDIM A AS SINGLEDIM B AS SINGLEDIM C AS SINGLEDIM Area AS SINGLEDIM Height AS SINGLE'The following values are what the known sides of your triangle are:A = 4B = 5C = 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 HeightHeight = 2 * Area / A 'Divide by whichever side is the base of the triangle.Print HeightSleep '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
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:
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
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-lbyend 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...