Bugfix: sorting constraint computation in the proper order which should

decrease the amount of unwanted artifacts appearing in some models
during animations.
This commit is contained in:
jmekaelthas 2014-12-12 23:51:48 +01:00
parent 858fd433ca
commit 65c3ff668c
20 changed files with 421 additions and 69 deletions

@ -67,9 +67,9 @@ public class BoneContext {
private float length; private float length;
/** The bone's deform envelope. */ /** The bone's deform envelope. */
private BoneEnvelope boneEnvelope; private BoneEnvelope boneEnvelope;
// The below data is used only for IK constraint computations. // The below data is used only for IK constraint computations.
/** The bone's stretch value. */ /** The bone's stretch value. */
private float ikStretch; private float ikStretch;
/** Bone's rotation minimum values. */ /** Bone's rotation minimum values. */
@ -366,6 +366,30 @@ public class BoneContext {
return (flag & flagMask) != 0; return (flag & flagMask) != 0;
} }
/**
* @return the root bone context of this bone context
*/
public BoneContext getRoot() {
BoneContext result = this;
while (result.parent != null) {
result = result.parent;
}
return result;
}
/**
* @return a number of bones from this bone to its root
*/
public int getDistanceFromRoot() {
int result = 0;
BoneContext boneContext = this;
while (boneContext.parent != null) {
boneContext = boneContext.parent;
++result;
}
return result;
}
@Override @Override
public String toString() { public String toString() {
return "BoneContext: " + boneName; return "BoneContext: " + boneName;

@ -62,7 +62,7 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
} }
} }
} }
return true; return constraintDefinition == null ? true : constraintDefinition.isTargetRequired();
} }
@Override @Override
@ -70,4 +70,19 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
super.apply(frame); super.apply(frame);
blenderContext.getBoneContext(ownerOMA).getBone().updateModelTransforms(); blenderContext.getBoneContext(ownerOMA).getBone().updateModelTransforms();
} }
@Override
public Long getTargetOMA() {
if(targetOMA != null && subtargetName != null && !subtargetName.trim().isEmpty()) {
Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE);
if(nodeTarget != null) {
if(blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, nodeTarget) != null) {
BoneContext boneContext = blenderContext.getBoneByName(targetOMA, subtargetName);
return boneContext != null ? boneContext.getBoneOma() : 0L;
}
return targetOMA;
}
}
return 0L;
}
} }

@ -116,14 +116,31 @@ public abstract class Constraint {
*/ */
public abstract boolean validate(); public abstract boolean validate();
/**
* @return the OMA of the target or 0 if no target is specified for the constraint
*/
public abstract Long getTargetOMA();
/** /**
* Applies the constraint to owner (and in some cases can alter other bones of the skeleton). * Applies the constraint to owner (and in some cases can alter other bones of the skeleton).
* @param frame * @param frame
* the frame of the animation * the frame of the animation
*/ */
public void apply(int frame) { public void apply(int frame) {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST, "Applying constraint: {0} for frame {1}", new Object[] { name, frame });
}
Transform targetTransform = targetOMA != null ? constraintHelper.getTransform(targetOMA, subtargetName, targetSpace) : null; Transform targetTransform = targetOMA != null ? constraintHelper.getTransform(targetOMA, subtargetName, targetSpace) : null;
constraintDefinition.bake(ownerSpace, targetSpace, targetTransform, (float)ipo.calculateValue(frame)); constraintDefinition.bake(ownerSpace, targetSpace, targetTransform, (float) ipo.calculateValue(frame));
}
/**
* @return determines if the definition of the constraint will change the bone in any way; in most cases
* it is possible to tell that even before the constraint baking simulation is started, so we can discard such bones from constraint
* computing to improve the computation speed and lower the computations complexity
*/
public boolean isTrackToBeChanged() {
return constraintDefinition == null ? false : constraintDefinition.isTrackToBeChanged();
} }
@Override @Override
@ -163,4 +180,9 @@ public abstract class Constraint {
} }
return true; return true;
} }
@Override
public String toString() {
return "Constraint(name = " + name + ", def = " + constraintDefinition + ")";
}
} }

