Help with line thickness and rendering setup (for custom hud/gui lib)

Hello everyone.
I am working on implementing a new HUD system in jme (purpose: make it easy for students that come from a java with swing to use jme) and for that i decided to implement a Graphics class. Everything works fine
(a tabbed panel, with : a table, a progress bar and a button. There is “hover” effect and actually it’s so easy to do everything that it’s … disturbing !)

But i have a problem with the rendering: the line thickness is always 2 (you can see it on the button), and way too often i have issues with the alpha (seems solved with premultalpha … but as i don’t understand why others don’t work … ).
I know that the setup of the scene is not good but i wasn’t able to fix it.

The whole code of the Graphics is posted at the end of the topic but i’ll quote 2 part that i think are the source of the bug:

float s = 1.f/w;
float w2 = w/2;
float h2 = h/2;

    Camera camera = new Camera(w, h);
    camera.setFrustum(1f, 10f, -w2*s, w2*s, h2*s, -h2*s);
    
    camera.setParallelProjection(true);
    camera.setLocation(new Vector3f(w2*s, -h2*s, 0));
    camera.setRotation(new Quaternion(0, 1, 0, 0));
    viewPort = new ViewPort("agui", camera);
    hudNode = new BatchNode();
    hudNode.setLocalTranslation(0, 0, -5);
    hudNode.setLocalScale(1*s, -1*s, 1);
    viewPort.attachScene(hudNode);

You are likely thinking “what the hell is he doing ?”.
Well, i want a top-to-bottom y axis and if the camera frustum is not -0.5 + 0.5 i got artifacts.
I tried to copy the camera from the guiviewport but it doesn’t work.
Note that if i draw 4 rectangle of 1 pixel i get the one-pixel thickness i want. But 8 triangles instead of 4 lines is kind of an overkill.

