ARGHH I suck at math!! Help!!!

BlitzMax Forums/BlitzMax Programming/ARGHH I suck at math!! Help!!!

I'm trying to make and asteroids game as my first Max project and I having an issue that would be super easy in 3D, but since I'm using 2d, I have to create the effect I want with math.

I'm trying to make an asteroids game that instead of the player ship rotating around and flying around the screen, the player ship stays stationary in the middle of the screen facing up and when the player turns the entire map, or at least the bad guys positions, rotates around him as if the world if turning relative to him, instead of vise versa. This would be all top down 2d.

I guess the closest thing I can relate to this is a top down radar in a 3d game. the player is in the middle and the bad guy blips rotate around the player relative to the direction he's face and where he's moving.

What I'm looking for is a way to plot the bad guy new transformed position relative to the player ship in 2d, so if a bad guy is in front of the player at the top of the sceen and the player turns left, all bad guys will rotate right around the player.

I guess what I'm trying to do is to transform the world space with all the bad guy ships in to the local space of the player ship.

Hope this all makes sense. Got any ideas?

I've never tried to do this myself, But I guess that you could position everything based from the middle. eg:

SetOrigin( GraphicsWidth()/2, Graphicsheight()/2 )

And then use SetRotation to rotate everything, except the player ship.

Just an idea :)

Yeah, unfortunately, that won't work for what I need.

Hrrmmm...
Would 2D Camera help? Sounds like you should seperate world coords from screen ones.
I might have some code lying around. It doesn't support rotation though. I'll try and find it.

this might help.

You would need nested calls to setting rotation, since each time you call it is resets any previous rotations. You need to be able to rotate the game world (the modelview matrix ideally) while also allowing individual rotations per object.

Alternatively you could set the handle for each alien ship or asteroid to be centered on the player so that when there is any rotation performed it rotates around the player.

Here is some code that might help, except for the matrix rotation issue. I'll let someone smarter than I help there.

Camera mod:
Rem
	Copyright (c) 2007 David J. Hayes
	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:
	
	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.
	
	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
End Rem 
SuperStrict

Rem
bbdoc: altitude.camera
End Rem
Rem
Module altitude.camera

ModuleInfo "Version: 1.00"
ModuleInfo "License: MIT"
ModuleInfo "Copyright: 2007 David J. Hayes"
ModuleInfo "Modserver: N/A"

ModuleInfo "History: 1.00"
ModuleInfo "History: First release."
End Rem

Import brl.Max2D
Import brl.LinkedList
Import brl.Random

