Refactoring: large changes in constraints system (see the proper topic on the forum for further changes)
git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10581 75d07b2b-3a1a-0410-a2c5-0572b91ccdca3.0
parent
c4fc9b723f
commit
29dd973122
@ -0,0 +1,415 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.constraints; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Map.Entry; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
import com.jme3.animation.AnimChannel; |
||||||
|
import com.jme3.animation.AnimControl; |
||||||
|
import com.jme3.animation.Animation; |
||||||
|
import com.jme3.animation.Bone; |
||||||
|
import com.jme3.animation.BoneTrack; |
||||||
|
import com.jme3.animation.Skeleton; |
||||||
|
import com.jme3.animation.SpatialTrack; |
||||||
|
import com.jme3.animation.Track; |
||||||
|
import com.jme3.math.Quaternion; |
||||||
|
import com.jme3.math.Transform; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.Node; |
||||||
|
import com.jme3.scene.Spatial; |
||||||
|
import com.jme3.scene.plugins.blender.BlenderContext; |
||||||
|
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; |
||||||
|
import com.jme3.scene.plugins.blender.animations.ArmatureHelper; |
||||||
|
import com.jme3.scene.plugins.blender.animations.BoneContext; |
||||||
|
import com.jme3.util.TempVars; |
||||||
|
|
||||||
|
/** |
||||||
|
* A node that represents either spatial or bone in constraint simulation. The |
||||||
|
* node is applied its translation, rotation and scale for each frame of its |
||||||
|
* animation. Then the constraints are applied that will eventually alter it. |
||||||
|
* After that the feature's transformation is stored in VirtualTrack which is |
||||||
|
* converted to new bone or spatial track at the very end. |
||||||
|
* |
||||||
|
* @author Marcin Roguski (Kaelthas) |
||||||
|
*/ |
||||||
|
public class SimulationNode { |
||||||
|
private static final Logger LOGGER = Logger.getLogger(SimulationNode.class.getName()); |
||||||
|
|
||||||
|
/** The name of the node (for debugging purposes). */ |
||||||
|
private String name; |
||||||
|
/** A list of children for the node (either bones or child spatials). */ |
||||||
|
private List<SimulationNode> children = new ArrayList<SimulationNode>(); |
||||||
|
/** A virtual track for each of the nodes. */ |
||||||
|
private Map<String, VirtualTrack> virtualTrack = new HashMap<String, VirtualTrack>(); |
||||||
|
/** A list of constraints that the current node has. */ |
||||||
|
private List<Constraint> constraints; |
||||||
|
/** A list of node's animations. */ |
||||||
|
private List<Animation> animations; |
||||||
|
|
||||||
|
/** The nodes spatial (if null then the boneContext should be set). */ |
||||||
|
private Spatial spatial; |
||||||
|
/** The skeleton of the bone (not null if the node simulated the bone). */ |
||||||
|
private Skeleton skeleton; |
||||||
|
/** Animation controller for the node's feature. */ |
||||||
|
private AnimControl animControl; |
||||||
|
|
||||||
|
/** |
||||||
|
* The star transform of a spatial. Needed to properly reset the spatial to |
||||||
|
* its start position. |
||||||
|
*/ |
||||||
|
private Transform spatialStartTransform; |
||||||
|
/** Star transformations for bones. Needed to properly reset the bones. */ |
||||||
|
private Map<Bone, Transform> boneStartTransforms; |
||||||
|
|
||||||
|
/** |
||||||
|
* Builds the nodes tree for the given feature. The feature (bone or |
||||||
|
* spatial) is found by its OMA. The feature must be a root bone or a root |
||||||
|
* spatial. |
||||||
|
* |
||||||
|
* @param featureOMA |
||||||
|
* the OMA of either bone or spatial |
||||||
|
* @param blenderContext |
||||||
|
* the blender context |
||||||
|
*/ |
||||||
|
public SimulationNode(Long featureOMA, BlenderContext blenderContext) { |
||||||
|
this(featureOMA, blenderContext, true); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates the node for the feature. |
||||||
|
* |
||||||
|
* @param featureOMA |
||||||
|
* the OMA of either bone or spatial |
||||||
|
* @param blenderContext |
||||||
|
* the blender context |
||||||
|
* @param rootNode |
||||||
|
* indicates if the feature is a root bone or root spatial or not |
||||||
|
*/ |
||||||
|
private SimulationNode(Long featureOMA, BlenderContext blenderContext, boolean rootNode) { |
||||||
|
Node spatial = (Node) blenderContext.getLoadedFeature(featureOMA, LoadedFeatureDataType.LOADED_FEATURE); |
||||||
|
if (spatial.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) != null) { |
||||||
|
this.skeleton = blenderContext.getSkeleton(featureOMA); |
||||||
|
|
||||||
|
Node nodeWithAnimationControl = blenderContext.getControlledNode(skeleton); |
||||||
|
this.animControl = nodeWithAnimationControl.getControl(AnimControl.class); |
||||||
|
|
||||||
|
boneStartTransforms = new HashMap<Bone, Transform>(); |
||||||
|
for (int i = 0; i < skeleton.getBoneCount(); ++i) { |
||||||
|
Bone bone = skeleton.getBone(i); |
||||||
|
boneStartTransforms.put(bone, new Transform(bone.getWorldBindPosition(), bone.getWorldBindRotation(), bone.getWorldBindScale())); |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (rootNode && spatial.getParent() != null) { |
||||||
|
throw new IllegalStateException("Given spatial must be a root node!"); |
||||||
|
} |
||||||
|
this.spatial = spatial; |
||||||
|
this.spatialStartTransform = spatial.getLocalTransform().clone(); |
||||||
|
} |
||||||
|
|
||||||
|
this.name = '>' + spatial.getName() + '<'; |
||||||
|
|
||||||
|
constraints = this.findConstraints(featureOMA, blenderContext); |
||||||
|
if (constraints == null) { |
||||||
|
constraints = new ArrayList<Constraint>(); |
||||||
|
} |
||||||
|
|
||||||
|
// add children nodes
|
||||||
|
if (skeleton != null) { |
||||||
|
// bone with index 0 is a root bone and should not be considered
|
||||||
|
// here
|
||||||
|
for (int i = 1; i < skeleton.getBoneCount(); ++i) { |
||||||
|
BoneContext boneContext = blenderContext.getBoneContext(skeleton.getBone(i)); |
||||||
|
List<Constraint> boneConstraints = this.findConstraints(boneContext.getBoneOma(), blenderContext); |
||||||
|
if (boneConstraints != null) { |
||||||
|
constraints.addAll(boneConstraints); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// each bone of the skeleton has the same anim data applied
|
||||||
|
BoneContext boneContext = blenderContext.getBoneContext(skeleton.getBone(1)); |
||||||
|
Long boneOma = boneContext.getBoneOma(); |
||||||
|
animations = blenderContext.getAnimData(boneOma) == null ? null : blenderContext.getAnimData(boneOma).anims; |
||||||
|
} else { |
||||||
|
animations = blenderContext.getAnimData(featureOMA) == null ? null : blenderContext.getAnimData(featureOMA).anims; |
||||||
|
for (Spatial child : spatial.getChildren()) { |
||||||
|
if (child instanceof Node) { |
||||||
|
children.add(new SimulationNode((Long) child.getUserData("oma"), blenderContext, false)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
LOGGER.info("Removing invalid constraints."); |
||||||
|
List<Constraint> validConstraints = new ArrayList<Constraint>(constraints.size()); |
||||||
|
for (Constraint constraint : this.constraints) { |
||||||
|
if (constraint.validate()) { |
||||||
|
validConstraints.add(constraint); |
||||||
|
} else { |
||||||
|
LOGGER.log(Level.WARNING, "Constraint {0} is invalid and will not be applied.", constraint.name); |
||||||
|
} |
||||||
|
} |
||||||
|
this.constraints = validConstraints; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Tells if the node already contains the given constraint (so that it is |
||||||
|
* not applied twice). |
||||||
|
* |
||||||
|
* @param constraint |
||||||
|
* the constraint to be checked |
||||||
|
* @return <b>true</b> if the constraint already is stored in the node and |
||||||
|
* <b>false</b> otherwise |
||||||
|
*/ |
||||||
|
public boolean contains(Constraint constraint) { |
||||||
|
boolean result = false; |
||||||
|
if (constraints != null && constraints.size() > 0) { |
||||||
|
for (Constraint c : constraints) { |
||||||
|
if (c.equals(constraint)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Resets the node's feature to its starting transformation. |
||||||
|
*/ |
||||||
|
private void reset() { |
||||||
|
if (spatial != null) { |
||||||
|
spatial.setLocalTransform(spatialStartTransform); |
||||||
|
for (SimulationNode child : children) { |
||||||
|
child.reset(); |
||||||
|
} |
||||||
|
} else if (skeleton != null) { |
||||||
|
for (Entry<Bone, Transform> entry : boneStartTransforms.entrySet()) { |
||||||
|
Transform t = entry.getValue(); |
||||||
|
entry.getKey().setBindTransforms(t.getTranslation(), t.getRotation(), t.getScale()); |
||||||
|
} |
||||||
|
skeleton.reset(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Simulates the spatial node. |
||||||
|
*/ |
||||||
|
private void simulateSpatial() { |
||||||
|
if (constraints != null && constraints.size() > 0) { |
||||||
|
boolean applyStaticConstraints = true; |
||||||
|
if (animations != null) { |
||||||
|
for (Animation animation : animations) { |
||||||
|
float[] animationTimeBoundaries = computeAnimationTimeBoundaries(animation); |
||||||
|
int maxFrame = (int) animationTimeBoundaries[0]; |
||||||
|
float maxTime = animationTimeBoundaries[1]; |
||||||
|
|
||||||
|
VirtualTrack vTrack = new VirtualTrack(maxFrame, maxTime); |
||||||
|
virtualTrack.put(animation.getName(), vTrack); |
||||||
|
for (Track track : animation.getTracks()) { |
||||||
|
for (int frame = 0; frame < maxFrame; ++frame) { |
||||||
|
spatial.setLocalTranslation(((SpatialTrack) track).getTranslations()[frame]); |
||||||
|
spatial.setLocalRotation(((SpatialTrack) track).getRotations()[frame]); |
||||||
|
spatial.setLocalScale(((SpatialTrack) track).getScales()[frame]); |
||||||
|
|
||||||
|
for (Constraint constraint : constraints) { |
||||||
|
constraint.apply(frame); |
||||||
|
vTrack.setTransform(frame, spatial.getLocalTransform()); |
||||||
|
} |
||||||
|
} |
||||||
|
Track newTrack = vTrack.getAsSpatialTrack(); |
||||||
|
if (newTrack != null) { |
||||||
|
animation.removeTrack(track); |
||||||
|
animation.addTrack(newTrack); |
||||||
|
} |
||||||
|
applyStaticConstraints = false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// if there are no animations then just constraint the static
|
||||||
|
// object's transformation
|
||||||
|
if (applyStaticConstraints) { |
||||||
|
for (Constraint constraint : constraints) { |
||||||
|
constraint.apply(0); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (SimulationNode child : children) { |
||||||
|
child.simulate(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Simulates the bone node. |
||||||
|
*/ |
||||||
|
private void simulateSkeleton() { |
||||||
|
if (constraints != null && constraints.size() > 0) { |
||||||
|
boolean applyStaticConstraints = true; |
||||||
|
|
||||||
|
if (animations != null) { |
||||||
|
TempVars vars = TempVars.get(); |
||||||
|
AnimChannel animChannel = animControl.createChannel(); |
||||||
|
for (Animation animation : animations) { |
||||||
|
float[] animationTimeBoundaries = this.computeAnimationTimeBoundaries(animation); |
||||||
|
int maxFrame = (int) animationTimeBoundaries[0]; |
||||||
|
float maxTime = animationTimeBoundaries[1]; |
||||||
|
|
||||||
|
Map<Integer, VirtualTrack> tracks = new HashMap<Integer, VirtualTrack>(); |
||||||
|
Map<Integer, Transform> previousTransforms = new HashMap<Integer, Transform>(); |
||||||
|
for (int frame = 0; frame < maxFrame; ++frame) { |
||||||
|
this.reset();// this MUST be done here, otherwise
|
||||||
|
// setting next frame of animation will
|
||||||
|
// lead to possible errors
|
||||||
|
// first set proper time for all bones in all the tracks
|
||||||
|
// ...
|
||||||
|
for (Track track : animation.getTracks()) { |
||||||
|
float time = ((BoneTrack) track).getTimes()[frame]; |
||||||
|
Integer boneIndex = ((BoneTrack) track).getTargetBoneIndex(); |
||||||
|
|
||||||
|
track.setTime(time, 1, animControl, animChannel, vars); |
||||||
|
skeleton.updateWorldVectors(); |
||||||
|
|
||||||
|
Transform previousTransform = previousTransforms.get(boneIndex); |
||||||
|
if (previousTransform == null) { |
||||||
|
Bone bone = skeleton.getBone(boneIndex); |
||||||
|
previousTransform = new Transform(); |
||||||
|
previousTransform.setTranslation(bone.getLocalPosition()); |
||||||
|
previousTransform.setRotation(bone.getLocalRotation()); |
||||||
|
previousTransform.setScale(bone.getLocalScale()); |
||||||
|
previousTransforms.put(boneIndex, previousTransform); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ... and then apply constraints ...
|
||||||
|
for (Constraint constraint : constraints) { |
||||||
|
constraint.apply(frame); |
||||||
|
} |
||||||
|
|
||||||
|
// ... and fill in another frame in the result track
|
||||||
|
for (Track track : animation.getTracks()) { |
||||||
|
Integer boneIndex = ((BoneTrack) track).getTargetBoneIndex(); |
||||||
|
Bone bone = skeleton.getBone(boneIndex); |
||||||
|
|
||||||
|
// take the initial transform of a bone
|
||||||
|
Transform previousTransform = previousTransforms.get(boneIndex); |
||||||
|
|
||||||
|
VirtualTrack vTrack = tracks.get(boneIndex); |
||||||
|
if (vTrack == null) { |
||||||
|
vTrack = new VirtualTrack(maxFrame, maxTime); |
||||||
|
tracks.put(boneIndex, vTrack); |
||||||
|
} |
||||||
|
|
||||||
|
Vector3f bonePositionDifference = bone.getLocalPosition().subtract(previousTransform.getTranslation()); |
||||||
|
Quaternion boneRotationDifference = bone.getLocalRotation().mult(previousTransform.getRotation().inverse()).normalizeLocal(); |
||||||
|
Vector3f boneScaleDifference = bone.getLocalScale().divide(previousTransform.getScale()); |
||||||
|
if (frame > 0) { |
||||||
|
bonePositionDifference = vTrack.translations.get(frame - 1).add(bonePositionDifference); |
||||||
|
boneRotationDifference = vTrack.rotations.get(frame - 1).mult(boneRotationDifference); |
||||||
|
boneScaleDifference = vTrack.scales.get(frame - 1).mult(boneScaleDifference); |
||||||
|
} |
||||||
|
vTrack.setTransform(frame, new Transform(bonePositionDifference, boneRotationDifference, boneScaleDifference)); |
||||||
|
|
||||||
|
previousTransform.setTranslation(bone.getLocalPosition()); |
||||||
|
previousTransform.setRotation(bone.getLocalRotation()); |
||||||
|
previousTransform.setScale(bone.getLocalScale()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (Entry<Integer, VirtualTrack> trackEntry : tracks.entrySet()) { |
||||||
|
Track newTrack = trackEntry.getValue().getAsBoneTrack(trackEntry.getKey()); |
||||||
|
if (newTrack != null) { |
||||||
|
for (Track track : animation.getTracks()) { |
||||||
|
if (((BoneTrack) track).getTargetBoneIndex() == trackEntry.getKey().intValue()) { |
||||||
|
animation.removeTrack(track); |
||||||
|
animation.addTrack(newTrack); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
applyStaticConstraints = false; |
||||||
|
} |
||||||
|
} |
||||||
|
vars.release(); |
||||||
|
animControl.clearChannels(); |
||||||
|
this.reset(); |
||||||
|
} |
||||||
|
|
||||||
|
// if there are no animations then just constraint the static
|
||||||
|
// object's transformation
|
||||||
|
if (applyStaticConstraints) { |
||||||
|
for (Constraint constraint : constraints) { |
||||||
|
constraint.apply(0); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Simulates the node. |
||||||
|
*/ |
||||||
|
public void simulate() { |
||||||
|
this.reset(); |
||||||
|
if (spatial != null) { |
||||||
|
this.simulateSpatial(); |
||||||
|
} else { |
||||||
|
this.simulateSkeleton(); |
||||||
|
} |
||||||
|
this.reset(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Computes the maximum frame and time for the animation. Different tracks |
||||||
|
* can have different lengths so here the maximum one is being found. |
||||||
|
* |
||||||
|
* @param animation |
||||||
|
* the animation |
||||||
|
* @return maximum frame and time of the animation |
||||||
|
*/ |
||||||
|
private float[] computeAnimationTimeBoundaries(Animation animation) { |
||||||
|
int maxFrame = Integer.MIN_VALUE; |
||||||
|
float maxTime = Float.MIN_VALUE; |
||||||
|
for (Track track : animation.getTracks()) { |
||||||
|
if (track instanceof BoneTrack) { |
||||||
|
maxFrame = Math.max(maxFrame, ((BoneTrack) track).getTranslations().length); |
||||||
|
maxTime = Math.max(maxTime, ((BoneTrack) track).getTimes()[((BoneTrack) track).getTimes().length - 1]); |
||||||
|
} else if (track instanceof SpatialTrack) { |
||||||
|
maxFrame = Math.max(maxFrame, ((SpatialTrack) track).getTranslations().length); |
||||||
|
maxTime = Math.max(maxTime, ((SpatialTrack) track).getTimes()[((SpatialTrack) track).getTimes().length - 1]); |
||||||
|
} else { |
||||||
|
throw new IllegalStateException("Unsupported track type for simuation: " + track); |
||||||
|
} |
||||||
|
} |
||||||
|
return new float[] { maxFrame, maxTime }; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Finds constraints for the node's features. |
||||||
|
* |
||||||
|
* @param ownerOMA |
||||||
|
* the feature's OMA |
||||||
|
* @param blenderContext |
||||||
|
* the blender context |
||||||
|
* @return a list of feature's constraints or empty list if none were found |
||||||
|
*/ |
||||||
|
private List<Constraint> findConstraints(Long ownerOMA, BlenderContext blenderContext) { |
||||||
|
List<Constraint> result = new ArrayList<Constraint>(); |
||||||
|
for (Constraint constraint : blenderContext.getAllConstraints()) { |
||||||
|
if (constraint.ownerOMA.longValue() == ownerOMA.longValue()) { |
||||||
|
if (constraint.isImplemented()) { |
||||||
|
result.add(constraint); |
||||||
|
} else { |
||||||
|
LOGGER.log(Level.WARNING, "Constraint named: ''{0}'' of type ''{1}'' is not implemented and will NOT be applied!", new Object[] { constraint.name, constraint.getConstraintTypeName() }); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return result.size() > 0 ? result : null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return name; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,146 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.constraints; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
|
||||||
|
import com.jme3.animation.BoneTrack; |
||||||
|
import com.jme3.animation.SpatialTrack; |
||||||
|
import com.jme3.math.Quaternion; |
||||||
|
import com.jme3.math.Transform; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
|
||||||
|
/** |
||||||
|
* A virtual track that stores computed frames after constraints are applied. |
||||||
|
* Not all the frames need to be inserted. If there are lacks then the class
|
||||||
|
* will fill the gaps. |
||||||
|
* |
||||||
|
* @author Marcin Roguski (Kaelthas) |
||||||
|
*/ |
||||||
|
/* package */class VirtualTrack { |
||||||
|
/** The last frame for the track. */ |
||||||
|
public int maxFrame; |
||||||
|
/** The max time for the track. */ |
||||||
|
public float maxTime; |
||||||
|
/** Translations of the track. */ |
||||||
|
public ArrayList<Vector3f> translations; |
||||||
|
/** Rotations of the track. */ |
||||||
|
public ArrayList<Quaternion> rotations; |
||||||
|
/** Scales of the track. */ |
||||||
|
public ArrayList<Vector3f> scales; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructs the object storing the maximum frame and time. |
||||||
|
* |
||||||
|
* @param maxFrame |
||||||
|
* the last frame for the track |
||||||
|
* @param maxTime |
||||||
|
* the max time for the track |
||||||
|
*/ |
||||||
|
public VirtualTrack(int maxFrame, float maxTime) { |
||||||
|
this.maxFrame = maxFrame; |
||||||
|
this.maxTime = maxTime; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the transform for the given frame. |
||||||
|
* |
||||||
|
* @param frameIndex |
||||||
|
* the frame for which the transform will be set |
||||||
|
* @param transform |
||||||
|
* the transformation to be set |
||||||
|
*/ |
||||||
|
public void setTransform(int frameIndex, Transform transform) { |
||||||
|
if (translations == null) { |
||||||
|
translations = this.createList(Vector3f.ZERO, frameIndex); |
||||||
|
} |
||||||
|
this.append(translations, Vector3f.ZERO, frameIndex - translations.size()); |
||||||
|
translations.add(transform.getTranslation().clone()); |
||||||
|
|
||||||
|
if (rotations == null) { |
||||||
|
rotations = this.createList(Quaternion.IDENTITY, frameIndex); |
||||||
|
} |
||||||
|
this.append(rotations, Quaternion.IDENTITY, frameIndex - rotations.size()); |
||||||
|
rotations.add(transform.getRotation().clone()); |
||||||
|
|
||||||
|
if (scales == null) { |
||||||
|
scales = this.createList(Vector3f.UNIT_XYZ, frameIndex); |
||||||
|
} |
||||||
|
this.append(scales, Vector3f.UNIT_XYZ, frameIndex - scales.size()); |
||||||
|
scales.add(transform.getScale().clone()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the track as a bone track. |
||||||
|
* |
||||||
|
* @param targetBoneIndex |
||||||
|
* the bone index |
||||||
|
* @return the bone track |
||||||
|
*/ |
||||||
|
public BoneTrack getAsBoneTrack(int targetBoneIndex) { |
||||||
|
if (translations == null && rotations == null && scales == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return new BoneTrack(targetBoneIndex, this.createTimes(), translations.toArray(new Vector3f[maxFrame]), rotations.toArray(new Quaternion[maxFrame]), scales.toArray(new Vector3f[maxFrame])); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the track as a spatial track. |
||||||
|
* |
||||||
|
* @return the spatial track |
||||||
|
*/ |
||||||
|
public SpatialTrack getAsSpatialTrack() { |
||||||
|
if (translations == null && rotations == null && scales == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return new SpatialTrack(this.createTimes(), translations.toArray(new Vector3f[maxFrame]), rotations.toArray(new Quaternion[maxFrame]), scales.toArray(new Vector3f[maxFrame])); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method creates times for the track based on the given maximum values. |
||||||
|
* |
||||||
|
* @return the times for the track |
||||||
|
*/ |
||||||
|
private float[] createTimes() { |
||||||
|
float[] times = new float[maxFrame]; |
||||||
|
float dT = maxTime / (float) maxFrame; |
||||||
|
float t = 0; |
||||||
|
for (int i = 0; i < maxFrame; ++i) { |
||||||
|
times[i] = t; |
||||||
|
t += dT; |
||||||
|
} |
||||||
|
return times; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Helper method that creates a list of a given size filled with given |
||||||
|
* elements. |
||||||
|
* |
||||||
|
* @param element |
||||||
|
* the element to be put into the list |
||||||
|
* @param count |
||||||
|
* the list size |
||||||
|
* @return the list |
||||||
|
*/ |
||||||
|
private <T> ArrayList<T> createList(T element, int count) { |
||||||
|
ArrayList<T> result = new ArrayList<T>(count); |
||||||
|
for (int i = 0; i < count; ++i) { |
||||||
|
result.add(element); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Appends the element to the given list. |
||||||
|
* |
||||||
|
* @param list |
||||||
|
* the list where the element will be appended |
||||||
|
* @param element |
||||||
|
* the element to be appended |
||||||
|
* @param count |
||||||
|
* how many times the element will be appended |
||||||
|
*/ |
||||||
|
private <T> void append(ArrayList<T> list, T element, int count) { |
||||||
|
for (int i = 0; i < count; ++i) { |
||||||
|
list.add(element); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,243 +1,49 @@ |
|||||||
package com.jme3.scene.plugins.blender.constraints.definitions; |
package com.jme3.scene.plugins.blender.constraints.definitions; |
||||||
|
|
||||||
import java.io.IOException; |
|
||||||
|
|
||||||
import com.jme3.animation.AnimChannel; |
|
||||||
import com.jme3.animation.AnimControl; |
|
||||||
import com.jme3.animation.BoneTrack; |
|
||||||
import com.jme3.animation.SpatialTrack; |
|
||||||
import com.jme3.animation.Track; |
|
||||||
import com.jme3.export.JmeExporter; |
|
||||||
import com.jme3.export.JmeImporter; |
|
||||||
import com.jme3.math.FastMath; |
|
||||||
import com.jme3.math.Quaternion; |
|
||||||
import com.jme3.math.Transform; |
import com.jme3.math.Transform; |
||||||
import com.jme3.math.Vector3f; |
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext; |
import com.jme3.scene.plugins.blender.BlenderContext; |
||||||
import com.jme3.scene.plugins.blender.animations.Ipo; |
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; |
||||||
import com.jme3.scene.plugins.blender.file.Structure; |
import com.jme3.scene.plugins.blender.file.Structure; |
||||||
import com.jme3.util.TempVars; |
|
||||||
|
|
||||||
public abstract class ConstraintDefinition { |
public abstract class ConstraintDefinition { |
||||||
protected int flag; |
protected int flag; |
||||||
|
private Object owner; |
||||||
|
private BlenderContext blenderContext; |
||||||
|
private Long ownerOMA; |
||||||
|
|
||||||
public ConstraintDefinition(Structure constraintData, BlenderContext blenderContext) { |
public ConstraintDefinition(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { |
||||||
if (constraintData != null) {// Null constraint has no data
|
if (constraintData != null) {// Null constraint has no data
|
||||||
Number flag = (Number) constraintData.getFieldValue("flag"); |
Number flag = (Number) constraintData.getFieldValue("flag"); |
||||||
if (flag != null) { |
if (flag != null) { |
||||||
this.flag = flag.intValue(); |
this.flag = flag.intValue(); |
||||||
} |
} |
||||||
} |
} |
||||||
} |
this.blenderContext = blenderContext; |
||||||
|
this.ownerOMA = ownerOMA; |
||||||
public void bake(Transform ownerTransform, Transform targetTransform, Track ownerTrack, Track targetTrack, Ipo influenceIpo) { |
|
||||||
TrackWrapper ownerWrapperTrack = ownerTrack != null ? new TrackWrapper(ownerTrack) : null; |
|
||||||
TrackWrapper targetWrapperTrack = targetTrack != null ? new TrackWrapper(targetTrack) : null; |
|
||||||
|
|
||||||
// uruchamiamy bake dla transformat zalenie od tego, ktre argumenty s nullami, a ktre - nie
|
|
||||||
this.bake(ownerTransform, targetTransform, influenceIpo.calculateValue(0)); |
|
||||||
if (ownerWrapperTrack != null) { |
|
||||||
float[] ownerTimes = ownerWrapperTrack.getTimes(); |
|
||||||
Vector3f[] translations = ownerWrapperTrack.getTranslations(); |
|
||||||
Quaternion[] rotations = ownerWrapperTrack.getRotations(); |
|
||||||
Vector3f[] scales = ownerWrapperTrack.getScales(); |
|
||||||
|
|
||||||
float[] targetTimes = targetWrapperTrack == null ? null : targetWrapperTrack.getTimes(); |
|
||||||
Vector3f[] targetTranslations = targetWrapperTrack == null ? null : targetWrapperTrack.getTranslations(); |
|
||||||
Quaternion[] targetRotations = targetWrapperTrack == null ? null : targetWrapperTrack.getRotations(); |
|
||||||
Vector3f[] targetScales = targetWrapperTrack == null ? null : targetWrapperTrack.getScales(); |
|
||||||
Vector3f translation = new Vector3f(), scale = new Vector3f(); |
|
||||||
Quaternion rotation = new Quaternion(); |
|
||||||
|
|
||||||
Transform ownerTemp = new Transform(), targetTemp = new Transform(); |
|
||||||
for (int i = 0; i < ownerTimes.length; ++i) { |
|
||||||
float t = ownerTimes[i]; |
|
||||||
ownerTemp.setTranslation(translations[i]); |
|
||||||
ownerTemp.setRotation(rotations[i]); |
|
||||||
ownerTemp.setScale(scales[i]); |
|
||||||
if (targetWrapperTrack == null) { |
|
||||||
this.bake(ownerTemp, targetTransform, influenceIpo.calculateValue(i)); |
|
||||||
} else { |
|
||||||
// getting the values that are the interpolation of the target track for the time 't'
|
|
||||||
this.interpolate(targetTranslations, targetTimes, t, translation); |
|
||||||
this.interpolate(targetRotations, targetTimes, t, rotation); |
|
||||||
this.interpolate(targetScales, targetTimes, t, scale); |
|
||||||
|
|
||||||
targetTemp.setTranslation(translation); |
|
||||||
targetTemp.setRotation(rotation); |
|
||||||
targetTemp.setScale(scale); |
|
||||||
|
|
||||||
this.bake(ownerTemp, targetTemp, influenceIpo.calculateValue(i)); |
|
||||||
} |
|
||||||
// need to clone here because each of the arrays will reference the same instance if they hold the same value in the compact array
|
|
||||||
translations[i] = ownerTemp.getTranslation().clone(); |
|
||||||
rotations[i] = ownerTemp.getRotation().clone(); |
|
||||||
scales[i] = ownerTemp.getScale().clone(); |
|
||||||
} |
|
||||||
ownerWrapperTrack.setKeyframes(ownerTimes, translations, rotations, scales); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
protected abstract void bake(Transform ownerTransform, Transform targetTransform, float influence); |
|
||||||
|
|
||||||
private void interpolate(Vector3f[] targetVectors, float[] targetTimes, float currentTime, Vector3f result) { |
|
||||||
int index = 0; |
|
||||||
for (int i = 1; i < targetTimes.length; ++i) { |
|
||||||
if (targetTimes[i] < currentTime) { |
|
||||||
++index; |
|
||||||
} else { |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
if (index >= targetTimes.length - 1) { |
|
||||||
result.set(targetVectors[targetTimes.length - 1]); |
|
||||||
} else { |
|
||||||
float delta = targetTimes[index + 1] - targetTimes[index]; |
|
||||||
if (delta == 0.0f) { |
|
||||||
result.set(targetVectors[index + 1]); |
|
||||||
} else { |
|
||||||
float scale = (currentTime - targetTimes[index]) / (targetTimes[index + 1] - targetTimes[index]); |
|
||||||
FastMath.interpolateLinear(scale, targetVectors[index], targetVectors[index + 1], result); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void interpolate(Quaternion[] targetQuaternions, float[] targetTimes, float currentTime, Quaternion result) { |
|
||||||
int index = 0; |
|
||||||
for (int i = 1; i < targetTimes.length; ++i) { |
|
||||||
if (targetTimes[i] < currentTime) { |
|
||||||
++index; |
|
||||||
} else { |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
if (index >= targetTimes.length - 1) { |
|
||||||
result.set(targetQuaternions[targetTimes.length - 1]); |
|
||||||
} else { |
|
||||||
float delta = targetTimes[index + 1] - targetTimes[index]; |
|
||||||
if (delta == 0.0f) { |
|
||||||
result.set(targetQuaternions[index + 1]); |
|
||||||
} else { |
|
||||||
float scale = (currentTime - targetTimes[index]) / (targetTimes[index + 1] - targetTimes[index]); |
|
||||||
result.slerp(targetQuaternions[index], targetQuaternions[index + 1], scale); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This class holds either the bone track or spatial track. Is made to improve |
|
||||||
* code readability. |
|
||||||
* |
|
||||||
* @author Marcin Roguski (Kaelthas) |
|
||||||
*/ |
|
||||||
private static class TrackWrapper implements Track { |
|
||||||
/** The spatial track. */ |
|
||||||
private SpatialTrack spatialTrack; |
|
||||||
/** The bone track. */ |
|
||||||
private BoneTrack boneTrack; |
|
||||||
|
|
||||||
/** |
|
||||||
* Constructs the object using the given track. The track must be of one of the types: <li>BoneTrack <li>SpatialTrack |
|
||||||
* |
|
||||||
* @param track |
|
||||||
* the animation track |
|
||||||
*/ |
|
||||||
public TrackWrapper(Track track) { |
|
||||||
if (track instanceof SpatialTrack) { |
|
||||||
this.spatialTrack = (SpatialTrack) track; |
|
||||||
} else if (track instanceof BoneTrack) { |
|
||||||
this.boneTrack = (BoneTrack) track; |
|
||||||
} else { |
|
||||||
throw new IllegalStateException("Unknown track type!"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return the array of rotations of this track |
|
||||||
*/ |
|
||||||
public Quaternion[] getRotations() { |
|
||||||
if (boneTrack != null) { |
|
||||||
return boneTrack.getRotations(); |
|
||||||
} |
|
||||||
return spatialTrack.getRotations(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return the array of scales for this track |
|
||||||
*/ |
|
||||||
public Vector3f[] getScales() { |
|
||||||
if (boneTrack != null) { |
|
||||||
return boneTrack.getScales(); |
|
||||||
} |
|
||||||
return spatialTrack.getScales(); |
|
||||||
} |
} |
||||||
|
|
||||||
/** |
/** |
||||||
* @return the arrays of time for this track |
* This method is here because we have no guarantee that the owner is loaded |
||||||
*/ |
* when constraint is being created. So use it to get the owner when it is |
||||||
public float[] getTimes() { |
* needed for computations. |
||||||
if (boneTrack != null) { |
|
||||||
return boneTrack.getTimes(); |
|
||||||
} |
|
||||||
return spatialTrack.getTimes(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return the array of translations of this track |
|
||||||
*/ |
|
||||||
public Vector3f[] getTranslations() { |
|
||||||
if (boneTrack != null) { |
|
||||||
return boneTrack.getTranslations(); |
|
||||||
} |
|
||||||
return spatialTrack.getTranslations(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Set the translations, rotations and scales for this bone track |
|
||||||
* |
* |
||||||
* @param times |
* @return the owner of the constraint or null if none is set |
||||||
* a float array with the time of each frame |
|
||||||
* @param translations |
|
||||||
* the translation of the bone for each frame |
|
||||||
* @param rotations |
|
||||||
* the rotation of the bone for each frame |
|
||||||
* @param scales |
|
||||||
* the scale of the bone for each frame |
|
||||||
*/ |
*/ |
||||||
public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) { |
public Object getOwner() { |
||||||
if (boneTrack != null) { |
if (ownerOMA != null && owner == null) { |
||||||
boneTrack.setKeyframes(times, translations, rotations, scales); |
owner = blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE); |
||||||
} else { |
if (owner == null) { |
||||||
spatialTrack.setKeyframes(times, translations, rotations, scales); |
throw new IllegalStateException("Cannot load constraint's owner for constraint type: " + this.getClass().getName()); |
||||||
} |
} |
||||||
} |
} |
||||||
|
return owner; |
||||||
public void write(JmeExporter ex) throws IOException { |
|
||||||
// no need to implement this one (the TrackWrapper is used internally and never serialized)
|
|
||||||
} |
} |
||||||
|
|
||||||
public void read(JmeImporter im) throws IOException { |
public boolean isImplemented() { |
||||||
// no need to implement this one (the TrackWrapper is used internally and never serialized)
|
return true; |
||||||
} |
} |
||||||
|
|
||||||
public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { |
public abstract String getConstraintTypeName(); |
||||||
if (boneTrack != null) { |
|
||||||
boneTrack.setTime(time, weight, control, channel, vars); |
|
||||||
} else { |
|
||||||
spatialTrack.setTime(time, weight, control, channel, vars); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public float getLength() { |
|
||||||
return spatialTrack == null ? boneTrack.getLength() : spatialTrack.getLength(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
public abstract void bake(Transform ownerTransform, Transform targetTransform, float influence); |
||||||
public TrackWrapper clone() { |
|
||||||
if (boneTrack != null) { |
|
||||||
return new TrackWrapper(boneTrack.clone()); |
|
||||||
} |
|
||||||
return new TrackWrapper(spatialTrack.clone()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
} |
||||||
|
@ -1,28 +1,33 @@ |
|||||||
package com.jme3.scene.plugins.blender.constraints.definitions; |
package com.jme3.scene.plugins.blender.constraints.definitions; |
||||||
|
|
||||||
import java.util.logging.Level; |
|
||||||
import java.util.logging.Logger; |
|
||||||
|
|
||||||
import com.jme3.math.Transform; |
import com.jme3.math.Transform; |
||||||
|
|
||||||
/** |
/** |
||||||
* This class represents a constraint that is defined by blender but not supported by either importer |
* This class represents a constraint that is defined by blender but not |
||||||
* ot jme. It only wirtes down a warning when baking is called. |
* supported by either importer ot jme. It only wirtes down a warning when |
||||||
|
* baking is called. |
||||||
* |
* |
||||||
* @author Marcin Roguski (Kaelthas) |
* @author Marcin Roguski (Kaelthas) |
||||||
*/ |
*/ |
||||||
/* package */class UnsupportedConstraintDefinition extends ConstraintDefinition { |
/* package */class UnsupportedConstraintDefinition extends ConstraintDefinition { |
||||||
private static final Logger LOGGER = Logger.getLogger(UnsupportedConstraintDefinition.class.getName()); |
private String typeName; |
||||||
|
|
||||||
|
public UnsupportedConstraintDefinition(String typeName) { |
||||||
|
super(null, null, null); |
||||||
|
this.typeName = typeName; |
||||||
|
} |
||||||
|
|
||||||
private String name; |
@Override |
||||||
|
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { |
||||||
|
} |
||||||
|
|
||||||
public UnsupportedConstraintDefinition(String name) { |
@Override |
||||||
super(null, null); |
public boolean isImplemented() { |
||||||
this.name = name; |
return false; |
||||||
} |
} |
||||||
|
|
||||||
@Override |
@Override |
||||||
protected void bake(Transform ownerTransform, Transform targetTransform, float influence) { |
public String getConstraintTypeName() { |
||||||
LOGGER.log(Level.WARNING, "'{0}' constraint NOT implemented!", name); |
return typeName; |
||||||
} |
} |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue