Vector Class

Monkey Forums/Monkey Code/Vector Class

Tibit(Posted 2011) [#1]
Sharing my Vector2D class. ´

I use this in almost every single little thing I do in Monkey.

Very tested, very useful.

Feel free to use it for free for whatever you want - use,share,improve,extend,bundle,and so on. It's even OK if you want to bundle this with a framework.

Strict

' A vector class with a lot of handy functionality for game-programming! 

' In simplicy a vector is an Arrow. It has an length
' and a direction. You can use these properties to 
' make the vector model a Position, a Velocity, a 
' Line, a Distance and many other things that can be
' measured using a length and a direction or anything
' that uses two float pair-values!

' Wikipedia: A vector is what is needed to "carry" the
' point A to the point B; the Latin word vector means "one who carries"
Class Vector
Public
	Field X:Float
	Field Y:Float 

	Const Zero:Vector = New Vector(0,0)
	
	Method New(x:Float=0,y:Float=0)
		X = x
		Y = y
	End
	
	'	    S E T
	'----------------------------------------------	
	Method Set:Vector( vector:Vector ) 
		X = vector.X 
		Y = vector.Y 
		Return Self	
	End
	Method Set:Vector(x:Float,y:Float ) 
		X = x
		Y = y
		Return Self	
	End
		
	'		A D D
	'----------------------------------------------	
	Method Add:Vector( vector:Vector ) 
		X += vector.X 
		Y += vector.Y 
		Return Self	
	End
	
	'		S U B T R A C T
	'----------------------------------------------	
	Method Subtract:Vector( vector:Vector ) 
		X = X - vector.X 
		Y = Y - vector.Y 
		Return Self	
	End
	
	'		 D O T  P R O D U C T 
	'---------------------------------------------------
	' Dot Product which is X*X2 + Y*Y2
	'
	'---------------------------------------------------
	Method Dot:Float( Vector:Vector )
		Return ( X * Vector.X + Y * Vector.Y)
	End
	
		'		M U L T I P L Y   V E C T O R 
	'----------------------------------------------
	Method Multiply:Vector( Value:Float )
		X*=Value
		Y*=Value
		Return Self	
	End
		
	' M I R R O R
	'----------------------------------------------------------
	' if the mirrorImage vector is a unit-vector this will 
	' make this vector flip 180 degrees around it
	' So that this vector now points in the exact opppisite 
	' direction To the mirrorImage vector
	' Returns Self, which will be opposite to mirrorImage.
	Method Mirror:Vector( mirrorImage:Vector )
		Local Dotprod# = -X * mirrorImage.X - Y * mirrorImage.Y
		X=X+2 * mirrorImage.X * dotprod
		Y=Y+2 * mirrorImage.Y * dotprod
		Return Self
	End
	
	' Set the vector to a direction and a length x,y
	' returns Self, which is now pointin the directio
	' provided, with the length provided
	Method MakeField:Vector( direction:Float, length:Float )	
		X = Cos( -direction )*length
		Y = Sin( -direction )*length
		Return Self
	End
	
	'----------------------------------------------
	' Create a copy --> depending on situation you might
	' want to use the VectorPool to do this instead for
	' when extrenme performance is required
	'-----------------------------------------------
	Method Copy:Vector() Property
		Local vector:Vector = New Vector
		vector.X = X
		vector.Y = Y
		Return vector	
	End
	
		'	 C R E A T E   L E F T   N O R M A L
	'----------------------------------------------	
	' Make this an Perpendicular Vector
	' As if you would rotate it 90 degrees Counter Clockwise
	' return ( Y, -X )
	Method LeftNormal:Vector( )
		Local tempX:Float = Y
		Y = -X
		X = tempX
		Return Self
	End

		
	'	 C R E A T E   R I G H T   N O R M A L
	'----------------------------------------------	
	' Make this a Perpendicular Vector
	' Same as rotating it 90 degrees ClockWise
	' return ( -Y, X )
	Method RightNormal:Vector( )
		Local tempY:Float = Y
		Y = X
		X = -tempY
		Return Self
	End
	
	'		N O R M A L I Z E 
	'----------------------------------------------	
	' Purpose: Sets Vector length To ONE but keeps it's direction		
	' Returns Self as a UnitVector
	Method Normalize:Vector()
		Local Length:Float = Self.Length()
		If Length = 0 
			Return Self ' Don't divide by zero, 
			'we do not normalize a zero-vector 
			'since this vector's direction is in
			' mathematical terms all directions!
		Endif
		Set( X / Length, Y / Length ) 'Make length = 1
		Return Self
	End
	
	'   G E T   L E N G T H
	'-----------------------------
	' Get current Length of vector, uses a single sqr operation
	Method Length:Float() Property
		Return Sqrt( X*X + Y*Y )'
	End
	
	'	S E T	L E N G T H
	'-------------------------------------
	Method Length:Void( length:Float ) Property
		'If we want to set vector to zero 
		If length = 0 
			X=0 
			Y=0
			Return
		Endif

		If X = 0 And Y = 0
			X = length
		Endif
		
		Normalize()
		Multiply( length )				
	End
	
	Method ReduceLength:Vector( amount:Float )
		Local currentAngle:Float = Direction
		Local currentLength:Float = Length		
		Local newLength:Float = currentLength - amount
		If newLength > 0
			MakeField( currentAngle, currentLength - amount  )
		Else 
			Set 0,0
		Endif
		Return Self	
	End
	
	' G E T  D I R E C T I O N
	'----------------------------------------------------
	' Calculates the current direction in degrees
	' in the 0 To < 360 range
	Method Direction:Float() Property
		Local angle:Float = ATan2(-Y , X)
		'If angle < 0 Then angle =+ 360
		Return angle
	End
	
	' S E T  D I R E C T I O N
	'----------------------------------------------
	'
	' Set the angle of this vector without changing the length,
	' has no effect if vector length is 0
	' uses a single sqr operation
	Method Direction:Void( direction:Float ) Property
		MakeField( direction, Length ) 
	End Method
	
	' D I S T A N C E
	'----------------------------------------------	
	' The Distance between the two points
	' This is NOT related to the vectors Length#	
	Method DistanceTo:Float(Other:Vector) 
		Return DistanceTo(Other.X, Other.Y)
	End
	Method DistanceTo:Float(x:Float, y:Float) 
		Local dx:Float = x - X 
		Local dy:Float = y - Y 
		Return Sqrt( dx*dx + dy*dy ) 	
	End
		
'		G E T  A N G L E   B E T W E E N
	'----------------------------------------------
	' If you have two vectors that start at the same position
	' the angle-distance between two vectors Result is from 0 
	' to 180 degrees, because two vectors can not be more than 180 
	' degrees apart, check AngleClockwise & AngleAntiClockwise
	' to get a 0-360 result instead
	' even tough it is counted as they are on the same position,
	' that is not a requirement at all for this to be correct
	Method AngleTo:Float( target:Vector )	
		Local dot:Float = Self.Dot( target )
		
		Local combinedLength:Float = Self.Length()*target.Length()
		If combinedLength = 0 Then Return 0
		
		Local quaski:Float = dot/combinedLength
		
		Local angle:Float = ACos( quaski )
		Return angle
	End
	
	
	' If you have two vectors so they both start at the same position
	' returns the angle from this vector to target vector (in degrees)
	' if you where to go Clockwise to it, result is 0 to 360	
	Method AngleToClockwise:Float( target:Vector )
		Local angle:Float = ATan2(-Y , X)  - ATan2(-target.Y , target.X)
		If angle < 0 Then angle += 360
		If angle >= 360 Then angle -= 360
		Return angle		
	End
	
	' If you have two vectors so they both start at the same position
	' returns the angle fromt this vector to target vector (in degrees)
	' if you where to go AntiClockwise to it, result is 0 to 360	
	Method AngleToAntiClockwise:Float( target:Vector )
		Local angle:Float =  AngleToClockwise(target)-360
		Return -angle
	End	
	
		'		V E C T O R    B E T W E E N
	'----------------------------------------------
	' Change the vector into a vector from Position1 to Position2
	' Return Self, as a vector that goes from first 
	' parameter's position to the second 
	Method MakeBetween:Vector( PositionFrom:Vector , PositionToo:Vector)
		If PositionFrom = Null Or PositionToo = Null Then Return Self
		X = ( PositionToo.X - PositionFrom.X ) 	
		Y = ( PositionToo.Y - PositionFrom.Y )
		Return Self		
	End

End 


Short use example can be found here: Vector Class


Sammy(Posted 2011) [#2]
Great stuff! :D

But possibly move it to the code archives for better archiving?


Xaron(Posted 2011) [#3]
Yep great! Thanks for that.


slenkar(Posted 2011) [#4]
ive used the angle functions of a vector class before its very useful, thanks


Sledge(Posted 2011) [#5]
Many thanks! I reckon this would be a good time to mention the Wolfire vector articles again.


Tibit(Posted 2011) [#6]
How many of you guys are familiar with Vectors since before?

My old Vector Tutorial for newbies, should brush it up for Monkey and this Vector class, but it might be somewhat helpful anyway since Blitzmax is quite similar and the focus is on understanding :)

Basic Vectors and Physics in Blitzmax


Xaron(Posted 2011) [#7]
Here's some vector stuff I do for my chipmunk port at the moment. It's still not optimized and need to be done more within the vector class (so methods instead of functions), but I work on that right now (at the moment it's more a 1:1 conversion from the original C code)

' chipmonkey - A Monkey Port of Scott Lembcke's chipmunk 2d physics engine
' 
' chipmonkey homepage: http://code.google.com/p/chipmonkey/
' chipmunk homepage: http://code.google.com/p/chipmunk-physics/
' 
' This port is maintained by Martin Leidel @ http://www.monkeycoder.de
'
' This software is provided 'as-is', without any express or implied
' warranty.  In no event will the authors be held liable for any damages
' arising from the use of this software.
' 
' Permission is granted to anyone to use this software for any purpose,
' including commercial applications, and to alter it and redistribute it
' freely, subject to the following restrictions:
' 
' 1. The origin of this software must not be misrepresented; you must not
' claim that you wrote the original software. If you use this software
' in a product, an acknowledgment in the product documentation would be
' appreciated but is not required.
' 2. Altered source versions must be plainly marked as such, and must not be
' misrepresented as being the original software.
' 3. This notice may not be removed or altered from any source distribution.

' Module chipmonkey.vect

Class cpVect
  Public  
    Const zero:cpVect = New cpVect( 0.0, 0.0 )
  
    Method New( x:Float, y:Float )
      Self.x = x
      Self.y = y
    End Method

  Private
    Field x:Float
    Field y:Float
End Class

' Returns the length of v.
Function cpvlength:Float( v:cpVect )
  Return Sqrt( cpvdot( v, v ) )
End Function

' Spherical linearly interpolate between v1 and v2. 
Function cpvslerp:cpVect( v1:cpVect, v2:cpVect, t:Float )
  Local omega:Float = ACos( cpvdot( v1, v2 ) )

  If( omega <> 0.0 )
    Local denom:Float = 1.0 / Sin( omega )
    Return cpvadd( cpvmult( v1, Sin( ( 1.0 - t ) * omega ) * denom ), cpvmult( v2, Sin( t * omega ) * denom ) )
  Else
    Return v1
  End If
End Function

' Spherical linearly interpolate between v1 towards v2 by no more than angle a radians 
Function cpvslerpconst:cpVect( v1:cpVect, v2:cpVect, a:Float )
  Local angle:Float = ACos( cpvdot( v1, v2 ) )
  Return cpvslerp( v1, v2, Min( a, angle ) / angle )
End Function

' Returns the unit length vector for the given angle (in radians). 
Function cpvforangle:cpVect( a:Float )
  Return New cpVect( Cos( a ), Sin( a ) )
End Function

' Returns the angular direction v is pointing in (in radians). 
Function cpvtoangle:Float( v:cpVect )
  Return ATan2( v.y, v.x )
End Function

' Returns a string representation of v. Intended mostly for debugging purposes and not production use.
Function cpvstr:String( v:cpVect )
  Return String( "( " + v.x + ", " + v.y + " )" )
End Function

' Check if two vectors are equal. (Be careful when comparing floating point numbers!) 
Function cpveql:Bool( v1:cpVect, v2:cpVect )
  Return ( v1.x = v2.x And v1.y = v2.y )
End Function

' Add two vectors
Function cpvadd:cpVect( v1:cpVect, v2:cpVect )
  Return New cpVect( v1.x + v2.x, v1.y + v2.y )
End Function

' Negate a vector.
Function cpvneg:cpVect( v:cpVect )
  Return New cpVect( -v.x, -v.y )
End Function 

' Subtract two vectors.
Function cpvsub:cpVect( v1:cpVect, v2:cpVect )
  Return New cpVect( v1.x - v2.x, v1.y - v2.y )
End Function

' Scalar multiplication.
Function cpvmult:cpVect( v:cpVect, s:Float )
  Return New cpVect( v.x * s, v.y * s )
End Function

' Vector dot product.
Function cpvdot:Float( v1:cpVect, v2:cpVect )
  Return v1.x * v2.x + v1.y * v2.y
End Function

' 2D vector cross product analog.
' The cross product of 2D vectors results in a 3D vector with only a z component.
' This function returns the magnitude of the z value.
Function cpvcross:Float( v1:cpVect, v2:cpVect )
  Return v1.x * v2.y - v1.y * v2.x
End Function

' Returns a perpendicular vector. (90 degree rotation)
Function cpvperp:cpVect( v:cpVect )
  Return New cpVect( -v.y, v.x )
End Function

' Returns a perpendicular vector. (-90 degree rotation)
Function cpvrperp:cpVect( v:cpVect )
  Return New cpVect( v.y, -v.x )
End Function

' Returns the vector projection of v1 onto v2.
Function cpvproject:cpVect( v1:cpVect, v2:cpVect )
  Return cpvmult( v2, cpvdot( v1, v2 ) / cpvdot( v2, v2 ) )
End Function

' Uses complex number multiplication to rotate v1 by v2. Scaling will occur if v1 is not a unit vector.
Function cpvrotate:cpVect( v1:cpVect, v2:cpVect )
  Return New cpVect( v1.x * v2.x - v1.y * v2.y, v1.x * v2.y + v1.y * v2.x )
End Function

' Inverse of cpvrotate().
Function cpvunrotate:cpVect( v1:cpVect, v2:cpVect )
  Return New cpVect( v1.x * v2.x + v1.y * v2.y, v1.y * v2.x - v1.x * v2.y )
End Function

' Returns the squared length of v. Faster than cpvlength() when you only need to compare lengths.
Function cpvlengthsq:Float( v:cpVect )
  Return cpvdot( v, v )
End Function

' Linearly interpolate between v1 and v2.
Function cpvlerp:cpVect( v1:cpVect, v2:cpVect, t:Float )
  Return cpvadd( cpvmult( v1, 1.0 - t ), cpvmult( v2, t ) )
End Function

' Returns a normalized copy of v.
Function cpvnormalize:cpVect( v:cpVect )
  Return cpvmult( v, 1.0 / cpvlength( v ) )
End Function

' Returns a normalized copy of v or vzero if v was already vzero. Protects against divide by zero errors.
Function cpvnormalize_safe:cpVect( v:cpVect )
  If( v.x = 0.0 And v.y = 0.0 )
    Return New cpVect()
  Else
    Return cpvnormalize( v )
  End If
End Function

' Clamp v to length len.
Function cpvclamp:cpVect( v:cpVect, len:Float )
  If( cpvdot( v, v ) > len * len )
    Return cpvmult( cpvnormalize( v ), len )
  Else
    Return v
  End If
End Function

' Linearly interpolate between v1 towards v2 by distance d.
Function cpvlerpconst:cpVect( v1:cpVect, v2:cpVect, d:Float )
  Return cpvadd( v1, cpvclamp( cpvsub( v2, v1 ), d ) )
End Function

' Returns the distance between v1 and v2.
Function cpvdist:Float( v1:cpVect, v2:cpVect )
  Return cpvlength( cpvsub( v1, v2 ) )
End Function

' Returns the squared distance between v1 and v2. Faster than vdist() when you only need to compare distances.
Function cpvdistsq:Float( v1:cpVect, v2:cpVect )
  Return cpvlengthsq( cpvsub( v1, v2 ) )
End Function

' Returns true if the distance between v1 and v2 is less than dist.
Function cpvnear:Bool( v1:cpVect, v2:cpVect, dist:Float )
  Return cpvdistsq( v1, v2 ) < dist * dist
End Function



Jesse(Posted 2011) [#8]
I have been trying to figure out how to really use vectors and in the process I ran in to these tutorials:
http://tonypa.pri.ee/vectors/start.html
they are in flash but are really good for beginners and for learning how to do stuff. such as ball to ball collision, ball to line collision, ball to arc collision and how to apply friction and gravity. I got most of it except how to do ball to arc collision.

here is the ball to wall collision in monkey:



Tibit(Posted 2011) [#9]
@Xaron, cool, looking forward to see the physics library btw, feel free to use my Vector class in the port, might be simpler and more powerful to use, however obviously the names of the methods won't be the same.


Richard Betson(Posted 2011) [#10]
All, great stuff. :)

L8r,


xzess(Posted 2011) [#11]
indeed, very interesting, thanks!


Shinkiro1(Posted 2011) [#12]
Because I spotted it in the above code: What is the advantage of using Property after a Method? Is it only for readability?


Tibit(Posted 2011) [#13]
Yes, it makes the code cleaner.

You can go myVector.Direction = 300

Instead of myVector.SetDirection 300

Not much of a difference, but it's nice still :)


GC-Martijn(Posted 2011) [#14]
Tibit made this simple mario movement example
Strict


Import mojo.app
Import mojo.graphics
Import mojo.input


Import classes.vector


Function Main:Int()
New MarioApp
End


Class Mario
	Field Position:Vector = New Vector( 200,200 )
	Field Velocity:Vector = New Vector
	Field Force:Vector = New Vector 
	Field MaximumVelocity:Float = 5
	Field jumpCount:Int = 0 ' Just for fun, showing how to add double-jumping
	Field jumpForce:Float = 5 'How high we can jump
	
	Method Update:Void()
		Local groundHeight:Float = DeviceHeight() - 15
		If KeyDown( KEY_LEFT ) 
			Force.Add( New Vector( -0.1, 0) )
		End
		If KeyDown( KEY_RIGHT ) 
			Force.Add( New Vector(0.1, 0) )
		End
		If KeyHit( KEY_SPACE ) 
			' We can only jump when out feet touch the ground
			If Abs(Position.Y - groundHeight) < 0.1
				Force.Add( New Vector(0, -jumpForce) ) 'Jump up = Add force to Y-axis 
				jumpCount+=1
			Else If Abs(Position.Y - groundHeight) > 30 And jumpCount > 0 And jumpCount < 2 And Velocity.Y > 0
				jumpCount+=1
				Force.Add( New Vector(0, -jumpForce*0.8) ) 'Double jump ability
			End
		End
		
	'We are always affected by gravity
	Force.Add( New Vector(0, 0.1) ) ' This affects how fast we will fall
	Velocity.Add( Force )
	Force.Set 0,0 'Reset force, sicne we converted it all into velocity
	' If Velocity.Length > MaximumVelocity
	' Velocity.Length = MaximumVelocity
	' Endif
	
	
	Position.Add Velocity
	Velocity.Multiply( 0.99 ) 'Friction 
	'Here I Reduce velocity by 5% each Update
	' Collision against ground (Against the whole X-Axis actually)
	If Position.Y > groundHeight
	'Print "Crash"
	Position.Y = groundHeight
	Velocity.Y = 0
	jumpCount = 0
	End
End

Method Draw:Void()
SetColor 0,0,0
DrawOval Position.X-15, Position.Y-15, 30, 30
End

End


Class MarioApp Extends App
Field Mario:Mario = New Mario
Method OnCreate:Int() 
SetUpdateRate 60
Return 0
End
Method OnUpdate:Int()
Mario.Update()


Return 0
End
Method OnRender:Int()
Cls 200,200,200
Mario.Draw


Return 0
End
End