One way to go is to model Weather as Random Process using Hidden Markov Model(HMM). For full explanation of it, check Hidden Markov Model - Wikipedia
This will solve the following problem: I'm in state A. From state A, which is the next state i should go into.In other words, if you are in Cloudy State, where should you go next:Remain in Cloudy state or go into Rainy or Fine state. This will not tell you when transition should happen. For that, you could simply use some random variable to indicate duration of each state.
Shortly, HMM is set of states and transitions between states based on probability theory(this is of course crude explanation and mathematicians will be angry if they see this). You could have state for all weather types, i.e. FINE_STATE, CLOUDY_STATE, STORM_STATE, and RAINY_STATE, and define set of transition between them and assign probabilities on them that would look something like this:

Sum of probabilities of outgoing paths from every state must be 1 as you see.
HMM is a way more general than this, but it does good job. You can use State Design Pattern with this and implement HMM as weighted digraph. For explanation of State Pattern, check
State Pattern - Wikipedia.
As Alan already said, "cross-fading" is nice feature to have, and you could easily combine it with HMM using State Pattern.
Here is the code that corresponds to theory said above(please note that for simplicity i did not accounted duration of the state, and "cross-fading"):
//Header files .h
class Graph
{
public:
//If nodes "from" and "to" does not exist, they will be added
//Complexity: O(1) average, O(n) worst case
void addEdge(string from, string to, float probability)
{
mAdj[from].push_back(pair<string, float>(to, probability));
}
//Return adjencent nodes to vertex "from"
//Complexity: O(1) average, O(n) worst case
list<pair<string, float>> operator[](string from) { return mAdj[from]; }
private:
unordered_map<string, list<pair<string, float>>> mAdj;
};
class State
{
public:
//Complexity:O(n) where n is total number of weather states
State* getNextState(Graph hmm);
virtual string toString() = 0;
static State* createState(string state); //Create State object from string argument
};
class FineState : public State
{
public:
string toString() { return "fine"; }
};
class CloudyState : public State
{
public:
string toString() { return "cloudy"; }
};
class RainyState : public State
{
public:
string toString() { return "rainy"; }
};
class StormState : public State
{
public:
string toString() { return "storm"; }
};
class WeatherManager
{
public:
WeatherManager() : mCurrentState(0) {}
~WeatherManager() { if(mCurrentState) delete mCurrentState; }
//Add weather states and transition between "from" -> "to" based on probability
//Complexity: Same as Graph::addEdge()
void addWeather(string from, string to, float probability)
{
mHMM.addEdge(from, to, probability);
}
//Calculate weather state for next frame
//Complexity: Same as State::getNextState()
//Please note that current implementation of weather system does not account duration of state
//i.e. Each state have duration of 1 frame
void changeWeather()
{
if (mCurrentState)
{
State* pState = mCurrentState->getNextState(mHMM);
delete mCurrentState;
mCurrentState = pState;
}
}
void setState(string state)
{
if (mCurrentState) delete mCurrentState;
mCurrentState = State::createState(state);
}
void printState()
{
cout << mCurrentState->toString() << endl;
}
private:
Graph mHMM;
State* mCurrentState;
};
//Source files .cpp
State* State::getNextState(Graph hmm)
{
string thisState = toString();
//Get number in [0, 1] interval.
//Use some advanced random generator if you need Normal Distribution
float r = static_cast <float> (rand()) / static_cast <float> (RAND_MAX);
float sum = .0f;
for (auto it : hmm[thisState])
{
float prob = it.second;
if (sum <= r && r < (sum + prob))
return State::createState(it.first);
sum += prob;
}
return nullptr;
}
State* State::createState(string state)
{
if (state == "fine")
return new FineState();
else if (state == "cloudy")
return new CloudyState();
else if (state == "rainy")
return new RainyState();
else if (state == "storm")
return new StormState();
return nullptr;
}
int main() {
srand(static_cast<unsigned>(time(nullptr)));
WeatherManager mgr;
mgr.addWeather("fine", "fine", 0.2f);
mgr.addWeather("fine", "cloudy", 0.8f);
mgr.addWeather("cloudy", "cloudy", 0.3f);
mgr.addWeather("cloudy", "fine", 0.1f);
mgr.addWeather("cloudy", "rainy", 0.2f);
mgr.addWeather("cloudy", "storm", 0.7f);
mgr.addWeather("rainy", "rainy", 0.4f);
mgr.addWeather("rainy", "cloudy", 0.4f);
mgr.addWeather("rainy", "storm", 0.2f);
mgr.addWeather("storm", "storm", 0.1f);
mgr.addWeather("storm", "cloudy", 0.1f);
mgr.addWeather("storm", "rainy", 0.8f);
mgr.setState("fine");
for (unsigned i = 0; i < 10; i++)
{
mgr.printState();
mgr.changeWeather();
}
return 0;
}
You can expand concrete states (i.e. FineState
) classes to add duration among the other things, and just use another State*
variable for "cross-fading" LERP(Linear Interpolation) to save previous state.
FINE
,CLOUDY
,RAINY
andSTORM
at the same time, in a ratio ranging from 0% to 100%. You would probably need to reorganize your architecture, but could be worth looked into. \$\endgroup\$