


如果很不幸你的公司使用clearcase管理代码:
备份你的ccrc_xcleardiff,并下载附件 的脚本.
cd /home/peigen/soft/java/eclipse/plugins/com.ibm.rational.clearcase.compare_merge.linux.x86_7.0.1.D061004 (版本不一定一样哦)
cp ccrc_xcleardiff ccrc_xcleardiff.bak
把附件脚本放到该目录下,mv xx ccrc_xcleardiff
下载脚本:ccrc_xcleardiff
真不知道每天使用BeyondCompare,称其软件如何如何好,又不给人家交钱的同学是怎么个心态。
此为老文,综合meld的使用而已。
- 今天天气可真好,早早起来,冲凉,阳台上看书。。。 #
- 第一名,yeah//RT @cissysong: 前三名哟 RT @CainDior: 清纯无害双鱼男游过 RT @ag108lau: 【12星座心计排名】1、水瓶座 2、魔羯座 3、双子座 4、天蝎座 5、天秤座 6、处女座 7、射手座 8、巨蟹座 9、狮子座 10、金牛座 #
- 虾米挂了?搞虾米??? #
- 我也要打十个 http://peigen.info/?p=16 #
- 中午,江山一贱男跟我抢座位。 #
- MB的,你们组有几十个人,老子只有一个人,草泥马。。。不可协调,不伺候。。。MB #
- 拿手机看漫画好累,ipad终于有应用场景了 #
- 推广体操有屁用。每天被钉死在座位上,人家上班很fun,我上班很fuck #
- 没看出Alienware何德何能卖10000++,比Mac Pro更没有性价比 #
- 偶也想参加,给技术部的土人也推广下吧//@hotoo 打算在 Vim 7.3 正式发布的当周给小组的同事进行一次 Vim 分享。 #
- 强烈要求入团//@hotoo Vim 7.3 正式发布在即,好想购一件纪念 Tee,谁来发起下? #
- 什么植物和什么动物最像鸡 #
- 见识了商业分析师的力量,关键字:我不管、你们业务有问题、你们渠道有问题。。。您咋不分析分析,在线上跑了2年的东西,有问题早发现了,等到您分析出来的时候支付宝早被XX通干掉了。。。脏话就不骂了,明天恭候大驾。。。 #
- google calendar在教育领域的应用 http://bit.ly/b0MJg5 #
- google calendar怎么标记这一天是休假呢。。。比如国庆假期 #
- 晚上去浙江体育职业技术学院打球,缴费时一收银贱女,吹空调打脑残游戏,死活不给我充值,丫理直气壮的说:我不加班。老子啥时候能抬头挺胸的对客户说老子不加班。求人肉。。。 #
- xiami的新播放器,歌词在哪里,在哪里,在哪里。。。 #
- 谁说喜爱linux就一定要是内核达人,一定要是Shell达人???用Win的小三们,你丫知道Win的内核么?你丫知道Dos命令么。。。 #
- 打开虾米看了下偶的音乐收藏,风格差异巨大。。。有抒情口水歌,有方言Rap;有积极向上到不行的五月天,还有玛丽安.曼森 #
- 加班ing。。。开始写周报。。。 #
- 这玩意在我老机器上巨慢,还是chromuim好//RT @Ubuntu_Tips Midori: Ubuntu 上的轻量 Web 级浏览器 http://twurl.nl/5lnfvx #
- 你发现了什么 http://tweetphoto.com/40401885 #
- 在哪里打呢//RT @AVshare 哎哟喂,打了几个小时篮球回来,老命都快交待在球场上了! #
- 谁能私我一个foursquare的api地址呢? #
- 可爱的小家伙 http://tweetphoto.com/40713068 #
Powered by Twitter Tools
- [孔雀哥] 中国达人秀孔雀哥蒋仁瑞,一开始以为是你傻,可后来才知道你感动了世人 http://item.feedsky.com/~feedsky/menxinwen/~8097187/401115536/6202607/1/item.html #
- 大陆音乐已经烂到这个地步了。。。[裸体激情门] 暧昧小猫眯:《我第一次裸体激情戏,事后很害怕》,女演员拍刘嘉亮的《原来你并不快乐》的MV被导演张清峰逼迫脱光 http://bit.ly/aMuEMp #
- MD,大清早来开机启动报grub找不到磁盘分区,吓出一身冷汗。。。 #
- 团队一哥们装Ubuntu10.10未果。。。爱追新啊,在偶的拼命鼓动下已有3位同学加入Ubuntu阵营,另有2位意向。。。偶们组是支付宝最开源的组了吧 #
- 今天晚饭,2个素菜,半碗米饭,开始减肥计划,抵制脂肪肝 #
- 找个支持API的客户端,再找一个可以用的api即可//RT@freetstar 国内手机怎么上推? #
- CC is evil,项目的延期有一半是CC的功劳,特别是大规模的改造类项目,一次改几十个产品,就是几十次代码下载,上百次的rebase。。。。悲剧就这么产生了//RT@killer624 又要从cc迁移到svn了,整天在迁 #
- http://img.ly/1RPQ @yegle 呃,选择802.11那里,如图 #
- 为啥现在网上卖衣服的都用老外的模特。。。。为啥。。。。为啥。。。。 #
- 问题是买衣服的都是中国人啊。。。RT @heysql: 身材容貌都好呗||RT @peigen: 为啥现在网上卖衣服的都用老外的模特。。。。为啥。。。。为啥。。。。 #
- 暂时pc版的对linux用户还不够友好,可以先用pc选好买什么,下单后,用手机版付款。手机版慢,切内容不够丰富//RT @ttylikl: @peigen 什么时候才能可以很方便的在ubuntu下使用 支付宝 呢。。。。 #
- 该死的六楼食堂,饮料1.5一杯,面条8元,饭票10元,剩下的0.5,只能被黑掉。。。该死。。。该死,公司要提高警惕,用我们巨大的用户群威胁其降价。 #
- GQ对一MM的介绍太吊。如果你熟悉游戏,你必然知道她,那个抱着大熊裸露自己肆无忌惮的姑娘。88—62—87的围度,166厘米的高度。这样条件的姑娘北上广一抓一大把,敢脱敢露的不在话下,更甚有排队等着被“潜”的。http://bit.ly/acy6Rm #
- 泳池派对 比基尼美女水枪激射,妆花掉有几个能看的?http://bit.ly/aRlPy8 #
- 台湾前几届宅男女神-舒舒,偶就不喜欢这个长相,怪了//RT @SexNewsTW: [美腿] 0401視訊聊天室 http://cli.gs/vHDHU #
- 刚入手,屏幕也没有惨到不能看的地步//RT @jlusdy: 整体配置都好,就是屏幕太差了 RT @peigen 俺正考虑WildFire// RT @jlusdy:@listen1984 多谢答复,我准备入手G6了 #
- linux用户表示支持//RT @finscn: 当整个公司的大环境都是在windows下开发,而且开发出的东西常常windows only时, 我用mac os来工作实在是显得太格格不入了,而且必须承认 有时候工作效率会受影响. 但是我还是想坚持,即使顶着"装逼"的骂名。 #
- 悲剧,一个开发找测试同学给解决开发机的环境问题。。。。 #
- 看成Sex God//RT @lostheidi: 今天最大的杯具是出门忘记喷防晒霜和Six Gods,防晒霜就无所谓了反正也没救了,可是Six Gods啊,我这满目疮痍的四肢啊 T___T #
- 貌似偶在米团买的红酒又偷摸给我发货了。。。 #
- RT @imagelife: RT @ghosTM55: 杭州Linux用户组正式成立 -> http://groups.google.com/group/hzlug | 欢迎Retweet帮忙宣传
# - 求教//RT @feelinglucky: 教会了一哥们如何使用 ssh 公钥登录,他表示能极大得提高工作效率 #
- 呃好文,但是没有解决我动态密码的问题/RT @4over7: RT @feelinglucky: RT @peigen: 求教 // RT @feelinglucky: 教会了一哥们如何使用 ssh 公钥登录,他表示能极大得提高工作效率 // http://is.gd/efHaK #
- 吃饭未必要喝酒,喝酒未必要喝挂,中国人的酒文化就是扯淡。今天跟你喝到挂,转身就捅你一刀 #
- 也未必//RT @beralee: 至少喝挂的人把心里话都说出来了RT @peigen: 吃饭未必要喝酒,喝酒未必要喝挂,中国人的酒文化就是扯淡。今天跟你喝到挂,转身就捅你一刀 #
- 公司开年会邀请了客户?ipad派上用场了。。。偶的psp会不会有点寒酸呢。。。 #
- 老婆要杀了我的//RT @beralee: 咱们也搞一台吧 RT @peigen: 公司开年会邀请了客户?ipad派上用场了。。。偶的psp会不会有点寒酸呢。。。 #
- 偶一直这么干,祝福几分钟有点夸张了,偶认识一个假重庆人倒是话非常少的。//RT @zuola: 我打电话很节约电话费的办法:多回答"清楚""明白";先说"再见"但最后挂断电话。遇到过挂电话都祝福几分钟的热情的重庆人。 #
- 鄙社正在大面积招聘。http://bit.ly/diE5Bp 以目前支付宝的复杂程度,如果在职的不配做应用架构师,你能买来一个架构师么? #
- 某团购网站打出NICK2.7折,耐克2010新款nike AIR MAX TAILWIND 团拍价178元 全国包快递。。。不知道可以买不。。。http://bit.ly/9O6cac #
- 充话费在支付宝站内有两处地方,有点不一样,有一个是内嵌的淘宝的页面,不知你是怎么充的呢,也可以考虑给支付宝提bug,lab@alipay.com//RT @jlusdy: 支付宝天天喊着用户体验,怎么越改越差,充个花费也搞得人晕头转向的 #
- 有vim//RT @beralee: eclipse要是能有chrome或firefox那样的手势插件就好了 #
- mongodb也很容易上手//RT @jlusdy: 百无聊赖,决定下载几个NoSQL Database玩玩,Redis最近出现的频率比较高,就从它开始吧 #
- mark//RT @WangShuo: 教程在此。RT @yufenglu: 本人近日建 twip oauth api 的筆記 http://bit.ly/d0Ns1x 以及新版 gtap 的筆記 http://bit.ly/a4fODX #
- 百姓上有,不知道 靠谱不//RT @jason5ng32: 有人要出手二手3GS不?我有个哥们收购。 #
- 公司果汁机里打出一杯白水。。。加班的同学就是受到歧视。。。 #
- RT @Fenng: RT @mmqimiao: 高招RT @ggh0st: 买肉记:精肉13元一斤,我说要十斤。肉贩麻力地为我称了大块肉。我说要不了这么多,要八斤吧。于是肉贩切下几块肉再称,说正好八斤。我拿起被切下的肉将二十六元扔给肉贩转身便走。肉贩哭丧着脸喊:哥,再给五块钱吧 #
- 杭州,雷阵子实在忍不住了,抓紧最后一会儿,娱乐了一把。。。 #
- 刚洗了,没敢拿出来晾。。。//RT @popomore: 今天刚洗的衣服就下这么大的雨,什么狗屎运嘛 #
Powered by Twitter Tools
Step 10: Now right-click the jME project → Properties → Java Build Path → Libraries tab → Add Library… → User Library → Next → User Libraries… → New, enter jME required for the User library name, click OK. Select jME required → Add JARs…, browse to the folder yourworkspace/jME/lib/, then hold down the ctrl key and select:
-
jinput.jar
-
jogg-0.0.7.jar
-
jorbis-0.0.15.jar
-
junit-4.1.jar
-
lwjgl.jar
-
lwjgl-util-applet.jar
Then Expand lwjgl.jar → Native Library Location → Edit → Workspace… → expand jME → select lib → OK → OK → Finish.
就是说在最后需要选择lwjgl的本地库文件,就是那一堆的*.dll和*.so(linux下).
在你的工程按右键下选择Properties,选择 Java Build Path,找到Libraries选项卡,找到lwjgl.jar,展开后有一个Native library location点击后,在右边选择Edit,选择你放那一堆*.dll和*.so(linux下)的文件夹即可.
官方解决方案:http://www.jmonkeyengine.com/wiki/doku.php/no_lwjgl_in_java.library.path
Vector3f loc = new Vector3f(250.0f, 100.0f, 250.0f);
Vector3f left = new Vector3f(-0.5f, 0.0f, 0.5f);
Vector3f up = new Vector3f(0.0f, 1.0f, 0.0f);
Vector3f dir = new Vector3f(-0.5f, 0.0f, -0.5f);
Secondly, our window has been missing the title. We certainly want to put our title on the screen! To do this, we tell the DisplaySystem what our title is going to be. This is added to the top of initGame.
display.setTitle("Flag Rush");
Vector3f terrainScale = new Vector3f(4, 0.0575f, 4);
ZBufferState buf = display.getRenderer().createZBufferState(); buf.setEnabled(true); buf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo); scene.setRenderState(buf);
Cylinder postGeometry = new Cylinder("post", 10, 10, 1, 10);
Quaternion q = new Quaternion();
//rotate the cylinder to be vertical
q.fromAngleAxis(FastMath.PI/2, new Vector3f(1,0,0));
postGeometry.setLocalRotation(q);
postGeometry.setModelBound(new BoundingBox());
postGeometry.updateModelBound();
//We will share the post 4 times (one for each post)
//It is *not* a good idea to add the original geometry
//as the sharedmeshes will alter its local values.
//We then translate the posts into position.
//Magic numbers are bad, but help illustrate the point.:)
SharedMesh post1 = new SharedMesh("post1", postGeometry);
post1.setLocalTranslation(new Vector3f(0,0.5f,0));
SharedMesh post2 = new SharedMesh("post2", postGeometry);
post2.setLocalTranslation(new Vector3f(32,0.5f,0));
SharedMesh post3 = new SharedMesh("post3", postGeometry);
post3.setLocalTranslation(new Vector3f(0,0.5f,32));
SharedMesh post4 = new SharedMesh("post4", postGeometry);
post4.setLocalTranslation(new Vector3f(32,0.5f,32));
//put all the posts into a tower node
Node towerNode = new Node("tower");
towerNode.attachChild(post1);
towerNode.attachChild(post2);
towerNode.attachChild(post3);
towerNode.attachChild(post4);
//load a texture for the towers
TextureState ts2 = display.getRenderer().createTextureState();
Texture t2 = TextureManager.loadTexture(Lesson4.class.getClassLoader()
.getResource("jmetest/data/texture/post.jpg"),
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear);
ts2.setTexture(t2);
towerNode.setRenderState(ts2);
//This cylinder will be the horizontal struts that hold
//the field in place.
Cylinder strutGeometry = new Cylinder("strut", 10,10, 0.125f, 32);
strutGeometry.setModelBound(new BoundingBox());
strutGeometry.updateModelBound();
//again, we'll share this mesh.
//Some we need to rotate to connect various posts.
SharedMesh strut1 = new SharedMesh("strut1", strutGeometry);
Quaternion rotate90 = new Quaternion();
rotate90.fromAngleAxis(FastMath.PI/2, new Vector3f(0,1,0));
strut1.setLocalRotation(rotate90);
strut1.setLocalTranslation(new Vector3f(16,3f,0));
SharedMesh strut2 = new SharedMesh("strut2", strutGeometry);
strut2.setLocalTranslation(new Vector3f(0,3f,16));
SharedMesh strut3 = new SharedMesh("strut3", strutGeometry);
strut3.setLocalTranslation(new Vector3f(32,3f,16));
SharedMesh strut4 = new SharedMesh("strut4", strutGeometry);
strut4.setLocalRotation(rotate90);
strut4.setLocalTranslation(new Vector3f(16,3f,32));
//put all the struts into a single node.
Node strutNode = new Node("strutNode");
strutNode.attachChild(strut1);
strutNode.attachChild(strut2);
strutNode.attachChild(strut3);
strutNode.attachChild(strut4);
//load a texture for the struts
TextureState ts3 = display.getRenderer().createTextureState();
Texture t3 = TextureManager.loadTexture(Lesson4.class.getClassLoader()
.getResource("jmetest/data/texture/rust.jpg"),
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear);
ts3.setTexture(t3);
strutNode.setRenderState(ts3);
//Create the actual forcefield
//The first box handles the X-axis, the second handles the z-axis.
//We don't rotate the box as a demonstration on how boxes can be
//created differently.
Box forceFieldX = new Box("forceFieldX", new Vector3f(-16, -3f, -0.1f), new Vector3f(16f, 3f, 0.1f));
forceFieldX.setModelBound(new BoundingBox());
forceFieldX.updateModelBound();
//We are going to share these boxes as well
SharedMesh forceFieldX1 = new SharedMesh("forceFieldX1",forceFieldX);
forceFieldX1.setLocalTranslation(new Vector3f(16,0,0));
SharedMesh forceFieldX2 = new SharedMesh("forceFieldX2",forceFieldX);
forceFieldX2.setLocalTranslation(new Vector3f(16,0,32));
//The other box for the Z axis
Box forceFieldZ = new Box("forceFieldZ", new Vector3f(-0.1f, -3f, -16), new Vector3f(0.1f, 3f, 16));
forceFieldZ.setModelBound(new BoundingBox());
forceFieldZ.updateModelBound();
//and again we will share it
SharedMesh forceFieldZ1 = new SharedMesh("forceFieldZ1",forceFieldZ);
forceFieldZ1.setLocalTranslation(new Vector3f(0,0,16));
SharedMesh forceFieldZ2 = new SharedMesh("forceFieldZ2",forceFieldZ);
forceFieldZ2.setLocalTranslation(new Vector3f(32,0,16));
//add all the force fields to a single node
Node forceFieldNode = new Node("forceFieldNode");
forceFieldNode.attachChild(forceFieldX1);
forceFieldNode.attachChild(forceFieldX2);
forceFieldNode.attachChild(forceFieldZ1);
forceFieldNode.attachChild(forceFieldZ2);
//load a texture for the force field elements
TextureState ts = display.getRenderer().createTextureState();
Texture t = TextureManager.loadTexture(Lesson4.class.getClassLoader()
.getResource("jmetest/data/texture/reflector.jpg"),
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear);
t.setWrap(Texture.WrapMode.Repeat);
t.setTranslation(new Vector3f());
ts.setTexture(t);
forceFieldNode.setRenderState(ts);
Node forceFieldFence = new Node("fence");
//Attach all the pieces to the main fence node
forceFieldFence.attachChild(forceFieldNode);
forceFieldFence.attachChild(towerNode);
forceFieldFence.attachChild(strutNode);
scene.attachChild(forceFieldFence);
//Add the alpha values for the transparent node BlendState as1 = DisplaySystem.getDisplaySystem().getRenderer().createBlendState(); as1.setBlendEnabled(true); as1.setSourceFunction(BlendState.SourceFunction.SourceAlpha); as1.setDestinationFunction(BlendState.DestinationFunction.One); as1.setTestEnabled(true); as1.setTestFunction(BlendState.TestFunction.GreaterThan); as1.setEnabled(true); forceFieldNode.setRenderState(as1);
forceFieldNode.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT); towerNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE); strutNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE); and in buildTerrain: tb.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
//We will use the interpolation value to keep the speed
//of the forcefield consistent between computers.
//we update the Y have of the texture matrix to give
//the appearance the forcefield is moving.
t.getTranslation().y += 0.3f * interpolation;
//if the translation is over 1, it's wrapped, so go ahead
//and check for this (to keep the vector's y value from getting
//too large.)
if(t.getTranslation().y > 1) {
t.getTranslation().y = 0;
}
//we will do a little 'tweaking' by hand to make it fit in the terrain a bit better. //first we'll scale the entire "model" by a factor of 5 forceFieldFence.setLocalScale(5); //now let's move the fence to to the height of the terrain and in a little bit. forceFieldFence.setLocalTranslation(new Vector3f(25, tb.getHeight(25,25) + 15, 25));
private void buildSkyBox() {
skybox = new Skybox("skybox", 10, 10, 10);
Texture north = TextureManager.loadTexture(
TestSkybox.class.getClassLoader().getResource(
"jmetest/data/texture/north.jpg"),
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear);
Texture south = TextureManager.loadTexture(
TestSkybox.class.getClassLoader().getResource(
"jmetest/data/texture/south.jpg"),
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear);
Texture east = TextureManager.loadTexture(
TestSkybox.class.getClassLoader().getResource(
"jmetest/data/texture/east.jpg"),
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear);
Texture west = TextureManager.loadTexture(
TestSkybox.class.getClassLoader().getResource(
"jmetest/data/texture/west.jpg"),
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear);
Texture up = TextureManager.loadTexture(
TestSkybox.class.getClassLoader().getResource(
"jmetest/data/texture/top.jpg"),
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear);
Texture down = TextureManager.loadTexture(
TestSkybox.class.getClassLoader().getResource(
"jmetest/data/texture/bottom.jpg"),
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear);
skybox.setTexture(Skybox.Face.North, north);
skybox.setTexture(Skybox.Face.West, west);
skybox.setTexture(Skybox.Face.South, south);
skybox.setTexture(Skybox.Face.East, east);
skybox.setTexture(Skybox.Face.Up, up);
skybox.setTexture(Skybox.Face.Down, down);
skybox.preloadTextures();
skybox.updateRenderState();
// scene.attachChild(skybox);
}
//we want to keep the skybox around our eyes, so move it with //the camera skybox.setLocalTranslation(cam.getLocation()); //Because we are changing the scene (moving the skybox) we need to update //the graph. scene.updateGeometricState(interpolation, true);
/*
* Copyright (c) 2003-2008 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package jmetest.flagrushtut;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import jmetest.renderer.TestSkybox;
import jmetest.terrain.TestTerrain;
import com.jme.app.BaseGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.light.DirectionalLight;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.SharedMesh;
import com.jme.scene.Skybox;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Cylinder;
import com.jme.scene.state.BlendState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.util.TextureManager;
import com.jme.util.Timer;
import com.jme.util.geom.Debugger;
import com.jmex.terrain.TerrainBlock;
import com.jmex.terrain.util.MidPointHeightMap;
import com.jmex.terrain.util.ProceduralTextureGenerator;
public class Lesson4 extends BaseGame {
private static final Logger logger = Logger.getLogger(Lesson4.class
.getName());
// the terrain we will drive over.
private TerrainBlock tb;
// The texture that makes up the "force field", we will keep a reference to it
// here to allow us to animate it.
private Texture t;
//Sky box (we update it each frame)
private Skybox skybox;
//the timer
protected Timer timer;
// Our camera object for viewing the scene
private Camera cam;
// the root node of the scene graph
private Node scene;
// display attributes for the window. We will keep these values
// to allow the user to change them
private int width, height, depth, freq;
private boolean fullscreen;
/**
* Main entry point of the application
*/
public static void main(String[] args) {
Lesson4 app = new Lesson4();
// We will load our own "fantastic" Flag Rush logo. Yes, I'm an artist.
app.setConfigShowMode(ConfigShowMode.AlwaysShow, Lesson4.class
.getClassLoader().getResource(
"jmetest/data/images/FlagRush.png"));
app.start();
}
/**
* During an update we look for the escape button and update the timer
* to get the framerate. Things are now starting to happen, so we will
* update
*
* @see com.jme.app.BaseGame#update(float)
*/
protected void update(float interpolation) {
// update the time to get the framerate
timer.update();
interpolation = timer.getTimePerFrame();
//We will use the interpolation value to keep the speed
//of the forcefield consistent between computers.
//we update the Y have of the texture matrix to give
//the appearance the forcefield is moving.
t.getTranslation().y += 0.3f * interpolation;
//if the translation is over 1, it's wrapped, so go ahead
//and check for this (to keep the vector's y value from getting
//too large.)
if(t.getTranslation().y > 1) {
t.getTranslation().y = 0;
}
//we want to keep the skybox around our eyes, so move it with
//the camera
skybox.setLocalTranslation(cam.getLocation());
// if escape was pressed, we exit
if (KeyBindingManager.getKeyBindingManager().isValidCommand("exit")) {
finished = true;
}
//Because we are changing the scene (moving the skybox) we need to update
//the graph.
scene.updateGeometricState(interpolation, true);
}
/**
* draws the scene graph
*
* @see com.jme.app.BaseGame#render(float)
*/
protected void render(float interpolation) {
// Clear the screen
display.getRenderer().clearBuffers();
display.getRenderer().draw(scene);
Debugger.drawBounds(scene, display.getRenderer());
}
/**
* initializes the display and camera.
*
* @see com.jme.app.BaseGame#initSystem()
*/
protected void initSystem() {
// store the settings information
width = settings.getWidth();
height = settings.getHeight();
depth = settings.getDepth();
freq = settings.getFrequency();
fullscreen = settings.isFullscreen();
try {
display = DisplaySystem.getDisplaySystem(settings.getRenderer());
display.createWindow(width, height, depth, freq, fullscreen);
cam = display.getRenderer().createCamera(width, height);
} catch (JmeException e) {
logger.log(Level.SEVERE, "Could not create displaySystem", e);
System.exit(1);
}
// set the background to black
display.getRenderer().setBackgroundColor(ColorRGBA.black.clone());
// initialize the camera
cam.setFrustumPerspective(45.0f, (float) width / (float) height, 1,
5000);
Vector3f loc = new Vector3f(250.0f, 100.0f, 250.0f);
Vector3f left = new Vector3f(-0.5f, 0.0f, 0.5f);
Vector3f up = new Vector3f(0.0f, 1.0f, 0.0f);
Vector3f dir = new Vector3f(-0.5f, 0.0f, -0.5f);
// Move our camera to a correct place and orientation.
cam.setFrame(loc, left, up, dir);
/** Signal that we've changed our camera's location/frustum. */
cam.update();
/** Get a high resolution timer for FPS updates. */
timer = Timer.getTimer();
display.getRenderer().setCamera(cam);
KeyBindingManager.getKeyBindingManager().set("exit",
KeyInput.KEY_ESCAPE);
}
/**
* initializes the scene
*
* @see com.jme.app.BaseGame#initGame()
*/
protected void initGame() {
display.setTitle("Flag Rush");
scene = new Node("Scene graph node");
/** Create a ZBuffer to display pixels closest to the camera above farther ones. */
ZBufferState buf = display.getRenderer().createZBufferState();
buf.setEnabled(true);
buf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo);
scene.setRenderState(buf);
//Add terrain to the scene
buildTerrain();
//Light the world
buildLighting();
//add the force field fence
buildEnvironment();
//Add the skybox
buildSkyBox();
// update the scene graph for rendering
scene.updateGeometricState(0.0f, true);
scene.updateRenderState();
}
/**
* buildEnvironment will create a fence. This is done by hand
* to show how to create geometry and shared this geometry.
* Normally, you wouldn't build your models by hand as it is
* too much of a trial and error process.
*/
private void buildEnvironment() {
//This is the main node of our fence
Node forceFieldFence = new Node("fence");
//This cylinder will act as the four main posts at each corner
Cylinder postGeometry = new Cylinder("post", 10, 10, 1, 10);
Quaternion q = new Quaternion();
//rotate the cylinder to be vertical
q.fromAngleAxis(FastMath.PI/2, new Vector3f(1,0,0));
postGeometry.setLocalRotation(q);
postGeometry.setModelBound(new BoundingBox());
postGeometry.updateModelBound();
//We will share the post 4 times (one for each post)
//It is *not* a good idea to add the original geometry
//as the sharedmeshes will alter its local values.
//We then translate the posts into position.
//Magic numbers are bad, but help illustrate the point.:)
SharedMesh post1 = new SharedMesh("post1", postGeometry);
post1.setLocalTranslation(new Vector3f(0,0.5f,0));
SharedMesh post2 = new SharedMesh("post2", postGeometry);
post2.setLocalTranslation(new Vector3f(32,0.5f,0));
SharedMesh post3 = new SharedMesh("post3", postGeometry);
post3.setLocalTranslation(new Vector3f(0,0.5f,32));
SharedMesh post4 = new SharedMesh("post4", postGeometry);
post4.setLocalTranslation(new Vector3f(32,0.5f,32));
//This cylinder will be the horizontal struts that hold
//the field in place.
Cylinder strutGeometry = new Cylinder("strut", 10,10, 0.125f, 32);
strutGeometry.setModelBound(new BoundingBox());
strutGeometry.updateModelBound();
//again, we'll share this mesh.
//Some we need to rotate to connect various posts.
SharedMesh strut1 = new SharedMesh("strut1", strutGeometry);
Quaternion rotate90 = new Quaternion();
rotate90.fromAngleAxis(FastMath.PI/2, new Vector3f(0,1,0));
strut1.setLocalRotation(rotate90);
strut1.setLocalTranslation(new Vector3f(16,3f,0));
SharedMesh strut2 = new SharedMesh("strut2", strutGeometry);
strut2.setLocalTranslation(new Vector3f(0,3f,16));
SharedMesh strut3 = new SharedMesh("strut3", strutGeometry);
strut3.setLocalTranslation(new Vector3f(32,3f,16));
SharedMesh strut4 = new SharedMesh("strut4", strutGeometry);
strut4.setLocalRotation(rotate90);
strut4.setLocalTranslation(new Vector3f(16,3f,32));
//Create the actual forcefield
//The first box handles the X-axis, the second handles the z-axis.
//We don't rotate the box as a demonstration on how boxes can be
//created differently.
Box forceFieldX = new Box("forceFieldX", new Vector3f(-16, -3f, -0.1f), new Vector3f(16f, 3f, 0.1f));
forceFieldX.setModelBound(new BoundingBox());
forceFieldX.updateModelBound();
//We are going to share these boxes as well
SharedMesh forceFieldX1 = new SharedMesh("forceFieldX1",forceFieldX);
forceFieldX1.setLocalTranslation(new Vector3f(16,0,0));
SharedMesh forceFieldX2 = new SharedMesh("forceFieldX2",forceFieldX);
forceFieldX2.setLocalTranslation(new Vector3f(16,0,32));
//The other box for the Z axis
Box forceFieldZ = new Box("forceFieldZ", new Vector3f(-0.1f, -3f, -16), new Vector3f(0.1f, 3f, 16));
forceFieldZ.setModelBound(new BoundingBox());
forceFieldZ.updateModelBound();
//and again we will share it
SharedMesh forceFieldZ1 = new SharedMesh("forceFieldZ1",forceFieldZ);
forceFieldZ1.setLocalTranslation(new Vector3f(0,0,16));
SharedMesh forceFieldZ2 = new SharedMesh("forceFieldZ2",forceFieldZ);
forceFieldZ2.setLocalTranslation(new Vector3f(32,0,16));
//add all the force fields to a single node and make this node part of
//the transparent queue.
Node forceFieldNode = new Node("forceFieldNode");
forceFieldNode.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
forceFieldNode.attachChild(forceFieldX1);
forceFieldNode.attachChild(forceFieldX2);
forceFieldNode.attachChild(forceFieldZ1);
forceFieldNode.attachChild(forceFieldZ2);
//Add the alpha values for the transparent node
BlendState as1 = display.getRenderer().createBlendState();
as1.setBlendEnabled(true);
as1.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
as1.setDestinationFunction(BlendState.DestinationFunction.One);
as1.setTestEnabled(true);
as1.setTestFunction(BlendState.TestFunction.GreaterThan);
as1.setEnabled(true);
forceFieldNode.setRenderState(as1);
//load a texture for the force field elements
TextureState ts = display.getRenderer().createTextureState();
t = TextureManager.loadTexture(Lesson2.class.getClassLoader()
.getResource("jmetest/data/texture/reflector.jpg"),
Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear);
t.setWrap(Texture.WrapMode.Repeat);
t.setTranslation(new Vector3f());
ts.setTexture(t);
forceFieldNode.setRenderState(ts);
//put all the posts into a tower node
Node towerNode = new Node("tower");
towerNode.attachChild(post1);
towerNode.attachChild(post2);
towerNode.attachChild(post3);
towerNode.attachChild(post4);
//add the tower to the opaque queue (we don't want to be able to see through them)
//and we do want to see them through the forcefield.
towerNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
//load a texture for the towers
TextureState ts2 = display.getRenderer().createTextureState();
Texture t2 = TextureManager.loadTexture(Lesson2.class.getClassLoader()
.getResource("jmetest/data/texture/post.jpg"),
Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear);
ts2.setTexture(t2);
towerNode.setRenderState(ts2);
//put all the struts into a single node.
Node strutNode = new Node("strutNode");
strutNode.attachChild(strut1);
strutNode.attachChild(strut2);
strutNode.attachChild(strut3);
strutNode.attachChild(strut4);
//this too is in the opaque queue.
strutNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
//load a texture for the struts
TextureState ts3 = display.getRenderer().createTextureState();
Texture t3 = TextureManager.loadTexture(Lesson2.class.getClassLoader()
.getResource("jmetest/data/texture/rust.jpg"),
Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear);
ts3.setTexture(t3);
strutNode.setRenderState(ts3);
//we will do a little 'tweaking' by hand to make it fit in the terrain a bit better.
//first we'll scale the entire "model" by a factor of 5
forceFieldFence.setLocalScale(new Vector3f(5,4,4));
//now let's move the fence to to the height of the terrain and in a little bit.
forceFieldFence.setLocalTranslation(new Vector3f(25, tb.getHeight(25,25) + 15, 25));
//Attach all the pieces to the main fence node
forceFieldFence.attachChild(forceFieldNode);
forceFieldFence.attachChild(towerNode);
forceFieldFence.attachChild(strutNode);
scene.attachChild(forceFieldFence);
}
/**
* creates a light for the terrain.
*/
private void buildLighting() {
/** Set up a basic, default light. */
DirectionalLight light = new DirectionalLight();
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.setDirection(new Vector3f(1,-1,0));
light.setEnabled(true);
/** Attach the light to a lightState and the lightState to rootNode. */
LightState lightState = display.getRenderer().createLightState();
lightState.setEnabled(true);
lightState.attach(light);
scene.setRenderState(lightState);
}
/**
* build the height map and terrain block.
*/
private void buildTerrain() {
MidPointHeightMap heightMap = new MidPointHeightMap(64, 1f);
// Scale the data
Vector3f terrainScale = new Vector3f(4, 0.0575f, 4);
// create a terrainblock
tb = new TerrainBlock("Terrain", heightMap.getSize(), terrainScale,
heightMap.getHeightMap(), new Vector3f(0, 0, 0));
tb.setModelBound(new BoundingBox());
tb.updateModelBound();
// generate a terrain texture with 2 textures
ProceduralTextureGenerator pt = new ProceduralTextureGenerator(
heightMap);
pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
.getResource("jmetest/data/texture/grassb.png")), -128, 0, 128);
pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
.getResource("jmetest/data/texture/dirt.jpg")), 0, 128, 255);
pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
.getResource("jmetest/data/texture/highest.jpg")), 128, 255,
384);
pt.createTexture(32);
// assign the texture to the terrain
TextureState ts = display.getRenderer().createTextureState();
Texture t1 = TextureManager.loadTexture(pt.getImageIcon().getImage(),
Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear, true);
ts.setTexture(t1, 0);
tb.setRenderState(ts);
tb.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
scene.attachChild(tb);
}
/**
* buildSkyBox creates a new skybox object with all the proper textures. The
* textures used are the standard skybox textures from all the tests.
*
*/
private void buildSkyBox() {
skybox = new Skybox("skybox", 10, 10, 10);
Texture north = TextureManager.loadTexture(
TestSkybox.class.getClassLoader().getResource(
"jmetest/data/texture/north.jpg"),
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear);
Texture south = TextureManager.loadTexture(
TestSkybox.class.getClassLoader().getResource(
"jmetest/data/texture/south.jpg"),
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear);
Texture east = TextureManager.loadTexture(
TestSkybox.class.getClassLoader().getResource(
"jmetest/data/texture/east.jpg"),
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear);
Texture west = TextureManager.loadTexture(
TestSkybox.class.getClassLoader().getResource(
"jmetest/data/texture/west.jpg"),
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear);
Texture up = TextureManager.loadTexture(
TestSkybox.class.getClassLoader().getResource(
"jmetest/data/texture/top.jpg"),
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear);
Texture down = TextureManager.loadTexture(
TestSkybox.class.getClassLoader().getResource(
"jmetest/data/texture/bottom.jpg"),
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear);
skybox.setTexture(Skybox.Face.North, north);
skybox.setTexture(Skybox.Face.West, west);
skybox.setTexture(Skybox.Face.South, south);
skybox.setTexture(Skybox.Face.East, east);
skybox.setTexture(Skybox.Face.Up, up);
skybox.setTexture(Skybox.Face.Down, down);
skybox.preloadTextures();
scene.attachChild(skybox);
}
/**
* will be called if the resolution changes
*
* @see com.jme.app.BaseGame#reinit()
*/
protected void reinit() {
display.recreateWindow(width, height, depth, freq, fullscreen);
}
/**
* close the window and also exit the program.
*/
protected void quit() {
super.quit();
System.exit(0);
}
/**
* clean up the textures.
*
* @see com.jme.app.BaseGame#cleanup()
*/
protected void cleanup() {
}
}
This tutorial we get to have a little fun. We are going to load the terrain for our game. There are a few goals for the style of terrain I want to use:
这个指南来点好玩的东东.在游戏中读取地形.有几个目的:
-
Random each time
-
Don’t require many triangles
-
“Bumpy” for jumps
-
Large enough for fast vehicles
-
每次随机读取
-
不需要很多的三角形
-
崎岖不平的
- 足够大的快速
We will be building on our Framework from Lesson 2. First, start by removing all the Sphere rendering code. We no longer need this example. You should have a fairly clean framework to work with now. Now, the terrain we are going to make is going to be rather large. So, for the time being, I want to alter the position of the Camera to keep it in view. So, in initSystem change:
我们将在Lesson2的架构基础上开始构建。首先,移除所有的球形渲染代码。不再需要这样的例子了。现在,我们就有了一个比较干净的框架去使用了。现在,我们的地形将是相当的大.之后再改变视角.那么把initSystem改变如下:
Vector3f loc = new Vector3f(0.0f, 0.0f, 25.0f); to: Vector3f loc = new Vector3f(500.0f, 150.0f, 500.0f);
This moves up far back to insure we have a decent view of the terrain.
这一举措确保有一个体面的地形.
Now, inside the initGame method we are going to add a call to a new method, and add a TerrainBlock to the scene. This TerrainBlock is called tb and should be defined at the top of the class. This new method is called buildTerrain and should be called just before adding tb to the scene graph. You should have something like the following:
现在,我们要在initGame()方法里面添加一个新的方法,添加一个TerrainBlock在场景上.这个TerrainBlock实例化为tb对象定义在类的最上边.这个新方法叫做buildTerrain(),确保在对象tb被添加到scene前被调用.就像下面那样:
protected void initGame() {
scene = new Node("Scene graph node");
buildTerrain();
scene.attachChild(tb);
// update the scene graph for rendering
scene.updateGeometricState(0.0f, true);
scene.updateRenderState();
}
Which leads us to the guts of this tutorial, buildTerrain.
There are three parts to our terrain building:
-
Creating a heightmap
-
Generating a mesh from the heightmap
-
Generating a texture based on the heights
在buildTerrain()里面都干了下面这三件事情:< br />
-
创建heightmap
-
生成heightmap网格
-
在上面加些图片
Creating a Heightmap 创建Heightmap
AbstractHeightMap defines a method for storing height data. At its core, it’s basically a two dimensional matrix of data, where any point (x, y) gives a height z. While this does not allow for complex terrain (caves, overhangs, etc) it provides a very solid base for terrain and is everything we need for Flag Rush.
We will create a MidPointHeightMap which generates terrain using a Midpoint displacement fractal. This will allow for enough interest and realism in the terrain, providing us with some bumps to jump from.
AbstractHeightMap定义了一个方法提供一个控制高的属性.在其核心,有两个基础的空间属性,每个点都加上高z.当不允许复杂的地形的时候,例如caves,overhangs等等,它会提供一个基础的固定地形,这就是Flag Rush所需要的一切了.
我们需要创建一个MidPointHeightMap
Creating this height map is straight forward, and the first line in our buildTerrain method:
直接创建MidPointHeightMap,在buildTerrain()方法的第一行
/**
* build the height map and terrain block.
*/
private void buildTerrain() {
// Generate a random terrain data
MidPointHeightMap heightMap = new MidPointHeightMap(64, 1f);
//...
}
We create a new heightMap object and call MidPointHeightMap’s constructor. It only takes two parameters: The size and the “roughness”.
用MidPointHeightMap的构造体创建一个叫heightMap的对象.附给俩参数:尺寸和”凸凹不平程度”.
The size for MidPointHeightMap must be a power of two. That is, 2, 4, 8, 16, 32, 64, and so on. In our case, we chose 64. This provides a decent sized map for our needs (our action will be contained in a fairly small arena). The roughness value is where the interesting stuff happens. The lower this number the rougher the terrain, higher the smoother. We chose 1 which at first, makes the terrain look like the pits of hell with spires. However, we aren’t done yet, these spikes will be toned down next.
MidPointHeightMap的尺寸必须是2的倍数,就像2,4,8,16..这里用64.它提供一个合适尺寸的地图(我们这里用了一个非常小的地形).”凸凹不平程度”值是非常有趣的.值越小越凹,值越大越凸.先用1试试,让地形看起来象层峦叠嶂的山峰.接下来把地形磨平一点.
We will define a terrain scale factor. This will basically stretch and squeeze the terrain mesh into the size we desire. So, add
定义一个地形比例因数.这是拉伸和挤压地形网格的基础
// Scale the data Vector3f terrainScale = new Vector3f(20, 0.5f, 20);
to the method. This means the following: We are going to stretch the X and Z values of the terrain by 20. This will make the terrain much larger feeling (20 times larger in fact). While at the same time, decreasing the size of the heights by half. This will give us the bumps we want, but keep them at a reasonable size (not huge spikes).
在这个方法中,他意味着:我们将去延伸地形的x和z的值到20倍。它将使地形给人一种很大的感觉(是实际大小的20倍)。同时,减小地形的高度到一半。他让地形有我们想要的凸起,但是要保持在一个合理的范围内。
Generating the Terrain Mesh
生成地形网格
Now, that we have our data set up, we can actually build the mesh. We are going to build a TerrainBlock which is a single Geometry. This will be added to the scene just like adding the Sphere in previous tutorials.
现在,我们有了一组数据,我们能够真正建立网格了。我们要建立一个TerrainBlock对象,这是一个单一的几何形状.他将想前一个向导中的球形一样被加入到scene中去.
// create a terrainblock
tb = new TerrainBlock("Terrain", heightMap.getSize(), terrainScale,
heightMap.getHeightMap(), new Vector3f(0, 0, 0));
tb.setModelBound(new BoundingBox());
tb.updateModelBound();
The TerrainBlock takes in a number of parameters, most are straight forward. First, the name of the terrain. The size of the heightmap is given next along with the terrainScale we set earlier. The actual heightmap data is given next. The next parameter defines the origin of the terrain. We have no reason to set anything fancy here, so just give a basic (0,0,0) origin.
该TerrainBlock带入了一些参数,大多数是直接传入(?)。第一个,地形对象的名字.heightmap的尺寸和缩放比例是早先我们设置的.紧接着,实际的heightmap数据被给予.再下一个参数定义了地形的原点.我们没有理由设置任何特别的值,所以只给一个基本的( 0,0,0 )原点.
We then set up the BoundingVolume of the terrain.
接着我们要设置地形的BoundingVolume.
You can probably go ahead and run the game now, and see something similar to:
Not a lot to see here, as the terrain is just one big hunk of white. We need to apply a texture to help give it a little dimension.
Generating a Texture
纹理生成
Creating the texture will be done using ProceduralTextureGenerator. This class will generate a texture based on the heights of the height map and blend between multiple textures. A texture is assigned a height area, and they are then blended into a single texture map. This allows us to create a fairly realistic looking terrain easily. In our case, we are going to use three textures, a grass texture for the low areas, rocks for the mid levels and snow for the high areas.
我们将使用ProceduralTextureGenerator来创建纹理.这个类会混合多个纹理生成一个基于heightmap的高度的纹理.每个纹理分配一个高度范围,他们然后混合成一个单一的纹理贴图.这样,我们就可以很容易的创造一个相当逼真的地形.在我们的代码里,我们将使用三个纹理,草地纹理底层的地区,岩石纹理在中间地区和雪地纹理在较高的地区。
// generate a terrain texture with 3 textures
ProceduralTextureGenerator pt = new ProceduralTextureGenerator(heightMap);
pt.addTexture(new ImageIcon(Lesson3.class.getClassLoader()
.getResource("jmetest/data/texture/grassb.png")), -128, 0, 128);
pt.addTexture(new ImageIcon(Lesson3.class.getClassLoader()
.getResource("jmetest/data/texture/dirt.jpg")), 0, 128, 255);
pt.addTexture(new ImageIcon(Lesson3.class.getClassLoader()
.getResource("jmetest/data/texture/highest.jpg")), 128, 255,
384);
pt.createTexture(32);
You’ll notice that every texture has three values. This represents the low, optimal and high elevations that this texture will be applied to. For example (dirt.jpg) will be blended from elevation 0 to 255. The heightmap generates values from 0 to 255. So this means, the dirt will be at its strongest (most visible) at height 128, and blend out to nothing at 0 and 255. While the other two textures fill in the low and high areas.
您会发现,每一个纹理有三个值.这表示纹理被应用到的最低,最优和最高海拔地区.例如(dirt.jpg)将从海拔0到255被混合.该heightmap产生从0到255的值.这意味着,在高度128的地方dirt最多(最可见),(和融合,以什么为0和255 。注:goolge翻译出来的,看不懂)而其他两个纹理填写低和高的地区。
addTexture takes an ImageIcon object to define the texture data. In this example we build an ImageIcon from a URL obtained from the getResource method of our class. This looks for the images in the classpath. This, of course, is not required, and the ImageIcon can be built in a way that is most appropriate for your application.
addTexture需要一个ImageIcon对象去定义纹理数据.在这个例子中,我们通过这个类的getResource方法从一个URL建立一个ImageIcon.这会查找类路径里的图片.当然,这并不必要,ImageIcon可以以一种更加合适你的应用程序的方式被建立.
createTexture actually creates the image that we will use for the texture. In this case, I tell it to generate a texture of size 32×32 pixels. While this may seem very small, I don’t want it to be detailed. This is just for the base color. Later on we will add detail textures and objects.
createTexture实际创建我们将用于纹理的图片.在这种情况下,我告诉它生成尺寸32 × 32像素的纹理.虽然这可能似乎非常小,我不希望它是详细。这仅仅是为基础的色彩。后来我们将添加详细的纹理和对象。
For example, during a run of the game, I saved a generated texture. It looks like:
You can see how the three textures (grass, rocks and snow) were blended into a single texture. The white areas, would be a high spot in the terrain, and the grass low.
Now that we have generated the texture, we put it into a TextureState and apply it to the terrain.
你将看到三种纹理(草地、岩石和雪地)如何混合成一种纹理.白色区域将是一个较高的地形,而草地很低.现在我们有了一个生成的纹理,我们将他放入一个TextureState中做为一个地形.
// assign the texture to the terrain TextureState ts = display.getRenderer().createTextureState(); Texture t1 = TextureManager.loadTexture(pt.getImageIcon().getImage(), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear, true); ts.setTexture(t1, 0); tb.setRenderState(ts);
And with this the terrain is done. You can now run the game and see something similar to:
NOTE: I keep saying similar because we are using a random method for generating the terrain, so it will be different everytime.
注意:为了更简单的解说所以我们采用了一个随机方法去产生一个地形,因此它每一次都不一样.
Let There Be Light
Even with the texture it is very difficult to make out the terrain itself. That’s because there is no light and shadows to help distinguish parts of the terrain. So, let’s go ahead and add a “sun”. Add a buildLighting to your initGame. We will add a DirectionalLight to shine on our terrain.
仅仅使用纹理是很难生产自己的地形.那是因为没有光和阴影来帮助区分不同区域的地形.所以,让我们去添加一个“太阳”.在initGame中添加一个buildLighting函数,我们将添加一个直线光去照亮我们的地形.
There are two parts to adding the lights. First, creating the DirectionalLight and then adding it to a LightState.
这里通过两个步骤去添加光.首先,新建一个DirectionalLight对象,然后把它添加到一个LightState对象中去.
private void buildLighting() {
/** Set up a basic, default light. */
DirectionalLight light = new DirectionalLight();
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.setDirection(new Vector3f(1,-1,0));
light.setEnabled(true);
/** Attach the light to a lightState and the lightState to rootNode. */
LightState lightState = display.getRenderer().createLightState();
lightState.setEnabled(true);
lightState.attach(light);
scene.setRenderState(lightState);
}
The DirectionalLight is set to shine in the (1,-1,0) direction (down and to the right). It is then added to the LightState and applied to the scene. This adds a lot more dimension to the scene, and you can make out the terrain features much better:
该DirectionalLight设置为照向( 1 , -1,0 )的方向(即右下角).紧接着将他加入到LightState并放入场景中.这增加了更多层面的场面,你能得出更好的地形特点:
Summary
We now have the level on which to race around. However, there is still that annoying black background. Next lesson, we will take care of that.
现在我们已经可以构建出赛道了.然而背景依然是惹人讨厌的黑色.下一节我们来改善.
Source
package jmetest.flagrushtut;
import javax.swing.ImageIcon;
import com.jme.app.BaseGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.input.InputSystem;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.light.DirectionalLight;
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.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.util.TextureManager;
import com.jme.util.Timer;
import com.jmex.terrain.TerrainBlock;
import com.jmex.terrain.util.MidPointHeightMap;
import com.jmex.terrain.util.ProceduralTextureGenerator;
/**
* Tutorial 3 Loads a random terrain for uses at the game level.
* framework for Flag Rush. For Flag Rush Tutorial Series.
*
* @author Mark Powell
*/
public class Lesson3 extends BaseGame {
private TerrainBlock tb;
protected Timer timer;
// Our camera object for viewing the scene
private Camera cam;
// the root node of the scene graph
private Node scene;
// display attributes for the window. We will keep these values
// to allow the user to change them
private int width, height, depth, freq;
private boolean fullscreen;
/**
* Main entry point of the application
*/
public static void main(String[] args) {
Lesson3 app = new Lesson3();
// We will load our own "fantastic" Flag Rush logo. Yes, I'm an artist.
app.setConfigShowMode(ConfigShowMode.AlwaysShow,
Lesson3.class.getClassLoader()
.getResource("jmetest/data/images/FlagRush.png"));
app.start();
}
/**
* During an update we only look for the escape button and update the timer
* to get the framerate.
*
* @see com.jme.app.SimpleGame#update()
*/
protected void update(float interpolation) {
// update the time to get the framerate
timer.update();
interpolation = timer.getTimePerFrame();
// if escape was pressed, we exit
if (KeyBindingManager.getKeyBindingManager().isValidCommand("exit")) {
finished = true;
}
}
/**
* draws the scene graph
*
* @see com.jme.app.SimpleGame#render()
*/
protected void render(float interpolation) {
// Clear the screen
display.getRenderer().clearBuffers();
display.getRenderer().draw(scene);
}
/**
* initializes the display and camera.
*
* @see com.jme.app.SimpleGame#initSystem()
*/
protected void initSystem() {
// store the properties information
width = settings.getWidth();
height = settings.getHeight();
depth = settings.getDepth();
freq = settings.getFreq();
fullscreen = settings.getFullscreen();
try {
display = DisplaySystem.getDisplaySystem(settings.getRenderer());
display.createWindow(width, height, depth, freq, fullscreen);
cam = display.getRenderer().createCamera(width, height);
} catch (JmeException e) {
e.printStackTrace();
System.exit(1);
}
// set the background to black
display.getRenderer().setBackgroundColor(ColorRGBA.black);
// initialize the camera
cam.setFrustumPerspective(45.0f, (float) width / (float) height, 1,
1000);
Vector3f loc = new Vector3f(500.0f, 150.0f, 500.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, 0.0f, -1.0f);
// Move our camera to a correct place and orientation.
cam.setFrame(loc, left, up, dir);
/** Signal that we've changed our camera's location/frustum. */
cam.update();
/** Get a high resolution timer for FPS updates. */
timer = Timer.getTimer();
display.getRenderer().setCamera(cam);
KeyBindingManager.getKeyBindingManager().set("exit",
KeyInput.KEY_ESCAPE);
}
/**
* initializes the scene
*
* @see com.jme.app.SimpleGame#initGame()
*/
protected void initGame() {
scene = new Node("Scene graph node");
buildTerrain();
scene.attachChild(tb);
buildLighting();
// update the scene graph for rendering
scene.updateGeometricState(0.0f, true);
scene.updateRenderState();
}
/**
* creates a light for the terrain.
*/
private void buildLighting() {
/** Set up a basic, default light. */
DirectionalLight light = new DirectionalLight();
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.setDirection(new Vector3f(1,-1,0));
light.setEnabled(true);
/** Attach the light to a lightState and the lightState to rootNode. */
LightState lightState = display.getRenderer().createLightState();
lightState.setEnabled(true);
lightState.attach(light);
scene.setRenderState(lightState);
}
/**
* build the height map and terrain block.
*/
private void buildTerrain() {
// Generate a random terrain data
MidPointHeightMap heightMap = new MidPointHeightMap(64, 1f);
// Scale the data
Vector3f terrainScale = new Vector3f(20, 0.5f, 20);
// create a terrainblock
tb = new TerrainBlock("Terrain", heightMap.getSize(), terrainScale,
heightMap.getHeightMap(), new Vector3f(0, 0, 0), false);
tb.setModelBound(new BoundingBox());
tb.updateModelBound();
// generate a terrain texture with 3 textures
ProceduralTextureGenerator pt = new ProceduralTextureGenerator(
heightMap);
pt.addTexture(new ImageIcon(Lesson3.class.getClassLoader()
.getResource("jmetest/data/texture/grassb.png")), -128, 0, 128);
pt.addTexture(new ImageIcon(Lesson3.class.getClassLoader()
.getResource("jmetest/data/texture/dirt.jpg")), 0, 128, 255);
pt.addTexture(new ImageIcon(Lesson3.class.getClassLoader()
.getResource("jmetest/data/texture/highest.jpg")), 128, 255,
384);
pt.createTexture(32);
// assign the texture to the terrain
TextureState ts = display.getRenderer().createTextureState();
ts.setEnabled(true);
Texture t1 = TextureManager.loadTexture(pt.getImageIcon().getImage(),
Texture.MM_LINEAR_LINEAR, Texture.FM_LINEAR, true);
ts.setTexture(t1, 0);
tb.setRenderState(ts);
}
/**
* will be called if the resolution changes
*
* @see com.jme.app.SimpleGame#reinit()
*/
protected void reinit() {
display.recreateWindow(width, height, depth, freq, fullscreen);
}
/**
* clean up the textures.
*
* @see com.jme.app.SimpleGame#cleanup()
*/
protected void cleanup() {
}
}
Moving away from SimpleGame with your own App
用你的程序替代SimpleGame
This tutorial, we are going to build the foundation for Flag Rush. We are going to extend BaseGame with our own implementation. We are going to use BaseGame as the super class, but may change to another game type later on as BaseGame simply renders and updates as fast as possible. We probably won’t need or want this type of loop. However, for now, BaseGame is a no-fuss loop. In the future, switching will be trivial, the only difference being the value passed in the update and render methods.
We will begin by creating a new class that extends BaseGame. You will notice there are six methods that require implementing: update, render, initSystem, initGame and reinit. For now, just create a stub method for them and we will fill in the logic as we go on.
在这份指南里,我们继续创建基础的Flag Rush.我们将要扩展BaseGame用我们的实现方式.我们继BaseGame,当你想要改变其他游戏类型直接改变BaseGame简单渲染和更新是很快的.我们也许不需要这个类型循环.然而,现在,我先创建一个类继承自BaseGame.你需要注意的是这里有6哥方法必须实现update(), render(), initSystem(), initGame() and reinit().现在先为它们创建子方法然后我们 把逻辑填充进去.
public class Lesson2 extends BaseGame {
public static void main(String[] args) {
}
protected void update(float interpolation) {
}
protected void render(float interpolation) {
}
protected void initSystem() {
}
protected void initGame() {
}
protected void reinit() {
}
protected void cleanup() {
}
}
So, let’s start at the beginning. We are again going to create the main method here. It will be just like the previous tutorial’s main, except for one key difference. This time, we will be displaying Flag Rush’s amazing new logo. AbstractGame defines a couple setDialogBehaviour methods, one of which takes a URL class for loading an image. Therefore, we will load the FlagRush.png file (the amazing logo) and pass it to this method. Now, when the PropertiesDialog is shown, it will display the new logo.
因此,让我们先开始。我们再次要建立main()方法,就跟上一个指南的main()方法一样,除了一个字不一样.这一次,我们将要显示Flag Rush令人惊奇的新logo.AbstractGame定义了一对setDialogBehaviour()方法(译译者注:我在AbstractGame.java里面并没有找到setDialogBehaviour()方法,也许版本不对吧,文档没有跟上…..),一个是需要读取图像的URL 类作为参数的.因此,我们读取FlagRush.png图片文件(就是那个logo)使用这个方法.现在当PropertiesDialog显示的时候,新的logo也显示出来了.
0px; margin-left: 0px; “>
/**
* Main entry point of the application
*/
public static void main(String[] args) {
Lesson2 app = new Lesson2();
//We will load our own "fantastic" Flag Rush logo. Yes, I'm an artist.
app.setConfigShowMode(ConfigShowMode.AlwaysShow,
Lesson2.class.getResource("/res/FlagRush.png"));
app.start();
}
Now, when the PropertiesDialog appears it should appear similar to the following:
现在,当PropertiesDialog显示的时候出现了一个类似下面的样子的东东:
Init System
初始化系统
Now you can run your application, but it won’t do much after displaying the dialog. We will want to implement the initSystem method next. This method is called before entering the main loop by BaseGame. This is where we want to set up the window and the display. We are going to want to store the width, height, depth, frequency, and fullscreen flag. We will use this values in the future, in case the user wants to change the resolution. So, first, let’s create the variables to hold these values:
现在你可以运行你的程序了,但是在显示以上对话框后没有其他的行为.下面我们实现initSystem()方法.这个方法在BaseGame的游戏主循环之前.这就是我们要建立的窗口和display.我们将要存储的宽度,高度,深度,频率,和全屏标帜.我们将会用到上面的值,在本例中将要改变分辨率.首先,创建变亮以保留这些值:
public class Lesson2 extends BaseGame {
//display attributes for the window. We will keep these values
//to allow the user to change them
private int width, height, depth, freq;
private boolean fullscreen;
//...
We are also going to maintain our own Camera in our application, so we should create a variable for that as well.
我们将要使用Camera在我们的程序里,所以我们为Camera创建一个对象
//Our camera object for viewing the scene private Camera cam;
The last items that will be initialized is the Timer. The timer will allow us to obtain our frame rate. So, again, this will be an instance variable.
最后一个组建就是初始化Timer类,用timer类我们可以获取我们的帧率,创建它:
protected Timer timer;
Now, we have our instance variables ready, and we will initialize them in the initSystem method.
现在,我们已经定义好我们的变量了,接下来我们在initSystem()方法里初始化它们
/**
* initializes the display and camera.
*
* @see com.jme.app.SimpleGame#initSystem()
*/
protected void initSystem() {
//store the properties information
width = this.settings.getWidth();
height = this.settings.getHeight();
depth = this.settings.getDepth();
freq = this.settings.getFrequency();
fullscreen = this.settings.isFullscreen();
try {
display = DisplaySystem.getDisplaySystem(this.getNewSettings().getRenderer());
display.createWindow(width, height, depth, freq, fullscreen);
cam = display.getRenderer().createCamera(width, height);
} catch (JmeException e) {
e.printStackTrace();
System.exit(1);
}
//set the background to black
display.getRenderer().setBackgroundColor(ColorRGBA.black);
//initialize the camera
cam.setFrustumPerspective(45.0f, (float)width / (float)height, 1, 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);
// Move our camera to a correct place and orientation.
cam.setFrame(loc, left, up, dir);
/** Signal that we've changed our camera's location/frustum. */
cam.update();
/** Get a high resolution timer for FPS updates. */
timer = Timer.getTimer();
display.getRenderer().setCamera(cam);
KeyBindingManager.getKeyBindingManager().set("exit",
KeyInput.KEY_ESCAPE);
}
This is a large method, so, I’ll go through it bit by bit.
这是个大方法,我们一点点来看.
//store the properties information width = this.settings.getWidth(); height = this.settings.getHeight(); depth = this.settings.getDepth(); freq = this.settings.getFrequency(); fullscreen = this.settings.isFullscreen();
First, we store the values obtained from the settings object (settings is built by AbstractGame). By storing these values we will be able to easily alter one or all of them in the future when the user switches screen settings from a menu system.
首先,我们从settings对象(settings在AbstractGame被创建)里获取这些变量.这样做的好处是,当用户从菜单里改变屏幕设置的时候,我们可以很方便的改变它们的值.
try {
display = DisplaySystem.getDisplaySystem(this.settings.getRenderer());
display.createWindow(width, height, depth, freq, fullscreen);
cam = display.getRenderer().createCamera(width, height);
} catch (JmeException e) {
e.printStackTrace();
System.exit(1);
}
Next, we obtain a new DisplaySystem and create a native window with our screen parameters we obtained earlier. We then use the DisplaySystem to create a Camera object. You’ll notice that it is wrapped in a try/catch block. If we try to create a window that the system is not capable of displaying, it will be shown here. Currently, it will just exit, but later on we’ll enhance this to play nice, and notify the user.
接下来,我们很容易获得DisplaySystem实例,又用我们的上面的参数创建了一个本地窗口.然后用DisplaySystem创建一个Camera对象.注意:用try/catch块包裹一下哦.如果我们尝试建立一个窗口,该系统不能够显示,将在这里显示异常。目前,它只会退出,但后来我们会加强这方面,并做出用户提示.
//set the background to black 设置背景为黑色 display.getRenderer().setBackgroundColor(ColorRGBA.black);
We then set the background color of the window. This is the default color that is displayed when no other data is rendered. I chose black because it tends to show a nice contrast for any text we might be displaying in the future. However, this is a moot point, because when things are working properly, there will always be some data covering the screen.
我们设置窗口的背景色.在没有提供其他选择的时候这是默认的渲染颜色.这里之所以用黑色是因为黑色是很容易跟其他的颜色作对比的颜色.然而,这是个待商榷的点,如果一切正常的话,它会被其他什么东西遮盖住的.
//initialize the camera cam.setFrustumPerspective(45.0f, (float)width / (float)height, 1, 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); // Move our camera to a correct place and orientation. cam.setFrame(loc, left, up, dir); /** Signal that we've changed our camera's location/frustum. */ cam.update(); display.getRenderer().setCamera(cam);
Next, I set up the camera. I want a standard camera, with the typical right handed coordinate system (Up is +Y, Right is +X, and into the screen is -Z). I also set up the perspective to be a 45 degree viewing angle. This is fairly standard in most games, and will work for Flag Rush. After the camera’s data is set, we call update this will set up all the OpenGL components, such as the ViewPort and Frustum.
接下来,我设置camera.我需要一个标准camera,等同于典型右手习惯(上是+Y轴,右是+X轴,-z是深度).也可以设置45°视角,这跟很多游戏一样,为Flag Rush做这样的设置.之后视频数据被设置好了,我们调用update就会设置OpenGl组件,就像ViewPort和Frustum.
/** Get a high resolution timer for FPS updates. */ timer = Timer.getTimer();
Here the timer is just initialized, obtaining the native timer (for example LWJGLTimer).
这里timer被初始化了,获得一个本地时间(例如:LWJGL Timer).
KeyBindingManager.getKeyBindingManager().set("exit",
KeyInput.KEY_ESCAPE);
Lastly, we create a new InputSystem, bind it to our KeyBindingManager and set up an input action. We only care about one key for this framework, and that is Escape. In this case, we set the action “exit” to the escape key. KeyBindingManager is a singleton class that will take care of initializing all the InputSystem components with the single get call.
Now, if you run the system you will actually get a window displayed. It will be filled with black (the background color we set) and nothing else.
最后,我们创建InputSystem,绑定到我们的KeyBindingManager上,然后设置一个input动作.这个例子里我们只关心一个键,既Esc键.我们设置一个退出动作到Esc键上.KeyBindingManager是一个单例模式类,它初始化所以InputSystem组件.
现在,如果你运行你的程序,你会看到一个窗口显示出来.它会被黑色填充(因为我们设置的背景色是黑色),就没有其他东西了.
Init Game
Now that we have a window and an OpenGL context, we will load our game data (a Sphere just like last tutorial).
We need to add this attibute to the class:
现在,我们有一个窗口和一个OpenGL context(上下文),我们将要读取我们的游戏数据(一个球,就像上一个指南里的一样).
//the root node of the scene graph
private Node scene;
//TextureState to show the monkey on the sphere.
private TextureState ts;
/**
* initializes the scene
*/
protected void initGame() {
scene = new Node("Scene graph node");
//Create our Sphere
Sphere s = new Sphere("Sphere", 30, 30, 25);
s.setLocalTranslation(new Vector3f(0, 0, -40));
s.setModelBound(new BoundingBox());
s.updateModelBound();
ts = display.getRenderer().createTextureState();
ts.setEnabled(true);
ts.setTexture(TextureManager.loadTexture(
Lesson2.class.getClassLoader().getResource("res/Monkey.jpg"),
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear));
s.setRenderState(ts);
scene.attachChild(s);
//update the scene graph for rendering
scene.updateGeometricState(0.0f, true);
scene.updateRenderState();
}
We now maintain our own Scene Graph root, I’ve chosen to name it scene but it really doesn’t matter. Because this is the root node of the scene, it is also an instance variable and was declared along with the other instance variables:
现在我们维持我们自己的Scene Graph根节点,我定义了节点的名字但是这并不是必要的.因为这是场景上的根节点,这是一个实例化变量的用法.
private Node scene;
This Node is then instantiated. Which we then create a Sphere and TextureState (just like last tutorial). The Sphere is then attached to scene. This should look very familiar as we did the same thing last tutorial. However, now, we also call updateGeometricState and updateRenderState. These methods are called for Scene Graph Updates. updateGeometricState is required whenever the Scene Graph structure has changed (add a node, remove a node, changes to a nodes translation, etc) in our case we added a Sphere to the scene. While updateRenderState should be called whenever a RenderState has been changed in some way (setting a new one, changing parameters on another, etc), in our case, we added the TextureState.
We now have data in our application, but it’s still not being rendered to the screen.
这个Node之后再说.下来我们创建哪个?Sphere或TextureState(就像上一个指南那样).把Sphere附给场景.这看起来很熟悉就像我们之前做的那样.然而,现在,我们调用updateGeometricState 和 updateRenderState.这俩方法被Scene Graph Updates调用.当Scene Graph的结果改变的时候(添加一个节点,删除一个节点,改变节点等等)updateGeometricState必须被调用,在我们的例子里我们给场景增加了一个Sphere.每当RenderState被改变的时候(设置一个新的变量或者改变参数的时候,等等)updateRenderState应该被调用.在我们的例子里,我们添加了TextureState.
现在我们的程序有了数据,但是并没有渲染给到屏幕上.
Render and Update 渲染和更新
Now that we have a window initialized and data loaded, it would be nice to be able to see it. That’s where the render method comes in. BaseGame calls update and then render as fast as it is capable. A call to render should handle all drawing calls, while update should do any game logic. In our case, we want update to do one bit of game logic, exit the game. In order to cleanly exit the game we will set the finished boolean to true.
现在我们的窗口已经初始化好啦数据也读取好啦,看起来也不错.这就是渲染方法的使用.就我们而言,我们希望做一个更新的游戏逻辑位,退出了比赛。为了干净地退出比赛,我们将设置完成布尔为true。
/**
* During an update we only look for the escape button and update the timer
* to get the framerate.
*/
protected void update(float interpolation) {
//update the time to get the framerate
timer.update();
interpolation = timer.getTimePerFrame();
//if escape was pressed, we exit
if (KeyBindingManager.getKeyBindingManager().isValidCommand("exit")) {
finished = true;
}
}
You’ll also notice that update gets the latest timer reading and sets the interpolation value to this. BaseGame always sends -1 on the update call, so we will go ahead and reuse this value to hold the real time per frame.
Next, we will render.
你应该已经注意到了update方法获得最后的timer来改变timer的值.BaseGame值调用update方法的时候总是发送 -1 ,所以利用此值来保存每帧的实时性。
下来,我们开始渲染.
/**
* draws the scene graph
*/
protected void render(float interpolation) {
//Clear the screen
display.getRenderer().clearBuffers();
display.getRenderer().draw(scene);
}
This is straight forward. We clear the screen with clearBuffers. 1) We then draw the scene which is the tree containing our Sphere.
You can now run the application and you should see:
看起来很简单.用clearBuffers方法来清屏.1)这时画出场景图就包含Sphere了.
现在运行程序将会看到如下图所示:
Which is the exact same display as the previous lesson without the lighting and statistics displayed.
和上一篇一样的显示,只是没有了灯光和统计数据.
reinit and cleanup
The last two methods we are going to override are reinit and cleanup. reinit should be called when the window needs rebuilding, such as the parameters changing. While cleanup is called during shutdown.
最后我们将要覆盖reinit()和cleanup()俩方法.当窗口需要重建的时候reinit()被调用,比如像参数变更的时候.当cleanup被调用的时候就shutdown.
/**
* will be called if the resolution changes
*
*/
protected void reinit() {
display.recreateWindow(width, height, depth, freq, fullscreen);
}
All we do here is pass the new values to the DisplaySystem for handling. Nothing more.
我们把所以的东西都扔给DisplaySystem处理,仅此而已
/**
* clean up the textures.
*
*/
protected void cleanup() {
ts.deleteAll();
}
This simple insures that the textures are deleted. This is not particularly needed, as OpenGL will take care of it while it is going down. “Better safe than sorry”.
这是个简单的确保贴图被删掉的方法.这也不是特别需要,像OpenGL会很小心的对待的.”安全点比说sorry强”
Summary汇总
Well, that’s it. We now have a very basic framework from which to work off of. By creating our own application type we can maintain complete control of everything in our scene. We will definately be enhancing and building upon this class as the tutorials run forward.
好啦,告一段落.我们现在有一个很基本的框架,以开展工作.通过建立我们自己的应用程序的类型,我们可以保持在场景图中完全控制一切。随着本教程的推进,会加强和建设这个类。
1)这并不会导致下一个帧是在当前帧的顶部渲染。这有时是一个预期的效果。
创建你的第一个程序SimpleGame
第一次翻译,估计会很慢…
SimpleGame is a default application type that is included in the jME package. SimpleGame attempts to take care of everything for you. This makes it easy to get prototypes up and running. It sets up all the elements such as Camera, InputHandler, basic RenderStates, etc. I’ll run through creating a simple application that draws a Sphere to the screen first, then the next tutorial we will create our own Application type to give you a better understanding of what is going on and better control.
第一个小例子SimpleGame是一个默认的程序类型,包括jme的包.SimpleGame帮你注意任何事情.它很容易 的获取原型和运行.它已经设置好了包括Camera,输入帮助类,基本的渲染,等等.我将首先创建一个简单的程序,程序绘画一个球,然后创建一个我们自己 的应用程序让我们更好的明白它是怎么运作的和如何更好的控制它.
First, we will create a new class that extends SimpleGame. In my case, I’m creating a class Lesson1:
public class Lesson1 extends SimpleGame {}
首先,我们创建一个类并继承SimpleGame,我创建了一个名字为Lesson1的类:
SimpleGame contains one abstract method: simpleInitGame. It is in this method that we will create the Sphere. Add the simpleInitGame method for now, we will come back to the Sphere later. First, we want to discuss the main method. This is the entry point for the jME application (just like any Java application). During creation, you must create your application and tell it to start executing the game loop. The Main Game Loop executes the update/render cycle until notified to exit and clean up. To start this loop a call to start is required.
SimpleGame包括一个抽象方法:simpleInitGame().这是一个用来创建球的方法.先加上simpleInitGame 方法,我们将在后面调用它,首先我们说说main方法.main方法是jME程序的入口点(就像所有的java程序那样).在创建的时候,你必须创建你的 应用程序然后告诉程序用start()方法开始执行程序.main方法执行更新/渲染的循环直到通知其结束和清理为止.启动程序必须调用start()方 法.
To allow the user to specify the window parameters (resolution, fullscreen, etc), we will always display the PropertiesDialog. Do do this, we set the application behavior to ConfigShowMode.AlwaysShow.
程序允许用户指定窗口参数(分辨率,全屏,等等),我们希望一直显示会话属性那么设置程序的行为为ConfigShowMode.AlwaysShow.
下面是完整程序源码
import com.jme.app.SimpleGame;
public class Lesson1 extends SimpleGame {
/**
* Main method is the entry point for this lesson. It creates a
* SimpleGame and tells the dialog to always appear. It then
* starts the main loop.
* @param args
*/
public static void main(String[] args) {
Lesson1 app = new Lesson1();
app.setConfigShowMode(ConfigShowMode.AlwaysShow);
app.start();
}
protected void simpleInitGame() {
}
}
The above code should actually compile and run, creating a blank window (with exception to the framerate and triangle count text).
上面的代码是可以编译运行的,就是创建了一个空白的窗口(有异常输出,帧率,三角形)
Displaying Something
Now, we want to add to the simpleInitGame to display a textured Sphere. To do so, we need to:
-
Load the Sphere
-
Load an image
-
Apply the image to the Sphere as a texture
-
Add the Sphere to the scene
现在,我们想要添加simpleInitGame()显示一个贴图的球型.做这个球,我们需要这样做:
-
读取球
- 读取图片
- 应用图片到球身上
- 添加这个贴图的球到场景
Creation of the Sphere is as simple as creating a new Sphere object.
创建这个球是很简单的,就是创建了一个球的对象(Sphere object.)
Sphere s = new Sphere("Sphere", 30, 30, 25);
s.setLocalTranslation(new Vector3f(0,0,-40));
s.setModelBound(new BoundingBox());
s.updateModelBound();
You define the number of sections on the vertical and the horizontal (in this case 30 and 30) and its radius (25). That is it. We now have a sphere. We can then manipulate the position of the Sphere. In this case, we want to move it along the negative Z direction (this is equivalent to moving it “into” the screen). We then set up the Bounding Volume of the Sphere. This allows the Camera’s Frustum Culling to work. This means, if we turn the camera away from the Sphere, it will not be drawn (and you will see the statistics drop to 0).
你定义了几个零件它的垂直和水平参数(在这个例子里是30X30)它的半径是(25).现在我们有一个球了.然后我们可以操作求得位置,在这个例子里,我们希望移动它的Z坐标为负值(就相当于把它移进屏幕里).然后我们设置这个球的约束(Bounding Volume).这允许视角平移(??不确定).这意味着如果我们视角离开球,这个球就不再被绘画了(你将看到统计信息掉到0,…..不明白啥意思)
Next, we will load the Monkey.jpg image and apply it as a texture to the Sphere. To load an image and obtain a Texture, we make use of the TextureManager and its loadTexture method. We will load the image with basic Texture values.
接下来,我们将读取Monkey.jpg图片再应用其贴到那个球上去.读取图片和获取贴图我们使用TextureManager管理器的读取贴图的方法.我们将读取图片的基本贴图值
Texture texture = TextureManager.loadTexture(
Lesson1.class.getClassLoader().getResource(
"jmetest/data/images/Monkey.jpg"),
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear);
We then create a TextureState and set this texture to it. To create the TextureState we use DisplaySystem as a factory method. SimpleGame has a reference to the current DisplaySystem instance: ‘display’.
然后我们创建一个TextureState.java设置贴图到其上.创建TextureState我们用DisplaySystem用工厂方法.SimpleGame参考当前DisplaySystem的实例’display’
TextureState ts = display.getRenderer().createTextureState();
Sets if this render state is enabled during rendering:
ts.setEnabled(true);
We then make use of the setTexture method to place the Texture of the Monkey.jpg image into the TextureState. The TextureState is now ready to be applied to the Sphere, and a call to setRenderState does this. Now that this TextureState is attached to the Sphere whenever the Sphere is rendered, it will use its texture coordinates and apply the image to itself.
然后我们用setTexture()方法任命贴图Monkey.jpg到TextureState上.那么现在调用setRenderState()方法来使得TextureState的贴图应用到球上.当球被渲染的时候TextureState已经附到球上了,它僵尸用贴图坐标和图片:
ts.setTexture(texture); s.setRenderState(ts);
Last, we attach the quot;>Sphere to the scene. SimpleGame provides an object call rootNode that represents the main scene. Attaching the Sphere to this Node prepares it for rendering. To attach, simply call:
最后,我们把球附给场景.SimpleGame提供了一个对象叫rootNode,表示主场景.把球附给这个Node准备渲染,方法为:
rootNode.attachChild(s);
With these few simple calls we have a textured Sphere rendered to the screen.
用以上的方法我们简单的把一个贴图了的球渲染到屏幕上了.
SimpleGame, like its name implies, makes things simple. However, for creating a full fledged game, we are going to want complete control. Next lesson will show us how to do this, by creating our own game type.
SimpleGame,就像名字一样,很简单.然而,做一个羽翼丰满的游戏,我们需要完全的控制,下一讲将讲述如何控制.