Rem
bbdoc: The main 2D camera type.
about: Allows for easy creation of game worlds larger than the screens resolution,
simple render queing, split screens, camera effects, etc.
End Rem
Type TCamera
	Global List:TList
	Field RenderList:TList
	Field WorldX:Float
	Field WorldY:Float
	Field TargetX:Float
	Field TargetY:Float
	Field X:Float
	Field Y:Float
	Field Width:Float
	Field Height:Float
	Field Zoom:Float
	Field ClsColor:Int[3]
	Field _VPX:Int, _VPY:Int, _VPW:Int, _VPH:Int, _SX:Float, _SY:Float, _OX:Float, _OY:Float, _A:Float, _B:Int, _R:Float, _C:Int[3]
	
	Rem
	bbdoc: Creates a new camera
	End Rem
	Function Create:TCamera (_WorldX:Float, _WorldY:Float,_X:Float, _Y:Float, _Width:Float, _Height:Float)
		Local Camera:TCamera = New TCamera
		Camera.WorldX = _WorldX
		Camera.WorldY = _WorldY
		Camera.Set(_X, _Y, _Width, _Height)
		Camera.Zoom = 1.0
		Return Camera
	End Function
	
	Method New()
		If List = Null List = CreateList()
		List.AddLast Self
		RenderList = CreateList()
	End Method

	Rem
	bbdoc: Resets render settings to default.
	about: Call before calling a render objects Render() function.
	End Rem
	Method StartRender()
		GetViewport _VPX,_VPY,_VPW,_VPH
		GetScale _SX,_SY
		GetOrigin _OX,_OY
		_A = GetAlpha()
		_B = GetBlend()
		_R = GetRotation()
		GetColor _C[0],_C[1],_C[2]
		SetViewport X,Y,Width,Height
		SetOrigin 0,0
		SetColor ClsColor[0],ClsColor[1],ClsColor[2]
		DrawRect X, Y,Width,Height	
		SetScale Zoom, Zoom
		SetRotation 0
		SetBlend ALPHABLEND
		SetAlpha 1	
		SetColor 255,255,255
	End Method
	
	Rem
	bbdoc: Returns to previous render settings.
	about: Call after calling a render objects Render() function.
	End Rem
	Method EndRender() 
		SetViewport _VPX,_VPY,_VPW,_VPH
		SetScale _SX, _SY
		SetOrigin _OX,_OY
		SetAlpha _A
		SetBlend _B
		SetRotation _R
		SetColor _C[0],_C[1],_C[2]
	End Method
	
	Rem
	bbdoc: Calls all Render() functions for TRenderObjects in RenderList.
	End Rem
	Method RenderAll() 
		StartRender
		For Local Obj:TRenderObject = EachIn RenderList
			If TRenderObject(obj) obj.Render(Self) 
		Next
		EndRender
	End Method
	
	Rem
	bbdoc: Calls all Render() functions for TRenderObjects in given list.
	End Rem
	Method RenderFromList(_List:TList) 
		StartRender
		For Local Obj:TRenderObject = EachIn _List
			If TRenderObject(obj) obj.Render(Self) 
		Next
		EndRender
	End Method
	
	Rem
	bbdoc: Sets the position and size of the camera.
	End Rem
	Method Set(_X:Float, _Y:Float, _Width:Float, _Height:Float) 
		X = _X
		Y = _Y
		Width = _Width
		Height = _Height
	End Method
	
	Rem
	bbdoc: Helper function for converting world to screen coordinates.
	End Rem
	Method ScreenX:Float (_WorldX:Float) 
		Return (_WorldX * Zoom) + WorldX
	End Method

	Rem
	bbdoc: Helper function for converting world to screen coordinates.
	End Rem
	Method ScreenY:Float (_WorldY:Float) 
		Return (_WorldY * Zoom) + WorldY
	End Method

	Rem
	bbdoc: Centers camera onto specified world position.
	End Rem
	Method CenterOnPosition(_X:Int, _Y:Int)
		WorldX = -_X * Zoom + (X + (Width / 2))
		WorldY = -_Y * Zoom + (Y + (Height / 2))
	End Method
	
	Rem
	bbdoc: Centers camera onto specified world position smoothly using easing.
	End Rem
	Method CenterOnPositionSmooth(_X:Float, _Y:Float, _Speed:Double = 1.0) 
		_X = Int(_X);_Y = Int(_Y)
		TargetX = -_X * Zoom + (X + (Width / 2))
		TargetY = -_Y * Zoom + (Y + (Height / 2))
		WorldX :+ (TargetX - WorldX) * (_Speed * Zoom)
		WorldY :+ (TargetY - WorldY) * (_Speed * Zoom) 
	End Method

	Rem
	bbdoc: Zooms camera to specified world position and zoom level.
	End Rem
	Method ZoomTo(_X:Float, _Y:Float, _Level:Float) 
		Zoom = _Level
		TargetX = -_X * Zoom + (X + (Width / 2))
		TargetY = -_X * Zoom + (Y + (Height / 2)) 
		WorldX = TargetX
		WorldY = TargetY
	End Method
	
	Rem
	bbdoc: Offsets camera position randomly by a given strength.
	End Rem
	Method Shake(_Strength:Float) 
		WorldX :+ Rnd(-_Strength,_Strength) * Zoom
		WorldY :+ Rnd(-_Strength,_Strength) * Zoom
	End Method
End Type

Rem
bbdoc: An abstract class for renderable game objects to extend.
End Rem
Type TRenderObject Abstract
	Method Render(_Camera:TCamera) 
	End Method
End Type


Quick example:
SuperStrict
Import altitude.camera

Graphics 640, 480

Const STATICFRICTION:Float =.02
Type TEntity Extends TRenderObject
	Field List:TList
	Field X:Float, Y:Float, VX:Float, VY:Float
	Field Radius:Float, Color:Int[]
	
	Function Create:TEntity (X:Float, Y:Float, Radius:Float, Color:Int[]) 
		Local Entity:TEntity = New TEntity
		Entity.X = X
		Entity.Y = Y
		Entity.Radius = Radius
		Entity.Color = Color
		Return Entity
	End Function
	
	Method New() 
		If List = Null List = CreateList()
		List.AddLast Self
	End Method
	
	Method Update() 
		VX:*(1 - STATICFRICTION) 
		VY:*(1 - STATICFRICTION) 
		X:+VX
		Y:+VY
	End Method
	
	Method Render(Cam:TCamera) 
		Local SX:Float = Cam.ScreenX(X - radius) 
		Local SY:Float = Cam.ScreenY(Y - radius) 
		SetColor Color[0], Color[1], Color[2]
		DrawOval SX, SY, (Radius * 2) * Cam.Zoom, (Radius * 2) * Cam.Zoom
	End Method
