Vector Projection with visual example + comments
Monkey Forums/Monkey Code/Vector Projection with visual example + comments
| ||
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 |
| ||
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. |
| ||
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? |
| ||
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. |
| ||
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 |
| ||
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. |
| ||
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! |
| ||
@NoOdle just a heads up. I just tried your code in flash and it crashes. using monkey v59. |
| ||
thanks Jesse, pretty quick fix, I've updated the code. |