(JAVA)MyColorCube5(另一个Matrix3D类与面消隐)
实现功能:显示一个有颜色的正方体, 拖动鼠标时,正方体绕中心点转动.
虽然实现功能与MyColorCube2相似,但本例在MyColorCube2基础上作了如下改动:
一)更换了一个三维矩阵类
此矩阵类与原矩阵类同名,均为"Matrix3D",但它很短小,其特点:
1)虽然只有一个旋转的方法,但这个方法可根据传入参数的不同,实现围绕不同轴的旋转.而且,还可实现不同方向(反时针与顺时针)的旋转.具体见代码中的说明.
2)本例的Matrix3D类没有"平移","变比"等方法.图形的平移与变比(缩小与放大),均在Matrix3D类外的代码实现.
3)使用本例的Matrix3D类,最好在建立物体坐标系时,将物体坐标系的原点直接选在物体旋转的中心.
4)使用原来的Matrix3D类,可将所有的旋转,平移与变比等变化的数据,都用一个级联矩阵来记录,要显示图形时,只要将图形的各顶点乘以这个级联矩阵,即可.
而使用本例的Matrix3D类,由于级联矩阵只记录旋转的数据,所以图形各顶点乘以这个级联矩阵后,得到的只是各点旋转后的各顶点坐标(本例用rotPts[][]数组来存放旋转后的点坐标),
还须另外进行平移与变比的变换,才能使之成为屏幕坐标(本例用scrPts[][]数组来存放最终得到的各点的屏幕坐标).
5)还有一个特别要注意的不同之处:
使用原矩阵类时,程序在运行期间始终维护一个记录当前图形最新状态的级联矩阵.
若图形发生新的变化,只要将此级联矩阵乘以一个反映图形新变化的矩阵即可.
本例的矩阵类则大不相同.本例定义了angleX,angleY,angleZ三个变量,它存放的是从程序开始运行直至当前为止,图形绕各坐标轴转动的角度之和.
可以说,本例维护的是累计转动的角度, 矩阵则是用完就丢弃.
两个"Matrix3D"类各有优劣,可依实际需要选用.
本例新更换的Matrix3D"类,将使后面添加"明暗变化","透视效果"等功能实现起来更为方便.
二)面消隐
在'MyColorCube1"中讨论过面消隐,这里用的基本上是同一方法.不同的是:判断可见性时用的是只经过旋转变换的rotPts数组的点坐标数据,
由于它未经放大和平移,精度较高.代码如下:
private boolean faceUp(int f)
{
return (rotPts[f][2]<0);
//rotPts[f][2]为该面的中心点的Z坐标
}
void MyDraw(Graphics2D g2,int
w,int h){
for(int f =0; f
if
(faceUp(f)) //调用faceUp()方法,判断面是否可见.
DrawPoly( g2, f);
}
}
*/
import java.awt.Color;
import java.awt.Graphics;
//import java.awt.Color;
//import java.awt.Event;
import java.awt.event.*;
//import java.applet.*;
import java.applet.Applet;
import java.awt.*;
public class MyColorCube5
extends Applet implements MouseListener,
MouseMotionListener {
public Image bgImage;
public Graphics bg;
public ColorCube obj;
static final double PI=3.1416;
int
prevX,prevY;//按下鼠标时,光标的坐标
//Matrix3D
mat,matRot;
int
h,w;
float
f;
public void init(){
w = this.getWidth();
h = this.getHeight();
obj = new ColorCube(w,h);
addMouseListener(this);
addMouseMotionListener(this);
}
public void start(){
}
public void update (Graphics g) {
Graphics2D g2 =(Graphics2D) g;
if (bgImage == null){
bgImage = createImage (this.getSize().width,
this.getSize().height);
bg = bgImage.getGraphics ();
}
Graphics2D bg2D =(Graphics2D)
bg;//用Image.getGraphics ()得到的是Graphics类,
//需要将其转换为Graphics2D类,才能完成以后绘制
// 后台清屏,
bg.setColor (getBackground ());
bg2D.fillRect(0, 0, w, h);
// bg.fillRect(0, 0, w, h);
//以下在后台绘制
if
(obj != null) {
//objs[i].render(g2);
obj.MyDraw(bg2D,w,h);//调用ColorCube的MyDraw方法,绘制转动变化后的图形
}
g2.drawImage (bgImage, 0, 0, null);
}//end
update
public void paint(Graphics g){
Graphics2D g2 = (Graphics2D)g;
obj.MyDraw(g2,w,h);
}
public void mouseClicked(MouseEvent
e) {
}
public void mousePressed(MouseEvent
e) {
prevX = e.getX();
prevY = e.getY();
e.consume();//不再执行原来(父类)的方法
}
public void
mouseReleased(MouseEvent e) {
//松开鼠标时发生
}
public void mouseEntered(MouseEvent
e) {
//进入显示区时发生
}
public void mouseExited(MouseEvent
e) {
//离开显示区域时发生
}
public void mouseDragged(MouseEvent
e) {
//拖动鼠标时发生
int x = e.getX();
int y = e.getY();
//鼠标左右移动(x轴坐标改变),绕Y轴转动. 鼠标移动的距离等于显示区域宽度时,转动180度
double thetaY = (x-prevX) * 180.0f /
getSize().width;
//鼠标上下移动(Y轴坐标改变),绕X轴转动 .鼠标移动的距离等于显示区域高度时,转动180度
double thetaX = (y-prevY) * 180.0f /
getSize().height;
obj.CubeRotate(thetaX, thetaY, 0);
obj.CalcScrPts((double) w/2,(double)
h/2, 0);
prevX = x; //此两句很关键,每计算完一次,都须将当前点作为起始点
prevY = y;
repaint();
e.consume();
}
public void mouseMoved(MouseEvent
e) {
}
public void destroy() {
removeMouseListener(this);
removeMouseMotionListener(this);
}
}
class ColorCube {
//本例用绘制多边形的方法(fillPolygon()与drawPolygon())来绘制几何体的棱(边)
private static final int[][]
polygons =
// Solid cube 正方体(每面一种颜色)
{{5, 0, 6, 10, 13, 9, 6},
{5, 1, 7, 11, 12, 8, 7},
{5, 2, 6, 7, 8, 9, 6},
{5, 3, 10, 11, 12, 13, 10},
{5, 4, 6, 7, 11, 10, 6},
{5, 5, 8, 9, 13, 12, 8}};
//以下数据与点坐标有关
private static final double[][]
points =
// Points
for solid cube & polygonal faces cube
{{1, 0, 0}, {-1, 0, 0}, {0, 1,
0},
{0, -1, 0}, {0, 0, 1}, {0, 0, -1},
{1, 1, 1}, {-1, 1, 1}, {-1, 1, -1},{1, 1,
-1},
{1, -1, 1}, {-1, -1, 1}, {-1, -1, -1},{1, -1, -1}};
//以下数据与面有关
private static final int[][]
faces =
// Solid
cube
{{1, 0}, {1, 1}, {1, 2}, {1, 3}, {1, 4}, {1, 5}};
private int npoly;
private int npoints;
private int nfaces;
private int ncolors = 6;
public double rotPts[][];
public double scrPts[][];
public int xx[],yy[];
public Color[] colors = new
Color[ncolors];
private double
angleX,angleY,angleZ;
public Matrix3D orient, tmp, tmp2,
tmp3;
private double scale;
private int p;
ColorCube(
int w,
int h) {
npoly = polygons.length;//
若前移(作为变量成员),出错.npoly=6; ;
npoints = points.length;// npoint=14;
nfaces = faces.length; // nface=6
double len;
// red
colors[0]=
new Color(255,0,0);
// green
colors[1]=
new Color(0,255,0);
// blue
colors[2]=
new Color(0,0,255);
//
yellow
colors[3]=
new Color(255,255,0);
// cyan
colors[4]=
new Color(0, 255 ,255);
//
magenta
colors[5]=
new Color(255 , 0,255);
rotPts =
new double[npoints][3];
scrPts = new double[npoints][3];
xx = new int[5];
yy = new int[5];
orient = new Matrix3D();
tmp =
new Matrix3D();
tmp2 =
new Matrix3D();
tmp3 =
new Matrix3D();
angleX=20;angleY=20; angleZ=10;
CubeRotate(1,1,1);
double max = 0;
for (int i = 0; i < npoints; i++) {
len =
Math.sqrt(points[i][0]*points[i][0] +
points[i][1]*points[i][1] +
points[i][2]*points[i][2]);
if (len >max) {
max = len;
}
}
scale = Math.min(w/2/max/1.2, h/2/max/1.2);
//计算放大比例
CalcScrPts((double) w/2,(double) h/2,
0); //此方法将先计算转动后的坐标, 再将其转换为屏幕坐标
}
public void
CubeRotate(double rotX,double rotY,double
rotZ){
angleX += rotX;
angleY += rotY;
angleZ += rotZ;
tmp.Rotation(1,2,Math.PI*angleX/180);//绕y轴旋转50度(前两个参数决定旋转所围绕的轴以及旋转的方向,详见后面Matrix3D类的Rotation())
tmp2.Rotation(0, 2, Math.PI*angleY/180);
tmp3.Rotation(0, 1, Math.PI*angleZ/180);
orient.M =
tmp3.Times(tmp2.Times(tmp.M));
}
//以下先将点乘以矩阵,计算出转动后的坐标, 再将其转换为屏幕坐标
public void CalcScrPts(double x, double
y, double z) {
for (p = 0; p < npoints; p++)
{ //将各点转换为转动后的坐标
rotPts[p][2] = points[p][0]*orient.M[2][0] +
points[p][1]*orient.M[2][1] +
points[p][2]*orient.M[2][2];
rotPts[p][0] = points[p][0]*orient.M[0][0] +
points[p][1]*orient.M[0][1] +
points[p][2]*orient.M[0][2];
rotPts[p][1] = -points[p][0]*orient.M[1][0] -
points[p][1]*orient.M[1][1] -
points[p][2]*orient.M[1][2];
}
//以下将转动后的各点转换为屏幕坐标
for (p = nfaces; p < npoints; p++)
{ //注意P的初值.(只转换后八个点,前6个点为各面的中心点,无须转换
scrPts[p][0] = (int)(rotPts[p][0]*scale+x);
scrPts[p][1] = (int)(rotPts[p][1]*scale+y);
}
}
private boolean faceUp(int f) {
return (rotPts[f][2]<0);
}
void MyDraw(Graphics2D g2,int w,int h){
for(int f =0; f
if (faceUp(f))
//调用faceUp()方法,判断面是否可见.
DrawPoly( g2, f);
}
}
//用画多边形的方法画各个面
void DrawPoly(Graphics2D g2, int
nf){
for
(int p=0; p < polygons[nf][0]; p++){
xx[p]=(int)scrPts[polygons[nf][p+2]][0];
yy[p]=(int)scrPts[polygons[nf][p+2]][1];
}
g2.setColor(colors[nf]);
g2.fillPolygon(xx,yy,polygons[nf][0]);//也可将polygons[nf][0]改作4;(polygons[nf][0]==5)
g2.setColor(Color.black);
g2.drawPolygon(xx,yy,polygons[nf][0]);//也可将polygons[nf][0]改作4;(polygons[nf][0]==5)
}//end DrawPoly
}
class
Matrix3D {
//以下定义一个3*3的单位矩阵,修改其中元素数值对程序运行并无影响.
//因为每次进行矩阵运算时,都先对这个矩阵进行初始化.见Rotation()中的代码.
public double[][] M = { { 1, 0, 0 },
{ 0, 1, 0 },
{ 0, 0, 1 } };
private double[][] tmp = new
double[3][3];
private int row, col, k;
//以下Rotation将根据传入参数i,j的不同,分别实现绕x轴,y轴,z轴的顺时针或反时针的旋转.
//如:
i=0,j=1时,绕Z轴顺时针; i=1,j=0时,绕Y轴反时针,
//如:
i=1,j=2时,绕X轴顺时针; i=2,j=1时,绕Y轴反时针,
//如: i=0,j=2时,绕Y轴顺时针; i=2,j=0时,绕Y轴反时针,
//参见"计算机图形学与矩阵"
public void Rotation(int i, int j,
double angle) {
//以下for循环对矩阵M进行初始化
for
(row = 0; row < 3; row++) {
for (col = 0; col <3; col++) {
if (row != col) {
M[row][col] = 0.0;
} else {
M[row][col] = 1.0;
}
}
}
M[i][i] = Math.cos(angle);
M[j][j] = Math.cos(angle);
M[i][j] = Math.sin(angle);
M[j][i] = -Math.sin(angle);
}
public double[][] Times(double[][] N) {
for (row = 0; row < 3; row++) {
for (col = 0; col < 3; col++) {
tmp[row][col] = 0.0;
for (k = 0; k < 3; k++) {
tmp[row][col]
+= M[row][k] * N[k][col];
}
}
}
return tmp;
}
} // End Matrix3D