Commits

Michael Ludwig  committed 6ef0e4e

Implement LightGroupController

  • Participants
  • Parent commits e6ceedb

Comments (0)

Files changed (4)

File ferox-scene/src/main/java/com/ferox/scene/controller/HierarchySpeedTest.java

 import com.ferox.scene.Camera;
 import com.ferox.scene.Renderable;
 import com.ferox.scene.Transform;
+import com.ferox.scene.controller.light.LightGroupController;
 import com.ferox.util.Bag;
 import com.ferox.util.geom.Box;
 import com.lhkbob.entreri.Controller;
         Controller frustumUpdate = new CameraController();
         Controller indexBuilder = new SpatialIndexController(new QuadTree<Entity>(new AxisAlignedBox(new Vector3(-150, -10, -150), new Vector3(150, 10, 150)), 6));
         Controller pvsComputer = new VisibilityController();
+        Controller lightGroups = new LightGroupController();
         Controller render = new RenderController();
         
         Map<String, Controller> controllers = new HashMap<String, Controller>();
         controllers.put("frustum", frustumUpdate);
         controllers.put("index-build", indexBuilder);
         controllers.put("pvs", pvsComputer);
+        controllers.put("lights", lightGroups);
         controllers.put("render", render);
         Map<String, Long> times = new HashMap<String, Long>();
         
         system.getControllerManager().addController(frustumUpdate);
         system.getControllerManager().addController(indexBuilder);
         system.getControllerManager().addController(pvsComputer);
+        system.getControllerManager().addController(lightGroups);
         system.getControllerManager().addController(render);
 
         

File ferox-scene/src/main/java/com/ferox/scene/controller/light/LightGroupController.java

 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 
+import com.ferox.math.AxisAlignedBox;
+import com.ferox.math.Matrix4;
+import com.ferox.scene.AmbientLight;
 import com.ferox.scene.Camera;
+import com.ferox.scene.DirectionLight;
+import com.ferox.scene.InfluenceRegion;
+import com.ferox.scene.Influences;
+import com.ferox.scene.Light;
+import com.ferox.scene.PointLight;
 import com.ferox.scene.Renderable;
+import com.ferox.scene.SpotLight;
+import com.ferox.scene.Transform;
 import com.ferox.scene.controller.PVSResult;
 import com.ferox.util.Bag;
+import com.lhkbob.entreri.Component;
+import com.lhkbob.entreri.ComponentIterator;
 import com.lhkbob.entreri.Entity;
 import com.lhkbob.entreri.EntitySystem;
 import com.lhkbob.entreri.Result;
 import com.lhkbob.entreri.SimpleController;
