Utility to sort lights in jme

Hear is the code for the utility to sort lights in jme. The code is fast and can be used in real time. It works very well in situations like the test. I suspect that there is a bug with the code but have ben unable to fined one. If you see a bug tell me but if you do not I think that is reedy for use.


/*
 * LightStateCreator.java
 *
 * Created on September 27, 2004, 8:49 PM
 */

package com.jme.util.light;

import java.util.ArrayList;
import com.jme.light.*;
import com.jme.scene.Spatial;
import com.jme.scene.state.LightState;
import com.jme.renderer.ColorRGBA;
import com.jme.bounding.BoundingVolume;
import com.jme.math.Plane;
/**
 *
 * @author  Badmi
 */
public class LightStateCreator {
    ArrayList lightList;
    /** Creates a new instance of LightStateCreator */
    public LightStateCreator()
    {
        lightList=new ArrayList();
    }
   
    public void addLight(Light l)
    {
        lightList.add(l);
    }
   
   
   
   
    public Light get(int i)
    {
        return (Light)lightList.get(i);
    }
   
    public int numberOfLights()
    {
        return lightList.size();
    }
   
    public LightState createLightState(Spatial sp)
    {
        LightState l=com.jme.system.DisplaySystem.getDisplaySystem().getRenderer().createLightState();
        resortLightsFor(l,sp);
        return l;
    }
   
    public void resortLightsFor(LightState ls,Spatial sp )
    {

       
        ls.detachAll();
        quickSort(0,lightList.size(),sp);
       
      /*  System.out.print(sp+ "  ");
       
        float min=0;
        for(int i=0;i<this.numberOfLights();i++)
        {
            float val=getValueFor(get(i),sp.getWorldBound());
           
            if(i<8)
                min=Math.min(min,val);
           
            if(val>min&&i>=8)
                System.out.print(val+"bug  ");
            else
                System.out.print(val+"  ");
        }
        System.out.println();*/
       
       
        for(int i=0;i<LightState.MAX_LIGHTS_ALLOWED;i++)
        {
            ls.attach(get(i));
        }
    }
   protected void quickSort(int minindex,int maxindex,Spatial sp)
  {
     
      //System.out.println(minindex+" "+maxindex+" " +sp);
      if(maxindex-minindex<=1)
          return;
      int chose=(maxindex-minindex)/2+minindex;
      float valofchos=getValueFor(get(chose),sp.getWorldBound());
      swamp(chose,minindex);
     
     
      int setmin=minindex+1;
      int setmax=maxindex-1;
      while(setmax-setmin>1)
      {
          while(setmax-setmin>1 && getValueFor(get(setmin),sp.getWorldBound())>valofchos)
              setmin++;
         
          while(setmax-setmin>1 && getValueFor(get(setmin),sp.getWorldBound())<=valofchos)
              setmax--;         
         
          if(setmax-setmin<=1)
              break;
          swamp(setmin,setmax);
          setmin++;
          setmax--;
         
      }
      swamp(0,setmin);
     
      if(setmin==LightState.MAX_LIGHTS_ALLOWED)
          return;
      else if(setmin<LightState.MAX_LIGHTS_ALLOWED)
          quickSort(setmin,maxindex,sp);
      else
          quickSort(minindex,setmin,sp);
         
  }
  /**
   *Swamps point a and b in the list
   */
  protected void swamp(int a,int b)
  {
      Object temp=lightList.get(a);
      lightList.set(a,lightList.get(b));
      lightList.set(b,temp);
  }
  protected   float max(ColorRGBA a)
    {
        return Math.max(Math.max(a.r,a.g),a.b);
    }
   protected float getColoarValue(Light l)
   {
      return Math.max(Math.max(max(l.getAmbient()),max(l.getDiffuse())), max(l.getSpecular()));
   }
   
   
   protected float getValueFor(Light l,BoundingVolume val)
   {
       if(!l.isEnabled())
           return 0;
       else if(l.getType()==Light.LT_AMBIENT)
       {
           return max(l.getAmbient());
       }
       else if(l.getType()==Light.LT_DIRECTIONAL)
       {
           return getColoarValue(l);
       }
       else if(l.getType()==Light.LT_POINT)
       {
            return getValueFor((PointLight)l,val);
       }
       else if(l.getType()==Light.LT_SPOT)
       {
           return getValueFor((SpotLight)l,val);
       }
       //If a new tipe of light was aded and this was not updated return .3
       return .3f;
   }
   
   
   float getValueFor(PointLight  l,BoundingVolume val)
   {
          if(l.isAttenuate())
          {
             float dist=val.distanceTo(l.getLocation());
           
             float colar=getColoarValue(l);
             float amlat=1;
             if(l.getConstant()!=0)
                 amlat=/*1*/l.getConstant()+l.getLinear()*dist+l.getQuadratic()*dist*dist;
             else
                 amlat=l.getLinear()*dist+l.getQuadratic()*dist*dist;

             return colar/amlat;
         }
         else
         {
             return getColoarValue(l);
        }       
   }
   float getValueFor(SpotLight l,BoundingVolume val)
   {
       Plane p= new Plane(l.getDirection(),l.getDirection().dot(l.getLocation()));
      if(val.whichSide(p)!=p.NEGATIVE_SIDE)
          return getValueFor((PointLight)l,val);
       
       return 0;
   }
   LightStateCreator makeCopy()
   {
       LightStateCreator newtool=new LightStateCreator();
       
       for(int i=0;i<this.numberOfLights();i++)
          newtool.addLight(this.get(i));
       
       return newtool;
   }
}



