Commits

Michael Ludwig committed 872f993

Implement solid color, camera states. Add proper blending when there are >8 lights in a group. Correct state application logic in StateNode so child group states aren't applied until after the actual states in the node.

Comments (0)

Files changed (10)

ferox-scene/src/main/java/com/ferox/scene/controller/ffp/AppliedEffects.java

 
 import com.ferox.math.Const;
 import com.ferox.math.Matrix4;
+import com.ferox.renderer.FixedFunctionRenderer;
 import com.ferox.renderer.Renderer.BlendFactor;
+import com.ferox.renderer.Renderer.BlendFunction;
+import com.ferox.renderer.Renderer.Comparison;
 import com.ferox.scene.Light;
 import com.lhkbob.entreri.Component;
 
 public class AppliedEffects {
-    private final boolean shadowedLightingPhase;
-    // FIXME this might need to be a set, since in the main phase, we need
-    // to specify all shadowed lights so that all of them can be disabled.
+    // Non-null when actively rendering a shadow map, the general set of
+    // shadow-casting lights is not the responsibility of AppliedEffects
     private final Component<? extends Light<?>> shadowLight;
 
     // BlendFunction will always be ADD
-    // FIXME do we need to specify separate factors for RGB and alpha?
     private final BlendFactor destBlend;
     private final BlendFactor sourceBlend;
 
     private final Matrix4 viewMatrix;
 
-    public AppliedEffects(@Const Matrix4 view) {
-        shadowedLightingPhase = false;
+    public AppliedEffects() {
         shadowLight = null;
         destBlend = BlendFactor.ZERO;
         sourceBlend = BlendFactor.ONE;
-        viewMatrix = view;
+        viewMatrix = new Matrix4();
     }
 
-    private AppliedEffects(@Const Matrix4 view, boolean shadowedLighting,
-                           BlendFactor sourceBlend, BlendFactor destBlend,
+    private AppliedEffects(@Const Matrix4 view, BlendFactor sourceBlend,
+                           BlendFactor destBlend,
                            Component<? extends Light<?>> shadowLight) {
         this.sourceBlend = sourceBlend;
         this.destBlend = destBlend;
         this.shadowLight = shadowLight;
         viewMatrix = view;
-        shadowedLightingPhase = shadowedLighting;
     }
 
-    public AppliedEffects setBlending(BlendFactor source, BlendFactor dest) {
-        return new AppliedEffects(viewMatrix,
-                                  shadowedLightingPhase,
-                                  source,
-                                  dest,
-                                  shadowLight);
+    public void pushBlending(FixedFunctionRenderer r) {
+        if (isBlendingEnabled()) {
+            r.setBlendingEnabled(true);
+            r.setBlendMode(BlendFunction.ADD, sourceBlend, destBlend);
+            // FIXME might need a different comparison for shadow-mapping?
+            r.setDepthTest(Comparison.LEQUAL);
+            r.setDepthWriteMask(false);
+        } else {
+            r.setBlendingEnabled(false);
+            r.setDepthTest(Comparison.LESS);
+            r.setDepthWriteMask(true);
+        }
     }
 
-    public @Const
-    Matrix4 getViewMatrix() {
+    public AppliedEffects applyBlending(BlendFactor source, BlendFactor dest) {
+        return new AppliedEffects(viewMatrix, source, dest, shadowLight);
+    }
+
+    public AppliedEffects applyShadowMapping(Component<? extends Light<?>> light) {
+        return new AppliedEffects(viewMatrix, sourceBlend, destBlend, light);
+    }
+
+    public AppliedEffects applyViewMatrix(@Const Matrix4 view) {
+        return new AppliedEffects(view, sourceBlend, destBlend, shadowLight);
+    }
+
+    @Const
+    public Matrix4 getViewMatrix() {
         return viewMatrix;
     }
 
     public boolean isShadowBeingRendered() {
-        return shadowedLightingPhase;
+        return shadowLight != null;
     }
 
     public boolean isBlendingEnabled() {
         return destBlend;
     }
 
-    public Component<? extends Light<?>> getShadowCaster() {
+    public Component<? extends Light<?>> getShadowMappingLight() {
         return shadowLight;
     }
 }

ferox-scene/src/main/java/com/ferox/scene/controller/ffp/CameraGroupFactory.java

+package com.ferox.scene.controller.ffp;
+
+import java.util.Collections;
+import java.util.List;
+
+import com.ferox.math.bounds.Frustum;
+import com.ferox.renderer.FixedFunctionRenderer;
+import com.lhkbob.entreri.Entity;
+
+public class CameraGroupFactory implements StateGroupFactory {
+    private final StateGroupFactory childFactory;
+
+    private final Frustum camera;
+
+    public CameraGroupFactory(Frustum camera, StateGroupFactory childFactory) {
+        this.camera = camera;
+        this.childFactory = childFactory;
+    }
+
+    @Override
+    public StateGroup newGroup() {
+        return new CameraGroup();
+    }
+
+    private class CameraGroup implements StateGroup {
+        private final StateNode node;
+
+        public CameraGroup() {
+            node = new StateNode((childFactory == null ? null : childFactory.newGroup()),
+                                 new CameraState());
+        }
+
+        @Override
+        public StateNode getNode(Entity e) {
+            return node;
+        }
+
+        @Override
+        public List<StateNode> getNodes() {
+            return Collections.singletonList(node);
+        }
+
+        @Override
+        public AppliedEffects applyGroupState(FixedFunctionRenderer r,
+                                              AppliedEffects effects) {
+            return effects;
+        }
+
+        @Override
+        public void unapplyGroupState(FixedFunctionRenderer r, AppliedEffects effects) {}
+
+    }
+
+    private class CameraState implements State {
+        @Override
+        public void add(Entity e) {}
+
+        @Override
+        public AppliedEffects applyState(FixedFunctionRenderer r, AppliedEffects effects,
+                                         int index) {
+            r.setProjectionMatrix(camera.getProjectionMatrix());
+            r.setModelViewMatrix(camera.getViewMatrix());
+            return effects.applyViewMatrix(camera.getViewMatrix());
+        }
+
+        @Override
+        public void unapplyState(FixedFunctionRenderer r, AppliedEffects effects,
+                                 int index) {}
+    }
+}

ferox-scene/src/main/java/com/ferox/scene/controller/ffp/FixedFunctionRenderController.java

             sm.setDepthCompareEnabled(true);
             sm.setDepthComparison(Comparison.LEQUAL);
 
-            // use the 3rd unit if available, or the 2nd if not
-            shadowmapTextureUnit = (numTex > 2 ? 2 : 1);
+            // use the 4th unit if available, or the last unit if we're under
+            shadowmapTextureUnit = Math.max(numTex, 3) - 1;
             // reserve one unit for the shadow map
             numTex--;
         } else {
             shadowmapTextureUnit = -1;
         }
 
-        // FIXME should this be the responsibility of the TextureGroupFactory
-        // I'm not sure because other factories might also need texture units
-        if (numTex >= 2) {
+        if (numTex >= 3) {
             diffuseTextureUnit = 0;
+            emissiveTextureUnit = 2;
+            decalTextureUnit = 1;
+        } else if (numTex == 2) {
+            diffuseTextureUnit = 0;
+            // merge emissive and decal units
             emissiveTextureUnit = 1;
-        } else {
-            // multiple passes for textures
+            decalTextureUnit = 1;
+        } else if (numTex == 1) {
+            // merge all units
             diffuseTextureUnit = 0;
             emissiveTextureUnit = 0;
+            decalTextureUnit = 0;
+        } else {
+            // disable texturing
+            diffuseTextureUnit = -1;
+            emissiveTextureUnit = -1;
+            decalTextureUnit = -1;
         }
     }
 
 
     public static long rendertime = 0L;
 
-    private Future<Void> render(final Surface surface, final Frustum view, Bag<Entity> pvs) {
+    private Future<Void> render(final Surface surface, Frustum view, Bag<Entity> pvs) {
+        // FIXME can we somehow preserve this tree across frames instead of continuing
+        // to build it over and over again?
         GeometryGroupFactory geomGroup = new GeometryGroupFactory(getEntitySystem(),
                                                                   view.getViewMatrix());
         TextureGroupFactory textureGroup = new TextureGroupFactory(getEntitySystem(),
                                                                    diffuseTextureUnit,
                                                                    emissiveTextureUnit,
+                                                                   decalTextureUnit,
                                                                    geomGroup);
         MaterialGroupFactory materialGroup = new MaterialGroupFactory(getEntitySystem(),
-                                                                      geomGroup);
+                                                                      (diffuseTextureUnit >= 0 ? textureGroup : geomGroup));
+
         LightGroupFactory lightGroup = new LightGroupFactory(getEntitySystem(),
                                                              lightGroups,
                                                              framework.getCapabilities()
                                                                       .getMaxActiveLights(),
                                                              materialGroup);
-        LightingGroupFactory lightingGroup = new LightingGroupFactory(materialGroup,
+        SolidColorGroupFactory solidColorGroup = new SolidColorGroupFactory(getEntitySystem(),
+                                                                            textureGroup);
+
+        LightingGroupFactory lightingGroup = new LightingGroupFactory(solidColorGroup,
                                                                       lightGroup);
+        CameraGroupFactory cameraGroup = new CameraGroupFactory(view, lightingGroup);
 
-        final StateNode rootNode = new StateNode(lightingGroup.newGroup());
+        final StateNode rootNode = new StateNode(cameraGroup.newGroup());
         for (Entity e : pvs) {
             rootNode.add(e);
         }
                     FixedFunctionRenderer ffp = ctx.getFixedFunctionRenderer();
                     // FIXME clear color should be configurable somehow
                     ffp.clear(true, true, true, new Vector4(0.5, 0.5, 0.5, 1.0), 1f, 0);
-                    // FIXME should these be moved into a ViewStateGroupFactory?
-                    ffp.setProjectionMatrix(view.getProjectionMatrix());
-                    ffp.setModelViewMatrix(view.getViewMatrix());
 
-                    rootNode.render(ffp, new AppliedEffects(view.getViewMatrix()));
+                    rootNode.render(ffp, new AppliedEffects());
                 }
                 rendertime += (System.nanoTime() - now);
                 return null;

ferox-scene/src/main/java/com/ferox/scene/controller/ffp/GeometryGroupFactory.java

             // set attributes to null
             r.setVertices(null);
             r.setNormals(null);
+            // restore matrix to camera only
             r.setModelViewMatrix(effects.getViewMatrix());
         }
     }

ferox-scene/src/main/java/com/ferox/scene/controller/ffp/LightGroupFactory.java

 import com.ferox.math.Vector4;
 import com.ferox.renderer.FixedFunctionRenderer;
 import com.ferox.renderer.Renderer.BlendFactor;
-import com.ferox.renderer.Renderer.BlendFunction;
 import com.ferox.scene.AmbientLight;
 import com.ferox.scene.DirectionLight;
 import com.ferox.scene.Light;
                 if (light != null) {
                     // check to see if this light should be used for the current
                     // stage of shadow mapping
-                    if ((effects.isShadowBeingRendered() && light.source == effects.getShadowCaster()) || (!effects.isShadowBeingRendered() && light.source != effects.getShadowCaster())) {
+                    if ((effects.isShadowBeingRendered() && light.source == effects.getShadowMappingLight()) || (!effects.isShadowBeingRendered() && light.source != effects.getShadowMappingLight())) {
                         // enable and configure the light
                         r.setLightEnabled(i, true);
                         r.setLightPosition(i, light.position);
                     // lights that were already rendered. If we're in the shadowing pass, we
                     // know that only a single light is active so it doesn't matter if the index > 0,
                     // we don't touch the blending
-                    r.setBlendingEnabled(true);
-                    r.setBlendMode(BlendFunction.ADD, effects.getSourceBlendFactor(),
-                                   BlendFactor.ONE);
-                    return effects.setBlending(effects.getSourceBlendFactor(),
-                                               BlendFactor.ONE);
-                } else {
-                    return effects;
+                    effects = effects.applyBlending(effects.getSourceBlendFactor(),
+                                                    BlendFactor.ONE);
+                    effects.pushBlending(r);
                 }
+
+                return effects;
             } else {
                 // if there aren't any configured lights, no need to render everything
                 return null;
         public void unapplyState(FixedFunctionRenderer r, AppliedEffects effects,
                                  int index) {
             if (index > 0 && !effects.isShadowBeingRendered()) {
-                r.setBlendingEnabled(effects.isBlendingEnabled());
-                r.setBlendMode(BlendFunction.ADD, effects.getSourceBlendFactor(),
-                               effects.getDestinationBlendFactor());
+                // these effects were the original, so we restore the blend state
+                effects.pushBlending(r);
             }
         }
     }

ferox-scene/src/main/java/com/ferox/scene/controller/ffp/MaterialGroupFactory.java

 import com.lhkbob.entreri.EntitySystem;
 
 public class MaterialGroupFactory implements StateGroupFactory {
-    private static final Vector4 DEFAULT_DIFFUSE = new Vector4(0.8, 0.8, 0.8, 1.0);
-    private static final Vector4 DEFAULT_SPECULAR = new Vector4(0.0, 0.0, 0.0, 1.0);
-    private static final Vector4 DEFAULT_EMITTED = new Vector4(0.0, 0.0, 0.0, 1.0);
-    private static final Vector4 DEFAULT_AMBIENT = new Vector4(0.2, 0.2, 0.2, 1.0);
+    public static final Vector4 DEFAULT_DIFFUSE = new Vector4(0.8, 0.8, 0.8, 1.0);
+    public static final Vector4 DEFAULT_SPECULAR = new Vector4(0.0, 0.0, 0.0, 1.0);
+    public static final Vector4 DEFAULT_EMITTED = new Vector4(0.0, 0.0, 0.0, 1.0);
+    public static final Vector4 DEFAULT_AMBIENT = new Vector4(0.2, 0.2, 0.2, 1.0);
 
     private final DiffuseColor diffuseColor;
     private final SpecularColor specularColor;
 
         @Override
         public void unapplyGroupState(FixedFunctionRenderer r, AppliedEffects effects) {
-            // do nothing
+            r.setMaterial(DEFAULT_AMBIENT, DEFAULT_DIFFUSE, DEFAULT_SPECULAR,
+                          DEFAULT_EMITTED);
         }
     }
 
         @Override
         public void unapplyState(FixedFunctionRenderer r, AppliedEffects effects,
                                  int index) {
-            r.setMaterial(DEFAULT_AMBIENT, DEFAULT_DIFFUSE, DEFAULT_SPECULAR,
-                          DEFAULT_EMITTED);
+            // do nothing
         }
 
         @Override

ferox-scene/src/main/java/com/ferox/scene/controller/ffp/ShadowMapGroupFactory.java

     // I don't think this even has an index, since there is only one group
     // and it just creates a bunch of states. Only one gropu is needed because
     // this will be underneath the 'lit' state that enables lighting in general
+
+    // FIXME I need to determine if multiple shadow-casting lights are allowed
+    // I really think they should be. but if that's the case then I need to update
+    // AppliedEffects to support that properly
 }

ferox-scene/src/main/java/com/ferox/scene/controller/ffp/SolidColorGroupFactory.java

 package com.ferox.scene.controller.ffp;
 
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import com.ferox.math.ColorRGB;
+import com.ferox.math.Vector4;
 import com.ferox.renderer.FixedFunctionRenderer;
+import com.ferox.scene.DiffuseColor;
+import com.ferox.scene.Transparent;
 import com.lhkbob.entreri.Entity;
+import com.lhkbob.entreri.EntitySystem;
 
 public class SolidColorGroupFactory implements StateGroupFactory {
+    private final StateGroupFactory childFactory;
+
+    private final DiffuseColor color;
+    private final Transparent alpha;
+
+    private final SolidColorState access;
+
+    public SolidColorGroupFactory(EntitySystem system, StateGroupFactory childFactory) {
+        this.childFactory = childFactory;
+        color = system.createDataInstance(DiffuseColor.ID);
+        alpha = system.createDataInstance(Transparent.ID);
+
+        access = new SolidColorState();
+    }
 
     @Override
     public StateGroup newGroup() {
-        // TODO Auto-generated method stub
-        return null;
+        return new SolidColorGroup();
     }
 
     private class SolidColorGroup implements StateGroup {
+        private final List<StateNode> allNodes;
+        private final Map<SolidColorState, StateNode> nodeLookup;
+
+        public SolidColorGroup() {
+            allNodes = new ArrayList<StateNode>();
+            nodeLookup = new HashMap<SolidColorState, StateNode>();
+        }
 
         @Override
         public StateNode getNode(Entity e) {
-            // TODO Auto-generated method stub
-            return null;
+            e.get(color);
+            e.get(alpha);
+
+            access.set(color, alpha);
+            StateNode node = nodeLookup.get(access);
+            if (node == null) {
+                // new color combination
+                SolidColorState newState = new SolidColorState();
+                newState.set(color, alpha);
+                node = new StateNode((childFactory == null ? null : childFactory.newGroup()),
+                                     newState);
+                nodeLookup.put(newState, node);
+                allNodes.add(node);
+            }
+
+            return node;
         }
 
         @Override
         public List<StateNode> getNodes() {
-            // TODO Auto-generated method stub
-            return null;
+            return allNodes;
         }
 
         @Override
         public AppliedEffects applyGroupState(FixedFunctionRenderer r,
                                               AppliedEffects effects) {
-            // TODO Auto-generated method stub
-            return null;
+            return effects;
         }
 
         @Override
         public void unapplyGroupState(FixedFunctionRenderer r, AppliedEffects effects) {
-            // TODO Auto-generated method stub
-
+            r.setMaterial(MaterialGroupFactory.DEFAULT_AMBIENT,
+                          MaterialGroupFactory.DEFAULT_DIFFUSE,
+                          MaterialGroupFactory.DEFAULT_SPECULAR,
+                          MaterialGroupFactory.DEFAULT_EMITTED);
         }
 
     }
 
     private class SolidColorState implements State {
-        private final ColorRGB rgb;
+        private final Vector4 color;
 
-        public SolidColorState(ColorRGB rgb) {
+        public SolidColorState() {
+            color = new Vector4();
+        }
 
+        public void set(DiffuseColor color, Transparent alpha) {
+            if (color.isEnabled()) {
+                ColorRGB rgb = color.getColor();
+                this.color.x = rgb.red();
+                this.color.y = rgb.green();
+                this.color.z = rgb.blue();
+            } else {
+                this.color.set(MaterialGroupFactory.DEFAULT_DIFFUSE);
+            }
+
+            this.color.w = (alpha.isEnabled() ? alpha.getOpacity() : 1.0);
         }
 
         @Override
         }
 
         @Override
-        public AppliedEffects applyState(FixedFunctionRenderer r, AppliedEffects effects, int index) {
-            r.setM
-            // TODO Auto-generated method stub
-            return null;
+        public AppliedEffects applyState(FixedFunctionRenderer r, AppliedEffects effects,
+                                         int index) {
+            r.setMaterial(MaterialGroupFactory.DEFAULT_AMBIENT, color,
+                          MaterialGroupFactory.DEFAULT_SPECULAR,
+                          MaterialGroupFactory.DEFAULT_SPECULAR);
+            return effects;
         }
 
         @Override
         public void unapplyState(FixedFunctionRenderer r, AppliedEffects effects,
                                  int index) {
+            // do nothing
+        }
 
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof SolidColorState)) {
+                return false;
+            }
+            return ((SolidColorState) o).color.equals(color);
+        }
+
+        @Override
+        public int hashCode() {
+            return color.hashCode();
         }
     }
 }

ferox-scene/src/main/java/com/ferox/scene/controller/ffp/StateNode.java

         List<StateNode> childNodes = (children != null ? children.getNodes() : null);
         int childCount = (children != null ? childNodes.size() : 0);
 
-        // FIXME we're applying a child group state before the specific states
-        // of this node, which means they can mutate the applied effects too soon,
-        // maybe the StateGroup mutations don't get to mutate the effects?
-        AppliedEffects forStates = (children != null ? children.applyGroupState(r,
-                                                                                effects) : effects);
-        if (forStates != null) {
+        if (effects != null) {
             for (int i = 0; i < state.length; i++) {
-                AppliedEffects childEffects = state[i].applyState(r, forStates, i);
-                if (childEffects != null) {
+                AppliedEffects newEffects = state[i].applyState(r, effects, i);
+                if (newEffects != null) {
+                    AppliedEffects groupEffects = (children != null ? children.applyGroupState(r,
+                                                                                               newEffects) : newEffects);
                     for (int j = 0; j < childCount; j++) {
-                        childNodes.get(j).render(r, childEffects);
+                        childNodes.get(j).render(r, groupEffects);
                     }
-                    state[i].unapplyState(r, forStates, i);
+                    if (children != null) {
+                        children.unapplyGroupState(r, newEffects);
+                    }
+                    state[i].unapplyState(r, effects, i);
                 }
             }
-
-            if (children != null) {
-                children.unapplyGroupState(r, effects);
-            }
         }
     }
 

ferox-scene/src/test/java/com/ferox/scene/controller/ffp/SimpleTest.java

 import com.ferox.renderer.Framework;
 import com.ferox.renderer.OnscreenSurface;
 import com.ferox.renderer.OnscreenSurfaceOptions;
+import com.ferox.renderer.OnscreenSurfaceOptions.MultiSampling;
 import com.ferox.renderer.impl.jogl.JoglFramework;
 import com.ferox.renderer.impl.lwjgl.LwjglFramework;
 import com.ferox.resource.VertexBufferObject.StorageMode;
         OnscreenSurface surface = framework.createSurface(new OnscreenSurfaceOptions().setWidth(800)
                                                                                       .setHeight(600)
                                                                                       //            .setFullscreenMode(new DisplayMode(1440, 900, PixelFormat.RGB_24BIT))
-                                                                                      //            .setMultiSampling(MultiSampling.FOUR_X)
+                                                                                      .setMultiSampling(MultiSampling.FOUR_X)
                                                                                       .setResizable(false));
-        surface.setVSyncEnabled(false);
+        surface.setVSyncEnabled(true);
 
         EntitySystem system = new EntitySystem();
 
              .setLocalBounds(b.getBounds())
              .setIndices(b.getPolygonType(), b.getIndices(), b.getIndexOffset(),
                          b.getIndexCount());
-            //            if (Math.random() < .9)
-            e.add(BlinnPhongMaterial.ID).getData().setNormals(b.getNormals());
+            if (Math.random() < .9) {
+                e.add(BlinnPhongMaterial.ID).getData().setNormals(b.getNormals());
+            }
             e.add(DiffuseColor.ID).getData().setColor(c);
-            //            e.add(SpecularColor.ID).getData().setColor(new ColorRGB(0.5, 0.5, 0.5));
             e.add(Transform.ID)
              .getData()
              .setMatrix(new Matrix4(1,
 
         System.out.println("Approximate total polygons / frame: " + totalpolys);
 
-        for (int i = 0; i < 10; i++) {
-            double falloff = 10.0 + Math.random() * 10.0 + Math.random() * 100.0;
+        for (int i = 0; i < 20; i++) {
+            double falloff = 100.0 + Math.random() * 40;
 
             Entity light = system.addEntity();
             light.add(PointLight.ID).getData().setFalloffDistance(falloff)
                  .setColor(new ColorRGB(Math.random(), Math.random(), Math.random()));
 
-            light.add(InfluenceRegion.ID)
-                 .getData()
-                 .setBounds(new AxisAlignedBox(new Vector3(-falloff, -falloff, -falloff),
-                                               new Vector3(falloff, falloff, falloff)));
+            if (falloff > 0) {
+                light.add(InfluenceRegion.ID)
+                     .getData()
+                     .setBounds(new AxisAlignedBox(new Vector3(-falloff,
+                                                               -falloff,
+                                                               -falloff),
+                                                   new Vector3(falloff, falloff, falloff)));
+            }
             light.add(Transform.ID)
                  .getData()
                  .setMatrix(new Matrix4(1,
         system.getControllerManager().addController(lights);
         system.getControllerManager().addController(render);
 
-        int numRuns = 1000;
         long now = System.nanoTime();
-
+        int numRuns = 0;
         try {
-            for (int i = 0; i < numRuns; i++) {
+            while (System.nanoTime() - now < 10000000000L) {
                 system.getControllerManager().process();
                 framework.flush(surface);
+                numRuns++;
                 for (String name : controllers.keySet()) {
                     Long time = times.get(name);
                     if (time == null) {