Lighting problem

Hi folks! Sorry to post again, but this thing is driving me frustrated… I need some help in working lighting out: I built a simple scene with lighting (> 8 lights), and must be able to save/load the scene.

The problem is: when I load the scene, everything is dark. What I expected was, that I could load the scene including the lighting. But it doesn't. What is wrong, or respectively how can I get it to work?



the 1st code is based on the TestLightStateController.java from the CVS and shows many spheres with more than 8 active lights. you can save the scene with pressing "x" (filechooser opens up).



the 2nd is a simple program to load a scene (uses filechooser at startup)




package jmetest.util;
import javax.swing.JFileChooser;
import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingSphere;
import com.jme.input.InputHandler;
import com.jme.input.KeyInput;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.light.LightControllerManager;
import com.jme.light.PointLight;
import com.jme.light.SimpleLightNode;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.LightState;
import com.jme.util.export.binary.BinaryExporter;

public class CopyOfTestLightStateController extends SimpleGame {
    final static float worldsize = 20;//The size of the world
    Node colornode;
    JFileChooser fileChooser;
    InputHandler input;
   
    public static void main(String[] args) {
        CopyOfTestLightStateController app = new CopyOfTestLightStateController();
        app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
        app.start();
    }
   
    void randomLight(int i) {
        //Chose the color for the lights.
        ColorRGBA LightColor = ColorRGBA.randomColor();

        //Create a sphere to show where the light is in the demo.
        Sphere LightSphere = new Sphere("lp" + i, 10, 10, .1f);
        LightSphere.setModelBound(new BoundingSphere());
        LightSphere.updateModelBound();
        LightSphere.setLightCombineMode(com.jme.scene.state.LightState.OFF);
        LightSphere.setDefaultColor(LightColor);

        //Create a new point light and fill out the properties
        PointLight pointLight = new PointLight();
        pointLight.setAttenuate(true);
        pointLight.setConstant(.1f);
        pointLight.setLinear(.1f);
        pointLight.setQuadratic(.1f);
        pointLight.setEnabled(true);
        pointLight.setDiffuse(LightColor);
        pointLight.setAmbient(new com.jme.renderer.ColorRGBA(.1f, .1f, .1f, .1f));

        //Add the light to the world part 1:
        //Add the light to the state creator.
        LightControllerManager.addLight(pointLight);

        //Create a node to hold the light and add a light node with this light.
        Node mnod = new Node("P" + i + " Light pos");
        SimpleLightNode ln = new SimpleLightNode("ln" + i, pointLight);
        mnod.setLocalTranslation(new Vector3f(FastMath.rand.nextFloat()
                * worldsize * 2 - worldsize, FastMath.rand.nextFloat()
                * worldsize * 2 - worldsize, FastMath.rand.nextFloat()
                * worldsize * 2 - worldsize));
        mnod.attachChild(LightSphere);
        mnod.attachChild(ln);
       
        colornode.attachChild(mnod);
               
        rootNode.attachChild(colornode);
    }

    void randomSphere(int i) {
        //Crate a sphere and position it.
        Sphere newSphere = new Sphere("sp" + i, 10, 10, 1);
        newSphere.setModelBound(new BoundingSphere());
        newSphere.updateModelBound();
        newSphere.setLocalTranslation(new Vector3f(FastMath.rand.nextFloat()
                * worldsize * 2 - worldsize, FastMath.rand.nextFloat()
                * worldsize * 2 - worldsize, FastMath.rand.nextFloat()
                * worldsize * 2 - worldsize));
        //Add a new state to the controller. We do not use createLightState
        // because it would require that the world bounds be updated first.
        LightState ls = com.jme.system.DisplaySystem.getDisplaySystem().getRenderer().createLightState();
        ls.setEnabled(true);
        newSphere.setRenderState(ls);

        //Create a controller to update the lighting and set the combine modes
        // to REPLACE. !!All other combine modes will not work!!
        LightControllerManager.addSpatial(newSphere);
        newSphere.setLightCombineMode(LightState.REPLACE);
       
        colornode.attachChild(newSphere);
        rootNode.attachChild(colornode);
    }

