/* * BVHReader.java * Created on 12. September 2006, 21:54 */ package mocap.reader; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.vecmath.Vector3d; import mocap.figure.AnimData; import mocap.figure.Bone; import mocap.figure.BoneGeom; /** * A BVH file contains first the skeleton hierarchy and then the animation data. * * See here for a very good summary: * * http://www.cs.wisc.edu/graphics/Courses/cs-838-1999/Jeff/BVH.html * * @author Michael Kipp */ public class BVHReader implements java.io.Serializable { static final long serialVersionUID = -2407211874494009862L; transient List _jointList = new ArrayList(); // sequential list of joints private double _scale = 1; // scaling factor used for bone length and position private int _motionVectorLength; public int _indexCounter; private double _maxRootDistance = 0; private double _targetHeight = 5; double cylinderradius = 0.5; // switch to 0.1 for limbs /** * Stores results. */ public class BVHResult implements java.io.Serializable { static final long serialVersionUID = 1258710093484653359L; //9114247830379077622L; transient public Bone skeleton; transient public BVHJoint joints; public AnimData animation; transient public BVHReader reader; } /** * Intermediate representation for 1st pass. */ abstract class BVHNode implements java.io.Serializable { Vector3d offset; // joint position (= dir/length of parent's bone) Vector3d rootOffset = new Vector3d(); // position in root space (for computing overall size) } class BVHEndSite extends BVHNode { } public class BVHJoint extends BVHNode { // public BVHJoint() // { // } String name; List subjoints = new ArrayList(); BVHEndSite endSite = null; int[] dof = new int[0]; boolean isRoot = false; public double findMax(double max) { double l = offset.length(); for (BVHJoint j : subjoints) { l = j.findMax(l); } return l > max ? l : max; } void scale(double scale) { offset.scale(scale); for (BVHJoint j : subjoints) { j.scale(scale); } if (endSite != null) { endSite.offset.scale(scale); } // System.out.println("scale " + name + ": " + offset); } } /** * * @param targetHeight * The height that the skeleton should have (automatic scaling). Specify -1 if you do * not want to rescale. */ public BVHReader(double targetHeight) { // _scale = scale; _targetHeight = targetHeight; _indexCounter = 0; } /** * Reads BVH file and returns skeleton and motion data. * * @throws java.io.IOException */ public BVHResult readFile(File file) throws IOException { BVHResult res = new BVHResult(); String line; BufferedReader in = new BufferedReader(new FileReader(file)); while ((line = in.readLine()) != null) { line = line.trim(); if (line.startsWith("HIERARCHY")) { _maxRootDistance = 0; BVHJoint r = readHierarchy(in); // System.out.println("### max = " + _maxRootDistance); // scale skeleton to target height if (_targetHeight > -1) { double s = _targetHeight / (2 * _maxRootDistance); // System.out.println("### scaling: " + s); r.scale(s); _scale = s; } // 2nd pass through skeleton //res.skeleton = processJoint(r, null, r.findMax(0) / 15); res.joints = r; res.reader = this; } if (line.startsWith("MOTION")) { res.animation = readMotion(in, res.skeleton); } } res.animation.scale = (float)_scale; return res; } public double getScale() { return _scale; } /** * 2nd pass for constructing the skeleton (recursively). */ public Bone processJoint(BVHJoint n, Bone parent, double maxRadius, boolean joints) { double cylrad = cylinderradius; Bone b = new Bone(n.name, _indexCounter++, n.dof); b.setRotationType(Bone.MOVING_AXES); if (parent != null) { b.setParent(parent); // EXTRA JOINTS // parent.attachGeom(n.offset, maxRadius, joints); } b.setBaseTranslation(n.offset); // for horse // if (n.name.equals("tail1")) { cylinderradius = 0.1; } if (n.name.equals("rThigh") || n.name.equals("lThigh")) { cylinderradius = 0.1; } if (n.name.equals("LeftUpLeg") || n.name.equals("RightUpLeg")) { cylinderradius = 0.1; } if (n.name.equals("rShldr") || n.name.equals("lShldr")) { cylinderradius = 0.1; } if (n.name.equals("LeftCollar") || n.name.equals("RightCollar")) { cylinderradius = 0.1; } if (n.name.equals("LeftHip") || n.name.equals("RightHip")) { cylinderradius = 0.1; } if (n.name.equals("head")) { cylinderradius = 0.1; joints = false; } if (n.name.equals("rShin") || n.name.equals("lShin")) { joints = false; } if (n.name.equals("LeftLowLeg") || n.name.equals("RightLowLeg")) { cylinderradius = 0.1; } if (n.name.equals("rForeArm") || n.name.equals("lForeArm")) { joints = false; } if (n.name.equals("LeftForeArm") || n.name.equals("RightForeArm")) { joints = false; } BoneGeom.CYLINDER_RADIUS = this.cylinderradius; // create children Bone[] ch = new Bone[n.subjoints.size()]; int i = 0; for (BVHNode c : n.subjoints) { if (c instanceof BVHJoint) { b.attachGeom(c.offset, maxRadius, joints); ch[i++] = processJoint((BVHJoint) c, b, maxRadius, joints); } } b.setChildren(ch); if (n.endSite != null) { b.attachGeom(n.endSite.offset, maxRadius, joints); } cylinderradius = cylrad; BoneGeom.CYLINDER_RADIUS = this.cylinderradius; return b; } /** * Starts 1st pass to construct the skeleton. */ private BVHJoint readHierarchy(BufferedReader in) throws IOException { String line; while ((line = in.readLine()) != null) { line = line.trim(); if (line.startsWith("ROOT")) { BVHJoint r = readJoint(in, null, line.substring(line.indexOf(' ') + 1)); r.isRoot = true; return r; } break; } return null; } /** * 1st pass joint processing. */ private BVHJoint readJoint(BufferedReader in, BVHJoint parent, String name) throws IOException { BVHJoint n = new BVHJoint(); _jointList.add(n); n.name = name; // do not count root location if (parent != null) { n.rootOffset.set(parent.offset); } String line; while ((line = in.readLine()) != null) { line = line.trim(); if (line.startsWith("JOINT")) { BVHJoint sub = readJoint(in, n, line.substring(line.indexOf(' ') + 1)); n.subjoints.add(sub); } else if (line.startsWith("OFFSET")) { n.offset = readVector(line.substring(6).trim()); n.offset.scale(_scale); n.rootOffset.add(n.offset); } else if (line.startsWith("CHANNELS")) { n.dof = readChannels(line.substring(8).trim()); } else if (line.startsWith("}")) { break; } else if (line.startsWith("End Site")) { n.endSite = readEndSite(in); n.endSite.rootOffset.set(n.rootOffset); n.endSite.rootOffset.add(n.endSite.offset); _maxRootDistance = Math.max(_maxRootDistance, n.endSite.rootOffset.length()); } } return n; } private Vector3d readVector(String str) { String[] arr = str.trim().split("\\s+"); return new Vector3d(Double.parseDouble(arr[0]), Double.parseDouble(arr[1]), Double.parseDouble(arr[2])); } private int[] readChannels(String str) { String[] tok = str.split(" "); int num = Integer.parseInt(tok[0]); int[] res = new int[num]; for (int i = 0; i < num; i++) { res[i] = getDOF(tok[i + 1]); } return res; } private int getDOF(String w) { if (w.equals("Xrotation")) { return Bone.RX; } else if (w.equals("Yrotation")) { return Bone.RY; } else if (w.equals("Zrotation")) { return Bone.RZ; } else if (w.equals("Xposition")) { return Bone.TX; } else if (w.equals("Yposition")) { return Bone.TY; } else if (w.equals("Zposition")) { return Bone.TZ; } return -1; } private BVHEndSite readEndSite(BufferedReader in) throws IOException { BVHEndSite n = new BVHEndSite(); String line; while ((line = in.readLine()) != null) { line = line.trim(); if (line.startsWith("OFFSET")) { n.offset = readVector(line.substring(6)); n.offset.scale(_scale); } else if (line.startsWith("}")) { return n; } } return n; } static public int EXTENSION = 64; // 128; public int mocapextension = 1; // for long motions usable for crowds /** * Reads motion (one line contains one frame) and stores the data in the respective Bone * objects. */ private AnimData readMotion(BufferedReader in, Bone root) throws IOException { computeMotionVectorLength(); float[][] motion = new float[_jointList.size()][]; // 1st index = joint, 2nd = data AnimData data = new AnimData(_jointList.size()); String line; int framecount = 0; while ((line = in.readLine()) != null) { line = line.trim(); if (line.startsWith("Frames:")) { int frames = Integer.parseInt(line.substring(7).trim()); if (frames < EXTENSION) { mocapextension = EXTENSION; // warning: 64 is bigger than 45 for motion fadein } data.setNumFrames(frames * mocapextension); // EXTEND MOCAP for (int i = 0; i < _jointList.size(); i++) { motion[i] = new float[data.getNumFrames() * _jointList.get(i).dof.length]; } } else if (line.startsWith("Frame Time:")) { float frametime = Float.parseFloat(line.substring(11).trim()); data.setFps(1 / frametime); } else if (line.length() > 0) { // motion line readMotionLine(motion, line, framecount++); //, data.getNumFrames()/mocapextension); } } if (mocapextension != 1) extendMotion(motion, data.getNumFrames() / mocapextension); else { // ShiftData(motion, data.getNumFrames(), 5); // skip "T" // data.setNumFrames(data.getNumFrames() - 5); } for (int i = 0; i < motion.length; i++) { data.putBoneData(i, motion[i]); } return data; } private void readMotionLine(float[][] motion, String line, int frame) //, int originalframes) { String[] tok = line.replaceAll("\\s+", " ").split(" "); if (tok.length != _motionVectorLength) { System.out.println("ERROR! Incorrect motion vector length: " + (frame + 1) + "th frame, " + tok.length + " found, " + _motionVectorLength + " required."); } int pos = 0; for (int i = 0; i < _jointList.size(); i++) { BVHJoint jnt = _jointList.get(i); int dof = jnt.dof.length; int motionindex = frame * dof; for (int j = 0; j < dof; j++) { // distinguish special case of 3 translational values for root double v = jnt.isRoot && j < 3 ? _scale * Float.parseFloat(tok[pos + j].trim()) : (float) Math.toRadians(Float.parseFloat(tok[pos + j].trim())); try { motion[i][motionindex + j] = (float) v; // for (int copy=0; copy 0) // { // v += motion[i][(copy-1)*originalframes*dof]; // } // } } catch (Exception e) { e.printStackTrace(); } } pos += dof; } } int ClosestData(float[][] motion, int totalframes) { double closestmean = Float.MAX_VALUE; int closestindex = totalframes; for (int frame = totalframes; --frame >= totalframes/4; ) { double mean = 0; for (int i = 1; i < _jointList.size(); i++) { BVHJoint jnt = _jointList.get(i); int dof = jnt.dof.length; // july 2014 assert(dof == 3); int beginoffset = 0; // 5; int motionindex = frame * dof; int beginindex = beginoffset * dof; for (int j = 0; j < dof; j++) { double v = motion[i][motionindex + j]; double d = motion[i][beginindex + j]; double delta = d - v; mean += delta*delta; } } if (mean < closestmean) { closestmean = mean; closestindex = frame; } } return closestindex; } void ShiftData(float[][] motion, int totalframes, int beginoffset) { for (int frame = beginoffset; frame < totalframes; frame++) { for (int i = 0; i < _jointList.size(); i++) { BVHJoint jnt = _jointList.get(i); int dof = jnt.dof.length; // assert(dof == 3); int motionindex = frame * dof; int beginindex = (frame - beginoffset) * dof; for (int j = 0; j < dof; j++) { motion[i][beginindex + j] = motion[i][motionindex + j]; } } } } private void extendMotion(float[][] motion, int totalframes) { int beginoffset = 5; ShiftData(motion, totalframes, beginoffset); // skip "T" totalframes -= beginoffset; System.err.println("totalframes = " + totalframes); totalframes = ClosestData(motion, totalframes); System.err.println("ClosestData = " + totalframes); for (int frame = 0; frame < totalframes; frame++) { for (int i = 0; i < _jointList.size(); i++) { BVHJoint jnt = _jointList.get(i); int dof = jnt.dof.length; int motionindex = frame * dof; // if (dof == 3 && frame < beginoffset) // skip "T" // { // motion[i][motionindex] = motion[i][beginoffset*dof]; // motion[i][motionindex + 1] = motion[i][beginoffset*dof+1]; // motion[i][motionindex + 2] = motion[i][beginoffset*dof+2]; // //// if (dof > 3) //// { //// motion[i][3] = motion[i][dof+3]; //// motion[i][4] = motion[i][dof+4]; //// motion[i][5] = motion[i][dof+5]; //// } // } for (int j = 0; j < dof; j++) { double v = motion[i][motionindex + j]; try { for (int copy = 1; copy < mocapextension * 4; copy++) { float offset = 0; if (jnt.isRoot && j < 3) { v += motion[i][(totalframes - 1) * dof + j]; offset = motion[i][j]; } motion[i][copy * totalframes * dof + motionindex + j] = (float) v - copy*offset; } } catch (Exception e) { //e.printStackTrace(); // extended out of bounds: OK } } } } } /** * Computes number of values expected in each line of the MOTION part of the BVH file. */ private void computeMotionVectorLength() { _motionVectorLength = 0; for (int i = 0; i < _jointList.size(); i++) { _motionVectorLength += _jointList.get(i).dof.length; } } }