Control for color transitions

Hi, i created a control to change/morph the color of its parent spatial, I thought I should share it to get some feedback if I am doing things totally wrong and maybe someone else finds it useful. So criticism would be appreciated :wink:

[java]

Spatial cube = newCube(ColorRGBA.Red);
cube.addControl(new C_ColorChanger(ColorRGBA.Green) {
private float percent = 0;
private final float MAX = 5;

		@Override
		public void update(float tpf) {
			percent += tpf * 100 / MAX;
			Log.d(LOG_TAG, "percent=" + percent);
			setPercent(percent);
			super.update(tpf);
		}
	});
	rootNode.attachChild(cube);

/**

  • adjusts the color from the target spatial and morphs it to the endColor. Use

  • {@link C_ColorChanger#setPercent(float)} to update the color
    */
    public class C_ColorChanger extends AbstractControl {

    private static final String LOG_TAG = “C_ColorChanger”;
    private ColorRGBA objectColor;
    private ColorRGBA startColor;
    private ColorRGBA endColor;
    private int percent = 0;
    private int lastPercent;
    private Spatial lastSpatial;
    private Geometry myGeometry;

    public C_ColorChanger(ColorRGBA endColor) {
    this.endColor = endColor.clone();
    }

    /**

    • @param startColor
    •        can be null, then the initial color of the object will be used
      
    •        as the start color
      
    • @param endColor
      */
      public C_ColorChanger(ColorRGBA startColor, ColorRGBA endColor) {
      if (startColor != null) {
      this.startColor = startColor.clone();
      }
      this.endColor = endColor.clone();
      }

    @Override
    protected void controlUpdate(float tpf) {
    if (percent != lastPercent) {
    lastPercent = percent;
    if (lastSpatial != getSpatial()) {
    lastSpatial = getSpatial();
    myGeometry = findOutestGeometryFor(lastSpatial);
    Log.d(LOG_TAG, “found myGeometry=” + myGeometry
    + " for spatial=" + lastSpatial);
    }
    if (objectColor == null) {
    objectColor = getfrom(myGeometry);
    if (startColor == null) {
    startColor = objectColor.clone();
    }
    }
    // then change the found color object
    objectColor.interpolate(startColor, endColor, percent / 100f);
    }
    }

    public void setEndColor(ColorRGBA endColor) {
    this.endColor = endColor;
    }

    /**

    • Changes the color directly to the passed value. The passed color will
    • then also be the new end color. If you only want to change the endColor
    • to a new value without applying it directly use
    • {@link C_ColorChanger#setEndColor(ColorRGBA)}
    • @param color
      */
      public void setColor(ColorRGBA color) {
      setEndColor(color);
      setPercent(100);
      }

    private ColorRGBA getfrom(Geometry g) {
    MatParam c;
    c = myGeometry.getMaterial().getParam(“Ambient”);
    if (c != null) {
    return (ColorRGBA) c.getValue();
    }
    c = myGeometry.getMaterial().getParam(“Diffuse”);
    if (c != null) {
    return (ColorRGBA) c.getValue();
    }
    c = myGeometry.getMaterial().getParam(“Specular”);
    if (c != null) {
    return (ColorRGBA) c.getValue();
    }
    Log.w(LOG_TAG, “Found no color attribute in geometry=” + g);
    return null;
    }

    private Geometry findOutestGeometryFor(Spatial s) {
    if (s instanceof Geometry) {
    return (Geometry) s;
    } else {
    List<Spatial> c = ((Node) s).getChildren();
    for (int i = 0; i < c.size(); i++) {
    Geometry g = findOutestGeometryFor(c.get(i));
    if (g != null) {
    return g;
    }
    }
    return null;
    }
    }

    /**

    • @param percent
    •        a value between 0 and 100
      

    */
    public void setPercent(float percent) {
    if (percent > 100) {
    percent = 100;
    } else if (percent < 0) {
    percent = 0;
    }
    this.percent = (int) percent;
    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
    }

}

[/java]

I already have something better than that in my project :

[java]

/*

  • To change this template, choose Tools | Templates
  • and open the template in the editor.
    */
    package real.common.tools;