    public void simpleInitGame() {
        //First we remove all the lights from the lightState
        this.lightState.detachAll();

        // create colornode
        colornode = new Node();
       
        for (int i = 0; i < 50; i++) {
            this.randomLight(i);
        }
        //Add the spheres.
        for (int i = 0; i < 40; i++) {
            this.randomSphere(i);
        }
        rootNode.updateRenderState();
       
        input = new InputHandler();       
        InputAction keyAction = new InputAction()
        {
           public void performAction( InputActionEvent iae )
            {
              if(iae.getTriggerPressed())   // MouseButton pressed   
               {
                  if(iae.getTriggerIndex()==KeyInput.KEY_X)
                  {
                     save();
                  }
               }
            }
        };
        input.addAction(keyAction,InputHandler.DEVICE_ALL,InputHandler.BUTTON_ALL,InputHandler.AXIS_NONE,false);
        fileChooser = new JFileChooser();
    }
   
    public void save()
    {
       int returnVal = fileChooser.showSaveDialog(null);      
      if(returnVal == JFileChooser.APPROVE_OPTION)
      {
         /*** Write File ***/      
         try
         {
            BinaryExporter.getInstance().save(colornode,fileChooser.getSelectedFile());
         }
         catch (Exception e)
         {
            e.printStackTrace();
         }
        }
      else
         System.out.println("Saving cancelled");
    }
       
    @Override
    public void simpleUpdate()
    {
       input.update(tpf);
    }
}




package jmetest.util;
import javax.swing.JFileChooser;
import com.jme.app.SimpleGame;
import com.jme.scene.Node;
import com.jme.util.export.binary.BinaryImporter;

public class Test extends SimpleGame {
    final static float worldsize = 20;//The size of the world   
    Node loaded;
    JFileChooser fileChooser;
   
    public static void main(String[] args) {
        Test app = new Test();
        app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
        app.start();
    }
    public void simpleInitGame() {
       // clear everything first
       rootNode.detachAllChildren();
       lightState.detachAll();
       
       fileChooser = new JFileChooser();
        int returnVal = fileChooser.showOpenDialog(null);
      if(returnVal == JFileChooser.APPROVE_OPTION)
      {
         /*** Read File ***/
         try
         {
            //Select the file you want to load
            loaded = (Node) BinaryImporter.getInstance().load(fileChooser.getSelectedFile());
                        
            rootNode.attachChild(loaded);
            rootNode.updateGeometricState(0,false);
            rootNode.updateRenderState();
         }
         catch (Exception e)
         {
            e.printStackTrace();
         }
      }
      else
         System.out.println("Loading cancelled");      
    }
}

i was debugging a bit and with the mighty SceneMonitor you see that all LightStates are disabled.



The problem is the LightControllerManager / LightManagement / LightController which controls which lights are used.

After loading the Scene, the lightList in the LightManagement is empty, and thus all LightStates get disabled.



LightManagement is implementing Savable, but its never actually saved.

The LightManagement instance is only created inside the LightControllerManager which is a Singleton and is not Savable since its not part of the Scene:)



The LightStateController which updates the lights, already gets the Manager as a reference in its constructor but its not used yet and thus not saved.



If we hold the Manager as a reference in the LightStateController and also save it, your problem should be solved. :slight_smile:



Can you try the following patch ?


Index: src/com/jme/light/LightStateController.java
===================================================================
RCS file: /cvs/jme/src/com/jme/light/LightStateController.java,v
retrieving revision 1.5
diff -u -r1.5 LightStateController.java
--- src/com/jme/light/LightStateController.java   5 Feb 2007 16:16:01 -0000   1.5
+++ src/com/jme/light/LightStateController.java   12 Oct 2008 20:26:05 -0000
@@ -67,6 +67,8 @@
     private float updateInterval;
 
     private Spatial parent;
+   
+    private LightManagement manager;
 
     public LightStateController() { }
    