+import com.lhkbob.entreri.TypeId;
 import com.lhkbob.entreri.property.IntProperty;
 
 public class LightGroupController extends SimpleController {
-    // FIXME add a way to register new light influence algorithms
-    // FIXME: come up with a good name for light influence algorithms
+    // read-only so threadsafe
+    private static final Matrix4 DEFAULT_MAT = new Matrix4().setIdentity();
+    private static final AxisAlignedBox DEFAULT_AABB = new AxisAlignedBox();
+    
     private IntProperty assignments;
     private List<Bag<Entity>> allVisibleSets;
     
+    @SuppressWarnings("rawtypes")
+    private final Map<TypeId, LightInfluence> lightInfluences;
+    
+    @SuppressWarnings("rawtypes")
+    public LightGroupController() {
+        lightInfluences = new HashMap<TypeId, LightInfluence>();
+        
+        // register default algorithms for known light types
+        registerInfluence(AmbientLight.ID, new AmbientLightInfluence());
+        registerInfluence(DirectionLight.ID, new DirectionLightInfluence());
+        registerInfluence(PointLight.ID, new PointLightInfluence());
+        registerInfluence(SpotLight.ID, new SpotLightInfluence());
+    }
+    
+    public <T extends Light<T>> void registerInfluence(TypeId<T> type, LightInfluence<T> algorithm) {
+        if (type == null || algorithm == null)
+            throw new NullPointerException("Arguments cannot be null");
+        lightInfluences.put(type, algorithm);
+    }
+    
+    /*
+     * This method makes excessive use of raw types, but it is safe given that
+     * TypeIds are used correctly. Java's type inference is just not good enough
+     * to keep track of it.
+     */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
     @Override
     public void process(double dt) {
-        // 1. collect all lights into an array
-        // 2. setup up group map and reusable bit set
-        //    a. group map is bitset->integer, paired with a list of
-        //        set<light> with indices == appr. value in map
+        // collect all lights into a list for later
+        Map<TypeId, Light> lightAccessors = new HashMap<TypeId, Light>();
+        List<Component> allLights = new ArrayList<Component>();
+        for (TypeId lightType: lightInfluences.keySet()) {
+            Light light = getEntitySystem().createDataInstance(lightType);
+            ComponentIterator it = new ComponentIterator(getEntitySystem());
+            it.addRequired(light);
+            
+            while(it.next()) {
+                // FIXME: is it faster to add an Iterator over Component<?> if
+                // that's what we really need
+                allLights.add(light.getComponent());
+            }
+            lightAccessors.put(lightType, light);
+        }
         
+        int numLights = allLights.size();
+        int groupId = 0;
+        Map<BitSet, Integer> groups = new HashMap<BitSet, Integer>();
+        
+        // instances reused for each iteration
+        Influences influenceSet = getEntitySystem().createDataInstance(Influences.ID);
+        InfluenceRegion influenceAABB = getEntitySystem().createDataInstance(InfluenceRegion.ID);
+        Transform transform = getEntitySystem().createDataInstance(Transform.ID);
+        Renderable renderable = getEntitySystem().createDataInstance(Renderable.ID);
+        BitSet entityLights = new BitSet(numLights);
+        
+        // process every visible entity
         for (Bag<Entity> pvs: allVisibleSets) {
-            // 3. process all entities in pvs and test for influence
-            //    b. clear bit set
-            //    a. each light influenced is put into bit set
-            // 4. add entity to group, and create new group if bitset was
-            //    not seen before
+            for (Entity entity: pvs) {
+                // get matrix and bounds for entity
+                AxisAlignedBox bounds = (entity.get(renderable) ? renderable.getWorldBounds() : DEFAULT_AABB);
+                
+                // check if we've already processed this entity in another pvs
+                if (assignments.get(renderable.getIndex(), 0) >= 0) {
+                    continue;
+                }
+                
+                // reset light influences for this entity
+                entityLights.clear();
+                // accumulate influencing lights into bit set
+                for (int i = 0; i < numLights; i++) {
+                    Component light = allLights.get(i);
+                    Entity lightEntity = light.getEntity();
+                    
+                    // check influence region on light
+                    if (lightEntity.get(influenceAABB)) {
+                        if (influenceAABB.isNegated()) {
+                            // skip light if entity is contained entirely in light bounds
+                            if (influenceAABB.getBounds().contains(bounds))
+                                continue;
+                        } else {
+                            // skip light if entity does not intersect light bounds
+                            if (!influenceAABB.getBounds().intersects(bounds))
+                                continue;
+                        }
+                    }
+                    
+                    // check influence set of light
+                    if (lightEntity.get(influenceSet)) {
+                        if (!influenceSet.canInfluence(entity))
+                            continue;
+                    }
+                    
+                    // use light influence algorithm for last check
+                    Light accessor = lightAccessors.get(light.getTypeId());
+                    accessor.set(light);
+                    Matrix4 matrix = (lightEntity.get(transform) ? transform.getMatrix() : DEFAULT_MAT);
+
+                    if (lightInfluences.get(light.getTypeId()).influences(accessor, matrix, bounds)) {
+                        // passed the last check, so the entity is influence by the ith light
+                        entityLights.set(i);
+                    }
+                }
+                
+                // light influence bit set is complete for the entity
+                Integer lightGroup = groups.get(entityLights);
+                if (lightGroup == null) {
+                    // new group encountered
+                    lightGroup = groupId++;
+                    groups.put((BitSet) entityLights.clone(), lightGroup);
+                }
+                
+                // assign group to entity
+                assignments.set(lightGroup.intValue(), renderable.getIndex(), 0);
+            }
         }
-        // 5. list of groups into an array and push the result (is necessary?
-        //    maybe we should just update the result defn.)
+        
+        // convert computed groups into LightGroupResult
+        Set<Component<? extends Light<?>>>[] finalGroups = new Set[groups.size()];
+        for (Entry<BitSet, Integer> group: groups.entrySet()) {
+            BitSet groupAsBitSet = group.getKey();
+            Set<Component<? extends Light<?>>> lightsInGroup = new HashSet<Component<? extends Light<?>>>();
+            for (int i = groupAsBitSet.nextSetBit(0); i >= 0; i = groupAsBitSet.nextSetBit(i+1)) {
+                lightsInGroup.add(allLights.get(i));
+            }
+            
+            // store in array
+            finalGroups[group.getValue()] = Collections.unmodifiableSet(lightsInGroup);
+        }
+        
+        getEntitySystem().getControllerManager().report(new LightGroupResult(finalGroups, assignments));
     }
     
     @Override

File ferox-scene/src/main/java/com/ferox/scene/controller/light/PointLightInfluence.java

 import com.ferox.math.AxisAlignedBox;
 import com.ferox.math.Const;
 import com.ferox.math.Matrix4;
+import com.ferox.math.Vector3;
 import com.ferox.scene.PointLight;
 import com.lhkbob.entreri.TypeId;
 
 public class PointLightInfluence implements LightInfluence<PointLight> {
+    private final Vector3 lightPos = new Vector3();
+    private final Vector3 objectPos = new Vector3();
+    
     @Override
     public boolean influences(PointLight light, @Const Matrix4 lightTransform, @Const AxisAlignedBox bounds) {
         if (light.getFalloffDistance() <= 0.0) {
         } else {
             // make sure the bounds intersects with the bounding sphere centered
             // on the light with a radius equal to the falloff distance
-            // FIXME
-            return false; 
+            lightPos.set(lightTransform.m03, lightTransform.m13, lightTransform.m23)
+                    .add(light.getPosition());
+            
+            // center of aabb
+            objectPos.add(bounds.min, bounds.max).scale(0.5);
+            // updated to direction vector from light to center
+            objectPos.sub(lightPos);
+            // compute near extent in place
+            objectPos.nearExtent(bounds, objectPos);
+            
+            // distance between lightPos and objectPos is distance of light
+            // to closest point on the entity bounds
+            return objectPos.distanceSquared(lightPos) <= light.getFalloffDistance() * light.getFalloffDistance();
         }
     }
 

File ferox-scene/src/main/java/com/ferox/scene/controller/light/SpotLightInfluence.java

 package com.ferox.scene.controller.light;
 
 import com.ferox.math.AxisAlignedBox;
+import com.ferox.math.Const;
+import com.ferox.math.Matrix4;
 import com.ferox.scene.SpotLight;
 import com.lhkbob.entreri.TypeId;
 
 public class SpotLightInfluence implements LightInfluence<SpotLight> {
 
     @Override
-    public boolean influences(SpotLight light, AxisAlignedBox bounds) {
+    public boolean influences(SpotLight light, @Const Matrix4 lightTransform, @Const AxisAlignedBox bounds) {
         // FIXME: make sure the bounds are within the cone of light.
         // FIXME: should I do distance or cone check first? I feel like it should
         // be the cone check since that will exclude more