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;
}
}