@@ -78,6 +80,7 @@
      */
     public LightStateController(Spatial par, LightManagement manager) {
         this.parent = par;
+        this.manager = manager;
 
         //Not needed but put in for clarification
         timePass = 0;
@@ -125,7 +128,7 @@
         if (parent.getLastFrustumIntersection() != Camera.OUTSIDE_FRUSTUM) {
                 if (timePass >= updateInterval || time < 0) {
                     timePass = 0;
-                    LightControllerManager.lm.resortLightsFor((LightState) parent
+                    manager.resortLightsFor((LightState) parent
                             .getRenderState(RenderState.RS_LIGHT), parent);
                 }
         }
@@ -137,6 +140,7 @@
         cap.write(timePass, "timePass", 0);
         cap.write(updateInterval, "updateInterval", 0);
         cap.write(parent, "parent", null);
+        cap.write(manager, "lightManager", null);
     }
    
     @Override
@@ -146,5 +150,6 @@
         timePass = cap.readFloat("timePass", 0);
         updateInterval = cap.readFloat("updateInterval", 0);
         parent = (Spatial)cap.readSavable("parent", null);
+        manager = (LightManagement)cap.readSavable("lightManager", null);
     }
 }

Thanks Core-Dump, this works! Also thanks for the hint with SceneMonitor :slight_smile:



Though I have encountered one last problem: I want to have the lights being able to pick and move around like the guy in this topic. Momoko suggested to use a LightNode and attach geometry to it. Now that I already have a SimpleLightNode I tried to extract the light itself stepwise. Every step works except extracting the light… it always returns null, as if the light isn’t there. why is that? where is the light?




//Select the file you want to load
loaded = (Node) BinaryImporter.getInstance().load(fileChooser.getSelectedFile());

// e.g. get node on a fixed position
Node node = (Node)loaded.getChild(5);
System.out.println(node); //P5 Light pos (com.jme.scene.Node)

// get element on position 0 of node
Spatial spatial = node.getChild(0);
System.out.println(spatial); //lp5 (com.jme.scene.shape.Sphere)

// get element on position 1 of node
SimpleLightNode ln = (SimpleLightNode)node.getChild(1);
System.out.println(ln); //ln5 (com.jme.light.SimpleLightNode)

// get Light from this Node
System.out.println(ln.getLight()); // null ??

Wait… what? How will you know where the light is? If you wanted, you could put something like a sphere in the same position and drag it, and when you drag the actual sphere node, it moves the light with it.

Yes, that's the way it should be. I plan to use a billboard geometry to move the lightnode around and be able to change the light properties.



The problem however is: after loading the file in Test.java I assumed that the light should still be within the SimpleLightNode because of  "SimpleLightNode ln = new SimpleLightNode("ln" + i, pointLight);" and I expected an output like that:



P5 Light pos (com.jme.scene.Node)

lp5 (com.jme.scene.shape.Sphere)

ln5 (com.jme.light.SimpleLightNode)

com.jme.light.PointLight@1d9fd51 // <- it should show this, but it instead it shows "null"



So when I check if the light is at the lightnode… it's not. There is no light anymore, although the scene is still being lit by the 40 lights! So where is the light?

Hmmm… I once had problems with this.

The SimpleLightNode isn't loading / saving the light correctly:



Notice the difference in reading / saving :slight_smile:



    public void write(JMEExporter e) throws IOException {
        super.write(e);
        OutputCapsule capsule = e.getCapsule(this);
        capsule.write(light, "Light", null);
        capsule.write(lightRotate, "lightRotate", new Quaternion());
       
    }
   
    public void read(JMEImporter e) throws IOException {
        super.read(e);
        InputCapsule capsule = e.getCapsule(this);
        light = (Light)capsule.readSavable("light", null);
        lightRotate = (Quaternion)capsule.readSavable("lightRotate", new Quaternion());
    }



the problems will be fixed here: http://www.jmonkeyengine.com/jmeforum/index.php?topic=9398.0

I think I know the problem. This needs to replace the old line of code:


        light = (Light)capsule.readSavable("Light", null);

