Portal code

Hi folks,

I somehow have a working version of portals. Still, it is not meant to be some sort of final implementation since there are still a lot of things missing. Things which are not yet complete are:

  • Checking the visibility of a portal is done by checking its bounding volume which currently is a BoundingSphere. This will cause the portal drawn even if it is not really possible. Perfect solution would check the planar polygon of the portal against the frustum. Since this only causes a bit more overhead, I am not currently investigating this.
  • Finding the region the cam is currently in is done by checking bounding volumes. This will cause erroneus behaviour if two regions are convex, but their bounding volumes overlap and the cam is in the overlapping region. The solution for this is to use BSPs, which I am waiting for.
  • You have to create everything manually currently. As I wrote in another post, I would like to implement this whole thing in the context of a map node.

    Therefore I publish this code as some sort of technology demo. If you like, you can try it out by using the test class I have written.



    Here is the code used in Camera and AbstractCamera for the handling of additional check planes.


Index: AbstractCamera.java
===================================================================
RCS file: /cvs/jme/src/com/jme/renderer/AbstractCamera.java,v
retrieving revision 1.26
diff -u -r1.26 AbstractCamera.java
--- AbstractCamera.java   18 Sep 2004 19:22:51 -0000   1.26
+++ AbstractCamera.java   14 Dec 2004 22:43:42 -0000
@@ -210,7 +210,7 @@
         viewPortTop = 1.0f;
         viewPortBottom = 0.0f;
 