The second part of the code that i really don’t like is:

  public void done()
  {
    hudNode.depthFirstTraversal(new SceneGraphVisitor() {

      @Override
      public void visit(Spatial spatial)
      {
        spatial.setQueueBucket(RenderQueue.Bucket.Translucent);
      }
    });

In my code, elements are already sorted back to front (i add an epsilon to their z position). But if i don’t put them in the translucent bucket, they are not transparent.
So, my question is:
can you give me the correct setup for the rendering ? (giving a width w and a height h, i need : camera frustum, camera width,height, position, rotation)
can you explain to me how setup the renderer, so i don’t rely on the bucket thing ?

Also, if i do this offscreen rendering in an update loop, it’s ok. But if i do the rendering in a render method (for example the render method of a control) nothing is drawn (or they are draw out of the camera view, i don’t know).

I can seems pretty stupid to build the end of something that is not working properly at the start (trying to make a gui when rectangles are not drawn properly) but i know what this method is supposed to do (i.e. draw a rectangle) so i build the rest and solve this bug later.
Everything in the code below could change.

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package mygame.gui;

import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.BatchNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.SceneGraphVisitor;
import com.jme3.scene.Spatial;
import com.jme3.scene.VertexBuffer;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.image.ImageRaster;
import javax.vecmath.Point4i;
import mygame.Main;

/**
 *
 * @author Bubuche
 */
public class Graphics
{
  private Image texture;
  private ImageRaster raster;
  private Renderer renderer;
  private FrameBuffer buffer;
  
  private ColorRGBA color;
  private Font font;
  
  private Material fontMaterial;
  private Material uniformColor;
  private Material wireMaterial;
  private ViewPort viewPort;
  private BatchNode hudNode;
  private float shift;
  private static final float addShift = 0.01f;
  private int shiftX;
  private int shiftY;
  
  public Graphics(
          Texture2D texture, 
          Font font)
  {
    this.texture = texture.getImage();
    this.font = font;
    //this.raster = ImageRaster.create(this.texture);
    this.color = new ColorRGBA(ColorRGBA.Black);
    this.shift = addShift;
    
    
    /*renderer = new LwjglRenderer();
    renderer.initialize();
    renderer.setBackgroundColor(ColorRGBA.Black);*/
    int w, h;
    w = this.texture.getWidth();
    h = this.texture.getHeight();
    buffer = new FrameBuffer(
            w, 
            h, 
            1);
    buffer.setColorTexture(texture);
    buffer.setDepthBuffer(Image.Format.Depth);
    
    fontMaterial = new Material(Main.instance.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
    fontMaterial.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.PremultAlpha);
    fontMaterial.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Front);
    fontMaterial.setBoolean("VertexColor", true);
    
    uniformColor = fontMaterial.clone();
    wireMaterial = fontMaterial.clone();
    fontMaterial.setTexture("ColorMap", font.texture());
    
    
    wireMaterial.getAdditionalRenderState().setWireframe(true);
    
    float s = 1.f/w;
    float w2 = w/2;
    float h2 = h/2;
    
    Camera camera = new Camera(w, h);
    camera.setFrustum(1f, 10f, -w2*s, w2*s, h2*s, -h2*s);
    
    camera.setParallelProjection(true);
    camera.setLocation(new Vector3f(w2*s, -h2*s, 0));
    camera.setRotation(new Quaternion(0, 1, 0, 0));
    viewPort = new ViewPort("agui", camera);
    hudNode = new BatchNode();
    hudNode.setLocalTranslation(0, 0, -5);
    hudNode.setLocalScale(1*s, -1*s, 1);
    viewPort.attachScene(hudNode);
    
    /*
    
    Camera camera = new Camera(w, h);
    camera.copyFrom(Main.instance.getGuiViewPort().getCamera());
    viewPort = new ViewPort("agui", camera);
    hudNode = new BatchNode();
    hudNode.setLocalTranslation(0, 0, 0.5f);
    hudNode.setLocalScale(1*s, 1*s, 1);
    viewPort.attachScene(hudNode);
    
     */
  }
  
  public void translate(int x, int y)
  {
    shiftX += x;
    shiftY += y;
  }
  
  public Image image()
  {
    return this.texture;
  }
  
  public void setColor(ColorRGBA color)
  {
    this.color = color;
  }
  
  public ColorRGBA getColor()
  {
    return this.color;
  }
  
  public void setFont(Font font)
  {
    this.font = font;
  }
  
  public void drawRect(int x, int y, int width, int height)
  {
    drawRect(x, y, width, height, color, color, color, color);
  }
  
  public void drawRect(
          int x, 
          int y, 
          int width, 
          int height,
          ColorRGBA top_left,
          ColorRGBA top_right,
          ColorRGBA bottom_left,
          ColorRGBA bottom_right)
  {
    Mesh m = new Mesh();
    m.setBuffer(VertexBuffer.Type.Position, 3, new float[]{
      0,      0,      shift,
      width,  0,      shift,
      width,  height, shift,
      0,      height, shift
      });
    m.setBuffer(VertexBuffer.Type.Index, 2, new short[]{
      0, 1,
      1, 2,
      2, 3,
      3, 0});
    m.setBuffer(VertexBuffer.Type.Color, 4, new float[] {
      top_left    .r, top_left    .g, top_left    .b, top_left    .a,
      top_right   .r, top_right   .g, top_right   .b, top_right   .a,
      bottom_right.r, bottom_right.g, bottom_right.b, bottom_right.a,
      bottom_left .r, bottom_left .g, bottom_left .b, bottom_left .a           
    });
    m.setBuffer(VertexBuffer.Type.Normal, 3, new float[]{
      0, 0, 1,
      0, 0, 1,
      0, 0, 1,
      0, 0, 1
    });
    
    m.setMode(Mesh.Mode.Lines);
    Geometry geom = new Geometry("quad", m);
    geom.setMaterial(wireMaterial);
    
    geom.setLocalTranslation(x+shiftX, y+shiftY, 0);
    
    hudNode.attachChild(geom);
    shift += addShift;
  }
  
  public void drawLine(int x1, int y1, int x2, int y2)
  {
    drawLine(x1, y1, x2, y2, color, color);
  }
  
  public void drawLine(
          int x1, 
          int y1, 
          int x2, 
          int y2,
          ColorRGBA start,
          ColorRGBA end)
  {
    Mesh m = new Mesh();
    m.setBuffer(VertexBuffer.Type.Position, 3, new float[]{
      x1, y1, shift,
      x2, y2, shift,
      });
    m.setBuffer(VertexBuffer.Type.Index, 2, new short[]{
      0, 1, 1, 0});
    m.setBuffer(VertexBuffer.Type.Color, 4, new float[] {
      start.r, start.g, start.b, start.a,
      end.r, end.g, end.b, end.a,     
    });
    m.setBuffer(VertexBuffer.Type.Normal, 3, new float[]{
      0, 0, 1,
      0, 0, 1,
    });
    
    Geometry geom = new Geometry("line", m);
    geom.setMaterial(wireMaterial);
    m.setMode(Mesh.Mode.Lines);
    geom.setLocalTranslation(shiftX, shiftY, 0);
    
    hudNode.attachChild(geom);
    shift += addShift;
  }
  
  public void fillRect(int x, int y, int width, int height)
  {
    fillRect(x, y, width, height, color, color, color, color);
  }
  
  public void fillRect(
          int x, 
          int y, 
          int width, 
          int height,
          ColorRGBA top_left,
          ColorRGBA top_right,
          ColorRGBA bottom_left,
          ColorRGBA bottom_right)
  {
    Mesh m = new Mesh();
    m.setBuffer(VertexBuffer.Type.Position, 3, new float[]{
      0,     0,      shift,
      width, 0,      shift,
      width, height, shift,
      0,     height, shift
      });
    m.setBuffer(VertexBuffer.Type.Index, 3, new short[]{
      0, 1, 2, 
      2, 3, 0});
    m.setBuffer(VertexBuffer.Type.Color, 4, new float[] {
      top_left    .r, top_left    .g, top_left    .b, top_left    .a,
      top_right   .r, top_right   .g, top_right   .b, top_right   .a,
      bottom_right.r, bottom_right.g, bottom_right.b, bottom_right.a,
      bottom_left .r, bottom_left .g, bottom_left .b, bottom_left .a
    });
    m.setBuffer(VertexBuffer.Type.Normal, 3, new float[]{
      0, 0, 1,
      0, 0, 1,
      0, 0, 1,
      0, 0, 1,
    });
    
    Geometry geom = new Geometry("quad", m);
    geom.setMaterial(uniformColor);
    m.setMode(Mesh.Mode.Triangles);
    geom.setLocalTranslation(x+shiftX, y+shiftY, 0);
    
    hudNode.attachChild(geom);
    shift += addShift;
  }
  
  public void drawImage(Image image, int x, int y, int sx, int sy)
  {
    Texture2D t = new Texture2D(image);
    drawImage(t, x, y, sx, sy);
  }
  
  public void drawImage(Texture2D image, int x, int y, int sx, int sy)
  {
    renderer = Main.instance.getRenderer();
    renderer.setFrameBuffer(buffer);
    
    Mesh m = new Mesh();
    m.setBuffer(VertexBuffer.Type.Position, 3, new float[]{
      0 ,  sy, 0,
      sx,  sy, 0,
      sx,  0 , 0,
      0 ,  0 , 0
      });
    m.setBuffer(VertexBuffer.Type.Index, 3, new short[]{
      2, 1, 0,
      3, 2, 0});
    m.setBuffer(VertexBuffer.Type.TexCoord, 2, new float[]{
      0, 0,
      1, 0,
      1, 1,
      0, 1});
    m.setBuffer(VertexBuffer.Type.Normal, 3, new float[]{
      0, 0, 1,
      0, 0, 1,
      0, 0, 1,
      0, 0, 1});
    Geometry g = new Geometry("image", m);
    Material mat = uniformColor.clone();
    mat.setTexture("Texture", image);
    mat.setBoolean("VertexColor", false);
    
    g.setMaterial(mat);
    g.setLocalTranslation(x+shiftX, y+shiftY, shift);
    shift += addShift;
    
    hudNode.attachChild(g);
  }
  
  public void drawImage(Image image, int x, int y, int imx, int imy, int imw, int imh)
  {
    imw = Math.min(imw, raster.getWidth()-x);
    imh = Math.min(imh, raster.getHeight()-y);
    
    ImageRaster r2 = ImageRaster.create(image);
    ColorRGBA c = new ColorRGBA();
    for ( int i = 0; i < imh; i++ )
    {
      for ( int j = 0; j < imw; j++ )
      {
        r2.getPixel(imx+j, imy+i, c);
        if ( c.equals(ColorRGBA.White) )
          continue;
        raster.setPixel(x+j, y+i, color);
      }
    }
  }
  
  public void drawNinePatch(Texture image, int x, int y, int width, int height)
  {
    Image img = image.getImage();
    int w = img.getWidth() / 3;
    int h = img.getHeight() / 3;
    
    Mesh m = new NinePatch(w, h, width, height);
    Geometry g = new Geometry("image", m);
    Material mat = new Material(Main.instance.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
    
    mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Back); 
    mat.setTexture("ColorMap", image);
    
    g.setMaterial(mat);
    g.setLocalTranslation(x+shiftX, y+shiftY, shift);
    shift += addShift;
    
    hudNode.attachChild(g);
  }
  
  public void drawString(String str, int x, int y)
  {
    renderer = Main.instance.getRenderer();
    int len = str.length();
    int real_len = 0;
    for ( int i = 0; i < len; i++ )
    {
      char c = str.charAt(i);
      if ( font.glyphOf(c).z == 0 )
        continue;
      
      if ( c == '\n' )
        continue;
      
      real_len++;
    }
    
    float[] points = new float[4*3*real_len];
    float[] texs = new float[4*2*real_len];
    int[] indexes = new int[3*2*real_len];
    float[] colors = new float[4*4*real_len];
    float[] normals = new float[4*3*real_len];
    
    float w, h;
    w = font.image().getWidth();
    h = font.image().getHeight();
    int tw = 0;
    int line_shift = 0;
    int i = 0;
    int lh = font.lineHeight();
    int fy = font.glyphOf(' ').y;
    
    for ( int char_i = 0; char_i < len; char_i++, i++ )
    {
      char c = str.charAt(char_i);
      
      if ( c == '\n' )
      {
        line_shift += font.lineHeight();
        tw = 0;
        i--;
        continue;
      }
      
      Point4i p = font.glyphOf(c);
      if ( p.z == 0 )
      {
        i--;
        continue;
      }
      
      int index = 4*3*i;
      int a = 4*i;
      int py = p.y - fy;
      
      points[index++] = tw    ; points[index++] = line_shift+py    ; points[index++] = shift;
      points[index++] = tw+p.z; points[index++] = line_shift+py    ; points[index++] = shift;
      points[index++] = tw+p.z; points[index++] = line_shift+py+p.w; points[index++] = shift;
      points[index++] = tw    ; points[index++] = line_shift+py+p.w; points[index++] = shift;
      tw = tw+p.z;
      
      index = 4*2*i;
      float ty = p.y/h;
      float th = (p.y+p.w)/h;
      texs[index++] = p.x       / w; texs[index++] = ty;
      texs[index++] = (p.x+p.z) / w; texs[index++] = ty;
      texs[index++] = (p.x+p.z) / w; texs[index++] = th;
      texs[index++] = p.x       / w; texs[index++] = th;
      
      index = 2*3*i;
      indexes[index+0] = a+0;
      indexes[index+1] = a+1;
      indexes[index+2] = a+2;
      indexes[index+3] = a+0;
      indexes[index+4] = a+2;
      indexes[index+5] = a+3;
      
      index = 4*4*i;
      colors[index++] = color.r; colors[index++] = color.g; colors[index++] = color.b; colors[index++] = color.a;
      colors[index++] = color.r; colors[index++] = color.g; colors[index++] = color.b; colors[index++] = color.a;
      colors[index++] = color.r; colors[index++] = color.g; colors[index++] = color.b; colors[index++] = color.a;
      colors[index++] = color.r; colors[index++] = color.g; colors[index++] = color.b; colors[index++] = color.a;
      
      index = 4*3*i;
      normals[index++] = 0; normals[index++] = 0; normals[index++] = 1; 
      normals[index++] = 0; normals[index++] = 0; normals[index++] = 1; 
      normals[index++] = 0; normals[index++] = 0; normals[index++] = 1; 
      normals[index++] = 0; normals[index++] = 0; normals[index++] = 1;
    }
    
    Mesh mesh = new Mesh();
    mesh.setBuffer(VertexBuffer.Type.Position, 3, points);
    mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, texs);
    mesh.setBuffer(VertexBuffer.Type.Index, 3, indexes);
    mesh.setBuffer(VertexBuffer.Type.Color, 4, colors );
    mesh.setBuffer(VertexBuffer.Type.Normal, 3, normals);
    
    mesh.setMode(Mesh.Mode.Triangles);
    mesh.updateBound();
    
    
    Geometry geom = new Geometry();
    geom.setMaterial(fontMaterial);
    geom.setMesh(mesh);
    geom.setLocalTranslation(x+shiftX, y+shiftY, 0);
    
    hudNode.attachChild(geom);
    shift += addShift;
  }
  
  public void drawStringInBox(int x, int y, int maxW, int maxH, String str)
  {
    String parts[] = str.split("\n|\r|(\n\r)");
    
    int posy = 0;
    int posx = 0;
    int lh = font.lineHeight();
    
    for ( String part : parts )
    {
      for ( int i = 0; i < part.length(); i++ )
      {
        char c = part.charAt(i);

        Point4i p = font.glyphOf(c);
        if ( p.w * p.z == 0 )
          continue;
        
        if ( posx + p.z >  maxW )
        {
          posy += lh;
          if ( posy > maxH )
            break;
          posx = 0;
        }

        drawImage(font.image(), x+posx, maxH+y-posy-lh, p.x, p.y, p.z, p.w);
        posx += p.z+2;
      }
      posy += lh;
      if ( posy > maxH )
        break;
      posx = 0;
    }
  }
  
  public void done()
  {
    hudNode.depthFirstTraversal(new SceneGraphVisitor() {

      @Override
      public void visit(Spatial spatial)
      {
        spatial.setQueueBucket(RenderQueue.Bucket.Translucent);
      }
    });
    hudNode.batch();
    
    hudNode.updateGeometricState();
    
    viewPort.setClearColor(true);
    viewPort.setClearDepth(true);
    viewPort.setBackgroundColor(ColorRGBA.BlackNoAlpha);
    viewPort.setOutputFrameBuffer(buffer);
    Main.instance.getRenderManager().renderViewPort(viewPort, 0);
    hudNode.detachAllChildren();
    shift = addShift;
    shiftX = 0;
    shiftY = 0;
  }
}
3 Likes

