Friction From Scratch

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
User avatar
BmB
Prole
Posts: 8
Joined: Mon Jan 30, 2012 8:33 am

Friction From Scratch

Post by BmB »

Ok this maybe sounds completely simple and obvious, but I just can't figure out what is wrong here. I've made a small circle that you can steer around using the arrow keys. The first thing I wanted to handle was to solve the "strafejumping" problem of full movement along two axes adding up to more than the maximum speed along one axis. I've got that part working nicely with acceleration. But my friction to slow the circle back down seems to act up?

Code: Select all

  local friction = 4 * dt
  if xspeed > 0 then
    xspeed = math.max(xspeed - friction, 0)
  elseif xspeed < 0 then
    xspeed = math.min(xspeed + friction, 0)
  end

  if yspeed > 0 then
    yspeed = math.max(yspeed - friction, 0)
  elseif yspeed < 0 then
    yspeed = math.min(yspeed + friction, 0)
  end
Here's how my friction code works inside the update function. I just slow it down along each axis by a constant amount each frame until it hits zero. Should work fine, right? But it arcs toward one of the axes which feels very weird.
Attachments
main.lua
(2.19 KiB) Downloaded 163 times
User avatar
The Burrito
Party member
Posts: 153
Joined: Mon Sep 21, 2009 12:14 am
Contact:

Re: Friction From Scratch

Post by The Burrito »

I don't think the friction is what's causing the issue, that's pretty straightforward and looks good. It has something to do with the acceleration, note that if you hold 2 arrows at once the velocities take a while to balance out, and once they do if you release the friction takes over and everything is good.

I haven't taken an in depth look at your code, but I think your method of normalizing the speed is creating the issue, instead you might want to try vectorizing the force up front, and come up with a different way of limiting max speed. If I press 2 keys at the same time the velocities shouldn't be off by more than a tiny fraction, but the y acceleration always seems to be faster.
User avatar
ivan
Party member
Posts: 1912
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Friction From Scratch

Post by ivan »

Hi. I have to admit that I don't fully understand your code.
However I do have some suggestions:

Code: Select all

  xspeed = math.clamp(xspeed + speed[1] * accel, -maxspeed, maxspeed)
  yspeed = math.clamp(yspeed + speed[2] * accel, -maxspeed, maxspeed)
You are clamping a vector along the two axes seperately.
This might be a problem since a diagonal vector (maxspeed, maxspeed) wouldn't be the same length as a horizontal/vertical vector (maxspeed, 0) or (0, maxspeed).
Secondly, I'm not sure why are you multiplying 'accel'. The formula for acceleration is:

Code: Select all

finalvelocity = initialvelocity + acceleration
My implementation would be something like:

Code: Select all

yloc = 300
xloc = 400
xvel = 0
yvel = 0
damping = 2
maxacc = 500
maxvel = 400

function math.clamp(n, low, high)
  return math.min(math.max(n, low), high)
end

function math.clamp_vec(x, y, len)
  local d = math.sqrt(x * x + y * y)
  if d > len then
    local nx, ny = x / d, y / d
    x, y = nx * len, ny * len
  end
  return x, y
end


function love.update(dt)
  -- apply damping
  local damp = math.clamp(1 - damping * dt, 0, 1)
  xvel = xvel * damp
  yvel = yvel * damp

  local xacc, yacc = 0, 0
  if love.keyboard.isDown("up") then
    yacc = -maxacc
  end
  if love.keyboard.isDown("down") then
    yacc = maxacc
  end
  if love.keyboard.isDown("left") then
    xacc = -maxacc
  end
  if love.keyboard.isDown("right") then
    xacc = maxacc
  end

  -- apply acceleration
  xacc, yacc = math.clamp_vec(xacc, yacc, maxacc)
  xvel = xvel + xacc * dt
  yvel = yvel + yacc * dt

  -- apply velocity
  xvel, yvel = math.clamp_vec(xvel, yvel, maxvel)
  xloc = xloc + xvel * dt
  yloc = yloc + yvel * dt
end


function love.draw()
  love.graphics.circle("fill", xloc, yloc, 20, 32)
  love.graphics.print("x"..xvel, 5, 5)
  love.graphics.print("y"..yvel, 5, 20)
end
Where :
'maxvel' is in pixels per second
'maxacc' is the max change in velocity per second
'damping' is a value that slows velocity over time (borrowing the same formula from Box2D)
User avatar
BmB
Prole
Posts: 8
Joined: Mon Jan 30, 2012 8:33 am

Re: Friction From Scratch

Post by BmB »

