Commits

Michael Ludwig committed 0cbd591

Improve ashikhmin demo to use true environment lighting with correct per-pixel glossiness.

  • Participants
  • Parent commits a834d30

Comments (0)

Files changed (23)

ferox-demos/src/main/java/com/ferox/physics/ConvexHullTest.java

+package com.ferox.physics;
+
+import com.ferox.input.KeyEvent;
+import com.ferox.input.logic.Action;
+import com.ferox.input.logic.InputManager;
+import com.ferox.input.logic.InputState;
+import com.ferox.input.logic.Predicates;
+import com.ferox.math.AxisAlignedBox;
+import com.ferox.math.Vector3;
+import com.ferox.physics.collision.shape.ConvexHull;
+import com.ferox.renderer.*;
+import com.ferox.renderer.geom.Geometry;
+import com.ferox.scene.Renderable;
+import com.lhkbob.entreri.Entity;
+
+import java.util.*;
+
+/**
+ *
+ */
+public class ConvexHullTest extends PhysicsApplicationStub {
+    private Geometry fastHull;
+
+    private List<Geometry> rotation;
+    private int selectedGeometry;
+
+    private Entity entity;
+
+    public static void main(String[] args) {
+        new ConvexHullTest().run();
+    }
+
+    @Override
+    protected void installInputHandlers(InputManager io) {
+        super.installInputHandlers(io);
+        io.on(Predicates.keyPress(KeyEvent.KeyCode.SPACE)).trigger(new Action() {
+            @Override
+            public void perform(InputState prev, InputState next) {
+                selectedGeometry = (selectedGeometry + 1) % rotation.size();
+                entity.get(Renderable.class).setGeometry(rotation.get(selectedGeometry));
+            }
+        });
+    }
+
+    @Override
+    protected void init(OnscreenSurface surface) {
+        super.init(surface);
+
+        rotation = new ArrayList<>();
+
+        long now = System.nanoTime();
+        List<Vector3> pointCloud = createPointCloud(10000);
+        System.out.printf("Creation time: %.4f\n", (System.nanoTime() - now) / 1e6);
+        //        Set<Vector3> divideHull = divideAndWrap(pointCloud, 0);
+        now = System.nanoTime();
+        Set<Vector3> divideHull = ConvexHull.construct(pointCloud).getPoints();
+        System.out.printf("Convexify time: %.4f\n", (System.nanoTime() - now) / 1e6);
+
+        //        now = System.nanoTime();
+        //        Set<Vector3> giftWrapHull = giftWrap(pointCloud);
+        //        System.out.printf("gift-wrap hull size: %d compute time: %.3f ms\n", giftWrapHull.size(),
+        //                          (System.nanoTime() - now) / 1e6);
+        //
+        //        now = System.nanoTime();
+        //        Set<Vector3> myGiftWrapHull = myGiftWrap(pointCloud);
+        //        System.out.printf("my-gift-wrap hull size: %d compute time: %.3f ms\n", myGiftWrapHull.size(),
+        //                          (System.nanoTime() - now) / 1e6);
+
+        rotation.add(makeFromPoints(pointCloud));
+        rotation.add(makeFromPoints(divideHull));
+        //        rotation.add(makeFromPoints(giftWrapHull));
+        //        rotation.add(makeFromPoints(myGiftWrapHull));
+
+        entity = system.addEntity();
+        selectedGeometry = 0;
+        entity.add(Renderable.class).setGeometry(rotation.get(0));
+    }
+
+    private List<Vector3> createPointCloud(int size) {
+        //        Set<Vector3> p = new HashSet<>(size);
+        //        for (int i = 0; i < 10; i++) {
+        //            double cx = Math.random() * 16 - 8;
+        //            double cy = Math.random() * 16 - 8;
+        //            double cz = Math.random() * 16 - 8;
+        //
+        //            double s = Math.random() * 8;
+        //            for (int j = 0; j < size; j++) {
+        //                p.add(new Vector3(cx + Math.random() * 2 * s - s, cy + Math.random() * 2 * s - s,
+        //                                  cz + Math.random() * 2 * s - s));
+        //            }
+        //        }
+        //                        for (int i = 0; i < size; i++) {
+        //                                        p.add(new Vector3(Math.random() * 16 - 8, Math.random() * 16 - 8, Math.random() * 16 - 8));
+        //                            p.add(new Vector3(Math.random() * 16 - 8, 0, Math.random() * 16 - 8f));
+        //                        }
+
+
+        //                Teapot.GeometryData d = new Teapot.GeometryData();
+        //                for (int i = 0; i < d.vertices.length; i += 3) {
+        //                    p.add(new Vector3(d.vertices[i], d.vertices[i + 1], d.vertices[i + 2]));
+        //                }
+        //        return new ArrayList<>(p);
+        return PLYReader.readVertices("/Users/mludwig/Desktop/teapot-tris.ply");
+    }
+
+    private Set<Vector3> divideAndWrap(List<Vector3> points, int depth) {
+        Set<Vector3> result;
+        long now = System.nanoTime();
+        int intermediate;
+
+        if (points.size() < 10000) {
+            intermediate = points.size();
+            result = myGiftWrap(points);
+        } else {
+
+            int numDivides = 8;
+            List[] divides = new List[numDivides];
+            int i = 0;
+            for (Vector3 v : points) {
+                //                int d = (int) (Math.random() * numDivides);
+                int d = (int) (i / (double) points.size() * numDivides);
+                if (divides[d] == null) {
+                    divides[d] = new ArrayList();
+                }
+                divides[d].add(v);
+                i++;
+            }
+
+            List<Vector3> merged = new ArrayList<>();
+            for (List d : divides) {
+                Set<Vector3> s = divideAndWrap(d, depth + 1);
+                merged.addAll(s);
+            }
+            intermediate = merged.size();
+            result = myGiftWrap(merged);
+        }
+        System.out.printf("depth %d from %d -> %d -> %d in %.3f\n", depth, points.size(), intermediate,
+                          result.size(), (System.nanoTime() - now) / 1e6);
+        return result;
+    }
+
+    private Set<Vector3> myGiftWrap(List<Vector3> points) {
+        Set<Vector3> vs = new HashSet<>();
+        Queue<Edge> openEdges = new ArrayDeque<>();
+        Set<Edge> visitedEdges = new HashSet<>();
+
+        // clone in case we have a duplicate maximum (there's probably a more efficient way about this)
+        List<Vector3> workingPoints = new ArrayList<>(points);
+        int startingIndex = getOuterPoint(workingPoints);
+        int secondIndex = getFarthestAngledPoint(workingPoints, startingIndex, -1);
+
+        // add first edge
+        openEdges.add(new Edge(startingIndex, secondIndex));
+        while (!openEdges.isEmpty()) {
+            Edge e = openEdges.poll();
+            if (visitedEdges.add(e)) {
+                // record vertices on hull
+                // TODO this can be optimized if we use 16bit indices and or-shift them together to create a pair index
+                vs.add(workingPoints.get(e.index1));
+                vs.add(workingPoints.get(e.index2));
+
+                // expand graph
+                int nextVertex = getFarthestAngledPoint(workingPoints, e.index1, e.index2);
+
+                // some edges have already been maxified implicitly, so 'visit' them and add reverse edges
+                openEdges.add(new Edge(e.index2, e.index1));
+                if (visitedEdges.add(new Edge(e.index2, nextVertex))) {
+                    openEdges.add(new Edge(nextVertex, e.index2)); // reverse for the above
+                }
+                if (visitedEdges.add(new Edge(nextVertex, e.index1))) {
+                    openEdges.add(new Edge(e.index1, nextVertex)); // reverse for the above
+                }
+            }
+        }
+
+        return vs;
+    }
+
+    private int getFarthestAngledPoint(List<Vector3> points, int index1, int index2) {
+        Vector3 p1 = points.get(index1);
+
+        // if we're just starting out, offset p1 by a vector that will keep p2 in the hyperplane
+        // but be guaranteed out of the hull, because p1 will have the smallest x (unique) and if that's
+        // not true, it will have the smallest y or z
+        Vector3 p2 = (index2 < 0 ? new Vector3(0, 1, 1).sub(p1).scale(-1) : points.get(index2));
+
+        Vector3 edge = new Vector3().sub(p2, p1);
+
+        Vector3 currentNormal = new Vector3();
+        Vector3 candidateNormal = new Vector3();
+        Vector3 cross = new Vector3();
+        int candidateIndex = -1;
+
+        for (int i = 0; i < points.size(); i++) {
+            if (i != index1 && i != index2) {
+                currentNormal.sub(points.get(i), p1).ortho(edge);
+                if (candidateIndex < 0 || cross.cross(currentNormal, candidateNormal).dot(edge) > 0) {
+                    candidateIndex = i;
+                    candidateNormal.set(currentNormal);
+                }
+            }
+        }
+        return candidateIndex;
+    }
+
+    private Vector3 getCenter(List<Vector3> points) {
+        Vector3 min = new Vector3(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY,
+                                  Double.POSITIVE_INFINITY);
+        Vector3 max = new Vector3(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY,
+                                  Double.NEGATIVE_INFINITY);
+
+        for (Vector3 v : points) {
+            // accumulate outer bounds
+            min.x = Math.min(min.x, v.x);
+            min.y = Math.min(min.y, v.y);
+            min.z = Math.min(min.z, v.z);
+            max.x = Math.max(max.x, v.x);
+            max.y = Math.max(max.y, v.y);
+            max.z = Math.max(max.z, v.z);
+        }
+
+        return new Vector3().add(min, max).scale(0.5);
+    }
+
+    private int getOuterPoint(List<Vector3> points) {
+        Vector3 outer = null;
+        int outerIndex = -1;
+
+        int currentIndex = 0;
+        Iterator<Vector3> it = points.iterator();
+        while (it.hasNext()) {
+            Vector3 v = it.next();
+
+            if (outer == null || v.x < outer.x) {
+                outer = v;
+                outerIndex = currentIndex;
+            } else if (v.x == outer.x) {
+                if (v.y < outer.y) {
+                    outer = v;
+                    outerIndex = currentIndex;
+                } else if (v.y == outer.y) {
+                    if (v.z < outer.z) {
+                        outer = v;
+                        outerIndex = currentIndex;
+                    } else if (v.z == outer.z) {
+                        // duplicate vertex remove it
+                        it.remove();
+                    }
+                }
+            }
+
+            currentIndex++;
+        }
+        return outerIndex;
+    }
+
+    // FIXME this implementation only works for non-planar points
+    private Set<Vector3> giftWrap(List<Vector3> points) {
+        Set<Vector3> vs = new HashSet<>();
+        Queue<Edge> openEdges = new ArrayDeque<>();
+        Set<Edge> createdEdges = new HashSet<>();
+
+        int index1 = lower(points);
+        int index2 = getNextPoint(points, index1, -1);
+        addEdge(index1, index2, createdEdges, openEdges);
+
+        while (!openEdges.isEmpty()) {
+            Edge p = openEdges.poll();
+            if (!createdEdges.contains(p)) {
+                int index3 = getNextPoint(points, p.index1, p.index2);
+
+                vs.add(points.get(p.index1));
+                vs.add(points.get(p.index2));
+                vs.add(points.get(index3));
+
+                addEdge(p.index1, p.index2, createdEdges, openEdges);
+                addEdge(p.index2, index3, createdEdges, openEdges);
+                addEdge(index3, p.index1, createdEdges, openEdges);
+            }
+        }
+
+        return vs;
+    }
+
+    private void addEdge(int index1, int index2, Set<Edge> created, Queue<Edge> open) {
+        Edge e = new Edge(index1, index2);
+        Edge sym = new Edge(index2, index1);
+
+        created.add(e);
+        if (!created.contains(sym)) {
+            open.add(sym);
+        }
+    }
+
+    private int getNextPoint(List<Vector3> points, int index1, int index2) {
+        Vector3 p1 = points.get(index1);
+
+        // FIXME why does this vector work for index = -1?
+        // it's because (1, 1, 0) creates a new point that is still in the hyperplane formed from (0, 0, 1)
+        // and p1
+        Vector3 p2 = (index2 < 0 ? new Vector3(0, 1, 1).sub(p1).scale(-1) : points.get(index2));
+
+        Vector3 edge = new Vector3().sub(p2, p1).normalize();
+
+        Vector3 work1 = new Vector3();
+        Vector3 work2 = new Vector3();
+        Vector3 work3 = new Vector3();
+        int candidateIndex = -1;
+        // FIXME how does this loop select the valid next point?
+        for (int i = 0; i < points.size(); i++) {
+            if (i != index1 && i != index2) {
+                if (candidateIndex == -1) {
+                    candidateIndex = i;
+                } else {
+                    // v = work1
+                    work1.sub(points.get(i), p1);
+                    work1.ortho(edge);
+                    //                    work1.sub(work2.project(work1, edge));
+
+                    // candidate = work2
+                    work2.sub(points.get(candidateIndex), p1);
+                    work2.ortho(edge);
+                    //                    work2.sub(work3.project(work2, edge));
+
+                    // cross = work3
+                    work3.cross(work2, work1);
+                    if (work3.dot(edge) > 0) {
+                        candidateIndex = i;
+                    }
+                }
+            }
+        }
+        return candidateIndex;
+    }
+
+    private int lower(List<Vector3> p) {
+        int index = 0;
+        for (int i = 1; i < p.size(); i++) {
+            if (p.get(i).x < p.get(index).x) {
+                index = i;
+            } else if (p.get(i).z == p.get(index).z) {
+                if (p.get(i).y < p.get(index).y) {
+                    index = i;
+                } else if (p.get(i).x < p.get(index).x) {
+                    index = i;
+                }
+                // else duplicate point, there's a problem
+            }
+        }
+        return index;
+    }
+
+    private static class Edge {
+        private final int index1;
+        private final int index2;
+
+        public Edge(int i1, int i2) {
+            index1 = i1;
+            index2 = i2;
+        }
+
+        @Override
+        public int hashCode() {
+            return index1 ^ index2;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof Edge)) {
+                return false;
+            }
+            Edge e = (Edge) o;
+            return e.index1 == index1 && e.index2 == index2;
+        }
+    }
+
+    private Geometry makeFromPoints(Collection<Vector3> points) {
+        float[] verts = new float[points.size() * 3];
+        int i = 0;
+        for (Vector3 v : points) {
+            verts[i * 3] = (float) v.x;
+            verts[i * 3 + 1] = (float) v.y;
+            verts[i * 3 + 2] = (float) v.z;
+            i++;
+        }
+
+        VertexBuffer vbo = getFramework().newVertexBuffer().from(verts).build();
+        final AxisAlignedBox aabb = new AxisAlignedBox(verts, 0, 0, points.size());
+        final VertexAttribute v = new VertexAttribute(vbo, 3);
+        return new Geometry() {
+            @Override
+            public AxisAlignedBox getBounds() {
+                return aabb;
+            }
+
+            @Override
+            public Renderer.PolygonType getPolygonType() {
+                return Renderer.PolygonType.POINTS;
+            }
+
+            @Override
+            public ElementBuffer getIndices() {
+                return null;
+            }
+
+            @Override
+            public int getIndexOffset() {
+                return 0;
+            }
+
+            @Override
+            public int getIndexCount() {
+                return v.getMaximumNumVertices();
+            }
+
+            @Override
+            public VertexAttribute getVertices() {
+                return v;
+            }
+
+            @Override
+            public VertexAttribute getNormals() {
+                return null;
+            }
+
+            @Override
+            public VertexAttribute getTextureCoordinates() {
+                return null;
+            }
+
+            @Override
+            public VertexAttribute getTangents() {
+                return null;
+            }
+        };
+    }
+
+    @Override
+    protected void renderFrame(OnscreenSurface surface) {
+        system.getScheduler().runOnCurrentThread(renderJob);
+    }
+}

