java3d设计的知识面比较多,常见的三维绘制有多边形造型和光束跟踪。
多边形造型(polygon modeling):将虚拟三维时间看做平面多边形。
光束跟踪(ray tracing):建立光束模型,简历眼到光源的模型。
java中常见的利用三维加速卡方法:Java3D和OpenGL关联。
Java3D核心是用OpenGL或DirectX绘制
现在我们用轻量级三维多边形绘制器,暂不用Java3D api
需要数学方面知识,但都很浅。
先考虑一个案例:我们构造一个四边形,然后它可以任意围绕x轴或y轴旋转,并且可以放大缩小。当然也可以围绕Z轴,只不过这样感觉不到3D效果。
解释一下这里的x,y,z。
在二维图形中,如果和屏幕相同的二维坐标,原点在屏幕左上方,x从左往右,y从上往下.
在三维图形中,x向右,y向上,z向里(感觉就是往屏幕里,远离人的方向)
我们从最终测试类出发,先构思出这个类应有的方法,然后回推该写的具体类和方法。
伪代码
package com.jsheng.game.test2;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import com.jsheng.game.util.GameAction;
import com.jsheng.game.util.GameCore;
import com.jsheng.game.util.InputManager;
/** function:
* company: jsheng
* @author wanghn [email protected]
*/
public class My3DTest1 extends GameCore {
public static void main(String[] args) {
new My3DTest1().run();
}
private 多边形 a = new 多边形();
private GameAction exit = new GameAction("exit");
private GameAction zoomIn = new GameAction("zoomIn");
private GameAction zoomOut = new GameAction("zoomOut");
public void init() {
super.init();
InputManager inputManager = new InputManager(
screen.getFullScreenWindow());
inputManager.setCursor(InputManager.INVISIBLE_CURSOR);
inputManager.mapToKey(exit, KeyEvent.VK_ESCAPE);
inputManager.mapToKey(zoomIn, KeyEvent.VK_UP);
inputManager.mapToKey(zoomOut, KeyEvent.VK_DOWN);
}
public void do设置多边形(){
}
public void do设置多边形旋转角度(){
}
public void update(long elapsedTime) {
if (exit.isPressed()) {
stop();
return;
}
elapsedTime = Math.min(elapsedTime, 100);
}
public void draw(Graphics2D g) {
//处理
this.draw多边形(g, a);
}
/**
在屏幕绘制多边形
*/
private void draw多边形(Graphics2D g,多边形 a)
{
}
}
向量,三角函数等数学知识就不介绍了,这里的程序模拟了这些算法。
说个概念:视图窗口和镜头
视图窗口是三维空间中语屏幕窗口大小相同的窗口。镜头就好像眼睛,去观察视图窗口,然后去将三维物体投影到视图窗口。(这里可以参考相关资料,或我以后整理上图更好说明这个问题)
3D中的数学
将三维物体投影到视图窗口中可以简化为直角三角形问题。
假设三维物体坐标二维点是(x,y),投影到视图窗口的二维点是(x1,y1)
从镜头到视图窗口距离是d
三维点到原点距离是z,得到:
x1=dx/-z;y1=dy/-z
d的值是我们来设置的
另外还能用夹角
从镜头出发到视图窗口两遍发射直线,两直线得到一个夹角,我们就可以通过三角函数来计算一些问题。
给出类代码,具备视图窗口和投影功能。
package com.jsheng.game.util.java3d;
import java.awt.Rectangle;
public class ViewWindow {
private Rectangle bounds; //视图窗口的边界
private float angle; //水平视角
private float distance; //视图窗口到镜头的距离
public ViewWindow(int left, int top, int width, int height,
float angle)
{
bounds = new Rectangle();
this.angle = angle;
setBounds(left, top, width, height);
}
public void setBounds(int left, int top, int width,
int height)
{
bounds.x = left;
bounds.y = top;
bounds.width = width;
bounds.height = height;
distance = (bounds.width/2) /
(float)Math.tan(angle/2);
}
public void setAngle(float angle) {
this.angle = angle;
distance = (bounds.width/2) /
(float)Math.tan(angle/2);
}
public float getAngle() {
return angle;
}
public int getWidth() {
return bounds.width;
}
public int getHeight() {
return bounds.height;
}
public int getTopOffset() {
return bounds.y;
}
public int getLeftOffset() {
return bounds.x;
}
public float getDistance() {
return distance;
}
public float convertFromViewXToScreenX(float x) {
return x + bounds.x + bounds.width/2;
}
public float convertFromViewYToScreenY(float y) {
return -y + bounds.y + bounds.height/2;
}
public float convertFromScreenXToViewX(float x) {
return x - bounds.x - bounds.width/2;
}
public float convertFromScreenYToViewY(float y) {
return -y + bounds.y + bounds.height/2;
}
//将指定的向量投影到屏幕
public void project(Vector3D v) {
v.x = distance * v.x / -v.z;
v.y = distance * v.y / -v.z;
// convert to screen coordinates
v.x = convertFromViewXToScreenX(v.x);
v.y = convertFromViewYToScreenY(v.y);
}
}
]
处理多边形:
package com.jsheng.game.util.java3d;
public class Polygon3D {
private static Vector3D temp1 = new Vector3D();
private static Vector3D temp2 = new Vector3D();
private Vector3D[] v;
private int numVertices;
private Vector3D normal;
public Polygon3D() {
numVertices = 0;
v = new Vector3D[0];
normal = new Vector3D();
}
public Polygon3D(Vector3D[] v) {
this.v = v;
numVertices = v.length;
calcNormal();
}
public void setTo(Polygon3D polygon) {
numVertices = polygon.numVertices;
normal.setTo(polygon.normal);
ensureCapacity(numVertices);
for (int i=0; i<numVertices; i++) {
v[i].setTo(polygon.v[i]);
}
}
public void ensureCapacity(int length) {
if (v.length < length) {
Vector3D[] newV = new Vector3D[length];
for (int i=v.length; i<newV.length; i++) {
newV[i] = new Vector3D();
}
v = newV;
}
}
public int getNumVertices() {
return numVertices;
}
public Vector3D getVertex(int index) {
return v[index];
}
public void project(ViewWindow view) {
for (int i=0; i<numVertices; i++) {
view.project(v[i]);
}
}
public void add(Vector3D u) {
for (int i=0; i<numVertices; i++) {
v[i].add(u);
}
}
public void subtract(Vector3D u) {
for (int i=0; i<numVertices; i++) {
v[i].subtract(u);
}
}
public void add(Transform3D xform) {
addRotation(xform);
add(xform.getLocation());
}
public void subtract(Transform3D xform) {
subtract(xform.getLocation());
subtractRotation(xform);
}
public void addRotation(Transform3D xform) {
for (int i=0; i<numVertices; i++) {
v[i].addRotation(xform);
}
normal.addRotation(xform);
}
public void subtractRotation(Transform3D xform) {
for (int i=0; i<numVertices; i++) {
v[i].subtractRotation(xform);
}
normal.subtractRotation(xform);
}
public Vector3D calcNormal() {
if (normal == null) {
normal = new Vector3D();
}
temp1.setTo(v[2]);
temp1.subtract(v[1]);
temp2.setTo(v[0]);
temp2.subtract(v[1]);
normal.setToCrossProduct(temp1, temp2);
normal.normalize();
return normal;
}
public Vector3D getNormal() {
return normal;
}
public void setNormal(Vector3D n) {
if (normal == null) {
normal = new Vector3D(n);
}
else {
normal.setTo(n);
}
}
public boolean isFacing(Vector3D u) {
temp1.setTo(u);
temp1.subtract(v[0]);
return (normal.getDotProduct(temp1) >= 0);
}
}
三维变换之旋转:
譬如沿z轴旋转,则z值不变。
r是旋转半径
则x = rcos(a)
y=rcos(a)
a是原始角度
x1=rcos(a+b)
y1=rsin(a+b)
b是旋转角度
x1=rcosacosb-rsinasinb
y1=rsinacosb+rsinbcosa
最后得到
x1=xcosb-ysinb
y1=xsinb+ycosb
开发包装旋转功能的类
package com.jsheng.game.util.java3d;
public class Transform3D {
protected Vector3D location;
private float cosAngleX;
private float sinAngleX;
private float cosAngleY;
private float sinAngleY;
private float cosAngleZ;
private float sinAngleZ;
public Transform3D() {
this(0,0,0);
}
public Transform3D(float x, float y, float z) {
location = new Vector3D(x, y, z);
setAngle(0,0,0);
}
public Transform3D(Transform3D v) {
location = new Vector3D();
setTo(v);
}
public Object clone() {
return new Transform3D(this);
}
public void setTo(Transform3D v) {
location.setTo(v.location);
this.cosAngleX = v.cosAngleX;
this.sinAngleX = v.sinAngleX;
this.cosAngleY = v.cosAngleY;
this.sinAngleY = v.sinAngleY;
this.cosAngleZ = v.cosAngleZ;
this.sinAngleZ = v.sinAngleZ;
}
public Vector3D getLocation() {
return location;
}
public float getCosAngleX() {
return cosAngleX;
}
public float getSinAngleX() {
return sinAngleX;
}
public float getCosAngleY() {
return cosAngleY;
}
public float getSinAngleY() {
return sinAngleY;
}
public float getCosAngleZ() {
return cosAngleZ;
}
public float getSinAngleZ() {
return sinAngleZ;
}
public float getAngleX() {
return (float)Math.atan2(sinAngleX, cosAngleX);
}
public float getAngleY() {
return (float)Math.atan2(sinAngleY, cosAngleY);
}
public float getAngleZ() {
return (float)Math.atan2(sinAngleZ, cosAngleZ);
}
public void setAngleX(float angleX) {
cosAngleX = (float)Math.cos(angleX);
sinAngleX = (float)Math.sin(angleX);
}
public void setAngleY(float angleY) {
cosAngleY = (float)Math.cos(angleY);
sinAngleY = (float)Math.sin(angleY);
}
public void setAngleZ(float angleZ) {
cosAngleZ = (float)Math.cos(angleZ);
sinAngleZ = (float)Math.sin(angleZ);
}
public void setAngle(float angleX, float angleY, float angleZ)
{
setAngleX(angleX);
setAngleY(angleY);
setAngleZ(angleZ);
}
public void rotateAngleX(float angle) {
if (angle != 0) {
setAngleX(getAngleX() + angle);
}
}
public void rotateAngleY(float angle) {
if (angle != 0) {
setAngleY(getAngleY() + angle);
}
}
public void rotateAngleZ(float angle) {
if (angle != 0) {
setAngleZ(getAngleZ() + angle);
}
}
public void rotateAngle(float angleX, float angleY,
float angleZ)
{
rotateAngleX(angleX);
rotateAngleY(angleY);
rotateAngleZ(angleZ);
}
}
最后完成最初设想的类
package com.jsheng.game.test2;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.geom.GeneralPath;
import com.jsheng.game.util.GameAction;
import com.jsheng.game.util.GameCore;
import com.jsheng.game.util.InputManager;
import com.jsheng.game.util.java3d.Polygon3D;
import com.jsheng.game.util.java3d.Transform3D;
import com.jsheng.game.util.java3d.Vector3D;
import com.jsheng.game.util.java3d.ViewWindow;
/** function:
* company: jsheng
* @author wanghn [email protected]
*/
public class My3DTest1 extends GameCore {
public static void main(String[] args) {
new My3DTest1().run();
}
Vector3D v1 = new Vector3D(-50, 0, 0);
Vector3D v2 = new Vector3D(50, 0, 0);
Vector3D v3 = new Vector3D(-50, 100, 0);
Vector3D v4 = new Vector3D(50, 100, 0);
Vector3D[] vs = new Vector3D[]{v1,v2,v3,v4};
private Polygon3D p = new Polygon3D(vs);
private Transform3D myTransform = new Transform3D(0,0,-500);
private Polygon3D transformedPolygon = new Polygon3D();
private ViewWindow viewWindow;
private GameAction exit = new GameAction("exit");
private GameAction zoomIn = new GameAction("zoomIn");
private GameAction zoomOut = new GameAction("zoomOut");
public void init() {
super.init();
InputManager inputManager = new InputManager(
screen.getFullScreenWindow());
inputManager.setCursor(InputManager.INVISIBLE_CURSOR);
inputManager.mapToKey(exit, KeyEvent.VK_ESCAPE);
inputManager.mapToKey(zoomIn, KeyEvent.VK_UP);
inputManager.mapToKey(zoomOut, KeyEvent.VK_DOWN);
viewWindow = new ViewWindow(0, 0,
screen.getWidth(), screen.getHeight(),
(float)Math.toRadians(75));
}
public void update(long elapsedTime) {
if (exit.isPressed()) {
stop();
return;
}
elapsedTime = Math.min(elapsedTime, 100);
myTransform.rotateAngleY(0.002f*elapsedTime);
if (zoomIn.isPressed()) {
myTransform.getLocation().z += 0.5f*elapsedTime;
}
if (zoomOut.isPressed()) {
myTransform.getLocation().z -= 0.5f*elapsedTime;
}
}
public void draw(Graphics2D g) {
g.setColor(Color.black);
g.fillRect(0, 0, screen.getWidth(), screen.getHeight());
g.setColor(Color.white);
g.drawString("按下ESC退出",
5, fontSize);
trandformAndDraw(g, p);
}
private void trandformAndDraw(Graphics2D g,
Polygon3D poly)
{
transformedPolygon.setTo(poly);
transformedPolygon.add(myTransform);
transformedPolygon.project(viewWindow);
GeneralPath path = new GeneralPath();
Vector3D v = transformedPolygon.getVertex(0);
path.moveTo(v.x, v.y);
for (int i=1; i<transformedPolygon.getNumVertices(); i++) {
v = transformedPolygon.getVertex(i);
path.lineTo(v.x, v.y);
}
g.setColor(Color.red);
g.fill(path);
}
}