-        planeQuantity = 6;
+        planeQuantity = FRUSTUM_PLANES;
 
         worldPlane = new Plane[MAX_WORLD_PLANES];
         for (int i = 0; i < MAX_WORLD_PLANES; i++) {
@@ -727,7 +727,7 @@
     public int contains(BoundingVolume bound) {
         if (bound == null) { return INSIDE_FRUSTUM; }
 
-        int planeCounter = FRUSTUM_PLANES - 1;
+        int planeCounter = planeQuantity - 1;
         int mask = 0;
 
         int rVal = INSIDE_FRUSTUM;
@@ -739,9 +739,9 @@
 
                 if (side == Plane.NEGATIVE_SIDE) {
                     //object is outside of frustum
-                    if (planeCounter != FRUSTUM_PLANES - 1) {
-                        int i = bound.getCheckPlane(FRUSTUM_PLANES - 1);
-                        bound.setCheckPlane(FRUSTUM_PLANES - 1, bound
+                    if (planeCounter != planeQuantity - 1) {
+                        int i = bound.getCheckPlane(planeQuantity - 1);
+                        bound.setCheckPlane(planeQuantity - 1, bound
                                 .getCheckPlane(planeCounter));
                         bound.setCheckPlane(planeCounter, i);
                     }
@@ -757,6 +757,33 @@
         }
 
         return rVal;
+    }
+       
+    /**
+     * <code>pushPlane</code> is used to add another check plane to the camera
+     * frustum. Up to MAX_WORLD_PLANES - FRUSTUM_PLANES can be added. This is
+     * used eg. by portals to limit the view through them.
+     * @param newPlane The new plane which further limits the view frustum.
+     * @return true if the plane could be pushed, false if the maximum number
+     * of planes already has been reached and the plane could not be added.
+     */
+    public boolean pushPlane(Plane newPlane) {
+        boolean success = false;
+        if (planeQuantity < MAX_WORLD_PLANES) {
+            worldPlane[planeQuantity++] = newPlane;
+            success = true;
+        }
+        return success;
+    }
+   
+    /**
+     * <code>popPlane</code> pops the last plane added. The standard view planes
+     * cannot be popped.
+     */
+    public void popPlane() {
+        if (planeQuantity > FRUSTUM_PLANES) {
+            planeQuantity--;
+        }
     }
 
     /**
Index: Camera.java
===================================================================
RCS file: /cvs/jme/src/com/jme/renderer/Camera.java,v
retrieving revision 1.14
diff -u -r1.14 Camera.java
--- Camera.java   14 Sep 2004 03:05:35 -0000   1.14
+++ Camera.java   14 Dec 2004 22:43:42 -0000
@@ -39,6 +39,7 @@
 import java.io.Serializable;
 
 import com.jme.bounding.BoundingVolume;
+import com.jme.math.Plane;
 import com.jme.math.Quaternion;
 import com.jme.math.Vector3f;
 
@@ -73,6 +74,18 @@
     public static final int INSIDE_FRUSTUM = 2;
 
     /**
+     * MAX_WORLD_PLANES holds the maximum planes allowed by the system.
+     * Moved here from AbstractCamera.
+     */
+    public static final int MAX_WORLD_PLANES = 32;
+   
+    /**
+     * FRUSTUM_PLANES represents the number of planes of the camera frustum.
+     * Moved here from AbstractCamera.
+     */
+    public static final int FRUSTUM_PLANES = 6;
+
+    /**
      *
      * <code>getLocation</code> returns the position of the camera.
      *
@@ -448,8 +461,6 @@
      */
     public void lookAt(Vector3f pos);
 
-
-
     /**
      * <code>resize</code> resizes this cameras view with the given width/height.
      * This is similar to constructing a new camera, but reusing the same
@@ -458,4 +469,21 @@
      * @param height int
      */
     public void resize(int width, int height);
+   
+    /**
+     * <code>pushPlane</code> is used to add another check plane to the camera
+     * frustum. Up to MAX_WORLD_PLANES - FRUSTUM_PLANES can be added. This is
+     * used eg. by portals to limit the view through them.
+     * @param newPlane The new plane which further limits the view frustum.
+     * @return true if the plane could be pushed, false if the maximum number
+     * of planes already has been reached and the plane could not be added.
+     */
+    public boolean pushPlane(Plane newPlane);
+   
+    /**
+     * <code>popPlane</code> pops the last plane added. The standard view planes
+     * cannot be popped.
+     */
+    public void popPlane();
+   
 }



Here is the code for the bounding volumes so that they only use the check planes for index 0 to MAX_WORLD_PLANES.


Index: BoundingBox.java
===================================================================
RCS file: /cvs/jme/src/com/jme/bounding/BoundingBox.java,v
retrieving revision 1.24
diff -u -r1.24 BoundingBox.java
--- BoundingBox.java   6 Dec 2004 19:04:10 -0000   1.24
+++ BoundingBox.java   14 Dec 2004 22:22:18 -0000
@@ -32,6 +32,7 @@
 
 package com.jme.bounding;
 
+import com.jme.renderer.Camera;
 import com.jme.scene.shape.*;
 import com.jme.math.*;
 
@@ -52,7 +53,7 @@
     private static final long serialVersionUID = 1L;
 
     /** These define the array of planes that are check during view culling. */
-    public int[] checkPlanes = new int[6];
+    public int[] checkPlanes = new int[Camera.MAX_WORLD_PLANES];
 
     private Vector3f minPnt = new Vector3f();
 
@@ -115,12 +116,9 @@
      * the AABB.
      */
     public void initCheckPlanes() {
-        checkPlanes[0] = 0;
-        checkPlanes[1] = 1;
-        checkPlanes[2] = 2;
-        checkPlanes[3] = 3;
-        checkPlanes[4] = 4;
-        checkPlanes[5] = 5;
+        for (int i = 0; i < Camera.MAX_WORLD_PLANES; i++) {
+            checkPlanes[i] = i;
+        }
     }
 
     /**
@@ -446,7 +444,9 @@
      * for intersection.
      */
     public void setCheckPlane(int index, int value) {
-        checkPlanes[index] = value;
+        if (index < Camera.FRUSTUM_PLANES && value < Camera.FRUSTUM_PLANES) {
+            checkPlanes[index] = value;
+        }
     }
 
     /**
Index: BoundingSphere.java
===================================================================
RCS file: /cvs/jme/src/com/jme/bounding/BoundingSphere.java,v
retrieving revision 1.22
diff -u -r1.22 BoundingSphere.java
--- BoundingSphere.java   6 Dec 2004 19:04:10 -0000   1.22
+++ BoundingSphere.java   14 Dec 2004 22:22:20 -0000
@@ -34,16 +34,16 @@
 import java.util.logging.Level;
 
 import com.jme.math.*;
+import com.jme.renderer.Camera;
 import com.jme.scene.shape.Sphere;
 import com.jme.util.LoggingSystem;
 
 /**
- * <code>BoundingSphere</code> defines a sphere that defines a container for a
- * group of vertices of a particular piece of geometry. This sphere defines a
- * radius and a center. <br>
+ * <code>BoundingSphere</code> defines a sphere that defines a container for a group of vertices
+ * of a particular piece of geometry. This sphere defines a radius and a center. <br>
  * <br>
- * A typical usage is to allow the class define the center and radius by calling
- * either <code>containAABB</code> or <code>averagePoints</code>. A call to
+ * A typical usage is to allow the class define the center and radius by calling either
+ * <code>containAABB</code> or <code>averagePoints</code>. A call to
  * <code>computeFramePoint</code> in turn calls <code>containAABB</code>.
  *
  * @author Mark Powell
@@ -52,12 +52,12 @@
 public class BoundingSphere extends Sphere implements BoundingVolume {
 
     /**
-     * When this flag is true, updateModelBound() for BoundingSphere will
-     * calculate the smallest bounding volume.
+     * When this flag is true, updateModelBound() for BoundingSphere will calculate the smallest
+     * bounding volume.
      */
     static public boolean useExactBounds = false;
 
-    public int[] checkPlanes = new int[6];
+    public int[] checkPlanes = new int[Camera.MAX_WORLD_PLANES];
 
     private float oldRadius;
 
@@ -72,13 +72,13 @@
 
     private static final Vector3f tempVecb = new Vector3f();
 
-    private static final Vector3f[] tempVarray = { new Vector3f(),
-            new Vector3f(), new Vector3f(), new Vector3f(), new Vector3f(),
-            new Vector3f(), new Vector3f(), new Vector3f() };
+    private static final Vector3f[] tempVarray = {
+            new Vector3f(), new Vector3f(), new Vector3f(), new Vector3f(), new Vector3f(),
+            new Vector3f(), new Vector3f(), new Vector3f()
+    };
 
     /**
-     * Default contstructor instantiates a new <code>BoundingSphere</code>
-     * object.
+     * Default contstructor instantiates a new <code>BoundingSphere</code> object.
      */
     public BoundingSphere() {
         super("bsphere");
@@ -88,10 +88,8 @@
     /**
      * Constructor instantiates a new <code>BoundingSphere</code> object.
      *
-     * @param radius
-     *            the radius of the sphere.
-     * @param center
-     *            the center of the sphere.
+     * @param radius the radius of the sphere.
+     * @param center the center of the sphere.
      */
     public BoundingSphere(float radius, Vector3f center) {
         super("bsphere", center, 10, 10, radius);
@@ -101,10 +99,8 @@
     /**
      * Constructor instantiates a new <code>BoundingSphere</code> object.
      *
-     * @param radius
-     *            the radius of the sphere.
-     * @param center
-     *            the center of the sphere.
+     * @param radius the radius of the sphere.
+     * @param center the center of the sphere.
      */
     public BoundingSphere(String name, float radius, Vector3f center) {
         super(name, center, 10, 10, radius);
@@ -112,12 +108,9 @@
     }
 
     public void initCheckPlanes() {
-        checkPlanes[0] = 0;
-        checkPlanes[1] = 1;
-        checkPlanes[2] = 2;
-        checkPlanes[3] = 3;
-        checkPlanes[4] = 4;
-        checkPlanes[5] = 5;
+        for (int i = 0; i < Camera.MAX_WORLD_PLANES; i++) {
+            checkPlanes[i] = i;
+        }
     }
 
     /**
@@ -141,8 +134,7 @@
     /**
      * <code>setRadius</code> sets the radius of this bounding sphere.
      *
-     * @param radius
-     *            the new radius of the bounding sphere.
+     * @param radius the new radius of the bounding sphere.
      */
     public void setRadius(float radius) {
         this.radius = radius;
@@ -151,20 +143,17 @@
     /**
      * <code>setCenter</code> sets the center of the bounding sphere.
      *
-     * @param center
-     *            the new center of the bounding sphere.
+     * @param center the new center of the bounding sphere.
      */
     public void setCenter(Vector3f center) {
         this.center = center;
     }
 
     /**
-     * <code>computeFromPoints</code> creates a new Bounding Sphere from a
-     * given set of points. It uses the <code>containAABB</code> method as
-     * default.
+     * <code>computeFromPoints</code> creates a new Bounding Sphere from a given set of points. It
+     * uses the <code>containAABB</code> method as default.
      *
-     * @param points
-     *            the points to contain.
+     * @param points the points to contain.
      */
     public void computeFromPoints(Vector3f[] points) {
         if (useExactBounds)
@@ -174,16 +163,16 @@
     }
 
     /**
-     * Calculates a minimum bounding sphere for the set of points. The algorithm
-     * was originally found at
+     * Calculates a minimum bounding sphere for the set of points. The algorithm was originally
+     * found at
      * http://www.flipcode.com/cgi-bin/msg.cgi?showThread=COTD-SmallestEnclosingSpheres&forum=cotd&id=-1
      * in C++ and translated to java by Cep21
      *
-     * @param points
-     *            The points to calculate the minimum bounds from.
+     * @param points The points to calculate the minimum bounds from.
      */
     public void calcWelzl(Vector3f[] points) {
-        if (center == null) center = new Vector3f();
+        if (center == null)
+            center = new Vector3f();
         Vector3f[] newRef = new Vector3f[points.length];
         for (int i = 0; i < points.length; i++)
             newRef[i] = points[i];
@@ -191,19 +180,14 @@
     }
 
     /**
-     * Used from calcWelzl. This function recurses to calculate a minimum
-     * bounding sphere a few points at a time.
+     * Used from calcWelzl. This function recurses to calculate a minimum bounding sphere a few
+     * points at a time.
      *
-     * @param points
-     *            The array of points to look through.
-     * @param p
-     *            The size of the list to be used.
-     * @param b
-     *            The number of points currently considering to include with the
-     *            sphere.
-     * @param ap
-     *            A variable simulating pointer arithmatic from C++, and offset
-     *            in <code>points</code>.
+     * @param points The array of points to look through.
+     * @param p The size of the list to be used.
+     * @param b The number of points currently considering to include with the sphere.
+     * @param ap A variable simulating pointer arithmatic from C++, and offset in
+     *        <code>points</code>.
      */
     private void recurseMini(Vector3f[] points, int p, int b, int ap) {
         switch (b) {
@@ -222,8 +206,7 @@
             setSphere(points[ap - 1], points[ap - 2], points[ap - 3]);
             break;
         case 4:
-            setSphere(points[ap - 1], points[ap - 2], points[ap - 3],
-                    points[ap - 4]);
+            setSphere(points[ap - 1], points[ap - 2], points[ap - 3], points[ap - 4]);
             return;
         }
         for (int i = 0; i < p; i++) {
@@ -239,17 +222,12 @@
     }
 
     /**
-     * Calculates the minimum bounding sphere of 4 points. Used in welzl's
-     * algorithm.
+     * Calculates the minimum bounding sphere of 4 points. Used in welzl's algorithm.
      *
-     * @param O
-     *            The 1st point inside the sphere.
-     * @param A
-     *            The 2nd point inside the sphere.
-     * @param B
-     *            The 3rd point inside the sphere.
-     * @param C
-     *            The 4th point inside the sphere.
+     * @param O The 1st point inside the sphere.
+     * @param A The 2nd point inside the sphere.
+     * @param B The 3rd point inside the sphere.
+     * @param C The 4th point inside the sphere.
      * @see #calcWelzl(com.jme.math.Vector3f[])
      */
     private void setSphere(Vector3f O, Vector3f A, Vector3f B, Vector3f C) {
@@ -257,27 +235,22 @@
         Vector3f b = B.subtract(O);
         Vector3f c = C.subtract(O);
 
-        float Denominator = 2.0f * (a.x * (b.y * c.z - c.y * b.z) - b.x
-                * (a.y * c.z - c.y * a.z) + c.x * (a.y * b.z - b.y * a.z));
+        float Denominator = 2.0f * (a.x * (b.y * c.z - c.y * b.z) - b.x * (a.y * c.z - c.y * a.z) + c.x
+                * (a.y * b.z - b.y * a.z));
         Vector3f o = a.cross(b).multLocal(c.lengthSquared()).addLocal(
                 c.cross(a).multLocal(b.lengthSquared())).addLocal(
-                b.cross(c).multLocal(a.lengthSquared())).divideLocal(
-                Denominator);
+                b.cross(c).multLocal(a.lengthSquared())).divideLocal(Denominator);
 
         radius = o.length() * radiusEpsilon;
         O.add(o, center);
     }
 
     /**
-     * Calculates the minimum bounding sphere of 3 points. Used in welzl's
-     * algorithm.
+     * Calculates the minimum bounding sphere of 3 points. Used in welzl's algorithm.
      *
-     * @param O
-     *            The 1st point inside the sphere.
-     * @param A
-     *            The 2nd point inside the sphere.
-     * @param B
-     *            The 3rd point inside the sphere.
+     * @param O The 1st point inside the sphere.
+     * @param A The 2nd point inside the sphere.
+     * @param B The 3rd point inside the sphere.
      * @see #calcWelzl(com.jme.math.Vector3f[])
      */
     private void setSphere(Vector3f O, Vector3f A, Vector3f B) {
@@ -288,39 +261,36 @@
         float Denominator = 2.0f * acrossB.dot(acrossB);
 
         Vector3f o = acrossB.cross(a).multLocal(b.lengthSquared()).addLocal(
-                b.cross(acrossB).multLocal(a.lengthSquared())).divideLocal(
-                Denominator);
+                b.cross(acrossB).multLocal(a.lengthSquared())).divideLocal(Denominator);
 
         radius = o.length() * radiusEpsilon;
         O.add(o, center);
     }
 
     /**
-     * Calculates the minimum bounding sphere of 2 points. Used in welzl's
-     * algorithm.
+     * Calculates the minimum bounding sphere of 2 points. Used in welzl's algorithm.
      *
-     * @param O
-     *            The 1st point inside the sphere.
-     * @param A
-     *            The 2nd point inside the sphere.
+     * @param O The 1st point inside the sphere.
+     * @param A The 2nd point inside the sphere.
      * @see #calcWelzl(com.jme.math.Vector3f[])
      */
     private void setSphere(Vector3f O, Vector3f A) {
-        radius = FastMath.sqrt(((A.x - O.x) * (A.x - O.x) + (A.y - O.y)
-                * (A.y - O.y) + (A.z - O.z) * (A.z - O.z)) / 4);
+        radius = FastMath.sqrt(((A.x - O.x) * (A.x - O.x) + (A.y - O.y) * (A.y - O.y) + (A.z - O.z)
+                * (A.z - O.z)) / 4);
         center.interpolate(O, A, .5f);
     }
 
     /**
-     * <code>containAABB</code> creates a minimum-volume axis-aligned bounding
-     * box of the points, then selects the smallest enclosing sphere of the box
-     * with the sphere centered at the boxes center.
+     * <code>containAABB</code> creates a minimum-volume axis-aligned bounding box of the points,
+     * then selects the smallest enclosing sphere of the box with the sphere centered at the boxes
+     * center.
      *
-     * @param points
-     *            the list of points.
+     * @param points the list of points.
      */
     public void containAABB(Vector3f[] points) {
-        if (points.length <= 0) { return; }
+        if (points.length <= 0) {
+            return;
+        }
 
         Vector3f min = tempVeca.set(points[0]);
         Vector3f max = tempVecb.set(tempVeca);
@@ -328,30 +298,32 @@
         for (int i = 1; i < points.length; i++) {
             if (points[i].x < min.x)
                 min.x = points[i].x;
-            else if (points[i].x > max.x) max.x = points[i].x;
+            else if (points[i].x > max.x)
+                max.x = points[i].x;
 
             if (points[i].y < min.y)
                 min.y = points[i].y;
-            else if (points[i].y > max.y) max.y = points[i].y;
+            else if (points[i].y > max.y)
+                max.y = points[i].y;
 
             if (points[i].z < min.z)
                 min.z = points[i].z;
-            else if (points[i].z > max.z) max.z = points[i].z;
+            else if (points[i].z > max.z)
+                max.z = points[i].z;
         }
 
-        if (center == null) center = new Vector3f();
+        if (center == null)
+            center = new Vector3f();
         max.add(min, center).multLocal(.5f);
 
         radius = max.subtractLocal(min).multLocal(.5f).length();
     }
 
     /**
-     * <code>averagePoints</code> selects the sphere center to be the average
-     * of the points and the sphere radius to be the smallest value to enclose
-     * all points.
+     * <code>averagePoints</code> selects the sphere center to be the average of the points and
+     * the sphere radius to be the smallest value to enclose all points.
      *
-     * @param points
-     *            the list of points to contain.
+     * @param points the list of points to contain.
      */
     public void averagePoints(Vector3f[] points) {
         LoggingSystem.getLogger().log(Level.INFO,
@@ -367,54 +339,46 @@
         for (int i = 0; i < points.length; i++) {
             Vector3f diff = points[i].subtract(center);
             float radiusSqr = diff.lengthSquared();
-            if (radiusSqr > maxRadiusSqr) maxRadiusSqr = radiusSqr;
+            if (radiusSqr > maxRadiusSqr)
+                maxRadiusSqr = radiusSqr;
         }
 
-        radius = (float) Math.sqrt(maxRadiusSqr);
+        radius = (float)Math.sqrt(maxRadiusSqr);
 
     }
 
     /**
-     * <code>transform</code> modifies the center of the sphere to reflect the
-     * change made via a rotation, translation and scale.
+     * <code>transform</code> modifies the center of the sphere to reflect the change made via a
+     * rotation, translation and scale.
      *
-     * @param rotate
-     *            the rotation change.
-     * @param translate
-     *            the translation change.
-     * @param scale
-     *            the size change.
+     * @param rotate the rotation change.
+     * @param translate the translation change.
+     * @param scale the size change.
      * @return BoundingVolume
      */
-    public BoundingVolume transform(Quaternion rotate, Vector3f translate,
-            Vector3f scale) {
-        Vector3f newCenter = rotate.mult(center).multLocal(scale).addLocal(
-                translate);
+    public BoundingVolume transform(Quaternion rotate, Vector3f translate, Vector3f scale) {
+        Vector3f newCenter = rotate.mult(center).multLocal(scale).addLocal(translate);
         return new BoundingSphere(getMaxAxis(scale) * radius, newCenter);
     }
 
     /**
-     * <code>transform</code> modifies the center of the sphere to reflect the
-     * change made via a rotation, translation and scale.
+     * <code>transform</code> modifies the center of the sphere to reflect the change made via a
+     * rotation, translation and scale.
      *
-     * @param rotate
-     *            the rotation change.
-     * @param translate
-     *            the translation change.
-     * @param scale
-     *            the size change.
-     * @param store
-     *            sphere to store result in
+     * @param rotate the rotation change.
+     * @param translate the translation change.
+     * @param scale the size change.
+     * @param store sphere to store result in
      * @return BoundingVolume
      * @return ref
      */
-    public BoundingVolume transform(Quaternion rotate, Vector3f translate,
-            Vector3f scale, BoundingVolume store) {
+    public BoundingVolume transform(Quaternion rotate, Vector3f translate, Vector3f scale,
+            BoundingVolume store) {
         BoundingSphere sphere;
         if (store == null || !(store instanceof BoundingSphere)) {
             sphere = new BoundingSphere(1, new Vector3f(0, 0, 0));
         } else {
-            sphere = (BoundingSphere) store;
+            sphere = (BoundingSphere)store;
         }
 
         rotate.mult(center, sphere.center);
@@ -438,11 +402,10 @@
     }
 
     /**
-     * <code>whichSide</code> takes a plane (typically provided by a view
-     * frustum) to determine which side this bound is on.
+     * <code>whichSide</code> takes a plane (typically provided by a view frustum) to determine
+     * which side this bound is on.
      *
-     * @param plane
-     *            the plane to check against.
+     * @param plane the plane to check against.
      * @return side
      */
     public int whichSide(Plane plane) {
@@ -458,31 +421,31 @@
     }
 
     /**
-     * <code>merge</code> combines this sphere with a second bounding sphere.
-     * This new sphere contains both bounding spheres and is returned.
+     * <code>merge</code> combines this sphere with a second bounding sphere. This new sphere
+     * contains both bounding spheres and is returned.
      *
-     * @param volume
-     *            the sphere to combine with this sphere.
+     * @param volume the sphere to combine with this sphere.
      * @return the new sphere
      */
     public BoundingVolume merge(BoundingVolume volume) {
-        if (volume == null) { return this; }
+        if (volume == null) {
+            return this;
+        }
         if (volume instanceof BoundingSphere) {
-            BoundingSphere sphere = (BoundingSphere) volume;
+            BoundingSphere sphere = (BoundingSphere)volume;
             float temp_radius = sphere.getRadius();
             Vector3f temp_center = sphere.getCenter();
             BoundingSphere rVal = new BoundingSphere();
             return merge(temp_radius, temp_center, rVal);
         } else if (volume instanceof BoundingBox) {
-            BoundingBox box = (BoundingBox) volume;
-            Vector3f radVect = new Vector3f(box.xExtent, box.yExtent,
-                    box.zExtent);
+            BoundingBox box = (BoundingBox)volume;
+            Vector3f radVect = new Vector3f(box.xExtent, box.yExtent, box.zExtent);
             Vector3f temp_center = box.getCenter();
             BoundingSphere rVal = new BoundingSphere();
             return merge(radVect.length(), temp_center, rVal);
         } else if (volume instanceof OrientedBoundingBox) {
-            OrientedBoundingBox box = (OrientedBoundingBox) volume;
-            BoundingSphere rVal = (BoundingSphere) this.clone(null);
+            OrientedBoundingBox box = (OrientedBoundingBox)volume;
+            BoundingSphere rVal = (BoundingSphere)this.clone(null);
             return rVal.mergeOBB(box);
         } else {
             return null;
@@ -490,29 +453,28 @@
     }
 
     /**
-     * <code>mergeLocal</code> combines this sphere with a second bounding
-     * sphere locally. Altering this sphere to contain both the original and the
-     * additional sphere volumes;
+     * <code>mergeLocal</code> combines this sphere with a second bounding sphere locally.
+     * Altering this sphere to contain both the original and the additional sphere volumes;
      *
-     * @param volume
-     *            the sphere to combine with this sphere.
+     * @param volume the sphere to combine with this sphere.
      * @return this
      */
     public BoundingVolume mergeLocal(BoundingVolume volume) {
-        if (volume == null) { return this; }
+        if (volume == null) {
+            return this;
+        }
         if (volume instanceof BoundingSphere) {
-            BoundingSphere sphere = (BoundingSphere) volume;
+            BoundingSphere sphere = (BoundingSphere)volume;
             float temp_radius = sphere.getRadius();
             Vector3f temp_center = sphere.getCenter();
             return merge(temp_radius, temp_center, this);
         } else if (volume instanceof BoundingBox) {
-            BoundingBox box = (BoundingBox) volume;
-            Vector3f radVect = new Vector3f(box.xExtent, box.yExtent,
-                    box.zExtent);
+            BoundingBox box = (BoundingBox)volume;
+            Vector3f radVect = new Vector3f(box.xExtent, box.yExtent, box.zExtent);
             Vector3f temp_center = box.getCenter();
             return merge(radVect.length(), temp_center, this);
         } else if (volume instanceof OrientedBoundingBox) {
-            return mergeOBB((OrientedBoundingBox) volume);
+            return mergeOBB((OrientedBoundingBox)volume);
         } else {
             return null;
         }
@@ -521,38 +483,29 @@
     /**
      * Merges this sphere with the given OBB.
      *
-     * @param volume
-     *            The OBB to merge.
+     * @param volume The OBB to merge.
      * @return This sphere, after merging.
      */
     private BoundingSphere mergeOBB(OrientedBoundingBox volume) {
-        if (!volume.correctCorners) volume.computeCorners();
+        if (!volume.correctCorners)
+            volume.computeCorners();
         Vector3f[] mergeArray = new Vector3f[16];
         for (int i = 0; i < 8; i++) {
             mergeArray[i] = volume.vectorStore[i];
         }
-        mergeArray[8] = tempVarray[0].set(center).addLocal(radius, radius,
-                radius);
-        mergeArray[9] = tempVarray[1].set(center).addLocal(-radius, radius,
-                radius);
-        mergeArray[10] = tempVarray[2].set(center).addLocal(radius, -radius,
-                radius);
-        mergeArray[11] = tempVarray[3].set(center).addLocal(radius, radius,
-                -radius);
-        mergeArray[12] = tempVarray[4].set(center).addLocal(-radius, -radius,
-                radius);
-        mergeArray[13] = tempVarray[5].set(center).addLocal(-radius, radius,
-                -radius);
-        mergeArray[14] = tempVarray[6].set(center).addLocal(radius, -radius,
-                -radius);
-        mergeArray[15] = tempVarray[7].set(center).addLocal(-radius, -radius,
-                -radius);
+        mergeArray[8] = tempVarray[0].set(center).addLocal(radius, radius, radius);
+        mergeArray[9] = tempVarray[1].set(center).addLocal(-radius, radius, radius);
+        mergeArray[10] = tempVarray[2].set(center).addLocal(radius, -radius, radius);
+        mergeArray[11] = tempVarray[3].set(center).addLocal(radius, radius, -radius);
+        mergeArray[12] = tempVarray[4].set(center).addLocal(-radius, -radius, radius);
+        mergeArray[13] = tempVarray[5].set(center).addLocal(-radius, radius, -radius);
+        mergeArray[14] = tempVarray[6].set(center).addLocal(radius, -radius, -radius);
+        mergeArray[15] = tempVarray[7].set(center).addLocal(-radius, -radius, -radius);
         computeFromPoints(mergeArray);
         return this;
     }
 
-    private BoundingVolume merge(float temp_radius, Vector3f temp_center,
-            BoundingSphere rVal) {
+    private BoundingVolume merge(float temp_radius, Vector3f temp_center, BoundingSphere rVal) {
         Vector3f diff = temp_center.subtract(center, tempVeca);
         float lengthSquared = diff.lengthSquared();
         float radiusDiff = temp_radius - radius;
@@ -567,7 +520,7 @@
             }
         }
 
-        float length = (float) Math.sqrt(lengthSquared);
+        float length = (float)Math.sqrt(lengthSquared);
 
         if (length > FastMath.FLT_EPSILON) {
             float coeff = (length + radiusDiff) / (2.0f * length);
@@ -581,17 +534,16 @@
     }
 
     /**
-     * <code>clone</code> creates a new BoundingSphere object containing the
-     * same data as this one.
+     * <code>clone</code> creates a new BoundingSphere object containing the same data as this
+     * one.
      *
-     * @param store
-     *            where to store the cloned information. if null or wrong class,
-     *            a new store is created.
+     * @param store where to store the cloned information. if null or wrong class, a new store is
+     *        created.
      * @return the new BoundingSphere
      */
     public Object clone(BoundingVolume store) {
         if (store != null && store instanceof BoundingSphere) {
-            BoundingSphere rVal = (BoundingSphere) store;
+            BoundingSphere rVal = (BoundingSphere)store;
             if (null == rVal.center) {
                 rVal.center = new Vector3f();
             }
@@ -607,30 +559,30 @@
             rVal.checkPlanes[5] = checkPlanes[5];
             return rVal;
         } else
-            return new BoundingSphere(radius,
-                    (center != null ? (Vector3f) center.clone() : null));
+            return new BoundingSphere(radius, (center != null ? (Vector3f)center.clone() : null));
     }
 
     /**
-     * <code>getCheckPlane</code> returns a specific check plane. This plane
-     * identitifies the previous value of the visibility check.
+     * <code>getCheckPlane</code> returns a specific check plane. This plane identitifies the
+     * previous value of the visibility check.
      */
     public int getCheckPlane(int index) {
         return checkPlanes[index];
     }
 
     /**
-     * <code>setCheckPlane</code> indentifies the value of one of the spheres
-     * checked planes. That is what plane of the view frustum has been checked
-     * for intersection.
+     * <code>setCheckPlane</code> indentifies the value of one of the spheres checked planes. That
+     * is what plane of the view frustum has been checked for intersection.
      */
     public void setCheckPlane(int index, int value) {
-        checkPlanes[index] = value;
+        if (index < Camera.FRUSTUM_PLANES && value < Camera.FRUSTUM_PLANES) {
+            checkPlanes[index] = value;
+        }
     }
 
     /**
-     * <code>recomputeMesh</code> regenerates the <code>BoundingSphere</code>
-     * based on new model information.
+     * <code>recomputeMesh</code> regenerates the <code>BoundingSphere</code> based on new model
+     * information.
      */
     public void recomputeMesh() {
         if (radius != oldRadius || !center.equals(oldCenter)) {
@@ -641,11 +593,9 @@
     }
 
     /**
-     * Find the distance from the center of this Bounding Volume to the given
-     * point.
+     * Find the distance from the center of this Bounding Volume to the given point.
      *
-     * @param point
-     *            The point to get the distance to
+     * @param point The point to get the distance to
      * @return distance
      */
     public float distanceTo(Vector3f point) {
@@ -655,8 +605,7 @@
     /**
      * Stores the current center of this BoundingSphere into the store vector.
      *
-     * @param store
-     *            The vector to store the center into.
+     * @param store The vector to store the center into.
      * @return The store vector, after setting it's contents to the center
      */
     public Vector3f getCenter(Vector3f store) {
@@ -664,14 +613,13 @@
     }
 
     /**
-     * <code>toString</code> returns the string representation of this object.
-     * The form is: "Radius: RRR.SSSS Center: <Vector>".
+     * <code>toString</code> returns the string representation of this object. The form is:
+     * "Radius: RRR.SSSS Center: <Vector>".
      *
      * @return the string representation of this.
      */
     public String toString() {
-        return "com.jme.scene.BoundingSphere [Radius: " + radius + " Center: "
-                + center + "]";
+        return "com.jme.scene.BoundingSphere [Radius: " + radius + " Center: " + center + "]";
     }
 
     /*
@@ -697,40 +645,37 @@
         return (diff.dot(diff) <= rsum * rsum);
     }
 
-    /*
-     * (non-Javadoc)
+    /*
+     * (non-Javadoc)
+     *
+     * @see com.jme.bounding.BoundingVolume#intersectsBoundingBox(com.jme.bounding.BoundingBox)
+     */
+    public boolean intersectsBoundingBox(BoundingBox bb) {
+        if (FastMath.abs(bb.center.x - getCenter().x) < getRadius() + bb.xExtent
+                && FastMath.abs(bb.center.y - getCenter().y) < getRadius() + bb.yExtent
+                && FastMath.abs(bb.center.z - getCenter().z) < getRadius() + bb.zExtent)
+            return true;
+
+        return false;
+    }
+
+    /*
+     * (non-Javadoc)
      *
-     * @see com.jme.bounding.BoundingVolume#intersectsBoundingBox(com.jme.bounding.BoundingBox)
-     */
-    public boolean intersectsBoundingBox(BoundingBox bb) {
-        if(FastMath.abs(bb.center.x-getCenter().x)<getRadius()+bb.xExtent
-        && FastMath.abs(bb.center.y-getCenter().y)<getRadius()+bb.yExtent
-        && FastMath.abs(bb.center.z-getCenter().z)<getRadius()+bb.zExtent)
-            return true;     
-       
-        return false;
-    }
-
-
-    /*
-     * (non-Javadoc)
-     *
-     * @see com.jme.bounding.BoundingVolume#intersectsOrientedBoundingBox(com.jme.bounding.OrientedBoundingBox)
-     */
-    public boolean intersectsOrientedBoundingBox(OrientedBoundingBox obb) {
-        return obb.intersectsSphere(this);
-    }
-
-
-    /*
-     * (non-Javadoc)
-     *
-     * @see com.jme.bounding.BoundingVolume#intersectsOBB2(com.jme.bounding.OBB2)
-     */
-    public boolean intersectsOBB2(OBB2 obb) {
-        return obb.intersectsSphere(this);
-    }
+     * @see com.jme.bounding.BoundingVolume#intersectsOrientedBoundingBox(com.jme.bounding.OrientedBoundingBox)
+     */
+    public boolean intersectsOrientedBoundingBox(OrientedBoundingBox obb) {
+        return obb.intersectsSphere(this);
+    }
 
+    /*
+     * (non-Javadoc)
+     *
+     * @see com.jme.bounding.BoundingVolume#intersectsOBB2(com.jme.bounding.OBB2)
+     */
+    public boolean intersectsOBB2(OBB2 obb) {
+        return obb.intersectsSphere(this);
+    }
 
     /*
      * (non-Javadoc)
@@ -748,7 +693,7 @@
         if (discr < 0.0) {
             return false;
         } else if (discr > 0.0) {
-            float root = (float) Math.sqrt(discr);
+            float root = (float)Math.sqrt(discr);
             float invA = 1.0f / a;
             t[0] = (-b - root) * invA;
             t[1] = (-b + root) * invA;
Index: OBB2.java
===================================================================
RCS file: /cvs/jme/src/com/jme/bounding/OBB2.java,v
retrieving revision 1.7
diff -u -r1.7 OBB2.java
--- OBB2.java   6 Dec 2004 19:04:10 -0000   1.7
+++ OBB2.java   14 Dec 2004 22:22:22 -0000
@@ -1,6 +1,7 @@
 package com.jme.bounding;
 
 import com.jme.math.*;
+import com.jme.renderer.Camera;
 
 /**
  * Started Date: Sep 5, 2004 <br>
@@ -423,12 +424,9 @@
    }
 
    public void initCheckPlanes() {
-      checkPlanes[0] = 0;
-      checkPlanes[1] = 1;
-      checkPlanes[2] = 2;
-      checkPlanes[3] = 3;
-      checkPlanes[4] = 4;
-      checkPlanes[5] = 5;
+        for (int i = 0; i < Camera.MAX_WORLD_PLANES; i++) {
+            checkPlanes[i] = i;
+        }
    }
 
    public int getCheckPlane(int index) {
@@ -436,7 +434,9 @@
    }
 
    public void setCheckPlane(int index, int value) {
-      checkPlanes[index] = value;
+        if (index < Camera.FRUSTUM_PLANES && value < Camera.FRUSTUM_PLANES) {
+            checkPlanes[index] = value;
+        }
    }
 
    public void recomputeMesh() {
Index: OrientedBoundingBox.java
===================================================================
RCS file: /cvs/jme/src/com/jme/bounding/OrientedBoundingBox.java,v
retrieving revision 1.10
diff -u -r1.10 OrientedBoundingBox.java
--- OrientedBoundingBox.java   6 Dec 2004 19:04:10 -0000   1.10
+++ OrientedBoundingBox.java   14 Dec 2004 22:22:24 -0000
@@ -1,5 +1,6 @@
 package com.jme.bounding;
 
+import com.jme.renderer.Camera;
 import com.jme.scene.shape.OrientedBox;
 import com.jme.math.*;
 
@@ -379,12 +380,9 @@
    }
 
    public void initCheckPlanes() {
-      checkPlanes[0] = 0;
-      checkPlanes[1] = 1;
-      checkPlanes[2] = 2;
-      checkPlanes[3] = 3;
-      checkPlanes[4] = 4;
-      checkPlanes[5] = 5;
+        for (int i = 0; i < Camera.MAX_WORLD_PLANES; i++) {
+            checkPlanes[i] = i;
+        }
    }
 
    public int getCheckPlane(int index) {
@@ -392,7 +390,9 @@
    }
 
    public void setCheckPlane(int index, int value) {
-   &nbs

The rest of it:



Here is the Portal class:


/*
 * Copyright (c) 2003-2004, jMonkeyEngine - Mojo Monkey Coding All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Mojo Monkey Coding, jME, jMonkey Engine, nor the
 * names of its contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 */
package com.jme.scene;

import com.jme.bounding.BoundingSphere;
import com.jme.bounding.BoundingVolume;
import com.jme.math.Plane;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.Renderer;

/**
 * <code>Portal</code> defines a unidirectinal connector between convex regions. It is attached to
 * exactly one convex region and leads to exactly one other convex region. Note that since portals
 * break the construction rule for a tree, this class does NOT extend Node!
 *
 * @author Thomas Baur
 * @version $Id: $
 */
public class Portal {
    // attributes
   
    //** whether the portal is open or not
    protected boolean open;
   
    //** the adjacent convex region where this portal leads to
    protected ConvexRegion adjacentRegion;
   
    //** the internal bounding volume of the portal
    protected BoundingVolume bounding;
   
    //** the vertices creating this bounding box
    protected Vector3f[] modelVertices;
    protected Vector3f[] worldVertices;
   
    //** portal name
    private String name;

    // constructors
   
    /**
     * Empty Constructor to be used internally only.
     */
    public Portal() {
    }

    /**
     * Construct a new portal. The portal will be defined by an array of vertices which build a
     * planar convex polygon (quantity >= 3 is implied). The coordinates of the portal vertices
     * should be in model space for the region that contains the portal. Although there is no upper
     * limit to the number of vertices which can be specified here, limiting them is a good idea
     * even if the portal itself graphically is a complex shape.
     *
     * @param name The name of the portal.
     * @param modelVertices The array of vertices which make up this portal.
     * @param adjacentRegion A reference to the adjacent region ie. the target region of this
     *        portal.
     * @param open Whether the portal initially is open.
     */
    public Portal(String name, Vector3f[] modelVertices, ConvexRegion adjacentRegion, boolean open) {
        this.name = name;
        this.adjacentRegion = adjacentRegion;
        this.open = open;
        this.modelVertices = modelVertices;
        this.worldVertices = new Vector3f[modelVertices.length];
        bounding = new BoundingSphere();
        bounding.computeFromPoints(modelVertices);
    }

    // public methods

    /**
     * Traverse the portal. The portal is checked for visibility in the view frustum. If it is
     * visible, the adjacent region will be drawn.
     *
     * @param r the renderer to draw to.
     * @param currentFrame The current frame count. Needed to prevent endless loops when traversing
     *        the portal graph.
     */
    public void traverse(Renderer r, int currentFrame) {
        if (open) {
            Camera cam = r.getCamera();
            if (cam.contains(bounding) != Camera.OUTSIDE_FRUSTUM) {
                // push portal planes
                int i0 = 0, i1 = worldVertices.length - 1;
                int nrPushedPlanes = 0;
                for (/**/; i0 < worldVertices.length; i1 = i0++) {
                    Vector3f edge1 = worldVertices[i0].subtract(cam.getLocation());
                    Vector3f edge2 = worldVertices[i1].subtract(cam.getLocation());
                    Vector3f planeNormal = edge1.cross(edge2).normalize();
                    float distance = planeNormal.dot(cam.getLocation());
                    Plane p = new Plane(planeNormal, distance);
                    if (cam.pushPlane(p)) {
                        nrPushedPlanes++;
                    }
                }
                adjacentRegion.draw(r, currentFrame);
               
                // pop portal planes
                for (int i = 0; i < nrPushedPlanes; i++) {
                    cam.popPlane();
                }
            }
        }
    }

    /**
     * Update the world orientation of the bounding volume of this portal.
     *
     * @param rotation
     * @param translation
     * @param scale
     */
    public void updateWorldData(Quaternion rotation, Vector3f translation, Vector3f scale,
            BoundingVolume store) {
        if (bounding != null) {
            bounding.transform(rotation, translation, scale, store);
        }
        for (int vertIx = 0; vertIx < modelVertices.length; vertIx++) {
            worldVertices[vertIx] = rotation.mult(modelVertices[vertIx]).multLocal(scale).addLocal(translation);
        }
    }

    /**
     * Getter for the target region of this portal.
     *
     * @return The adjacent region.
     */
    public ConvexRegion getAdjacentRegion() {
        return adjacentRegion;
    }

    /**
     * Sets the reference to the region which this portal leads to.
     *
     * @param adjacentRegion The target region of this portal.
     */
    public void setAdjacentRegion(ConvexRegion adjacentRegion) {
        this.adjacentRegion = adjacentRegion;
    }

    /**
     * Checks, whether the portal currently is in open state.
     *
     * @return the open state.
     */
    public boolean isOpen() {
        return open;
    }

    /**
     * Allows setting of the open state of this portal.
     *
     * @param open the new open state.
     */
    public void setOpen(boolean open) {
        this.open = open;
    }
   
    /**
     * Returns the name of the portal.
     * @return The portal name.
     */
    public String getName() {
        return name;
    }
}



The convex region:


/*
 * Copyright (c) 2003-2004, jMonkeyEngine - Mojo Monkey Coding All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Mojo Monkey Coding, jME, jMonkey Engine, nor the
 * names of its contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 */
package com.jme.scene;

import com.jme.renderer.Renderer;

/**
 * <code>ConvexRegion</code> defines a convex region inside the region system. A ConvexRegion can
 * be connected to any number of other ConvexRegions via portals.
 *
 * @author Thomas Baur
 * @version $Id: $
 */
public class ConvexRegion extends Node {
    // attributes

    //** for region graph traversal
    protected int lastFrame = -1;

    //** the portal list
    protected Portal[] regionPortals;

    // constructors

    /**
     * Empty Constructor to be used internally only.
     */
    public ConvexRegion() {
    }

    /**
     * Create a new ConvexRegion.
     * @param name The unique node name.
     */
    public ConvexRegion(String name) {
        this(name, null, null);
    }

    /**
     * Create a new ConvexRegion.
     * @param name The unique node name.
     * @param portals an array of portals which leading out of this convex region.
     * @param representation The geometrical representation of this convex region.
     */
    public ConvexRegion(String name, Portal[] portals, Spatial representation) {
        super(name);
        regionPortals = portals;
        attachRepresentation(representation);
    }

    // public methods

    /**
     * Attach the representation data.
     *
     * @param representation The representation data.
     * @return The old child at the index 1.
     */
    public int attachRepresentation(Spatial representation) {
        return attachChild(representation);
    }

    /**
     * Detach the representation data.
     *
     * @return The child at the index.
     */
    public Spatial detachRepresentation(Spatial representation) {
        return detachChildAt(0);
    }

    /**
     * Getter for the representation data.
     *
     * @return The representation data.
     */
    public Spatial getRepresentation() {
        return getChild(0);
    }

    /**
     * Returns the number of portals attached to this region.
     *
     * @return The number of portals.
     */
    public int getPortalQuantity() {
        if (regionPortals != null) {
            return regionPortals.length;
        } else {
            return 0;
        }
    }

    /**
     * Retrieve the portal at the given position.
     *
     * @param i The index of the requested portal.
     * @return Either the correct portal or null, if not there.
     */
    public Portal getPortal(int i) {
        if (regionPortals != null) {
            return regionPortals[i];
        } else {
            return null;
        }
    }

    /**
     * This is a version of <code>draw</code> which is called by the convex region manager to
     * prevent endless loops. Since portals can create circular graphs, a frame counter allows for
     * checking, whether this convex region has already been drawn during this frame.
     *
     * @param r the renderer to draw to.
     * @param currentFrame the current frame count
     */
    public void draw(Renderer r, int currentFrame) {
        // prevent overdraw
        if (lastFrame != currentFrame) {
            lastFrame = currentFrame;
            draw(r);
        }
    }

    /**
     * <code>draw</code> checks all portals for visibility, renders them and then renders the
     * representation of this convex region.
     *
     * @see com.jme.scene.Spatial#draw(com.jme.renderer.Renderer)
     * @param r the renderer to draw to.
     */
    public void draw(Renderer r) {
        if (regionPortals != null) {
            for (int portalIx = 0; portalIx < regionPortals.length; portalIx++) {
                regionPortals[portalIx].traverse(r, lastFrame);
            }
        }
        getRepresentation().draw(r);
    }

    /**
     * <code>updateWorldData</code> updates all portals, especially their bounding volumes.
     *
     * @param time the frame time.
     */
    public void updateWorldData(float time) {
        super.updateWorldData(time);
        // make sure portals get recalculated
        if (regionPortals != null) {
            for (int portalIx = 0; portalIx < regionPortals.length; portalIx++) {
                regionPortals[portalIx].updateWorldData(worldRotation, worldTranslation,
                        worldScale, worldBound);
            }
        }
    }

    /**
     * <code>updateGeometricState</code> updates all the geometry information for the convex
     * region and its connected portals.
     *
     * @param time the frame time.
     * @param initiator true if this node started the update process.
     */
    public void updateGeometricState(float time, boolean initiator) {
        super.updateGeometricState(time, initiator);
        if (regionPortals != null) {
            for (int portalIx = 0; portalIx < regionPortals.length; portalIx++) {
                regionPortals[portalIx].updateWorldData(worldRotation, worldTranslation,
                        worldScale, worldBound);
            }
        }
    }
} // public class ConvexRegion extends Node



The ConvexRegionManager:


/*
 * Copyright (c) 2003-2004, jMonkeyEngine - Mojo Monkey Coding All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Mojo Monkey Coding, jME, jMonkey Engine, nor the
 * names of its contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 */
package com.jme.scene;

import java.util.Stack;

import com.jme.bounding.BoundingBox;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.Renderer;

/**
 * <code>ConvexRegionManager</code> maintains a closed system of convex regions which
 * are connected to each other via portals. Additionally, an "outside" can be connected
 * to this node which is drawn if the camera is not inside any of the regions.
 *
 * @author Thomas Baur
 * @version $Id: $
 */
public class ConvexRegionManager extends Node {
    // attributes
   
    //** flag to prevent an error when the camera is in one region and the near plane in another one
    protected boolean useEyePlusNear;
   
    //** the array of regions maintained by this region manager
    protected ConvexRegion[] regions;
   
    //** frame counter used by this region manager
    private int currentFrame = 0;

    // constructors
   
    /**
     * Empty Constructor to be used internally only.
     */
    public ConvexRegionManager() {
    }

    /**
     * Create a new convex region manager.
     *
     * @param name
     *            The node name.
     */
    public ConvexRegionManager(String name) {
        this(name, false);
    }

    /**
     * Create a new convex region manager.
     *
     * @param name The node name.
     * @param useEyePlusNear Set this to true if you want to prevent an error when
     * the camera is in one region and the near plane in another one.
     */
    public ConvexRegionManager(String name, boolean useEyePlusNear) {
        super(name);
        this.useEyePlusNear = useEyePlusNear;
    }

    // public methods
   
    /**
     * Attach the representation data.
     *
     * @param representation
     *            The outside data.
     * @return The old child at the index 1.
     */
    public int attachOutside(Spatial outside) {
        return attachChild(outside);
    }

    /**
     * Detach the outside data.
     *
     * @return The child at the index.
     */
    public Spatial detachOutside(Spatial outside) {
        return detachChildAt(0);
    }

    /**
     * Getter for the outside data.
     *
     * @return The outside data.
     */
    public Spatial getOutside() {
        if (getQuantity() > 0) {
            return getChild(0);
        } else {
            return null;
        }
    }

    /**
     * Determine the region which contains the point. If the point is outside the set of regions,
     * returns null.
     *
     * @param point
     *            The point to check the regions against.
     * @return Either the convex region containing the point or null.
     */
    public ConvexRegion getContainingRegion(Vector3f point) {
        ConvexRegion result = null;
        for (int regionIx = 0; regionIx < regions.length; regionIx++) {
            Spatial rep = regions[regionIx].getRepresentation();
            BoundingBox bb = ((BoundingBox)rep.getWorldBound());
            Vector3f center = bb.getCenter();
            if (point.x > center.x - bb.xExtent && point.x < center.x + bb.xExtent
                    && point.y > center.y - bb.yExtent && point.y < center.y + bb.yExtent
                    && point.z > center.z - bb.zExtent && point.z < center.z + bb.zExtent) {
                //System.out.println("In region " + regionIx);
                result = regions[regionIx];
                break;
            }
        }
        return result;
    }

    /**
     * Getter for the useEyePlusNear flag.
     * @param whether the convex region manager shall take care of the
     * special situation, when the camera is in one region and the near plane
     * in a different one.
     */
    public boolean isUseEyePlusNear() {
        return useEyePlusNear;
    }

    /**
     * <code>draw</code> renders all regions managed by this manager.
     * This either starts drawing regions with the region the camera is
     * currently in or it draws the outside.
     *
     * @see com.jme.scene.Spatial#draw(com.jme.renderer.Renderer)
     * @param r
     *            the renderer to draw to.
     */
    public void draw(Renderer r) {
        Camera cam = r.getCamera();
        Vector3f eye = cam.getLocation();
        if (useEyePlusNear) {
            eye = eye.add(cam.getDirection().mult(cam.getFrustumNear()));
        }
        ConvexRegion region = getContainingRegion(eye);
        currentFrame++;
        if (region != null) {
            // we're inside our regions, draw them by starting with the one we're in
            region.draw(r, currentFrame);
        } else {
            // we're outside, so draw the outside if it exists
            if (getOutside() != null) {
                getOutside().draw(r);
            }
        }
    }

    /**
     * Returns an array of all regions.
     * @return All regions maintained by this convex region manager.
     */
    public ConvexRegion[] getRegions() {
        return regions;
    }

    /**
     * Sets the list of regions maintained by this convex region manager.
     * @param regions The array of regions.
     */
    public void setRegions(ConvexRegion[] regions) {
        this.regions = regions;
        for (int regionIx = 0; regionIx < regions.length; regionIx++) {
            regions[regionIx].setParent(this);
            regions[regionIx].setForceCull(forceCull);
            regions[regionIx].setForceView(forceView);
           
        }
    }

    /**
     * Applies the stack of render states to each child by calling
     * updateRenderState(states) on each child.
     *
     * @param states
     *            The Stack[] of render states to apply to each child.
     */
    protected void applyRenderState(Stack[] states) {
        super.applyRenderState(states);
        for (int regionIx = 0; regionIx < regions.length; regionIx++) {
            regions[regionIx].updateRenderState(states);
        }
    }
   
   
    /**
     * <code>updateWorldData</code> updates all convex regions of this
     * convex region manager, especially their bounding volumes.
     *
     * @param time the frame time.
     */
    public void updateWorldData(float time) {
        super.updateWorldData(time);
        for (int regionIx = 0; regionIx < regions.length; regionIx++) {
            regions[regionIx].updateWorldData(time);
        }
    }
       
} // public class ConvexRegionManager extends BSPNode



The test class:



package jmetest.renderer;
import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.ConvexRegion;
import com.jme.scene.ConvexRegionManager;
import com.jme.scene.Node;
import com.jme.scene.Portal;
import com.jme.scene.TriMesh;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.MaterialState;

/**
 * Portal example.
 */
public class TestPortal extends SimpleGame {
    /*
     * create a cache array for the vertices of the whole scene
     * the scene looks like this:
     *
     *  +


+
     *  |                                       |
     *  |                                       |
     *  |    x->       room3 (blue)             |
     *  |                                       |
     *  |                                       |
     *  +
+-room3P2----+
     *                             +-room2P3----+
     *                             |            |
     *                             |            |
     *                             |   room2    |
     *                             |  (green)   |
     *                             |            |
     *                             +-room2P1----+
     *  +
+-room1P2----+
     *  |   _                                   |
     *  |  /                                   |
     *  | | s |         room1 (red)             |
     *  |  _/                                  |
     *  |                                       |
     *  +
+
     *
     * So there are three convex regions, two with one portal
     * and one in the middle with two portals. One region has a "model"
     * attached to it, a sphere. The whole scene is surrounded by a box
     * containing all three regions simulating the "outside". The x marks
     * the initial camera position and direction.
     */
    private Vector3f[] vertices = new Vector3f[] {
            new Vector3f(-10f, -10f, 50f), new Vector3f(50f, -10f, 50f),
            new Vector3f(-10f, 10f, 50f), new Vector3f(50f, 10f, 50f),
            new Vector3f(-10f, -10f, 30f), new Vector3f(30f, -10f, 30f),
            new Vector3f(50f, -10f, 30f), new Vector3f(-10f, 10f, 30f),
            new Vector3f(30f, 10f, 30f), new Vector3f(50f, 10f, 30f),
            new Vector3f(-10f, -10f, 10f), new Vector3f(30f, -10f, 10f),
            new Vector3f(50f, -10f, 10f), new Vector3f(-10f, 10f, 10f),
            new Vector3f(30f, 10f, 10f), new Vector3f(50f, 10f, 10f),
            new Vector3f(-10f, -10f, -10f), new Vector3f(50f, -10f, -10f),
            new Vector3f(-10f, 10f, -10f), new Vector3f(50f, 10f, -10f)
    };

    private Vector3f[] normals = new Vector3f[] {
            new Vector3f(1f, 1f, 1f).normalize(), new Vector3f(-1f, 1f, 1f).normalize(),
            new Vector3f(1f, -1f, -1f).normalize(), new Vector3f(-1f, -1f, -1f).normalize(),
            new Vector3f(1f, 1f, 1f).normalize(), new Vector3f(1f, 1f, 1f).normalize(),
            new Vector3f(-1f, 1f, 1f).normalize(), new Vector3f(1f, -1f, 1f).normalize(),
            new Vector3f(1f, -1f, 1f).normalize(), new Vector3f(-1f, -1f, 1f).normalize(),
            new Vector3f(1f, 1f, 1f).normalize(), new Vector3f(1f, 1f, 1f).normalize(),
            new Vector3f(-1f, 1f, 1f).normalize(), new Vector3f(1f, -1f, 1f).normalize(),
            new Vector3f(1f, -1f, 1f).normalize(), new Vector3f(-1f, -1f, 1f).normalize(),
            new Vector3f(1f, 1f, -1f).normalize(), new Vector3f(-1f, 1f, -1f).normalize(),
            new Vector3f(1f, -1f, 1f).normalize(), new Vector3f(-1f, -1f, 1f).normalize()
    };

    /**
     * Start application.
     *
     * @param args
     */
    public static void main(String[] args) {
        TestPortal app = new TestPortal();
        app.setDialogBehaviour(SimpleGame.ALWAYS_SHOW_PROPS_DIALOG);
        app.start();
    }

    /**
     * @see com.jme.app.SimpleGame#simpleInitGame()
     */
    protected void simpleInitGame() {
        display.setTitle("Portal test");
        lightState.setTwoSidedLighting(true);
        // set up room 1
        TriMesh room1 = new TriMesh("room1");
        Vector3f[] room1Vertices = new Vector3f[] {
                vertices[0], vertices[1], vertices[2], vertices[3], vertices[4], vertices[5],
                vertices[6], vertices[7], vertices[8], vertices[9]
        };
        room1.setVertices(room1Vertices);
        Vector3f[] room1Normals = new Vector3f[] {
                normals[0], normals[1], normals[2], normals[3], normals[4], normals[5], normals[6],
                normals[7], normals[8], normals[9]
        };
        room1.setNormals(room1Normals);
        int[] room1Indices = new int[] {
                0, 3, 1, 0, 2, 3, 0, 1, 4, 1, 6, 4, 1, 3, 6, 3, 9, 6, 0, 4, 2, 2, 4, 7, 2, 7, 3, 3,
                7, 9, 4, 5, 7, 5, 8, 7
        };
        room1.setIndices(room1Indices);

        MaterialState ms = display.getRenderer().createMaterialState();
        ms.setAmbient(new ColorRGBA(0.5f, 0f, 0f, 1f));
        ms.setDiffuse(new ColorRGBA(0.5f, 0.5f, 0.5f, 1f));
        ms.setEnabled(true);
        room1.setRenderState(ms);
        room1.setModelBound(new BoundingBox());
        room1.updateModelBound();

        // we want a shape in the room, so create a node for the room to attach it to
        Node room1Complete = new Node("room1Complete");
        room1Complete.attachChild(room1);
        Sphere s1 = new Sphere("s1", new Vector3f(0f, 0f, 40f), 12, 12, 8f);
        s1.setModelBound(new BoundingBox());
        s1.updateModelBound();
        room1Complete.attachChild(s1);

        // set up the portal from room 1 to room 2
        Vector3f[] room1P2Vertices = new Vector3f[] {
                vertices[5], vertices[6], vertices[9], vertices[8]
        };
        Portal room1P2 = new Portal("room1P2", room1P2Vertices, null, true);
       
        // portal created, build the region
        ConvexRegion room1C = new ConvexRegion("room1C", new Portal[] {room1P2}, room1Complete);

        // set up room 2
        TriMesh room2 = new TriMesh("room2");
        Vector3f[] room2Vertices = new Vector3f[] {
                vertices[5], vertices[6], vertices[8], vertices[9], vertices[11], vertices[12],
                vertices[14], vertices[15]
        };
        room2.setVertices(room2Vertices);
        Vector3f[] room2Normals = new Vector3f[] {
                normals[5], normals[6], normals[8], normals[9], normals[11], normals[12],
                normals[14], normals[15]
        };
        room2.setNormals(room2Normals);
        int[] room2Indices = new int[] {
                0, 4, 2, 2, 4, 6, 0, 1, 5, 0, 5, 4, 1, 3, 5, 3, 7, 5, 2, 7, 3, 2, 6, 7
        };
        room2.setIndices(room2Indices);

        ms = display.getRenderer().createMaterialState();
        ms.setAmbient(new ColorRGBA(0f, 0.5f, 0f, 1f));
        ms.setDiffuse(new ColorRGBA(0f, 0.5f, 0f, 1f));
        ms.setEnabled(true);
        room2.setRenderState(ms);
        room2.setModelBound(new BoundingBox());
        room2.updateModelBound();

        // set up the portal from room 2 to room 1
        Vector3f[] room2P1Vertices = new Vector3f[] {
                vertices[5], vertices[8], vertices[9], vertices[6]
        };
        Portal room2P1 = new Portal("room2P1", room2P1Vertices, null, true);

        // set up the portal from room 2 to room 3
        Vector3f[] room2P3Vertices = new Vector3f[] {
                vertices[11], vertices[12], vertices[15], vertices[14]
        };
        Portal room2P3 = new Portal("room2P3", room2P3Vertices, null, true);

        // portals created, build the region
        ConvexRegion room2C = new ConvexRegion("room2C", new Portal[] {room2P1, room2P3}, room2);

        // set up room 3
        TriMesh room3 = new TriMesh("room3");

        Vector3f[] room3Vertices = new Vector3f[] {
                vertices[10], vertices[11], vertices[12], vertices[13], vertices[14], vertices[15],
                vertices[16], vertices[17], vertices[18], vertices[19]
        };
        room3.setVertices(room3Vertices);

        Vector3f[] room3Normals = new Vector3f[] {
                normals[10], normals[11], normals[12], normals[13], normals[14], normals[15],
                normals[16], normals[17], normals[18], normals[19]
        };
        room3.setNormals(room3Normals);

        int[] room3Indices = new int[] {
                0, 3, 4, 0, 4, 1, 0, 6, 3, 3, 6, 8, 0, 2, 6, 2, 7, 6, 3, 8, 5, 5, 8, 9, 2, 5, 7, 5,
                9, 7, 6, 7, 8, 7, 9, 8
        };
        room3.setIndices(room3Indices);

        ms = display.getRenderer().createMaterialState();
        ms.setAmbient(new ColorRGBA(0f, 0f, 0.5f, 1f));
        ms.setDiffuse(new ColorRGBA(0f, 0f, 0.5f, 1f));
        ms.setEnabled(true);
        room3.setRenderState(ms);
        room3.setModelBound(new BoundingBox());
        room3.updateModelBound();

        // set up the portal from room 3 to room 2
        Vector3f[] room3P2Vertices = new Vector3f[] {
                vertices[11], vertices[14], vertices[15], vertices[12]
        };
        Portal room3P2 = new Portal("room3P2", room3P2Vertices, null, true);

        // portal created, build the region
        ConvexRegion room3C = new ConvexRegion("room3C", new Portal[] {room3P2}, room3);

        // now connect the portals to the adjacend regions
        room1P2.setAdjacentRegion(room2C);
        room2P1.setAdjacentRegion(room1C);
        room2P3.setAdjacentRegion(room3C);
        room3P2.setAdjacentRegion(room2C);

        // create a convex region manager and place all regions into it
        ConvexRegionManager crm = new ConvexRegionManager("crm", false);
        crm.setRegions(new ConvexRegion[] {
                room1C, room2C, room3C
        });

        // create the outside
        TriMesh outside = new TriMesh("outside");
        Vector3f[] outsideVertices = new Vector3f[] {
                vertices[0], vertices[1], vertices[2], vertices[3], vertices[16], vertices[17],
                vertices[18], vertices[19]
        };
        outside.setVertices(outsideVertices);
        Vector3f[] outsideNormals = new Vector3f[] {
                new Vector3f(-1f, -1f, 1f).normalize(), new Vector3f(1f, -1f, 1f).normalize(),
                new Vector3f(-1f, 1f, 1f).normalize(), new Vector3f(1f, 1f, 1f).normalize(),
                new Vector3f(-1f, -1f, -1f).normalize(), new Vector3f(1f, -1f, -1f).normalize(),
                new Vector3f(-1f, 1f, -1f).normalize(), new Vector3f(1f, 1f, -1f).normalize()
        };
        outside.setNormals(outsideNormals);
        int[] outsideIndices = new int[] {
                0, 1, 2, 2, 1, 3, 1, 5, 3, 3, 5, 7, 2, 3, 6, 6, 3, 7, 0, 2, 4, 4, 2, 6, 0, 4, 1, 4,
                5, 1, 4, 6, 5, 5, 6, 7
        };
        outside.setIndices(outsideIndices);
       
        // and pass it to the convex region manager.
        crm.attachOutside(outside);

        // add the convex region manager to the world
        rootNode.attachChild(crm);

        // place the camera inside region 3 facing partly the portal to region 2
        cam.setLocation(new Vector3f(0f, 0f, 0f));
        cam.setDirection(new Vector3f(1f, 0f, 0f));
        cam.setLeft(new Vector3f(0f, 0f, -1f));
        cam.update();
       
        // switch on wireframe display
        wireState.setEnabled(true);
        rootNode.updateRenderState();
       
    }

}



Happy code pasting :)

Fly around with the cam, especially inside of the three rooms and note, how they get culled - even the sphere in room1. If you leave the regions, you will see the "outside", this is not a bug :)

Comments appreciated.

I haven’t time to look to the code more in depth, but it seems that you’ve made a very good work ! Adding portals to jME is so… cute :smiley:

A new great feature for jME !



Chman

How would a user build a true portal level? Is it truely this laborious to create a portal each time? This is for 3 rooms (4 including the outside), I couldn’t imagine what would be required if you wanted to do a true level.

Can’t you put support for portals and make a converter/compiler/(possibly editor) from .map.

Batman said this in the other thread:


Consider the following situation: you have a hollow cube and centered in it is a sphere. With any automatic solution for generating portals that I found you would create zillions of portals and the engine would be dying from the clipping calculations. I doubt that there is an automatic solution for this - generating a bsp tree is an easy task compared to portal finding. Finding the optimal bsp tree is just a matter of time. Finding "optimal" portals without at least a hint seems a hopeless task.


Which means completely automatic portal generation wouldn't be possibly, I guess. However, there must be a way of easing the load somehow.

Do .map files have portal information contained inside them?
Consider the following situation: you have a hollow cube and centered in it is a sphere. With any automatic solution for generating portals that I found you would create zillions of portals and the engine would be dying from the clipping calculations. I doubt that there is an automatic solution for this - generating a bsp tree is an easy task compared to portal finding. Finding the optimal bsp tree is just a matter of time. Finding "optimal" portals without at least a hint seems a hopeless task.


What did he mean when he said "a hint"? We can have the converter ask for hints when it needs one.

protected ConvexRegion[] regions;



Why not make this an ArayList? Some applications such as editors would want this to be mutable.
"Badmi" wrote:
What did he mean when he said "a hint"? We can have the converter ask for hints when it needs one.

Well, a hint is a polygon drawn in a "hint" texture. That's the way several map editors solve the problem. You have non-convex rooms and place hint brushes in them, then the bsp compiler or what else uses them to partition the rooms and removes the hint polygons from the output data.
All this is the reason why I said, this is just a tech demo.
Currently, the Q3 bsp format does not even contain portal information. Instead, it builds up bit vectors which say which regions can potentially be seen from which other regions, but the information, through which viewport that happens, is not given. That's the reason why I think the "visibility" test should be pluggable. Some times you only have pvs bitvectors, like in Q3, other editor/compiler combinations may give real portal information.

About the ArrayList: absolutely true, Badmi. But when I found out, that this won't be the final thing, I did not look any further into dynamic creation of things.

The major reason why I published the code is that now I think it is easier to have a discussion about the general design decisions which have to be made for jME. I cannot decide on my own how an indoor map system should work, but now you all can understand more easily what issues there are.

Oh, btw: another reason to publish this is because I changed Camera/AbstractCamera and all bounding volumes to handle the additional camera planes - which were already prepared in AbstractCamera. I do not know which impact the multipass rendering has on the camera code. So I wanted to give e.g. renanse a chance the see, in which way I had to change those classes. As you can see, now the nr of check planes is dynamic between 6 and 32, therefore I restrict the plane swapping to the planes 0 to 5 only - they are always there. No matter how we introduce portals, this code is necessary anyway for visibility checking and culling when peeking through a portal

Remember that the id tools cannot be used for Commercial licenses so they can not be used for jme.

"Badmi" wrote:
Remember that the id tools cannot be used for Commercial licenses so they can not be used for jme.

As I understand, they cannot be used for commercial games without a licence, but they can be used for jME if people do not want to create commercial games. That is, if there are options in jME which kind of map technology you can use, devs have more freedom to chose.

I was thinking of preparing a poll about this. Sometimes today (it's 7:41 in the morning currently, I got to go to work now :)

I’ve made some work with octrees, it’s a good way to optimise a game for a low-end machine, although I don’t know if it’s the best way to make a big game…

Hmm…and I was thinking it was a newer method…well, I’m just a n artist.



Anyway, they thinking all this for indoors, only, no?



Sorry if I continue "spamming" just a bit more...got interested in seing the real limits of BSPs...

In depth tut about q3 bsp rendering.
http://graphics.cs.brown.edu/games/quake/quake3.html


a french comment about *.map format...
http://www.photo-sport.ch/diverts/mod/mapformat.htm

opengl code to load a q3 bsp
http://www.paulsprojects.net/opengl/q3bsp/q3bsp.html


better info about map files, but this time Halflife *.map ones...i suspect hl are just quake1 based...

http://collective.valve-erc.com/index.php?go=map_format


this engine is open source and has demo code for bsp as well as md3
http://collective.valve-erc.com/index.php?go=map_format


also for hl edrived maps, you have viewers of BSP and MAP files, and their source code here:
http://countermap.counter-strike.net/Nemesis/index.php?p=18



Oh, about the open source bsp compiler, MAP3BSPC, (tehre's another, openbsp, but dunno if based in id tools!) , I think is not much useful unless you continue coding it and seems the part remaining is not trivial...and in c++.
" At the moment it just parses the map file provided and generates the BSP tree based on the level geometry"

[That late news of the map3bsp compiler so unended may leave things only to getic, or Openbsp compiler : http://www.osmanturan.com/ ]

hmm...seems is incomplete, too...heck...all this leads to getic3d as the very only option, for comercial projects!! :o

a freaking load of step by step tuts about creating a portal engine…(just tottally scroll till the end)

http://www.flipcode.com/articles/index.shtml



first page…

http://www.flipcode.com/articles/portals_issue01.shtml





here speaks bout bsps and octrees…

http://www.flipcode.com/articles/portals_issue15.shtml



Anyway, the article is from 19 99, hehe.





hehe, in a 2002 article…



“Anyhow. There are, as you probably know a few limitations in the geometry you can feed the BSP compiler. (Which is why most FPS engine levels are build from boxes :)”



:wink:



That’s probably what we used…



reading more…ehm…we used portals…

our game was all indoors…(some of 65k tris…)



I understood what the term is here, lol :

http://www.flipcode.com/cgi-bin/fcarticles.cgi?show=64471





One thing I have to point out…I think Halflife levels force u touse wad format …this forces limits in textures way weirder than quake3 RGB ones in every thing…



If that means that hl bsp levels can only eat 256 cols or hi colors textures, man , that’s a real disadvantage…I think all the wad stuff needed special utils for that…I prefer plain good rgb tgas…



All seems is leading to Getic… :wink:



also as both tools (compiler and editor) are made by same authors (he’s been 15 years with c++, he says) it should allow more compatibility…

And at least, I know the limits(and are ok)…can find any specs for that matters for q3 and hl bsp maps…

…everywhere on th enet I’m reading the same…bsp based editor brush based editing is way much more limited in geometry editing than max or any standard modeller…



Still, I understand we do this as we need bsps…?



I agree with the AP. If BSP compiling is not needed by your engine, use polysoup modelers (3DS MAX, Maya, etc) to make your levels, since they allow you a GREAT deal more of freedom than the brush-based map formats, like accurate UV manipulation and the ability to adjust normals at vertex-level.



MAX and Maya also allow you to use their own high-profile renderers to bake lightmaps, so you can get much superior diffuse lighting, with global illumination and radiosity effects.




building an exporter and doing the collissions/visibility (and lightmaps in x files or ase second uv channel) by some code methods, and using a modeller, is said to also not be limited by format and bsp compiling issues…



Anyway, just wanted to mention, as I am reading again that now. In understand there’s a need of using this technology.



another quake 3 bsp loader code.

http://www.flipcode.com/cgi-bin/fcarticles.cgi?show=64172

Just a short comment on all of this: you do not need BSP trees for portals. The sample implementation does not have a single line of BSP in it. The only reason you may want BSP structure in the data is that it is a very fast way to find out, where the camera is. Which not only helps finding the region, but also limits the calculations you need to do for collision detection.



On the other hand, Q3 map compilers create a BSP tree, but no portal information, just PVS vectors.



The idea I am currently thinking of is have a plug-in structure for map loading and rendering. That way, you’ll have a basic algorithm and depending on the technologies your map editor supports, you add plug-ins which handle the various algorithms like visibility culling and collision detection.



I too believe, that expensive 3D modellers can create wonderful levels without any limitations caused by the modelling tool. But you need to be able to render them quickly in the engine. Therefore the engine needs help. The plain geometric data must be enriched by additional information, and there are various possibilities to do so. That’s what it is all about.



I hope I complete my thoughts tomorrow, so I can start a poll. Then we probably can all discuss this more clearly.

The advantage of converters is that the rendering code is independent of the loading code, all of jme features can be accessed with one simple format, all of jme’s users will be using one file type witch makes it simpler to share files, I am convised that there are more.



For example:



for bsp

<BSP> ...info neded to build a bsp class hear</bsp>



for portals

<Portal> ..info neded to build a portal hear</portal>

I see. You use BSP trees as a "finding" method, and of course, as is giving the clue where camera is, you need to check less fo rpolygon collisions (if I understood well)



I deduce then that…well, the actual tools , bsp based, are not used, nr needed? You may still need a tool to generate that bsp tree that you are going to use?



Humm…then…well, this all escapes from my knowledge of programming (which exactly equals to zero :wink: ) but…then, those tools that say to provide only the bsp tree (which seems actually to be the geometry, and even more an special type of convex geometry) because they are not ended, so they don’t provide stuff like lightmaps, shaders, or even portals, thos etools then are enough for building the bsp tree that you need?



open source Map3BSPC then would do the deal, as well as Openbsp, probably. (getic does it all)



The limits in type of geometry are already in bsp type. But I suppose (only suppose) you don’t get other restrictions that were put in bsp compilers in their old moment.(as seems you are only interested in the basic part of build the bsp tree.)



Excuse my ignorance, I -in the artist side :wink: - am trying to guess this stuff…



Funnily I thought initially the advantage of Quark or getic editor + bsp compilers where that it gave a lot of the game-ready stuff in a shot.(lightmaps, occlussion, collisions) Which is stuff that you plainly can’t provide with freeware or open source standard modellers. (Unless you have a coder working hard to, for example, make a Blender python script adapated also to the engine(bad example, Blender doesn’t support a second uv channel for lightmaps))



The new road leaves me a bit confused…I may be to worried by the tools, but is my personal wall when making graphics :wink:


The idea I am currently thinking of is have a plug-in structure for map loading and rendering. That way, you'll have a basic algorithm and depending on the technologies your map editor supports, you add plug-ins which handle the various algorithms like visibility culling and collision detection.


I see now. That sounds clever.

So...the two free for comercial editors, (Getic and Quark, may have plugins for them (I know quark can have plugins,and is open source))would have this plugins.

What I don't know is if the info they handle is enough for you to port and generate that in jME, or actually most of the extra stuff is generated darkly inside the bsp compiler. I know in these editors you as a user mainly worry about putting lights , boolean-like build your geometry, add some prefabs, setup textures...and not much more... So I guess...the collissions, lightmaps , etc, is mor egenerated in the way the ascii map files are written, and is the bsp compiler which generates the stuff.

I suppose two ways...one making a direct plugin, inside the editor, another a converter to convert *.map files from gtkradiant and /or Quark ...What I know about that is...for geometry, quake3 *.map files and HL ones are pretty similar in geometry. I have read HL1 is mainly quake 1 maps...that would explain some sort of compatibility..only that HL1 is weirder for textures, while quake3 ones is plain rgb texture common formats (I think tga)l .Not sure either, is what I have read in forums.

Those two ways if planning using usual quake editors.
The other kind of possibilities, is making plugins for standard advanced modellers, so that the extra info is...somehow generated from simple special boxes/attributes in the tool? (this way would be a way simpler plugins, so to avoid extra complex plugins, that sometimes depend on having an sdk, not often available in comercial tools..) ...the complexity would come for you in traslating those specially named boxes in the features you need for bsp trees, etc.

I think you need at least a tool able to generate a bsp tree. SO, seems you need quake modellers, getic, quark.. isn't it?

(I know my questions sound dumb, but is what would ask any artist ;) )


"I too believe, that expensive 3D modellers can create wonderful levels without any limitations caused by the modelling tool. But you need to be able to render them quickly in the engine."

There was planned a bsp export plugin for Blender, but I think is an abandoned project for its complexity...

My ultimate unwrap opens Quake1,2,3 and Halflife BSP files...sadly, it does not export them...

" Therefore the engine needs help. The plain geometric data must be enriched by additional information, and there are various possibilities to do so. That's what it is all about."

Then , the poll would be about..?

The fact, apart from the actual coding dilema, is that there's an "user-relate issue" , here...

The main thing of it is that...not much users (if it's a coder making art, logically, more) have control of how to make lightmpas, how to handle and advanced modeller. Even if they have some access to the tools (expensive coemrcial packages) they usually don't know how to deal with it.

And there's also my case, the opposite: I know how to do all that, but have not the money. (but i can do just as well, as found my way purchasing low cost sofwtare and using some free)

Is really rare , but really, to find an artist that purchases Ultimate Unwrap, Gile and that also handle well a free modeller. While I am very happy for that, I know is not a majority.

The vast majority uses a waresed Max(some Maya, LW).
And most of them don't know how to handle it well. Most don't know how to create a lightmap. But being in an open source project, I'd hate even to consider the mod-like way....

Another big amount use Milkshape(25$). (while is a compact solution, is way less powerful than the combo Wings3d+Gile[40$]+Ultimate Unwrap(40$)+Character Fx(15$)) But close to none know this. Mainly it requires to learn 3 softwares. Smaller than Max, but three. people runs away from that concept. Milkshape does not provide what that combo gives you: radiosity hi quality lightmaps, much more power/speed in modelling (Wings3d) , many times better and advanced uv mapping (Ultimate) , animation with weights AND conveting them (Ultimate+Character fx), conversions from max weighted files.

Milkshape and the combo both can do full sets for a game. Just th efirst one only for poorer, older game standards. (well, lightmaps comes to be really old, they were in Hl1 mods Milkshape was mainly built for...)

Blender yet can't do all of what the combo can't do, but unlike mIlkshape, it can "fake" most of stuff: at least it has a render, and can use some complex workflows for baking. (and has strong aims to continue covering those fields) The strong advantage of it is its script/plugin system, and that being open source BSD license, you can even do a fork and do your custom level editor with it. Only you can't sell Blender itself, but users of it can sell art made with it. Actually, all needed would be pyhton(they say is very easy code)...

But many artists don't like Blender.

Still, I thinks is quite a much better option than using Quark or getic...I have seen them now again, and the more I see them, the more I am convinced they aren't neither "intuitive" for the new comers (and speaks to you someone used to learn a new 3d package without a need to read the doc)

I'd only see the need for your bsp partition needs...But even so, a plugin for an standard package, or an stand alone converter for example OBJ bsp (or maybe if actually needed an 3d editor...Wings3d and Blender would be my bets. Wings3d is thousands easier to learn than Blender. Ok, Blender is more complete, but also Blender is lightining fast even in the hands of a newbie...(Blender is also once you know it))

Wings3d forces you to actually only output convex geometry (while you can export the other type if you know the tricks)

This one is also bsd open source, but based in Erlang code, though I suppose other language could be used, maybe...There are exports of OBJ, wrl, and other formats.

This quake level editors I find them so unfriendly for the standard artist...Maybe a modder knows a lot of them, but...Not to count the lack of power , control over uv mapping, lightmaps, etc...

As I know what you need is that tree, I'll see if I find bsp exporters for a more standard tool, or ideal, an obj-->bsp tree converter. As then would be used any standard 3d modeller.

oops



seems you already put the poll…



And actually clarified even more the options…



I’ll read that carefully now, maybe much of what i said had no sense, above.