I still have the problem.
To avoid a pure “up” reply:

  • line edit is working, with selection, copy and past, using reflection for everything awt related so if the application doesn’t run on a desktop the clipboard will not be handled (i don’t have the code to use clipboard on android) but the application will still run.
  • as a proof of concept i implemented a small tetrix game in the gui:

    (and as you can see … i suck at this game :stuck_out_tongue: )

The code of the tetrix (not compilable, just for the record)
(it’s not the best tetrix i ever did)

    /*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package mygame.gui.widgets.tetrix;

import com.jme3.asset.AssetManager;
import com.jme3.input.KeyInput;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import javax.vecmath.Point2i;
import mygame.gui.AGUI;
import mygame.gui.Graphics;
import mygame.gui.GuiElementAdapter;
import mygame.gui.Style;
import mygame.gui.Tools;
import mygame.inputs.DeviceManager;
import mygame.inputs.KeyboardDevice;

/**
 *
 * @author Bubuche
 */
public class Tetrix extends GuiElementAdapter
{
  private int cells[];
  private int line_index[];
  private int width;
  private int height;
  private int score;
  
  private Piece current;
  private Point2i currentPosition;
  private Piece next;
  private Texture block;
  private Material empty;
  private Material material;
  private ColorRGBA pieceColor;
  private float timeToNextStep;
  
