6
\$\begingroup\$

I have a universe: a list of "Systems", each with their own center, type and radius.

A small part of such a universe could look like this: Example Universe

Systems:

  • Can be very close to a different system, e.g. overlap
  • Can be inside another, much bigger system
  • Can be very far away from any other systems
  • Spawn system specific entities and particles inside the system radius
  • Have some properties like background color

So far so good. However, the player can fly around freely, inside and outside of systems, in real time.

How do I interpolate and determine things like the background color now, depending on camera position?

E.g. if you are halfway between a green and a red system you should see a background halfway between red and green, or if you are inside a lilac system near the center and at the border of a green system you should get a mostly lilac background etc.

\$\endgroup\$
  • \$\begingroup\$ How many overlapping systems do you expect to have at once? 2, 10, 50? \$\endgroup\$ – Kromster says support Monica Jun 1 '14 at 10:32
  • \$\begingroup\$ @KromStern It's unlikely that more than 5 systems will overlap. \$\endgroup\$ – API-Beast Jun 1 '14 at 13:57
5
\$\begingroup\$

I would say you should break the problem down into these two simple functions, which you can choose as you like:

  • A function which determines the "weight" of a system according to the distance of the player from it and its radius.

    For example, radius / distance would be a simple smooth one. radius < distance ? 1/radius : 0 would produce sharp boundaries and allow small systems inside large ones to override them.

  • A function which takes a list of background colors, with associated computed "weights", and produces a single background color.

    For example, it could mix them according to the weights (multiply each color by weight, sum all, divide by sum of weights); in some pseudocode:

    function mixColors(colors, weights) {
      var accum_weight = 0, accum_color = Vector(0, 0, 0);
      for i from 0 to colors.length - 1 {
        accum_weight += weights[i];
        accum_color += colors[i] * weights[i];
      }
      if (accum_weight > 0) {  // don't divide by zero
        accum_color /= accum_weight;
      }
      return accum_color;
    }
    

    Or it could take the one with the highest weight, possibly combined with time-based transitions as mentioned below.

    function mixColors(colors, weights) {
      var best_weight = 0, best_color = Vector(0, 0, 0);
      for i from 0 to colors.length - 1 {
        if weights[i] >= best_weight {
          best_weight = weights[i];
          best_color = colors[i];
        }
      }
      return best_color;
    }
    

Then the overall algorithm is that you apply the first function for each system, and feed the results into the second function, and that's your background color. Different functions produce different effects; the reason I've specified the above design is because it allows you to experiment with the specific results without needing to design new algorithms, just simple arithmetic.


Another thing that is often done in games with this sort of feature (especially with music rather than color) is to change the background over time. That is, when the color changes significantly you interpolate to the new color at a fixed rate over time rather than taking it only from the player position. This can be added to the above process as a final step of computation, if you want it.

\$\endgroup\$
  • \$\begingroup\$ The "mix" function would be the tricky part, since it is required to work with any amount of factors and the result would have to be the same independent of the order to avoid any sudden jumps in color, any pointers on that? \$\endgroup\$ – API-Beast Jun 1 '14 at 7:31
  • \$\begingroup\$ @API-Beast The first example I gave will do that. I will expand it a bit. \$\endgroup\$ – Kevin Reid Jun 1 '14 at 13:56
2
\$\begingroup\$

I figured that what essentially needs to be done is similar to what the computer is doing when blending sprites.

So what we need to do is:

  1. Find all systems that overlap with Point A.
  2. Determine a color and a alpha value at Point A for each system. In graphics context this is usually sampled from a bitmap, but in this case we just use a simple radial gradient function.
  3. Determine a "drawing order". In this case we sort by the size of the system so that smaller systems will be "drawn" on top of larger systems.
  4. Set the background color to a fixed value.
  5. Alpha blend all colors together, in the order previously determined.

Or in Pseudo code:

Vec2 A;
List<System> OverlappingSystems = [... systems overlapping Point A ...];
OverlappingSystems.sortBy(Radius);
Color BGColor = Black;
for(System in OverlappingSystems)
  BGColor = AlphaBlend(BGColor, System.BackgroundColor, System.AlphaAt(A));

AlphaAt would be defined as following:

func AlphaAt(Vec2 Point)
{
  return Max(1 - DistanceBetween(Point, Center)/Radius, 0);
}

And AlphaBlend as:

func AlphaBlend(Destination, Source, Number Alpha)
{
  return Destination*(1-Alpha) + Source*Alpha;
}

Different blending modes can be used as needed, for example Multiplication or Addition:

// Multiplication
Destination * (Source*Alpha + White*(1-Alpha));
// Addition
Destination + Source*Alpha;

These operations are even order independent making the sorting unnecessary.

\$\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.