Ah that was the reason… a typo in the SimpleLightNode class XD

It works now, thanks!! I'm glad I could contribute something even if it's only in a passive way.



After saving and loading a new scene the output is now correct as expected:

P5 Light pos (com.jme.scene.Node)

lp5 (com.jme.scene.shape.Sphere)

ln5 (com.jme.light.SimpleLightNode)

com.jme.light.PointLight@189c036



retrieve Color also no problem:

com.jme.renderer.ColorRGBA: [R=0.584631, G=0.26959676, B=0.8976097, A=1.0]

cool, i'll see that it gets fixed

in jme 2 you can use Node.sortLights(), see also the test jmetest.util.TestManyLights.

There no need for the LightManagement stuff anymore.


yeah, a reason to move towards JME2 (I also read the irc chat log my friend copied ;). Currently I use this TestManyLights as a dummy for trying out saving/loading lights in XML format (using jdom). It's quite comfortable now because the lightState contains all lights and there is a nice function to get the lights.



ArrayList<Light> arrlights = new ArrayList<Light>();

arrlights = lightState.getLightList();

… // then some loop to get the properties of each light and save them in XML

I had to delete my last post, because they were some weird errors I had to solve first. So now I figured out that lights are definitely savable in a binary way. No need to use XML. Eventually I could clean up the code a bit and I have one last question (i hope it remains the last :p):



Since I am creating all lights with LightNodes, I experienced that when I'm adding a DirectionalLight, this DirectionalLight doesn't show up. PointLight and SpotLight work flawlessly.



I considered to add a DirectionalLight and a Quad to a LightNode, and rotating the Quad would rotate the LightNode and therefore the direction of the DirectionalLight (as written in the API). But now this does not seem to work because the DirectionalLight doesn't even show up. Why is that so?






import java.util.ArrayList;

import javax.swing.JFileChooser;
import com.acarter.scenemonitor.SceneMonitor;
import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingSphere;
import com.jme.input.InputHandler;
import com.jme.input.KeyInput;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.light.DirectionalLight;
import com.jme.light.LightNode;
import com.jme.light.PointLight;
import com.jme.light.SpotLight;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.LightState;
import com.jme.util.export.binary.BinaryExporter;
import com.jme.util.export.binary.BinaryImporter;

public class BinaryTest extends SimpleGame {

    Node geometrieknoten;
    JFileChooser fileChooser;
    InputHandler input;
    String path;
    String filename;
    static Node lichtknoten;

    static final float worldsize = 40;//The size of the world

    public static void main(String[] args) {
        BinaryTest app = new BinaryTest();
        app.setConfigShowMode(ConfigShowMode.NeverShow);
        app.start();
    }

    void randomLight(int i) {
        //Chose the color for the lightSphere AND the lights
        ColorRGBA LightColor = ColorRGBA.randomColor();

        //Create a sphere to show where the light is in the demo.
        Sphere LightSphere = new Sphere("lp" + i, 10, 10, .25f);
        LightSphere.setModelBound(new BoundingSphere());
        LightSphere.updateModelBound();
        LightSphere.setLightCombineMode(Spatial.LightCombineMode.Off);//diese sollen nicht beleuchtet sein
        LightSphere.setDefaultColor(LightColor);

        //Create a new point light and fill out the properties
        PointLight pointLight = new PointLight();
        pointLight.setDiffuse(LightColor);
        pointLight.setQuadratic(0.01f);
        pointLight.setAttenuate(true);
        pointLight.setShadowCaster(true);
        pointLight.setEnabled(true);

        lightState.attach(pointLight);

        //Create a node to hold the light and add a light node with this light.
        LightNode ln = new LightNode("ln");
        ln.setLight(pointLight);
       
        //also attach the lightsphere to the lightnode (so it can be selected via clicking on it
        ln.attachChild(LightSphere);
       
        ln.setLocalTranslation(new Vector3f(FastMath.rand.nextFloat()
                * worldsize * 2 - worldsize, FastMath.rand.nextFloat()
                * worldsize * 2 - worldsize, FastMath.rand.nextFloat()
                * worldsize * 2 - worldsize));
        lichtknoten.attachChild(ln);
    }

