Vector Projection with visual example + comments

Monkey Forums/Monkey Code/Vector Projection with visual example + comments

NoOdle(Posted 2012) [#1]
As the title says a quick test I wrote to visually check that the projection was working correctly. Currently trying implement SAT Separating Axis Theorem which I will eventually post here when I have finished it. Please excuse my vector class just quickly knocked it up for this test.

[monkeycode]
Import mojo


'/ Quick and dirty Vec2 Class
Class Vec2

Field x : Float
Field y : Float

Method New( x : Float, y : Float )
Self.x = x
Self.y = y
End Method

Method add : Vec2( other : Vec2 )
Return New Vec2( Self.x + other.x, Self.y + other.y )
End Method

Method add : Vec2( scalar : Float )
Return New Vec2( Self.x + scalar, Self.y + scalar )
End Method

Method subtract : Vec2( other : Vec2 )
Return New Vec2( Self.x - other.x, Self.y - other.y )
End Method

Method subtract : Vec2( scalar : Float )
Return New Vec2( Self.x - scalar, Self.y - scalar )
End Method

Method divide : Vec2( other : Vec2 )
Return New Vec2( Self.x / other.x, Self.y / other.y )
End Method

Method divide : Vec2( scalar : Float )
Return New Vec2( Self.x / scalar, Self.y / scalar )
End Method

Method multiply : Vec2( other : Vec2 )
Return New Vec2( Self.x * other.x, Self.y * other.y )
End Method

Method multiply : Vec2( scalar : Float )
Return New Vec2( Self.x * scalar, Self.y * scalar )
End Method

Method dot : Float( other : Vec2 )
Return ( Self.x * other.x ) + ( Self.y * other.y )
End Method

Method squaredLength : Float()
Return ( Self.x * Self.x ) + ( Self.y * Self.y )
End Method

Method length : Float()
Return Sqrt(( Self.x * Self.x ) + ( Self.y * Self.y ))
End Method

Method perpendicular : Vec2()
Return New Vec2( -Self.y, Self.x )
End Method

Method unitVector : Vec2()
Local l : Float = Abs( Self.length())
Return New Vec2( x / l, y / l )
End Method

End Class



'/ Simple Touch in Square Rect
Function TouchVec2 : Bool( p : Vec2, radius : Float = 25 )
If TouchX() < p.x - radius Or TouchX() > p.x + radius Then Return False
If TouchY() < p.y - radius Or TouchY() > p.y + radius Then Return False
Return True
End Function




Class MyApp Extends App

'/ stores projection result
Field projected : Vec2

'/ point to project
Field p0 : Vec2

'/ axis to project onto
Field axis : Vec2

'/ two visual points to represent the axis to project onto
'/ normaly you wouldnt need these points as you would obtain the axis by other means.
Field p1 : Vec2
Field p2 : Vec2


'/ used to store which point is being dragged around
Field selected : Vec2


Method OnCreate()

SetUpdateRate( 60 )


'/ point projection vector
p0 = New Vec2( 100, 100 )


'/ line passing through points
p1 = New Vec2( 50, 350 )
p2 = New Vec2( 250, 270 )


'/** fix for flash **
axis = New Vec2( 0, 0 )
projected = New Vec2( 0, 0 )

End Method



Method OnUpdate()

'/ simple touch move interaction with the points
'/ when touch Hit find any rectangle point that the mouse is inside and store it
If TouchHit()
If selected = Null
If TouchVec2( p0, 8 ) Then selected = p0
If TouchVec2( p1, 8 ) Then selected = p1
If TouchVec2( p2, 8 ) Then selected = p2
Endif
Endif
'/ if still touching and probably moving around and we have found a selected point, move this point to touch location
'/ if not still touching clear selected to check next loop for a new selection
If TouchDown()
If selected
selected.x = TouchX()
selected.y = TouchY()
Endif
Else
selected = Null
Endif


'/** Projecting Vector A onto Vector B
'/ -----------------------------------
'/ The formula for projecting vector ( a ) onto vector ( b ) is:

' proj.x = ( a.dot( b ) / squaredLength( b )) * b.x
' proj.y = ( a.dot( b ) / squaredLength( b )) * b.y

' we can simplify this further by making ( b ) a unit vector ( b.x * b.x + b.y * b.y ) = 1
' which becomes:

' proj.x = a.dot( b ) * b.x
' proj.y = a.dot( b ) * b.y


'/ compute Vector B - the unit vector representing the axis.
' In our case imagine the line that passes through the 2 points represent a horizontal x axis
axis = p2.subtract( p1 ).unitVector()

'/ computes Vector A - the vector to project p1 -> p0
projected = p0.subtract( p1 )

'/ computes dot product of this vector and the line, relative orientation.
Local dp : Float = projected.dot( axis )

'/ multiplies the dot product with the axis
projected = axis.multiply( dp )


End Method



Method OnRender()
Cls 255, 255, 255

'/ Draws Vector from p1 -> p0
SetColor 0, 0, 175
SetAlpha 0.35
DrawLine p0.x, p0.y, p1.x, p1.y
Local testVector : Vec2 = p0.subtract( p1 )
Local tArrow : Vec2 = testVector.subtract( testVector.perpendicular() )
tArrow = tArrow.unitVector()
DrawLine p0.x, p0.y, p0.x - tArrow.x * 10, p0.y - tArrow.y * 10
DrawLine p0.x, p0.y, p0.x - ( -tArrow.y * 10 ), p0.y - tArrow.x * 10
SetAlpha 1.0
DrawRect p0.x - 4, p0.y - 4, 8, 8

'/ Draws vector thats passes through points p1 and p2
SetColor 175, 0, 0
SetAlpha 0.35
DrawLine p1.x - axis.x * 640, p1.y - axis.y * 640, p2.x + axis.x * 640, p2.y + axis.y * 640
SetAlpha 1.0

'/ Draws projected vector
SetColor 0, 175, 0
DrawLine p1.x, p1.y, p1.x + projected.x, p1.y + projected.y
Local pArrow : Vec2 = projected.subtract( projected.perpendicular() )
pArrow = pArrow.unitVector()
DrawLine p1.x + projected.x, p1.y + projected.y, p1.x + projected.x - pArrow.x * 10, p1.y + projected.y - pArrow.y * 10
DrawLine p1.x + projected.x, p1.y + projected.y, p1.x + projected.x - ( -pArrow.y * 10 ), p1.y + projected.y - pArrow.x * 10

SetColor 175, 175, 175
SetAlpha( 0.35 )
DrawLine p1.x + projected.x, p1.y + projected.y, p0.x, p0.y
SetAlpha( 1.0 )

'/ Draws points that pass through the line vector
SetColor 175, 0, 0
DrawRect p1.x - 4, p1.y - 4, 8, 8
DrawRect p2.x - 4, p2.y - 4, 8, 8

End Method


End Class


Function Main()
New MyApp()
End Function
[/monkeycode]

please let me know if there are any mistakes and I will correct them


Jesse(Posted 2012) [#2]
This is good. I really think a lot more programmers need to understand this. Projection is really useful for many things game related. When I was doing the research for the pool game I realized I needed to know all of this. Projection, dot product and pythagoreans theorem is what allowed me to create the pool game. as well as 3d in 2d but this was just visuals.

I did several similar test as yours above while I was learning the subject and really help me to understand it.

one of the things I realized is that I had to recycle vectors as this has a potential of creating excessive amount of objects when implemented in a game.


NoOdle(Posted 2012) [#3]
one of the things I realized is that I had to recycle vectors as this has a potential of creating excessive amount of objects when implemented in a game.

yeah I noticed that when I was knocking up the quick Vec2 class I used in the example above. How did you implement yours? Is there any advantage to using float arrays over a Vec class?


Jesse(Posted 2012) [#4]
I didn't use arrays. I added a couple of fields for temporary vectors in certain classes that I would pass to different functions and classes. I was going to release it as a library for everybody but I realized that it was too personalized and others would have a hard time understanding it so I didn't. yours looks quite elegant compared to mine.


NoOdle(Posted 2012) [#5]
Ok thats cleared that up, I wasn't sure if I should use arrays. Mine turned out to be pretty simple to use and I like how you can stack operations but like you said it will lead to excessive amounts of objects.

I guess a fairly simple solution would be to create more methods, instead of returning a new vector it alters the calling vector; leaving it up to the user to manage when vector objects are created or not created. Do you think that would work? Im worried it could be confusing when reading code, if the method names weren't obvious it could be hard to differentiate and be unsure if it returned a new vector or not! I can't think of a suitable name extension that would sound obvious for add, subtract, divide etc etc


muddy_shoes(Posted 2012) [#6]
My experience is that creating functions with an output vector is the most straightforward way to allow the user to avoid generating lots of short-lived objects. I tried pooling and found that the cost of managing the pool was often much higher than the GC it was avoiding. I've moved bits of the box2D module over to output parameters but only the most GC unfriendly stuff. The vector stuff is in common/math: http://code.google.com/p/monkeybox2d/source/browse/#hg%2Fcommon%2Fmath

The downside is that the user needs to keep track of their re-used vector instances and can end up running into trouble if methods share them.


NoOdle(Posted 2012) [#7]
thanks muddy I really should have thought to check there, doh!

and can end up running into trouble if methods share them.

Yea that has happened to me in the past, I made a mistake (more than once :P) and didn't notice it for ages!


Jesse(Posted 2012) [#8]
@NoOdle
just a heads up. I just tried your code in flash and it crashes.

using monkey v59.


NoOdle(Posted 2012) [#9]
thanks Jesse, pretty quick fix, I've updated the code.