这是一个用空间几何计算实现点的坐标变换、模拟视觉的例子。将2个java文件放在同一个包里。
主文件:
package com.baobao.sayhello;
import android.app.*;
import android.content.*;
import android.graphics.*;
import android.os.*;
import android.view.*;
import android.widget.*;
import java.util.*;
public class MainActivity extends Activity
{
mview view;
int winx,winy,bmx,bmy,ct=8;
Bitmap bm;
Handler mHandler;
ArrayList<tri3d.Triangle> t=new ArrayList<tri3d.Triangle>();
tri3d.Point p=new tri3d.Point(0,-10,0);
class mview extends View{//自定义一个叫mview的新类型,继承View
public mview(Context con){//构造(初始化)函数
super(con);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
//窗口大小改变时触发
super.onSizeChanged(w, h, oldw, oldh);
winx=w;winy=h;bmx=winx/16;bmy=winy/16;
Random r=new Random();
int i;
for(i=0;i<ct;i++)t.add(new tri3d.Triangle(
new tri3d.Point(r.nextDouble()*4-2,r.nextDouble()*4-2,r.nextDouble()*4-2),
new tri3d.Point(r.nextDouble()*4-2,r.nextDouble()*4-2,r.nextDouble()*4-2),
new tri3d.Point(r.nextDouble()*4-2,r.nextDouble()*4-2,r.nextDouble()*4-2)
));
bm=Bitmap.createBitmap(bmx,bmy,Bitmap.Config.RGB_565);//建立指定宽高的图片bm
mHandler = new Handler() {
public void handleMessage(Message msg) {
super.handleMessage(msg);
int i;
for(i=0;i<t.size();i++){
t.set(i,t.get(i).rotate(new tri3d.Line(new tri3d.Point(0,0,0),new tri3d.Point(0,0,1)),0.2));
}
view.invalidate();//重绘窗口
removeMessages(1);
sendEmptyMessageDelayed(1, 100);//这里想几秒刷新一次就写几秒
}
};
mHandler.sendEmptyMessage(1);
}
@Override
protected void onDraw(Canvas canvas)
{
Paint paint=new Paint();
int ix,iy,i,k;
double min;
ArrayList<tri3d.Base> b=new ArrayList<tri3d.Base>();
for(i=0;i<t.size();i++)b.add(t.get(i).base());
for(iy=0;iy<bmy;iy++)
for(ix=0;ix<bmx;ix++){
min=1000;k=0;
ArrayList<ArrayList> ll=new ArrayList<ArrayList>();
tri3d.Point p1=new tri3d.Point((ix-bmx/2.0)*4/bmx,0,(bmy/2.0-iy)*6/bmy);
for(i=0;i<t.size();i++){
ArrayList l=new tri3d.Line(p,p.vectorTo(p1)).shootAt(b.get(i));
if((double)l.get(2)<min&&((tri3d.Point)l.get(1)).x>0&&((tri3d.Point)l.get(1)).y>0&&((tri3d.Point)l.get(1)).x+((tri3d.Point)l.get(1)).y<1){
min=l.get(2);
k=i;
}
ll.add(l);
}
tri3d.Point p2=(tri3d.Point)ll.get(k).get(1);
bm.setPixel(ix,iy,((boolean)ll.get(k).get(0)&&p2.x>0&&p2.y>0&&p2.x+p2.y<1)?Color.rgb((int)(p2.y*10)%2==0?255:127,(int)(255-((double)ll.get(k).get(2)-7)*256/6),0):Color.BLACK);//设置图片每个像素
}
canvas.drawBitmap(bm,null,new RectF(0,0,winx,winy),paint);//把图片贴到屏幕
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
//触控屏幕时触发
if(event.getAction()==MotionEvent.ACTION_DOWN){
//如果事件类型为按下
}
return true;//返回真,表示已处理消息
}
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
view=new mview(this);//新建一个叫view的mview
setContentView(view);//把view设为整个界面
}
}
3d计算库:
package com.baobao.sayhello;
import java.util.*;
public class tri3d//空间几何计算库
{
public static class Point{//点
public double x,y,z;
Point (double x,double y,double z){
this.x=x;this.y=y;this.z=z;
}
public double length(){//和原点距离(长度)
return Math.sqrt(x*x+y*y+z*z);
}
public Point unit(){//单位化
return new Point(x/this.length(),y/this.length(),z/this.length());
}
public Point cross(Point p){//叉乘
return new Point(y*p.z-z*p.y,z*p.x-x*p.z,x*p.y-y*p.x);
}
public Point translate(Point p){//平移(矢量和)
return new Point(x+p.x,y+p.y,z+p.z);
}
public Point vectorTo(Point p){//由p1指向p2的向量
return new Point(p.x-x,p.y-y,p.z-z);
}
public Point scale(double k){//以原点为中心缩放k倍
return new Point(x*k,y*k,z*k);
}
public Point scale(Point p,double k){//以p为中心缩放k倍
return p.translate(p.vectorTo(this).scale(k));
}
public Point rotate(Line l,double rad){//以l为轴右手向旋转角度rad
Point n=l.p.unit();
double sin=Math.sin(rad),cos=Math.cos(rad),cos1=1-cos;
double ax=x-l.p0.x,ay=y-l.p0.y,az=z-l.p0.z;
double cx=n.x*sin,cy=n.y*sin,cz=n.z*sin;
double bxy=n.x*n.y*cos1,byz=n.y*n.z*cos1,bzx=n.z*n.x*cos1;
return new Point(
ax*(n.x*n.x*cos1+cos)+ay*(bxy-cz)+az*(bzx+cy)+l.p0.x,
ay*(n.y*n.y*cos1+cos)+az*(byz-cx)+ax*(bxy+cz)+l.p0.y,
az*(n.z*n.z*cos1+cos)+ax*(bzx-cy)+ay*(byz+cx)+l.p0.z
);
}
public Point fromBase(Base b){//从b坐标系的坐标转为默认坐标系的坐标
return b.p0.
translate(b.px.scale(this.x)).
translate(b.py.scale(this.y)).
translate(b.pz.scale(this.z));
}
public Point toBase(Base b){//从默认坐标系的坐标转为b坐标系的坐标
double p1x=b.px.x,p2x=b.py.x,p3x=b.pz.x;
double p1y=b.px.y,p2y=b.py.y,p3y=b.pz.y;
double p1z=b.px.z,p2z=b.py.z,p3z=b.pz.z;
double p0x=x-b.p0.x,p0y=y-b.p0.y,p0z=z-b.p0.z;
double d=p1x*(p2y*p3z-p3y*p2z)+p1y*(p2z*p3x-p2x*p3z)+p1z*(p2x*p3y-p2y*p3x);
double dx=p0x*(p2y*p3z-p3y*p2z)+p0y*(p2z*p3x-p2x*p3z)+p0z*(p2x*p3y-p2y*p3x);
double dy=p0x*(p1z*p3y-p3z*p1y)+p0y*(p1x*p3z-p3x*p1z)+p0z*(p1y*p3x-p3y*p1x);
double dz=p0x*(p1y*p2z-p2y*p1z)+p0y*(p1z*p2x-p2z*p1x)+p0z*(p1x*p2y-p2x*p1y);
return new Point(dx/d,dy/d,dz/d);
}
}
public static class Line{//点向式直线
public Point p0,p;
Line (Point p0,Point p){
this.p0=p0;this.p=p;
}
public Line toBase(Base b){//转到坐标系b的坐标
Point p1=p0.toBase(b);
return new Line(p1,p1.vectorTo(p0.translate(p).toBase(b)));
}
public ArrayList shootAt(Base b){//获得直线与t坐标系xoy面的交点(t坐标系),in为是否朝向平面
Line l=this.toBase(b);
ArrayList list=new ArrayList();
list.add(l.p.z<0&&l.p0.z>0||l.p.z>0&&l.p0.z<0?true:false);
list.add(l.p0.translate(l.p).scale(l.p0,-l.p0.z/l.p.z));
list.add(p0.vectorTo(((Point)list.get(1)).fromBase(b)).length());
return list;
}
}
public static class Base{//坐标系
public Point p0,px,py,pz;
Base(Point p0,Point px,Point py,Point pz){
this.p0=p0;this.px=px;this.py=py;this.pz=pz;
}
}
public static class Triangle{//三角形
public Point px,po,py;
Triangle(Point px,Point po,Point py){
this.px=px;this.po=po;this.py=py;
}
public Base base(){
Point vx=po.vectorTo(px),vy=po.vectorTo(py);
return new Base(po,vx,vy,vx.cross(vy));
}
public Triangle translate(Point p){
return new Triangle(px.translate(p),po.translate(p),py.translate(p));
}
public Triangle scale(Point p,double k){
return new Triangle(px.scale(p,k),po.scale(p,k),py.scale(p,k));
}
public Triangle rotate(Line l,double rad){
return new Triangle(px.rotate(l,rad),po.rotate(l,rad),py.rotate(l,rad));
}
}
}
设置文件:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.baobao.sayhello" >
<application
android:allowBackup="true"
android:icon="@drawable/baobao"
android:label="天线宝宝"
android:theme="@style/AppTheme"
android:resizeableActivity = "true"
android:hardwareAccelerated="false">
<activity
android:name=".MainActivity"
android:label="天线宝宝说你好"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
注意要把代码里的包名改成你的包名。
主文件里的bmx和bmy是绘制图片的宽高。图片越大质量越高但运行起来越卡。图片越小运行越流畅但接近马赛克。
这两个参数都是用屏幕宽高除以一个数计算的。可以改变除数值。除的越大图片宽和高就越小。
每个三角形三个顶点位置是随机的,每次运行一般不一样。
主要原理:
首先每一帧要计算出每个三角形所有点的坐标。
把眼睛当成一个点,从这个点向可视的方向引射线,就会和某些三角形内部相交。
由于遮挡关系,只能看到距离眼睛点最近的交点,根据这个交点在对应三角形平面上的位置来决定这个点是什么颜色。
对过眼睛点和每个屏幕像素点的射线,都枚举计算颜色,就能拼出一副3d图片。