2
\$\begingroup\$

The following code is what I use to simulate a particles movement in a gravitational scenario.

The problem is that this simulation needs to work well on devices with different aspect ratios, and different frame rates.

Basically offscreen particles spawn in corners are shot towards the corner of the opposite side. Meanwhile each touch on the screen is a gravitational force attracting the particles to it. The simulation works fine in 30fps, however at other fps' it begins to mess up.

func gravitationalMovement()
{
    var c = vv                                          //vv is simply holding the velocity I put it into c to make the code smaller, and debugging easier

    let pos = Point(x: particlePosX, y: particlePosY)   //In the real code this isn't that simple but for this questions sake it is

    var build = Point()
    let array = TB.allFingersDown                       //An array of finger structs for each one on screen

    for fing in array                                   //Go through every finger
    {
        let planet = fing.location
        let vector = Math.subtract(pos, planet)         //Vector between particle and finger
        let dist = Math.dist2Psq(vector)                //Distance squared
        let theta = Math.calculateAngle(vector)

        var force = 19920 / dist
        let cap = Storage.bounds.tb / 50                //Height / 50
        if (force > cap){force = cap}                   //Cap it off so things don't go bonkers

        build.x += force * cos(theta)                   //Add the resulting velocity to the one that will hold the additive velocity
        build.y += force * sin(theta)
    }

    c.x += build.x                                      //Add the resulting velocity from all of the fingers to the existing velocity
    c.y += build.y

    particlePosX += c.x                                 //Add velocity to position
    particlePosY += c.y


    let drag = pow(1.009, Storage.dtPercC)              //Drag the velocity
    c.x /= drag
    c.y /= drag


    if (isInBoundsPlus() != -1)                         //If out of bounds
    {
        //Respawn in one of the 4 corners, and get shot out to the opposite corner
        let corner = arc4random_uniform(4)              ///0-3
        var p2 = Point()
        if (corner == 0)
        {
            //Bottom left
            particlePosX = 0
            particlePosY = 0
            p2 = Point(x: Storage.bounds.rb, y: Storage.bounds.tb)
        }
        else if (corner == 1)
        {
            //bottom right
            particlePosX = Storage.bounds.rb
            particlePosY = 0
            p2 = Point(x: 0, y: Storage.bounds.tb)
        }
        else if (corner == 2)
        {
            //Top Left
            particlePosX = Storage.bounds.lb
            particlePosY = Storage.bounds.tb
            p2 = Point(x: Storage.bounds.rb, y: 0)

        }
        else if (corner == 3)
        {
            //Top Right
            particlePosX = Storage.bounds.rb
            particlePosY = Storage.bounds.tb
            p2 = Point(x: 0, y: 0)
        }
        let p1 = Point(x: particlePosX, y: particlePosY)

        let angle = Math.calculateAngle(p1, p2) + Math.rand2(-0.01, secondNum: 0.01) //Add some variance

        let ar = Storage.bounds.rb / Storage.bounds.tb                              //Multiplied by aspect ratio so that the particle has enough speed to get to other corner even if device is wide
        let diagonal = (Math.dist2P(p1, p2) / 60) * ar

        c.x = diagonal * cos(angle)                                                 //Set the velocity
        c.y = diagonal * sin(angle)
    }
    vv = c                                                                          //Save the velocity to the variable vv
}

I believe the solution is to better timestep this code. I have 2 variables that will help me with this.

  1. dt: probably needs no explanation but this is the time between frames
  2. Storage.dtPercC: This is the dt percent, which is basically dt / (1 / 60.0). If the simulation runs at an ideal framerate this variable will be 1, however as the fps lowers that number will go up.

Yes I have checked and these number work and are accurate.

The movement is velocity based, each frame the velocity is added to position. The velocity is affected by two forces.

  1. Drag
  2. The additive force from the fingers
  3. Bursting force when a particle spawns in a corner, it must be enough to get it to the other corner, but not too much where the particles skip around gravitation.

So far I have figured out how to make the drag force the same on all devices by doing pow(1.006, Storage.dtPerc) as a devisor to the velocity however I still have two issues.

  1. The bursting time stepping, the lower the fps the less likely it is for particles to get to the other corner because of increased drag per frame
  2. The velocity from the planets (fingers)

I have tried multiplying numbers by the dt percent, and pow'ing numbers to the dt percent.

My question is where in the simulation do I multiply, divide, or raise a value to the power of dt Percent? I can not figure out where and how this belongs.

\$\endgroup\$
  • \$\begingroup\$ To be honest, I haven't seen the concept of dtPercent before. Usually deltatime is in miliseconds in itself already a decimal measure. What kind of value is dt in your code then? \$\endgroup\$ – Felsir Jul 14 '16 at 8:06
  • \$\begingroup\$ Really? It has been very useful on simpler simulations. Because let's say you want it to move 10 pixels when fps is 60. Then you just move by 10 * do dtperc. However if something happens that makes the fps 30 then dtpercent is ((1/30)/(1/60)) which is 2. And it will move 20 pixels in one frame instead. \$\endgroup\$ – J.Doe Jul 14 '16 at 8:09
  • \$\begingroup\$ That being said I do have the traditional dt in ms stored. Feel free to use that instead in your solution. \$\endgroup\$ – J.Doe Jul 14 '16 at 8:10
  • \$\begingroup\$ The concept of the dt already takes that into account. So if you have 60fps, dt will be 0.0166. If you run at 30 fps dt will be 0.032. So if you want to move by 10 pixels per second that equals to 10 * dt = 0.16 pixels per frame. In a 30 fps situation that equals to 0.32 pixels per frame meaning in half the number of frames the object has travelled equal distance- which means your objects are framerate independant. \$\endgroup\$ – Felsir Jul 15 '16 at 6:51
3
\$\begingroup\$

The solution is to abandon your concept of dtpercent. To make it framerate independent deltatime between frames is all you need.

Framerate 60 fps: dt will be 0.016 miliseconds with a framerate of 30 this value will be 0.032 miliseconds. Thus have everything based on the dt and scale your simulation units to 'x per second' and things will run framerate independant.

So if you want something to move 60 pixels per second:

at 60fps: speed * dt = 60 * 0.016 => 1 pixel per frame.

at 30fps: speed * dt = 60 * 0.032 => 2 pixels per frame.

Looking at your code, swap out dtpercent for dt and it should work fine. You may need to adapt your base values (since those seem to be based on 30fps?)

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.