ferox-demos/src/main/java/com/ferox/physics/GravityTest.java

 import com.ferox.math.Matrix4;
 import com.ferox.math.Vector3;
 import com.ferox.physics.collision.CollisionBody;
+import com.ferox.physics.collision.Shape;
+import com.ferox.physics.collision.shape.Box;
 import com.ferox.physics.dynamics.Gravity;
 import com.ferox.physics.dynamics.RigidBody;
 import com.ferox.renderer.OnscreenSurface;
 import com.ferox.renderer.geom.Geometry;
+import com.ferox.renderer.geom.Shapes;
 import com.ferox.scene.*;
 import com.lhkbob.entreri.Entity;
 
               .setMatrix(new Matrix4().set(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, .75 * BOUNDS, 0, 0, 0, 1));
 
         // shapes
-        Geometry geomShape1 = com.ferox.renderer.geom.Box.create(getFramework(), 2 + 2 * MARGIN);
-        com.ferox.physics.collision.Shape physShape1 = new com.ferox.physics.collision.shape.Box(2, 2, 2);
+        Geometry geomShape1 = Shapes.createBox(getFramework(), 2 + 2 * MARGIN);
+        Shape physShape1 = new Box(2, 2, 2);
 
-        Geometry geomShape2 = com.ferox.renderer.geom.Box.create(getFramework(), 2 + 2 * MARGIN);
-        com.ferox.physics.collision.Shape physShape2 = new com.ferox.physics.collision.shape.Box(2, 2, 2);
+        Geometry geomShape2 = Shapes.createBox(getFramework(), 2 + 2 * MARGIN);
+        Shape physShape2 = new Box(2, 2, 2);
 
         physShape1.setMargin(MARGIN);
         physShape2.setMargin(MARGIN);
         // a point light
         Entity point = system.addEntity();
         point.add(Light.class).setColor(new ColorRGB(0.5, 0.5, 0.5)).setCutoffAngle(180.0);
