Contributions

In course of my actions i've changed a number jME classes, some changes might be worth including in CVS.



Change i needed to allow subclassing the LWJGLRenderer, a simple change from "private" to "protected":



***************
*** 140,146 ****
 
      private FloatBuffer[] prevTex;
     
!     private ContextCapabilities capabilities;
     
      private int prevTextureNumber = 0;
 
--- 140,146 ----
 
      private FloatBuffer[] prevTex;
     
!     protected ContextCapabilities capabilities;
     
      private int prevTextureNumber = 0;
 
***************
*** 1061,1067 ****
       * @param t
       *            the geometry to process.
       */
!     private void predrawGeometry(Geometry t) {
          // set world matrix
          if (!generatingDisplayList || (t.getLocks() & Spatial.LOCKED_TRANSFORMS) != 0) {
              doTransforms(t);
--- 1061,1067 ----
       * @param t
       *            the geometry to process.
       */
!     protected void predrawGeometry(Geometry t) {
          // set world matrix
          if (!generatingDisplayList || (t.getLocks() & Spatial.LOCKED_TRANSFORMS) != 0) {
              doTransforms(t);



Change needed for SharedMesh's target to decide, how it should be drawn:
SharedMesh.java


***************
*** 485,491 ****
        target.setLocalTranslation(worldTranslation);
        target.setLocalRotation(worldRotation);
        target.setLocalScale(worldScale);
!       r.draw(target);
     }
     
     /**
--- 485,493 ----
        target.setLocalTranslation(worldTranslation);
        target.setLocalRotation(worldRotation);
        target.setLocalScale(worldScale);
!                 // call draw of target
!                 target.draw(r);
!       //r.draw(target);
     }
     
     /**



FastMath.java


// The base n logarithm:
    public static float log(float value, float base) {
        return (float)(Math.log10((double)value)/Math.log10((double)base));
    }



Quaternion.java
This i found on the net and tested so that Q.fromAngles(Q.toAngles()) gives back the same rotation.


// Convert to Euler angles
   
    public float[] toAngles(float[] angles)
    {
        if(angles==null)
            angles=new float[3];
        else if (angles.length != 3)
            throw new IllegalArgumentException(
                    "Angles array must have three elements");
       
        float sqw = w*w;
        float sqx = x*x;
        float sqy = y*y;
        float sqz = z*z;
   float unit = sqx + sqy + sqz + sqw; // if normalised is one, otherwise is correction factor
   float test = x*y + z*w;
   if (test > 0.499*unit) { // singularity at north pole
      angles[1] = 2 * FastMath.atan2(x,w);
      angles[2] = FastMath.HALF_PI;
      angles[0] = 0;
   } else if (test < -0.499*unit) { // singularity at south pole
      angles[1] = -2 * FastMath.atan2(x,w);
      angles[2] = -FastMath.HALF_PI;
      angles[0] = 0;
   } else {
            angles[1] = FastMath.atan2(2*y*w-2*x*z , sqx - sqy - sqz + sqw);
            angles[2] = FastMath.asin(2*test/unit);
            angles[0] = FastMath.atan2(2*x*w-2*y*z , -sqx + sqy - sqz + sqw);
        }
        return angles;
    }



DiscreteLodNode generates 2 Vector3f and a Float garbage each update cycle. After changing it will still generate that Float garbage.


Index: DiscreteLodNode.java
*** C:jmesrccomjmescenelodDiscreteLodNode.java Base (1.7)
--- C:jmesrccomjmescenelodDiscreteLodNode.java Locally Modified (Based On 1.7)
***************
*** 46,55 ****
  public class DiscreteLodNode extends SwitchNode {
     private static final long serialVersionUID = 1L;
     private Vector3f modelCenter;
!    private Vector3f worldCenter;
 
 
-
     private float lastUpdate;
     private SwitchModel model;
 
--- 46,58 ----
  public class DiscreteLodNode extends SwitchNode {
     private static final long serialVersionUID = 1L;
     private Vector3f modelCenter;
!    private Vector3f worldCenter=new Vector3f();
 
+         private static Vector3f tmpVs=new Vector3f();
 
     private float lastUpdate;
     private SwitchModel model;
***************
*** 65,76 ****
     {
        super.updateWorldData(lastUpdate);
 
        // compute world LOD center
!       worldCenter = worldTranslation.add(worldRotation.mult(modelCenter).multLocal(worldScale));
 
        // compute world squared distance intervals
!       float worldSqrScale = worldScale.mult(worldScale).length();
!       model.set(worldCenter.subtract(camera.getLocation()));
        model.set(new Float(worldSqrScale));
        setActiveChild(model.getSwitchChild());
 
--- 65,79 ----
     {
        super.updateWorldData(lastUpdate);
 
+                
+                
        // compute world LOD center
!                 worldCenter = worldRotation.multLocal(worldCenter.set(modelCenter)).multLocal(worldScale).addLocal(worldTranslation);
 
        // compute world squared distance intervals
!                
!       float worldSqrScale = tmpVs.set(worldScale).multLocal(worldScale).length();
!       model.set(worldCenter.subtractLocal(camera.getLocation()));
        model.set(new Float(worldSqrScale));
        setActiveChild(model.getSwitchChild());



Two minor enhancements to LittleEndien.java


// read an unsigned int as a long
    public final long readUInt() throws IOException{
        return (long)(
            (in.read()&0xff) |
            ((in.read()&0xff) << 8) |
            ((in.read()&0xff) << 16) |
            (((long)(in.read()&0xff)) << 24)
        );
    }

    public final int available() throws IOException{
        return in.available();
    }



To be continued...

Flipping a TGA image when loading, also extending an RGB image to RGBA.



Index: TGALoader.java
*** C:jmesrccomjmeutilTGALoader.java Base (1.3)
--- C:jmesrccomjmeutilTGALoader.java Locally Modified (Based On 1.3)
***************
*** 84,102 ****
      return (short) (input << 8 | (input & 0xFF00) >>> 8);
    }
 
    /**
     * <code>loadImage</code> is a manual image loader which is entirely
     * independent of AWT.
     *
     * OUT: RGB8888 or RGBA8888 jme.image.Image object
-    *
-    * @param fis
-    *            InputStream of an uncompressed 24b RGB or 32b RGBA TGA
-    *
     * @return <code>com.jme.image.Image</code> object that contains the
     *         image, either as a RGB888 or RGBA8888
     */
!   public static com.jme.image.Image loadImage(InputStream fis) throws
        IOException {
      byte red = 0;
      byte green = 0;
--- 84,120 ----
      return (short) (input << 8 | (input & 0xFF00) >>> 8);
    }
 
+   public static com.jme.image.Image loadImage(InputStream fis) throws
+       IOException {
+       return loadImage(fis,false);
+   }
+  
      /**
+      *
+      * @param fis InputStream of an uncompressed 24b RGB or 32b RGBA TGA
+      * @param flip Flip the image
+      * @return <code>com.jme.image.Image</code> object that contains the
+      *         image, either as a RGB888 or RGBA8888
+      * @throws java.io.IOException
+      */
+   public static com.jme.image.Image loadImage(InputStream fis, boolean flip) throws
+       IOException {
+       return loadImage(fis,flip,false);
+   }
+  
+   /**
       * <code>loadImage</code> is a manual image loader which is entirely
       * independent of AWT.
       *
       * OUT: RGB8888 or RGBA8888 jme.image.Image object
       * @return <code>com.jme.image.Image</code> object that contains the
       *         image, either as a RGB888 or RGBA8888
+      * @param flip Flip the image
+      * @param exp32 Add a dummy Alpha channel to 24b RGB image.
+      * @param fis InputStream of an uncompressed 24b RGB or 32b RGBA TGA
+      * @throws java.io.IOException
       */
!   public static com.jme.image.Image loadImage(InputStream fis, boolean flip, boolean exp32) throws
        IOException {
      byte red = 0;
      byte green = 0;
***************
*** 123,140 ****
        bis.skip(idLength);
        // Allocate image data array
      byte[] rawData = null;
!     if (pixelDepth == 32)
        rawData = new byte[width * height * 4];
!     else
        rawData = new byte[width * height * 3];
      int rawDataIndex = 0;
      // Faster than doing a 24-or-32 check on each individual pixel,
      // just make a seperate loop for each.
      if (pixelDepth == 24)
        for (int i = 0; i <= (height - 1); i++) {
          for (int j = 0; j < width; j++) {
            blue = dis.readByte();
--- 141,161 ----
        bis.skip(idLength);
        // Allocate image data array
      byte[] rawData = null;
!     int dl;
!     if ((pixelDepth == 32)||(exp32)) {
        rawData = new byte[width * height * 4];
!       dl=4;
!     } else {
        rawData = new byte[width * height * 3];
+       dl=3;
+     }
      int rawDataIndex = 0;
+    
      // Faster than doing a 24-or-32 check on each individual pixel,
      // just make a seperate loop for each.
      if (pixelDepth == 24)
        for (int i = 0; i <= (height - 1); i++) {
+         if(flip) rawDataIndex=(height-1-i)*width*dl;
          for (int j = 0; j < width; j++) {
            blue = dis.readByte();
            green = dis.readByte();
***************
*** 139,148 ****
--- 163,178 ----
            rawData[rawDataIndex++] = (byte) red;
            rawData[rawDataIndex++] = (byte) green;
            rawData[rawDataIndex++] = (byte) blue;
+           if(dl==4) {
+               // create an alpha channel
+               rawData[rawDataIndex++] = (byte)255;
            }
+              
          }
+       }
      else if (pixelDepth == 32)
        for (int i = 0; i <= (height - 1); i++) {
+         if(flip) rawDataIndex=(height-1-i)*width*dl;
          for (int j = 0; j < width; j++) {
            blue = dis.readByte();
            green = dis.readByte();
***************
*** 162,168 ****
      scratch.rewind();
      // Create the jme.image.Image object
      com.jme.image.Image textureImage = new com.jme.image.Image();
!     if (pixelDepth == 32)
        textureImage.setType(com.jme.image.Image.RGBA8888);
      else
        textureImage.setType(com.jme.image.Image.RGB888);
--- 192,198 ----
      scratch.rewind();
      // Create the jme.image.Image object
      com.jme.image.Image textureImage = new com.jme.image.Image();
!     if (dl == 4)
        textureImage.setType(com.jme.image.Image.RGBA8888);
      else
        textureImage.setType(com.jme.image.Image.RGB888);


Note that i didnt change TextureManager.java also to allow flipping of TGA's, because then it breaks some other part of the engine, which assumes that flipping was ignored for TGA's.

Creating a FloatBuffer from float[] in BufferUtils.java:


    public static FloatBuffer createFloatBuffer(float[] data) {
        if (data == null) return null;
        FloatBuffer buff = createFloatBuffer(data.length);
        buff.clear();
        buff.put(data);
        buff.flip();
        return buff;
    }



Loading 16bit RAW heightmaps, and switching the X and Y coordinates in RawHeightMap.java


Index: RawHeightMap.java
*** C:jmesrccomjmexterrainutilRawHeightMap.java Base (1.3)
--- C:jmesrccomjmexterrainutilRawHeightMap.java Locally Modified (Based On 1.3)
***************
*** 32,43 ****
 
  package com.jmex.terrain.util;
 
  import java.io.DataInputStream;
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.IOException;
  import java.util.logging.Level;
!
  import com.jme.system.JmeException;
  import com.jme.util.LoggingSystem;
 
--- 32,44 ----
 
  package com.jmex.terrain.util;
 
+ import com.jme.math.FastMath;
  import java.io.DataInputStream;
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.IOException;
  import java.util.logging.Level;
! import com.jme.util.LittleEndien;
  import com.jme.system.JmeException;
  import com.jme.util.LoggingSystem;
 
***************
*** 50,56 ****
--- 51,75 ----
   * @version $Id: RawHeightMap.java,v 1.3 2006/01/13 19:39:52 renanse Exp $
   */
  public class RawHeightMap extends AbstractHeightMap {
+    
+     /**
+      * Format specification for 8 bit precision heightmaps
+      */
+     public static final int FORMAT_8BIT = 0;
+    
+     /**
+      * Format specification for 16 bit little endian heightmaps
+      */
+     public static final int FORMAT_16BITLE = 1;
+    
+     /**
+      * Format specification for 16 bit big endian heightmaps
+      */
+     public static final int FORMAT_16BITBE = 2;
+    
      private String filename;
+     private int format;
+     private boolean swapxy;
     
      /**
       * Constructor creates a new <code>RawHeightMap</code> object and
***************
*** 66,71 ****
--- 85,99 ----
       *      if the size is 0 or less.
       */
      public RawHeightMap(String filename, int size) {
+         this(filename, size, FORMAT_8BIT, false);
+     }
+
+     public RawHeightMap(int heightData[]) {
+         this.heightData=heightData;
+         this.size=(int)FastMath.sqrt(heightData.length);
+     }
+    
+     public RawHeightMap(String filename, int size, int format, boolean swapxy) {
          //varify that filename and size are valid.
          if (null == filename || size <= 0) {
              throw new JmeException(
***************
*** 79,84 ****
--- 107,114 ----
 
          this.filename = filename;
          this.size = size;
+         this.format=format;
+         this.swapxy=swapxy;
          load();
      }
 
***************
*** 109,130 ****
 
          try {
              fis = new FileInputStream(filename);
!
!             DataInputStream dis = new DataInputStream(fis);
!             if(heightData.length != dis.available()) {
                  LoggingSystem.getLogger().log(Level.WARNING, "Incorrect map size. Aborting raw load.");
              }
              //read in each byte from the raw file.
              for (int i = 0; i < size; i++) {
                  for(int j = 0; j < size; j++) {
!                     heightData[i + (j*size)] = dis.readUnsignedByte();
                  }
              }
!
              dis.close();
              fis.close();
 
--- 139,197 ----
 
          try {
              fis = new FileInputStream(filename);
!             int bpd;
!             if((format==this.FORMAT_16BITLE)||(format==this.FORMAT_16BITBE))
!             {
!                 bpd=2;
!             } else {
!                 bpd=1;
!             }
!             if(format==this.FORMAT_16BITLE)
!             {
!                 LittleEndien dis=new LittleEndien(fis);
!                 if(heightData.length != dis.available()/bpd) {
                      LoggingSystem.getLogger().log(Level.WARNING, "Incorrect map size. Aborting raw load.");
                  }
+                 int index;
                  //read in each byte from the raw file.
                  for (int i = 0; i < size; i++) {
                      for(int j = 0; j < size; j++) {
!                         if(swapxy)
!                         {
!                             index=i + j*size;
!                         } else {
!                             index=(i*size) + j;
                          }
+                         heightData[index] = dis.readShort();
                      }
!                 }
                  dis.close();
+             } else {
+                 DataInputStream dis=new DataInputStream(fis);
+                 if(heightData.length != dis.available()/bpd) {
+                     LoggingSystem.getLogger().log(Level.WARNING, "Incorrect map size. Aborting raw load.");
+                 }
+                 //read in each byte from the raw file.
+                 for (int i = 0; i < size; i++) {
+                     for(int j = 0; j < size; j++) {
+                         int index;
+                         if(swapxy)
+                         {
+                             index=i + j*size;
+                         } else {
+                             index=(i*size) + j;
+                         }
+                         if(format==this.FORMAT_16BITBE)
+                         {
+                             heightData[index] = dis.readShort();
+                         } else {
+                             heightData[index] = dis.readUnsignedByte();
+                         }
+                     }
+                 }
+                 dis.close();
+             }
+
              fis.close();
 
          } catch (FileNotFoundException e) {

Thanks vear! I'll try to get those in…

Hi vear,

I've begun checking these in (FloatBuffer and LittleEndien are in)



I have some question/remarks on some of the others:



SharedMesh.java : this could (I think) lead to some undesired effects, like the original target mesh begin added to the renderqueue. It's better to override SharedMesh draw() method yourself, and set any changes to the target there… or is there any reason you have to do it this way?



FastMath.java: could you whip up a quick javadoc for that one?



Quaternion.java: still have the link where you found this? (so we can properly attribute it)



LODNode/TGA/Heighmap seem good and I'll do them later.








Hi!



You are right, in most cases it is not required, only if the target's draw method has to do something, like in my case set shader attributes (on per mesh basis). As i didn't tested with the queues, dont know how it would turn out, so its better to leave as is, and I'll use a subclassed SharedMesh, as you suggested.



    /**
     * Returns the logarithm of value with given base, calculated as log10(value)/log10(base),
     * so that pow(base, return)==value
     * @param value The value to log.
     * @param base Base of logarithm.
     * @return The logarithm of value with given base
     */
    public static float log(float value, float base) {
        return (float)(Math.log10((double)value)/Math.log10((double)base));
    }



I've found the quaternion-to-Euler code here:
http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm
The whole site is a very good Java math resource.

All changes are in except the ones in patch form. Eclipse doesn't seem to like the patches, what did you use to generate them? I remember something about Eclipse requiring the "unified" format (the stuff with + / -)



You can update the patches to a new format, but I'll also PM you my email address, so you can just send the whole files if you want. Or someone can explain me to how to make these work with Eclipse :slight_smile:

I see you succeeded applying the patches. Have no clue why Eclipse doesn't like them, they were generated in Netbeans as "Export diff patch". Have you changed something to Eclipse could apply them?

How do you see that?? I haven't done anything (nor anyone else as far as I can see) I only checked in changed that were not patches but just snippets of code.



Anyway, my version of Eclipse only seems to support unified, not diff. So you'll need to update the patches or send me the files (you should have received my PM).

Sent you the files. CVS says i have no locally modified files, so i thought the changes were already made.  :?

log10 does not exist in jdk 1.4 -> please change it to log

@llama: consider using jdk1.4 to compile jME ;)    It's great, btw, that you see to all this contributions that fast!

Ah, I use 1.5 but with 1.4 compliance settings… but yes that doesn't cover the API :confused:



I'll have to remove this function for now vear, if you can rewrite it for 1.4 I'll include it again. And: I received your files, thanks.

simply replacing log10 with log should be fine - base e or base 10 does not matter, does it?

Ok vear, your other changes are in too.

It doesn't matter whether log10 or ln is used, your right!

Thanks Llama.



    /**
     * Returns the logarithm of value with given base, calculated as log(value)/log(base),
     * so that pow(base, return)==value (contributed by vear)
     * @param value The value to log.
     * @param base Base of logarithm.
     * @return The logarithm of value with given base
     */
    public static float log(float value, float base) {
        return (float)(Math.log((double)value)/Math.log((double)base));
    }



Forgot to put this back in… done now.