    void randomSphere(int i) {
        //Crate the big spheres and position it.
        Sphere newSphere = new Sphere("sp" + i, 8, 8, 2);
        newSphere.setModelBound(new BoundingSphere());
        newSphere.updateModelBound();
        newSphere.setLocalTranslation(new Vector3f(FastMath.rand.nextFloat()
                * worldsize * 2 - worldsize, FastMath.rand.nextFloat()
                * worldsize * 2 - worldsize, FastMath.rand.nextFloat()
                * worldsize * 2 - worldsize));
        geometrieknoten.attachChild(newSphere);
    }

    protected void simpleInitGame() {
        //First we remove all the lights
        this.lightState.detachAll();

        FastMath.rand.setSeed(1520);
       
        lichtknoten = new Node("lichtknoten");
        geometrieknoten = new Node("geometrieknoten");
       
        for (int i = 0; i < 30; i++) {
            this.randomLight(i);
        }
        for (int i = 0; i < 60; i++) {
            this.randomSphere(i);
        }       
        //We do not want to use lighting on the spheres
        LightState nl = com.jme.system.DisplaySystem.getDisplaySystem().getRenderer().createLightState();
        nl.setEnabled(false);
        geometrieknoten.setRenderState(nl);

        rootNode.attachChild(lichtknoten);
        rootNode.attachChild(geometrieknoten);
        rootNode.updateGeometricState(0.0f, true);
        rootNode.updateRenderState();
       
        input = new InputHandler();       
        InputAction keyAction = new InputAction()
        {
           public void performAction( InputActionEvent iae )
            {
              if(iae.getTriggerPressed())   // MouseButton pressed   
               {
                  if(iae.getTriggerIndex()==KeyInput.KEY_X)
                  {
                     save();
                  }
                  if(iae.getTriggerIndex()==KeyInput.KEY_C)
                  {
                     clear();
                  }
                  if(iae.getTriggerIndex()==KeyInput.KEY_F)
                  {
                     load();
                  }
               }
            }
        };
        input.addAction(keyAction,InputHandler.DEVICE_ALL,InputHandler.BUTTON_ALL,InputHandler.AXIS_NONE,false);
       
        SceneMonitor.getMonitor().registerNode(rootNode, "Root Node");
        SceneMonitor.getMonitor().showViewer(true);
    }
   
    public void clear()
    {
       lightState.detachAll();
       lichtknoten.detachAllChildren();
       rootNode.updateGeometricState(0,false);
       rootNode.updateRenderState();
    }
   
    public void save()
    {
       fileChooser = new JFileChooser();
       int returnVal = fileChooser.showSaveDialog(null);
       
      if(returnVal == JFileChooser.APPROVE_OPTION)
      {
         /*** Write File ***/      
         try
         {
            // Save Geometry as Binary
            BinaryExporter.getInstance().save(lichtknoten,fileChooser.getSelectedFile());
         }
         catch (Exception e)
         {
            e.printStackTrace();
         }
        }
      else
         System.out.println("Saving cancelled");
    }
   