  int block_w, block_h;
  
  private static final ColorRGBA border = new ColorRGBA(200/255f, 200/255f, 200/255f, 1);
  private static final Piece[] pieces;
  private static final ColorRGBA colors[];
  static
  {
    int i = 1;
    pieces = new Piece[]
    {
      new Piece(i++, new boolean[][]
      {
        {true, true, true, true} /* xxxx */
      }),
      new Piece(i++, new boolean[][]
      {
        {true, true, true},  /* xxx */
        {true, false, false} /* x   */
      }),
      new Piece(i++, new boolean[][]
      {
        {true, true, true},  /* xxx */
        {false, true, false} /*  x  */
      }),
      new Piece(i++, new boolean[][]
      {
        {true, true, true},  /* xxx */
        {false, false, true} /*   x */
      }),
      new Piece(i++, new boolean[][]
      {
        {true, true}, /* xx */
        {true, true}  /* xx */
      }),
      new Piece(i++, new boolean[][]
      {
        {false, true, true}, /*  xx */
        {true, true, false}  /* xx  */
      }),
      new Piece(i++, new boolean[][]
      {
        {true, true, false}, /* xx  */
        {false, true, true}  /*  xx */
      }),
      
    };
    
    colors = new ColorRGBA[pieces.length];
    float h = 1.f / pieces.length;
    
    for ( i = 0; i < colors.length; i++ )
      colors[i] = Tools.hsv((i*360f)/ pieces.length, 1, 1);
  }
  
