tags:

views:

317

answers:

8

Ok, I have a control that want to fade in and out (continuously). In order to fade it I adjust the transparency value of the control in the drawing routine. So I set up a timer that runs and I adjust the m_transparency member. The timer runs and it should sweeps back and forth between the two defined values m_start, m_end. These can be 0-255.

eg. start 30, end 55, increment value = 5. It would look like:

30, 35, 40, 45, 55, 50, 45, 40, 35, 30, 35, 40 .......

should also handle start 55, end 30.

Anyways, I wrote a horrific function that basically tests every condition (am I moving up or down and have I reached the start, end, turn around point, etc). It is ugly and I'm embarrassed (triple nested ifs) but I can't figure out a cleaner way to do it then test everything. Is there an easier way?

+4  A: 

This is how I'd approach it, a C-based solution (that will work in C++ of course, but doesn't have any C++ features):

int nextVal (int curr, int min, int max, int *pStep) {
    // Handle situations where you want to turn around.

    if ((curr == min) || (curr == max)) {
        *pStep = -(*pStep);
        return curr + *pStep;
    }

    // Handle situation where you would exceed your bounds (just
    //   go to the bound).

    if (curr + *pStep < min) return min;
    if (curr + *pStep > max) return max;

    // Otherwise, just move within the bounds.

    return curr + *pStep;
}

 

int main(void) {
    int i;

    // Set up your variables here.

    int min = 30;
    int max = 55;
    int step = 5;

    // This is the running current value and we need to negate the step
    //   to force initial turn-around.

    int curr = min;
    step = -step;

    // Simple loop to demonstrate.

    printf ("%d\n", curr);
    for (i = 19; i > 0; i--) {
        curr = nextVal (curr, min,max,&step);
        printf ("%d\n", curr);
    }

    return 0;
}

This outputs:

30, 35, 40, 45, 50, 55, 50, 45, 40, 35, 30, 35, 40, 45, 50, 55, 50, 45, 40, 35

for your test case. It also intelligently handles sequences where the range isn't an exact multiple of the increment (for example, [7,40,10]):

7, 17, 27, 37, 40, 30, 20, 10, 7, 17, 27, 37, 40, 30, 20, 10, 7, 17, 27, 37
paxdiablo
if ((curr <= min) || (curr >= max)) {
ralu
That's not necessary, @ralu, the value is never allowed to go beyond the bounds and if, for some bizarre reason, the caller starts with it outside those bounds, the first step will bring it back.
paxdiablo
+6  A: 

The following class does it fairly simply. You'll want to think about how you want it to behave in around the border cases (this will emit 55 for a RangeScanner(30, 54, 5) for example, but it's easy to change this to the behaviour you want).

class RangeScanner
{
public:
    RangeScanner(int start, int end, unsigned int inc) :
        value(start),
        lower(std::min(start, end)),
        upper(std::max(start, end)),
        increment(inc),
        incrementing(start < end)
    {
    }

    int nextValue()
    {
        int ret = value;

        value += incrementing ? increment : -increment;

        if (value >= upper || value <= lower)
        {
            incrementing = !incrementing;
        }

        return ret;
    }

private:
    int value;
    const int lower;
    const int upper;
    const unsigned int increment;
    bool incrementing;
};
Martin
The "incrementing" variable could be encoded in the sign of the "increment" variable.
camh
It could, but I don't believe that would be as clear.
Martin
Really? A negative increment to mean decrement is a pretty common idiom. For something as simple as this, I personally find extraneous variables to be a distraction. YMMV.
camh
As a general rule, I prefer to keep different pieces of object state separate. It makes it harder for some idiot to come along and break it later, and reduces the amount of error checking required - consider someone misunderstanding the parameters and trying to create a RangeScanner(55, 30, -5).
Martin
+1  A: 

My version repeats the limit values: ... 50, 55, 55, 50, ..., 35, 30, 30, 35, ...

struct UpDown {
    int lo_val, hi_val, curr_val;
    int step;
};

void fade_in_out(struct UpDown *x) {
    x->curr_val += x->step;
    if ((x->curr_val > x->hi_val) || (x->curr_val < x->lo_val)) {
        x->step *= -1;
        x->curr_val += x->step;
    }
}

