最近正在学习安卓openGL(其实各个平台相差不多),为了让人物模型上来自己动,而且不要动的那么露骨,我就在网上找现成的动作数据想将它绑定到模型对象上,搜了一下才发现原来这种数据有几种专门的文件格式来存储的,我就点了百度出来的第一个结果(果断入坑),我点的这篇文章介绍的是bvh,本来想找个现成的解析器来用,但是一个都没有,哎,,只好自己发功了(大家闪开,我要装b了)。
果断百度bvh的文件格式。
注意//并不是bvh文件的注释这样写只是为了方便说明
//---------------------bvh文件
HIERARCHY //必须的关键字
ROOT rootName
{//听说这个括号按bvh标准必须另起一行
OFFSET 0 0 0 //相对父节点的坐标(x,y,z) 因为是根节点所以一般全为0
CHANNELS 6 Xposition Yposition Zposition Xrotation Yrotation Zrotation
JOINT jointName{
OFFSET 0 1 0 //相对父节点的坐标
CHANNELS 3 Xrotation Yrotation Zrotation
......递归joint
End Site{//此节点只有offset一个字段
OFFSET 0 0 1
}
......递归回来
}
}
Frames: 50 //本文件包含了多少帧数据
Frame Time: 0.033333 //每帧间隔(是秒。。。。)
MOTION
0 1 3 3 2 4.....//数据内容
//------------------文件结束
为了通俗易懂咱们不说什么矩阵运算什么的,也没有必要。
节点描述部分
OFFSET 只指出了每个节点相对父节点的坐标,数据部分又只给出根节点的位置偏移和每个节点(End Site节点除外)相对父节点的旋转角度,因此无论节点如何旋转节点相对父节点的距离是不会发生变化的,也就不会出现胳膊被拉长什么的惊悚事件了
CHANNELS 是一个很重要的字段
每个节点有几个channel值每帧就有几条数据,如
Root hip{
OFFSET 0 0 0
CHANNELS 6 xposition zposition yposition yrotation xrotation zrotation
}
则hip节点每帧有6条数据 0:x坐标 1:z坐标 2:y坐标 3:y旋转角度 4:x旋转角度 5:z旋转角度
我故意没有按照x,y,z的顺序写是想突出一下channels定义顺序的重要性,因为数据段是按照channels的定义顺序来排列的,bvh的传统做法是使用z,x,y的顺序,有的文件却是以x,y,z的顺序出现的(就是有人这么的放纵不羁)。
注意Root节点是可以有多个的,但我下载的所有文件都只有一个Root节点,我的解析器也是以一个Root节点来解析的。Channels也可以有多个,我的解析器同样只允许一个(同样没有见过一个以上的)。
而每个节点在数据部分的位置又是以其出现顺序而定的,如有主从关系的节点结构如下:
- hip
- chest
- neck
- head
- head2//惊喜不可怕不
- lcolar
- lhand
- rcolar
- rhand
- lForeLeg
- lLeg
- rForeLeg
- rLeg
则数据段每个节点的数据按照上图从上到下的顺序从左向右出现。
bb了这么多,是时候表演真正的技术了,关门放代码。
慢着容我再bb两句
解析过程我感觉有点慢,亲测(红米note2)740k多点的文件需要4秒左右,1m以上的话就慢的难以忍受了,如有什么效率更高的办法希望各位不吝高见,慷慨陈辞,当头棒喝,发给本猿
Segment.calcBest()方法本来是想给各位客观找一个好的位子来看自己,但找到的位置并不好瞎将就吧。
真的要放代码了
Parser.java
import java.util.zip.*;
import java.util.*;
import java.io.*;
/**
* 此类用于读取文件并对文件进行一定程度的格式化访问
*/
public class Parser{
RandomAccessFile in;
public Parser(String filePath)throws IOException,FileNotFoundException{
this.in=new RandomAccessFile(filePath,"r");
}
//
public char readChar()throws IOException{
return (char)in.readByte();
}
public void close(){
try{
in.close();
}catch(Exception e){
e.printStackTrace();
}
}
public String readLine()throws IOException{
return in.readLine();
}
public void skipBytes(int i)throws IOException{
if(i<0){
in.seek(in.getFilePointer()+i);
}else{
in.skipBytes(i);
}
}
//读取完成后光标将位于endFlag之后
//返回的字符串不会包含endFlag
//convertFlag 第一个字符为转意字符 后面是所有允许转意的字符
public String readString(char endFlag, char[]convertFlag)throws IOException,DataFormatException{
StringBuilder tmp=new StringBuilder();
boolean flag=true;
char c;
while(flag){
c=readChar();
if(c==endFlag){
break;
}
if(convertFlag!=null){
if(c==convertFlag[0]){
tmp.append(c);
c=readChar();
if(Arrays.binarySearch(convertFlag,c)>0){
tmp.append(c);
}else{
throw new DataFormatException("\""+tmp+"\"之后出现非法转意符");
}
}
}else{
tmp.append(c);
}
}
return tmp.toString();
}
//如果没有读取到任何字符将抛出异常
//读取到非允许的字符后将返回,指针将指向结尾的字符后面
public String readWord(char...enable)throws IOException,DataFormatException{
StringBuilder tmp=new StringBuilder();
while(true){
char c=readChar();
if(Character.isLetter(c)||Character.isDigit(c)||c=='_'||c=='$'||
(enable!=null&&Arrays.binarySearch(enable,c)!=-1)){
tmp.append(c);
}else{
//将指针回退一个位置
skipBytes(-1);
if(tmp.length()==0){
throw new DataFormatException("没有读取到任何字符");
}
break;
}
}
return tmp.toString();
}
//默认跳过所有空字符
//可以指定disable来阻止这种行为
public void skipBlank(char...disable)throws IOException{
if(disable!=null){
while(true){
char c=readChar();
if(Arrays.binarySearch(disable,c)!=-1){
break;
}
if(!Character.isWhitespace(c)){
break;
}
}
}else{
while(true){
char c=readChar();
if(!Character.isWhitespace(c)){
break;
}
}
}
skipBytes(-1);
}
//如果没有发现任何空字符则抛出异常
//将指向空白字符之后的第一个位置
public void skipBlankRequired(char...disable)throws IOException,DataFormatException{
boolean flag=true;
if(disable!=null){//避免重复判断
while(true){
char c=readChar();
if(Arrays.binarySearch(disable,c)!=-1){
break;
}
if(!Character.isWhitespace(c)){
break;
}
flag=false;
}
}else{
while(true){
char c=readChar();
if(!Character.isWhitespace(c)){
break;
}
flag=false;
}
}
if(flag){
throw new DataFormatException("没有发现必须的空白字符");
}
skipBytes(-1);//回退一个位置
}
//如果没有出现空格将抛出异常
public void skipSpaceOnly()throws DataFormatException,IOException{
boolean flag=true;
while(readChar()==' '){
flag=false;
}
if(flag){
throw getError("必须的空格没有出现");
}
skipBytes(-1);
}
//跳过指定字符串之前的所有数据
//跳过后光标将指向endFlag之后的位置
public void skipUntill(String endFlag)throws IOException,DataFormatException{
char[]cs=endFlag.toCharArray();
boolean flag=true;
while(true){
char c=readChar();
if(c==cs[0]){
for(int i=1;iif(c!=cs[i]){
//跳回cs[0]之后的位置
skipBytes(-i+1);
flag=false;
break;
}
}
if(flag){
break;
}
flag=true;
}
}
if(!flag){
throw new DataFormatException("没有找到指定目标串:"+endFlag);
}
}
public String readNumber()throws IOException,DataFormatException,NumberFormatException{
StringBuilder tmp=new StringBuilder();
while(true){
char c=readChar();
if(Character.isDigit(c)||c=='-'||c=='.'){
tmp.append(c);
}else{
//将指针回退一个位置
skipBytes(-1);
if(tmp.length()==0){
throw new DataFormatException("无法从文件中读取数字,没有读取到任何字符");
}
break;
}
}
return tmp.toString();
}
public boolean requiredKeyWord(String key)throws IOException,DataFormatException{
for(int i=0;iif(key.charAt(i)!=readChar()){
throw getError("指定的关键字没有找到:"+key);
}
}
return true;
}
//如果下一个单词是给出的单词将返回true并将指针移动到key之后的字符上
//否则指针指回调用此函数之前的位置并返回false
public boolean isNextString(String key)throws IOException{
for(int i=0;iif(key.charAt(i)!=readChar()){
skipBytes(-i-1);
return false;
}
}
return true;
}
private DataFormatException getError(String msg)throws IOException{
return new DataFormatException(msg+",Position:"+in.getFilePointer());
}
public char testChar()throws IOException{
char c=(char)in.readByte();
skipBytes(-1);
return c;
}
}
ActionReader.java
import java.util.*;
import java.net.*;
import java.io.*;
import java.util.zip.*;
import java.nio.channels.*;
public class ActionReader{
/*使用示例
public static void main(String[] args){
try{
Parser p=new Parser("/storage/emulated/0/#c/action/backflip_583ad.bvh");
ActionReader m=new ActionReader();
long b=System.currentTimeMillis();
for(int i=0;i<1;i++){
p.in.seek(0);
Segment s=new Segment();
m.read(p,s);
}
System.out.println(System.currentTimeMillis()-b);
}catch(IOException e){
e.printStackTrace();
}catch(DataFormatException e){
e.printStackTrace();
}
}
*/
//返回第一个值为总帧数,第二个值为帧间隔
public float[] read(Parser p,Segment s)
throws IOException,DataFormatException{
p.requiredKeyWord("HIERARCHY");
p.skipBlank();
//暂时只支持一个Root节点的情况
p.requiredKeyWord("ROOT");
readSegment(p,s,0);
p.skipBlank();
p.requiredKeyWord("MOTION");
p.skipBlankRequired();
p.requiredKeyWord("Frames:");
p.skipBlankRequired();
int frames=Integer.parseInt(p.readNumber());
p.skipBlankRequired();
p.requiredKeyWord("Frame Time:");
p.skipSpaceOnly();
String time=p.readNumber();
//p.skipBlankRequired();//由数据读取方法跳过
for(int i=0;i
readLineData(p,s);
}
//Loger.l("root data length:"+s.data.size());
float[]result={frames,Float.parseFloat(time)};
return result;
}
//此函数预期的指针位置为ROOT或JOINT关键字之后
public boolean readSegment(Parser p,Segment s,int iii)throws IOException,DataFormatException{
p.skipBlankRequired();
String str=p.readWord();
s.name=str;
p.skipBlank();
p.requiredKeyWord("{");
p.skipBlank();
p.requiredKeyWord("OFFSET");
p.skipBlankRequired();
float x=Float.parseFloat(p.readNumber());
p.skipBlankRequired();
float y=Float.parseFloat(p.readNumber());
p.skipBlankRequired();
float z=Float.parseFloat(p.readNumber());
p.skipBlankRequired();
s.line.put(new float[]{x,y,z});
s.line.position(0);
//此数组的作用为告知data读取方法当前位置读取的数据应该放到数据数组的第几位
byte[]chans=s.chanels=new byte[6];
//只支持一个通道的情况
if(p.isNextString("CHANNELS")){
p.skipSpaceOnly();
//要求通道数必须为3或6
int channelNum=s.dataLen=(byte)Integer.parseInt(p.readNumber());
for(int i=0;i
p.skipSpaceOnly();
String channel=p.readWord();
if(channel.equalsIgnoreCase("xrotation")){
chans[i]=0;
}else if(channel.equalsIgnoreCase("yrotation")){
chans[i]=1;
}else if(channel.equalsIgnoreCase("zrotation")){
chans[i]=2;
}else if(channel.equalsIgnoreCase("xposition")){
chans[i]=3;
}else if(channel.equalsIgnoreCase("yposition")){
chans[i]=4;
}else if(channel.equalsIgnoreCase("zposition")){
chans[i]=5;
}else{
throw new DataFormatException("无法识别的通道名称");
}
}
}
p.skipBlank();
if(p.isNextString("End Site")){
//遇到结束节点直接处理,不再递归
Segment end=new Segment();
s.sub.add(end);
end.base=s;
p.skipBlank();
p.requiredKeyWord("{");
p.skipBlank();
p.requiredKeyWord("OFFSET");
p.skipBlankRequired();
float ex=Float.parseFloat(p.readNumber());
p.skipBlankRequired();
float ey=Float.parseFloat(p.readNumber());
p.skipBlankRequired();
float ez=Float.parseFloat(p.readNumber());
float[]d=new float[]{ex,ey,ez};
end.line.put(d);
end.line.position(0);
end.data.add(d);
p.skipBlank();
p.requiredKeyWord("}");
p.skipBlank();
}else{
p.requiredKeyWord("JOINT");
do{
Segment sub=new Segment();
sub.base=s;
s.sub.add(sub);
readSegment(p,sub,iii+1);
}while(p.isNextString("JOINT"));
}
//Log.log("this is:"+p.readWord('|'));
p.requiredKeyWord("}");
p.skipBlank();
return true;
}
public void readLineData(Parser p,Segment s)throws DataFormatException,IOException{
if(s.chanels==null){
return;
}
float[]data=new float[s.dataLen];
for(int i=0;i
p.skipBlankRequired();
float f=Float.parseFloat(p.readNumber());
data[s.chanels[i]]=f;
}
s.data.add(data);
for(Segment sub:s.sub){
readLineData(p,sub);
}
}
}
Segment.java
import java.util.*;
import javax.microedition.khronos.opengles.*;
import java.nio.*;
public class Segment{
FloatBuffer line;
public String name;
public Segment base;
public List sub=new ArrayList();
public byte[]chanels;
//当前节点Channel的数量
public byte dataLen;
//放入时即按照xrotate,yrotate,zrotate[,xposition,yposition,zposition]的顺序放入
public List<float[]>data=new ArrayList<float[]>();
public void draw(GL10 gl){
}
public Segment(){
line=ByteBuffer.allocateDirect(6*4).order(ByteOrder.nativeOrder()).asFloatBuffer();
//因为bvh的子节点总是以父节点为原点定位的所以线永远从(0,0,0)画到自己的位置
//Loger.l("data"+x+","+y+","+z);
line.position(0);
}
public void draw(GL10 gl,int index,float x,float y,float z,float...rotate){
float[]p=null;
if(dataLen!=0){
p=data.get(index);
}
//矩阵压栈
gl.glPushMatrix();
if(base==null){//根节点
//先平移到目标位置
gl.glTranslatef(x,y,z);
//旋转基础角度
if(rotate!=null&&rotate.length>1){
gl.glRotatef(rotate[0],1,0,0);
gl.glRotatef(rotate[1],0,1,0);
}
//再偏移到相对位置
gl.glTranslatef(p[3],p[4],p[5]);
}else if(base!=null){
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glVertexPointer(3,GL10.GL_FLOAT,0,line);
gl.glDrawArrays(GL10.GL_LINES,0,2);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
//绘制完成后将自身坐标置为原点
gl.glTranslatef(line.get(0),line.get(1),line.get(2));
}
//将矩阵旋转
if(p!=null){
gl.glRotatef(p[0],1,0,0);
gl.glRotatef(p[1],0,1,0);
gl.glRotatef(p[2],0,0,1);
}
//迭代子节点
for(Segment s:sub){
s.draw(gl,index,x,y,z);
}
//矩阵出栈
gl.glPopMatrix();
}
//返回y轴和z轴的最佳值
public float[]calcBestPoint(float near,float top,float bottom){
float[]tmp=maxAndMinY();
Loger.l("max min:"+Arrays.toString(tmp));
float max=tmp[0];
float min=tmp[1];
tmp[0]=top/(top-bottom)*(max-min);
tmp[1]=(max-min)/(top-bottom)*near;
//Loger.l("y z:"+Arrays.toString(tmp));
return tmp;
}
public float[] maxAndMinY(){
float[]r=new float[]{this.line.get(1),this.line.get(1)};
if(sub.size()==0){
return r;
}
for(Segment sub:sub){
float[]t=sub.maxAndMinY();
if(t[0]>r[0]){
r[0]=t[0];
}
if(t[1]1]){
r[1]=t[1];
}
}
return r;
}
}
GLV.java
import android.opengl.*;
import android.content.*;
import android.util.*;
import javax.microedition.khronos.opengles.*;
import javax.microedition.khronos.egl.EGLConfig;
import java.nio.*;
import android.view.*;
import android.widget.*;
import java.util.*;
import java.io.*;
import android.os.*;
public class GLV extends GLSurfaceView implements GLSurfaceView.Renderer{
public GLV(Context ctx){
super(ctx);
setRenderer(this);
}
public GLV(Context ctx,AttributeSet as){
super(ctx,as);
setRenderer(this);
place=Toast.makeText(getContext(),"",Toast.LENGTH_SHORT);
}
float rotatex;
float rotatey;
Toast place;
private void alert(Object o){
place.setText(o+"");
place.show();
}
MotionEvent start;
public boolean onTouchEvent(MotionEvent e){
if(e.getAction()==MotionEvent.ACTION_DOWN){
start=MotionEvent.obtain(e);
}else{
rotatey=start.getX()-e.getX();
rotatex=start.getY()-e.getY();
}
return true;
}
Handler progressCallBack;
int index=0;
float z=50;
float y=0;
private Segment seg;
public void setSegment(Segment s){
float[]best=s.calcBestPoint(1,1,-1);
y=best[0];
z=best[1];
this.seg=s;
}
public void onDrawFrame(GL10 gl){
gl.glClear(GL10.GL_DEPTH_BUFFER_BIT|GL10.GL_COLOR_BUFFER_BIT);
//gl.glClearColor(255,255,255,0);
float[]pos={0,0,10,1};
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_POSITION,pos,0);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glPushMatrix();
gl.glLineWidth(3);
gl.glColor4f(0,0,0,0);
GLU.gluLookAt(gl,0,y,z,0,0,0,0,1,0);
//*
drawLine(gl,-9999,0,0,9999,0,0);
drawLine(gl,0,-9999,0,0,9999,0);
drawLine(gl,0,0,-9999,0,0,9999);
//*/
gl.glLineWidth(10);
gl.glColor4f(1,0,0,0);
if(seg!=null){
if(!(index<(seg.data.size()/2)-1)){
index=0;
}
seg.draw(gl,index,0,0,0,rotatex,rotatey);
index++;
}
gl.glPopMatrix();
}
public void onSurfaceCreated(GL10 gl,EGLConfig p2){
//gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,GL10.GL_FASTEST);
}
public void onSurfaceChanged(GL10 gl,int width,int height){
float rate=getHeight()*1f/getWidth();
gl.glDisable(GL10.GL_CULL_FACE);
gl.glViewport(0,0,width,height);
gl.glClearColor(1,1,1,0);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
gl.glFrustumf(-1,1,-rate,rate,1f,9999);
gl.glEnable(GL10.GL_DEPTH_TEST);
/*
gl.glEnable(GL10.GL_LIGHTING);
gl.glEnable(GL10.GL_LIGHT0);
float[]ambient={0f,0f,0f,1};
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_AMBIENT,ambient,0);
float[]col={0,0.5f,0,1};
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_DIFFUSE,ambient,0);
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_SPECULAR,col,0);
float[]meta={1f,1f,1f,1};
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK,GL10.GL_AMBIENT,ambient,0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK,GL10.GL_DIFFUSE,ambient,0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK,GL10.GL_SPECULAR,meta,0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK,GL10.GL_SHININESS,new float[]{0.8f},0);
*/
//gl.glMaterialfv(GL10.GL_FRONT_AND_BACK,GL10.GL_EMISSION,col,0);
gl.glShadeModel(GL10.GL_FLAT);
//gl.glOrthof(-1,1,-rate,rate,0.2f,9999);
}
public void drawPoint(GL10 gl,float x,float y,float z){
gl.glPushMatrix();
ByteBuffer bb=ByteBuffer.allocateDirect(12);
bb.order(ByteOrder.nativeOrder());
FloatBuffer fb=bb.asFloatBuffer();
fb.put(new float[]{x,y,z});
fb.position(0);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glVertexPointer(3,GL10.GL_FLOAT,0,bb);
gl.glDrawArrays(GL10.GL_POINTS,0,1);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glPopMatrix();
}
public static void drawLine(GL10 gl,float x,float y,float z,float tx,float ty,float tz){
gl.glPushMatrix();
ByteBuffer bb=ByteBuffer.allocateDirect(24);
bb.order(ByteOrder.nativeOrder());
FloatBuffer fb=bb.asFloatBuffer();
fb.put(new float[]{x,y,z,tx,ty,tz});
fb.position(0);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glVertexPointer(3,GL10.GL_FLOAT,0,bb);
gl.glDrawArrays(GL10.GL_LINES,0,2);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glPopMatrix();
}
}
MainActivity.java
import android.app.*;
import android.os.*;
import android.widget.*;
import android.view.View.*;
import android.view.*;
import javax.microedition.khronos.opengles.*;
import android.content.*;
import android.net.*;
import android.provider.*;
public class MainActivity extends Activity{
private static final String PATH_KEY="PATH";
SharedPreferences sp;
GLV gl;
Toast pl;
public void alert(Object o){
pl.setText(o+"");
pl.show();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data){
if(resultCode!=0&&data!=null&&data.getData()!=null){
Uri u=data.getData();
String pth=u.getPath();
if(u.getScheme().equals("file")&&pth.toLowerCase().endsWith(".bvh")){
sp.edit().putString(PATH_KEY,pth).commit();
load(pth);
}else{
alert("不支持的文件格式!!必须是bvh动作文件");
}
}else{
alert("您没有选择文件!");
}
}
@Override
protected void onCreate(Bundle savedInstanceState){
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setProgressBarVisibility(true);
setProgress(100);
pl=Toast.makeText(this,"",Toast.LENGTH_SHORT);
gl=(GLV)findViewById(R.id.main_gl);
sp=getPreferences(MODE_PRIVATE);
String pth=sp.getString(PATH_KEY,null);
if(pth!=null){
load(pth);
}
}
public void click(View v){
switch(v.getId()){
case R.id.mainAdd:{
gl.z++;
break;
}
case R.id.mainSub:{
gl.z--;
break;
}
case R.id.main_load:{
if(busy){
alert("正在解析另一个文件");
}else{
Intent i=new Intent(Intent.ACTION_GET_CONTENT);
i.setType("*/*");
startActivityForResult(i,0);
}
break;
}
}
}
boolean busy;
public boolean load(final String path){
if(busy){
return false;
}
busy=true;
final View v=findViewById(R.id.main_progressContainer);
final ProgressBar pb=(ProgressBar)v.findViewById(R.id.main_progress);
final TextView disp=(TextView)v.findViewById(R.id.main_disp);
v.setVisibility(View.VISIBLE);
AsyncTask at=new AsyncTask
Loger.java 。。。抱歉我只是一个龙套
import android.util.*;
public class Loger{
public static void l(Object o){
Log.d("gl test",o+"");
}
}
送佛送到西,把我丑的一逼的布局文件也拿出来了,小伙伴们给每个文件加个包名在manifest.xml中配置好就能瞎几把看了
main.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:gravity="center"
android:layout_marginBottom="20dp"
android:visibility="invisible"
android:id="@+id/main_progressContainer"
android:orientation="vertical">
<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/main_disp"
android:text="Text"/>
<ProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_height="wrap_content"
android:id="@+id/main_progress"
android:layout_width="wrap_content"/>
LinearLayout>
<com.dance.glTest.GLV
android:id="@+id/main_gl"
android:text="@string/hello_world"
android:layout_width="200dp"
android:layout_height="200dp"/>
<Button
style="?android:attr/buttonStyleSmall"
android:id="@+id/mainSub"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:onClick="click"
android:text="nearer"/>
<Button
style="?android:attr/buttonStyleSmall"
android:id="@+id/mainAdd"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:onClick="click"
android:text="farer"/>
<Button
style="?android:attr/buttonStyleSmall"
android:id="@+id/main_load"
android:onClick="click"
android:layout_marginTop="20dp"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="读取bvh动作文件"/>
LinearLayout>
更新于一天后
由于一直对速度耿耿于怀,所以又对耗时大户Parser进行了一点优化,原来io操作使用的是RandomAccessFile,本来以为文件指针需要要很多反向跳转但实际上跳转距离都很短,所以用BufferedInputStream是一个很好的选择,关键字:mark(int len) reset() 供不明真相的小伙伴享用。
import java.util.zip.*;
import java.util.*;
import java.io.*;
public class Parser{
private BufferedInputStream in;
private byte[]buf=new byte[1];
public long length;
public long pos;
public Parser(String filePath)throws IOException,FileNotFoundException{
File f=new File(filePath);
length=f.length();
this.in=new BufferedInputStream(new FileInputStream(f));
}
public char readChar()throws IOException{
in.read(buf);
return (char)buf[0];
}
//注意此方法按每个字两字节读取
public String readString(int charNum)throws IOException,DataFormatException{
byte[]buf=new byte[charNum*2];
int len=in.read(buf);
if(len!=buf.length){
//没有读取到指定数量的字符
throw new DataFormatException();
}
pos+=charNum;
return new String(buf);
}
public void close(){
try{
in.close();
}catch(IOException e){
}
}
//读取完成后光标将位于endFlag之后
//返回的字符串不会包含endFlag
//convertFlag 第一个字符为转意字符 后面是所有允许转意的字符
public String readString(char endFlag, char[]convertFlag)throws IOException,DataFormatException{
StringBuilder tmp=new StringBuilder();
boolean flag=true;
char c;
while(flag){
c=readChar();
if(c==endFlag){
break;
}
if(convertFlag!=null){
if(c==convertFlag[0]){
tmp.append(c);
c=readChar();
if(Arrays.binarySearch(convertFlag,c)>0){
tmp.append(c);
}else{
throw new DataFormatException("\""+tmp+"\"之后出现非法转意符");
}
}
}else{
tmp.append(c);
}
}
String s=tmp.toString();
pos+=s.length();
return s;
}
//如果没有读取到任何字符将抛出异常
//读取到非允许的字符后将返回,指针将指向结尾的字符后面
public String readWord(char...enable)throws IOException,DataFormatException{
StringBuilder tmp=new StringBuilder();
while(true){
in.mark(1);
char c=readChar();
if(Character.isLetter(c)||Character.isDigit(c)||c=='_'||c=='$'||
(enable!=null&&Arrays.binarySearch(enable,c)!=-1)){
tmp.append(c);
}else{
//将指针回退一个位置
in.reset();
if(tmp.length()==0){
throw new DataFormatException("没有读取到任何字符");
}
break;
}
}
pos+=tmp.length();
return tmp.toString();
}
//默认跳过所有空字符
//可以指定disable来阻止这种行为
public void skipBlank(char...disable)throws IOException{
if(disable!=null){
while(true){
in.mark(1);
pos++;
char c=readChar();
if(Arrays.binarySearch(disable,c)!=-1){
break;
}
if(!Character.isWhitespace(c)){
break;
}
}
}else{
while(true){
in.mark(1);
pos++;
char c=readChar();
if(!Character.isWhitespace(c)){
break;
}
}
}
in.reset();
pos--;
}
//如果没有发现任何空字符则抛出异常
//将指向空白字符之后的第一个位置
public void skipBlankRequired(char...disable)throws IOException,DataFormatException{
boolean flag=true;
if(disable!=null){//避免重复判断
while(true){
in.mark(1);
pos++;
char c=readChar();
if(Arrays.binarySearch(disable,c)!=-1){
break;
}
if(!Character.isWhitespace(c)){
break;
}
flag=false;
}
}else{
while(true){
in.mark(1);
pos++;
char c=readChar();
if(!Character.isWhitespace(c)){
break;
}
flag=false;
}
}
if(flag){
throw new DataFormatException("没有发现必须的空白字符");
}
in.reset();
pos--;
}
//如果没有出现空格将抛出异常
public void skipSpaceOnly()throws DataFormatException,IOException{
boolean flag=true;
in.mark(1);
pos++;
while(readChar()==' '){
flag=false;
in.mark(1);
pos++;
}
if(flag){
throw getError("必须的空格没有出现");
}
in.reset();
pos--;
}
//跳过指定字符串之前的所有数据
//跳过后光标将指向endFlag之后的位置
public void skipUntill(String endFlag)throws IOException,DataFormatException{
char[]cs=endFlag.toCharArray();
boolean flag=true;
while(flag){
char c=readChar();
pos++;
if(c==cs[0]){
in.mark(endFlag.length());
for(int i=1;iif(c!=cs[i]){
//跳回cs[0]之后的位置
in.reset();
flag=false;
break;
}
}
if(flag){
break;
}
flag=true;
}
}
if(!flag){
throw new DataFormatException("没有找到指定目标串:"+endFlag);
}else{
pos+=endFlag.length();
}
}
public String readNumber()throws IOException,DataFormatException,NumberFormatException{
StringBuilder tmp=new StringBuilder();
while(true){
in.mark(1);
char c=readChar();
if(Character.isDigit(c)||c=='-'||c=='.'){
tmp.append(c);
}else{
//将指针回退一个位置
in.reset();
if(tmp.length()==0){
throw new DataFormatException("无法从文件中读取数字,没有读取到任何字符");
}
break;
}
}
pos+=tmp.length();
return tmp.toString();
}
public boolean requiredKeyWord(String key)throws IOException,DataFormatException{
for(int i=0;iif(key.charAt(i)!=readChar()){
throw getError("指定的关键字没有找到:"+key);
}
}
pos+=key.length();
return true;
}
//如果下一个单词是给出的单词将返回true并将指针移动到key之后的字符上
//否则指针指回调用此函数之前的位置并返回false
public boolean isNextString(String key)throws IOException{
in.mark(key.length());
for(int i=0;iif(key.charAt(i)!=readChar()){
in.reset();
return false;
}
}
pos+=key.length();
return true;
}
private DataFormatException getError(String msg)throws IOException{
return new DataFormatException(msg+",Position:");
}
public char testChar()throws IOException{
in.mark(1);
char c=readChar();
in.reset();
return c;
}
}