diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java index 3613fc85d..08a3e644f 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java @@ -34,6 +34,8 @@ public class BoneContext { private BlenderContext blenderContext; /** The OMA of the bone's armature object. */ private Long armatureObjectOMA; + /** The OMA of the model that owns the bone's skeleton. */ + private Long skeletonOwnerOma; /** The structure of the bone. */ private Structure boneStructure; /** Bone's name. */ @@ -128,6 +130,7 @@ public class BoneContext { * @return newly created bone */ public Bone buildBone(List bones, Long skeletonOwnerOma, BlenderContext blenderContext) { + this.skeletonOwnerOma = skeletonOwnerOma; Long boneOMA = boneStructure.getOldMemoryAddress(); bone = new Bone(boneName); bones.add(bone); @@ -183,6 +186,13 @@ public class BoneContext { public Long getArmatureObjectOMA() { return armatureObjectOMA; } + + /** + * @return the OMA of the model that owns the bone's skeleton + */ + public Long getSkeletonOwnerOma() { + return skeletonOwnerOma; + } /** * @return the skeleton the bone of this context belongs to @@ -200,4 +210,9 @@ public class BoneContext { private boolean is(int flagMask) { return (flag & flagMask) != 0; } + + @Override + public String toString() { + return "BoneContext: " + bone.getName(); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java index f2a694a0e..3bdbef447 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java @@ -3,7 +3,6 @@ package com.jme3.scene.plugins.blender.constraints; import java.util.logging.Level; import java.util.logging.Logger; -import com.jme3.math.Transform; import com.jme3.scene.Spatial; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; @@ -21,8 +20,6 @@ import com.jme3.scene.plugins.blender.file.Structure; /* package */class BoneConstraint extends Constraint { private static final Logger LOGGER = Logger.getLogger(BoneConstraint.class.getName()); - protected boolean isNodeTarget; - /** * The bone constraint constructor. * @@ -45,14 +42,14 @@ import com.jme3.scene.plugins.blender.file.Structure; public boolean validate() { if (targetOMA != null) { Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE); - if(nodeTarget == null) { + if (nodeTarget == null) { LOGGER.log(Level.WARNING, "Cannot find target for constraint: {0}.", name); return false; } // the second part of the if expression verifies if the found node // (if any) is an armature node if (blenderContext.getMarkerValue(ArmatureHelper.ARMATURE_NODE_MARKER, nodeTarget) != null) { - if(subtargetName.trim().isEmpty()) { + if (subtargetName.trim().isEmpty()) { LOGGER.log(Level.WARNING, "No bone target specified for constraint: {0}.", name); return false; } @@ -63,28 +60,8 @@ import com.jme3.scene.plugins.blender.file.Structure; LOGGER.log(Level.WARNING, "Bone constraint {0} must target bone in the its own skeleton! Targeting bone in another skeleton is not supported!", name); return false; } - } else { - isNodeTarget = true; } } return true; } - - @Override - public void apply(int frame) { - BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); - Transform ownerTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace); - if (targetOMA != null) { - if (isNodeTarget) { - Transform targetTransform = targetOMA != null ? constraintHelper.getTransform(targetOMA, subtargetName, targetSpace) : null; - constraintDefinition.bake(ownerTransform, targetTransform, this.ipo.calculateValue(frame)); - } else { - Transform targetTransform = constraintHelper.getTransform(targetOMA, subtargetName, targetSpace); - constraintDefinition.bake(ownerTransform, targetTransform, this.ipo.calculateValue(frame)); - } - } else { - constraintDefinition.bake(ownerTransform, null, this.ipo.calculateValue(frame)); - } - constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace, ownerTransform); - } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java index eee6572a2..f4fe3452e 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java @@ -1,8 +1,10 @@ package com.jme3.scene.plugins.blender.constraints; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import com.jme3.math.Transform; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.animations.Ipo; import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; @@ -58,15 +60,15 @@ public abstract class Constraint { */ public Constraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { this.blenderContext = blenderContext; - this.name = constraintStructure.getFieldValue("name").toString(); + name = constraintStructure.getFieldValue("name").toString(); Pointer pData = (Pointer) constraintStructure.getFieldValue("data"); if (pData.isNotNull()) { Structure data = pData.fetchData(blenderContext.getInputStream()).get(0); constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, ownerOMA, blenderContext); Pointer pTar = (Pointer) data.getFieldValue("tar"); if (pTar != null && pTar.isNotNull()) { - this.targetOMA = pTar.getOldMemoryAddress(); - this.targetSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("tarspace")).byteValue()); + targetOMA = pTar.getOldMemoryAddress(); + targetSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("tarspace")).byteValue()); Object subtargetValue = data.getFieldValue("subtarget"); if (subtargetValue != null) {// not all constraint data have the // subtarget field @@ -77,10 +79,10 @@ public abstract class Constraint { // Null constraint has no data, so create it here constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, null, blenderContext); } - this.ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue()); - this.ipo = influenceIpo; + ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue()); + ipo = influenceIpo; this.ownerOMA = ownerOMA; - this.constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + constraintHelper = blenderContext.getHelper(ConstraintHelper.class); LOGGER.log(Level.INFO, "Created constraint: {0} with definition: {1}", new Object[] { name, constraintDefinition }); } @@ -100,6 +102,13 @@ public abstract class Constraint { return constraintDefinition.getConstraintTypeName(); } + /** + * @return the OMAs of the features whose transform had been altered beside the constraint owner + */ + public Set getAlteredOmas() { + return constraintDefinition.getAlteredOmas(); + } + /** * Performs validation before baking. Checks factors that can prevent * constraint from baking that could not be checked during constraint @@ -107,14 +116,22 @@ public abstract class Constraint { */ public abstract boolean validate(); - public abstract void apply(int frame); + /** + * Applies the constraint to owner (and in some cases can alter other bones of the skeleton). + * @param frame + * the frame of the animation + */ + public void apply(int frame) { + Transform targetTransform = targetOMA != null ? constraintHelper.getTransform(targetOMA, subtargetName, targetSpace) : null; + constraintDefinition.bake(ownerSpace, targetSpace, targetTransform, ipo.calculateValue(frame)); + } @Override public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((ownerOMA == null) ? 0 : ownerOMA.hashCode()); + result = prime * result + (name == null ? 0 : name.hashCode()); + result = prime * result + (ownerOMA == null ? 0 : ownerOMA.hashCode()); return result; } @@ -126,7 +143,7 @@ public abstract class Constraint { if (obj == null) { return false; } - if (getClass() != obj.getClass()) { + if (this.getClass() != obj.getClass()) { return false; } Constraint other = (Constraint) obj; diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java index 241f89c2f..a49d02907 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java @@ -228,11 +228,14 @@ public class ConstraintHelper extends AbstractBlenderHelper { switch (space) { case CONSTRAINT_SPACE_WORLD: - return new Transform(bone.getModelSpacePosition(), bone.getModelSpaceRotation(), bone.getModelSpaceScale()); + Spatial model = (Spatial) blenderContext.getLoadedFeature(targetBoneContext.getSkeletonOwnerOma(), LoadedFeatureDataType.LOADED_FEATURE); + Transform worldTransform = new Transform(bone.getModelSpacePosition(), bone.getModelSpaceRotation(), bone.getModelSpaceScale()); + worldTransform.getTranslation().addLocal(model.getWorldTranslation()); + worldTransform.getRotation().multLocal(model.getWorldRotation()); + worldTransform.getScale().multLocal(model.getWorldScale()); + return worldTransform; case CONSTRAINT_SPACE_LOCAL: - Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation()); - localTransform.setScale(bone.getLocalScale()); - return localTransform; + return new Transform(bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale()); case CONSTRAINT_SPACE_POSE: Node nodeWithAnimationControl = blenderContext.getControlledNode(targetBoneContext.getSkeleton()); Matrix4f m = this.toMatrix(nodeWithAnimationControl.getWorldTransform()); @@ -313,15 +316,16 @@ public class ConstraintHelper extends AbstractBlenderHelper { bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale()); break; case CONSTRAINT_SPACE_WORLD: - Matrix4f boneMatrix = this.toMatrix(transform); + Matrix4f boneMatrixInWorldSpace = this.toMatrix(transform); + Matrix4f invertedModelMatrix = this.toMatrix(this.getTransform(targetBoneContext.getSkeletonOwnerOma(), null, Space.CONSTRAINT_SPACE_WORLD)).invertLocal(); + Matrix4f boneMatrixInModelSpace = invertedModelMatrix.mult(boneMatrixInWorldSpace); Bone parent = bone.getParent(); if (parent != null) { - Matrix4f invertedParentWorldMatrix = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale()).invertLocal(); - boneMatrix = invertedParentWorldMatrix.multLocal(boneMatrix); - } + Matrix4f invertedParentMatrixInModelSpace = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale()).invertLocal(); - boneMatrix = invertedNodeMatrix.multLocal(boneMatrix); - bone.setBindTransforms(boneMatrix.toTranslationVector(), boneMatrix.toRotationQuat(), boneMatrix.toScaleVector()); + boneMatrixInModelSpace = invertedParentMatrixInModelSpace.mult(boneMatrixInModelSpace); + } + bone.setBindTransforms(boneMatrixInModelSpace.toTranslationVector(), boneMatrixInModelSpace.toRotationQuat(), boneMatrixInModelSpace.toScaleVector()); break; case CONSTRAINT_SPACE_POSE: Matrix4f armatureWorldMatrix = this.toMatrix(feature.getWorldTransform()); @@ -391,11 +395,10 @@ public class ConstraintHelper extends AbstractBlenderHelper { * @return 4x4 matrix that represents the given transform */ public Matrix4f toMatrix(Transform transform) { - Matrix4f result = Matrix4f.IDENTITY; if (transform != null) { - result = this.toMatrix(transform.getTranslation(), transform.getRotation(), transform.getScale()); + return this.toMatrix(transform.getTranslation(), transform.getRotation(), transform.getScale()); } - return result; + return Matrix4f.IDENTITY.clone(); } /** diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SimulationNode.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SimulationNode.java index 71696beaa..ce72dda87 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SimulationNode.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SimulationNode.java @@ -2,9 +2,11 @@ package com.jme3.scene.plugins.blender.constraints; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -38,31 +40,33 @@ import com.jme3.util.TempVars; * @author Marcin Roguski (Kaelthas) */ public class SimulationNode { - private static final Logger LOGGER = Logger.getLogger(SimulationNode.class.getName()); + private static final Logger LOGGER = Logger.getLogger(SimulationNode.class.getName()); + /** The blender context. */ + private BlenderContext blenderContext; /** The name of the node (for debugging purposes). */ - private String name; + private String name; /** A list of children for the node (either bones or child spatials). */ - private List children = new ArrayList(); + private List children = new ArrayList(); /** A list of constraints that the current node has. */ - private List constraints; + private List constraints; /** A list of node's animations. */ - private List animations; + private List animations; /** The nodes spatial (if null then the boneContext should be set). */ - private Spatial spatial; + private Spatial spatial; /** The skeleton of the bone (not null if the node simulated the bone). */ - private Skeleton skeleton; + private Skeleton skeleton; /** Animation controller for the node's feature. */ - private AnimControl animControl; + private AnimControl animControl; /** * The star transform of a spatial. Needed to properly reset the spatial to * its start position. */ - private Transform spatialStartTransform; + private Transform spatialStartTransform; /** Star transformations for bones. Needed to properly reset the bones. */ - private Map boneStartTransforms; + private Map boneStartTransforms; /** * Builds the nodes tree for the given feature. The feature (bone or @@ -89,12 +93,13 @@ public class SimulationNode { * indicates if the feature is a root bone or root spatial or not */ private SimulationNode(Long featureOMA, BlenderContext blenderContext, boolean rootNode) { + this.blenderContext = blenderContext; Node spatial = (Node) blenderContext.getLoadedFeature(featureOMA, LoadedFeatureDataType.LOADED_FEATURE); if (blenderContext.getMarkerValue(ArmatureHelper.ARMATURE_NODE_MARKER, spatial) != null) { - this.skeleton = blenderContext.getSkeleton(featureOMA); + skeleton = blenderContext.getSkeleton(featureOMA); Node nodeWithAnimationControl = blenderContext.getControlledNode(skeleton); - this.animControl = nodeWithAnimationControl.getControl(AnimControl.class); + animControl = nodeWithAnimationControl.getControl(AnimControl.class); boneStartTransforms = new HashMap(); for (int i = 0; i < skeleton.getBoneCount(); ++i) { @@ -106,10 +111,10 @@ public class SimulationNode { throw new IllegalStateException("Given spatial must be a root node!"); } this.spatial = spatial; - this.spatialStartTransform = spatial.getLocalTransform().clone(); + spatialStartTransform = spatial.getLocalTransform().clone(); } - this.name = '>' + spatial.getName() + '<'; + name = '>' + spatial.getName() + '<'; constraints = this.findConstraints(featureOMA, blenderContext); if (constraints == null) { @@ -143,14 +148,14 @@ public class SimulationNode { LOGGER.info("Removing invalid constraints."); List validConstraints = new ArrayList(constraints.size()); - for (Constraint constraint : this.constraints) { + for (Constraint constraint : 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; + constraints = validConstraints; } /** @@ -246,6 +251,7 @@ public class SimulationNode { private void simulateSkeleton() { if (constraints != null && constraints.size() > 0) { boolean applyStaticConstraints = true; + Set alteredOmas = new HashSet(); if (animations != null) { TempVars vars = TempVars.get(); @@ -256,49 +262,45 @@ public class SimulationNode { float maxTime = animationTimeBoundaries[1]; Map tracks = new HashMap(); - Map previousTransforms = new HashMap(); + Map previousTransforms = this.getInitialTransforms(); for (int frame = 0; frame < maxFrame; ++frame) { // this MUST be done here, otherwise setting next frame of animation will // lead to possible errors this.reset(); - + // 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); + if (constraint.getAlteredOmas() != null) { + alteredOmas.addAll(constraint.getAlteredOmas()); + } } + // ... add virtual tracks if neccessary, for bones that were altered but had no tracks before ... + for (Long boneOMA : alteredOmas) { + BoneContext boneContext = blenderContext.getBoneContext(boneOMA); + int boneIndex = skeleton.getBoneIndex(boneContext.getBone()); + if (!tracks.containsKey(boneIndex)) { + tracks.put(boneIndex, new VirtualTrack(maxFrame, maxTime)); + } + } + alteredOmas.clear(); + // ... and fill in another frame in the result track - for (Track track : animation.getTracks()) { - Integer boneIndex = ((BoneTrack) track).getTargetBoneIndex(); + for (Entry trackEntry : tracks.entrySet()) { + Integer boneIndex = trackEntry.getKey(); Bone bone = skeleton.getBone(boneIndex); - // take the initial transform of a bone + // take the initial transform of a bone and its virtual track Transform previousTransform = previousTransforms.get(boneIndex); - - VirtualTrack vTrack = tracks.get(boneIndex); - if (vTrack == null) { - vTrack = new VirtualTrack(maxFrame, maxTime); - tracks.put(boneIndex, vTrack); - } + VirtualTrack vTrack = trackEntry.getValue(); Vector3f bonePositionDifference = bone.getLocalPosition().subtract(previousTransform.getTranslation()); Quaternion boneRotationDifference = bone.getLocalRotation().mult(previousTransform.getRotation().inverse()).normalizeLocal(); @@ -319,13 +321,18 @@ public class SimulationNode { for (Entry trackEntry : tracks.entrySet()) { Track newTrack = trackEntry.getValue().getAsBoneTrack(trackEntry.getKey()); if (newTrack != null) { + boolean trackReplaced = false; for (Track track : animation.getTracks()) { if (((BoneTrack) track).getTargetBoneIndex() == trackEntry.getKey().intValue()) { animation.removeTrack(track); animation.addTrack(newTrack); + trackReplaced = true; break; } } + if (!trackReplaced) { + animation.addTrack(newTrack); + } } applyStaticConstraints = false; } @@ -341,6 +348,7 @@ public class SimulationNode { for (Constraint constraint : constraints) { constraint.apply(0); } + skeleton.updateWorldVectors(); } } } @@ -355,7 +363,6 @@ public class SimulationNode { } else { this.simulateSkeleton(); } - this.reset(); } /** @@ -406,6 +413,19 @@ public class SimulationNode { return result.size() > 0 ? result : null; } + /** + * Creates the initial transforms for all bones in the skelketon. + * @return the map where the key is the bone index and the value us the bone's initial transformation + */ + private Map getInitialTransforms() { + Map result = new HashMap(); + for (int i = 0; i < skeleton.getBoneCount(); ++i) { + Bone bone = skeleton.getBone(i); + result.put(i, new Transform(bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale())); + } + return result; + } + @Override public String toString() { return name; diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java index e35ae9785..887986c4a 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java @@ -1,6 +1,5 @@ package com.jme3.scene.plugins.blender.constraints; -import com.jme3.math.Transform; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; import com.jme3.scene.plugins.blender.animations.Ipo; @@ -25,12 +24,4 @@ import com.jme3.scene.plugins.blender.file.Structure; } return true; } - - @Override - public void apply(int frame) { - Transform ownerTransform = constraintHelper.getTransform(ownerOMA, null, ownerSpace); - Transform targetTransform = targetOMA != null ? constraintHelper.getTransform(targetOMA, subtargetName, targetSpace) : null; - constraintDefinition.bake(ownerTransform, targetTransform, this.ipo.calculateValue(frame)); - constraintHelper.applyTransform(ownerOMA, subtargetName, ownerSpace, ownerTransform); - } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java index fa1b651cd..75021b75b 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java @@ -1,8 +1,12 @@ package com.jme3.scene.plugins.blender.constraints.definitions; +import java.util.Set; + import com.jme3.math.Transform; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; import com.jme3.scene.plugins.blender.file.Structure; /** @@ -11,14 +15,17 @@ import com.jme3.scene.plugins.blender.file.Structure; * @author Marcin Roguski (Kaelthas) */ public abstract class ConstraintDefinition { + protected ConstraintHelper constraintHelper; /** Constraints flag. Used to load user's options applied to the constraint. */ - protected int flag; + protected int flag; /** The constraint's owner. Loaded during runtime. */ - private Object owner; + private Object owner; /** The blender context. */ - private BlenderContext blenderContext; + protected BlenderContext blenderContext; /** The constraint's owner OMA. */ - private Long ownerOMA; + protected Long ownerOMA; + /** Stores the OMA addresses of all features whose transform had been altered beside the constraint owner. */ + protected Set alteredOmas; /** * Loads a constraint definition based on the constraint definition @@ -39,6 +46,7 @@ public abstract class ConstraintDefinition { } } this.blenderContext = blenderContext; + constraintHelper = blenderContext.getHelper(ConstraintHelper.class); this.ownerOMA = ownerOMA; } @@ -67,6 +75,13 @@ public abstract class ConstraintDefinition { return true; } + /** + * @return a list of all OMAs of the features that the constraint had altered beside its owner + */ + public Set getAlteredOmas() { + return alteredOmas; + } + /** * @return the type name of the constraint */ @@ -75,12 +90,14 @@ public abstract class ConstraintDefinition { /** * Bakes the constraint for the current feature (bone or spatial) position. * - * @param ownerTransform - * the input transform (here the result is stored) + * @param ownerSpace + * the space where owner transform will be evaluated in + * @param targetSpace + * the space where target transform will be evaluated in * @param targetTransform * the target transform used by some of the constraints * @param influence * the influence of the constraint (from range <0; 1>) */ - public abstract void bake(Transform ownerTransform, Transform targetTransform, float influence); + public abstract void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence); } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java index 986e589c9..54aa2bcb5 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java @@ -4,6 +4,8 @@ import com.jme3.animation.Bone; import com.jme3.math.Transform; import com.jme3.math.Vector3f; import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; import com.jme3.scene.plugins.blender.file.Structure; /** @@ -24,17 +26,19 @@ import com.jme3.scene.plugins.blender.file.Structure; mode = ((Number) constraintData.getFieldValue("mode")).intValue(); dist = ((Number) constraintData.getFieldValue("dist")).floatValue(); } - + @Override - public void bake(Transform ownerTransform, 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) { // distance limit does not work on bones who have parent return; } + + BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); + Transform ownerTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace); Vector3f v = ownerTransform.getTranslation().subtract(targetTransform.getTranslation()); float currentDistance = v.length(); - switch (mode) { case LIMITDIST_INSIDE: if (currentDistance >= dist) { @@ -62,6 +66,8 @@ import com.jme3.scene.plugins.blender.file.Structure; default: throw new IllegalStateException("Unknown distance limit constraint mode: " + mode); } + + constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace, ownerTransform); } @Override diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java index 552a8b014..080881ea1 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java @@ -50,6 +50,7 @@ public class ConstraintDefinitionFactory { CONSTRAINT_CLASSES.put("bRotLimitConstraint", ConstraintDefinitionRotLimit.class); CONSTRAINT_CLASSES.put("bSizeLikeConstraint", ConstraintDefinitionSizeLike.class); CONSTRAINT_CLASSES.put("bSizeLimitConstraint", ConstraintDefinitionSizeLimit.class); + CONSTRAINT_CLASSES.put("bKinematicConstraint", ConstraintDefinitionIK.class); } private static final Map UNSUPPORTED_CONSTRAINTS = new HashMap(); @@ -58,7 +59,6 @@ public class ConstraintDefinitionFactory { UNSUPPORTED_CONSTRAINTS.put("bChildOfConstraint", "Child of"); UNSUPPORTED_CONSTRAINTS.put("bClampToConstraint", "Clamp to"); UNSUPPORTED_CONSTRAINTS.put("bFollowPathConstraint", "Follow path"); - UNSUPPORTED_CONSTRAINTS.put("bKinematicConstraint", "Inverse kinematic"); UNSUPPORTED_CONSTRAINTS.put("bLockTrackConstraint", "Lock track"); UNSUPPORTED_CONSTRAINTS.put("bMinMaxConstraint", "Min max"); UNSUPPORTED_CONSTRAINTS.put("bPythonConstraint", "Python/Script"); diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java new file mode 100644 index 000000000..0887426b3 --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java @@ -0,0 +1,115 @@ +package com.jme3.scene.plugins.blender.constraints.definitions; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import com.jme3.animation.Bone; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; +import com.jme3.scene.plugins.blender.file.Structure; + +public class ConstraintDefinitionIK extends ConstraintDefinition { + + private static final int FLAG_POSITION = 0x20; + + /** The number of affected bones. Zero means that all parent bones of the current bone should take part in baking. */ + private int bonesAffected; + private float chainLength; + private BoneContext[] bones; + private boolean needToCompute = true; + + public ConstraintDefinitionIK(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); + bonesAffected = ((Number) constraintData.getFieldValue("rootbone")).intValue(); + + if ((flag & FLAG_POSITION) == 0) { + needToCompute = false; + } + + if (needToCompute) { + alteredOmas = new HashSet(); + } + } + + @Override + public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { + if (needToCompute) { + ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + BoneContext[] boneContexts = this.getBones(); + float b = chainLength; + Quaternion boneWorldRotation = new Quaternion(); + + for (int i = 0; i < boneContexts.length; ++i) { + Bone bone = boneContexts[i].getBone(); + + bone.updateWorldVectors(); + Transform boneWorldTransform = constraintHelper.getTransform(boneContexts[i].getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD); + + Vector3f head = boneWorldTransform.getTranslation(); + Vector3f tail = head.add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(boneContexts[i].getLength()))); + + Vector3f vectorA = tail.subtract(head); + float a = vectorA.length(); + vectorA.normalizeLocal(); + + Vector3f vectorC = targetTransform.getTranslation().subtract(head); + float c = vectorC.length(); + vectorC.normalizeLocal(); + + b -= a; + float theta = 0; + + if (c >= a + b) { + theta = vectorA.angleBetween(vectorC); + } else if (c <= FastMath.abs(a - b) && i < boneContexts.length - 1) { + theta = vectorA.angleBetween(vectorC) - FastMath.HALF_PI; + } else { + theta = vectorA.angleBetween(vectorC) - FastMath.acos(-(b * b - a * a - c * c) / (2 * a * c)); + } + + if (theta != 0) { + Vector3f vectorR = vectorA.cross(vectorC); + boneWorldRotation.fromAngleAxis(theta, vectorR); + boneWorldTransform.getRotation().multLocal(boneWorldRotation); + constraintHelper.applyTransform(boneContexts[i].getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD, boneWorldTransform); + } + + bone.updateWorldVectors(); + alteredOmas.add(boneContexts[i].getBoneOma()); + } + } + } + + @Override + public String getConstraintTypeName() { + return "Inverse kinematics"; + } + + /** + * @return the bone contexts of all bones that will be used in this constraint computations + */ + private BoneContext[] getBones() { + if (bones == null) { + List bones = new ArrayList(); + Bone bone = (Bone) this.getOwner(); + while (bone != null) { + BoneContext boneContext = blenderContext.getBoneContext(bone); + bones.add(0, boneContext); + chainLength += boneContext.getLength(); + if (bonesAffected != 0 && bones.size() >= bonesAffected) { + break; + } + bone = bone.getParent(); + } + this.bones = bones.toArray(new BoneContext[bones.size()]); + } + return bones; + } +} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java index a79d730f4..7970fe569 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java @@ -4,6 +4,8 @@ import com.jme3.animation.Bone; import com.jme3.math.Transform; import com.jme3.math.Vector3f; import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; import com.jme3.scene.plugins.blender.file.Structure; /** @@ -40,14 +42,18 @@ import com.jme3.scene.plugins.blender.file.Structure; flag |= invZ >> 1; } } - + @Override - public void bake(Transform ownerTransform, 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) { // cannot copy the location of a bone attached to its parent, // Blender forbids that return; } + + BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); + Transform ownerTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace); + Vector3f ownerLocation = ownerTransform.getTranslation(); Vector3f targetLocation = targetTransform.getTranslation(); @@ -82,6 +88,8 @@ import com.jme3.scene.plugins.blender.file.Structure; startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence); ownerLocation.addLocal(startLocation); } + + constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace, ownerTransform); } @Override diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java index 68d6a9cf6..14b5bb49d 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java @@ -4,6 +4,8 @@ import com.jme3.animation.Bone; import com.jme3.math.Transform; import com.jme3.math.Vector3f; import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; import com.jme3.scene.plugins.blender.file.Structure; /** @@ -51,14 +53,17 @@ import com.jme3.scene.plugins.blender.file.Structure; limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); } } - + @Override - public void bake(Transform ownerTransform, 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) { // location limit does not work on bones who have parent return; } - + + BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); + Transform ownerTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace); + Vector3f translation = ownerTransform.getTranslation(); if ((flag & LIMIT_XMIN) != 0 && translation.x < limits[0][0]) { @@ -79,6 +84,8 @@ import com.jme3.scene.plugins.blender.file.Structure; if ((flag & LIMIT_ZMAX) != 0 && translation.z > limits[2][1]) { translation.z -= (translation.z - limits[2][1]) * influence; } + + constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace, ownerTransform); } @Override diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java index 71b536d0a..6744a7884 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java @@ -2,6 +2,7 @@ package com.jme3.scene.plugins.blender.constraints.definitions; import com.jme3.math.Transform; import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; import com.jme3.scene.plugins.blender.file.Structure; /** @@ -16,7 +17,7 @@ import com.jme3.scene.plugins.blender.file.Structure; } @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { // null constraint does nothing so no need to implement this one } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java index c476bf3c5..9ef618f69 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java @@ -3,6 +3,8 @@ package com.jme3.scene.plugins.blender.constraints.definitions; import com.jme3.math.Quaternion; import com.jme3.math.Transform; import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; import com.jme3.scene.plugins.blender.file.Structure; /** @@ -27,7 +29,10 @@ import com.jme3.scene.plugins.blender.file.Structure; } @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { + BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); + Transform ownerTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace); + Quaternion ownerRotation = ownerTransform.getRotation(); ownerAngles = ownerRotation.toAngles(ownerAngles); targetAngles = targetTransform.getRotation().toAngles(targetAngles); @@ -64,6 +69,8 @@ import com.jme3.scene.plugins.blender.file.Structure; // ownerLocation.addLocal(startLocation); // TODO } + + constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace, ownerTransform); } @Override diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java index 9e07f8f45..51ca94698 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java @@ -3,6 +3,8 @@ package com.jme3.scene.plugins.blender.constraints.definitions; import com.jme3.math.FastMath; import com.jme3.math.Transform; import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; import com.jme3.scene.plugins.blender.file.Structure; /** @@ -65,9 +67,12 @@ import com.jme3.scene.plugins.blender.file.Structure; * limits[i][0] = limits[i][1]; limits[i][1] = temp; } } */ } - + @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { + BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); + Transform ownerTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace); + ownerTransform.getRotation().toAngles(angles); // make sure that the rotations are always in range [0, 2PI) // TODO: same comment as in constructor @@ -105,6 +110,8 @@ import com.jme3.scene.plugins.blender.file.Structure; angles[2] -= difference; } ownerTransform.getRotation().fromAngles(angles); + + constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace, ownerTransform); } @Override diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java index ebeb9e887..9abcf2e46 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java @@ -3,6 +3,8 @@ package com.jme3.scene.plugins.blender.constraints.definitions; import com.jme3.math.Transform; import com.jme3.math.Vector3f; import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; import com.jme3.scene.plugins.blender.file.Structure; /** @@ -28,9 +30,12 @@ import com.jme3.scene.plugins.blender.file.Structure; flag |= z >> 1; } } - + @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { + BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); + Transform ownerTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace); + Vector3f ownerScale = ownerTransform.getScale(); Vector3f targetScale = targetTransform.getScale(); @@ -50,6 +55,8 @@ import com.jme3.scene.plugins.blender.file.Structure; ownerScale.z = targetScale.z * influence + (1.0f - influence) * ownerScale.z; } ownerScale.addLocal(offset); + + constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace, ownerTransform); } @Override diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java index ad6e23eab..63ffee26a 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java @@ -3,6 +3,8 @@ package com.jme3.scene.plugins.blender.constraints.definitions; import com.jme3.math.Transform; import com.jme3.math.Vector3f; import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; import com.jme3.scene.plugins.blender.file.Structure; /** @@ -50,11 +52,13 @@ import com.jme3.scene.plugins.blender.file.Structure; limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); } } - + @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { + BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); + Transform ownerTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace); + Vector3f scale = ownerTransform.getScale(); - if ((flag & LIMIT_XMIN) != 0 && scale.x < limits[0][0]) { scale.x -= (scale.x - limits[0][0]) * influence; } @@ -73,6 +77,8 @@ import com.jme3.scene.plugins.blender.file.Structure; if ((flag & LIMIT_ZMAX) != 0 && scale.z > limits[2][1]) { scale.z -= (scale.z - limits[2][1]) * influence; } + + constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace, ownerTransform); } @Override diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java index e9ef8c844..3002cfb88 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java @@ -1,6 +1,7 @@ package com.jme3.scene.plugins.blender.constraints.definitions; import com.jme3.math.Transform; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; /** * This class represents a constraint that is defined by blender but not @@ -18,7 +19,7 @@ import com.jme3.math.Transform; } @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { } @Override