int main(void) {
    struct UpDown myControl = {30, 55, 30, 5};
    for (;;) {
        fade_in_out(&myControl);
    }
    return 0;
}
pmg
+4  A: 
erickson
A: 

This one is flexible, you can start from anywhere in the range and also specify the direction you want to start going in by making dir either 1 or -1.

class Fader
{
public:
 Fader(int min, int max, int start, int dir = 1)
  : _min(min)
  , _max(max)
  , _val(start)
  , _dir(dir)
 { }

 int getValue()
 {
   if(_val <= min || _val >= _max)
     _dir = -_dir;

   _val += _dir;
   return _val;
  }

private:
  int _min, _max, _dir, _val;
};
Adam Pierce
This is actually quite fragile. Try Fader(0, 10, 20, 2). Also, I'd expect Fader(0, 10, 0, 1).getValue() to return 0, but it returns 1 (i.e. "start" is not the first value returned).
Martin
True enough, but it works OK if the initial values are sensible. You could add a check for bad initial values in the constructor.
Adam Pierce
A: 

This is how I did it. Seems pretty straightforward.

int computeTransparency(int start, int end, int& increment, int oldVal)
{
    if (start > end)
    {
     swap(start, end);
    }

    int newVal = oldVal + increment;
    bool goingUp = increment > 0;

    if (goingUp)
    {
     if (newVal >= end)
     {
      newVal = end;
      increment = -increment;
     }
    }
    else
    {
     if (newVal <= start)
     {
      newVal = start;
      increment = -increment;
     }
    }

    return newVal;
}
JRL
+2  A: 

How about using a sine function to get a more natural looking fade...

#include <math.h>

class SineFader
{
public:
  SineFader(int min, int max)
    : base((double)min + ((double)(max - min) / 2))
    , range((double)(max - min) / 2)
    , theta(4.71)
    , speed(0.1)
  { }

  int getValue()
  {
    theta += speed;
    return (int)(base + (range * sin(theta)));
  }

private:
  double base, theta, range, speed;
};


Here's how you'd use that in your code:

SineFader myfade(0, 55);

void onTimer()
{
   setTransparency(myfade.getValue());
}
Adam Pierce
Good idea, but you should check your math. What happens when theta = 4.7?
bk1e
That's what I get for just banging out code off the cuff. Thanks for spotting that bk1e. I've fixed it up a bit, it makes the constructor more complicated though.
Adam Pierce
Is there anyway to set the pulse period in millisec instead of speed?
max
It depends how often you call onTimer() and what value you set speed to. The formula would be one flash every (timer_interval * ((2 * PI) / speed)).
Adam Pierce
A: 

Here's another approach, bouncing between start and start+span in increments of increment, with a helper function:

// Goes from start to start + span, and repeats.
int ramp(start, increment, span) {
  static int lastval = start;
  lastval += increment;
  lastval = lastval % (span + 1);
  return lastval;
}

// Goes from start up to start+span and back down, and repeats.
int bounce(start, increment, span) {
  val = ramp(0, increment, 2*span - 1);
  if (val > span) val = span - val;
  return val + start;
}

Essentially what this is doing is creating a continuously increasing line, then using a MOD function to chop that into a sawtooth, and then reflecting the top half of the sawtooth back down to make a zigzag.

Note that, unlike all the other implementations presented (unless I've missed one), this one actually takes you the same distance along the zigzag even if increment doesn't divide evenly -- for example, for a start of 0, a span of 10, and a step of 3, it will go up 3 to 3, up 3 to 6, up 3 to 9, up 1 and down the other 2 to 8, down 3 to 5, down 3 to 2, down 2 and up the other 1 to 1, and so forth -- 3, 6, 9, 8, 5, 2, 1, 4, 7, 10, 7, 4, 1, .... And it also guarantees that the numbers always fall in the right range and does the right thing even if increment is much larger than span or is negative.

(You should probably rewrite this to put things like start, increment, and span somewhere in static storage rather than in function arguments, because this will get rather confused if they change, but I'm feeling lazy about that. And, really, it will still fall in the right range and do somewhat sensible things if they change -- you can change increment at any point and it will speed up or slow down without jumping around.)

Brooks Moses