在“使用M
下面是指南前四部分的鏈接:
l 第一部分:快速進入移動java 3D編程世界
l 第二部分:光的3D理論與定位
l 第三部分:粒子系統和立即模式渲染
l 第四部分:M
介紹
歡迎來到M
通過使用heightmap,設計者/開發者能夠容易地創建自然地形(可能相當于使用一個花邊噪音生成器,但不是必須的),幾乎不花費時間。Heightmap的魅力在于它有一個復雜的概念,例如一個漂亮的現實的3D地形,則把這個問題簡化為一個簡單的2D圖像。
和以前一樣,無論什么時候你感到了困惑就參考這里。
首先,或許也是最重要的,就是在索尼愛立信開發者世界上專業的移動Java 3D網絡區。其次,如果你碰到困難,就去索尼愛立信移動Java 3D論壇。對于其他的任何情況,使用索尼愛立信開發者世界網絡門戶,在那里你可以找到你的問題的答案,并且可以了解到更多。
預備知識
在通讀指南的這一部分之前你應該已經閱讀過前四部分,因為我將在這里使用前面已經寫過的代碼。
3D地形
我們先來對地形進行定義,好嗎?地形是真實世界的一個模型,有平原、山脈、河流、懸崖和丘陵等。地形的要點就是給用戶如在“真實”或者“現實”世界中漫步的印象。然而,如果從一個更加抽象的觀點看,我們會很快的意識到地形僅僅是在高度上的變化。例如,一個草原就是一個高度為常數的地形(除了可能有一些起伏和山丘外)。一個山區就是一個具有很大高度變化的地形,在空地間創建鴻溝,如此創建山區的幻覺。一個河流就是由一個平原和穿過它的曲線組成,這個曲線比它周圍的平原高度稍低。檢查這張地形圖:
如你所見,上面的地形由三塊高一些的區域描述(三個灰色的山丘),剩下的是一個深深的峽谷,里面充滿了水。此外,除了高度變化沒有什么。
Heightmap
下面就是heightmap了。它們是存儲高度變化的和使表面平滑的上好解決方案。在我開始講解有啟發的東西之前,現在我們觀察這個圖片。
這個一個灰度等比變化的圖像。沒有任何奇特的。它看起來像一個中間有個白色斑點的圓環。如此看來什么是一個灰色等比變化的圖像呢?那么,簡單來看,它是一個像素的集合,每個像素都是在灰度上從0到255上等比變化,0是黑色,255是全白。對嗎?聽起來很熟悉吧?那么如果你使用像素確定高度,如何?如果黑色的像素(值0)是最小的高度,白色的像素(值255)是最大的高度,如此你就有了一個描述高度的地圖,heightmap!對此另外一個重要的事情就是,如果你只想模糊圖像,因為像素是從0到255,所以你要對地形自動插補。
那么,你所有需要做的就是打開最中意的圖像程序,使用畫筆畫一些白色素材,然后建立一個heightmap。這聽起來很容易,當然,但是我們怎么樣將它轉化為可以渲染的網面呢?
Quad
將一個heightmap轉化為可以渲染的網面一點都不難。我們需要讀取heightmap的像素,然后創建由高度變化反應出的平面。在這個問題上一個非常易用的平面當然是Quad(四邊形)。因為所有圖像都是規則的矩形,四方形能夠很好地適合他們。那么Quad是什么呢?很簡單,一個Quad就是兩個三角形放在一起形成一個矩形平面。
上面的圖像代表了由兩個三角形組成的一個四邊形。如你所見,這個四邊形有四個脫節的端點,因為我們使用了兩個三角形描述它。四個角都被給予不同的高度,如此我們就開始了在3D世界中描述高度。然而一個四邊形描述整個地形還是遠遠不夠的,如果想要地形看起來有一點真實的感覺,需要很多四邊形。我們稍后再來講解這些,首先來看如何使用代碼創建一個四邊形。我們將在xz平面上創建一個有y變量的四邊形,因而有變化的高度。我們為前篇指南中創建的MeshFactory類引入一個新的方法,叫做createQuad。這個方法需要知道的就是在這個四邊形不同的角上有不同的高度,和選擇標志。下面是這個方法的第一個片段:
public static Mesh createQuad(short[] heights, int cullFlags)
{
// The vertrices of the quad
short[] vertrices = {-255, heights[0], -255,
255, heights[1], -255,
255, heights[2], 255,
-255, heights[3], 255};
看起來熟悉嗎?一個普通的四邊形由四個頂點組成,每一個頂點都有一個變化的y坐標,但是x和z不變。
// Create the model's vertex colors
VertexArray colorArray = new VertexArray(color.length/3, 3, 1);
colorArray.set(0, color.length / 3, color);
// Compose a VertexBuffer out of the PRevious vertrices and texture coordinates
VertexBuffer vertexBuffer = new VertexBuffer();
vertexBuffer.setPositions(vertexArray,
vertexBuffer.setColors(colorArray);
// Create indices and face lengths
int indices[] = new int[] {0, 1, 3, 2};
int[] stripLengths = new int[] {4};
// Create the model's triangles
triangles = new TriangleStripArray(indices, stripLengths);
這里我們創建在M
// Create the appearance
Appearance appearance = new Appearance();
PolygonMode pm = new PolygonMode();
pm.setCulling(cullFlags);
pm.setPerspectiveCorrectionEnable(true);
pm.setShading(PolygonMode.SHADE_SMOOTH);
appearance.setPolygonMode(pm);
這是一些標準的Appearance材料,然而我想讓你明白如何使用平滑陰影,這就意味著頂點的顏色在整個平面上以內插值替換,創建一個平滑的外觀。我們需要在以后說明為什么?,F在剩下的就是創建網面,相當直截了當:
// Finally create the Mesh
Mesh mesh = new Mesh(vertexBuffer, triangles, appearance);
從Heightmap創建四邊形
使用上面的方法,我們可以創建一個高度變化的四邊形,但是如我前面所說,我們需要很多四邊形來使地形看起來更真實,那么現在的問題就是如何將heightmap轉化為四邊形。這實際上并不算問題??催@個圖片:
我在前面的heightmap上面畫了一個白色的柵格。如果觀察柵格每一個小塊,你會發現矩形的格子區域就是另外一個heightmap,只是小了些。如果我們創建一個分辨率很高的柵格,你可能會意識到格子的區域變得非常小,這樣也非常容易接近一個四邊形。把它簡單化,為了接近一個heightmap,我們將它劃分成很多十分小的部分,每一部分代表一個四邊形。我們如何創建一個四邊形呢?簡單,下面就是必須的步驟:
l 劃分圖像為很多小部分(最小尺寸為2*2像素)
l 得到每一部分的角的像素,讀取它們的值(0-255)
l 將這些值作為高度賦給四邊形(參考方法聲明)
那么,從heightmap創建四邊形實在是簡單。在創建后,你只需渲染這些四邊形,一個接一個?,F在有些事情你必須了解。隨著heightmap柵格分辨率的不斷增加,而地形的平滑度也在不斷的提高,因為你使用了更多的四邊形來代表這個地形。然而,你也在激劇增加內存的使用空間和GPU需要運算的多邊形的數量。這是在每一個移動電話都需要依據可用內存、GPU運算能力等等進行平衡的。
實現
我們來看在M
1、 裝載一個heightmap
2、 建立一個數組,等比劃分柵格的尺寸
3、 從heightmap中讀取像素值,并且保存在一個新的數組里
4、 使用上述數組根據不同的高度產生四邊形(Quads)
這是一個簡單的四步程序。來,我們先來查看HeightMap類的私有成員:
// The actual heightmap containing the Y-coords of our triangles
private short[] heightMap;
private int[] data;
private int imgw, imgh;
// Map dimensions
private int mapWidth;
private int mapHeight;
// Actual quads
private Mesh[][] map;
// Water
private Mesh water;
// Local transform used for internal calculations
private Transform localTransform = new Transform();
它們是相當地自明,但是我將講解其中的一部分。首先heightMap數組是保存高度的等比數組。它不保存來自heightmap圖像中的像素。Mesh保存產生的所有要渲染的四邊形。最后,water網面是一個普通的平面,代表地形中的水(用來創建河流等等)。然后,我們看如何創建一個HeightMap:
public HeightMap(String imageName, float resolution, int waterLevel) throws IOException
{
// Check for invalid resolution values
if(resolution <=
throw new IllegalArgumentException("Resolution too small or too large");
// Load image and allocate the internal array
loadImage(imageName, resolution);
// Create quads
createQuads();
// Create the water
createWater(waterLevel);
}
我們將上面的代碼劃分為幾個步驟。首先檢查非法的分辨率值。非法值大于
接下來我們要裝載圖像,作為一個構造器參數提供。然而,我想你要明白在loadImage方法中還有一些其它有趣的事情。代碼如下:
// Load actual image
Image img = Image.createImage(path);
// Allocate temporary memory to store pixels
data = new int[img.getWidth() * img.getHeight()];
// Get its rgb values
img.getRGB(data, 0, img.getWidth(), 0, 0, img.getWidth(), img.getHeight());
imgw = img.getWidth();
imgh = img.getHeight();
// Clear image
img = null;
System.gc();
// Calculate new width and height
mapWidth = (int)(res * imgw);
mapHeight = (int)(res * imgh);
// Allocate heightmap
heightMap = new short[mapWidth * mapHeight];
// Calculate height and width offset into image
int xoff = imgw / mapWidth;
int yoff = imgh / mapHeight;
// Set height values
for(int y = 0; y < mapHeight; y++)
{
for(int x = 0; x < mapWidth; x++)
{
heightMap[x + y * mapWidth] = (short)((data[x * xoff + y * yoff * imgw] & 0x000000ff) * 10);
}
}
// Clear data
data = null;
img = null;
System.gc();
我將不再講述上面代碼的細節,因為我希望你能把它作為一個練習來審查??傊覀兿葘嶋H的圖像裝載到內存中,然后抽取它的像素值。接著利用構造函數提供的分辨率參數,我們依據尺寸創建一個柵格,并且使用像素值進行填充。最后我們做一個手動的垃圾收集釋放不必要的數據。這是非常重要的,因為loadImage方法是一個大量使用內存的方法,我們想要確保被接下來的少許認為所需要的至關重要的內存不被垃圾數據占有。
構造函數中的下一個方法就是createQuads。這是一個十分易懂的方法,得到生成的heightMap數組并且從其中創建四邊形。我們來看它的具體內容:
private void createQuads()
{
map = new Mesh[mapWidth][mapHeight];
short[] heights = new short[4];
for(int x = 0; x < (mapWidth - 1); x++)
{
for(int y = 0; y < (mapHeight - 1); y++)
{
// Set heights
setQuadHeights(heights, x, y, mapWidth);
// Create mesh
map[x][y] = MeshFactory.createQuad(heights, PolygonMode.CULL_NONE);
}
}
}
如你所見,我們所有需要做的就是反復遍歷heightMap表和抽取四個值,在MeshFactory.createQuad方法中我們作為高度值使用。
我要留給你的是檢查createWater方法。在這一點上它應該是你爛熟于胸的東西。我們僅僅使用MeshFactory.createPlane方法創建一個具有水面紋理的大平面。
渲染
如何渲染生成的四邊形呢?你應該知道這個問題的答案,不過無論如何我們還是來看看HeightMap類的render方法。如下:
public void render(Graphics3D g3d, Transform t)
{
for(int x = 0; x < map.length - 1; x++)
{
for(int y = 0; y < map[x].length - 1; y++)
{
localTransform.setIdentity();
localTransform.postTranslate(x *
localTransform.postMultiply(t);
g3d.render(map[x][y], localTransform);
}
}
localTransform.setIdentity();
localTransform.postScale(255, 255, 255);
localTransform.postRotate(-90,
g3d.render(water, localTransform);
}
所有你需要做的就是遍歷四邊形表,并對它們在空間中給定的位置上進行渲染。Render方法的使用者可能會在本地變換后提供一個應用于每個四邊形的變換,本地變化僅僅是將每一個四邊形放在自己的位置上。最后,我們將water網面放在heightmap創建期間定義好的高度上。
集合所有
現在使用稱心的HeightMap類需要的事情如下:
1、 從一個現有的灰度等變化圖像裝載一個HeightMap
2、 渲染
聽起來很簡單吧?因為的確是這樣。我們來看看裝載HeightMap的代碼:
private void createScene()
{
try
{
// We're using a pretty high resolution. If you want to test this on an actual
// handheld, try using a lower resolution, sUCh as 0.20 or 0.10
hm = new HeightMap("/res/heightmap4.png",
t.postTranslate(
t.postScale(
camTrans.postTranslate(
//camTrans.postTranslate(
}
catch(Exception e)
{
System.out.println("Heightmap error: " + e.getMessage());
e.printStackTrace();
TutorialMidlet.die();
}
}
這里沒有任何陌生的。我們只是裝載heightmap,并且對它進行一些變換,因為它將是提供給HeightMap渲染方法的變換。我們只是想讓它在屏幕后面向上一點點。我們還應該以很大數量測量它,因為一個地形通常是無限的,但是我只想你看到一個小的總攬圖。
另外一件重要的事情就是這篇指南中的HeightMap完全沒有使用任何剔出渲染。這是很必要,特別是很大的地形。然而為了使代碼保持清晰,我選擇移除任何種類的空間分割或者軟件剔出。你可以把它作為一個練習,只發送網面給可見的渲染者(也就是說,沒有網面離得太遠,或者在照相機后面)。
最后,渲染HeightMap的代碼是什么呢?下面就是主要的draw方法:
// Get the Graphics3D context
g3d = Graphics3D.getInstance();
// First bind the graphics object. We use our pre-defined rendering hints.
g3d.bindTarget(g, true, RENDERING_HINTS);
// Clear background
g3d.clear(back);
// Bind camera at fixed position in origo
g3d.setCamera(cam, camTrans);
// Render everything
hm.render(g3d, t);
// Check controls for camera movement
if(key[UP])
{
camTrans.postTranslate(
}
if(key[DOWN])
{
camTrans.postTranslate(
}
if(key[LEFT])
{
camTrans.postRotate(5,
}
if(key[RIGHT])
{
camTrans.postRotate(-5,
}
// Fly forward
if(key[FIRE])
camTrans.postTranslate(
真是沒有多說的了。HeightMap.render(g3d,t)方法相當干凈和直觀。盡管控制對你來說可能有些多余。你可以使用操縱桿移動照相機。上、下和旋轉左、右。使用FIRE鍵向前移動照相機。
總結
那么,為了延伸這個課程,你為什么不試著裝載源代碼zip文件中提供的其它的heightmap呢?看看出現了什么樣的地形。甚至更近一步,創建你自己的heightmap圖像!
放飛你的思緒,把它放在MIDlet中,在你的風景中巡游!
TutorialMidlet
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
public class TutorialMidlet extends MIDlet implements CommandListener
{
// A variable that holds the unique display
private Display display = null;
// The canvas
private M3GCanvas canvas = null;
// The MIDlet itself
private static MIDlet self = null;
/** Called when the application starts, and when it is resumed.
* We ignore the resume here and allocate data for our canvas
* in the startApp method. This is generally very bad practice.
*/
protected void startApp() throws MIDletStateChangeException
{
// Allocate
display = Display.getDisplay(this);
canvas = new M3GCanvas(30);
// Add a quit command to the canvas
// This command won't be seen, as we
// are running in fullScreen mode
// but it's always nice to have a quit command
canvas.addCommand(new Command("Quit", Command.EXIT, 1));
// Set the listener to be the MIDlet
canvas.setCommandListener(this);
// Start canvas
canvas.start();
display.setCurrent(canvas);
// Set the self
self = this;
}
/** Called when the game should pause, such as during a call */
protected void pauseApp()
{
}
/** Called when the application should shut down */
protected void destroyApp(boolean unconditional) throws MIDletStateChangeException
{
// Method that shuts down the entire MIDlet
notifyDestroyed();
}
/** Listens to commands and processes */
public void commandAction(Command c, Displayable d) {
// If we get an EXIT command we destroy the application
if(c.getCommandType() == Command.EXIT)
notifyDestroyed();
}
/** Static method that quits our application
* by using the static field 'self' */
public static void die()
{
self.notifyDestroyed();
}
}
M3GCanvas
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.m
import javax.microedition.m
import javax.microedition.m
import javax.microedition.m
public class M3GCanvas
extends GameCanvas
implements Runnable {
// Thread-control
boolean running = false;
boolean done = true;
// If the game should end
public static boolean gameOver = false;
// Rendering hints
public static final int STRONG_RENDERING_HINTS = Graphics3D.TRUE_COLOR Graphics3D.DITHER;
public static final int WEAK_RENDERING_HINTS = 0;
public static int RENDERING_HINTS = STRONG_RENDERING_HINTS;
// Key array
boolean[] key = new boolean[9];
// Global identity matrix
Transform identity = new Transform();
// Global Graphics3D object
Graphics3D g3d = null;
// The background
Background back = null;
// The global camera object
Camera cam = null;
Transform camTrans = new Transform();
// Transforms
/** Constructs the canvas
*/
public M3GCanvas(int fps)
{
// We don't want to capture keys normally
super(true);
// We want a fullscreen canvas
setFullScreenMode(true);
// Create our scene
createScene();
// Load our camera
loadCamera();
// Load our background
loadBackground();
// Set up graphics 3d
setUp();
}
/** Prepares the Graphics3D engine */
private void setUp()
{
// Get the instance
g3d = Graphics3D.getInstance();
}
/** When fullscreen mode is set, some devices will call
* this method to notify us of the new width/height.
* However, we don't really care about the width/height
* in this tutorial so we just let it be
*/
public void sizeChanged(int newWidth, int newHeight)
{
}
/** Loads our camera */
private void loadCamera()
{
// Create a new camera
cam = new Camera();
// Set the perspective of our camera. On a handheld you might want to set the far plane
// (currently
cam.setPerspective(
}
/** Loads the background */
private void loadBackground()
{
// Create a new background, set bg color to black
back = new Background();
back.setColor(0);
}
// The heightmap and its transform
HeightMap hm;
Transform t = new Transform();
/** Creates our scene */
private void createScene()
{
try
{
// We're using a pretty high resolution. If you want to test this on an actual
// handheld, try using a lower resolution, such as 0.20 or 0.10
hm = new HeightMap("/res/heightmap4.png",
t.postTranslate(
t.postScale(
camTrans.postTranslate(
//camTrans.postTranslate(
}
catch(Exception e)
{
System.out.println("Heightmap error: " + e.getMessage());
e.printStackTrace();
TutorialMidlet.die();
}
}
/** Draws to screen
*/
private void draw(Graphics g)
{
// Envelop all in a try/catch block just in case
try
{
// Get the Graphics3D context
g3d = Graphics3D.getInstance();
// First bind the graphics object. We use our pre-defined rendering hints.
g3d.bindTarget(g, true, RENDERING_HINTS);
// Clear background
g3d.clear(back);
// Bind camera at fixed position in origo
g3d.setCamera(cam, camTrans);
// Render everything
hm.render(g3d, t);
// Check controls for camera movement
if(key[UP])
{
camTrans.postTranslate(
}
if(key[DOWN])
{
camTrans.postTranslate(
}
if(key[LEFT])
{
camTrans.postRotate(5,
}
if(key[RIGHT])
{
camTrans.postRotate(-5,
}
// Fly forward
if(key[FIRE])
camTrans.postTranslate(
}
catch(Exception e)
{
reportException(e);
}
finally
{
// Always remember to release!
g3d.releaseTarget();
}
}
/** Starts the canvas by firing up a thread
*/
public void start() {
Thread myThread = new Thread(this);
// Make sure we know we are running
running = true;
done = false;
// Start
myThread.start();
}
/** Runs the whole thread. Also keeps track of FPS
*/
public void run() {
while(running) {
try {
// Call the process method (computes keys)
process();
// Draw everything
draw(getGraphics());
flushGraphics();
// Sleep to prevent starvation
try{ Thread.sleep(30); } catch(Exception e) {}
}
catch(Exception e) {
reportException(e);
}
}
// Notify completion
done = true;
}
/**
* @param e
*/
private void reportException(Exception e) {
System.out.println(e.getMessage());
System.out.println(e);
e.printStackTrace();
}
/** Pauses the game
*/
public void pause() {}
/** Stops the game
*/
public void stop() { running = false; }
/** Processes keys
*/
protected void process()
{
int keys = getKeyStates();
if((keys & GameCanvas.FIRE_PRESSED) != 0)
key[FIRE] = true;
else
key[FIRE] = false;
if((keys & GameCanvas.UP_PRESSED) != 0)
key[UP] = true;
else
key[UP] = false;
if((keys & GameCanvas.DOWN_PRESSED) != 0)
key[DOWN] = true;
else
key[DOWN] = false;
if((keys & GameCanvas.LEFT_PRESSED) != 0)
key[LEFT] = true;
else
key[LEFT] = false;
if((keys & GameCanvas.RIGHT_PRESSED) != 0)
key[RIGHT] = true;
else
key[RIGHT] = false;
}
/** Checks if thread is running
*/
public boolean isRunning() { return running; }
/** checks if thread has finished its execution completely
*/
public boolean isDone() { return done; }
}
MeshFactory
import java.io.IOException;
import javax.microedition.lcdui.Image;
import javax.microedition.m
import javax.microedition.m
import javax.microedition.m
import javax.microedition.m
import javax.microedition.m
import javax.microedition.m
import javax.microedition.m
import javax.microedition.m
import javax.microedition.m
import javax.microedition.m
import javax.microedition.m
/**
* Static class that handles creation of code-generated Meshes
*/
public class MeshFactory
{
/** Creates a texture plane that is alpha-blended
*
* @param texFilename The name of the texture image file
* @param cullFlags The flags for culling. See PolygonMode.
* @param alpha The alpha value of blending. Is a full color in 0xAARRGGBB format
* @return The finished textured mesh
*/
public static Mesh createAlphaPlane(String texFilename, int cullFlags, int alpha)
{
// Create a normal mesh
Mesh mesh = createPlane(texFilename, cullFlags);
// Make it blended
MeshOperator.convertToBlended(mesh, alpha, Texture2D.FUNC_BLEND);
return mesh;
}
/**
* Creates a colored quad consisting of two triangles.
* The heights parameter is an array that holds the heights of the new quad.
* The array must have a length of 4 and goes counter-clockwise starting from the
* northwestern position of the quad (-1, -1)
* @param heights The height array, denoting the heights (y-coords) of the four corners.
* @param cullFlags
* @param texFilename
* @return
*/
public static Mesh createQuad(short[] heights, int cullFlags)
{
// The vertrices of the quad
short[] vertrices = {-255, heights[0], -255,
255, heights[1], -255,
255, heights[2], 255,
-255, heights[3], 255};
// The arrays
VertexArray vertexArray;
IndexBuffer triangles;
// Create the model's vertrices
vertexArray = new VertexArray(vertrices.length/3, 3, 2);
vertexArray.set(0, vertrices.length/3, vertrices);
// Allocate color array
byte[] color = new byte[12];
for(int i = 0; i < heights.length; i++)
{
int j = i * 3;
// Altitude check
if(heights[i] >= 1000)
{
byte col = (byte)(57 + (heights[i] /
color[j] = col;
color[j + 1] = col;
color[j + 2] = col;
}
else
{
byte gCol = 110;
byte bCol = 25;
color[j] = 0;
color[j + 1] = (byte)(gCol - (heights[i] /
color[j + 2] = (byte)(bCol - (heights[i] /
}
}
// Create the model's vertex colors
VertexArray colorArray = new VertexArray(color.length/3, 3, 1);
colorArray.set(0, color.length / 3, color);
// Compose a VertexBuffer out of the previous vertrices and texture coordinates
VertexBuffer vertexBuffer = new VertexBuffer();
vertexBuffer.setPositions(vertexArray,
vertexBuffer.setColors(colorArray);
// Create indices and face lengths
int indices[] = new int[] {0, 1, 3, 2};
int[] stripLengths = new int[] {4};
// Create the model's triangles
triangles = new TriangleStripArray(indices, stripLengths);
// Create the appearance
Appearance appearance = new Appearance();
PolygonMode pm = new PolygonMode();
pm.setCulling(cullFlags);
pm.setPerspectiveCorrectionEnable(true);
pm.setShading(PolygonMode.SHADE_SMOOTH);
appearance.setPolygonMode(pm);
// Finally create the Mesh
Mesh mesh = new Mesh(vertexBuffer, triangles, appearance);
// All done
return mesh;
}
/**
* @param texFilename
* @return
* @throws IOException
*/
public static Texture2D createTexture2D(String texFilename) throws IOException {
// Open image
Image texImage = Image.createImage(texFilename);
Texture2D theTexture = new Texture2D(new Image2D(Image2D.RGBA, texImage));
// Modulated blending
theTexture.setBlending(Texture2D.FUNC_MODULATE);
// Set wrapping and filtering
theTexture.setWrapping(Texture2D.WRAP_CLAMP, Texture2D.WRAP_CLAMP);
theTexture.setFiltering(Texture2D.FILTER_NEAREST, Texture2D.FILTER_NEAREST);
return theTexture;
}
/**
* Creates a textured plane.
* @param texFilename The name of the texture image file
* @param cullFlags The flags for culling. See PolygonMode.
* @return The finished textured mesh
*/
public static Mesh createPlane(String texFilename, int cullFlags)
{
// The vertrices of the plane
short vertrices[] = new short[] {-1, -1, 0,
1, -1, 0,
1, 1, 0,
-1, 1, 0};
// Texture coords of the plane
short texCoords[] = new short[] {0, 255,
255, 255,
255, 0,
0, 0};
// The classes
VertexArray vertexArray, texArray;
IndexBuffer triangles;
// Create the model's vertrices
vertexArray = new VertexArray(vertrices.length/3, 3, 2);
vertexArray.set(0, vertrices.length/3, vertrices);
// Create the model's texture coords
texArray = new VertexArray(texCoords.length / 2, 2, 2);
texArray.set(0, texCoords.length / 2, texCoords);
// Compose a VertexBuffer out of the previous vertrices and texture coordinates
VertexBuffer vertexBuffer = new VertexBuffer();
vertexBuffer.setPositions(vertexArray,
vertexBuffer.setTexCoords(0, texArray,
// Create indices and face lengths
int indices[] = new int[] {0, 1, 3, 2};
int[] stripLengths = new int[] {4};
// Create the model's triangles
triangles = new TriangleStripArray(indices, stripLengths);
// Create the appearance
Appearance appearance = new Appearance();
PolygonMode pm = new PolygonMode();
pm.setCulling(cullFlags);
appearance.setPolygonMode(pm);
// Create and set the texture
try
{
// Open image
Texture2D theTexture = createTexture2D(texFilename);
// Add texture to the appearance
appearance.setTexture(0, theTexture);
}
catch(Exception e)
{
// Something went wrong
System.out.println("Failed to create texture");
System.out.println(e);
}
// Finally create the Mesh
Mesh mesh = new Mesh(vertexBuffer, triangles, appearance);
// All done
return mesh;
}
}
MeshOperator
import javax.microedition.m
import javax.microedition.m
/**
* Performs some basic operations on Mesh objects
*/
public class MeshOperator
{
/** Sets the alpha blending of a mesh. Only meaningful if the mesh already is alpha blended */
public static void setMeshAlpha(Mesh m, int alpha)
{
m.getVertexBuffer().setDefaultColor(alpha);
}
/**
*
* @param m The mesh to convert to a blended one
* @param alpha The alpha color to blend with
* @param textureBlending The texture blending parameter.
*/
public static void convertToBlended(Mesh m, int alpha, int textureBlending)
{
// Set the alpha
setMeshAlpha(m, alpha);
// Fix the compositing mode
CompositingMode cm = new CompositingMode();
cm.setBlending(CompositingMode.ALPHA);
m.getAppearance(0).setCompositingMode(cm);
m.getAppearance(0).getTexture(0).setBlending(textureBlending);
}
public static void setPerspectiveCorrection(Mesh m, boolean on)
{
m.getAppearance(0).getPolygonMode().setPerspectiveCorrectionEnable(on);
}
}
HeightMap
import java.io.IOException;
import javax.microedition.lcdui.Image;
import javax.microedition.m
import javax.microedition.m
import javax.microedition.m
import javax.microedition.m
/**
*
*/
public class HeightMap
{
// The actual heightmap containing the Y-coords of our triangles
private short[] heightMap;
private int[] data;
private int imgw, imgh;
// Map dimensions
private int mapWidth;
private int mapHeight;
// Actual quads
private Mesh[][] map;
// Water
private Mesh water;
// Local transform used for internal calculations
private Transform localTransform = new Transform();
/**
* Allocates the internal heightmap and prepares it for rendering.
* @param imageName The path to the actual heightmap BW image
* @param resolution The resolution of the heightmap. A value of 1.0 means that each pixel is one quad.
* @param waterLevel TODO
* @throws IOException if any error occurs while loading the heightmap image or the texture images.
*/
public HeightMap(String imageName, float resolution, int waterLevel) throws IOException
{
// Check for invalid resolution values
if(resolution <=
throw new IllegalArgumentException("Resolution too small or too large");
// Load image and allocate the internal array
loadImage(imageName, resolution);
// Create quads
createQuads();
// Create the water
createWater(waterLevel);
}
/** Create water in a very simple manner by representing it with
* a textured quad.
*/
private void createWater(int level) throws IOException
{
water = MeshFactory.createPlane("/res/water0.png", PolygonMode.CULL_NONE);
}
/**
* Create all quads (Triangle pairs) that our
*/
private void createQuads()
{
map = new Mesh[mapWidth][mapHeight];
short[] heights = new short[4];
for(int x = 0; x < (mapWidth - 1); x++)
{
for(int y = 0; y < (mapHeight - 1); y++)
{
// Set heights
setQuadHeights(heights, x, y, mapWidth);
// Create mesh
map[x][y] = MeshFactory.createQuad(heights, PolygonMode.CULL_NONE);
}
}
}
/**
* @param heights
* @param x
* @param y
*/
private void setQuadHeights(short[] heights, int x, int y, int scanline)
{
heights[0] = heightMap[x + y * scanline];
heights[1] = heightMap[x + y * scanline + 1];
heights[3] = heightMap[x + (y + 1) * scanline];
heights[2] = heightMap[x + (y + 1) * scanline + 1];
}
private void loadImage(String path, float res) throws IOException
{
// Load actual image
Image img = Image.createImage(path);
// Allocate temporary memory to store pixels
data = new int[img.getWidth() * img.getHeight()];
// Get its rgb values
img.getRGB(data, 0, img.getWidth(), 0, 0, img.getWidth(), img.getHeight());
imgw = img.getWidth();
imgh = img.getHeight();
// Clear image
img = null;
System.gc();
// Calculate new width and height
mapWidth = (int)(res * imgw);
mapHeight = (int)(res * imgh);
// Allocate heightmap
heightMap = new short[mapWidth * mapHeight];
// Calculate height and width offset into image
int xoff = imgw / mapWidth;
int yoff = imgh / mapHeight;
// Set height values
for(int y = 0; y < mapHeight; y++)
{
for(int x = 0; x < mapWidth; x++)
{
heightMap[x + y * mapWidth] = (short)((data[x * xoff + y * yoff * imgw] & 0x000000ff) * 10);
}
}
// Clear data
data = null;
img = null;
System.gc();
}
/**
* Renders this heightmap using a specified graphics context and a transform.
* @param g3d
* @param t
*/
public void render(Graphics3D g3d, Transform t)
{
for(int x = 0; x < map.length - 1; x++)
{
for(int y = 0; y < map[x].length - 1; y++)
{
localTransform.setIdentity();
localTransform.postTranslate(x *
localTransform.postMultiply(t);
g3d.render(map[x][y], localTransform);
}
}
localTransform.setIdentity();
localTransform.postScale(255, 255, 255);
localTransform.postRotate(-90,
g3d.render(water, localTransform);
}
}
相關資源鏈接:
源代碼、應用程序(JAR/JAD)
原文地址
(出處:http://www.49028c.com)
新聞熱點
疑難解答