/*
 * LightStateContraller.java
 *
 * Created on October 4, 2004, 7:14 PM
 */

package com.jme.util.light;
import com.jme.scene.Controller;
import com.jme.scene.Spatial;
import com.jme.scene.CloneCreator;
import com.jme.scene.state.LightState;
import com.jme.renderer.Camera;
/**
 *
 * @author  Badmi
 */
public class LightStateContraller extends Controller{
   
    float timepass;
   
    float updateinterval;
   
    Spatial par;
   
    LightStateCreator lsc;
    /** Creates a new instance of LightStateContraller */
    public LightStateContraller(Spatial par, LightStateCreator lightCreator) {
        this.par=par;
        lsc=lightCreator.makeCopy();
       
        //Not nesisery but put in for clarificatiaon
        timepass=0;
        updateinterval=0;
    }
   
    public void setUpdateInterval(float inter)
    {
        updateinterval=inter;
    }
   
    public float getUpdateInterval()
    {
        return updateinterval;
    }
    public void update(float time) {
        timepass+=time;
        if(isActive()&&par.getLastFrustumIntersection()!=Camera.OUTSIDE_FRUSTUM)
        if(timepass>updateinterval||time<0)
        {
            timepass=0;
            lsc.resortLightsFor( (LightState)par.getRenderStateList()[LightState.RS_LIGHT],par);
            //par.updateRenderState();
        }
    }
    /*public Controller putClone(Controller store, CloneCreator properties) {
        if (!properties.isSet("keyframecontroller")) return null;
       
       LightStateContraller toReturn;
        if (store == null)
            toReturn = new LightStateContraller(<Methid to fined the spatial that is being made cep21 please add one>,);
        else
            toReturn = (LightStateContraller) store;
        super.putClone(toReturn, properties);
        toReturn.timepass=timepass;
        toReturn.updateinterval=updateinterval;
       
        return toReturn;
    }*/
   
}



And the test:

/*
 * TestMoreThenMaxLight.java
 *
 * Created on September 21, 2004, 2:36 PM
 */

package jmetest.renderer;
import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingSphere;
import com.jme.image.Texture;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.LightState;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;
import com.jme.light.PointLight;
import com.jme.light.SimpleLightNode;
import com.jme.light.AmbientLight;
import com.jme.animation.SpatialTransformer;
import com.jme.math.FastMath;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.util.light.LightStateCreator;
import com.jme.util.light.LightStateContraller;
/**
 *
 * @author  Badmi
 */