@ -1,6 +1,8 @@
package com.jme3.scene.plugins.blender.constraints; package com.jme3.scene.plugins.blender.constraints;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -132,7 +134,7 @@ public class SimulationNode {
} }
} }
Node node = blenderContext.getControlledNode(skeleton); Node node = blenderContext.getControlledNode(skeleton);
Long animatedNodeOMA = ((Number)blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, node)).longValue(); Long animatedNodeOMA = ((Number) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, node)).longValue();
animations = blenderContext.getAnimations(animatedNodeOMA); animations = blenderContext.getAnimations(animatedNodeOMA);
} else { } else {
animations = blenderContext.getAnimations(featureOMA); animations = blenderContext.getAnimations(featureOMA);
@ -206,7 +208,7 @@ public class SimulationNode {
int maxFrame = (int) animationTimeBoundaries[0]; int maxFrame = (int) animationTimeBoundaries[0];
float maxTime = animationTimeBoundaries[1]; float maxTime = animationTimeBoundaries[1];
VirtualTrack vTrack = new VirtualTrack(maxFrame, maxTime); VirtualTrack vTrack = new VirtualTrack(spatial.getName(), maxFrame, maxTime);
for (Track track : animation.getTracks()) { for (Track track : animation.getTracks()) {
for (int frame = 0; frame < maxFrame; ++frame) { for (int frame = 0; frame < maxFrame; ++frame) {
spatial.setLocalTranslation(((SpatialTrack) track).getTranslations()[frame]); spatial.setLocalTranslation(((SpatialTrack) track).getTranslations()[frame]);
@ -252,6 +254,8 @@ public class SimulationNode {
if (animations != null) { if (animations != null) {
TempVars vars = TempVars.get(); TempVars vars = TempVars.get();
AnimChannel animChannel = animControl.createChannel(); AnimChannel animChannel = animControl.createChannel();
List<Bone> bonesWithConstraints = this.collectBonesWithConstraints(skeleton);
for (Animation animation : animations) { for (Animation animation : animations) {
float[] animationTimeBoundaries = this.computeAnimationTimeBoundaries(animation); float[] animationTimeBoundaries = this.computeAnimationTimeBoundaries(animation);
int maxFrame = (int) animationTimeBoundaries[0]; int maxFrame = (int) animationTimeBoundaries[0];
@ -271,11 +275,8 @@ public class SimulationNode {
} }
// ... and then apply constraints from the root bone to the last child ... // ... and then apply constraints from the root bone to the last child ...
for (Bone rootBone : skeleton.getRoots()) { for (Bone rootBone : bonesWithConstraints) {
if (skeleton.getBoneIndex(rootBone) > 0) { this.applyConstraints(rootBone, alteredOmas, frame);
// ommit the 0 - indexed root bone as it is the bone added by importer
this.applyConstraints(rootBone, alteredOmas, frame);
}
} }
// ... add virtual tracks if neccessary, for bones that were altered but had no tracks before ... // ... add virtual tracks if neccessary, for bones that were altered but had no tracks before ...
@ -283,7 +284,7 @@ public class SimulationNode {
BoneContext boneContext = blenderContext.getBoneContext(boneOMA); BoneContext boneContext = blenderContext.getBoneContext(boneOMA);
int boneIndex = skeleton.getBoneIndex(boneContext.getBone()); int boneIndex = skeleton.getBoneIndex(boneContext.getBone());
if (!tracks.containsKey(boneIndex)) { if (!tracks.containsKey(boneIndex)) {
tracks.put(boneIndex, new VirtualTrack(maxFrame, maxTime)); tracks.put(boneIndex, new VirtualTrack(boneContext.getBone().getName(), maxFrame, maxTime));
} }
} }
alteredOmas.clear(); alteredOmas.clear();
@ -292,12 +293,12 @@ public class SimulationNode {
for (Entry<Integer, VirtualTrack> trackEntry : tracks.entrySet()) { for (Entry<Integer, VirtualTrack> trackEntry : tracks.entrySet()) {
Bone bone = skeleton.getBone(trackEntry.getKey()); Bone bone = skeleton.getBone(trackEntry.getKey());
Transform startTransform = boneStartTransforms.get(bone); Transform startTransform = boneStartTransforms.get(bone);
// track contains differences between the frame position and bind positions of bones/spatials // track contains differences between the frame position and bind positions of bones/spatials
Vector3f bonePositionDifference = bone.getLocalPosition().subtract(startTransform.getTranslation()); Vector3f bonePositionDifference = bone.getLocalPosition().subtract(startTransform.getTranslation());
Quaternion boneRotationDifference = startTransform.getRotation().inverse().mult(bone.getLocalRotation()).normalizeLocal(); Quaternion boneRotationDifference = startTransform.getRotation().inverse().mult(bone.getLocalRotation()).normalizeLocal();
Vector3f boneScaleDifference = bone.getLocalScale().divide(startTransform.getScale()); Vector3f boneScaleDifference = bone.getLocalScale().divide(startTransform.getScale());
trackEntry.getValue().setTransform(frame, new Transform(bonePositionDifference, boneRotationDifference, boneScaleDifference)); trackEntry.getValue().setTransform(frame, new Transform(bonePositionDifference, boneRotationDifference, boneScaleDifference));
} }
} }
@ -349,9 +350,6 @@ public class SimulationNode {
alteredOmas.add(boneContext.getBoneOma()); alteredOmas.add(boneContext.getBoneOma());
} }
} }
for (Bone child : bone.getChildren()) {
this.applyConstraints(child, alteredOmas, frame);
}
} }
/** /**
@ -366,6 +364,150 @@ public class SimulationNode {
} }
} }
/**
* Collects the bones that will take part in constraint computations.
* The result will not include bones whose constraints will not change them or are invalid.
* The bones are sorted so that the constraint applying is done in the proper order.
* @param skeleton
* the simulated skeleton
* @return a list of bones that will take part in constraints computations
*/
private List<Bone> collectBonesWithConstraints(Skeleton skeleton) {
Map<BoneContext, List<Constraint>> bonesWithConstraints = new HashMap<BoneContext, List<Constraint>>();
for (int i = 1; i < skeleton.getBoneCount(); ++i) {// ommit the 0 - indexed root bone as it is the bone added by importer
Bone bone = skeleton.getBone(i);
BoneContext boneContext = blenderContext.getBoneContext(bone);
List<Constraint> constraints = this.findConstraints(boneContext.getBoneOma(), blenderContext);
if (constraints != null && constraints.size() > 0) {
bonesWithConstraints.put(boneContext, constraints);
}
}
// first sort out constraints that are not implemented or invalid or will not affect the bone's tracks
List<BoneContext> bonesToRemove = new ArrayList<BoneContext>(bonesWithConstraints.size());
for (Entry<BoneContext, List<Constraint>> entry : bonesWithConstraints.entrySet()) {
List<Constraint> validConstraints = new ArrayList<Constraint>(entry.getValue().size());
for (Constraint constraint : entry.getValue()) {// TODO: sprawdziæ czy wprowadza jakiekolwiek zmiany
if (constraint.isImplemented() && constraint.validate() && constraint.isTrackToBeChanged()) {
validConstraints.add(constraint);
}
}
if (validConstraints.size() > 0) {
entry.setValue(validConstraints);
} else {
bonesToRemove.add(entry.getKey());
}
}
for (BoneContext boneContext : bonesToRemove) {
bonesWithConstraints.remove(boneContext);
}
List<BoneContext> bonesConstrainedWithoutTarget = new ArrayList<BoneContext>();
Set<Long> remainedOMAS = new HashSet<Long>();
// later move all bones with not dependant constraints to the front
bonesToRemove.clear();
for (Entry<BoneContext, List<Constraint>> entry : bonesWithConstraints.entrySet()) {
boolean hasDependantConstraints = false;
for (Constraint constraint : entry.getValue()) {
if (constraint.targetOMA != null) {
hasDependantConstraints = true;
break;
}
}
if (!hasDependantConstraints) {
bonesConstrainedWithoutTarget.add(entry.getKey());
bonesToRemove.add(entry.getKey());
} else {
remainedOMAS.add(entry.getKey().getBoneOma());
}
}
for (BoneContext boneContext : bonesToRemove) {
bonesWithConstraints.remove(boneContext);
}
this.sortBonesByChain(bonesConstrainedWithoutTarget);
// another step is to add those bones whose constraints depend only on bones already added to the result or to those
// that are not included neither in the result nor in the remaining map
// do this as long as bones are being moved to the result and the 'bonesWithConstraints' is not empty
List<BoneContext> bonesConstrainedWithTarget = new ArrayList<BoneContext>();
do {
bonesToRemove.clear();
for (Entry<BoneContext, List<Constraint>> entry : bonesWithConstraints.entrySet()) {
boolean unconstrainedBone = true;
for (Constraint constraint : entry.getValue()) {
if (remainedOMAS.contains(constraint.getTargetOMA())) {
unconstrainedBone = false;
break;
}
}
if (unconstrainedBone) {
bonesToRemove.add(entry.getKey());
bonesConstrainedWithTarget.add(entry.getKey());
}
}
for (BoneContext boneContext : bonesToRemove) {
bonesWithConstraints.remove(boneContext);
remainedOMAS.remove(boneContext.getBoneOma());
}
} while (bonesWithConstraints.size() > 0 && bonesToRemove.size() > 0);
this.sortBonesByChain(bonesConstrainedWithoutTarget);
// prepare the result
List<Bone> result = new ArrayList<Bone>();
for (BoneContext boneContext : bonesConstrainedWithoutTarget) {
result.add(boneContext.getBone());
}
for (BoneContext boneContext : bonesConstrainedWithTarget) {
result.add(boneContext.getBone());
}
// in the end prepare the mapping between bone OMA
if (bonesWithConstraints.size() > 0) {
LOGGER.warning("Some bones have loops in their constraints' definitions. The result might not be properly computed!");
for (BoneContext boneContext : bonesWithConstraints.keySet()) {
result.add(boneContext.getBone());
}
}
return result;
}
/**
* The method sorts the given bones from root to top.
* If the list contains bones from different branches then those branches will be listed
* one after another - which means that bones will be grouped by branches they belong to.
* @param bones
* a list of bones
*/
private void sortBonesByChain(List<BoneContext> bones) {
Map<BoneContext, List<BoneContext>> branches = new HashMap<BoneContext, List<BoneContext>>();
for (BoneContext bone : bones) {
BoneContext root = bone.getRoot();
List<BoneContext> list = branches.get(root);
if (list == null) {
list = new ArrayList<BoneContext>();
branches.put(root, list);
}
list.add(bone);
}
// sort the bones in each branch from root to leaf
bones.clear();
for (Entry<BoneContext, List<BoneContext>> entry : branches.entrySet()) {
Collections.sort(entry.getValue(), new Comparator<BoneContext>() {
@Override
public int compare(BoneContext o1, BoneContext o2) {
return o1.getDistanceFromRoot() - o2.getDistanceFromRoot();
}
});
bones.addAll(entry.getValue());
}
}
/** /**
* Computes the maximum frame and time for the animation. Different tracks * Computes the maximum frame and time for the animation. Different tracks
* can have different lengths so here the maximum one is being found. * can have different lengths so here the maximum one is being found.
@ -376,7 +518,7 @@ public class SimulationNode {
*/ */
private float[] computeAnimationTimeBoundaries(Animation animation) { private float[] computeAnimationTimeBoundaries(Animation animation) {
int maxFrame = Integer.MIN_VALUE; int maxFrame = Integer.MIN_VALUE;
float maxTime = Float.MIN_VALUE; float maxTime = -Float.MAX_VALUE;
for (Track track : animation.getTracks()) { for (Track track : animation.getTracks()) {
if (track instanceof BoneTrack) { if (track instanceof BoneTrack) {
maxFrame = Math.max(maxFrame, ((BoneTrack) track).getTranslations().length); maxFrame = Math.max(maxFrame, ((BoneTrack) track).getTranslations().length);

@ -32,4 +32,10 @@ import com.jme3.scene.plugins.blender.file.Structure;
public void apply(int frame) { public void apply(int frame) {
LOGGER.warning("Applying constraints to skeleton is not supported."); LOGGER.warning("Applying constraints to skeleton is not supported.");
} }
@Override
public Long getTargetOMA() {
LOGGER.warning("Constraints for skeleton are not supported.");
return null;
}
} }

@ -22,6 +22,11 @@ import com.jme3.scene.plugins.blender.file.Structure;
if (targetOMA != null) { if (targetOMA != null) {
return blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE) != null; return blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE) != null;
} }
return true; return constraintDefinition == null ? true : constraintDefinition.isTargetRequired();
}
@Override
public Long getTargetOMA() {
return targetOMA;
} }
} }

@ -16,6 +16,8 @@ import com.jme3.math.Vector3f;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class VirtualTrack { /* package */class VirtualTrack {
/** The name of the track (for debugging purposes). */
private String name;
/** The last frame for the track. */ /** The last frame for the track. */
public int maxFrame; public int maxFrame;
/** The max time for the track. */ /** The max time for the track. */
@ -35,7 +37,8 @@ import com.jme3.math.Vector3f;
* @param maxTime * @param maxTime
* the max time for the track * the max time for the track
*/ */
public VirtualTrack(int maxFrame, float maxTime) { public VirtualTrack(String name, int maxFrame, float maxTime) {
this.name = name;
this.maxFrame = maxFrame; this.maxFrame = maxFrame;
this.maxTime = maxTime; this.maxTime = maxTime;
} }
@ -101,7 +104,7 @@ import com.jme3.math.Vector3f;
*/ */
private float[] createTimes() { private float[] createTimes() {
float[] times = new float[maxFrame]; float[] times = new float[maxFrame];
float dT = maxTime / (float) maxFrame; float dT = maxTime / maxFrame;
float t = 0; float t = 0;
for (int i = 0; i < maxFrame; ++i) { for (int i = 0; i < maxFrame; ++i) {
times[i] = t; times[i] = t;
@ -143,4 +146,20 @@ import com.jme3.math.Vector3f;
list.add(element); list.add(element);
} }
} }
@Override
public String toString() {
StringBuilder result = new StringBuilder(2048);
result.append("TRACK: ").append(name).append('\n');
if (translations != null && translations.size() > 0) {
result.append("TRANSLATIONS: ").append(translations.toString()).append('\n');
}
if (rotations != null && rotations.size() > 0) {
result.append("ROTATIONS: ").append(rotations.toString()).append('\n');
}
if (scales != null && scales.size() > 0) {
result.append("SCALES: ").append(scales.toString()).append('\n');
}
return result.toString();
}
} }

@ -28,6 +28,8 @@ public abstract class ConstraintDefinition {
protected Long ownerOMA; protected Long ownerOMA;
/** Stores the OMA addresses of all features whose transform had been altered beside the constraint owner. */ /** Stores the OMA addresses of all features whose transform had been altered beside the constraint owner. */
protected Set<Long> alteredOmas; protected Set<Long> alteredOmas;
/** The variable that determines if the constraint will alter the track in any way. */
protected boolean trackToBeChanged = true;
/** /**
* Loads a constraint definition based on the constraint definition * Loads a constraint definition based on the constraint definition
@ -52,6 +54,20 @@ public abstract class ConstraintDefinition {
this.ownerOMA = ownerOMA; this.ownerOMA = ownerOMA;
} }
/**
* @return determines if the definition of the constraint will change the bone in any way; in most cases
* it is possible to tell that even before the constraint baking simulation is started, so we can discard such bones from constraint
* computing to improve the computation speed and lower the computations complexity
*/
public boolean isTrackToBeChanged() {
return trackToBeChanged;
}
/**
* @return determines if this constraint definition requires a defined target or not
*/
public abstract boolean isTargetRequired();
/** /**
* This method is here because we have no guarantee that the owner is loaded * 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 * when constraint is being created. So use it to get the owner when it is
@ -132,4 +148,9 @@ public abstract class ConstraintDefinition {
* the influence of the constraint (from range <0; 1>) * the influence of the constraint (from range <0; 1>)
*/ */
public abstract void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence); public abstract void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence);
@Override
public String toString() {
return this.getConstraintTypeName();
}
} }

