Rotating an object around another moving object

Monkey Forums/Monkey Programming/Rotating an object around another moving object

Sensei(Posted 2015) [#1]
Hi guys,

Since I'm (still) crap at maths, I was hoping someone can help kick me in the shin for my stupidity and still help me with my wee little problem.

As the title suggests, I'm trying to rotate an object around another object. Only it is a moving object (basically imagine a ball rotating around a moving player image)

I found this link quite useful for the math that works out the calculations, but for some reason, in my implementation, the "ball" moves further and further away the more you move the player ship.

Here's a rough snippet of my code:

This is the code that updates the ball's new X and Y position:
Local rotatedBy:Float = 90 * dt.delta
Local rotatedPosition:Float[] = rotatePoint(ballX, ballY, shipX, shipY, rotatedBy)
ballX = rotatedPosition[0]
ballY = rotatedPosition[1]


And this is the code that is my converted method for the formula used in the link above:
  Method rotatePoint:Float[](pointX:Float, pointY:Float, centerX:Float, centerY:Float, angle:Float)
    Local result:Float[]
    Local radians:Float = angle * PI / 180
    Local rotatedX:Float = Cos(radians) * (pointX - centerX) - Sin(radians) * (pointY - centerY) + centerX
    Local rotatedY:Float = Sin(radians) * (pointX - centerX) + Cos(radians) * (pointY - centerY) + centerY
    result = [rotatedX, rotatedY]
    Return result
  End


So, if I don't move, it works perfectly, but as soon as I do move, the more I move, the further the ball moves away, ie the distance between the player ship and ball.

I'm so close I can taste victory! I just need a little hint... :)


muddy_shoes(Posted 2015) [#2]
You need to attach the ball to the player. Some frameworks refer to this as parenting. In your situation I'd do this by simply storing the ball offset from the player, rotating that and then using playerPosition + rotatedOffset as the final ball position.


Samah(Posted 2015) [#3]
The drift you're experiencing is probably due to floating point precision with your frame interpolation. My suggestion would be to manually set the position based on its parent rather than trying to calculate from its previous position. Store the expected radius of the orbit and just calculate it from there.
Local radius:Float = 100
Local rotatedBy:Float = 90 * dt.delta
ballX = shipX + Cos(rotatedBy) * radius
ballY = shipY + Sin(rotatedBy) * radius

Edit: Updated for delta timing.
Edit 2: Delta timing removed.


Sensei(Posted 2015) [#4]
Thanks for the update guys.

Samah, that doesn't work. I keep getting jitters of the ball and it doesn't move/rotate.
Here's an example of the ballX values spat out over say a few seconds:
55.939203232516554
59.03883077220927
61.80836696244423
59.03883077220927
61.80836696244423
61.80836696244423
55.939203232516554
64.22373055608624
59.03883077220927
55.939203232516554
59.03883077220927
64.22373055608624
55.939203232516554
61.80836696244423
61.80836696244423
55.939203232516554
61.80836696244423
55.939203232516554
61.80836696244423
61.80836696244423
61.80836696244423
59.03883077220927
55.939203232516554
61.80836696244423
61.80836696244423
61.80836696244423
55.939203232516554


I didn't move the ship, so its X position remained the same.

Here's a simple quicky I made (erm)..
Strict

Import mojo

Class Test Extends App
  Field shipX:Float = 30, shipY:Float = 100
	Field ballX:Float = 30, ballY:Float = 50, ballRadius:Float = 50
		
	Method OnCreate:Int()
		SetUpdateRate 60
		
		Return(0)
	End

	Method OnUpdate:Int()
		Local rotatedBy:Float = 90 
		#Rem - Method 1
		ballX = shipX + Cos(rotatedBy) * ballRadius
		ballY = shipY + Sin(rotatedBy) * ballRadius
		#End
		
		' Method 2
		Local rotatedPosition:Float[] = rotatePoint(ballX, ballY, shipX, shipY, rotatedBy)
		ballX = rotatedPosition[0]
		ballY = rotatedPosition[1]
	
		If shipX < 600
			shipX += 1
		Else
			shipX = 0
		End
    Return(0)
	End

	Method OnRender:Int()
		Cls
    DrawRect(shipX, shipY, 20, 20)
    DrawCircle(ballX, ballY, 10)
		Return(0)
	End
	
	' For method 2
	Method rotatePoint:Float[](pointX:Float, pointY:Float, centerX:Float, centerY:Float, angle:Float)
		Local result:Float[]
		Local radians:Float = angle * PI / 180
		Local rotatedX:Float = Cos(radians) * (pointX - centerX) - Sin(radians) * (pointY - centerY) + centerX
		Local rotatedY:Float = Sin(radians) * (pointX - centerX) + Cos(radians) * (pointY - centerY) + centerY
		result = [rotatedX, rotatedY]
		Return result
	End
End

Function Main:Int()
	New Test
  Return(0)
End


Note method 1 is your method and method 2 is the way I was doing it before.
Weird things are happening :/


nullterm(Posted 2015) [#5]


I think what you have is an unintended feedback loop. ballX/Y are fed into rotatePoint, but then are modified to the same. So you'll get all kinds of weird orbit behaviour as the ship moves. What I'd try...



localPointX/Y is relative to the center/shipX/Y. So the ball should maintain a steady/consistent orbit around the ship regardless of it's movement.

Also, Sin/Cos are degree based in Monkey, no need converting to radians.


Samah(Posted 2015) [#6]
@Sensei: Samah, that doesn't work.

Sorry, remove the delta timing from the last two lines and it should work.


Sensei(Posted 2015) [#7]
Nullterm, I think you've solved it, thanks!
I'll adapt my stufff accordingly..

Really appreciate all your help guys!
Hope someone else can make use of this too :)


Samah(Posted 2015) [#8]
Just a couple of things:
1. You're creating a new array object every time you call rotatePoint. If this is in a loop, it's very bad. I'd suggest passing in a local array and reusing it on every call.
2. I don't see the need for a rotatePoint call anyway when you can just do it in two lines... :/


nullterm(Posted 2015) [#9]
1. yeah, if you can get away from that, the garbage collector would be much happier.



Ideally, ballPos is new'd only once when the object is created or at app start, so you re-use the same ballPos memory.

2. Makes life easier in the long run to keep rotatePoint a function/method. You can re-use the function elsewhere for other objects. And keep the calling function smaller/readable.


Sensei(Posted 2015) [#10]
Thanks you guys. I wasn't quite sure how to go about it until Nullterm's example above. I'll give it a try now!


Sensei(Posted 2015) [#11]
Ok I got it working great:
The rotation parameters are actually part of the ship and so is the same as Nullterm's Class Point(): ship.x, ship.y, ship.rotateTime, ship.rotatorX, etc.

In the looping code, all I do now is this:
ship.rotateTime += 8 * dt.delta ' The rotation speed
rotatePoint(ship)

Then in the function:
Function rotatePoint:Void(p:Ship)
	p.rotatorX = Cos(p.rotateTime) * (p.radius) - Sin(p.rotateTime) + p.x 'Removed the * (localBallY) as it wasn't being used at all
	p.rotatorY = Sin(p.rotateTime) * (p.radius) + Cos(p.rotateTime) + p.y
End


This is great as I've now learned how to improve code efficiency, thanks!

@Samah: I tried your code as in the test.monkey I pasted earlier in this thread and it didn't move in there either. Grab that code and run it yourself and you will see. Thank you for your kind help though!

Even though I've been using Monkey-X for the past 1.5 years, I'm still quite new to OO style coding, so my learning skills are slow..