user1423893
user1423893

Reputation: 796

Oscillate or "ping pong" between two values?

I have a path that is evaluate at time 't' and returns an orientation and position based on the path type.

The value for time is affected by the path type:

switch (type)
{
    case PathType.Closed:
        time = ToolBox.Wrap(time, StartTime, EndTime);
        break; // Wrap time around the path time to loop

    case PathType.Open:
        time = ToolBox.Min(time, EndTime);
        break; // Clamp the time value to the max path time range

    case PathType.Oscillating:
        break;
}

The missing link is oscillating.

My question is what is a good, efficient way for oscillating between two values?

For example (2, 7). If time reaches 7 it reverses and decrements towards to 2 and once it reaches 2 it reverses and increases towards 7.

The algorithm should know whether to increase/decrease the value based on the original value so if the value is 9 it knows the answer is 7 - (Abs(7 - 9). If the value is 14 the value has wrapped around so it will result in an increase of 1.

Higher values will also increase or decrease the value depending on the number of times it wraps around the original range.

I hope that makes sense as I'm finding it difficult to explain.

EDIT:

Doesn't oscillate with floating point values:

        for (float i = 0; i < 100; i += 0.1f)
        {
            Console.WriteLine("{0} {1}", i, Oscillate(2.5f, 7.5f, i));
        }

    private float Oscillate(float min, float max, float value)
    {
        float range = max - min;

        float multiple = value / range;

        bool ascending = multiple % 2 == 0;
        float modulus = value % range;

        return ascending ? modulus + min : max - modulus;
    }

Upvotes: 2

Views: 12450

Answers (6)

Echo
Echo

Reputation: 1

this is kinds old, but it showed up in my search. Here is my simple solution for ping-pong-ing between 0 and a variable:

def ping_pong(value,maximum):
odd_pong = (int(value / maximum) % 2 == 1)
mod = value % maximum
if odd_pong:
    return maximum - mod
else:

Upvotes: 0

andand
andand

Reputation: 17507

Ideally, you should be abstracting this functionality into some kind of a class and not be concerned about how the implementation actually works when you're using it. Here's an initial take on what that would look like in C++ (my C# is a little rusty). I think you can work it into C# with only little difficulty.

class oscillator
{
  private:
    float min;
    float max;

    static float mod(float num, float div)
    {
        float ratio = num / div;
        return div * (ratio - std::floor(ratio));
    }

  public:
    oscillator(float a, float b)
      : min(a < b ? a : b), max(a > b ? a : b) {}

    float range() ( return max-min; }

    float cycle_length() { return 2*range(); } 

    float normalize(float val)
    {
        float state = mod(val-min, cycle_length());

        if (state > range())
            state = cycle_length()-state;

        return state + min;
    }
};

Upvotes: 4

Reacher Gilt
Reacher Gilt

Reputation: 1813

What you're describing sounds a lot like periodic linear interpolation between two values. Consider using XNA's MathHelper.Lerp function as the basis for your oscillation.

Note that it uses a percentage to control the oscillation as its third parameter. You'll have to figure out how to translate your time t value into a percentile, but you could start with ex. sin(t) to see how things work.

If you're reluctant to import XNA into your project, the core equation is very simple:

value1 + (value2 - value1) * amount

Edit: If your question, at its heart, really is "What is a good, efficient way for oscillating between two values?", then Math.Sin(t) (or Cos) can provide you with regular oscillation between 0 and 1.


Upvotes: 0

RickL
RickL

Reputation: 2821

New answer to account for float values:

    // Note: Increase FACTOR depending on how many decimal places of accuracy you need.
    private const float FACTOR = 10;

    public void Test()
    {
        for (float i = 0; i < 1000; i += 0.1F)
        {
            Console.WriteLine("{0} {1}", i, Oscillate(2.5F, 7.5F, i));
        }
    }

    private float Oscillate(float min, float max, float time)
    {
        return (float)(Oscillate_Aux(Upscale(min), Upscale(max), Upscale(time))) / FACTOR;
    }

    private int Upscale(float value)
    {
        return (int)(value * FACTOR);
    }

    private int Oscillate_Aux(int min, int max, int time)
    {
        int range = max - min;

        int multiple = time / range;

        bool ascending = multiple % 2 == 0;
        int modulus = time % range;

        return ascending ? modulus + min : max - modulus;
    }

Upvotes: 1

Servy
Servy

Reputation: 203819

Here is what I came up with:

public static int Oscillate(int input, int min, int max)
{
    int range = max - min ;
    return min + Math.Abs(((input + range) % (range * 2)) - range);
}

I'm assuming input will be a counter starting at 0.

Upvotes: 10

naspinski
naspinski

Reputation: 34707

This will oscillate your numbers between 2 & 7, in this example, time is an int:

bool isIncreasing = time <= 7;
for (int i = 0; i < 20; i++) //some random loop
{
    time = time + (isIncreasing ? 1 : -1);
    if (time >= 7 || time <= 2) isIncreasing = !isIncreasing;
}

Upvotes: 3

Related Questions