@ -26,18 +26,17 @@ import com.jme3.scene.plugins.blender.file.Structure;
mode = ((Number) constraintData.getFieldValue("mode")).intValue(); mode = ((Number) constraintData.getFieldValue("mode")).intValue();
dist = ((Number) constraintData.getFieldValue("dist")).floatValue(); dist = ((Number) constraintData.getFieldValue("dist")).floatValue();
} }
@Override @Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT)) {
blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT)) {
// distance limit does not work on bones who are connected to their parent // distance limit does not work on bones who are connected to their parent
return; return;
} }
if(influence == 0 || targetTransform == null) { if (influence == 0 || targetTransform == null) {
return ;// no need to do anything return;// no need to do anything
} }
Transform ownerTransform = this.getOwnerTransform(ownerSpace); Transform ownerTransform = this.getOwnerTransform(ownerSpace);
Vector3f v = ownerTransform.getTranslation().subtract(targetTransform.getTranslation()); Vector3f v = ownerTransform.getTranslation().subtract(targetTransform.getTranslation());
@ -73,6 +72,11 @@ import com.jme3.scene.plugins.blender.file.Structure;
this.applyOwnerTransform(ownerTransform, ownerSpace); this.applyOwnerTransform(ownerTransform, ownerSpace);
} }
@Override
public boolean isTargetRequired() {
return true;
}
@Override @Override
public String getConstraintTypeName() { public String getConstraintTypeName() {
return "Limit distance"; return "Limit distance";

@ -29,8 +29,6 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
private int bonesAffected; private int bonesAffected;
/** The total length of the bone chain. Useful for optimisation of computations speed in some cases. */ /** The total length of the bone chain. Useful for optimisation of computations speed in some cases. */
private float chainLength; private float chainLength;
/** Tells if there is anything to compute at all. */
private boolean needToCompute = true;
/** Indicates if the tail of the bone should be used or not. */ /** Indicates if the tail of the bone should be used or not. */
private boolean useTail; private boolean useTail;
/** The amount of iterations of the algorithm. */ /** The amount of iterations of the algorithm. */
@ -43,23 +41,23 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
useTail = (flag & FLAG_USE_TAIL) != 0; useTail = (flag & FLAG_USE_TAIL) != 0;
if ((flag & FLAG_POSITION) == 0) { if ((flag & FLAG_POSITION) == 0) {
needToCompute = false; trackToBeChanged = false;
} }
if (needToCompute) { if (trackToBeChanged) {
alteredOmas = new HashSet<Long>(); alteredOmas = new HashSet<Long>();
} }
} }
@Override @Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (influence == 0 || !needToCompute || targetTransform == null) { if (influence == 0 || !trackToBeChanged || targetTransform == null) {
return;// no need to do anything return;// no need to do anything
} }
Quaternion q = new Quaternion(); Quaternion q = new Quaternion();
Vector3f t = targetTransform.getTranslation(); Vector3f t = targetTransform.getTranslation();
List<BoneContext> bones = this.loadBones(); List<BoneContext> bones = this.loadBones();
if(bones.size() == 0) { if (bones.size() == 0) {
return;// no need to do anything return;// no need to do anything
} }
float distanceFromTarget = Float.MAX_VALUE; float distanceFromTarget = Float.MAX_VALUE;
@ -186,4 +184,20 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
} }
return bones; return bones;
} }
@Override
public boolean isTrackToBeChanged() {
if (trackToBeChanged) {
// need to check the bone structure too (when constructor was called not all of the bones might have been loaded yet)
// that is why it is also checked here
List<BoneContext> bones = this.loadBones();
trackToBeChanged = bones.size() > 0;
}
return trackToBeChanged;
}
@Override
public boolean isTargetRequired() {
return true;
}
} }

