关于java解析bvh动作文件

最近正在学习安卓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();
            //要求通道数必须为36
            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(){
            public Object doInBackground(Object...obj){
                try{
                    Loger.l("load action Begin parse");
                    ActionReader ar=new ActionReader();
                    final Parser p=new Parser(path);
                    publishProgress(0,p.in.length());
                    Thread noti=new Thread(){
                        boolean exit=false;
                        public void run(){
                            Loger.l("progress Thread start");
                            while(!exit){
                                try{
                                    synchronized(this){
                                        this.wait(100);
                                        publishProgress(1,p.in.length(),p.in.getFilePointer());
                                    }
                                }catch(Exception e){
                                    exit=true;
                                    break;
                                }
                            }
                            Loger.l("progress Thread exit");
                        }
                    };
                    noti.start();
                    Segment s=new Segment();
                    ar.read(p,s);
                    gl.setSegment(s);
                    busy=false;
                    Loger.l("load action complete!");
                    noti.interrupt();
                    publishProgress(2);
                }catch(Exception e){
                    Loger.l("load action error:"+e);
                    e.printStackTrace();
                }
                return null;
            }
            public void onProgressUpdate(Object...prog){
                int what=prog[0];
                if(what==0){
                    long l=prog[1];
                    pb.setMax((int)l);
                }else if(what==1){
                    long total=prog[1];
                    long curr=prog[2];
                    float rate=curr*10000/total/100f;//保留两位小数
                    disp.setText("己加载"+rate+"%");
                    pb.setProgress((int)curr);
                }else if(what==2){
                    v.setVisibility(View.INVISIBLE);
                }
            }
        };
        at.execute();
        return true;
    }
}

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;
    }
}

你可能感兴趣的:(关于java解析bvh动作文件)