public class TestMoreThenMaxLight extends SimpleGame{
   
    boolean fowerd;
    float dist;
    SimpleLightNode ln;
    SimpleLightNode ln2;
    PointLight pl;
    PointLight p2;
    Sphere sp1;
    Node colornode;
    LightStateCreator mtcer;
   
    final static float worldsize=20;
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        TestMoreThenMaxLight app = new TestMoreThenMaxLight();
        app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
        app.start();
    }
    protected void simpleUpdate() {
       
       
        if(super.input.getKeyBindingManager().isValidCommand("StopMoving",false)) {
            deactivate(rootNode);
        }
       
    }
    void deactivate(com.jme.scene.Spatial s) {
        for(int i=0;i<s.getControllers().size();i++)
            s.getController(i).setActive(!s.getController(i).isActive());
        if(s instanceof Node) {
            Node n=(Node)s;
            for(int i=0;i<n.getQuantity();i++)
                deactivate(n.getChild(i));
        }
    }
    void randomLight(int i) {
        ColorRGBA mc=ColorRGBA.randomColor();
        Sphere sp2=new Sphere("lp"+i,10,10,.1f);
        sp2.setModelBound(new BoundingSphere());
        sp2.updateModelBound();
        sp2.setLightCombineMode(com.jme.scene.state.LightState.OFF);
        sp2.setSolidColor(mc);
       
        pl=new PointLight();
        pl.setAttenuate(true);
        pl.setConstant(/*FastMath.rand.nextFloat()*/.1f);
        pl.setLinear(/*FastMath.rand.nextFloat()**/.1f);
        pl.setQuadratic(/*FastMath.rand.nextFloat()*/.1f);
        pl.setEnabled(true);
        pl.setDiffuse(mc);
        //pl.setSpecular(mc);
        pl.setAmbient(new com.jme.renderer.ColorRGBA(.1f,.1f,.1f,.1f));
        //this.lightState.attach(pl);
        mtcer.addLight(pl);
        Node mnod=new Node("P"+i+" Light pos");
        ln =new SimpleLightNode("ln"+i,pl);
        mnod.setLocalTranslation(new Vector3f(FastMath.rand.nextFloat()*worldsize*2-worldsize,FastMath.rand.nextFloat()*worldsize*2-worldsize,FastMath.rand.nextFloat()*worldsize*2-worldsize));
        mnod.attachChild(sp2);
       
        SpatialTransformer st=new SpatialTransformer(1);
        // I tell my spatial controller to change pivot
        st.setObject(mnod,0,-1);
       
        st.setPosition(0,0, mnod.getLocalTranslation());
        int num=FastMath.rand.nextInt(10)+1;
        for(int inom=0;inom<num;inom++) {
            st.setPosition(0,inom*4+4,new Vector3f(FastMath.rand.nextFloat()*worldsize*2-worldsize,FastMath.rand.nextFloat()*worldsize*2-worldsize,FastMath.rand.nextFloat()*worldsize*2-worldsize));
        }
        st.setRepeatType(st.RT_CYCLE);
        // Prepare my controller to start moving around
        st.interpolateMissing();
        st.setActive(false);
        // Tell my pivot it is controlled by st
        mnod.addController(st);
       
        mnod.attachChild(ln);
        colornode.attachChild(mnod);
    }
    void randomeSphear(int i) {
        sp1=new Sphere("sp"+i,10,10,1);
        sp1.setModelBound(new BoundingSphere());
        sp1.updateModelBound();
       
       
        sp1.setLocalTranslation(new Vector3f(FastMath.rand.nextFloat()*worldsize*2-worldsize,FastMath.rand.nextFloat()*worldsize*2-worldsize,FastMath.rand.nextFloat()*worldsize*2-worldsize));
       
        LightState ls=com.jme.system.DisplaySystem.getDisplaySystem().getRenderer().createLightState();
        ls.setEnabled(true);
       
        sp1.setRenderState(ls);//mtcer.createLightState(sp1));
        sp1.addController(new LightStateContraller(sp1,mtcer));
        sp1.setLightCombineMode(ls.REPLACE);
       
        rootNode.attachChild(sp1);
    }
    protected void simpleInitGame() {
        this.lightState.detachAll();
        mtcer=new LightStateCreator();
       
        colornode=new Node("LightNode");
        for(int i=0;i<20;i++) {
            this.randomLight(i);
        }
        for(int i=0;i<40;i++) {
            this.randomeSphear(i);
        }
        LightState nl=com.jme.system.DisplaySystem.getDisplaySystem().getRenderer().createLightState();
        nl.setEnabled(false);
        colornode.setRenderState(nl);
        rootNode.attachChild(colornode);
        rootNode.updateRenderState();
       
       
         super.input.getKeyBindingManager().set("StopMoving",com.jme.input.KeyInput.KEY_P);
 
    }
   
}