@ -34,27 +34,30 @@ import com.jme3.scene.plugins.blender.file.Structure;
int invZ = flag & LOCLIKE_Z_INVERT; int invZ = flag & LOCLIKE_Z_INVERT;
// clear the other flags to swap them // clear the other flags to swap them
flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET; flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET;
flag |= y << 1; flag |= y << 1;
flag |= invY << 1; flag |= invY << 1;
flag |= z >> 1; flag |= z >> 1;
flag |= invZ >> 1; flag |= invZ >> 1;
trackToBeChanged = (flag & LOCLIKE_X) != 0 || (flag & LOCLIKE_Y) != 0 || (flag & LOCLIKE_Z) != 0;
} }
} }
@Override
public boolean isTrackToBeChanged() {
// location copy does not work on bones who are connected to their parent
return trackToBeChanged && !(this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT));
}
@Override @Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && if (influence == 0 || targetTransform == null || !this.isTrackToBeChanged()) {
blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT)) {
// location copy does not work on bones who are connected to their parent
return; return;
} }
if(influence == 0 || targetTransform == null) {
return ;// no need to do anything
}
Transform ownerTransform = this.getOwnerTransform(ownerSpace); Transform ownerTransform = this.getOwnerTransform(ownerSpace);
Vector3f ownerLocation = ownerTransform.getTranslation(); Vector3f ownerLocation = ownerTransform.getTranslation();
Vector3f targetLocation = targetTransform.getTranslation(); Vector3f targetLocation = targetTransform.getTranslation();
@ -88,7 +91,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence); startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence);
ownerLocation.addLocal(startLocation); ownerLocation.addLocal(startLocation);
} }
this.applyOwnerTransform(ownerTransform, ownerSpace); this.applyOwnerTransform(ownerTransform, ownerSpace);
} }
@ -96,4 +99,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
public String getConstraintTypeName() { public String getConstraintTypeName() {
return "Copy location"; return "Copy location";
} }
@Override
public boolean isTargetRequired() {
return true;
}
} }

