package com.jme3.scene.plugins.blender.animations; import com.jme3.math.Matrix4f; import com.jme3.math.Vector3f; import com.jme3.scene.plugins.blender.file.DynamicArray; import com.jme3.scene.plugins.blender.file.Structure; /** * An implementation of bone envelope. Used when assigning bones to the mesh by envelopes. * * @author Marcin Roguski */ public class BoneEnvelope { /** A defined distance that will be included in the envelope space. */ private float distance; /** The bone's weight. */ private float weight; /** The radius of the bone's head. */ private float boneHeadRadius; /** The radius of the bone's tail. */ private float boneTailRadius; /** Head position in rest pose in world space. */ private Vector3f head; /** Tail position in rest pose in world space. */ private Vector3f tail; /** * The constructor of bone envelope. It reads all the needed data. Take notice that the positions of head and tail * are computed in the world space and that the points' positions given for computations should be in world space as well. * * @param boneStructure * the blender bone structure * @param armatureWorldMatrix * the world matrix of the armature object * @param fixUpAxis * a variable that tells if we use the Y-is up axis orientation */ @SuppressWarnings("unchecked") public BoneEnvelope(Structure boneStructure, Matrix4f armatureWorldMatrix, boolean fixUpAxis) { distance = ((Number) boneStructure.getFieldValue("dist")).floatValue(); weight = ((Number) boneStructure.getFieldValue("weight")).floatValue(); boneHeadRadius = ((Number) boneStructure.getFieldValue("rad_head")).floatValue(); boneTailRadius = ((Number) boneStructure.getFieldValue("rad_tail")).floatValue(); DynamicArray<Number> headArray = (DynamicArray<Number>) boneStructure.getFieldValue("arm_head"); head = new Vector3f(headArray.get(0).floatValue(), headArray.get(1).floatValue(), headArray.get(2).floatValue()); if (fixUpAxis) { float z = head.z; head.z = -head.y; head.y = z; } armatureWorldMatrix.mult(head, head);// move the head point to global space DynamicArray<Number> tailArray = (DynamicArray<Number>) boneStructure.getFieldValue("arm_tail"); tail = new Vector3f(tailArray.get(0).floatValue(), tailArray.get(1).floatValue(), tailArray.get(2).floatValue()); if (fixUpAxis) { float z = tail.z; tail.z = -tail.y; tail.y = z; } armatureWorldMatrix.mult(tail, tail);// move the tail point to global space } /** * The method verifies if the given point is inside the envelope. * @param point * the point in 3D space (MUST be in a world coordinate space) * @return <b>true</b> if the point is inside the envelope and <b>false</b> otherwise */ public boolean isInEnvelope(Vector3f point) { Vector3f v = tail.subtract(head); float boneLength = v.length(); v.normalizeLocal(); // computing a plane that contains 'point' and v is its normal vector // the plane's equation is: Ax + By + Cz + D = 0, where v = [A, B, C] float D = -v.dot(point); // computing a point where a line that contains head and tail crosses the plane float temp = -(v.dot(head) + D) / v.dot(v); Vector3f p = head.add(v.x * temp, v.y * temp, v.z * temp); // determining if the point p is on the same or other side of head than the tail point Vector3f headToPointOnLineVector = p.subtract(head); float headToPointLength = headToPointOnLineVector.length(); float cosinus = headToPointOnLineVector.dot(v) / headToPointLength;// the length of v is already = 1; cosinus should be either 1, 0 or -1 if (cosinus < 0 && headToPointLength > boneHeadRadius || headToPointLength > boneLength + boneTailRadius) { return false;// the point is outside the anvelope } // now check if the point is inside and envelope float pointDistanceFromLine = point.subtract(p).length(), maximumDistance = 0; if (cosinus < 0) { // checking if the distance from p to point is inside the half sphere defined by head envelope // compute the distance from the line to the half sphere border maximumDistance = boneHeadRadius; } else if (headToPointLength < boneLength) { // compute the maximum available distance if (boneTailRadius > boneHeadRadius) { // compute the distance from head to p float headToPDistance = p.subtract(head).length(); // from tangens function we have float x = headToPDistance * ((boneTailRadius - boneHeadRadius) / boneLength); maximumDistance = x + boneHeadRadius; } else if (boneTailRadius < boneHeadRadius) { // compute the distance from head to p float tailToPDistance = p.subtract(tail).length(); // from tangens function we have float x = tailToPDistance * ((boneHeadRadius - boneTailRadius) / boneLength); maximumDistance = x + boneTailRadius; } else { maximumDistance = boneTailRadius; } } else { // checking if the distance from p to point is inside the half sphere defined by tail envelope maximumDistance = boneTailRadius; } return pointDistanceFromLine <= maximumDistance + distance; } /** * @return the weight of the bone */ public float getWeight() { return weight; } @Override public String toString() { return "BoneEnvelope [d=" + distance + ", w=" + weight + ", hr=" + boneHeadRadius + ", tr=" + boneTailRadius + ", (" + head + ") -> (" + tail + ")]"; } }