Hey so i have been on the forums trying to figure out how to do this for a few times now and I have finally found a way to do it. So just to give back to a community that is very helpful, I am going to be posting some of the code from the project i am working on, which is basically a world editor. I know there are probably plenty of people interested in making world editors or something like it, so here is an example (which should be usable in its current form) of how to do model previews.
import com.jme3.app.SimpleApplication;
import com.jme3.bounding.BoundingBox;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.post.SceneProcessor;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Spatial;
import com.jme3.system.AppSettings;
import com.jme3.system.JmeContext;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image;
import com.jme3.util.BufferUtils;
import com.jme3.util.Screenshots;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JPanel;
public class AssetPreview extends SimpleApplication implements SceneProcessor
{
private FrameBuffer offBuffer;
private ViewPort offView;
private Camera offCamera;
private AssetPreview.ImageDisplay display;
private int x, y, width, height;
private final ByteBuffer cpuBuf;
private final BufferedImage image;
private String model;
private boolean isStarted = false;
private boolean startRender = false;
private Thread curThread;
private class ImageDisplay extends JPanel
{
@Override
public void paintComponent(Graphics gfx)
{
super.paintComponent(gfx);
Graphics2D g2d = (Graphics2D) gfx;
synchronized (image)
{
g2d.drawImage(image, null, 0, 0);
}
}
}
public AssetPreview(int x, int y, int width, int height)
{
// initializing members
this.x = x;
this.y = y;
this.width = width;
this.height = height;
cpuBuf = BufferUtils.createByteBuffer(width * height * 4);
image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
// making panel
display = new AssetPreview.ImageDisplay();
Logger logger = Logger.getLogger(AssetPreview.class.getName());
logger.getLogger("").setLevel(Level.OFF);
}
public void loadModel(String model)
{
while (curThread != null && curThread.isAlive())
{
try
{
Thread.sleep(2);
} catch (Exception e)
{
e.printStackTrace();
}
}
this.model = model;
if (isStarted)
{
this.restart();
} else
{
isStarted = true;
// setting up jmonkey renderer
this.setPauseOnLostFocus(false);
AppSettings settings = new AppSettings(true);
settings.setResolution(1, 1);
this.setSettings(settings);
this.start(JmeContext.Type.OffscreenSurface);
}
}
public void setupOffscreenView()
{
offCamera = new Camera(width, height);
// create a pre-view. a view that is rendered before the main view
offView = renderManager.createPreView(“Offscreen View”, offCamera);
offView.setBackgroundColor(ColorRGBA.White);
offView.setClearFlags(true, true, true);
// this will let us know when the scene has been rendered to the
// frame buffer
offView.addProcessor(this);
// create offscreen framebuffer
offBuffer = new FrameBuffer(width, height, 1);
// setup framebuffer’s cam
offCamera.setFrustumPerspective(45f, 1f, 1f, 1000f);
offCamera.setLocation(new Vector3f(0f, 0f, -2.6f));
offCamera.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);
// setup framebuffer to use renderbuffer
// this is faster for gpu → cpu copies
offBuffer.setDepthBuffer(Image.Format.Depth);
offBuffer.setColorBuffer(Image.Format.RGBA8);
// set viewport to render to offscreen framebuffer
offView.setOutputFrameBuffer(offBuffer);
}
@Override
public void simpleInitApp()
{
curThread = Thread.currentThread();
setupOffscreenView();
// show model
Spatial loadedModel = assetManager.loadModel(model);
float height = ((BoundingBox) loadedModel.getWorldBound()).getYExtent();
float width = ((BoundingBox) loadedModel.getWorldBound()).getZExtent();
// scale the model to size 1
float scale = 0.0f;
if (height > width)
{
scale = 1f / height;
}else{
scale = 1f / width;
}
loadedModel.scale(scale, scale, scale);
// center the model
loadedModel.setLocalTranslation(loadedModel.getWorldBound().getCenter().mult(-1));
rootNode.attachChild(loadedModel);
// create light
DirectionalLight light = new DirectionalLight();
light.setDirection(new Vector3f(0.0f, 0.0f, 1.0f));
rootNode.addLight(light);
offView.attachScene(rootNode);
startRender = true;
}
public JPanel getJPanel()
{
display.setLocation(x, y);
display.setSize(width, height);
return (JPanel) display;
}
public void initialize(RenderManager rm, ViewPort vp)
{
}
public void reshape(ViewPort vp, int w, int h)
{
}
public boolean isInitialized()
{
return true;
}
public void preFrame(float tpf)
{
}
public void postQueue(RenderQueue rq)
{
}
/**
- Update the CPU image’s contents after the scene has been rendered to the
-
framebuffer.
*/
public void postFrame(FrameBuffer out)
{
if (startRender)
{
cpuBuf.clear();
renderer.readFrameBuffer(offBuffer, cpuBuf);
synchronized (image)
{
Screenshots.convertScreenShot(cpuBuf, image);
}
if (display != null)
{
display.repaint();
}
startRender = false;
this.stop(true);
}
}
public void cleanup()
{
}
}
I literally just got this working, so I will probably edit this code later on since it is an essential part of my project. Anyway it is really just a modification of the TestRenderToMemory.java example that doesn’t eat up 30% of your cpu. An example usage of the class would be:
JFrame window = new JFrame();
// … do all your window init junk
// the layout is null for absolute positioning. That’s how i use it, but you don’t have too.
window.setLayout(null);
AssetPreview assetPreview = new AssetPreview(x,y,width,height);
assetPreview.loadModel(“Models/Ninja/Ninja.mesh.xml”);
window.add(assetPreview.getJPanel());
window.setVisible(true);
And that’s it.
Btw, you can make multiple instances of the class (unlike a normal JME3 canvas) and you can also reuse the same one by calling loadModel again to load a different model.
I am going to add texture support in the next few days.
Tell me if you have any problems with it.