@ -52,18 +52,24 @@ import com.jme3.scene.plugins.blender.file.Structure;
limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
} }
trackToBeChanged = (flag & (LIMIT_XMIN | LIMIT_XMAX | LIMIT_YMIN | LIMIT_YMAX | LIMIT_ZMIN | LIMIT_ZMAX)) != 0;
} }
@Override
public boolean isTrackToBeChanged() {
// location limit does not work on bones who are connected to their parent
return trackToBeChanged && !(this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT));
}
@Override @Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && if (influence == 0 || !this.isTrackToBeChanged()) {
blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT)) { return;// no need to do anything
// location limit does not work on bones who are connected to their parent
return;
} }
Transform ownerTransform = this.getOwnerTransform(ownerSpace); Transform ownerTransform = this.getOwnerTransform(ownerSpace);
Vector3f translation = ownerTransform.getTranslation(); Vector3f translation = ownerTransform.getTranslation();
if ((flag & LIMIT_XMIN) != 0 && translation.x < limits[0][0]) { if ((flag & LIMIT_XMIN) != 0 && translation.x < limits[0][0]) {
@ -84,7 +90,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
if ((flag & LIMIT_ZMAX) != 0 && translation.z > limits[2][1]) { if ((flag & LIMIT_ZMAX) != 0 && translation.z > limits[2][1]) {
translation.z -= (translation.z - limits[2][1]) * influence; translation.z -= (translation.z - limits[2][1]) * influence;
} }
this.applyOwnerTransform(ownerTransform, ownerSpace); this.applyOwnerTransform(ownerTransform, ownerSpace);
} }
@ -92,4 +98,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
public String getConstraintTypeName() { public String getConstraintTypeName() {
return "Limit location"; return "Limit location";
} }
@Override
public boolean isTargetRequired() {
return false;
}
} }