-        point.add(Transform.class).setMatrix(
-                new Matrix4().set(1, 0, 0, BOUNDS / 2, 0, 1, 0, BOUNDS / 2, 0, 0, 1, BOUNDS / 2, 0, 0, 0, 1));
+        point.add(Transform.class).setMatrix(new Matrix4()
+                                                     .set(1, 0, 0, BOUNDS / 2, 0, 1, 0, BOUNDS / 2, 0, 0, 1,
+                                                          BOUNDS / 2, 0, 0, 0, 1));
     }
 
     public static void main(String[] args) throws Exception {

ferox-demos/src/main/java/com/ferox/physics/PLYReader.java

+package com.ferox.physics;
+
+import com.ferox.math.Vector3;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ */
+public class PLYReader {
+    private static enum State {
+        PREAMBLE,
+        HEADER,
+        VERTICES,
+        FACES,
+        POST_FACE
+    }
+
+    public static List<Vector3> readVertices(String file) {
+        try (BufferedReader in = new BufferedReader(new FileReader(file))) {
+            int vertexCount = -1;
+            int xPropertyIndex = -1;
+            int yPropertyIndex = -1;
+            int zPropertyIndex = -1;
+            int[] faces = null;
+            List<Vector3> vertices = new ArrayList<>();
+
+            int propertyCount = 0;
+            int faceCount = 0;
+            State state = State.PREAMBLE;
+
+            String line;
+            while ((line = in.readLine()) != null) {
+                switch (state) {
+                case PREAMBLE:
+                    if (line.trim().equalsIgnoreCase("ply")) {
+                        state = State.HEADER;
+                    }
+                    break;
+                case HEADER:
+                    if (line.trim().equalsIgnoreCase("end_header")) {
+                        state = State.VERTICES;
+                    } else {
+                        String[] parts = line.trim().split("\\s+");
+                        if (parts.length == 3 && parts[0].equalsIgnoreCase("element") &&
+                            parts[1].equalsIgnoreCase("vertex")) {
+                            vertexCount = Integer.parseInt(parts[2]);
+                        } else if (parts.length == 3 && parts[0].equalsIgnoreCase("element") &&
+                                   parts[1].equalsIgnoreCase("face")) {
+                            int count = Integer.parseInt(parts[2]);
+                            faces = new int[count * 3];
+                        } else if (parts.length >= 1 && parts[0].equalsIgnoreCase("property")) {
+                            if (parts.length == 3 && parts[1].equalsIgnoreCase("float")) {
+                                if (parts[2].equalsIgnoreCase("x")) {
+                                    xPropertyIndex = propertyCount;
+                                } else if (parts[2].equalsIgnoreCase("y")) {
+                                    yPropertyIndex = propertyCount;
+                                } else if (parts[2].equalsIgnoreCase("z")) {
+                                    zPropertyIndex = propertyCount;
+                                }
+                            }
+                            propertyCount++;
+                        }
+                    }
+                    break;
+                case VERTICES:
+                    String[] parts = line.trim().split("\\s+");
+                    vertices.add(new Vector3(.2 * Float.parseFloat(parts[xPropertyIndex]),
+                                             .2 * Float.parseFloat(parts[yPropertyIndex]),
+                                             .2 * Float.parseFloat(parts[zPropertyIndex])));
+                    if (vertices.size() >= vertexCount) {
+                        state = State.FACES;
+                    }
+                    break;
+                case FACES:
+                    String[] ind = line.trim().split("\\s+");
+                    if (ind.length > 0 && ind[0].equals("3")) {
+                        faces[faceCount * 3] = Integer.parseInt(ind[1]);
+                        faces[faceCount * 3 + 1] = Integer.parseInt(ind[2]);
+                        faces[faceCount * 3 + 2] = Integer.parseInt(ind[3]);
+                        faceCount++;
+                    }
+                    break;
+                }
+            }
+
+            return vertices;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

ferox-demos/src/main/java/com/ferox/physics/PhysicsApplicationStub.java

     protected static final int BOUNDS = 50;
     protected static final double MARGIN = .05;
 
-    protected static final AxisAlignedBox worldBounds = new AxisAlignedBox(
-            new Vector3(-2 * BOUNDS - 1, -2 * BOUNDS - 1, -2 * BOUNDS - 1),
-            new Vector3(2 * BOUNDS + 1, 2 * BOUNDS + 1, 2 * BOUNDS + 1));
+    protected static final AxisAlignedBox worldBounds = new AxisAlignedBox(new Vector3(-2 * BOUNDS - 1,
+                                                                                       -2 * BOUNDS - 1,
+                                                                                       -2 * BOUNDS - 1),
+                                                                           new Vector3(2 * BOUNDS + 1,
+                                                                                       2 * BOUNDS + 1,
+                                                                                       2 * BOUNDS + 1));
 
     // positive half-circle
-    private static final double MAX_THETA = Math.PI;
-    private static final double MIN_THETA = 0;
+    private static final double MAX_THETA = 10000;
+    private static final double MIN_THETA = -10000;
 
     // positive octant
     private static final double MAX_PHI = Math.PI / 2.0;
     private static final double MIN_PHI = Math.PI / 12.0;
 
-    private static final double MIN_ZOOM = 1.0;
+    private static final double MIN_ZOOM = .2;
     private static final double MAX_ZOOM = BOUNDS;
 
     private static final double ANGLE_RATE = Math.PI / 4.0;
     private double zoom; // distance from origin
 
     protected final EntitySystem system;
-    private Job physicsJob;
-    private Job renderJob;
+    protected Job physicsJob;
+    protected Job renderJob;
 
     public PhysicsApplicationStub() {
         super(Framework.Factory.create());
         zoom = (MAX_ZOOM + MIN_ZOOM) / 2.0;
 
         camera = system.addEntity();
-        camera.add(Camera.class).setSurface(surface).setZDistances(1.0, BOUNDS);
+        camera.add(Camera.class).setSurface(surface).setZDistances(.1, BOUNDS);
         updateCameraOrientation();
     }
 

ferox-demos/src/main/java/com/ferox/physics/PhysicsTest.java

 import com.ferox.math.Vector3;
 import com.ferox.math.Vector4;
 import com.ferox.physics.collision.CollisionBody;
+import com.ferox.physics.collision.Shape;
+import com.ferox.physics.collision.shape.Box;
 import com.ferox.physics.dynamics.RigidBody;
 import com.ferox.renderer.Framework;
 import com.ferox.renderer.OnscreenSurface;
 import com.ferox.renderer.Renderer;
-import com.ferox.renderer.geom.Box;
 import com.ferox.renderer.geom.Geometry;
+import com.ferox.renderer.geom.Shapes;
 import com.ferox.scene.*;
 import com.ferox.scene.AtmosphericFog.Falloff;
 import com.lhkbob.entreri.Entity;
         super.init(surface);
 
         // shapes
-        Geometry box = Box.create(getFramework(), 2 + 2 * MARGIN);
-        //        Geometry sphere = Sphere.create(1 + MARGIN, 32, COMPILE_TYPE);
+        Geometry box = Shapes.createBox(getFramework(), 2 + 2 * MARGIN);
+        //                Geometry sphere = Shapes.createSphere(getFramework(), 1 + MARGIN, 32);
+        //        Geometry box = Shapes.createCylinder(getFramework(), 1 + MARGIN, 2 + 2 * MARGIN);
 
-        com.ferox.physics.collision.Shape boxShape = new com.ferox.physics.collision.shape.Box(2, 2, 2);
-        //        com.ferox.physics.collision.Shape sphereShape = new com.ferox.physics.collision.shape.Sphere(1);
+        Shape boxShape = new Box(2, 2, 2);
+        //        Shape sphereShape = new Sphere(1);
         boxShape.setMargin(MARGIN);
-        //        sphereShape.setMargin(MARGIN);
+        //                sphereShape.setMargin(MARGIN);
 
         double startX = START_POS_X - NUM_X / 2.0;
         double startY = START_POS_Y;
                      .setDrawStyle(Renderer.DrawStyle.SOLID, Renderer.DrawStyle.NONE);
                     e.add(LambertianDiffuseModel.class).setColor(color);
 
-                    e.add(CollisionBody.class).setShape(physShape).setTransform(
-                            new Matrix4().setIdentity().setCol(3, new Vector4((SCALE_X + 2 * MARGIN) * x +
-                                                                              rx +
-                                                                              startX,
-                                                                              (SCALE_Y + 2 * MARGIN) * y +
-                                                                              ry +
-                                                                              startY,
-                                                                              (SCALE_Z + 2 * MARGIN) * z +
-                                                                              rz +
-                                                                              startZ, 1)));
+                    e.add(CollisionBody.class).setShape(physShape).setTransform(new Matrix4().setIdentity()
+                                                                                             .setCol(3,
+                                                                                                     new Vector4((SCALE_X +
+                                                                                                                  2 *
+                                                                                                                  MARGIN) *
+                                                                                                                 x +
+                                                                                                                 rx +
+                                                                                                                 startX,
+                                                                                                                 (SCALE_Y +
+                                                                                                                  2 *
+                                                                                                                  MARGIN) *
+                                                                                                                 y +
+                                                                                                                 ry +
+                                                                                                                 startY,
+                                                                                                                 (SCALE_Z +
+                                                                                                                  2 *
+                                                                                                                  MARGIN) *
+                                                                                                                 z +
+                                                                                                                 rz +
+                                                                                                                 startZ,
+                                                                                                                 1)));
                     e.add(RigidBody.class).setMass(1.0);
                 }
             }
         }
 
         // some walls
-        Geometry bottomWall = Box.create(getFramework(), BOUNDS + 2 * MARGIN, 1, BOUNDS + 2 * MARGIN);
+        Geometry bottomWall = Shapes.createBox(getFramework(), BOUNDS + 2 * MARGIN, 1, BOUNDS + 2 * MARGIN);
         Entity wall = system.addEntity();
         wall.add(Renderable.class).setGeometry(bottomWall)
             .setDrawStyle(Renderer.DrawStyle.SOLID, Renderer.DrawStyle.NONE);
         // a point light
         Entity point = system.addEntity();
         point.add(Light.class).setColor(new ColorRGB(0.5, 0.5, 0.5)).setCutoffAngle(180.0);
-        point.get(Transform.class).setMatrix(
-                new Matrix4().setIdentity().setCol(3, new Vector4(BOUNDS / 2, BOUNDS / 2, BOUNDS / 2, 1)));
+        point.get(Transform.class).setMatrix(new Matrix4().setIdentity().setCol(3, new Vector4(BOUNDS / 2,
+                                                                                               BOUNDS / 2,
+                                                                                               BOUNDS / 2,
+                                                                                               1)));
 
         // a directed light, which casts shadows
         Entity inf = system.addEntity();
-        inf.add(Light.class).setColor(new ColorRGB(1, 1, 1)).setShadowCaster(true)
-           .setCutoffAngle(Double.NaN);
+        inf.add(Light.class).setColor(new ColorRGB(1, 1, 1)).setShadowCaster(true).setCutoffAngle(Double.NaN);
         inf.get(Transform.class)
            .setMatrix(new Matrix4().lookAt(new Vector3(), new Vector3(15, 15, 15), new Vector3(0, 1, 0)));
     }