    public void load()
    {
       fileChooser = new JFileChooser();
        int returnVal = fileChooser.showOpenDialog(null);
      if(returnVal == JFileChooser.APPROVE_OPTION)
      {
         /*** Read File ***/
         try   
         {
            //Select the file you want to load
            Node loaded = new Node();
            loaded = (Node) BinaryImporter.getInstance().load(fileChooser.getSelectedFile());
            
            System.out.println(loaded.getQuantity());
            
            // Das ist IMMANENT! Wenn man nur die normale Schleife macht fehlt die H

I considered to add a DirectionalLight and a Quad to a LightNode, and rotating the Quad would rotate the LightNode and therefore the direction of the DirectionalLight


You mean rotating the LightNode would rotate the Quad and DirectionalLight.

If you simply add a DirectionalLight to a lightNode, and rotate the node, does it behave correctly ?

Rotating a LightNode works without a problem (at least from what I've experienced by using a SpotLight instead). The problem however in here is with the "DirectionalLight" class itself (not the SpotLight and not the PointLight): JME doesn't show the DirectionalLight I added - and thats the issue.



I abbreviated the code to show what I mean: the green PointLight works (also would a SpotLight), but the DirectionalLight (the "Sunlight") is not visible. If you delete Line 75 (lichtknoten.attachChild(drn)) the DirectionalLight is visible! But this makes no sense to me.

It would mean, that I am not allowed to put DirectionalLight into a LightNode  :?


import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingSphere;
import com.jme.light.DirectionalLight;
import com.jme.light.LightNode;
import com.jme.light.PointLight;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.shape.Sphere;

public class LightNodeTest extends SimpleGame {
   static Node lichtknoten;
    static Node geometrieknoten;
    static final float worldsize = 40;//The size of the world

    public static void main(String[] args) {
        LightNodeTest app = new LightNodeTest();
        app.setConfigShowMode(ConfigShowMode.NeverShow);
        app.start();
    }
   
    void randomSphere(int i) {
        //Crate the big spheres and position it.
        Sphere newSphere = new Sphere("sp" + i, 8, 8, 2);
        newSphere.setModelBound(new BoundingSphere());
        newSphere.updateModelBound();
        newSphere.setLocalTranslation(new Vector3f(FastMath.rand.nextFloat()
                * worldsize * 2 - worldsize, FastMath.rand.nextFloat()
                * worldsize * 2 - worldsize, FastMath.rand.nextFloat()
                * worldsize * 2 - worldsize));
        geometrieknoten.attachChild(newSphere);
    }

    protected void simpleInitGame() {
        this.lightState.detachAll();
       
        geometrieknoten = new Node("geom");
        lichtknoten = new Node("licht");

        FastMath.rand.setSeed(1520);
       
        //Add the spheres.
        for (int i = 0; i < 60; i++) {
            this.randomSphere(i);
        }
       
        rootNode.attachChild(lichtknoten);
        rootNode.attachChild(geometrieknoten);
        rootNode.updateGeometricState(0.0f, true);
        rootNode.updateRenderState();
       
        //Create a new PointLight
        PointLight pl1 = new PointLight();
        pl1.setDiffuse(ColorRGBA.green);
        pl1.setQuadratic(0.01f);
        pl1.setAttenuate(true);
        pl1.setEnabled(true);
       
       LightNode pln = new LightNode("pln");
       pln.setLight(pl1);
       lichtknoten.attachChild(pln);
       lightState.attach(pl1);            
            
       //Create a new DirectionalLight
      DirectionalLight dr = new DirectionalLight();
        dr.setEnabled(true);
        dr.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
        dr.setAmbient(new ColorRGBA(0.2f, 0.2f, 0.2f, 1.0f));
        dr.setDirection(new Vector3f(0.5f, -0.5f, 0));
        dr.setShadowCaster(true);
       
        LightNode drn = new LightNode("drn");
        drn.setLight(dr);
        lichtknoten.attachChild(drn);
        lightState.attach(dr);
      
        rootNode.updateGeometricState(0,false);
       rootNode.updateRenderState();
    }
}



http://code.google.com/p/jmonkeyengine/source/browse/trunk/src/com/jme/light/LightNode.java

line 117:

dLight.getDirection().set(worldTranslation).negateLocal();



does that make sense?  :)

shouldn't it be:

dLight.getDirection().set(worldRotation.getRotationColumn(2));

Woohoo! I guess it should, because it wooorks now!

Definitely must be fixed in SVN :slight_smile:



Sorry, I'm always trying to avoid looking into the source Code, although the solution was very close … but soon every lightbug is eliminated :smiley:

you need to look at the code to see how things work :slight_smile:

but most of jme is really easy to read and understand, so never hesistate to start up the debugger and step through it.



edit:

there still seems to be a problem when the LightNode is rotated and translated at the same time.