p.s. If you can think of a better way of handling spot lights tell me.

Any comments/ insults? Will it be added to the cvs?

Hehe, no insults here :smiley:



To be honest I don’t know what you mean by light sorting.

You are limited to having 8 lights per light state. The code allows you to have more then 8 lights in a seen by finding the best lights for each state.

You are limited to having 8 lights per light state. The code allows you to have more then 8 lights in a seen by finding the best lights for each state.


If I may, I want to clear up this comment. This does not allow you to have more than 8 active lights per light state (as this is a hard limit from OpenGL), however, it picks the 8 lights that provide the most illumination (i.e. the most important lights for a particular node). Secondly, there is no limit on the number of lightstates in a scene, only lights in a lightstate. So, you can have unlimited lights in a scene, but single nodes can only be affected by a single light state. So, Badmi's utility allows you to place many lights and dynamically builds that single lightstate that will affect a node in the most significant way.

Badmi: After looking over it, I don't see any problems. It's on my list to put in and test. I expect to approve it before long (if no negative comments from Cep or Renanse).

As an external util, looks ok.

You’ll need to fix all the misuse of words, there are just too many to do and there are a few where I’m not sure what you were meaning to say, and don’t want to assume.



Write the class comments, and missing method comments.

"mojomonk" wrote:
You'll need to fix all the misuse of words, there are just too many to do and there are a few where I'm not sure what you were meaning to say, and don't want to assume.

Write the class comments, and missing method comments.

I see what you mean. I will update them when I get a chance. ://
"mojomonk" wrote:
You'll need to fix all the misuse of words, there are just too many to do and there are a few where I'm not sure what you were meaning to say, and don't want to assume.

It would be very helpful if you could give me a list of words you want replaced.

Just do your best to fix what you can and comment everything, I’ll edit it after that.

I started commenting the code and fixed the bug. It should be done soon.

Btw, Quicksort is very fast when sorting pieces over about 8-15 (depends) items. Under that, a normal insertion sort is faster. If you want a really fast sorter, combine those two (use quicksort for pieces over 8-15 items in size, insertionsort below that).



You might also want to look at java.util.Collections which hands you a n log(n) mergesort which is quite nice.

I am using my own sort because I do not need a perfectly sorted list. All I need is the best 8 lights put on the left side. I do not need to sort lists with <= 8 lights so the speed should not be a problem. I will look into the java code to see if is is much faster then mine any way. Thank you for your advice and if you have any more please tell me.

Is this the latest code?



I think there are some strangenesses (I think I’ve found one bug this far)

No. I know that there are bugs and I have fixed them on my computer. I need to finish commenting it before I post it hear. I have been very busy lately so I did not have the time. Finishing the code one of the things on top of my todo list.

After I finish with this stripifier code, I’ll go through it. Still some spelling issue, but not nearly as bad as before.


The <code> LightStateCreator </code> class is used to sort lights in a seen. This alowse you to bypass the 8 light limit by only using the best 8 lights


This doesn't bypass the limit, as you are still only using 8 lights.

How about:


The <code> LightStateCreator </code> class is used to sort lights in a seen. This allows you to create the illusion of bypassing the 8 light limit by only using the best 8 lights





I apologize for the spelling. Spelling is not one of my strengths. :(

committed.