End Type

Local cam:TCamera = TCamera.Create(0, 0, 0, 0, 640, 480) 
Local cam2:TCamera = TCamera.Create(0, 0, 540, 380, 100, 100) 
cam2.Zoom =.25
cam2.ClsColor =[0, 128, 0]


For Local i:Int = 1 To 100
Cam.RenderList.AddLast(TEntity.Create(Rnd(- 1000, 1000), Rnd(- 1000, 1000), 25, [Int(Rnd(0, 255)), Int(Rnd(0, 255)), Int(Rnd(0, 255))])) 
Next

Local e:TEntity = TEntity.Create(100, 100, 50, [255, 255, 0]) 
Cam.RenderList.AddLast(e) 


While not KeyHit(KEY_ESCAPE) 
	e.VX:+(KeyDown(KEY_RIGHT) - KeyDown(KEY_LEFT)) *.5
	e.VY:+(KeyDown(KEY_DOWN) - KeyDown(KEY_UP)) *.5
	e.Update() 
	Cam.Zoom:+(KeyDown(KEY_A) - KeyDown(KEY_Z)) *.005
	Cam.CenterOnPositionSmooth(e.X, e.Y, .5) 
	Cam.RenderAll() 
	Cam2.CenterOnPosition(e.X, e.Y) 
	Cam2.RenderFromList(Cam.RenderList) 
	DrawText "Zoom: " + Cam.Zoom, 0, 0
	Flip;Cls
Wend
End


see if this helps not thourougly tested, optimized or as sophisticated but hopefully you'll get the idea:
Global width:Int = 800
Global height:Int = 600
Global List:TList = CreateList()


Graphics width,height

SetOrigin(width/2,height/2)

Type obj

	Field x:Float
	Field y:Float
	Field speed:Float
	Field bearing:Float
	Field turnangle:Float
	Field radius:Float

	Function Create:obj(x:Float,y:Float,ang:Float)
		Local O:obj = New obj
		List.addlast(O)
		O.x = x
		O.y = Y
		O.speed = 0.1
		O.turnAngle = ang
		O.radius = Sqr(x*x+y*y)
		O.bearing = angle(x,y,0,0)		
	End Function 

	Method rotate(ang:Float)
			turnangle = (turnangle+ang) Mod 360.0
			bearing = (bearing+ang) Mod 360.0
			radius = Sqr(x*x+y*y)
			x = Cos(bearing)*radius
			y = Sin(bearing)*radius
	End Method

	Method shift(y)
			Self.y :+y
			bearing = angle(x,Self.y,0,0)
	End Method
	Method move()
		x :+Cos(turnangle)*speed
		y :+Sin(turnangle)*speed
		radius = Sqr(x*x+y*y)
		bearing = angle(x,y,0,0)
	End Method	
	Method draw()
			SetRotation(turnangle)
			DrawRect x,y,12,4
	End Method
End Type

Function ANGLE:Float(x1:Float,y1:Float,x2:Float,y2:Float)
		Return (ATan2(x2-x1,y1-y2)+450) Mod 360.0 
End Function

For n = 1 To 9
	Local x:Float = Rand(width)-width/2
	Local y:Float = Rand(height)-width/2
	obj.Create(x,y,Rand(360))
Next

Local O:obj
Repeat
	Cls
	DrawOval -2,-4,4,8
	If KeyDown(key_left) 
		For O = EachIn List
			O.rotate(-1)
		Next
	ElseIf KeyDown(key_right)
		For O = EachIn List
			O.rotate(1)
		Next
	EndIf
	If KeyDown(key_up) 
		For O = EachIn List
			O.shift(+1)
		Next
	ElseIf KeyDown(key_down)
		For O = EachIn List
			O.shift(-1)
		Next
	EndIf
	For O = EachIn List
		O.move()
		O.draw()
	Next
	SetRotation 0
	Flip()
	
Until KeyDown(key_escape)


My sprite system would allow you to do this. You would just create a pivot in the center of the screen, and attach every sprite to it, except the player ship. Then you turn the pivot instead of the ship, and everything attached to it would rotate around the pivot.

