This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
KingofGamesYami's profile picture

Gravity Manipulation Questions

Started by KingofGamesYami, 19 January 2015 - 03:17 AM
KingofGamesYami #1
Posted 19 January 2015 - 04:17 AM
As the title says, I've been screwing with "gravity". Lots of fun math in that, force vectors and such. Currently I'm having a little problem where I should hit the "planet", but never do (actually if it's small enough you can force your way to it with the controls, but…).

Plus, I'm getting acceleration from who-knows-where, probably the planet but not in the direction of the planet.

It's a little hard to describe, just run the code and it'll be fairly obvious.

Controls: arrow keys

Spoiler

local function radians( x1, y1, x2, y2 )
	return math.atan2( x2-x1, (y2-y1) ) --#might add 0.66 compensate for strange CC pixels
end

local function getXY( r, mag )
	return mag * math.cos( r ), mag * math.sin( r )
end

local function fGrav( m1, x1, y1, m2, x2, y2 )
	return (m1*m2)/math.sqrt((x2 - x1)^2 - ((y2 - y1)*0.66)^2)^2 --#might add0.66 compensate for strange CC pixels
end

local function mass( r )
	return math.pi * r^2
end

local maxx, maxy = term.getSize()

local gravPoints = {}
gravPoints[ #gravPoints + 1 ] = { x = maxx, y = math.random( 1, maxy ), magnitude = math.random( 2, 4 ) }

--Thanks to TheOriginalBIT for this circle code
local function drawCircle( centerX, centerY, radius, col, solid )
    solid = solid or false
    local isAdvanced = term.isColor and term.isColor()
    local char = isAdvanced and " " or "|"
    if isAdvanced then term.setBackgroundColor( col ) end
    local radStep = 1/(1.5*radius)
    for angle = 1, math.pi+radStep, radStep do
        local pX = math.cos( angle ) * radius * 1.5
        local pY = math.sin( angle ) * radius
        if solid then
            local chord = 2*math.abs(pX)
            term.setCursorPos( centerX - chord/2, centerY - pY )
            write( char:rep( chord ) )
            term.setCursorPos( centerX - chord/2, centerY + pY )
            write( char:rep( chord ) )
        else
            for i=-1,1,2 do
                for j=-1,1,2 do
                    term.setCursorPos( centerX + i*pX, centerY + j*pY )
                    write( char )
                end
            end
        end
    end
end

local object = { x = 1, y = 1, mag = 0, angle = 0 }

local function draw()
	term.setBackgroundColor( colors.lightGray )
	term.clear()
	--draw gravity points
	for _, point in pairs( gravPoints ) do
		drawCircle( point.x - object.x, point.y - object.y, point.magnitude, colors.gray, true )
	end
	--draw player
	term.setBackgroundColor( colors.green )
	term.setCursorPos( maxx / 2, maxy / 2 )
	term.write( " " )
	term.setBackgroundColor( colors.lightGray )
	term.setTextColor( colors.black )
	term.setCursorPos( 1, 1 )
	term.write( "Speed: " .. tostring(object.mag):match( "%d+%.%d%d" ) )
	term.setCursorPos( 1, 2 )
	term.write( "X: " .. object.x )
	term.setCursorPos( 1, 3 )
	term.write( "Y: " .. object.y )
end

local tControls = {}

local id = os.startTimer( 0.1 )
while true do
	local event = { os.pullEvent() }
	if event[ 1 ] == "timer" and event[ 2 ] == id then
		--calc movement on object
		local vx, vy = getXY( object.angle, object.mag )
		id = os.startTimer( 0.1 )
		for _, point in pairs( gravPoints ) do
			local mag = fGrav( mass( 1 ) * 2, object.x, object.y, mass( point.magnitude ) * 2, point.x, point.y )
			local angle = radians( object.x, object.y, point.x, point.y )
			local x, y = getXY( angle, mag )
			vx = vx + x
			vy = vy + y
		end
		--add player forces
		if tControls[ keys.up ] then
			vy = vy - 0.66
		end
		if tControls[ keys.down ] then
			vy = vy + 0.66
		end
		if tControls[ keys.right ] then
			vx = vx + 1
		end
		if tControls[ keys.left ] then
			vx = vx - 1
		end
		tControls = {}
		object.x = object.x + vx
		object.y = object.y + vy
		object.angle = math.atan2( vx, vy )
		object.mag = math.sqrt(vx^2 + vy^2) 
		draw()
	elseif event[ 1 ] == "key" then
		tControls[ event[ 2 ] ] = true
	end
end

PS: Anyone know a good way to generate an infinite map? My attempts have been… not so great. If you feel interested in contributing, reply here or PM me.
Exerro #2
Posted 19 January 2015 - 07:49 AM
The getXY() function should use sin() for x and cos() for y I believe (I have always used it like this and it's worked perfectly).

As for terrain generation, does it generate vertically and horizontally or just horizontally?
Edited on 19 January 2015 - 06:49 AM
HometownPotato #3
Posted 19 January 2015 - 08:36 AM
Awsum, actually that's incorrect. Since the Y is vertical and X is horizontal it would be as it is.
But whenever you want to use it you have take note that the y axis increases as you go down, meaning 45 degree rotation would actually be -45 degrees.
KingofGamesYami #4
Posted 19 January 2015 - 01:25 PM
@awsumben13 - horizontally and vertically
Also, I tried using the sin/cos you suggested, it's still being derpy though.

@HometownPotato - I know the y axis increases as I go down, thats why keys.down is bound to +0.66. But I have no idea what you mean by the rotation, AFAIK I'm not doing rotations.

Edit: What seems to be happening is I get attracted through the planet, but after getting to it I'm accelerated through it.
Edited on 19 January 2015 - 12:50 PM
Exerro #5
Posted 19 January 2015 - 04:40 PM
sin(0) is 0, sin(90) is 1, so assuming 0 degrees is facing upwards I was correct in saying sin(angle) should be used for horizontal and cos(angle) should be used for vertical. Also, you ever tried doing cos of a negative angle? That's not how things work. You need to negate the result after you cos() it.


local function getXY( r, mag )
	    return mag * math.sin( r ), mag * -math.cos( r )
end

Again, this is assuming that 0 degrees is facing upwards.

Accelerating towards the planet is to be expected I think, but you should slow down afterwards. - Nope, you don't slow the velocity down at all (multiply it by .95 every frame for example). Any increase in speed going into the planet should be matched (negatively) going out of the planet, so you'll be going at the same speed you entered it as a result of pressing keys.

I've noticed that you're multiplying some things by .66 but not for others, so that could be causing unexpected behaviour. You also do math.sqrt(…)^2 in the fGrav() function, which is a little pointless?
KingofGamesYami #6
Posted 19 January 2015 - 06:14 PM
I was taught to use the degrees from +x axis (ei right), I've only changed it to radians. So 0 degrees should be right. I bet the negative thing is the problem, will it always need to be negative or will I have to do something like this:

Actually it wasn't this. It was the speed I was moving at: I was achieving a pendulum effect - but the force was so large that I was moving around 7,000 pixels / second. Now that I've scaled that down, it still exists, but not in such a huge capacity. On top of this, I'm hoping to get the hitboxes going, so I'll die if something like this happens.



local function getXY( r, mag )
  return mag * ( r > 0 and math.sin( r ) or -math.sin( r ), mag * (r > 0 and math.cos( r ) or -math.cos( r )
end


Yeah, I should go towards the planet, but I continue acceleration after passing it. I'm betting its the getXY that's the problem, I'll fix that.

I've been testing with/without the .66 modifications, I'll get it figured out after the scripts other bugs make it possible to see the effects clearly.

This is pretty much put off for now, I'll come back to it

Also, the math.sqrt(…)^2 is pointless, I realize now. Basically the math.sqrt & everything inside is part of the distance formula, and the forumla for gravity squares the distance… So when I got rid of the distance() function, that happened. I'll edit it out now.

Fixed.
Edited on 20 January 2015 - 01:00 AM
KingofGamesYami #7
Posted 20 January 2015 - 01:56 AM
Spoiler
local function radians( x1, y1, x2, y2 )
	return math.atan2( x2-x1, (y2-y1) ) --0.66 (maybe) compensate for strange CC pixels
end

local function getXY( r, mag )
	return mag * math.sin( r ), mag * -math.cos( r )
end

local function fGrav( m1, x1, y1, m2, x2, y2 )
	return (m1*m2)/((x2 - x1)^2 - (y2 - y1)^2) --0.66 (maybe) compensate for strange CC pixels
end

local function mass( r )
	return math.pi * r^2
end

local maxx, maxy = term.getSize()

local gravPoints = {}
gravPoints[ #gravPoints + 1 ] = { x = maxx, y = math.random( 1, maxy ), magnitude = math.random( 2, 4 ) }

--Thanks to TheOriginalBIT for this circle code
local function drawCircle( centerX, centerY, radius, col, solid )
    solid = solid or false
    local isAdvanced = term.isColor and term.isColor()
    local char = isAdvanced and " " or "|"
    if isAdvanced then term.setBackgroundColor( col ) end
    local radStep = 1/(1.5*radius)
    for angle = 1, math.pi+radStep, radStep do
        local pX = math.cos( angle ) * radius * 1.5
        local pY = math.sin( angle ) * radius
        if solid then
            local chord = 2*math.abs(pX)
            term.setCursorPos( centerX - chord/2, centerY - pY )
            write( char:rep( chord ) )
            term.setCursorPos( centerX - chord/2, centerY + pY )
            write( char:rep( chord ) )
        else
            for i=-1,1,2 do
                for j=-1,1,2 do
                    term.setCursorPos( centerX + i*pX, centerY + j*pY )
                    write( char )
                end
            end
        end
    end
end

local object = { x = 1, y = 1, mag = 0, angle = 0 }

local function draw()
	term.setBackgroundColor( colors.lightGray )
	term.clear()
	--draw gravity points
	for _, point in pairs( gravPoints ) do
		drawCircle( point.x - object.x, point.y - object.y, point.magnitude, colors.gray, true )
	end
	--draw player
	term.setBackgroundColor( colors.green )
	term.setCursorPos( maxx / 2, maxy / 2 )
	term.write( " " )
	term.setBackgroundColor( colors.lightGray )
	term.setTextColor( colors.black )
	term.setCursorPos( 1, 1 )
	term.write( "Speed: " .. tostring(object.mag):match( "%d+%.%d?%d?" ) )
	term.setCursorPos( 1, 2 )
	term.write( "Angle: " .. tostring(object.angle * 180/math.pi):match( "%d+%.%d?%d?" ) )
	term.setCursorPos( 1, 3 )
	term.write( "X: " .. tostring( object.x ):match( "%d+%.%d?%d?" ) )
	term.setCursorPos( 1, 4 )
	term.write( "Y: " .. tostring( object.y ):match( "%d+%.%d?%d?" ) )
end

local tControls = {}
local bRunning = true

local id = os.startTimer( 0.1 )
while bRunning do
	local event = { os.pullEvent() }
	if event[ 1 ] == "timer" and event[ 2 ] == id then
		--calc movement on object
		local vx, vy = getXY( object.angle, object.mag )
		id = os.startTimer( 0.1 )
		for _, point in pairs( gravPoints ) do
			if math.sqrt( (point.x - object.x)^2 - ((point.y - object.y)*0.66)^2 ) <= object.mag then
				bRunning = false
			end
			local mag = fGrav( mass( 1 ), object.x, object.y, mass( point.magnitude / 2 ), point.x, point.y )
			local angle = radians( object.x, object.y, point.x, point.y )
			local x, y = getXY( angle, mag )
			vx = vx + x
			vy = vy + y
		end
		--add player forces
		if tControls[ keys.up ] then
			vy = vy - 1
		end
		if tControls[ keys.down ] then
			vy = vy + 1
		end
		if tControls[ keys.right ] then
			vx = vx + 1
		end
		if tControls[ keys.left ] then
			vx = vx - 1
		end
		tControls = {}
		object.x = object.x + vx
		object.y = object.y + vy
		object.angle = math.atan2( vx, vy )
		object.mag = math.sqrt(vx^2 + vy^2)
		draw()
	elseif event[ 1 ] == "key" then
		tControls[ event[ 2 ] ] = true
	end
end

I've fixed a lot of issues after figuring out what was happening from that stats, but I now have a different problem. It's not properly registering the planets position, or something like that. It won't stop when I'm actually passing through the planet, but it will stop randomly at other points - noticeably far away. A couple of times the planet wasn't even on screen.
Exerro #8
Posted 20 January 2015 - 05:03 PM
Maybe it's due to the combined effect of multiple planets pulling the (ship?). Stopping randomly at other points gives me the idea of getting stuck between a load of planets, so maybe add a limit to the range of a planet's pull just to see if that's the cause.
KingofGamesYami #9
Posted 20 January 2015 - 06:29 PM
I don't have multiple planets - yet.


Eventually I'll add a limit on the planet just to reduce the amount of calculations, but it should effectively diffuse, the pull getting smaller and smaller over the distance.

At the moment I'm just checking if the distance to the ship is less than or equal to the radius of the planet - but I can go strait through the planet. Also, the gravitational field seems a little off as well.
Exerro #10
Posted 20 January 2015 - 07:23 PM
Maybe try rendering the gravitational field in some way, render the centre of the planet, add in a load of prints, that kind of thing. When you have no idea what is going on these things help enormously.
KingofGamesYami #11
Posted 21 January 2015 - 01:26 AM
Figured it out. My "ship" starts out at 0,0 but is rendered in the middle of the screen. After compensating for that, the detection works properly.

However, I've now encountered an issue where I get up to 600 pixels / sec, effectively jumping between two points on either side of the planet. So, I'm now trying to project the path of the line it will travel and figure out if a point on that line contains a planet.
Bomb Bloke #12
Posted 21 January 2015 - 02:23 AM
Assuming the line's straight, that's easy enough - figure out any two points on it, use them to determine the line's equation, then solve that equation for the x-co-ord of the planet. If that equals the y-co-ord of the planet, then the planet's on that line.
KingofGamesYami #13
Posted 21 January 2015 - 02:34 AM
Assuming the line's straight, that's easy enough - figure out any two points on it, use them to determine the line's equation, then solve that equation for the x-co-ord of the planet. If that equals the y-co-ord of the planet, then the planet's on that line.

Problem: Planet is more than 1 pixel wide.
Exerro #14
Posted 21 January 2015 - 07:13 AM
Do what bomb bloke suggested but check the y point is within the planet's height depending on how far across it is from the centre of the planet.

Edit: this won't work actually. Instead, find the closest point on the line to the centre of the planet and check to see if it's within the radius. You can find the closest point by finding gradient of the normal of the line, finding the y intercept by using the planets centre coordinates, then finding where that line intersects with the original line.
Edited on 21 January 2015 - 06:40 AM
Bomb Bloke #15
Posted 21 January 2015 - 07:52 AM
Unfortunately, that'd only work if the line happened to be intersecting the circle at a point on the same level as the circle's center (I'm assuming the planets are circles) - that doesn't have to be the case! It's a little more complex to calculate if it's not.

Eg:

http://stackoverflow.com/questions/1073336/circle-line-segment-collision-detection-algorithm
GopherAtl #16
Posted 21 January 2015 - 01:30 PM
you appear to be using the euler method of integration, which is always going to give very sketchy and unstable results; unless your time step is incredibly small, you're not going to be able to have anything in a remotely stable orbit, and in general the whole system will be prone to instability, with objects being flung completely out of the simulation.

this article will explain in more detail why euler sucks, and walk you through how to implement RK4 for much better results, which seems scary but isn't really so bad. Linky
The code is in C, but it being mostly math, it shouldn't be too hard to adapt. Just replace the structs with lua tables and you're set.
Edited on 21 January 2015 - 12:30 PM
KingofGamesYami #17
Posted 21 January 2015 - 03:23 PM
Thanks for the link, I'm trying to make sense of it. I know a little C, but this leaves me confused:


Derivative evaluate( const State &amp;initial,  float t,  float dt,  const Derivative &amp;d ) { --#What is the &amp;initial / &amp;d doing?
 State state; --#is this even needed in lua?
 state.x = initial.x + d.dx*dt; --#ok, adding.  I get it
 state.v = initial.v + d.dv*dt;
 Derivative output; --#Derivative == function, right?  But then this makes no sense.
 output.dx = state.v;
 output.dv = acceleration(state, t+dt) --#Uses function acceleration, which I don't have (yet).
 return output;
}
Lyqyd #18
Posted 21 January 2015 - 03:26 PM
State and Derivative are both types (well, close enough for our purposes). The function you've got there is a function named evaluate that returns a Derivative.
GopherAtl #19
Posted 21 January 2015 - 03:44 PM
yah, c is a language with formal typing, so you have to have structs and classes where lua just has tables. Basically,


struct State
	{
		float x;	  // position
		float v;	  // velocity
	};
defines a struct (table) which has exactly 2 values in it, called x and v, which must hold floats, i.e., numbers.

In lua you wouldn't have the struct declaration at all, you'd just make state tables like so:

  initialState = { x=someposition, v=somevelocity }

also note that the example, for simplicity, is one-dimensional; you'll have x and y, so basically all the code will be changed to using vectors for position instead of just the singular x. So a, b, c, d will all be {x,y} pairs.

ok, on reflection that's a bit more involved than I was initially thinking. Still, not too bad if you're patient and methodical about it, honest! XD
Edited on 21 January 2015 - 02:47 PM
KingofGamesYami #20
Posted 21 January 2015 - 03:52 PM

local function evaluate( initial,  t,  dt, d )
 local state = { x = initial.x + d.dx * dt, v = initial.v + d.dv*dt}
 local output = {}
 output.dx = state.v;
 output.dv = acceleration(state, t+dt)
 return output;
end

This the closest translation of the code I could get, correct me now if I've gone wrong somewhere.
GopherAtl #21
Posted 21 January 2015 - 04:07 PM
I see no mistakes there. As I said, you'll have to extend it to 2 dimensions, which will make that particular function more like this…


local function evaluate( initial,  t,  dt, d )
local state = { x = initial.x + d.dx * dt,   y=initial.y + d.dy*dt,
	vx = initial.vx + d.dvx * dt, vy=initial.vy+d.dvy*dy }
local output = {}
output.dx = state.vx
output.dy=state.vy
output.dvx, output.dvy = acceleration(state, t+dt) --assuming updated acceleration returns x,y
return output;
end
Or, if you wanted to use the vector API…
and changed "x" to "p" for position


local function evaluate( initial,  t,  dt, d )
local state = { pos = initial.pos + d.dx * dt, v  = initial.v + d.dv * dt }
local output = {}
output.dp = state.v
output.dv, = acceleration(state, t+dt) --assuming updated acceleration returns a vector
return output;
end


With the vector api version, you have to make a new vector with vector.new(xval,yval) (you can just ignore z, leaving it at 0 for all) so you would set up your initial state something like:
(changing "x" to "p" for pos, leaving v for velocity)
local initialState = { p = vector.new(10,10), v=vector.new(0,0) }
Edited on 21 January 2015 - 03:13 PM