  public Tetrix(int width, int height)
  {
    this.width = width;
    this.height = height;
    this.cells = new int[width * height];
    this.line_index = new int[height];
    this.currentPosition = new Point2i();
    this.next = createRandomPiece();
    this.pieceColor = new ColorRGBA();
    this.timeToNextStep = 0.5f;
    goNextPiece();
    
    
    
    AssetManager am = AGUI.instance.application().getAssetManager();
    block = am.loadTexture("Textures/tetrix_block.png");
    
    Image block_img = block.getImage();
    block_w = block_img.getWidth();
    block_h = block_img.getHeight();
    
    material = new Material(am, "Common/MatDefs/Misc/Unshaded.j3md");
    empty = material.clone();
    
    material.setTexture("ColorMap", block);
    material.setBoolean("VertexColor", true);
    material.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.PremultAlpha);
    
    empty.setTexture("ColorMap", am.loadTexture("Textures/empty.png"));
    empty.getTextureParam("ColorMap").getTextureValue().setWrap(Texture.WrapMode.Repeat);
    
  }
  
  public Piece createRandomPiece()
  {
    Piece base = pieces[(int) (Math.random() * pieces.length)];
    int nb = (int) (Math.random() * 10);
    for ( int i = 0; i < nb; i++ )
    {
      base = base.next;
    }
    return base;
  }
  
  public final void goNextPiece()
  {
    this.current = next;
    this.currentPosition.set((width - this.current.width())/ 2 , 0);
    this.next = createRandomPiece();
  }
  
  private int removeLines()
  {
    int k = height-1;
    for ( int i = height-1; i >= 0; i-- )
    {
      if ( ! fullLine(i) )
        line_index[k--] = i;
    }
    if ( k == -1 ) return 0;
    score += k+1;
    line_index[k] = -1;
    
    for ( int i = height-1; i > k; i-- )
    {
      for ( int j = 0; j < width; j++ )
      {
        cells[indexOf(j, i)] = cells[indexOf(j, line_index[i])];
      }
    }
    
    for ( int i = 0; i <= k; i++ )
    {
      for ( int j = 0; j < width; j++ )
      {
        cells[indexOf(j, i)] = 0;
      }
    }
    return height - k;
  }
  
  private boolean fullLine(int line)
  {
    for ( int i = 0; i < width; i++ )
    {
      if ( cells[indexOf(i, line)] == 0 )
        return false;
    }
    return true;
  }
  
  @Override
  protected void calculateDimension()
  {
    dimension.x = (width+5) * (block_w-1) + 1;
    dimension.y = height * (block_h-1);
  }
  
  @Override
  public void update(float tpf)
  {
    handleMovement();
    
    timeToNextStep -= tpf;
    if ( timeToNextStep > 0 )
      return;
    
    timeToNextStep = 0.5f;
    
    if ( pieceCanGoDown() )
      currentPosition.y += 1;
    else
    {
      validatePiece();
      goNextPiece();
    }
    
    removeLines();
  }
  
  @Override
  public void paint(Graphics g)
  {
    int ts = block.getImage().getWidth();
    int dx = width * (ts-1) + 1;
    g.setColor(border);
    g.drawLine(0, dimension.y, dx, dimension.y);
    g.drawLine(dx, 0, dx, dimension.y);
    
    g.setColor(ColorRGBA.White);
    g.repeatImage(
            empty, 
            0, 
            0, 
            dx, 
            dimension.y,
            width, 
            height);
    
    for ( int i = 0; i < height; i++ )
    {
      for ( int j = 0; j < width; j++ )
      {
        int color = cells[indexOf(j, i)];
        if ( color == 0 )
          continue;
        
        g.setColor(colors[color-1]);
        g.drawImage(
                material, 
                j*(block_w-1), 
                i*(block_h-1), 
                block_w, 
                block_h);
      }
    }
    
    drawPiece(
      g, 
      current, 
      currentPosition.x*(block_w-1), 
      currentPosition.y*(block_h-1));
    
    int sx = dx + ((5 - next.width())*ts)/2;
    int sy = ((5 - next.height())*ts)/2;
    
    drawPiece(
      g, 
      next, 
      sx, 
      sy);
     
    
    sx = dx;
    sy = 5*block_h;
    
    Style style = AGUI.styleOf(this);
    g.setColor(style.foreground());
    g.drawString(String.format("Score: %5d", score), sx, sy);
  }
  
  private void drawPiece(Graphics g, Piece piece, int x, int y)
  {
    /*
    pieceColor.set(colors[piece.value()-1]);
    pieceColor.a = 0.5f
    g.setColor(pieceColor);
    */
    g.setColor(colors[piece.value()-1]);
    for ( int i = 0; i < piece.height(); i++ )
    {
      for ( int j = 0; j < piece.width(); j++ )
      {
        if ( ! piece.at(j, i) )
          continue;
        
        g.drawImage(
                material, 
                x + j*(block_w-1), 
                y + i*(block_h-1), 
                block_w, 
                block_h);
      }
    }
  }
  
  private int indexOf(int x, int y)
  {
    return (y*width)+x;
  }
  
  private void handleMovement()
  {
    KeyboardDevice device = AGUI.instance.application().getStateManager().getState(DeviceManager.class).keyboard();
    
    if ( device.pressedThisFrame(KeyInput.KEY_LEFT) )
    {
      if ( pieceCanBeThere(current, currentPosition.x-1, currentPosition.y) )
        currentPosition.x -= 1;
    }
    else if ( device.pressedThisFrame(KeyInput.KEY_RIGHT) )
    {
      if ( pieceCanBeThere(current, currentPosition.x+1, currentPosition.y) )
        currentPosition.x += 1;
    }
    else if ( device.pressedThisFrame(KeyInput.KEY_SPACE) )
    {
      if ( pieceCanBeThere(current.next, currentPosition.x, currentPosition.y) )
        current = current.next;
    }
    else if ( device.pressedThisFrame(KeyInput.KEY_DOWN) )
    {
      timeToNextStep = 0;
    }
  }

  private boolean pieceCanGoDown()
  {
    return pieceCanBeThere(current, currentPosition.x, currentPosition.y+1);
  }
  
  private boolean pieceCanBeThere(Piece piece, int x, int y)
  {
    if ( y + piece.height() > height ) return false;
    if ( x + piece.width()  > width  ) return false;
    if ( x < 0 )                       return false;
    
    for ( int i = 0; i < piece.height(); i++ )
    {
      for ( int j = 0; j < piece.width(); j++ )
      {
        if ( ! piece.at(j, i) )
          continue;
        if ( cells[indexOf(x+j, y+i)] > 0 )
          return false;
      }
    }
    
    return true;
  }
  
  private void validatePiece()
  {
    for ( int i = 0; i < current.height(); i++ )
    {
      for ( int j = 0; j < current.width(); j++ )
      {
        if ( ! current.at(j, i) )
          continue;
        cells[indexOf(currentPosition.x+j, currentPosition.y+i)] = current.value();
      }
    }
  }
  
  
  public static class Piece
  {
    private int value;
    private boolean[][] filled;
    private Piece prec;
    private Piece next;
    
    public Piece(int value, boolean[][] filled)
    {
      this.value = value;
      this.filled = filled;
      
      boolean[][] last = filled;
      boolean[][] n;
      Piece lastPiece = this;
      Piece nextPiece;
      
      while(true)
      {
        n = rotate(last);
        if ( compareArrays(n, filled) )
          break;
        
        last = n;
        nextPiece = new Piece();
        nextPiece.value = value;
        nextPiece.filled = last;
        nextPiece.prec = lastPiece;
        lastPiece.next = nextPiece;
        
        lastPiece = nextPiece;
      }
      
      lastPiece.next = this;
      this.prec = lastPiece;
    }
    
    public int value()
    {
      return this.value;
    }
    
    public Piece next()
    {
      return this.next;
    }
    
    public int width()
    {
      return this.filled[0].length;
    }
    
    public int height()
    {
      return this.filled.length;
    }
    
    public boolean at(int x, int y)
    {
      return filled[y][x];
    }
    
    private Piece()
    {
      
    }
    
    private boolean[][] rotate(boolean[][] in)
    {
      int h = in.length;
      int w = in[0].length;
      
      boolean[][] result = new boolean[w][h];
      for ( int i = 0; i < h; i++ )
      {
        for ( int j = 0; j < w; j++ )
        {
          result[j][i] = in[h - i - 1][j];
        }
      }
      return result;
    }
    
    private boolean compareArrays(boolean[][] a, boolean[][] b)
    {
      if ( a.length != b.length )
        return false;
      
      for ( int i = 0; i < a.length; i++ )
      {
        for ( int j = 0; j < a[0].length; j++ )
        {
          if ( a[i][j] != b[i][j] )
            return false;
        }
      }
      return true;
    }
  }
}
1 Like