|
|
@ -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,19 +275,16 @@ 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) { |
|
|
|
|
|
|
|
// ommit the 0 - indexed root bone as it is the bone added by importer
|
|
|
|
|
|
|
|
this.applyConstraints(rootBone, alteredOmas, frame); |
|
|
|
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 ...
|
|
|
|
for (Long boneOMA : alteredOmas) { |
|
|
|
for (Long boneOMA : alteredOmas) { |
|
|
|
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(); |
|
|
@ -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); |
|
|
|