Is what you are looking for: a dynamic texture that you can draw pixels onto that you can then put onto a quad (Lemur works fine with spatials so that should be fine there). I did something like that for a radar display.
If so this may help. You can call setPixel
(circles were important to me so there is a method for that as well) then when you are finished building it up call getTexture()
to get a JMonkeyEngine Texture you can use as normal. (You can also get an antiAliased texture)
/**
* @author Richard Tingle
* License: public domain
*/
import com.jme3.math.ColorRGBA;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.MagFilter;
import com.jme3.texture.Texture2D;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
public class DrawableTexture{
public static int ENTIES_PER_PIXEL=4;
private enum ColorEntry{
RED,
GREEN,
BLUE,
ALPHA
}
protected byte[] data;
protected byte[] antiAliasedData;
protected Image image;
protected Texture2D texture;
int sizeX;
int sizeY;
public DrawableTexture(int width, int height) {
this.sizeX = width;
this.sizeY = height;
// create black image
data = new byte[width * height * 4];
antiAliasedData=new byte[data.length];
setBackground (new ColorRGBA(0,0,0,255));
// set data to texture
ByteBuffer buffer = BufferUtils.createByteBuffer(data);
image = new Image(Format.RGBA8, width, height, buffer);
texture = new Texture2D(image);
texture.setMagFilter(Texture.MagFilter.Nearest);
}
/**
* used for any drawable texture that can be rotated by the video block
* when rotated the edge gets stretched to the end, so it needs to be
* transparent
*/
public void createTransparentEdge(){
ColorRGBA transparent =new ColorRGBA(0,0,0,0);
for(int i=0;i<sizeX;i++){
setPixel(i, 0,transparent);
setPixel(i, sizeY-1,transparent);
}
for(int i=0;i<sizeY;i++){
setPixel(0, i, transparent);
setPixel(sizeX-1, i, transparent);
}
}
public Texture getTexture() {
ByteBuffer buffer = BufferUtils.createByteBuffer(data);
image.setData(buffer);
return texture;
}
public Texture getTexture_antiAliased(){
for(int i=0;i<sizeX;i++){
for(int j=0;j<sizeY;j++){
int index = (i + j * sizeX) * ENTIES_PER_PIXEL;
antiAliasedData[index] = getAntiAliasedEntry(i, j, ColorEntry.RED);
antiAliasedData[index + 1] = getAntiAliasedEntry(i, j, ColorEntry.GREEN);
antiAliasedData[index + 2] = getAntiAliasedEntry(i, j, ColorEntry.BLUE);
antiAliasedData[index + 3] = getAntiAliasedEntry(i, j, ColorEntry.ALPHA);
}
}
ByteBuffer buffer = BufferUtils.createByteBuffer(antiAliasedData);
image.setData(buffer);
return texture;
}
private byte getAntiAliasedEntry(int x, int y, ColorEntry element){
int returnValue=(int)(
0.5*getEntry(x,y,element) +
(1.0/12)*getEntry(x+1,y,element) +
(1.0/12)*getEntry(x,y+1,element) +
(1.0/12)*getEntry(x-1,y,element) +
(1.0/12)*getEntry(x,y-1,element) +
(1.0/24)*getEntry(x+1,y+1,element) +
(1.0/24)*getEntry(x+1,y-1,element) +
(1.0/24)*getEntry(x-1,y+1,element) +
(1.0/24)*getEntry(x-1,y-1,element)
);
if (returnValue>127){
returnValue-=256;
}
return (byte)returnValue;
}
private int getEntry(int x, int y, ColorEntry element){
if (x>=sizeX || y>=sizeY || x<0 || y<0){
return 0; //outside the image
}
int i = (x + y * sizeX) * 4;
byte dataValue;
switch(element){
case RED:
dataValue=data[i];
break;
case GREEN:
dataValue=data[i+1];
break;
case BLUE:
dataValue=data[i+2];
break;
case ALPHA:
dataValue=data[i+3];
break;
default:
throw new RuntimeException("Element does not exist");
}
return (dataValue<0?dataValue+256:dataValue);
}
private void setEntry(int x, int y, ColorEntry element, int newValue){
if (x>=sizeX || y>=sizeY || x<0 || y<0){
return; //outside the image
}
int i = (x + y * sizeX) * 4;
byte dataValue=(byte)(newValue>127?newValue-256:newValue);
switch(element){
case RED:
data[i]=dataValue;
break;
case GREEN:
data[i+1]=dataValue;
break;
case BLUE:
data[i+2]=dataValue;
break;
case ALPHA:
data[i+3]=dataValue;
break;
default:
throw new RuntimeException("Element does not exist");
}
}
public void setPixel(int x, int y, ColorRGBA color) {
if (x>=sizeX || y>=sizeY || x<0 || y<0){
return; //outside the image
}
int i = (x + y * sizeX) * 4;
//color.asBytesRGBA() extracted to avoid temporary byte[]
data[i] = (byte) ((int) (color.r * 255) & 0xFF); // r
data[i + 1] = (byte) ((int) (color.g * 255) & 0xFF); // g
data[i + 2] = (byte) ((int) (color.b * 255) & 0xFF); // b
data[i + 3] = (byte) ((int) (color.a * 255) & 0xFF); // a
}
public void setPixel(int x, int y, byte[] color) {
if (x>=sizeX || y>=sizeY || x<0 || y<0){
return; //outside the image
}
int i = (x + y * sizeX) * 4;
data[i] = color[0]; // r
data[i + 1] = color[1]; // g
data[i + 2] = color[2]; // b
data[i + 3] = color[3]; // a
}
public ColorRGBA getPixel(int x, int y) {
if (x>=sizeX || y>=sizeY || x<0 || y<0){
return new ColorRGBA(0,0,0,0); //outside the image
}
ColorRGBA color=new ColorRGBA();
int i = (x + y * sizeX) * 4;
color.set(data[i], data[i + 1], data[i + 2], data[i + 3]);
return color;
}
public void setCircle(int x, int y, double radius, ColorRGBA color){
byte[] byteColor=color.asBytesRGBA();
for(int i=(int)(x-radius-1);i<x+radius+1;i++){
//see http://stackoverflow.com/questions/14285358/find-all-integer-coordinates-in-a-given-radius
//-sqrt(r^2 - x^2) to sqrt(r^2 - x^2)
final int minJ=(int)-Math.sqrt(radius*radius-(i-x)*(i-x))+y;
final int maxJ=(int)+Math.sqrt(radius*radius-(i-x)*(i-x))+y;
for(int j=minJ;j<maxJ;j++){
setPixel(i,j, byteColor);
}
}
}
public final void setBackground(ColorRGBA color) {
byte[] colorBytes=color.asBytesRGBA();
for (int i = 0; i < sizeX * sizeY * 4; i += 4) {
data[i] = colorBytes[0]; // r
data[i + 1] = colorBytes[1]; // g
data[i + 2] = colorBytes[2]; // b
data[i + 3] = colorBytes[3]; // a
}
}
public void setMagFilter(MagFilter filter) {
texture.setMagFilter(filter);
}
public int getSizeX() {
return sizeX;
}
public int getSizeY() {
return sizeY;
}
}
If you’re updating this at a high frame rate you may want to look at alternatives (that don’t fully push a new buffer each time) but for occasionally refreshed dynamic textures this has worked well for me