ferox-demos/src/main/java/com/ferox/renderer/AshikhminDeferredShader.java

+package com.ferox.renderer;
+
+import com.ferox.input.KeyEvent;
+import com.ferox.input.MouseEvent;
+import com.ferox.input.logic.Action;
+import com.ferox.input.logic.InputManager;
+import com.ferox.input.logic.InputState;
+import com.ferox.math.Matrix4;
+import com.ferox.math.Vector3;
+import com.ferox.math.Vector4;
+import com.ferox.math.bounds.Frustum;
+import com.ferox.renderer.builder.Builder;
+import com.ferox.renderer.builder.DepthMap2DBuilder;
+import com.ferox.renderer.builder.ShaderBuilder;
+import com.ferox.renderer.builder.Texture2DBuilder;
+import com.ferox.renderer.geom.Geometry;
+import com.ferox.renderer.geom.Shapes;
+import com.ferox.renderer.loader.RadianceImageLoader;
+import com.ferox.renderer.loader.TextureLoader;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.concurrent.CancellationException;
+
+import static com.ferox.input.logic.Predicates.*;
+
+/**
+ *
+ */
+public class AshikhminDeferredShader implements Task<Void> {
+    private static enum MoveState {
+        NONE,
+        CAMERA,
+        OBJECT,
+        LIGHT
+    }
+
+    private static final int WIDTH = 800;
+    private static final int HEIGHT = 800;
+
+    // framework
+    private final Framework framework;
+    private final OnscreenSurface window;
+    private final InputManager input;
+
+    // deferred rendering
+    private final Frustum fullscreenProjection;
+    private final Geometry fullscreenQuad;
+    private final Texture2D normalGBuffer; // RGBA32F
+    private final Texture2D tangentGBuffer; // RGBA32F
+    private final Texture2D shininessDiffuseRGGBuffer; // RGBA32F
+    private final DepthMap2D depthGBuffer; // DEPTH24
+    private final Texture2D specularDiffuseBGBuffer; // RGBA32F
+
+    private final TextureSurface gbuffer;
+
+    private final TextureSurface accumulateBuffer;
+    private final Texture2D accumulateDiff;
+    private final Texture2D accumulateSpec;
+
+    private final EnvironmentMap envMap;
+    private final TextureCubeMap envCubeMap;
+    private final TextureCubeMap envDiffMap;
+    private boolean showCubeMap = true;
+    private boolean showDiffMap = false;
+
+    private final Shader simpleShader;
+    private final Shader fillGbufferShader;
+    private final Shader specularGbufferShader;
+    private final Shader diffuseGbufferShader;
+    private final Shader finalShader;
+
+    // tone mapping
+    private volatile double exposure = 1.0 / 1000.0;
+    private volatile double sensitivity = 100;
+    private volatile double fstop = 2.8;
+    private volatile double gamma = 2.2;
+
+    // geometry
+    private final Geometry shape;
+
+    private final Geometry xAxis;
+    private final Geometry yAxis;
+    private final Geometry zAxis;
+    private boolean showAxis;
+
+    private final Geometry lightCube;
+    private boolean showLight;
+    private final Vector4 light;
+
+    private final Geometry envCube;
+
+    // camera controls
+    private final TrackBall modelTrackBall;
+    private final TrackBall viewTrackBall;
+    private final Frustum camera;
+    private double cameraDist;
+    private MoveState moveState;
+
+    private volatile boolean invalidateGbuffer;
+    private volatile int specularSamplesLeft;
+
+    // inpute textures
+    private volatile Sampler specularNormalTexA;
+    private volatile Sampler specularAlbedoTexA;
+    private volatile Sampler diffuseNormalTexA;
+    private volatile Sampler diffuseAlbedoTexA;
+    private volatile Sampler shininessTexA;
+
+    private volatile Sampler specularNormalTexB;
+    private volatile Sampler specularAlbedoTexB;
+    private volatile Sampler diffuseNormalTexB;
+    private volatile Sampler diffuseAlbedoTexB;
+    private volatile Sampler shininessTexB;
+
+    // blending
+    private volatile double normalAlpha;
+    private volatile double specularAlbedoAlpha;
+    private volatile double diffuseAlbedoAlpha;
+    private volatile double shininessAlpha;
+
+    // appearance tweaks
+    private volatile double shininessXScale;
+    private volatile double shininessYScale;
+    private volatile double texCoordAScale;
+    private volatile double texCoordBScale;
+
+    private final JFrame properties;
+
+    public static void main(String[] args) throws Exception {
+        Framework framework = Framework.Factory.create();
+        try {
+            new AshikhminDeferredShader(framework).run();
+        } finally {
+            framework.destroy();
+        }
+    }
+
+    private static ShaderBuilder loadShader(Framework framework, String root) throws Exception {
+        ShaderBuilder shaderBuilder = framework.newShader();
+
+        try (BufferedReader vertIn = new BufferedReader(new InputStreamReader(AshikhminDeferredShader.class
+                                                                                      .getResourceAsStream(root +
+                                                                                                           ".vert")))) {
+            StringBuilder sb = new StringBuilder();
+            String line;
+            while ((line = vertIn.readLine()) != null) {
+                sb.append(line).append('\n');
+            }
+            shaderBuilder.withVertexShader(sb.toString());
+        } catch (IOException e) {
+            throw new FrameworkException("Unable to load vertex shader", e);
+        }
+
+        try (BufferedReader fragIn = new BufferedReader(new InputStreamReader(AshikhminDeferredShader.class
+                                                                                      .getResourceAsStream(root +
+                                                                                                           ".frag")))) {
+            StringBuilder sb = new StringBuilder();
+            String line;
+            while ((line = fragIn.readLine()) != null) {
+                sb.append(line).append('\n');
+            }
+            shaderBuilder.withFragmentShader(sb.toString());
+        } catch (IOException e) {
+            throw new FrameworkException("Unable to load fragment shader", e);
+        }
+
+        return shaderBuilder;
+    }
+
+    public AshikhminDeferredShader(Framework f) throws Exception {
+        modelTrackBall = new TrackBall(false);
+        viewTrackBall = new TrackBall(true);
+
+        shininessXScale = 1.0;
+        shininessYScale = 1.0;
+        texCoordAScale = 1.0;
+        texCoordBScale = 1.0;
+
+        light = new Vector4(1, 1, 0, 1);
+        moveState = MoveState.NONE;
+
+        this.framework = f;
+        OnscreenSurfaceOptions opts = new OnscreenSurfaceOptions().withDepthBuffer(24).windowed(WIDTH, HEIGHT)
+                                                                  .fixedSize();
+        window = framework.createSurface(opts);
+        //        window.setVSyncEnabled(true);
+        window.setLocation(0, 0);
+
+        camera = new Frustum(60, window.getWidth() / (float) window.getHeight(), 0.1, 25);
+        cameraDist = 1.5;
+
+        Texture2DBuilder b = framework.newTexture2D().width(WIDTH).height(HEIGHT);
+        b.rgba().mipmap(0).from((float[]) null);
+        normalGBuffer = b.build();
+        b = framework.newTexture2D().width(WIDTH).height(HEIGHT);
+        b.rgba().mipmap(0).from((float[]) null);
+        tangentGBuffer = b.build();
+        b = framework.newTexture2D().width(WIDTH).height(HEIGHT);
+        b.rgba().mipmap(0).from((float[]) null);
+        shininessDiffuseRGGBuffer = b.build();
+        b = framework.newTexture2D().width(WIDTH).height(HEIGHT);
+        b.rgba().mipmap(0).from((float[]) null);
+        specularDiffuseBGBuffer = b.build();
+        DepthMap2DBuilder db = framework.newDepthMap2D().width(WIDTH).height(HEIGHT);
+        db.depth().mipmap(0).fromUnsignedNormalized((int[]) null);
+        depthGBuffer = db.build();
+
+        fullscreenQuad = Shapes.createRectangle(framework, 0, WIDTH, 0, HEIGHT);
+        fullscreenProjection = new Frustum(true, 0, WIDTH, 0, HEIGHT, -1.0, 1.0);
+
+        envMap = new EnvironmentMap(new File("/Users/mludwig/Desktop/grace_cross.hdr"));
+        envCubeMap = envMap.createEnvironmentMap(framework);
+        envDiffMap = envMap.createDiffuseMap(framework);
+        envCube = Shapes.createBox(framework, 6.0);
+
+        input = new InputManager();
+        input.attach(window);
+        input.on(keyPress(KeyEvent.KeyCode.ESCAPE)).trigger(new Action() {
+            @Override
+            public void perform(InputState prev, InputState next) {
+                framework.destroy();
+            }
+        });
+        input.on(or(mouseRelease(MouseEvent.MouseButton.LEFT), mouseRelease(MouseEvent.MouseButton.RIGHT)))
+             .trigger(new Action() {
+                 @Override
+                 public void perform(InputState prev, InputState next) {
+                     moveState = MoveState.NONE;
+                 }
+             });
+        input.on(and(and(mousePress(MouseEvent.MouseButton.LEFT), not(keyHeld(KeyEvent.KeyCode.LEFT_META))),
+                     not(keyHeld(KeyEvent.KeyCode.LEFT_CONTROL)))).trigger(new Action() {
+            @Override
+            public void perform(InputState prev, InputState next) {
+                moveState = MoveState.CAMERA;
+                viewTrackBall.startDrag(getNormalizedDeviceX(next.getMouseState().getX()),
+                                        getNormalizedDeviceY(next.getMouseState().getY()));
+            }
+        });
+        input.on(and(and(mousePress(MouseEvent.MouseButton.LEFT), keyHeld(KeyEvent.KeyCode.LEFT_META)),
+                     not(keyHeld(KeyEvent.KeyCode.LEFT_CONTROL)))).trigger(new Action() {
+            @Override
+            public void perform(InputState prev, InputState next) {
+                moveState = MoveState.LIGHT;
+            }
+        });
+        input.on(mouseMove(true)).trigger(new Action() {
+            @Override
+            public void perform(InputState prev, InputState next) {
+                if (moveState == MoveState.LIGHT) {
+                    Vector4 projectedLight = new Vector4();
+                    projectedLight.mul(camera.getViewMatrix(), light);
+                    projectedLight.mul(camera.getProjectionMatrix(), projectedLight);
+                    double w = projectedLight.w;
+                    projectedLight.scale(1.0 / w);
+
+                    projectedLight.x += (getNormalizedDeviceX(next.getMouseState().getX()) -
+                                         getNormalizedDeviceX(prev.getMouseState().getX()));
+                    projectedLight.y += (getNormalizedDeviceY(next.getMouseState().getY()) -
+                                         getNormalizedDeviceY(prev.getMouseState().getY()));
+
+                    Matrix4 inv = new Matrix4();
+                    light.mul(inv.inverse(camera.getProjectionMatrix()), projectedLight.scale(w));
+                    light.mul(inv.inverse(camera.getViewMatrix()), light);
+                } else if (moveState == MoveState.CAMERA) {
+                    viewTrackBall.drag(getNormalizedDeviceX(next.getMouseState().getX()),
+                                       getNormalizedDeviceY(next.getMouseState().getY()),
+                                       viewTrackBall.getRotation());
+                    updateCamera();
+
+                } else if (moveState == MoveState.OBJECT) {
+                    modelTrackBall.drag(getNormalizedDeviceX(next.getMouseState().getX()),
+                                        getNormalizedDeviceY(next.getMouseState().getY()),
+                                        viewTrackBall.getRotation());
+                    updateGBuffer();
+                }
+            }
+        });
+        input.on(or(mousePress(MouseEvent.MouseButton.RIGHT),
+                    and(mousePress(MouseEvent.MouseButton.LEFT), keyHeld(KeyEvent.KeyCode.LEFT_CONTROL))))
+             .trigger(new Action() {
+                 @Override
+                 public void perform(InputState prev, InputState next) {
+                     moveState = MoveState.OBJECT;
+                     modelTrackBall.startDrag(getNormalizedDeviceX(next.getMouseState().getX()),
+                                              getNormalizedDeviceY(next.getMouseState().getY()));
+                 }
+             });
+        input.on(forwardScroll()).trigger(new Action() {
+            @Override
+            public void perform(InputState prev, InputState next) {
+                cameraDist = Math.max(.2, cameraDist - 0.02);
+                updateCamera();
+            }
+        });
+        input.on(backwardScroll()).trigger(new Action() {
+            @Override
+            public void perform(InputState prev, InputState next) {
+                cameraDist = Math.min(20.0, cameraDist + 0.02);
+                updateCamera();
+            }
+        });
+        input.on(keyPress(KeyEvent.KeyCode.A)).trigger(new Action() {
+            @Override
+            public void perform(InputState prev, InputState next) {
+                showAxis = !showAxis;
+                showLight = !showLight;
+            }
+        });
+        input.on(keyPress(KeyEvent.KeyCode.E)).trigger(new Action() {
+            @Override
+            public void perform(InputState prev, InputState next) {
+                showCubeMap = !showCubeMap;
+            }
+        });
+        input.on(keyPress(KeyEvent.KeyCode.D)).trigger(new Action() {
+            @Override
+            public void perform(InputState prev, InputState next) {
+                showDiffMap = !showDiffMap;
+            }
+        });
+        input.on(keyPress(KeyEvent.KeyCode.S)).trigger(new Action() {
+            @Override
+            public void perform(InputState prev, InputState next) {
+                System.out.println("Samples left: " + specularSamplesLeft);
+            }
+        });
+
+        //        shape = Shapes.createSphere(framework, 0.5, 128);
+        shape = Shapes.createTeapot(framework);
+
+        showAxis = true;
+        xAxis = Shapes.createCylinder(framework, new Vector3(1, 0, 0), new Vector3(1, 0, 0), 0.01, 0.5, 4);
+        yAxis = Shapes.createCylinder(framework, new Vector3(0, 1, 0), new Vector3(0, 1, 0), 0.01, 0.5, 4);
+        zAxis = Shapes.createCylinder(framework, new Vector3(0, 0, 1), new Vector3(0, 0, 1), 0.01, 0.5, 4);
+
+        showLight = true;
+        lightCube = Shapes.createBox(framework, 0.1);
+
+        simpleShader = loadShader(framework, "simple").bindColorBuffer("fColor", 0).build();
+        specularGbufferShader = loadShader(framework, "ashik-specular").bindColorBuffer("fColor", 0).build();
+        diffuseGbufferShader = loadShader(framework, "ashik-diffuse").bindColorBuffer("fColor", 0).build();
+        finalShader = loadShader(framework, "final").bindColorBuffer("fColor", 0).build();
+        fillGbufferShader = loadShader(framework, "ashik-gbuffer").bindColorBuffer("fNormal", 0)
+                                                                  .bindColorBuffer("fTangent", 1)
+                                                                  .bindColorBuffer("fShininessXYDiffuseRG", 2)
+                                                                  .bindColorBuffer("fSpecularAlbedoDiffuseB",
+                                                                                   3).build();
+
+        TextureSurfaceOptions gOpts = new TextureSurfaceOptions().size(WIDTH, HEIGHT)
+                                                                 .colorBuffers(normalGBuffer
+                                                                                       .getRenderTarget(),
+                                                                               tangentGBuffer
+                                                                                       .getRenderTarget(),
+                                                                               shininessDiffuseRGGBuffer
+                                                                                       .getRenderTarget(),
+                                                                               specularDiffuseBGBuffer
+                                                                                       .getRenderTarget())
+                                                                 .depthBuffer(depthGBuffer.getRenderTarget());
+        gbuffer = framework.createSurface(gOpts);
+
+        b = framework.newTexture2D().width(WIDTH).height(HEIGHT);
+        b.rgb().mipmap(0).from((float[]) null);
+        accumulateDiff = b.build();
+        b = framework.newTexture2D().width(WIDTH).height(HEIGHT);
+        b.rgb().mipmap(0).from((float[]) null);
+        accumulateSpec = b.build();
+        gOpts = new TextureSurfaceOptions().size(WIDTH, HEIGHT)
+                                           .colorBuffers(accumulateDiff.getRenderTarget());
+        accumulateBuffer = framework.createSurface(gOpts);
+
+        properties = new JFrame("Properties");
+        properties.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+        JPanel pl = new JPanel();
+        GroupLayout layout = new GroupLayout(pl);
+        pl.setLayout(layout);
+        properties.add(pl);
+
+        JButton loadTexturesA = new JButton("Load Textures A");
+        final JLabel texLabelA = new JLabel("None");
+        loadTexturesA.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                JFileChooser fc = new JFileChooser("/Users/mludwig/Desktop/LLS");
+                fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+                if (fc.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
+                    String dir = fc.getSelectedFile().getAbsolutePath();
+                    loadTexturesA(dir);
+                    texLabelA.setText(fc.getSelectedFile().getName());
+                    properties.pack();
+                }
+            }
+        });
+
+        JButton loadTexturesB = new JButton("Load Textures B");
+        final JLabel texLabelB = new JLabel("None");
+        loadTexturesB.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                JFileChooser fc = new JFileChooser("/Users/mludwig/Desktop/LLS");
+                fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+                if (fc.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
+                    String dir = fc.getSelectedFile().getAbsolutePath();
+                    loadTexturesB(dir);
+                    texLabelB.setText(fc.getSelectedFile().getName());
+                    properties.pack();
+                }
+            }
+        });
+
+        final JSlider fullSlider = createSlider(0, 1000);
+        final JSlider normalSlider = createSlider(0, 1000);
+        final JSlider specAlbedoSlider = createSlider(0, 1000);
+        final JSlider diffAlbedoSlider = createSlider(0, 1000);
+        final JSlider shininessSlider = createSlider(0, 1000);
+
+        JLabel fullLabel = new JLabel("All");
+        fullSlider.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                if (fullSlider.getValueIsAdjusting()) {
+                    // Update the other sliders
+                    normalSlider.setValue(fullSlider.getValue());
+                    specAlbedoSlider.setValue(fullSlider.getValue());
+                    diffAlbedoSlider.setValue(fullSlider.getValue());
+                    shininessSlider.setValue(fullSlider.getValue());
+                }
+            }
+        });
+        JLabel normalLabel = new JLabel("Normals");
+        normalSlider.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                normalAlpha = normalSlider.getValue() / 1000.0;
+                updateGBuffer();
+
+                if (normalSlider.getValueIsAdjusting()) {
+                    fullSlider
+                            .setValue((int) (1000 * (normalAlpha + specularAlbedoAlpha + diffuseAlbedoAlpha +
+                                                     shininessAlpha) / 4.0));
+                }
+            }
+        });
+        JLabel specLabel = new JLabel("Specular");
+        specAlbedoSlider.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                specularAlbedoAlpha = specAlbedoSlider.getValue() / 1000.0;
+                updateGBuffer();
+
+                if (specAlbedoSlider.getValueIsAdjusting()) {
+                    fullSlider
+                            .setValue((int) (1000 * (normalAlpha + specularAlbedoAlpha + diffuseAlbedoAlpha +
+                                                     shininessAlpha) / 4.0));
+                }
+            }
+        });
+        JLabel diffLabel = new JLabel("Diffuse");
+        diffAlbedoSlider.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                diffuseAlbedoAlpha = diffAlbedoSlider.getValue() / 1000.0;
+                updateGBuffer();
+
+                if (diffAlbedoSlider.getValueIsAdjusting()) {
+                    fullSlider
+                            .setValue((int) (1000 * (normalAlpha + specularAlbedoAlpha + diffuseAlbedoAlpha +
+                                                     shininessAlpha) / 4.0));
+                }
+            }
+        });
+        JLabel shinyLabel = new JLabel("Shininess");
+        shininessSlider.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                shininessAlpha = shininessSlider.getValue() / 1000.0;
+                updateGBuffer();
+
+                if (shininessSlider.getValueIsAdjusting()) {
+                    fullSlider
+                            .setValue((int) (1000 * (normalAlpha + specularAlbedoAlpha + diffuseAlbedoAlpha +
+                                                     shininessAlpha) / 4.0));
+                }
+            }
+        });
+
+        JLabel expULabel = new JLabel("Shiny U Scale");
+        final JSlider expUSlider = createSlider(10, 1000);
+        expUSlider.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                shininessXScale = expUSlider.getValue() / 10.0;
+                updateGBuffer();
+            }
+        });
+        JLabel expVLabel = new JLabel("Shiny V Scale");
+        final JSlider expVSlider = createSlider(10, 1000);
+        expVSlider.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                shininessYScale = expVSlider.getValue() / 10.0;
+                updateGBuffer();
+            }
+        });
+
+        JLabel tcALabel = new JLabel("TC A Scale");
+        final JSlider tcASlider = createSlider(100, 10000);
+        tcASlider.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                texCoordAScale = tcASlider.getValue() / 100.0;
+                updateGBuffer();
+            }
+        });
+        JLabel tcBLabel = new JLabel("TC B Scale");
+        final JSlider tcBSlider = createSlider(100, 10000);
+        tcBSlider.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                texCoordBScale = tcBSlider.getValue() / 100.0;
+                updateGBuffer();
+            }
+        });
+
+        JLabel sensitivityLabel = new JLabel("Film ISO");
+        final JSpinner sensitivitySlider = new JSpinner(new SpinnerNumberModel(sensitivity, 20, 8000, 10));
+        sensitivitySlider.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                sensitivity = (Double) sensitivitySlider.getValue();
+                updateGBuffer();
+            }
+        });
+        JLabel exposureLabel = new JLabel("Shutter Speed");
+        final JSpinner exposureSlider = new JSpinner(new SpinnerNumberModel(exposure, 0.00001, 30, 0.001));
+        exposureSlider.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                exposure = (Double) exposureSlider.getValue();
+                updateGBuffer();
+            }
+        });
+        JLabel fstopLabel = new JLabel("F-Stop");
+        final JSpinner fstopSlider = new JSpinner(new SpinnerNumberModel(fstop, 0.5, 128, 0.1));
+        fstopSlider.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                fstop = (Double) fstopSlider.getValue();
+                updateGBuffer();
+            }
+        });
+        JLabel gammaLabel = new JLabel("Gamma");
+        final JSpinner gammaSlider = new JSpinner(new SpinnerNumberModel(gamma, 0.0, 5, 0.01));
+        gammaSlider.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(ChangeEvent e) {
+                gamma = (Double) gammaSlider.getValue();
+                updateGBuffer();
+            }
+        });
+
+        layout.setHorizontalGroup(layout.createSequentialGroup()
+                                        .addGroup(layout.createParallelGroup().addComponent(loadTexturesA)
+                                                        .addComponent(tcASlider).addComponent(loadTexturesB)
+                                                        .addComponent(tcBSlider).addComponent(fullSlider)
+                                                        .addComponent(normalSlider)
+                                                        .addComponent(diffAlbedoSlider)
+                                                        .addComponent(specAlbedoSlider)
+                                                        .addComponent(shininessSlider)
+                                                        .addComponent(expUSlider).addComponent(expVSlider)
+                                                        .addComponent(sensitivitySlider)
+                                                        .addComponent(exposureSlider)
+                                                        .addComponent(fstopSlider).addComponent(gammaSlider))
+                                        .addGroup(layout.createParallelGroup().addComponent(texLabelA)
+                                                        .addComponent(tcALabel).addComponent(texLabelB)
+                                                        .addComponent(tcBLabel).addComponent(fullLabel)
+                                                        .addComponent(normalLabel).addComponent(diffLabel)
+                                                        .addComponent(specLabel).addComponent(shinyLabel)
+                                                        .addComponent(expULabel).addComponent(expVLabel)
+                                                        .addComponent(sensitivityLabel)
+                                                        .addComponent(exposureLabel).addComponent(fstopLabel)
+                                                        .addComponent(gammaLabel)));
+        layout.setVerticalGroup(layout.createSequentialGroup()
+                                      .addGroup(layout.createParallelGroup().addComponent(loadTexturesA)
+                                                      .addComponent(texLabelA))
+                                      .addGroup(layout.createParallelGroup().addComponent(tcASlider)
+                                                      .addComponent(tcALabel)).addGap(10)
+                                      .addGroup(layout.createParallelGroup().addComponent(loadTexturesB)
+                                                      .addComponent(texLabelB))
+                                      .addGroup(layout.createParallelGroup().addComponent(tcBSlider)
+                                                      .addComponent(tcBLabel)).addGap(15)
+                                      .addGroup(layout.createParallelGroup().addComponent(fullSlider)
+                                                      .addComponent(fullLabel))
+                                      .addGroup(layout.createParallelGroup().addComponent(normalSlider)
+                                                      .addComponent(normalLabel))
+                                      .addGroup(layout.createParallelGroup().addComponent(diffAlbedoSlider)
+                                                      .addComponent(diffLabel))
+                                      .addGroup(layout.createParallelGroup().addComponent(specAlbedoSlider)
+                                                      .addComponent(specLabel))
+                                      .addGroup(layout.createParallelGroup().addComponent(shininessSlider)
+                                                      .addComponent(shinyLabel)).addGap(15)
+                                      .addGroup(layout.createParallelGroup().addComponent(expUSlider)
+                                                      .addComponent(expULabel))
+                                      .addGroup(layout.createParallelGroup().addComponent(expVSlider)
+                                                      .addComponent(expVLabel)).addGap(15)
+                                      .addGroup(layout.createParallelGroup().addComponent(sensitivitySlider)
+                                                      .addComponent(sensitivityLabel))
+                                      .addGroup(layout.createParallelGroup().addComponent(exposureSlider)
+                                                      .addComponent(exposureLabel))
+                                      .addGroup(layout.createParallelGroup().addComponent(fstopSlider)
+                                                      .addComponent(fstopLabel))
+                                      .addGroup(layout.createParallelGroup().addComponent(gammaSlider)
+                                                      .addComponent(gammaLabel)));
+
+        properties.pack();
+        properties.setLocation(810, 0);
+        properties.setVisible(true);
+
+        updateCamera();
+    }
+
+    private static JSlider createSlider(int min, int max) {
+        JSlider slider = new JSlider(min, max);
+        slider.setPaintLabels(false);
+        slider.setPaintTicks(false);
+        slider.setSnapToTicks(true);
+        slider.setValue(0);
+        return slider;
+    }
+
+    public void run() throws Exception {
+        while (!window.isDestroyed()) {
+            try {
+                framework.invoke(this).get();
+            } catch (CancellationException e) {
+                // ignore
+            }
+        }
+        properties.setVisible(false);
+        properties.dispose();
+    }
+
+    private double getNormalizedDeviceX(double windowX) {
+        return 2.0 * windowX / window.getWidth() - 1.0;
+    }
+
+    private double getNormalizedDeviceY(double windowY) {
+        return 2.0 * windowY / window.getHeight() - 1.0;
+    }
+
+    private void updateGBuffer() {
+        invalidateGbuffer = true;
+        specularSamplesLeft = envMap.getSamples().size();
+    }
+
+    private void updateCamera() {
+        Matrix4 camT = new Matrix4()
+                               .lookAt(new Vector3(), new Vector3(0, 0, cameraDist), new Vector3(0, 1, 0));
+        camT.mul(viewTrackBall.getTransform(), camT);
+        camera.setOrientation(camT);
+
+        updateGBuffer();
+    }
+
+    private void loadTexturesA(final String directory) {
+        new Thread("texture loader") {
+            @Override
+            public void run() {
+                try {
+                    Builder<? extends Sampler> diffNormal = TextureLoader.readTexture(framework,
+                                                                                      new File(directory +
+                                                                                               File.separator +
+                                                                                               "diffuseNormal.hdr"));
+                    Builder<? extends Sampler> diffAlbedo = TextureLoader.readTexture(framework,
+                                                                                      new File(directory +
+                                                                                               File.separator +
+                                                                                               "diffuseAlbedo.hdr"));
+                    Builder<? extends Sampler> specNormal = TextureLoader.readTexture(framework,
+                                                                                      new File(directory +
+                                                                                               File.separator +
+                                                                                               "specularNormal.hdr"));
+                    Builder<? extends Sampler> specAlbedo = TextureLoader.readTexture(framework,
+                                                                                      new File(directory +
+                                                                                               File.separator +
+                                                                                               "specularAlbedo.hdr"));
+                    RadianceImageLoader.PRINT_NEGATIVES = true;
+                    Builder<? extends Sampler> shininess = TextureLoader.readTexture(framework,
+                                                                                     new File(directory +
+                                                                                              File.separator +
+                                                                                              "shininessXY.hdr"));
+                    RadianceImageLoader.PRINT_NEGATIVES = false;
+
+                    diffuseNormalTexA = diffNormal.build();
+                    diffuseAlbedoTexA = diffAlbedo.build();
+                    specularNormalTexA = specNormal.build();
+                    specularAlbedoTexA = specAlbedo.build();
+                    shininessTexA = shininess.build();
+                    updateGBuffer();
+                } catch (IOException e) {
+                    System.err.println("Error loading images:");
+                    e.printStackTrace();
+                }
+            }
+        }.start();
+    }
+
+    private void loadTexturesB(final String directory) {
+        new Thread("texture loader") {
+            @Override
+            public void run() {
+                try {
+                    Builder<? extends Sampler> diffNormal = TextureLoader.readTexture(framework,
+                                                                                      new File(directory +
+                                                                                               File.separator +
+                                                                                               "diffuseNormal.hdr"));
+                    Builder<? extends Sampler> diffAlbedo = TextureLoader.readTexture(framework,
+                                                                                      new File(directory +
+                                                                                               File.separator +
+                                                                                               "diffuseAlbedo.hdr"));
+                    Builder<? extends Sampler> specNormal = TextureLoader.readTexture(framework,
+                                                                                      new File(directory +
+                                                                                               File.separator +
+                                                                                               "specularNormal.hdr"));
+                    Builder<? extends Sampler> specAlbedo = TextureLoader.readTexture(framework,
+                                                                                      new File(directory +
+                                                                                               File.separator +
+                                                                                               "specularAlbedo.hdr"));
+                    Builder<? extends Sampler> shininess = TextureLoader.readTexture(framework,
+                                                                                     new File(directory +
+                                                                                              File.separator +
+                                                                                              "shininessXY.hdr"));
+
+                    diffuseNormalTexB = diffNormal.build();
+                    diffuseAlbedoTexB = diffAlbedo.build();
+                    specularNormalTexB = specNormal.build();
+                    specularAlbedoTexB = specAlbedo.build();
+                    shininessTexB = shininess.build();
+                    updateGBuffer();
+                } catch (IOException e) {
+                    System.err.println("Error loading images:");
+                    e.printStackTrace();
+                }
+            }
+        }.start();
+    }
+
+    @Override
+    public Void run(HardwareAccessLayer access) {
+        input.process();
+
+        Context ctx;
+        GlslRenderer r;
+
+        // if we've moved the camera or modelview, then the gbuffer is invalidated, so
+        // fill in the gbuffer and the diffuse lighting buffer
+        if (invalidateGbuffer) {
+            ctx = access.setActiveSurface(gbuffer);
+            if (ctx == null) {
+                return null;
+            }
+
+            r = ctx.getGlslRenderer();
+            r.clear(true, true, false, new Vector4(), 1.0, 0);
+
+            // fill gbuffer
+            r.setShader(fillGbufferShader);
+            r.setUniform(fillGbufferShader.getUniform("uProjection"), camera.getProjectionMatrix());
+            r.setUniform(fillGbufferShader.getUniform("uView"), camera.getViewMatrix());
+            r.setUniform(fillGbufferShader.getUniform("uModel"), modelTrackBall.getTransform());
+
+            r.setUniform(fillGbufferShader.getUniform("uSpecularNormalTexA"), specularNormalTexA);
+            r.setUniform(fillGbufferShader.getUniform("uSpecularAlbedoTexA"), specularAlbedoTexA);
+            r.setUniform(fillGbufferShader.getUniform("uDiffuseAlbedoTexA"), diffuseAlbedoTexA);
+            r.setUniform(fillGbufferShader.getUniform("uShininessTexA"), shininessTexA);
+
+            r.setUniform(fillGbufferShader.getUniform("uSpecularNormalTexB"), specularNormalTexB);
+            r.setUniform(fillGbufferShader.getUniform("uSpecularAlbedoTexB"), specularAlbedoTexB);
+            r.setUniform(fillGbufferShader.getUniform("uShininessTexB"), shininessTexB);
+            r.setUniform(fillGbufferShader.getUniform("uDiffuseAlbedoTexB"), diffuseAlbedoTexB);
+
+            r.setUniform(fillGbufferShader.getUniform("uNormalAlpha"), normalAlpha);
+            r.setUniform(fillGbufferShader.getUniform("uSpecularAlpha"), specularAlbedoAlpha);
+            r.setUniform(fillGbufferShader.getUniform("uShininessAlpha"), shininessAlpha);
+            r.setUniform(fillGbufferShader.getUniform("uDiffuseAlpha"), diffuseAlbedoAlpha);
+
+            r.setUniform(fillGbufferShader.getUniform("uShininessScale"), shininessXScale, shininessYScale);
+            r.setUniform(fillGbufferShader.getUniform("uTCScale"), texCoordAScale, texCoordBScale);
+
+            r.bindAttribute(fillGbufferShader.getAttribute("aPos"), shape.getVertices());
+            r.bindAttribute(fillGbufferShader.getAttribute("aNorm"), shape.getNormals());
+            r.bindAttribute(fillGbufferShader.getAttribute("aTan"), shape.getTangents());
+            r.bindAttribute(fillGbufferShader.getAttribute("aTC"), shape.getTextureCoordinates());
+
+            r.setIndices(shape.getIndices());
+            r.render(shape.getPolygonType(), shape.getIndexOffset(), shape.getIndexCount());
+            ctx.flush();
+
+            // accumulate lighting into another texture (linear pre gamma correction)
+            ctx = access.setActiveSurface(accumulateBuffer, accumulateDiff.getRenderTarget());
+            if (ctx == null) {
+                return null;
+            }
+            r = ctx.getGlslRenderer();
+            // avoid the clear and just overwrite everything
+            r.setDepthTest(Renderer.Comparison.ALWAYS);
+            r.setDepthWriteMask(false);
+
+            r.setShader(diffuseGbufferShader);
+            r.setUniform(diffuseGbufferShader.getUniform("uShininessXYDiffuseRG"), shininessDiffuseRGGBuffer);
+            r.setUniform(diffuseGbufferShader.getUniform("uSpecularAlbedoDiffuseB"), specularDiffuseBGBuffer);
+            r.setUniform(diffuseGbufferShader.getUniform("uNormal"), normalGBuffer);
+            r.setUniform(diffuseGbufferShader.getUniform("uDepth"), depthGBuffer);
+            r.setUniform(diffuseGbufferShader.getUniform("uDiffuseIrradiance"), envDiffMap);
+
+            Matrix4 inv = new Matrix4();
+            r.setUniform(diffuseGbufferShader.getUniform("uInvProjection"),
+                         inv.inverse(camera.getProjectionMatrix()));
+            r.setUniform(diffuseGbufferShader.getUniform("uInvView"), inv.inverse(camera.getViewMatrix()));
+            r.setUniform(diffuseGbufferShader.getUniform("uCamPos"), camera.getLocation());
+
+            r.setUniform(diffuseGbufferShader.getUniform("uProjection"),
+                         fullscreenProjection.getProjectionMatrix());
+            r.bindAttribute(diffuseGbufferShader.getAttribute("aPos"), fullscreenQuad.getVertices());
+            r.bindAttribute(diffuseGbufferShader.getAttribute("aTC"), fullscreenQuad.getTextureCoordinates());
+            r.setIndices(fullscreenQuad.getIndices());
+            r.render(fullscreenQuad.getPolygonType(), fullscreenQuad.getIndexOffset(),
+                     fullscreenQuad.getIndexCount());
+
+        }
+
+        //        if (envMap.getSamples().size() - specularSamplesLeft < 30) {
+        if (specularSamplesLeft > 0) {
+            ctx = access.setActiveSurface(accumulateBuffer, accumulateSpec.getRenderTarget());
+            if (ctx == null) {
+                return null;
+            }
+            r = ctx.getGlslRenderer();
+
+            if (invalidateGbuffer) {
+                r.clear(true, false, false, new Vector4(0.0, 0.0, 0.0, 1.0), 1.0, 0);
+            }
+            // avoid the clear and just overwrite everything
+            r.setDepthTest(Renderer.Comparison.ALWAYS);
+            r.setDepthWriteMask(false);
+
+            r.setBlendingEnabled(true);
+            r.setBlendMode(Renderer.BlendFunction.ADD, Renderer.BlendFactor.ONE, Renderer.BlendFactor.ONE);
+
+            r.setShader(specularGbufferShader);
+            r.setUniform(specularGbufferShader.getUniform("uShininessXYDiffuseRG"),
+                         shininessDiffuseRGGBuffer);
+            r.setUniform(specularGbufferShader.getUniform("uSpecularAlbedoDiffuseB"),
+                         specularDiffuseBGBuffer);
+            r.setUniform(specularGbufferShader.getUniform("uNormal"), normalGBuffer);
+            r.setUniform(specularGbufferShader.getUniform("uTangent"), tangentGBuffer);
+            r.setUniform(specularGbufferShader.getUniform("uDepth"), depthGBuffer);
+            r.setUniform(specularGbufferShader.getUniform("uEnvMap"), envCubeMap);
+
+            Matrix4 inv = new Matrix4();
+            r.setUniform(specularGbufferShader.getUniform("uInvProjection"),
+                         inv.inverse(camera.getProjectionMatrix()));
+            r.setUniform(specularGbufferShader.getUniform("uInvView"), inv.inverse(camera.getViewMatrix()));
+            r.setUniform(specularGbufferShader.getUniform("uCamPos"), camera.getLocation());
+
+
+            r.setUniform(specularGbufferShader.getUniform("uProjection"),
+                         fullscreenProjection.getProjectionMatrix());
+            r.bindAttribute(specularGbufferShader.getAttribute("aPos"), fullscreenQuad.getVertices());
+            r.bindAttribute(specularGbufferShader.getAttribute("aTC"),
+                            fullscreenQuad.getTextureCoordinates());
+            r.setIndices(fullscreenQuad.getIndices());
+
+            for (int i = 0; i < 30 && specularSamplesLeft > 0; i++) {
+                //                Vector3 rand = new Vector3(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).normalize();
+                r.setUniform(specularGbufferShader.getUniform("uLightDirection"),
+                             //                             rand);
+                             envMap.getSamples().get(specularSamplesLeft - 1));
+                r.render(fullscreenQuad.getPolygonType(), fullscreenQuad.getIndexOffset(),
+                         fullscreenQuad.getIndexCount());
+                specularSamplesLeft--;
+            }
+        }
+
+        invalidateGbuffer = false;
+
+        // display everything to the window
+        ctx = access.setActiveSurface(window);
+        if (ctx == null) {
+            return null;
+        }
+        r = ctx.getGlslRenderer();
+        r.clear(true, true, false, new Vector4(0.5, 0.5, 0.5, 1.0), 1.0, 0);
+
+        r.setShader(finalShader);
+        r.setUniform(finalShader.getUniform("uProjection"), fullscreenProjection.getProjectionMatrix());
+        r.setUniform(finalShader.getUniform("uDiffuse"), accumulateDiff);
+        r.setUniform(finalShader.getUniform("uSpecular"), accumulateSpec);
+        r.setUniform(finalShader.getUniform("uDepth"), depthGBuffer);
+        r.setUniform(finalShader.getUniform("uGamma"), gamma);
+        r.setUniform(finalShader.getUniform("uSensitivity"), sensitivity);
+        r.setUniform(finalShader.getUniform("uExposure"), exposure);
+        r.setUniform(finalShader.getUniform("uFstop"), fstop);
+
+        r.bindAttribute(finalShader.getAttribute("aPos"), fullscreenQuad.getVertices());
+        r.bindAttribute(finalShader.getAttribute("aTC"), fullscreenQuad.getTextureCoordinates());
+        r.setIndices(fullscreenQuad.getIndices());
+        r.render(fullscreenQuad.getPolygonType(), fullscreenQuad.getIndexOffset(),
+                 fullscreenQuad.getIndexCount());
+
+        r.setShader(simpleShader);
+        r.setUniform(simpleShader.getUniform("uProjection"), camera.getProjectionMatrix());
+        r.setUniform(simpleShader.getUniform("uView"), camera.getViewMatrix());
+        r.setUniform(simpleShader.getUniform("uUseEnvMap"), false);
+
+        // draw light
+        if (showLight) {
+            r.setUniform(simpleShader.getUniform("uModel"), new Matrix4().setIdentity().setCol(3, light));
+            r.setUniform(simpleShader.getUniform("uSolidColor"), new Vector4(1, 1, 0, 1));
+
+            r.bindAttribute(simpleShader.getAttribute("aPos"), lightCube.getVertices());
+
+            r.setIndices(lightCube.getIndices());
+            r.render(lightCube.getPolygonType(), lightCube.getIndexOffset(), lightCube.getIndexCount());
+        }
+
+        // axis rendering
+        if (showAxis) {
+            r.setUniform(simpleShader.getUniform("uModel"), new Matrix4().setIdentity());
+            r.setUniform(simpleShader.getUniform("uSolidColor"), new Vector4(1, 0, 0, 1));
+
+            r.bindAttribute(simpleShader.getAttribute("aPos"), xAxis.getVertices());
+
+            r.setIndices(xAxis.getIndices());
+            r.render(xAxis.getPolygonType(), xAxis.getIndexOffset(), xAxis.getIndexCount());
+
+            r.setUniform(simpleShader.getUniform("uSolidColor"), new Vector4(0, 1, 0, 1));
+            r.bindAttribute(simpleShader.getAttribute("aPos"), yAxis.getVertices());
+
+            r.setIndices(yAxis.getIndices());
+            r.render(yAxis.getPolygonType(), yAxis.getIndexOffset(), yAxis.getIndexCount());
+
+            r.setUniform(simpleShader.getUniform("uSolidColor"), new Vector4(0, 0, 1, 1));
+            r.bindAttribute(simpleShader.getAttribute("aPos"), zAxis.getVertices());
+
+            r.setIndices(zAxis.getIndices());
+            r.render(zAxis.getPolygonType(), zAxis.getIndexOffset(), zAxis.getIndexCount());
+        }
+
+        if (showCubeMap) {
+            // draw environment map
+            r.setShader(simpleShader);
+            r.setDrawStyle(Renderer.DrawStyle.NONE, Renderer.DrawStyle.SOLID);
+            r.setUniform(simpleShader.getUniform("uModel"), new Matrix4().setIdentity());
+            r.setUniform(simpleShader.getUniform("uUseEnvMap"), true);
+            r.setUniform(simpleShader.getUniform("uEnvMap"), (showDiffMap ? envDiffMap : envCubeMap));
+
+            r.bindAttribute(simpleShader.getAttribute("aPos"), envCube.getVertices());
+
+            r.setIndices(envCube.getIndices());
+            r.render(envCube.getPolygonType(), envCube.getIndexOffset(), envCube.getIndexCount());
+        }
+
+        ctx.flush();
+        return null;
+    }
+}

ferox-demos/src/main/java/com/ferox/renderer/AshikhminShader.java