New Trimesh, "Grid"

I was just doing some HUD stuff, and I needed something like Quad, but subdivided. So I put together a simple TriMesh that is just a grid of quads, like the "Plane" object you get in many 3D modellers. If it looks useful, please feel free to use it (I hope there isn't something exactly like this in jme already!).



The second class is a dialog box (or at least an outline for one) showing one use of the grid.




/*
 *  $Id: Grid.java,v 1.1 2007/03/18 22:44:19 shingoki Exp $
 *
 *    Copyright (c) 2005-2006 shingoki
 *
 *  This file is part of AirCarrier, see http://aircarrier.dev.java.net/
 *
 *    AirCarrier is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.

 *    AirCarrier is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.

 *    You should have received a copy of the GNU General Public License
 *    along with AirCarrier; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

package net.java.dev.aircarrier.scene;

import java.nio.FloatBuffer;

import com.jme.renderer.ColorRGBA;
import com.jme.scene.TriMesh;
import com.jme.scene.batch.TriangleBatch;
import com.jme.util.geom.BufferUtils;

/**
 * <code>Grid</code> defines a four sided, two dimensional  rectangular shape.
 * The local height of the <code>Quad</code> defines it's size about the y-axis, while
 * the width defines the x-axis. The z-axis will always be 0.
 * The plane is subdivided into one or more sections along each axis, to form a grid
 * of smaller quads.
 *
 * @author shingoki
 * @version $Id: Grid.java,v 1.1 2007/03/18 22:44:19 shingoki Exp $
 */
public class Grid extends TriMesh {

   int xQuads;
   int yQuads;
   
   private static final long serialVersionUID = 1L;

   /**
    * Constructor creates a new <code>Quade</code> object with the provided
    * width and height.
    *
    * @param name
    *       The name of the <code>Quad</code>.
    * @param width
    *      The width of the <code>Quad</code>.
    * @param height
    *      The height of the <code>Quad</code>.
    * @param xQuads
    *       The number of quads along the x axis
    * @param yQuads
    *       The number of quads along the y axis
    * @param centered
    *       If true, grid is centered on 0,0,0, otherwise the
    *       grid is from 0, 0, 0 to width, height, 0
    */
   public Grid(String name, float width, float height, int xQuads, int yQuads, boolean centered) {
      super(name);
      if ( (xQuads < 1) || (yQuads < 1) ) throw new IllegalArgumentException("xQuads and yQuads must be greater than 0.");
      initialize(width, height, xQuads, yQuads, centered);
   }
   
   /**
    *
    * <code>initialize</code> builds the data for the <code>Grid</code>
    * object.
    *
    *
    * @param width
    *            the width of the <code>Quad</code>.
    * @param height
    *            the height of the <code>Quad</code>.
    * @param xQuads
    *       The number of quads along the x axis
    * @param yQuads
    *       The number of quads along the y axis
    * @param centered
    *       If true, grid is centered on 0,0,0, otherwise the
    *       grid is from 0, 0, 0 to width, height, 0
    *           
    */
   public void initialize(float width, float height, int xQuads, int yQuads, boolean centered) {
      
      this.xQuads = xQuads;
      this.yQuads = yQuads;
      
        TriangleBatch batch = getBatch(0);
       
        //We have one more vertex than the number of quads, along each axis
      batch.setVertexCount((xQuads + 1) * (yQuads + 1));
      
      //We need enough vertices in the buffer
      batch.setVertexBuffer(BufferUtils.createVector3Buffer(batch.getVertexCount()));
      
      //One normal per vertex
      batch.setNormalBuffer(BufferUtils.createVector3Buffer(batch.getVertexCount()));
   
      //One UV position per vertex
        FloatBuffer tbuf = BufferUtils.createVector2Buffer(batch.getVertexCount());
        setTextureBuffer(0,tbuf);
       
        //Two triangles per quad
       batch.setTriangleQuantity(2 * xQuads * yQuads);
      
       //3 indices per triangle
       batch.setIndexBuffer(BufferUtils.createIntBuffer(batch.getTriangleCount() * 3));

       //Set vertex positions, in reading order
       float offset = centered ? -0.5f : 0;
      for (int x = 0; x < xQuads + 1; x++) {
         for (int y = 0; y < yQuads + 1; y++) {
            batch.getVertexBuffer().put(width * (offset + ((float)x) / ((float)xQuads))).put(height * (offset + ((float)y) / ((float)yQuads))).put(0);
         }
      }

      //All normals face along z axis
      for (int i = 0; i < batch.getVertexCount(); i++) {
         batch.getNormalBuffer().put(0).put(0).put(1);
      }

      //Textures are set evenly from 0 to 1 on each axis, vertices are in reading order,
      //so texture coords are the same
      for (int x = 0; x < xQuads + 1; x++) {
         for (int y = 0; y < yQuads + 1; y++) {
            tbuf.put(((float)x) / ((float)xQuads)).put(((float)y) / ((float)yQuads));
         }
      }

       setDefaultColor(ColorRGBA.white);

       //Set up triangles. x and y are the top left of the quad we are building,
       //moving through all verts that are not on the right column or bottom row
       //We put a pair of tris:
       //        __
       //   |   |
       //   |_   |
       for (int x = 0; x < xQuads; x++) {
          for (int y = 0; y < yQuads; y++) {
             int topLeft = x + y * (xQuads + 1);

             //first triangle
             batch.getIndexBuffer().put(topLeft);               //top left
             batch.getIndexBuffer().put(topLeft + (xQuads + 1));      //down a line of verts
             batch.getIndexBuffer().put(topLeft + (xQuads + 1) + 1);   //down a line, and one to the right

             //second triangle
             batch.getIndexBuffer().put(topLeft);               //top left
             batch.getIndexBuffer().put(topLeft + (xQuads + 1) + 1);   //down a line, and one to the right
             batch.getIndexBuffer().put(topLeft + 1);            //one to the right
          }          
       }
   }

   /**
    * Move a point within the grid
    * @param xIndex
    *       The x index of the point, from 0 to xQuads
    * @param yIndex
    *       The y index of the point, from 0 to yQuads
    * @param x
    *       The new x coordinate of the point
    * @param y
    *       The new y coordinate of the point
    */
   public void movePoint(int xIndex, int yIndex, float x, float y) {
        TriangleBatch batch = getBatch(0);
      batch.getVertexBuffer().position((xIndex + yIndex * (xQuads + 1)) * 3);
      batch.getVertexBuffer().put(x).put(y).put(0);
   }
   
   /**
    * Move the texture coord of a point within the grid
    * @param xIndex
    *       The x index of the point, from 0 to xQuads
    * @param yIndex
    *       The y index of the point, from 0 to yQuads
    * @param x
    *       The new texture x coordinate of the point
    * @param y
    *       The new texture y coordinate of the point
    */
   public void moveUV(int xIndex, int yIndex, float x, float y) {
        TriangleBatch batch = getBatch(0);
      batch.getTextureBuffer(0).position((xIndex + yIndex * (xQuads + 1)) * 2);
      batch.getTextureBuffer(0).put(x).put(y);
   }

}



And the dialog box:



/*
 *  $Id: DialogBox.java,v 1.1 2007/03/18 22:45:18 shingoki Exp $
 *
 *    Copyright (c) 2005-2006 shingoki
 *
 *  This file is part of AirCarrier, see http://aircarrier.dev.java.net/
 *
 *    AirCarrier is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.

 *    AirCarrier is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.

 *    You should have received a copy of the GNU General Public License
 *    along with AirCarrier; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

package net.java.dev.aircarrier.hud;

import net.java.dev.aircarrier.scene.Grid;

import com.jme.image.Texture;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;

/**
 * A grid with 3x3 quads, textured so as to have nice corners.
 * That is, the uv coords correspond to a 32x32 image which has
 * a border of 1 blank pixel, with the remaining 30x30 image being
 * made up of 9 10x10 sections, which are the corners, edges and
 * middle of the dialog box. The image corners are then always drawn at
 * the same scale in the corners of the dialog. The top and bottom
 * edges on the image are stretched along the x axis to match the
 * top and bottom of the dialog, similarly for the left and right edges.
 * The center is stretched in both axes.
 *
 * The image used may also be a multiple of 32x32 pixels (or some other
 * size, but this will not look as neat).
 *
 * @author shingoki
 */
public class DialogBox extends Grid {
   private static final long serialVersionUID = -2906219587285439289L;
   
   float imageBaseSize;
   float tileSize;
   float borderSize;

   /**
    * Create a dialog box, assumed to use a
    * 32x32 image with 1 pixel border, and 10x10 tiles
    * @param name
    *       The name of the box
    * @param t
    *       The texture
    */
   public DialogBox(
         String name,
         Texture t) {
      this(name, 32, 10, 1, t);
   }

   /**
    * Create a dialog box
    * @param name
    *       The name of the box
    * @param imageBaseSize
    *       The size of the texture used for tiles, the border size
    *       is set to imageBaseSize/32, and the tile size to
    *       (imageBaseSize/32) * 10. So it is a scaled version of
    *       a 32x32 image with 1 pixel border, and 10x10 tiles
    * @param t
    *       The texture
    */
   public DialogBox(
         String name,
         float imageBaseSize,
         Texture t) {
      //Assume a border size of 1, hence tile size is image size - 2, divided into 3 tiles
      this(name, imageBaseSize, (imageBaseSize/32) * 10, imageBaseSize/32, t);
   }
   
   /**
    * Create a dialog box
    * @param name
    *       The name of the box
    * @param imageBaseSize
    *       The size of the image used for the texture
    * @param tileSize
    *       The size of the tiles in the texture (corner, edge and middle tiles)
    * @param borderSize
    *       The size of the border around the texture
    * @param t
    *       The texture
    */
   public DialogBox(
         String name,
         float imageBaseSize,
         float tileSize,
         float borderSize,
         Texture t) {
      //Make a 3x3 grid, which is sized to have 1 unit per tile pixel on each quad
      super(name, tileSize * 3, tileSize * 3, 3, 3, false);
      
      this.imageBaseSize = imageBaseSize;
      this.tileSize = tileSize;
      this.borderSize = borderSize;
      
      //Move the texture coords appropriately
      for (int x = 0; x < 4; x++) {
         for (int y = 0; y < 4; y++) {
            moveUV(x, y, (borderSize + tileSize * x)/imageBaseSize, (borderSize + tileSize * y)/imageBaseSize);
         }
      }
      
      //Set texture
      TextureState textureState = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
      textureState.setEnabled(true);
      textureState.setTexture(t);
      setRenderState(textureState);
      
      //set initial dimension to 3x3 tiles
      //setDimension(tileSize * 3, tileSize * 3);
   }

   /**
    * Scale the dialog box
    * @param width
    *       The box width
    * @param height
    *       The box height
    */
   public void setDimension(float width, float height) {
      
      //Smallest dimension is 0
      if (width < 0) width = 0;
      if (height < 0) height = 0;
      
      //Work out the tile size to use - if either width or height
      //are too small to fit the tile size twice (for corners) then
      //reduce the tile size we use to half that dimension
      float smallestDimension = Math.min(width, height);
      float useTileSize = tileSize;
      if (smallestDimension < tileSize * 2) {
         useTileSize = smallestDimension/2;
      }
      
      //We just need to set the second and third columns and rows
      float widthMinusTile = width - useTileSize;
      float heightMinusTile = height - useTileSize;

      //Move the vertex coords appropriately
      for (int xIndex = 0; xIndex < 4; xIndex++) {
         for (int yIndex = 0; yIndex < 4; yIndex++) {
            
            float x = 0;
            if (xIndex == 0) {
               x = 0;
            } else if (xIndex == 1) {
               x = useTileSize;
            } else if (xIndex == 2) {
               x = widthMinusTile;
            } else {
               x = width;
            }

            float y = 0;
            if (yIndex == 0) {
               y = 0;
            } else if (yIndex == 1) {
               y = useTileSize;
            } else if (yIndex == 2) {
               y = heightMinusTile;
            } else {
               y = height;
            }

            movePoint(xIndex, yIndex, x, y);
         }
      }
      
   }


}

Are you ok with non-GPL projects using it?



And that aircraft carrier game is starting to look pretty damned good :slight_smile:

Yup BSD would be fine (jME is BSD right?)



Thanks :smiley: There's a lot more to do though :slight_smile: