本文是此系列两部分中的第 1 部分,介绍了 Mobile 3D Graphics API (JSR 184) 的有关内容。作者将带领您进入 Java 移动设备的 3D 编程世界,并展示了处理光线、摄像机和材质的方法。
键位映射 示例中使用了 MIDP 的动作游戏作为处理按键事件的范例。其控制游戏动作的物理键映射到运行示例的设备上。Sun 的 Java Wireless Toolkit 将 LEFT、RIGHT、UP、DOWN 和 FIRE 映射为游戏操纵杆。GAME_A 映射为 1 键、GAME_B 映射为 3 键、GAME_C 映射为 7 键、GAME_D 映射为 9 键。
按下相应的键更改以下三个属性之一:渲染模式(平面着色渲染模式或光影渲染模式)、背景拣出(看见的是立方体的外面还是里面)、环绕(逆时针三角形表示的是正面还是背面)。图 4 展示了这些选项。VertexColorsSample.java 中包含该示例的完整源代码。
图 4. 经着色的立方体:a) 光影渲染模式;b) 平面着色渲染模式,背面被拣出;c) 正面被拣出,逆时针环绕
转换 在本文开始处,我曾经使用了一个 Transform 对象将摄像机向后移动,以便查看整个立方体。通过同样的方式可以转换任意 3D 对象。
您可以通过数学方式将转换表示为矩阵操作。一个向量 —— 例如,摄像机位置 —— 乘以恰当的平移矩阵从而得到相应移动的向量。Transform 对象就表示了这样的一个矩阵。对于绝大多数普通转换来说,M3G 提供了 3 种便于使用的接口,隐藏了底层的数学计算:
Transform.postScale(float sx, float sy, float sz):在 x、y、z 方向伸缩 3D 对象。大于 1 的值将按照给定因数扩大对象;0 和 1 之间的值将缩小对象。负值则同时执行伸缩和镜像操作。
Transform.postTranslate(float tx, float ty, float tz):通过为 x、y 和 z 坐标增加指定值移动 3D 对象。负值则表示向负轴方向移动对象。
Transform.postRotate(float angle, float ax, float ay, float az):按给定角度绕穿过(0, 0, 0)和(ax, ay, az)的轴旋转对象。角度为正值,则表示若您顺着正旋转轴方向观察,对象是按顺时针旋转的。例如,postRotate(30, 1, 0, 0) 将绕 x 轴将对象旋转 30 度。
所有操作名都是以 "post" 开头的,表示当前 Transform 对象是从右边与给定转换矩阵相乘的 —— 矩阵操作的顺序是非常重要的。如果您向右旋转 90 度,然后走两步,这时您所处的位置显然与先走两步再转身不同。您可以在各步行指令之后调用两个 post 方法 postRotate() 和 postTranslate(),从而获得上面的步行指令。调用顺序决定了所获得的步行指令。由于使用的是后乘,所以您最后使用的转换会首先应用。
M3G 有一个 Transform 类和一个 Transformable 接口。所有快速模式的 API 均可接受 Transform 对象作为参数,用于修改其关联的 3D 对象。另外,在保留模式下使用 Transformable 接口来转换作为 3D 世界一部分的节点。在本系列的第 2 部分中将就此详细讨论。
清单 6 的示例展示了转换。
清单 6. 转换
/**
* Renders the sample on the screen.
*
* @param graphics the graphics object to draw on.
*/
protected void paint(Graphics graphics)
{
_graphics3d.bindTarget(graphics);
_graphics3d.clear(null);
_graphics3d.render(_cubeVertexData, _cubeTriangles,
new Appearance(), _cubeTransform);
_graphics3d.releaseTarget();
drawMenu(graphics);
}
/**
* Handles key presses.
*
* @param keyCode key code.
*/
protected void keyPressed(int keyCode)
{
switch (getGameAction(keyCode))
{
case UP:
transform(_transformation, TRANSFORMATION_X_AXIS, false);
break;
case DOWN:
transform(_transformation, TRANSFORMATION_X_AXIS, true);
break;
case LEFT:
transform(_transformation, TRANSFORMATION_Y_AXIS, false);
break;
case RIGHT:
transform(_transformation, TRANSFORMATION_Y_AXIS, true);
break;
case GAME_A:
transform(_transformation, TRANSFORMATION_Z_AXIS, false);
break;
case GAME_B:
transform(_transformation, TRANSFORMATION_Z_AXIS, true);
break;
case FIRE:
init();
break;
case GAME_C:
_transformation++;
_transformation %= 3;
break;
// no default
}
repaint();
}