把JME程序“嵌入”到SWT界面
一般来说,如果我运行JME程序,那么其运行结果是出现在一个windows窗口中,如图:
但是在一些应用中,我需要把JME的显示结果在图形用户界面(比如说用Swing或者SWT/Jface做的图形用户界面)上显示出来,而不是又重新弹出一个窗口--这样怪麻烦,也不方便用户操作。对于Swing,JME提供了有一套方法可以方便的把JME程序的显示效果在Swing做的界面是显示出来(具体做法可参考JME提供的例子),但是对于SWT,JME就没有说明该怎么做了(似乎对于SWT来说有些不公平)。虽然没有说明,但是实际上我们还可以通过以Swing为过渡,使SWT实现这样的功能。不过这样好像有些麻烦了,因为自Eclipse3.2后,SWT提供了一个类--GLCanvas,可以使OpenGL在SWT的界面上绘制3D图形。这样就没有问题了,JME可以直接在SWT界面中绘制图形图像,因为JME本身是封装了OpenGL的API。
在介绍具体做法之前,我想说一下我碰到的一些问题。
第一个问题,应该怎么做?我原先的想法就是以swing作为过渡,而且我还不知道有GLCanvas这个类,更加不知道用法。在JME的论坛上搜索的时候发现了国外友人编的一份很好的源代码正是实现这个功能的。下载下来打开运行一看,果然不错,但是太复杂了,里面有很多其它的功能(像鼠标和键盘的事件响应等等),而我只是想知道把JME“嵌入”到SWT界面中的原理。不过不管怎么说,我要的东西肯定在这里面了。在大致上看了几遍代码后,我决定照着代码试着编一个,于是我又碰上了我的第二个问题。
第二个问题,为什么要继承BaseHeadlessApp类,而不是简单的SimpleGame类?首先,我想说明一下BaseHeadlessApp类与SimpleGame类的关系。这两个类都直接或间接的继承了AbstractGame类,其中BaseHeadlessApp类,看类名都知道,它实现的是“无头的”应用程序,就是没有一般窗口的最上面的那一条,而SimpleGame类则是提供了一个比较完善的可以构建满足简单需求的(比如说灯光、渲染系统、按键关联等等)系统,用户可以根据它构建简单的“游戏”,它当然是“有头“的。
如果你是要继承SimpleGame来实现这个功能的话,实际上也可以说基本上成功了,因为看到的效果是SWT的界面是出现了JME绘制的图形,但令人沮丧的是,弹出SWT界面的同时还弹出了JME原先的窗口,虽然这个窗口上什么也没有(而且还不停的被背景的图像所覆盖)。哈,这可怎么办?直觉告诉我,源代码!经过长时间的奋战和一个个子类与父类的查找与下载下来的代码的比较(为什么要继承BaseHeadlessApp?!)and不停的运行实验,我终于发现了,在SimpleGame中(实际上是他的一个父类中),display用了一个这样的方法:createWindow(int w, int h, int bpp, int frq, boolean fs),而在继承了BaseHeadlessApp类后,作者使用了display的createHeadlessWindow(int w, int h, int bpp)方法。哦,原来如此,单独运行JME程序需要使用createWindow(int w, int h, int bpp, int frq, boolean fs)方法,而需要嵌入到其它界面时则需要“无头”,在Swing中也是同样的道理。ok,第二个问题解决,下面是第三个问题。
第三个问题,GLCanvas类怎么用?啊,也许你会说,你不是下了源代码吗?源代码肯定有GLCanvas类的使用方法啊。嘿嘿,的确,照葫芦画瓢,我还真是把把“瓢”画出来了,只是,到底为什么呢?为什么要这么用?在这里要感谢一下海边沫沫同志了,他的一篇“使用Eclipse RCP进行桌面程序开发(六):向OpenGL进 军 ”使我茅塞顿开。在这里引用一下海边沫沫同志的话,有不对之处,望请见谅。比如GLCanvas的定义:“GLCanvas类可以直接创建一个用于OpenGL渲染的控件”,GLCanvas几个主要方法的使用意义:“setCurrent()方法就是为了把该控件的context设置为OpenGL的当前着色描述表,然后使用GL和GLU类中的方法在当前context上进行绘图,绘制完图形以后,再使用GLCanvas类的swapBuffers()方法交换缓冲区,也就是把context中的3D场景渲染到控件上。”还有出现的问题:“主要是因为我们渲染的场景很快会被操作系统的其他绘图操作所覆盖,只有不断的渲染我们才能看到连续的3D图形。”
ok,主要问题解决。实际上在编程的时候我还碰上了很多问题,不过都不是什么太大的障碍,这里就不一一:叙述了。
下面来看源代码:
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.opengl.GLCanvas;
import org.eclipse.swt.opengl.GLData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import com.jme.app.BaseHeadlessApp;
import com.jme.light.PointLight;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.state.LightState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.system.lwjgl.LWJGLDisplaySystem;
import com.jme.util.Timer;
public class MyGame extends BaseHeadlessApp {
private LWJGLDisplaySystem l;
private GLCanvas canvas;
private Composite parent;
private Camera cam;
protected Node rootNode;
private Timer timer;
private float tpf;
private LightState lightState;
/** *//**
* 断的渲染才能看到连续的3D图形
*
* @author jrkui
*
*/
private class Redrawer implements Runnable {
private int refreshTime = 10;
public void run() {
if (canvas != null) {
update(tpf);
render(tpf);
canvas.swapBuffers();
Display.getCurrent().timerExec(refreshTime, this);
}
}
}
/** *//**
* 启动JME
*
* @param _parent
*/
public void startGame(Composite _parent) {
parent = _parent;
try {
getAttributes();
initSystem();
assertDisplayCreated();
initGame();
} catch (Throwable t) {
t.printStackTrace();
}
Display.getCurrent().syncExec(new Redrawer());
}
protected void simpleInitGame() {
}
/** *//**
* 初始化游戏,实际上就是设置根节点,灯光等等
*/
@Override
protected void initGame() {
rootNode = new Node("rootNode");
ZBufferState buf = display.getRenderer().createZBufferState();
buf.setEnabled(true);
buf.setFunction(ZBufferState.CF_LEQUAL);
rootNode.setRenderState(buf);
PointLight light = new PointLight();
light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f));
light.setLocation(new Vector3f(100, 100, 100));
light.setEnabled(true);
lightState = display.getRenderer().createLightState();
lightState.setEnabled(true);
lightState.attach(light);
rootNode.setRenderState(lightState);
display.getRenderer().setBackgroundColor(ColorRGBA.gray);
simpleInitGame();
rootNode.updateGeometricState(0.0f, true);
rootNode.updateRenderState();
}
/** *//**
* 初始化系统
*/
@Override
protected void initSystem() {
l = (LWJGLDisplaySystem) DisplaySystem.getDisplaySystem();
display = l;
GLData data = new GLData();
data.doubleBuffer = true;
canvas = new GLCanvas(parent, SWT.BORDER, data);
canvas.setSize(parent.getSize().x, parent.getSize().y);
//无头的窗体
l.createHeadlessWindow(parent.getSize().x, parent.getSize().y,
properties.getDepth());
canvas.setCurrent();
cam = l.getRenderer().createCamera(640, 480);//初始化摄像头
cam.setFrustumPerspective(45, (float) l.getWidth()
/ (float) l.getHeight(), 2, 1000);
Vector3f loc = new Vector3f(0.0f, 0.0f, 25.0f);
Vector3f left = new Vector3f(-1.0f, 0.0f, 0.0f);
Vector3f up = new Vector3f(0.0f, 1.0f, 0.0f);
Vector3f dir = new Vector3f(0.0f, 0f, -1.0f);
cam.setFrame(loc, left, up, dir);
cam.update();
l.getRenderer().setCamera(cam);
timer = Timer.getTimer();
l.getRenderer().enableStatistics(true);
canvas.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
Point dimensions = canvas.getSize();
l.setWidth(dimensions.x);
l.setHeight(dimensions.y);
l.getRenderer().reinit(dimensions.x, dimensions.y);
}
});
}
@Override
protected void reinit() {
// TODO Auto-generated method stub
}
@Override
protected void render(float interpolation) {
display.getRenderer().clearStatistics();
display.getRenderer().clearBuffers();
display.getRenderer().draw(rootNode);
}
protected void simpleUpdate() {
}
@Override
protected void update(float interpolation) {
timer.update();
tpf = timer.getTimePerFrame();
simpleUpdate();
rootNode.updateGeometricState(tpf, true);
}
@Override
protected void cleanup() {
// TODO Auto-generated method stub
}
}
继承MyGame,绘制简单的3D图形:
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.shape.Box;
public class Box_MyGame extends MyGame {
private Box box;
@Override
protected void simpleInitGame() {
display.getRenderer().setBackgroundColor(ColorRGBA.gray);
box = new Box("Mybox", new Vector3f(0,0,0), new Vector3f(1,1,1));
box.setLocalScale(new Vector3f(6,6,6));
Quaternion q=new Quaternion().fromAngleAxis(0.5f, new Vector3f(1,1,0).normalize());
box.setLocalRotation(box.getLocalRotation().mult(q));
rootNode.attachChild(box);
}
@Override
protected void simpleUpdate() {
Quaternion q=new Quaternion().fromAngleAxis(0.01f, new Vector3f(0.3f,1,0.6f).normalize());
box.setLocalRotation(box.getLocalRotation().mult(q));
}
}
在SWT中“嵌入”JME:
import jrkui.swtjme.Box_MyGame;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class TestBox_MyGame {
/** *//**
* @param args
*/
public static void main(String[] args) {
Display swtdisplay = new Display();
Box_MyGame app = new Box_MyGame();
Shell shell= new Shell(swtdisplay);
shell.setText("Hello JME-SWT-World");
shell.setSize(640,480);
shell.open();
app.startGame(shell);
while (shell != null && ! shell.isDisposed()) {
try {
if (!swtdisplay.readAndDispatch()) {
swtdisplay.sleep();
}
} catch (Throwable e) {
e.printStackTrace();
}
}
swtdisplay.update();
}
}
运行效果图:
好了,具体就是这样了,有不足的地方欢迎大家提出。最后再次感谢一下海边沫沫与那位国际友人 。