BVH檔案 Java解析器
阿新 • • 發佈:2019-02-08
BVH檔案解析器
簡介
對於BVH檔案的格式,可以參考:BVH檔案格式解析
解析器
使用java編寫,具體使用方法:
匯入google的gson包,用來生成json:
- 如果是eclipse,下載jar匯入到專案中:gson jar下載
- 如果是使用IDEA,則通過File - Project Structure - Modules - Dependencies - +(加號) - Libraries - From Mave, 搜尋gson,新增com.google.gson.Gson;
建立
BVHParser
的類物件:
BVHParser parser = new BVHParser();
- 傳入bvh檔案的絕對路徑,呼叫
parse(String)
方法解析得到json格式的字串:
String json = parser.parse("your absoulte path of the .bvh file's ");
- 如果要寫入檔案:
File out = new File("out.json"); // print to a json file
try {
PrintStream ps = new PrintStream(new FileOutputStream(out));
ps.println(json);
} catch (IOException e) {
e.printStackTrace();
} finally {
parser.release();
}
- 最後釋放I/O
parser.release();
原始碼
對於邏輯,部分已在程式碼中註釋
import com.google.gson.Gson;
import java.io.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
/**
* Parsing the standard .bvh file, some infor cannot be tranfered to json if not a standard .bvh file
* Only get segment's data without motion's data.
* <p>
* The parser need external library: com.google.code.gson:gson
* <p>
* Created by PentonBin on 16-3-23.
*/
public class BVHParser {
private static final String TAG = "BVHParser";
private static final String KEYWORD_HIERARCHY = "HIERARCHY";
private static final String KEYWORD_ROOT = "ROOT";
private static final String KEYWORD_OFFSET = "OFFSET";
private static final String KEYWORD_CHANNELS = "CHANNELS";
private static final String KEYWORD_JOINT = "JOINT";
private static final String KEYWORD_END_SITE = "End Site";
private static final String KEYWORD_MOTION = "MOTION";
private static final String KEYWORD_FRAME = "Frames";
private static final String KEYWORD_FRAME_TIME = "Frame Time";
private static final int NUM_OFFSET = 3;
private static final int NUM_ROOT_CHANNELS = 6;
private static final int NUM_JOINT_CHANNELS = 3;
private BufferedReader mParseReader;
private boolean isReaderMarkConsume = true;
enum SegmentType {
Root, Joint, EndSite
}
enum ChannelType {
Xposition, Yposition, Zposition, Zrotation, Xrotation, Yrotation
}
static class Segment {
private String name;
private SegmentType type;
private ArrayList<Double> offsetValueList;
private int channelsNum;
private ArrayList<ChannelType> channelTypeList;
private ArrayList<Segment> subSegments;
public Segment() {
}
public void putOffset(double offset) {
if (offsetValueList == null) {
offsetValueList = new ArrayList<Double>();
}
offsetValueList.add(offset);
}
public void setChannelsNum(int channelsNum) {
this.channelsNum = channelsNum;
}
public void putChannelType(ChannelType type) {
if (channelTypeList == null) {
channelTypeList = new ArrayList<ChannelType>();
}
channelTypeList.add(type);
}
public void setName(String name) {
this.name = name;
}
public void setSegmentType(SegmentType type) {
this.type = type;
}
public SegmentType getType() {
return type;
}
public void putSubSegment(Segment subSegment) {
if (subSegments == null) {
subSegments = new ArrayList<Segment>();
}
subSegments.add(subSegment);
}
}
static class Motion {
private int framesNum;
private double framesTime;
private ArrayList<Double> motionsData;
public Motion() {
}
}
/**
* Parse the .bvh file provided by {@param filePath}
*
* @param filePath the path of the file
* @return String of the json format, null if the file does not exist or is not file
*/
public String parse(String filePath) {
File file = new File(filePath);
if (file != null && file.exists() && file.isFile()) {
Gson gson = new Gson();
Segment rootSegment = getSegmentFromFile(file);
return gson.toJson(rootSegment);
}
return null;
}
/**
* Must call this function to release the I/O after parsing the .bvh file
*/
public void release() {
if (mParseReader != null) {
try {
mParseReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private Segment getSegmentFromFile(File file) {
mParseReader = getFileBufferedReader(file);
if (mParseReader == null) {
return null;
}
try {
String header = mParseReader.readLine().trim();
if (!readFileHeader(header)) {
throw new IllegalArgumentException("File : " + file.getName() + " is not a .bvh file");
}
return parseSegment(mParseReader);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Parsing the header of the .bvh file
*
* @param header the header string of the .bvh file
* @return true if the header is real header of the .bvh file, else return false
*/
private static boolean readFileHeader(String header) {
if (header == null || header.equalsIgnoreCase("")) {
return false;
}
if (header.equalsIgnoreCase(KEYWORD_HIERARCHY)) {
return true;
}
return false;
}
private Segment parseSegment(BufferedReader reader) {
if (reader == null) {
return null;
}
try {
Segment segment = new Segment();
String line = reader.readLine().trim(); // Header of segment
readSegmentFromeHeadString(segment, line);
line = reader.readLine().trim();
if (line.equals("{")) { // left curly brace
// do nothing
} else {
System.out.println("Error : left curly brace");
System.exit(-1);
}
line = reader.readLine().trim(); // offset
readOffsetsFromString(segment, line);
if (segment.type.equals(SegmentType.Root)) {
line = reader.readLine().trim(); // channel
readChannelsFromString(segment, line);
while (true) {
markReader(reader); // mark if need to call reset()
line = reader.readLine().trim(); // get the next line to judge whether have a subsegment
if (line.startsWith(KEYWORD_JOINT)) { // should call reset if has subsegment
resetReader(reader);
segment.putSubSegment(parseSegment(reader));
} else {
consumeMark(); // has no subsegment, dismark the reader
break;
}
}
} else if (segment.type.equals(SegmentType.Joint)) {
line = reader.readLine().trim(); // channel
readChannelsFromString(segment, line);
while (true) {
markReader(reader); // mark
line = reader.readLine().trim();
if (line.equalsIgnoreCase("}")) { // end joint
consumeMark(); // no subsegment, dismark
break;
}
if (line.startsWith(KEYWORD_JOINT)) { // has subsegment
resetReader(reader); // reset
segment.putSubSegment(parseSegment(reader)); // put joint
} else if (line.startsWith(KEYWORD_END_SITE)) {
resetReader(reader);
segment.putSubSegment(parseSegment(reader)); // put endsite
}
}
} else if (segment.type.equals(SegmentType.EndSite)) {
// end parsing, no more data
line = reader.readLine().trim(); // right curly brace
if (line.equalsIgnoreCase("}")) {
// do nothing
} else {
System.out.println("Error : right curly brace");
System.exit(-1);
}
}
return segment;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Parsing the string of a segment's head
*
* @param segment the header of this segment
* @param segmentHeader the string of the segment's head
*/
private static void readSegmentFromeHeadString(Segment segment, String segmentHeader) {
if (segmentHeader == null || segmentHeader.equalsIgnoreCase("")) {
return;
}
String[] splits = segmentHeader.split(" ");
if (splits[0].equalsIgnoreCase(KEYWORD_ROOT)) {
segment.setSegmentType(SegmentType.Root);
segment.setName(splits[1]);
} else if (splits[0].equalsIgnoreCase(KEYWORD_JOINT)) {
segment.setSegmentType(SegmentType.Joint);
segment.setName(splits[1]);
} else if (segmentHeader.equalsIgnoreCase(KEYWORD_END_SITE)) {
segment.setSegmentType(SegmentType.EndSite); // End site has no name
}
}
/**
* Parsing the string of the segment's offset
*
* @param segment the offset of this segment
* @param offsetString the string of the segment's offset
*/
private static void readOffsetsFromString(Segment segment, String offsetString) {
if (offsetString == null || offsetString.equalsIgnoreCase("")) {
return;
}
String[] splits = offsetString.split(" "); // may contains '\t' instead of ' '
if (!splits[0].equalsIgnoreCase(KEYWORD_OFFSET)) {
splits = offsetString.split("\\t");
if (!splits[0].equalsIgnoreCase(KEYWORD_OFFSET)) {
return;
}
}
if (segment != null) {
for (int i = 1; i <= NUM_OFFSET; ++i) {
try {
double offset = Double.parseDouble(splits[i]);
segment.putOffset(offset);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
* Parsing the string of the segment's channel
*
* @param segment the channel of this segment
* @param channelString the string of the segment's channel
*/
private static void readChannelsFromString(Segment segment, String channelString) {
if (channelString == null || channelString.equalsIgnoreCase("")) {
return;
}
String[] splits = channelString.split(" ");
if (!splits[0].equalsIgnoreCase(KEYWORD_CHANNELS)) {
return;
}
if (segment != null) {
try {
int channelsNum = Integer.parseInt(splits[1]);
segment.setChannelsNum(channelsNum);
for (int i = 2; i < 2 + channelsNum; ++i) {
ChannelType channelType = ChannelType.valueOf(ChannelType.class, splits[i]);
segment.putChannelType(channelType);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Get the BufferReader of the file
*
* @param file
* @return the BufferReader of {@param file}
*/
private BufferedReader getFileBufferedReader(File file) {
if (file == null || !file.exists() || !file.isFile()) {
return null;
}
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
return reader;
} catch (Exception e) {
e.printStackTrace();
}
return reader;
}
/**
* To use the function reset() of the BufferReader, should call mark(int) first,
* mark(int) need a param: readAheadLimit, it's recorded by a field of BufferReader,
* using reflection to get the value of the field which named "nextChar"
*
* @param reader to get this reader's field
* @return the field value of the {@param reader}
*/
private static int getReaderNextChar(BufferedReader reader) {
Class<?> clazz = BufferedReader.class;
try {
Field nextCharField = clazz.getDeclaredField("nextChar");
nextCharField.setAccessible(true);
return nextCharField.getInt(reader);
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
/**
* It's necessary to call BufferReader.mark(int) when call BufferReader.reset()
*
* To reset only once, using the field {@field isReaderMarkConsume} to control
*
* @param reader the reader we want to mark
*/
private void markReader(BufferedReader reader) {
try {
reader.mark(getReaderNextChar(reader));
isReaderMarkConsume = false;
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* If the reader was marked, using this function to dismark the reader
*/
private void consumeMark() {
isReaderMarkConsume = true;
}
/**
* When using BufferReader.readLine(), we need to call reset() when sometime need to rollback to the privious line,
* it's necessary to call mark(int) before calling reset(), the function of the {@function markReader} is to mark the reader
*
* To reset only once, using the field {@field isReaderMarkConsume} to control
*
* @param reader the reader we want to reset
*/
private void resetReader(BufferedReader reader) {
try {
if (!isReaderMarkConsume) {
reader.reset();
isReaderMarkConsume = true;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}