JME 2.X tutorial: material transition controller

Here is my second tutorial about colors regarding materials. This one extends the previous one to show the possibilities of controllers:


import com.jme.app.SimpleGame;
import com.jme.input.KeyBindingManager;
import com.jme.light.PointLight;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Spatial;
import com.jme.scene.Text;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.MaterialState;

import java.util.ArrayList;
import java.util.List;

/**
 * Second material tutorial.
 * <p/>
 * Adapted from tutorial: http://jerome.jouvie.free.fr/OpenGl/Tutorials/Tutorial14.php
 * <p/>
 * This tutorial will deals with material transitioning using a Controller object.
 * Inspired from http://www.jmonkeyengine.com/jmeforum/index.php?topic=7214.0
 *
 * @author <a href="mailto:loic.lefevre@gmail.com">Lo

And the MaterialTransitionController:


import com.jme.scene.Controller;
import com.jme.scene.state.MaterialState;

/**
 * The material transition controller handles material transition control over time.
 * It needs a changing material that will change (i.e. <b>it is mutable</b>) in order
 * to meet the caracteristics of a "target" material starting from a "source" material.
 * This transition is controlled to last a given amount of milli-seconds.
 */
public class MaterialTransitionController extends Controller {

        /**
         * Flag to denote that the transition has not started yet.
         */
        private static final long TRANSITION_NOT_STARTED = -1;

        /**
         * The changing material (mutable).
         */
        private MaterialState changingMaterial;

        /**
         * The source material (immutable).
         */
        private MaterialState sourceMaterial;

        /**
         * The target material (immutable).
         */
        private MaterialState targetMaterial;

        /**
         * The transition duration in milli-seconds.
         */
        private long transitionDuration;

        /**
         * Time at which the transition effect started.
         */
        private long start;

        /**
         * Keep track of the change amount for optimization.
         */
        private float oldChangeAmount;

        /**
         * Instanciate a material transition controller.
         *
         * @param changingMaterial   changing material, this one will be modified!
         * @param sourceMaterial     starting material, this one will <b>not</b> be modified!
         * @param targetMaterial     target material, this one will <b>not</b> be modified!
         * @param transitionDuration duration of the transition in milli-seconds
         * @param active             tells if this controller is active at instanciation time
         */
        public MaterialTransitionController(final MaterialState changingMaterial,
                                            final MaterialState sourceMaterial,
                                            final MaterialState targetMaterial,
                                            final long transitionDuration,
                                            final boolean active) {
            // do not start unless setup is complete!
            setActive(false);

            if (transitionDuration <= 0) {
                throw new IllegalArgumentException("transitionDuration must be a positive integer!");
            }

            if (changingMaterial == null) {
                throw new IllegalArgumentException("changingMaterial must be not null!");
            }

            if (sourceMaterial == null) {
                throw new IllegalArgumentException("sourceMaterial must be not null!");
            }

            if (targetMaterial == null) {
                throw new IllegalArgumentException("targetMaterial must be not null!");
            }

            this.changingMaterial = changingMaterial;

            setNewMaterialTransition(sourceMaterial, targetMaterial, transitionDuration, active);
        }

        /**
         * Apply the transition.
         *
         * @param time time since last frame in second
         */
        @Override
        public void update(final float time) {
            if (isActive()) {
                if (start == TRANSITION_NOT_STARTED) {
                    // note the starting time
                    start = System.currentTimeMillis();
                }

                // note the amount of time since start
                final long amount = System.currentTimeMillis() - start;

                // if not yet finished...
                if (amount < transitionDuration) {
                    final float changeAmount = (float)amount / (float)transitionDuration;

                    if (changeAmount != oldChangeAmount) {
                        changingMaterial.getEmissive().interpolate(sourceMaterial.getEmissive(), targetMaterial.getEmissive(), changeAmount);
                        changingMaterial.getAmbient().interpolate(sourceMaterial.getAmbient(), targetMaterial.getAmbient(), changeAmount);
                        changingMaterial.getDiffuse().interpolate(sourceMaterial.getDiffuse(), targetMaterial.getDiffuse(), changeAmount);
                        changingMaterial.getSpecular().interpolate(sourceMaterial.getSpecular(), targetMaterial.getSpecular(), changeAmount);
                        final float newShininess = (1 - changeAmount) * sourceMaterial.getShininess() + changeAmount * targetMaterial.getShininess();
                        changingMaterial.setShininess(newShininess);

                        oldChangeAmount = changeAmount;
                    }
                } else {
                    changingMaterial.getEmissive().interpolate(sourceMaterial.getEmissive(), targetMaterial.getEmissive(), 1.0f);
                    changingMaterial.getAmbient().interpolate(sourceMaterial.getAmbient(), targetMaterial.getAmbient(), 1.0f);
                    changingMaterial.getDiffuse().interpolate(sourceMaterial.getDiffuse(), targetMaterial.getDiffuse(), 1.0f);
                    changingMaterial.getSpecular().interpolate(sourceMaterial.getSpecular(), targetMaterial.getSpecular(), 1.0f);
                    changingMaterial.setShininess(targetMaterial.getShininess());

                    // finalize transition
                    setActive(false);
                }
            }
        }

        /**
         * Public method to initiate a transition.
         *
         * @param sourceMaterial     the new source material
         * @param targetMaterial     the new target material
         * @param transitionDuration the new transition duration in milli-seconds
         */
        public void setNewMaterialTransition(final MaterialState sourceMaterial, final MaterialState targetMaterial, final long transitionDuration) {
            setNewMaterialTransition(sourceMaterial, targetMaterial, transitionDuration, true);
        }

        /**
         * Internal method that initialize the local variables for efficient transition.
         *
         * @param sourceMaterial     the new source material
         * @param targetMaterial     the new target material
         * @param transitionDuration the new transition duration in milli-seconds
         * @param active             is it active right now?
         */
        private void setNewMaterialTransition(final MaterialState sourceMaterial, final MaterialState targetMaterial, final long transitionDuration, final boolean active) {

            this.transitionDuration = transitionDuration;

            this.sourceMaterial = sourceMaterial;

            this.targetMaterial = targetMaterial;

            start = TRANSITION_NOT_STARTED;

            oldChangeAmount = -1.0f;

            setActive(active);
        }
    }



N.