@ -6,6 +6,11 @@ import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
/**
* This class represents 'Maintain volume' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas)
*/
public class ConstraintDefinitionMaintainVolume extends ConstraintDefinition { public class ConstraintDefinitionMaintainVolume extends ConstraintDefinition {
private static final int FLAG_MASK_X = 0; private static final int FLAG_MASK_X = 0;
private static final int FLAG_MASK_Y = 1; private static final int FLAG_MASK_Y = 1;
@ -16,11 +21,12 @@ public class ConstraintDefinitionMaintainVolume extends ConstraintDefinition {
public ConstraintDefinitionMaintainVolume(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { public ConstraintDefinitionMaintainVolume(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, ownerOMA, blenderContext); super(constraintData, ownerOMA, blenderContext);
volume = (float) Math.sqrt(((Number) constraintData.getFieldValue("volume")).floatValue()); volume = (float) Math.sqrt(((Number) constraintData.getFieldValue("volume")).floatValue());
trackToBeChanged = volume != 1 && (flag & (FLAG_MASK_X | FLAG_MASK_Y | FLAG_MASK_Z)) != 0;
} }
@Override @Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (volume != 1 && influence > 0) { if (trackToBeChanged && influence > 0) {
// the maintain volume constraint is applied directly to object's scale, so no need to do it again // the maintain volume constraint is applied directly to object's scale, so no need to do it again
// but in case of bones we need to make computations // but in case of bones we need to make computations
if (this.getOwner() instanceof Bone) { if (this.getOwner() instanceof Bone) {
@ -47,4 +53,9 @@ public class ConstraintDefinitionMaintainVolume extends ConstraintDefinition {
public String getConstraintTypeName() { public String getConstraintTypeName() {
return "Maintain volume"; return "Maintain volume";
} }
@Override
public boolean isTargetRequired() {
return false;
}
} }

@ -14,6 +14,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
public ConstraintDefinitionNull(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { public ConstraintDefinitionNull(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, ownerOMA, blenderContext); super(constraintData, ownerOMA, blenderContext);
trackToBeChanged = false;
} }
@Override @Override
@ -25,4 +26,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
public String getConstraintTypeName() { public String getConstraintTypeName() {
return "Null"; return "Null";
} }
@Override
public boolean isTargetRequired() {
return false;
}
} }

@ -25,15 +25,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
public ConstraintDefinitionRotLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { public ConstraintDefinitionRotLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, ownerOMA, blenderContext); super(constraintData, ownerOMA, blenderContext);
trackToBeChanged = (flag & (ROTLIKE_X | ROTLIKE_Y | ROTLIKE_Z)) != 0;
} }
@Override @Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if(influence == 0 || targetTransform == null) { if (influence == 0 || targetTransform == null || !trackToBeChanged) {
return ;// no need to do anything return;// no need to do anything
} }
Transform ownerTransform = this.getOwnerTransform(ownerSpace); Transform ownerTransform = this.getOwnerTransform(ownerSpace);
Quaternion ownerRotation = ownerTransform.getRotation(); Quaternion ownerRotation = ownerTransform.getRotation();
ownerAngles = ownerRotation.toAngles(ownerAngles); ownerAngles = ownerRotation.toAngles(ownerAngles);
targetAngles = targetTransform.getRotation().toAngles(targetAngles); targetAngles = targetTransform.getRotation().toAngles(targetAngles);
@ -70,7 +71,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
// ownerLocation.addLocal(startLocation); // ownerLocation.addLocal(startLocation);
// TODO // TODO
} }
this.applyOwnerTransform(ownerTransform, ownerSpace); this.applyOwnerTransform(ownerTransform, ownerSpace);
} }
@ -78,4 +79,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
public String getConstraintTypeName() { public String getConstraintTypeName() {
return "Copy rotation"; return "Copy rotation";
} }
@Override
public boolean isTargetRequired() {
return true;
}
} }