Alright yeah I probably should have vectored it from the beginning. I just suck at math. :(

To clarify what I'm doing is I find the approximate equivalent diagonal length to an orthogonal length given by 1/ sqrt2 then I normalize the vector to be positive and within a range of 0 - 1:

Code: Select all

--not really a circle but don't know what else to call it
a2 = math.sqrt(2)

function vectorcontrol(xspd, yspd, maxspeed)
  --takes x speed, y speed and max speed and returns multiplication factor
  --for maintaining speed at arbitrary angles
  --input 1 for maxspeed if speed values fall in the range 0 - 1 etc

  local xspdc = math.abs(xspd / maxspeed)
  local yspdc = math.abs(yspd / maxspeed)
Then I find the maximum value and minimum value and determine the function to slow down by, by the difference between them, giving a negative function:

Code: Select all

local max = math.max(xspdc, yspdc)
  local min = math.min(xspdc, yspdc)
  local diff = (min - max)
I compress it by 0.5 / a2 - not sure about the 0.5 value, but seems close enough - and mirror it with -diff and finally raise it to it's proper value by * (1 / a2) this gives the final multiplication factor that is 1/sqrt2 when min and max are equal and 1 when the difference is 1.0--- roughly. Then I multiply it again with accel to give the final acceleration factor instead of a 0 - 1 value.
User avatar
BmB
Prole
Posts: 8
Joined: Mon Jan 30, 2012 8:33 am

Re: Friction From Scratch

Post by BmB »

Alright I vectored everything up. Works pretty well now. I've attached the new code if you want to check it out.

My next challenge would be collision detection. I have some ideas and some questions, should I just post them now or make a new thread?
Attachments
main.lua
v2
(2.33 KiB) Downloaded 143 times
User avatar
ivan
Party member
Posts: 1912
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Friction From Scratch

Post by ivan »

Hi again. The code looks better although I still think it's way more complicated than it needs to be.
For example:

Code: Select all

function vlength(x,y)
  x = math.abs(x)
  y = math.abs(y)
  if x ~= 0 and y ~= 0 then
    return math.sqrt(x^2 + y^2)
  else
    return x+y
  end
end
Could be simplified to:

Code: Select all

function vlength(x, y)
  return math.sqrt(x * x + y * y)
end
I personally write n*n instead of n^2 since the latter probably does a ''pow' function call which is unnecessary in this case.

Code: Select all

function setvlength(x, y, length, newlength)
  newlength = newlength / length
  return x*newlength, y*newlength
end
I think this function might be better if you didn't have to supply the old length of the vector:

Code: Select all

function setvlength(x, y, len)
  local d = math.sqrt(x * x + y * y)
  d = 1/d * len
  return x*d, y*d
end
BTW, you have to be careful not to call the above func with a 0 length lector or you'll get a division by 0.

Code: Select all

  local xspdc = math.abs(xspd)
  local yspdc = math.abs(yspd)
  if xspdc == yspdc then
    xspd = xspd * circle
    yspd = yspd * circle
  end
Here, I would have used the 'clamp_vec' function from my last post.
Or perhaps you can just normalize the vector by hand which I think is much more clear
(primarily because it doesn't need the 'circle' global):

Code: Select all

local d = vlength(xspd,yspd)
if d > 1 then
  xspd, yspd = xspd/d, yspd/d
end
Regarding 'friction': you are applying it in a linear manner:

Code: Select all

v = max(v - friction * dt, 0)
Can't say for sure but I think the Box2D approach that I mentioned is probably smoother:

Code: Select all

v = v * clamp(1 - damping * dt, 0, 1)
Lastly, I would advise against using table to represent vectors.
In my experience, it's an unnecessary strain on the Lua garbage collector.
Hope this helps
User avatar
tentus
Inner party member
Posts: 1060
Joined: Sun Oct 31, 2010 7:56 pm
Location: Appalachia
Contact:

Re: Friction From Scratch

Post by tentus »

ivan wrote: I personally write n*n instead of n^2 since the latter probably does a ''pow' function call which is unnecessary in this case.
If I remember correctly, n*n is faster than n^2 which is faster than math.pow(n, 2). Can anyone confirm this please?
Kurosuke needs beta testers
User avatar
The Burrito
Party member
Posts: 153
Joined: Mon Sep 21, 2009 12:14 am
Contact:

Re: Friction From Scratch

Post by The Burrito »

tentus wrote:If I remember correctly, n*n is faster than n^2 which is faster than math.pow(n, 2). Can anyone confirm this please?
math.pow(n,2) takes about twice as long as n^2. n*n takes about a third of the time so it's definitely the fastest method. If you are doing ^3 or higher n^N is going to be the fastest.
User avatar
BmB
Prole
Posts: 8
Joined: Mon Jan 30, 2012 8:33 am

Re: Friction From Scratch

Post by BmB »

Hey, the reason I'm taking the old length is so you don't have to compute the length more than once. It's a bit funny that you are arguing about whether n*n or n^2 is faster but use sqrt(x*x,y*y) frivolously. :crazy:

The length function is perhaps a bit complex yes. Most of it is just vestigal remnants from me trying to figure out how to do it. :P

Friction I'll say, I'm pretty sure it works linearly IRL. I made a point out of this from the get go when making this.

I tried using tables at first but it was kinda buggy, so I put it back. I left loc as a table because why not? Seems to make no diff and the variable is global for now so there's no collection to be done surely?
User avatar
ivan
Party member
Posts: 1912
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Friction From Scratch

Post by ivan »

If I remember correctly, n*n is faster than n^2 which is faster than math.pow(n, 2). Can anyone confirm this please?
Haven't tested this but sounds about right.
BmB wrote:Hey, the reason I'm taking the old length is so you don't have to compute the length more than once. It's a bit funny that you are arguing about whether n*n or n^2 is faster but use sqrt(x*x,y*y) frivolously. :crazy:
I belive 'sqrt' is used twice in my previous code example.
In general I think code reusability is often more important than optimizations.
BmB wrote:Seems to make no diff and the variable is global for now so there's no collection to be done surely?

Code: Select all

  control = controlvector()
  control = {control[1]*accel, control[2]*accel}

Code: Select all

  loc = {loc[1] + xspeed, loc[2] + yspeed}
Each of these lines creates a new table.
The old table refernces that you are overwriting have to be collected by the GC.
Post Reply

Who is online

Users browsing this forum: No registered users and 4 guests