Allowing the ship to move through this rotated space is a bit more difficult. I'm sure the system can handle it, I'm just having a hard time visualizing how you would need to move and rotate that pivot. I think all you would need to do is move the pivot in the opposite way that you want the ship to move, so turning the ship to the left would equal a pivot rotation to the right, and moving the ship upwards, would result in the pivot moving downwards. Hm... no that's not right. Cause when the pivot is no longer centered on the ship, if you then turn the ship, and the pivot turns, then the ship's movement in pivot space would be an arc, instead of turning in place...

Maybe you would need to create two pivots. One stays centered in the middle of the screen, and rotates, and the other is attached to that, and moves, and everything else is attached to the moving one.

Or maybe you could detach and reattach everything to the pivot each frame to calculate the rotation and movement seperately and leave them where they end up on detachment.

Here is a copy of our current 2DVector class pulled straight from svn:
SuperStrict

'{module}

Import BRL.Math
Import "doubleconv.bmx"

Type asC2DVector
	Field dxc:Double, dyc:Double

	Function Create:asC2DVector( dx:Double , dy:Double )
		Local temp:asC2DVector = New asC2DVector
		temp.dxc = dx
		temp.dyc = dy
		Return temp
	EndFunction

	Function fromAngle:asC2DVector( dAnAngle:Double )
		Local temp:asC2DVector = New asC2DVector
		temp.setAngle(dAnAngle)
		Return temp
	EndFunction

	Function NORTH:asC2DVector()
		Return asC2DVector.Create(0, -1)
	EndFunction

	Function SOUTH:asC2DVector()
 		Return asC2DVector.Create(0, 1)
	EndFunction

	Function EAST:asC2DVector()
		Return asC2DVector.Create(1, 0)
	EndFunction

	Function WEST:asC2DVector()
		Return asC2DVector.Create(-1, 0)
	EndFunction

	Method getNormal:asC2DVector()
		Local temp:asC2DVector = New asC2DVector
		temp.dxc = dxc/getLength()
		temp.dyc = dyc/getLength()
		Return temp
	EndMethod

	Method getAngle:Double()
		Return ATan2( dyc , dxc )
	EndMethod

	Method setAngle( dAnAngle:Double )
		dxc = Cos ( dAnAngle )
		dyc = Sin ( dAnAngle )
	EndMethod

	Method distanceToSquared:Double(as2dvDest:asC2DVector)
		Return (as2dvDest.dxc - dxc) * (as2dvDest.dxc - dxc) + (as2dvDest.dyc - dyc) * (as2dvDest.dyc - dyc)
	EndMethod

	Method distanceTo:Double(as2dvDest:asC2DVector)
		Return Sqr((as2dvDest.dxc - dxc) * (as2dvDest.dxc - dxc) + (as2dvDest.dyc - dyc) * (as2dvDest.dyc - dyc))
	EndMethod

	Method toString:String()
		Return "X position: " + dxc + " ; Y position: " + dyc
	EndMethod

	Method Clone:asC2DVector()
		Return asC2DVector.Create(dxc, dyc)
	EndMethod

	Method Scale:asC2DVector(dFactor:Double)
		dxc:*dFactor
		dyc:*dFactor
		Return Self
	EndMethod

	Method getLength:Double()
		Return Sqr(dxc * dxc + dyc * dyc)
	EndMethod

	Method getProduct:asC2DVector( as2dvOther:asC2DVector )
		Return asC2DVector.Create(dxc * as2dvOther.dxc, dyc * as2dvOther.dyc)
	EndMethod

	Method multiply( as2dvOther:asC2DVector )
		dxc :* as2dvOther.dxc
		dyc :* as2dvOther.dyc
	EndMethod
	
	Method getDotProduct:Double( as2dvOther:asC2DVector )
		Return dxc * as2dvOther.dxc + dyc * as2dvOther.dyc
	EndMethod
	
	Method getPerpendicular:asC2DVector()
		Return asC2DVector.Create(dyc,-dxc)
	EndMethod
	
	Method getReverse:asC2DVector()
		Return asC2DVector.Create(-dxc, -dyc)
	EndMethod
	
	Method getDifference:asC2DVector(as2dvOther:asC2DVector)
		Return asC2DVector.Create(dxc - as2dvOther.dxc, dyc - as2dvOther.dyc)
	EndMethod

	Method subtract(as2dvOther:asC2DVector)
		dxc :- as2dvOther.dxc
		dyc :- as2dvOther.dyc
	EndMethod

	Method getTotal:asC2DVector(as2dvOther:asC2DVector)
		Return asC2DVector.Create(dxc + as2dvOther.dxc, dyc + as2dvOther.dyc)
	EndMethod
	
	Method getSum:asC2DVector(as2dvOther:asC2DVector)
		Return getTotal(as2dvOther:asC2DVector)
	EndMethod

	Method add:asC2DVector(as2dvOther:asC2DVector)
		dxc :+ as2dvOther.dxc
		dyc :+ as2dvOther.dyc
		Return Self
	EndMethod

	Method xMerge:asC2DVector(as2dvOther:asC2DVector)
		Return asC2DVector.Create(dxc + as2dvOther.dxc, dyc )
	EndMethod

	Method yMerge:asC2DVector(as2dvOther:asC2DVector)
		Return asC2DVector.Create(dxc , dyc + as2dvOther.dyc)
	EndMethod

	Method rotate:asC2DVector(dAngle:Double)
		Local tx:Double = dxc * Cos( dAngle ) - dyc * Sin( dAngle )
		Local ty:Double = dyc * Cos( dAngle ) + dxc * Sin( dAngle )
		dxc = tx
		dyc = ty
		Return Self
	EndMethod
	
	Method getLinter:asC2DVector( dest:asC2DVector , alpha:Double )
		Local dtx:Double = dxc + alpha * ( dest.dxc - dxc )
		Local dty:Double = dyc + alpha * ( dest.dyc - dyc )
		Return asC2DVector.Create( dtx , dty )
	End Method

	Method rotateAbout:asC2DVector( as2dvTransformPoint:asC2DVector , dAngle:Double )
		dxc :- as2dvTransformPoint.dxc
		dyc :- as2dvTransformPoint.dyc
		rotate(dAngle)
		dxc :+ as2dvTransformPoint.dxc
		dyc :+ as2dvTransformPoint.dyc
		Return Self
	EndMethod

	Method equals:Int( as2dvOtherVector:asC2DVector, dEpsilon:Double = 0.000001)
		'Now using epsilon to compensate for FP accuracy loss
	
		If Abs(dxc - as2dvOtherVector.dxc) < dEpsilon And Abs(dyc - as2dvOtherVector.dyc) < dEpsilon
			Return True
		EndIf
		Return False
	EndMethod

	Method serialize:Byte[]()
		Local temp:Byte[] = New Byte[17]
		temp[0] = 16
		Local tempx:Byte[] = packDouble(dxc)
		For Local i:Int = 0 Until tempx.length
			temp[i+1] = tempx[i]
		Next

		Local tempy:Byte[] = packDouble(dyc)
		For Local i:Int = 0 Until tempx.length
			temp[i+9] = tempy[i]
		Next
		Return temp
	EndMethod

	Function deSerialize:asC2DVector(myBytes:Byte[])
		Local temp:asC2DVector = New asC2DVector
		Local tempx:Byte[] = New Byte[8]
		For Local i:Int = 0 Until tempx.length
			tempx[i] = myBytes[i+1]
		Next
		Local tempy:Byte[] = New Byte[8]
		For Local i:Int = 0 Until tempy.length
			tempy[i] = myBytes[i+9]
		Next
		temp.dxc = unpackDouble(tempx)
		temp.dyc = unpackDouble(tempy)
		Return temp
	EndFunction
EndType

You might want to comment out the dependencies. I believe the function you're most interested in is the rotateAbout() method.

I don't have bmax so there may be some trickery I'm not aware of, but I'd just do something like:

- Move and set each game objects position and rotation in the game logic as normal, just as if the gameworld was axis-aligned, ala Asteroids.

- At render time, or just before, transform all game objects to the rotated world as follows (pseudo code):
cos_rot# = Cos(-90 - player.rot)
sin_rot# = Sin(-90 - player.rot)

origin_x = screen_width / 2
origin_y = screen_height / 2

For this = Each game_object

	; Get vector from player to this game object.
	x = this.x - player.x
	y = this.y - player.y

	; Transform (rotate) vector.
	trans_x = (x * cos_rot) - (y * sin_rot)
	trans_y = (y * cos_rot) + (x * sin_rot)
	
	; Set actual position this object needs to be drawn at.
	this.screen_x = origin_x + trans_x
	this.screen_y = origin_y + trans_y
	
	; Set actual rotation this object needs to be rotated to.
	this.screen_rot = this.rot + player.rot

Next

(Not tested, but you should get the idea)

[edit]Ooops. Forgot something.