@ -65,12 +65,17 @@ import com.jme3.scene.plugins.blender.file.Structure;
* if(limits[i][0] > limits[i][1]) { float temp = limits[i][0]; * if(limits[i][0] > limits[i][1]) { float temp = limits[i][0];
* limits[i][0] = limits[i][1]; limits[i][1] = temp; } } * limits[i][0] = limits[i][1]; limits[i][1] = temp; } }
*/ */
trackToBeChanged = (flag & (LIMIT_XROT | LIMIT_YROT | LIMIT_ZROT)) != 0;
} }
@Override @Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (influence == 0 || !trackToBeChanged) {
return;
}
Transform ownerTransform = this.getOwnerTransform(ownerSpace); Transform ownerTransform = this.getOwnerTransform(ownerSpace);
ownerTransform.getRotation().toAngles(angles); ownerTransform.getRotation().toAngles(angles);
// make sure that the rotations are always in range [0, 2PI) // make sure that the rotations are always in range [0, 2PI)
// TODO: same comment as in constructor // TODO: same comment as in constructor
@ -108,7 +113,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
angles[2] -= difference; angles[2] -= difference;
} }
ownerTransform.getRotation().fromAngles(angles); ownerTransform.getRotation().fromAngles(angles);
this.applyOwnerTransform(ownerTransform, ownerSpace); this.applyOwnerTransform(ownerTransform, ownerSpace);
} }
@ -116,4 +121,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
public String getConstraintTypeName() { public String getConstraintTypeName() {
return "Limit rotation"; return "Limit rotation";
} }
@Override
public boolean isTargetRequired() {
return false;
}
} }