import com.jme3.math.ColorRGBA;
import java.util.Map;
import java.util.TreeMap;

/**
*

  • @author Bubuche
    */
    public class ProgressiveColorHandler
    {
    private TreeMap<Float, ColorRGBA> steps;

public ProgressiveColorHandler(ColorRGBA zeroColor)
{
this.steps = new TreeMap<>();
this.steps.put(0f, zeroColor);
}

public void addStep(Float value, ColorRGBA color)
{
this.steps.put(value, color);
}

public void getColor(Float value, ColorRGBA store)
{
Map.Entry<Float, ColorRGBA> entry_min = this.steps.floorEntry(value);
if ( entry_min == null )
{
throw new IllegalArgumentException(“no color for “+value+” it’s out of bounds (”+this.steps.firstKey()+" -> “+this.steps.lastKey()+”)");
}

Map.Entry&lt;Float, ColorRGBA&gt; entry_max = this.steps.ceilingEntry(value);
if ( entry_max == null )
{
  store.set(entry_min.getValue());
  return;
}

if ( entry_max.getValue() == entry_min.getValue() )
{
  store.set(entry_max.getValue());
  return;
}

float n_value = (value - entry_min.getKey()) / (entry_max.getKey() - entry_min.getKey());

ColorRGBA min_color, max_color;
min_color = entry_min.getValue();
max_color = entry_max.getValue();

store.r = min_color.r * (1 - n_value) + (max_color.r * n_value);
store.g = min_color.g * (1 - n_value) + (max_color.g * n_value);
store.b = min_color.b * (1 - n_value) + (max_color.b * n_value);

}
}
[/java]

This class allow you to have many colors (more than just “begin/end” and will interpolate the resulting color in the following (obivous but hard to explain without a draw) :

if you have a map like this :

0->black
1.1->red
2->yellow
2.5->green

and you ask for the color for the value 1.5, it will interpolate the red and the yellow (and will be closer to the red) etc. If you are exactly at 2, the color will be plain yellow. If you are higher than 2.5 you’ll get green etc.

I don’t remember what are the details of my implementation.

This class doesn’t modify the color of a Spatial, and it’s NORMAL.Because you can want to modify something else than a geometry. Btw, your class doesn’t work for unshaded geometries, or for geometries that have a different material than the lighting.j3md.

And a recursive method (i.e. “findOutestGeometry”) is not a great idea. Plus, “outest” in your class is not the outest visually, it is only the outest in the scenegraph, and it can be something else than the outest visually. For exemple, if you attach a box around your char (if you are creating a mgs like) or anything like that, this box will be deeper in the scene tree (it’s a child geom for the character) but will be outside visually. Same thing occurs for clothes etc.

@bubuche said:

This class doesn’t modify the color of a Spatial, and it’s NORMAL.Because you can want to modify something else than a geometry.

looks like you do your interpolation manually, why dont you use color.interpolate(… ?
The functionality with multiple colors sounds interesting, when do you use it, for which scenarios is it useful?

@bubuche said: Btw, your class doesn't work for unshaded geometries, or for geometries that have a different material than the lighting.j3md.

And a recursive method (i.e. “findOutestGeometry”) is not a great idea. Plus, “outest” in your class is not the outest visually, it is only the outest in the scenegraph, and it can be something else than the outest visually. For exemple, if you attach a box around your char (if you are creating a mgs like) or anything like that, this box will be deeper in the scene tree (it’s a child geom for the character) but will be outside visually. Same thing occurs for clothes etc.

Yes thats right it will not work on any spatial and only on the ones which use lighting.j3md but first I wanted to have a small control class which works for most scenarios before I extend it and my experience is that it works normally if you dont do too crasy stuff with your scene :wink:

I don’t use the color interpolate because i ported this code from an other language (where i wrote it in the first place).

But now i think that i should have an overridable method that will blend the colors, cause there is more than one way to do it. Here, i do it with the rgb approach, but i would have a different result with a hsb interpolation for example.

And for the use case : i use it for the health bar with a map like this :

0 -> red
0.5 -> yellow
1 -> green

So when you are full like your energy is green, then become progressively yellow, then red.

1 Like