@ -27,16 +27,18 @@ import com.jme3.scene.plugins.blender.file.Structure;
// them // them
flag |= y << 1; flag |= y << 1;
flag |= z >> 1; flag |= z >> 1;
trackToBeChanged = (flag & (SIZELIKE_X | SIZELIKE_Y | SIZELIKE_Z)) != 0;
} }
} }
@Override @Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if(influence == 0 || targetTransform == null) { if (influence == 0 || targetTransform == null || !trackToBeChanged) {
return;// no need to do anything return;// no need to do anything
} }
Transform ownerTransform = this.getOwnerTransform(ownerSpace); Transform ownerTransform = this.getOwnerTransform(ownerSpace);
Vector3f ownerScale = ownerTransform.getScale(); Vector3f ownerScale = ownerTransform.getScale();
Vector3f targetScale = targetTransform.getScale(); Vector3f targetScale = targetTransform.getScale();
@ -56,7 +58,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
ownerScale.z = targetScale.z * influence + (1.0f - influence) * ownerScale.z; ownerScale.z = targetScale.z * influence + (1.0f - influence) * ownerScale.z;
} }
ownerScale.addLocal(offset); ownerScale.addLocal(offset);
this.applyOwnerTransform(ownerTransform, ownerSpace); this.applyOwnerTransform(ownerTransform, ownerSpace);
} }
@ -64,4 +66,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
public String getConstraintTypeName() { public String getConstraintTypeName() {
return "Copy scale"; return "Copy scale";
} }
@Override
public boolean isTargetRequired() {
return true;
}
} }

@ -50,12 +50,17 @@ import com.jme3.scene.plugins.blender.file.Structure;
limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
} }
trackToBeChanged = (flag & (LIMIT_XMIN | LIMIT_XMAX | LIMIT_YMIN | LIMIT_YMAX | LIMIT_ZMIN | LIMIT_ZMAX)) != 0;
} }
@Override @Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (influence == 0 || !trackToBeChanged) {
return;
}
Transform ownerTransform = this.getOwnerTransform(ownerSpace); Transform ownerTransform = this.getOwnerTransform(ownerSpace);
Vector3f scale = ownerTransform.getScale(); Vector3f scale = ownerTransform.getScale();
if ((flag & LIMIT_XMIN) != 0 && scale.x < limits[0][0]) { if ((flag & LIMIT_XMIN) != 0 && scale.x < limits[0][0]) {
scale.x -= (scale.x - limits[0][0]) * influence; scale.x -= (scale.x - limits[0][0]) * influence;
@ -75,7 +80,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
if ((flag & LIMIT_ZMAX) != 0 && scale.z > limits[2][1]) { if ((flag & LIMIT_ZMAX) != 0 && scale.z > limits[2][1]) {
scale.z -= (scale.z - limits[2][1]) * influence; scale.z -= (scale.z - limits[2][1]) * influence;
} }
this.applyOwnerTransform(ownerTransform, ownerSpace); this.applyOwnerTransform(ownerTransform, ownerSpace);
} }
@ -83,4 +88,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
public String getConstraintTypeName() { public String getConstraintTypeName() {
return "Limit scale"; return "Limit scale";
} }
@Override
public boolean isTargetRequired() {
return false;
}
} }

@ -35,8 +35,8 @@ public class ConstraintDefinitionTransLike extends ConstraintDefinition {
@Override @Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if(influence == 0 || targetTransform == null) { if (influence == 0 || targetTransform == null) {
return ;// no need to do anything return;// no need to do anything
} }
Object target = this.getTarget();// Bone or Node Object target = this.getTarget();// Bone or Node
Object owner = this.getOwner();// Bone or Node Object owner = this.getOwner();// Bone or Node
@ -73,4 +73,9 @@ public class ConstraintDefinitionTransLike extends ConstraintDefinition {
public String getConstraintTypeName() { public String getConstraintTypeName() {
return "Copy transforms"; return "Copy transforms";
} }
@Override
public boolean isTargetRequired() {
return true;
}
} }

@ -16,6 +16,7 @@ import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
public UnsupportedConstraintDefinition(String typeName) { public UnsupportedConstraintDefinition(String typeName) {
super(null, null, null); super(null, null, null);
this.typeName = typeName; this.typeName = typeName;
trackToBeChanged = false;
} }
@Override @Override
@ -31,4 +32,9 @@ import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
public String getConstraintTypeName() { public String getConstraintTypeName() {
return typeName; return typeName;
} }
@Override
public boolean isTargetRequired() {
return false;
}
} }