瀏覽代碼

* Fixed syntax error in ModifierHelper
* Other minor fixes

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7855 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

sha..rd 14 年之前
父節點
當前提交
b8826716b1

+ 668 - 663
engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ModifierHelper.java

@@ -40,6 +40,7 @@ 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;
 
 import com.jme3.animation.AnimControl;
@@ -84,668 +85,672 @@ import com.jme3.scene.shape.Curve;
  */
 public class ModifierHelper extends AbstractBlenderHelper {
 
-	private static final Logger	LOGGER	= Logger.getLogger(ModifierHelper.class.getName());
-
-	/**
-	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
-	 * different blender versions.
-	 * @param blenderVersion
-	 *        the version read from the blend file
-	 */
-	public ModifierHelper(String blenderVersion) {
-		super(blenderVersion);
-	}
-
-	/**
-	 * This method applies modifier to the object.
-	 * @param node
-	 *        the loaded object
-	 * @param modifier
-	 *        the modifier to apply
-	 * @param dataRepository
-	 *        the data repository
-	 * @return the node to whom the modifier was applied
-	 */
-	public Node applyModifier(Node node, Modifier modifier, DataRepository dataRepository) {
-		if (Modifier.ARMATURE_MODIFIER_DATA.equals(modifier.getType())) {
-			return this.applyArmatureModifierData(node, modifier, dataRepository);
-		} else if (Modifier.OBJECT_ANIMATION_MODIFIER_DATA.equals(modifier.getType())) {
-			return this.applyObjectAnimationModifier(node, modifier, dataRepository);
-		} else if (Modifier.ARRAY_MODIFIER_DATA.equals(modifier.getType())) {
-			return this.applyArrayModifierData(node, modifier, dataRepository);
-		} else if (Modifier.PARTICLE_MODIFIER_DATA.equals(modifier.getType())) {
-			return this.applyParticleSystemModifierData(node, modifier, dataRepository);
-		} else if (Modifier.MIRROR_MODIFIER_DATA.equals(modifier.getType())) {
-			return this.applyMirrorModifierData(node, modifier, dataRepository);
-		} else {
-			LOGGER.warning("Modifier: " + modifier.getType() + " not yet implemented!!!");
-			return node;
-		}
-	}
-
-	/**
-	 * This method reads the given object's modifiers.
-	 * @param objectStructure
-	 *        the object structure
-	 * @param dataRepository
-	 *        the data repository
-	 * @param converter
-	 *        the converter object (in some cases we need to read an object first before loading the modifier)
-	 * @throws BlenderFileException
-	 *         this exception is thrown when the blender file is somehow corrupted
-	 */
-	@SuppressWarnings("unchecked")
-	public void readModifiers(Structure objectStructure, DataRepository dataRepository) throws BlenderFileException {
-		Structure modifiersListBase = (Structure) objectStructure.getFieldValue("modifiers");
-		List<Structure> modifiers = modifiersListBase.evaluateListBase(dataRepository);
-		for (Structure modifier : modifiers) {
-			Object loadedModifier = null;
-			Object modifierAdditionalData = null;
-			if (Modifier.ARRAY_MODIFIER_DATA.equals(modifier.getType())) {// ****************ARRAY MODIFIER
-				Map<String, Object> params = new HashMap<String, Object>();
-
-				Number fittype = (Number) modifier.getFieldValue("fit_type");
-				params.put("fittype", fittype);
-				switch (fittype.intValue()) {
-					case 0:// FIXED COUNT
-						params.put("count", modifier.getFieldValue("count"));
-						break;
-					case 1:// FIXED LENGTH
-						params.put("length", modifier.getFieldValue("length"));
-						break;
-					case 2:// FITCURVE
-						Pointer pCurveOb = (Pointer) modifier.getFieldValue("curve_ob");
-						float length = 0;
-						if(pCurveOb.isNotNull()) {
-							Structure curveStructure = pCurveOb.fetchData(dataRepository.getInputStream()).get(0);
-							ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
-							Node curveObject = (Node)objectHelper.toObject(curveStructure, dataRepository);
-							Set<Number> referencesToCurveLengths = new HashSet<Number>(curveObject.getChildren().size());
-							for(Spatial spatial : curveObject.getChildren()) {
-								if(spatial instanceof Geometry) {
-									Mesh mesh = ((Geometry) spatial).getMesh();
-									if(mesh instanceof Curve) {
-										length += ((Curve) mesh).getLength();
-									} else {
-										//if bevel object has several parts then each mesh will have the same reference
-										//to length value (and we should use only one)
-										Number curveLength = spatial.getUserData("curveLength");
-										if(curveLength!=null && !referencesToCurveLengths.contains(curveLength)) {
-											length += curveLength.floatValue();
-											referencesToCurveLengths.add(curveLength);
-										}
-									}
-								}
-							}
-						}
-						params.put("length", Float.valueOf(length));
-						params.put("fittype", Integer.valueOf(1));// treat it like FIXED LENGTH
-						break;
-					default:
-						assert false : "Unknown array modifier fit type: " + fittype;
-				}
-
-				// offset parameters
-				int offsettype = ((Number) modifier.getFieldValue("offset_type")).intValue();
-				if ((offsettype & 0x01) != 0) {// Constant offset
-					DynamicArray<Number> offsetArray = (DynamicArray<Number>) modifier.getFieldValue("offset");
-					float[] offset = new float[] { offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue() };
-					params.put("offset", offset);
-				}
-				if ((offsettype & 0x02) != 0) {// Relative offset
-					DynamicArray<Number> scaleArray = (DynamicArray<Number>) modifier.getFieldValue("scale");
-					float[] scale = new float[] { scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue() };
-					params.put("scale", scale);
-				}
-				if ((offsettype & 0x04) != 0) {// Object offset
-					Pointer pOffsetObject = (Pointer) modifier.getFieldValue("offset_ob");
-					if (pOffsetObject.isNotNull()) {
-						params.put("offsetob", pOffsetObject);
-					}
-				}
-
-				// start cap and end cap
-				Pointer pStartCap = (Pointer) modifier.getFieldValue("start_cap");
-				if (pStartCap.isNotNull()) {
-					params.put("startcap", pStartCap);
-				}
-				Pointer pEndCap = (Pointer) modifier.getFieldValue("end_cap");
-				if (pEndCap.isNotNull()) {
-					params.put("endcap", pEndCap);
-				}
-				loadedModifier = params;
-			} else if (Modifier.MIRROR_MODIFIER_DATA.equals(modifier.getType())) {// ****************MIRROR MODIFIER
-				Map<String, Object> params = new HashMap<String, Object>();
-
-				params.put("flag", modifier.getFieldValue("flag"));
-				params.put("tolerance", modifier.getFieldValue("tolerance"));
-				Pointer pMirrorOb = (Pointer) modifier.getFieldValue("mirror_ob");
-				if (pMirrorOb.isNotNull()) {
-					params.put("mirrorob", pMirrorOb);
-				}
-				loadedModifier = params;
-			} else if (Modifier.ARMATURE_MODIFIER_DATA.equals(modifier.getType())) {// ****************ARMATURE MODIFIER
-				Pointer pArmatureObject = (Pointer) modifier.getFieldValue("object");
-				if (pArmatureObject.isNotNull()) {
-					ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
-					Structure armatureObject = (Structure) dataRepository.getLoadedFeature(pArmatureObject.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_STRUCTURE);
-					if (armatureObject == null) {// we check this first not to fetch the structure unnecessary
-						armatureObject = pArmatureObject.fetchData(dataRepository.getInputStream()).get(0);
-					}
-					modifierAdditionalData = armatureObject.getOldMemoryAddress();
-					ArmatureHelper armatureHelper = dataRepository.getHelper(ArmatureHelper.class);
-
-					// changing bones matrices so that they fit the current object (that is why we need a copy of a skeleton)
-					Matrix4f armatureObjectMatrix = objectHelper.getTransformationMatrix(armatureObject);
-					Matrix4f inverseMeshObjectMatrix = objectHelper.getTransformationMatrix(objectStructure).invert();
-					Matrix4f additionalRootBoneTransformation = inverseMeshObjectMatrix.multLocal(armatureObjectMatrix);
-					Bone[] bones = armatureHelper.buildBonesStructure(Long.valueOf(0L), additionalRootBoneTransformation);
-
-					//setting the bones structure inside the skeleton (thus completing its loading)
-					Skeleton skeleton = new Skeleton(bones);
-					dataRepository.addLoadedFeatures(armatureObject.getOldMemoryAddress(), armatureObject.getName(), armatureObject, skeleton);
-					
-					String objectName = objectStructure.getName();
-					Set<String> animationNames = dataRepository.getBlenderKey().getAnimationNames(objectName);
-					if (animationNames != null && animationNames.size() > 0) {
-						ArrayList<BoneAnimation> animations = new ArrayList<BoneAnimation>();
-						List<FileBlockHeader> actionHeaders = dataRepository.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00));
-						for (FileBlockHeader header : actionHeaders) {
-							Structure actionStructure = header.getStructure(dataRepository);
-							String actionName = actionStructure.getName();
-							if (animationNames.contains(actionName)) {
-								int[] animationFrames = dataRepository.getBlenderKey().getAnimationFrames(objectName, actionName);
-								int fps = dataRepository.getBlenderKey().getFps();
-								float start = (float) animationFrames[0] / (float) fps;
-								float stop = (float) animationFrames[1] / (float) fps;
-								BoneAnimation boneAnimation = new BoneAnimation(actionName, stop - start);
-								boneAnimation.setTracks(armatureHelper.getTracks(actionStructure, dataRepository, objectName, actionName));
-								animations.add(boneAnimation);
-							}
-						}
-						loadedModifier = new AnimData(new Skeleton(bones), animations);
-					}
-				} else {
-					LOGGER.warning("Unsupported modifier type: " + modifier.getType());
-				}
-			} else if (Modifier.PARTICLE_MODIFIER_DATA.equals(modifier.getType())) {// ****************PARTICLES MODIFIER
-				Pointer pParticleSystem = (Pointer) modifier.getFieldValue("psys");
-				if (pParticleSystem.isNotNull()) {
-					ParticlesHelper particlesHelper = dataRepository.getHelper(ParticlesHelper.class);
-					Structure particleSystem = pParticleSystem.fetchData(dataRepository.getInputStream()).get(0);
-					loadedModifier = particlesHelper.toParticleEmitter(particleSystem, dataRepository);
-				}
-			}
-			// adding modifier to the modifier's lists
-			if (loadedModifier != null) {
-				dataRepository.addModifier(objectStructure.getOldMemoryAddress(), modifier.getType(), loadedModifier, modifierAdditionalData);
-				modifierAdditionalData = null;
-			}
-		}
-		
-		//at the end read object's animation modifier
-		Modifier objectAnimationModifier =  this.readObjectAnimation(objectStructure, dataRepository);
-		if(objectAnimationModifier != null) {
-			dataRepository.addModifier(objectStructure.getOldMemoryAddress(), 
-									   objectAnimationModifier.getType(), 
-									   objectAnimationModifier.getJmeModifierRepresentation(), 
-									   objectAnimationModifier.getAdditionalData());
-		}
-	}
-	
-	/**
-	 * This method reads animation of the object itself (without bones) and stores it as an ArmatureModifierData
-	 * modifier. The animation is returned as a modifier. It should be later applied regardless other modifiers. The
-	 * reason for this is that object may not have modifiers added but it's animation should be working.
-	 * @param objectStructure
-	 *        the structure of the object
-	 * @param dataRepository
-	 *        the data repository
-	 * @return animation modifier is returned, it should be separately applied when the object is loaded
-	 * @throws BlenderFileException
-	 *         this exception is thrown when the blender file is somehow corrupted
-	 */
-	protected Modifier readObjectAnimation(Structure objectStructure, DataRepository dataRepository) throws BlenderFileException {
-		Pointer pIpo = (Pointer)objectStructure.getFieldValue("ipo");
-		if(pIpo.isNotNull()) {
-			//check if there is an action name connected with this ipo
-			String objectAnimationName = null;
-			List<FileBlockHeader> actionBlocks = dataRepository.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00));
-			for(FileBlockHeader actionBlock : actionBlocks) {
-				Structure action = actionBlock.getStructure(dataRepository);
-				List<Structure> actionChannels = ((Structure)action.getFieldValue("chanbase")).evaluateListBase(dataRepository);
-				if(actionChannels.size() == 1) {//object's animtion action has only one channel
-					Pointer pChannelIpo = (Pointer)actionChannels.get(0).getFieldValue("ipo");
-					if(pChannelIpo.equals(pIpo)) {
-						objectAnimationName = action.getName();
-						break;
-					}
-				}
-			}
-			
-			String objectName = objectStructure.getName();
-			if(objectAnimationName == null) {//set the object's animation name to object's name
-				objectAnimationName = objectName;
-			}
-
-			IpoHelper ipoHelper = dataRepository.getHelper(IpoHelper.class);
-			Structure ipoStructure = pIpo.fetchData(dataRepository.getInputStream()).get(0);
-			Ipo ipo = ipoHelper.createIpo(ipoStructure, dataRepository);
-			int[] animationFrames = dataRepository.getBlenderKey().getAnimationFrames(objectName, objectAnimationName);
-			if(animationFrames == null) {//if the name was created here there are no frames set for the animation
-				animationFrames = new int[] {1, ipo.getLastFrame()};
-			}
-			int fps = dataRepository.getBlenderKey().getFps();
-			float start = (float)animationFrames[0] / (float)fps;
-			float stop = (float)animationFrames[1] / (float)fps;
-
-			//calculating track for the only bone in this skeleton
-			BoneTrack[] tracks = new BoneTrack[1];
-			tracks[0] = ipo.calculateTrack(0, animationFrames[0], animationFrames[1], fps);
-
-			BoneAnimation boneAnimation = new BoneAnimation(objectAnimationName, stop - start);
-			boneAnimation.setTracks(tracks);
-			ArrayList<BoneAnimation> animations = new ArrayList<BoneAnimation>(1);
-			animations.add(boneAnimation);
-
-			//preparing the object's bone
-			ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
-			Transform t = objectHelper.getTransformation(objectStructure);
-			Bone bone = new Bone(null);
-			bone.setBindTransforms(t.getTranslation(), t.getRotation(), t.getScale());
-
-			return new Modifier(Modifier.OBJECT_ANIMATION_MODIFIER_DATA, new AnimData(new Skeleton(new Bone[] {bone}), animations), objectStructure.getOldMemoryAddress());
-		}
-		return null;
-	}
-
-	/**
-	 * This method applies particles emitter to the given node.
-	 * @param node
-	 *        the particles emitter node
-	 * @param modifier
-	 *        the modifier containing the emitter data
-	 * @param dataRepository
-	 *        the data repository
-	 * @return node with particles' emitter applied
-	 */
-	protected Node applyParticleSystemModifierData(Node node, Modifier modifier, DataRepository dataRepository) {
-		MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);
-		ParticleEmitter emitter = (ParticleEmitter) modifier.getJmeModifierRepresentation();
-		emitter = emitter.clone();
-
-		// veryfying the alpha function for particles' texture
-		Integer alphaFunction = MaterialHelper.ALPHA_MASK_HYPERBOLE;
-		char nameSuffix = emitter.getName().charAt(emitter.getName().length() - 1);
-		if (nameSuffix == 'B' || nameSuffix == 'N') {
-			alphaFunction = MaterialHelper.ALPHA_MASK_NONE;
-		}
-		// removing the type suffix from the name
-		emitter.setName(emitter.getName().substring(0, emitter.getName().length() - 1));
-
-		// applying emitter shape
-		EmitterShape emitterShape = emitter.getShape();
-		List<Mesh> meshes = new ArrayList<Mesh>();
-		for (Spatial spatial : node.getChildren()) {
-			if (spatial instanceof Geometry) {
-				Mesh mesh = ((Geometry) spatial).getMesh();
-				if (mesh != null) {
-					meshes.add(mesh);
-					Material material = materialHelper.getParticlesMaterial(((Geometry) spatial).getMaterial(), alphaFunction, dataRepository);
-					emitter.setMaterial(material);// TODO: divide into several pieces
-				}
-			}
-		}
-		if (meshes.size() > 0 && emitterShape instanceof EmitterMeshVertexShape) {
-			((EmitterMeshVertexShape) emitterShape).setMeshes(meshes);
-		}
-
-		node.attachChild(emitter);
-		return node;
-	}
-
-	/**
-	 * This method applies ArmatureModifierData to the loaded object.
-	 * @param node
-	 *        the loaded object
-	 * @param modifier
-	 *        the modifier to apply
-	 * @param dataRepository
-	 *        the data repository
-	 * @return the node to whom the modifier was applied
-	 */
-	protected Node applyArmatureModifierData(Node node, Modifier modifier, DataRepository dataRepository) {
-		AnimData ad = (AnimData) modifier.getJmeModifierRepresentation();
-		ArrayList<BoneAnimation> animList = ad.anims;
-		Long modifierArmatureObject = (Long) modifier.getAdditionalData();
-		if (animList != null && animList.size() > 0) {
-			ConstraintHelper constraintHelper = dataRepository.getHelper(ConstraintHelper.class);
-			Constraint[] constraints = constraintHelper.getConstraints(modifierArmatureObject);
-			HashMap<String, BoneAnimation> anims = new HashMap<String, BoneAnimation>();
-			for (int i = 0; i < animList.size(); ++i) {
-				BoneAnimation boneAnimation = animList.get(i).clone();
-
-				// baking constraints into animations
-				if (constraints != null && constraints.length > 0) {
-					for (Constraint constraint : constraints) {
-						constraint.affectAnimation(ad.skeleton, boneAnimation);
-					}
-				}
-
-				anims.put(boneAnimation.getName(), boneAnimation);
-			}
-
-			// getting meshes
-			Mesh[] meshes = null;
-			List<Mesh> meshesList = new ArrayList<Mesh>();
-			List<Spatial> children = node.getChildren();
-			for (Spatial child : children) {
-				if (child instanceof Geometry) {
-					meshesList.add(((Geometry) child).getMesh());
-				}
-			}
-			if (meshesList.size() > 0) {
-				meshes = meshesList.toArray(new Mesh[meshesList.size()]);
-			}
-
-			// applying the control to the node
-			SkeletonControl skeletonControl = new SkeletonControl(meshes, ad.skeleton);
-			AnimControl control = node.getControl(AnimControl.class);
-
-			if (control == null) {
-				control = new AnimControl(ad.skeleton);
-			} else {
-				// merging skeletons
-				Skeleton controlSkeleton = control.getSkeleton();
-				int boneIndexIncrease = controlSkeleton.getBoneCount();
-				Skeleton skeleton = this.merge(controlSkeleton, ad.skeleton);
-
-				// merging animations
-				HashMap<String, BoneAnimation> animations = new HashMap<String, BoneAnimation>();
-				for (String animationName : control.getAnimationNames()) {
-					animations.put(animationName, control.getAnim(animationName));
-				}
-				for (Entry<String, BoneAnimation> animEntry : anims.entrySet()) {
-					BoneAnimation ba = animEntry.getValue();
-					for (int i = 0; i < ba.getTracks().length; ++i) {
-						BoneTrack bt = ba.getTracks()[i];
-						int newBoneIndex = bt.getTargetBoneIndex() + boneIndexIncrease;
-						ba.getTracks()[i] = new BoneTrack(newBoneIndex, bt.getTimes(), bt.getTranslations(), bt.getRotations(), bt.getScales());
-					}
-					animations.put(animEntry.getKey(), animEntry.getValue());
-				}
-
-				// replacing the control
-				node.removeControl(control);
-				control = new AnimControl(skeleton);
-			}
-			control.setAnimations(anims);
-			node.addControl(control);
-			node.addControl(skeletonControl);
-		}
-		return node;
-	}
-
-	protected Node applyObjectAnimationModifier(Node node, Modifier modifier, DataRepository dataRepository) {
-		AnimData ad = (AnimData) modifier.getJmeModifierRepresentation();
-		ad.skeleton.getBone(0).setAttachNode(node);
-		return this.applyArmatureModifierData(node, modifier, dataRepository);
-	}
-	
-	/**
-	 * This method applies the array modifier to the node.
-	 * @param node
-	 *        the object the modifier will be applied to
-	 * @param modifier
-	 *        the modifier to be applied
-	 * @param dataRepository
-	 *        the data repository
-	 * @return object node with array modifier applied
-	 */
-	@SuppressWarnings("unchecked")
-	protected Node applyArrayModifierData(Node node, Modifier modifier, DataRepository dataRepository) {
-		Map<String, Object> modifierData = (Map<String, Object>) modifier.getJmeModifierRepresentation();
-		int fittype = ((Number) modifierData.get("fittype")).intValue();
-		float[] offset = (float[]) modifierData.get("offset");
-		if (offset == null) {// the node will be repeated several times in the same place
-			offset = new float[] { 0.0f, 0.0f, 0.0f };
-		}
-		float[] scale = (float[]) modifierData.get("scale");
-		if (scale == null) {// the node will be repeated several times in the same place
-			scale = new float[] { 0.0f, 0.0f, 0.0f };
-		} else {
-			// getting bounding box
-			node.updateModelBound();
-			BoundingVolume boundingVolume = node.getWorldBound();
-			if (boundingVolume instanceof BoundingBox) {
-				scale[0] *= ((BoundingBox) boundingVolume).getXExtent() * 2.0f;
-				scale[1] *= ((BoundingBox) boundingVolume).getYExtent() * 2.0f;
-				scale[2] *= ((BoundingBox) boundingVolume).getZExtent() * 2.0f;
-			} else if (boundingVolume instanceof BoundingSphere) {
-				float radius = ((BoundingSphere) boundingVolume).getRadius();
-				scale[0] *= radius * 2.0f;
-				scale[1] *= radius * 2.0f;
-				scale[2] *= radius * 2.0f;
-			} else {
-				throw new IllegalStateException("Unknown bounding volume type: " + boundingVolume.getClass().getName());
-			}
-		}
-
-		// adding object's offset
-		float[] objectOffset = new float[] { 0.0f, 0.0f, 0.0f };
-		Pointer pOffsetObject = (Pointer) modifierData.get("offsetob");
-		if (pOffsetObject != null) {
-			FileBlockHeader offsetObjectBlock = dataRepository.getFileBlock(pOffsetObject.getOldMemoryAddress());
-			ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
-			try {// we take the structure in case the object was not yet loaded
-				Structure offsetStructure = offsetObjectBlock.getStructure(dataRepository);
-				Vector3f translation = objectHelper.getTransformation(offsetStructure).getTranslation();
-				objectOffset[0] = translation.x;
-				objectOffset[1] = translation.y;
-				objectOffset[2] = translation.z;
-			} catch (BlenderFileException e) {
-				LOGGER.warning("Problems in blender file structure! Object offset cannot be applied! The problem: " + e.getMessage());
-			}
-		}
-
-		// getting start and end caps
-		Node[] caps = new Node[] { null, null };
-		Pointer[] pCaps = new Pointer[] { (Pointer) modifierData.get("startcap"), (Pointer) modifierData.get("endcap") };
-		for (int i = 0; i < pCaps.length; ++i) {
-			if (pCaps[i] != null) {
-				caps[i] = (Node) dataRepository.getLoadedFeature(pCaps[i].getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
-				if (caps[i] != null) {
-					caps[i] = (Node) caps[i].clone();
-				} else {
-					FileBlockHeader capBlock = dataRepository.getFileBlock(pOffsetObject.getOldMemoryAddress());
-					try {// we take the structure in case the object was not yet loaded
-						Structure capStructure = capBlock.getStructure(dataRepository);
-						ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
-						caps[i] = (Node) objectHelper.toObject(capStructure, dataRepository);
-						if (caps[i] == null) {
-							LOGGER.warning("Cap object '" + capStructure.getName() + "' couldn't be loaded!");
-						}
-					} catch (BlenderFileException e) {
-						LOGGER.warning("Problems in blender file structure! Cap object cannot be applied! The problem: " + e.getMessage());
-					}
-				}
-			}
-		}
-
-		Vector3f translationVector = new Vector3f(offset[0] + scale[0] + objectOffset[0], offset[1] + scale[1] + objectOffset[1], offset[2] + scale[2] + objectOffset[2]);
-
-		// getting/calculating repeats amount
-		int count = 0;
-		if (fittype == 0) {// Fixed count
-			count = ((Number) modifierData.get("count")).intValue() - 1;
-		} else if (fittype == 1) {// Fixed length
-			float length = ((Number) modifierData.get("length")).floatValue();
-			if (translationVector.length() > 0.0f) {
-				count = (int) (length / translationVector.length()) - 1;
-			}
-		} else if (fittype == 2) {// Fit curve
-			throw new IllegalStateException("Fit curve should be transformed to Fixed Length array type!");
-		} else {
-			throw new IllegalStateException("Unknown fit type: " + fittype);
-		}
-
-		// adding translated nodes and caps
-		if (count > 0) {
-			Node[] arrayNodes = new Node[count];
-			Vector3f newTranslation = new Vector3f();
-			for (int i = 0; i < count; ++i) {
-				newTranslation.addLocal(translationVector);
-				Node nodeClone = (Node) node.clone();
-				nodeClone.setLocalTranslation(newTranslation);
-				arrayNodes[i] = nodeClone;
-			}
-			for (Node nodeClone : arrayNodes) {
-				node.attachChild(nodeClone);
-			}
-			if (caps[0] != null) {
-				caps[0].getLocalTranslation().set(node.getLocalTranslation()).subtractLocal(translationVector);
-				node.attachChild(caps[0]);
-			}
-			if (caps[1] != null) {
-				caps[1].getLocalTranslation().set(newTranslation).addLocal(translationVector);
-				node.attachChild(caps[1]);
-			}
-		}
-		return node;
-	}
-
-	/**
-	 * This method applies the mirror modifier to the node.
-	 * @param node
-	 *        the object the modifier will be applied to
-	 * @param modifier
-	 *        the modifier to be applied
-	 * @param dataRepository
-	 *        the data repository
-	 * @return object node with mirror modifier applied
-	 */
-	@SuppressWarnings("unchecked")
-	protected Node applyMirrorModifierData(Node node, Modifier modifier, DataRepository dataRepository) {
-		Map<String, Object> modifierData = (Map<String, Object>) modifier.getJmeModifierRepresentation();
-		int flag = ((Number) modifierData.get("flag")).intValue();
-		float[] mirrorFactor = new float[] { 
-				(flag & 0x08) != 0 ? -1.0f : 1.0f,
-				(flag & 0x10) != 0 ? -1.0f : 1.0f,
-				(flag & 0x20) != 0 ? -1.0f : 1.0f
-		};
-		float[] center = new float[] { 0.0f, 0.0f, 0.0f };
-		Pointer pObject = (Pointer) modifierData.get("mirrorob");
-		if (pObject != null) {
-			Structure objectStructure;
-			try {
-				objectStructure = pObject.fetchData(dataRepository.getInputStream()).get(0);
-				ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
-				Node object = (Node) objectHelper.toObject(objectStructure, dataRepository);
-				if (object != null) {
-					Vector3f translation = object.getWorldTranslation();
-					center[0] = translation.x;
-					center[1] = translation.y;
-					center[2] = translation.z;
-				}
-			} catch (BlenderFileException e) {
-				LOGGER.severe("Cannot load mirror's reference object. Cause: " + e.getLocalizedMessage());
-			}
-		}
-		float tolerance = ((Number) modifierData.get("tolerance")).floatValue();
-		boolean mirrorU = (flag & 0x01) != 0;
-		boolean mirrorV = (flag & 0x02) != 0;
+    private static final Logger LOGGER = Logger.getLogger(ModifierHelper.class.getName());
+
+    /**
+     * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+     * different blender versions.
+     * @param blenderVersion
+     *        the version read from the blend file
+     */
+    public ModifierHelper(String blenderVersion) {
+        super(blenderVersion);
+    }
+
+    /**
+     * This method applies modifier to the object.
+     * @param node
+     *        the loaded object
+     * @param modifier
+     *        the modifier to apply
+     * @param dataRepository
+     *        the data repository
+     * @return the node to whom the modifier was applied
+     */
+    public Node applyModifier(Node node, Modifier modifier, DataRepository dataRepository) {
+        if (Modifier.ARMATURE_MODIFIER_DATA.equals(modifier.getType())) {
+            return this.applyArmatureModifierData(node, modifier, dataRepository);
+        } else if (Modifier.OBJECT_ANIMATION_MODIFIER_DATA.equals(modifier.getType())) {
+            return this.applyObjectAnimationModifier(node, modifier, dataRepository);
+        } else if (Modifier.ARRAY_MODIFIER_DATA.equals(modifier.getType())) {
+            return this.applyArrayModifierData(node, modifier, dataRepository);
+        } else if (Modifier.PARTICLE_MODIFIER_DATA.equals(modifier.getType())) {
+            return this.applyParticleSystemModifierData(node, modifier, dataRepository);
+        } else if (Modifier.MIRROR_MODIFIER_DATA.equals(modifier.getType())) {
+            return this.applyMirrorModifierData(node, modifier, dataRepository);
+        } else {
+            LOGGER.log(Level.WARNING, "Modifier: {0} not yet implemented!!!", modifier.getType());
+            return node;
+        }
+    }
+
+    /**
+     * This method reads the given object's modifiers.
+     * @param objectStructure
+     *        the object structure
+     * @param dataRepository
+     *        the data repository
+     * @param converter
+     *        the converter object (in some cases we need to read an object first before loading the modifier)
+     * @throws BlenderFileException
+     *         this exception is thrown when the blender file is somehow corrupted
+     */
+    @SuppressWarnings("unchecked")
+    public void readModifiers(Structure objectStructure, DataRepository dataRepository) throws BlenderFileException {
+        Structure modifiersListBase = (Structure) objectStructure.getFieldValue("modifiers");
+        List<Structure> modifiers = modifiersListBase.evaluateListBase(dataRepository);
+        for (Structure modifier : modifiers) {
+            Object loadedModifier = null;
+            Object modifierAdditionalData = null;
+            if (Modifier.ARRAY_MODIFIER_DATA.equals(modifier.getType())) {// ****************ARRAY MODIFIER
+                Map<String, Object> params = new HashMap<String, Object>();
+
+                Number fittype = (Number) modifier.getFieldValue("fit_type");
+                params.put("fittype", fittype);
+                switch (fittype.intValue()) {
+                    case 0:// FIXED COUNT
+                        params.put("count", modifier.getFieldValue("count"));
+                        break;
+                    case 1:// FIXED LENGTH
+                        params.put("length", modifier.getFieldValue("length"));
+                        break;
+                    case 2:// FITCURVE
+                        Pointer pCurveOb = (Pointer) modifier.getFieldValue("curve_ob");
+                        float length = 0;
+                        if (pCurveOb.isNotNull()) {
+                            Structure curveStructure = pCurveOb.fetchData(dataRepository.getInputStream()).get(0);
+                            ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
+                            Node curveObject = (Node) objectHelper.toObject(curveStructure, dataRepository);
+                            Set<Number> referencesToCurveLengths = new HashSet<Number>(curveObject.getChildren().size());
+                            for (Spatial spatial : curveObject.getChildren()) {
+                                if (spatial instanceof Geometry) {
+                                    Mesh mesh = ((Geometry) spatial).getMesh();
+                                    if (mesh instanceof Curve) {
+                                        length += ((Curve) mesh).getLength();
+                                    } else {
+                                        //if bevel object has several parts then each mesh will have the same reference
+                                        //to length value (and we should use only one)
+                                        Number curveLength = spatial.getUserData("curveLength");
+                                        if (curveLength != null && !referencesToCurveLengths.contains(curveLength)) {
+                                            length += curveLength.floatValue();
+                                            referencesToCurveLengths.add(curveLength);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        params.put("length", Float.valueOf(length));
+                        params.put("fittype", Integer.valueOf(1));// treat it like FIXED LENGTH
+                        break;
+                    default:
+                        assert false : "Unknown array modifier fit type: " + fittype;
+                }
+
+                // offset parameters
+                int offsettype = ((Number) modifier.getFieldValue("offset_type")).intValue();
+                if ((offsettype & 0x01) != 0) {// Constant offset
+                    DynamicArray<Number> offsetArray = (DynamicArray<Number>) modifier.getFieldValue("offset");
+                    float[] offset = new float[]{offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue()};
+                    params.put("offset", offset);
+                }
+                if ((offsettype & 0x02) != 0) {// Relative offset
+                    DynamicArray<Number> scaleArray = (DynamicArray<Number>) modifier.getFieldValue("scale");
+                    float[] scale = new float[]{scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue()};
+                    params.put("scale", scale);
+                }
+                if ((offsettype & 0x04) != 0) {// Object offset
+                    Pointer pOffsetObject = (Pointer) modifier.getFieldValue("offset_ob");
+                    if (pOffsetObject.isNotNull()) {
+                        params.put("offsetob", pOffsetObject);
+                    }
+                }
+
+                // start cap and end cap
+                Pointer pStartCap = (Pointer) modifier.getFieldValue("start_cap");
+                if (pStartCap.isNotNull()) {
+                    params.put("startcap", pStartCap);
+                }
+                Pointer pEndCap = (Pointer) modifier.getFieldValue("end_cap");
+                if (pEndCap.isNotNull()) {
+                    params.put("endcap", pEndCap);
+                }
+                loadedModifier = params;
+            } else if (Modifier.MIRROR_MODIFIER_DATA.equals(modifier.getType())) {// ****************MIRROR MODIFIER
+                Map<String, Object> params = new HashMap<String, Object>();
+
+                params.put("flag", modifier.getFieldValue("flag"));
+                params.put("tolerance", modifier.getFieldValue("tolerance"));
+                Pointer pMirrorOb = (Pointer) modifier.getFieldValue("mirror_ob");
+                if (pMirrorOb.isNotNull()) {
+                    params.put("mirrorob", pMirrorOb);
+                }
+                loadedModifier = params;
+            } else if (Modifier.ARMATURE_MODIFIER_DATA.equals(modifier.getType())) {// ****************ARMATURE MODIFIER
+                Pointer pArmatureObject = (Pointer) modifier.getFieldValue("object");
+                if (pArmatureObject.isNotNull()) {
+                    ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
+                    Structure armatureObject = (Structure) dataRepository.getLoadedFeature(pArmatureObject.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_STRUCTURE);
+                    if (armatureObject == null) {// we check this first not to fetch the structure unnecessary
+                        armatureObject = pArmatureObject.fetchData(dataRepository.getInputStream()).get(0);
+                    }
+                    modifierAdditionalData = armatureObject.getOldMemoryAddress();
+                    ArmatureHelper armatureHelper = dataRepository.getHelper(ArmatureHelper.class);
+
+                    // changing bones matrices so that they fit the current object (that is why we need a copy of a skeleton)
+                    Matrix4f armatureObjectMatrix = objectHelper.getTransformationMatrix(armatureObject);
+                    Matrix4f inverseMeshObjectMatrix = objectHelper.getTransformationMatrix(objectStructure).invert();
+                    Matrix4f additionalRootBoneTransformation = inverseMeshObjectMatrix.multLocal(armatureObjectMatrix);
+                    Bone[] bones = armatureHelper.buildBonesStructure(Long.valueOf(0L), additionalRootBoneTransformation);
+
+                    //setting the bones structure inside the skeleton (thus completing its loading)
+                    Skeleton skeleton = new Skeleton(bones);
+                    dataRepository.addLoadedFeatures(armatureObject.getOldMemoryAddress(), armatureObject.getName(), armatureObject, skeleton);
+
+                    String objectName = objectStructure.getName();
+                    Set<String> animationNames = dataRepository.getBlenderKey().getAnimationNames(objectName);
+                    if (animationNames != null && animationNames.size() > 0) {
+                        ArrayList<BoneAnimation> animations = new ArrayList<BoneAnimation>();
+                        List<FileBlockHeader> actionHeaders = dataRepository.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00));
+                        for (FileBlockHeader header : actionHeaders) {
+                            Structure actionStructure = header.getStructure(dataRepository);
+                            String actionName = actionStructure.getName();
+                            if (animationNames.contains(actionName)) {
+                                int[] animationFrames = dataRepository.getBlenderKey().getAnimationFrames(objectName, actionName);
+                                int fps = dataRepository.getBlenderKey().getFps();
+                                float start = (float) animationFrames[0] / (float) fps;
+                                float stop = (float) animationFrames[1] / (float) fps;
+                                BoneAnimation boneAnimation = new BoneAnimation(actionName, stop - start);
+                                boneAnimation.setTracks(armatureHelper.getTracks(actionStructure, dataRepository, objectName, actionName));
+                                animations.add(boneAnimation);
+                            }
+                        }
+                        loadedModifier = new AnimData(new Skeleton(bones), animations);
+                    }
+                } else {
+                    LOGGER.log(Level.WARNING, "Unsupported modifier type: {0}", modifier.getType());
+                }
+            } else if (Modifier.PARTICLE_MODIFIER_DATA.equals(modifier.getType())) {// ****************PARTICLES MODIFIER
+                Pointer pParticleSystem = (Pointer) modifier.getFieldValue("psys");
+                if (pParticleSystem.isNotNull()) {
+                    ParticlesHelper particlesHelper = dataRepository.getHelper(ParticlesHelper.class);
+                    Structure particleSystem = pParticleSystem.fetchData(dataRepository.getInputStream()).get(0);
+                    loadedModifier = particlesHelper.toParticleEmitter(particleSystem, dataRepository);
+                }
+            }
+            // adding modifier to the modifier's lists
+            if (loadedModifier != null) {
+                dataRepository.addModifier(objectStructure.getOldMemoryAddress(), modifier.getType(), loadedModifier, modifierAdditionalData);
+                modifierAdditionalData = null;
+            }
+        }
+
+        //at the end read object's animation modifier
+        Modifier objectAnimationModifier = this.readObjectAnimation(objectStructure, dataRepository);
+        if (objectAnimationModifier != null) {
+            dataRepository.addModifier(objectStructure.getOldMemoryAddress(),
+                    objectAnimationModifier.getType(),
+                    objectAnimationModifier.getJmeModifierRepresentation(),
+                    objectAnimationModifier.getAdditionalData());
+        }
+    }
+
+    /**
+     * This method reads animation of the object itself (without bones) and stores it as an ArmatureModifierData
+     * modifier. The animation is returned as a modifier. It should be later applied regardless other modifiers. The
+     * reason for this is that object may not have modifiers added but it's animation should be working.
+     * @param objectStructure
+     *        the structure of the object
+     * @param dataRepository
+     *        the data repository
+     * @return animation modifier is returned, it should be separately applied when the object is loaded
+     * @throws BlenderFileException
+     *         this exception is thrown when the blender file is somehow corrupted
+     */
+    protected Modifier readObjectAnimation(Structure objectStructure, DataRepository dataRepository) throws BlenderFileException {
+        Pointer pIpo = (Pointer) objectStructure.getFieldValue("ipo");
+        if (pIpo.isNotNull()) {
+            //check if there is an action name connected with this ipo
+            String objectAnimationName = null;
+            List<FileBlockHeader> actionBlocks = dataRepository.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00));
+            for (FileBlockHeader actionBlock : actionBlocks) {
+                Structure action = actionBlock.getStructure(dataRepository);
+                List<Structure> actionChannels = ((Structure) action.getFieldValue("chanbase")).evaluateListBase(dataRepository);
+                if (actionChannels.size() == 1) {//object's animtion action has only one channel
+                    Pointer pChannelIpo = (Pointer) actionChannels.get(0).getFieldValue("ipo");
+                    if (pChannelIpo.equals(pIpo)) {
+                        objectAnimationName = action.getName();
+                        break;
+                    }
+                }
+            }
+
+            String objectName = objectStructure.getName();
+            if (objectAnimationName == null) {//set the object's animation name to object's name
+                objectAnimationName = objectName;
+            }
+
+            IpoHelper ipoHelper = dataRepository.getHelper(IpoHelper.class);
+            Structure ipoStructure = pIpo.fetchData(dataRepository.getInputStream()).get(0);
+            Ipo ipo = ipoHelper.createIpo(ipoStructure, dataRepository);
+            int[] animationFrames = dataRepository.getBlenderKey().getAnimationFrames(objectName, objectAnimationName);
+            if (animationFrames == null) {//if the name was created here there are no frames set for the animation
+                animationFrames = new int[]{1, ipo.getLastFrame()};
+            }
+            int fps = dataRepository.getBlenderKey().getFps();
+            float start = (float) animationFrames[0] / (float) fps;
+            float stop = (float) animationFrames[1] / (float) fps;
+
+            //calculating track for the only bone in this skeleton
+            BoneTrack[] tracks = new BoneTrack[1];
+            tracks[0] = ipo.calculateTrack(0, animationFrames[0], animationFrames[1], fps);
+
+            BoneAnimation boneAnimation = new BoneAnimation(objectAnimationName, stop - start);
+            boneAnimation.setTracks(tracks);
+            ArrayList<BoneAnimation> animations = new ArrayList<BoneAnimation>(1);
+            animations.add(boneAnimation);
+
+            //preparing the object's bone
+            ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
+            Transform t = objectHelper.getTransformation(objectStructure);
+            Bone bone = new Bone(null);
+            bone.setBindTransforms(t.getTranslation(), t.getRotation(), t.getScale());
+
+            return new Modifier(Modifier.OBJECT_ANIMATION_MODIFIER_DATA, new AnimData(new Skeleton(new Bone[]{bone}), animations), objectStructure.getOldMemoryAddress());
+        }
+        return null;
+    }
+
+    /**
+     * This method applies particles emitter to the given node.
+     * @param node
+     *        the particles emitter node
+     * @param modifier
+     *        the modifier containing the emitter data
+     * @param dataRepository
+     *        the data repository
+     * @return node with particles' emitter applied
+     */
+    protected Node applyParticleSystemModifierData(Node node, Modifier modifier, DataRepository dataRepository) {
+        MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);
+        ParticleEmitter emitter = (ParticleEmitter) modifier.getJmeModifierRepresentation();
+        emitter = emitter.clone();
+
+        // veryfying the alpha function for particles' texture
+        Integer alphaFunction = MaterialHelper.ALPHA_MASK_HYPERBOLE;
+        char nameSuffix = emitter.getName().charAt(emitter.getName().length() - 1);
+        if (nameSuffix == 'B' || nameSuffix == 'N') {
+            alphaFunction = MaterialHelper.ALPHA_MASK_NONE;
+        }
+        // removing the type suffix from the name
+        emitter.setName(emitter.getName().substring(0, emitter.getName().length() - 1));
+
+        // applying emitter shape
+        EmitterShape emitterShape = emitter.getShape();
+        List<Mesh> meshes = new ArrayList<Mesh>();
+        for (Spatial spatial : node.getChildren()) {
+            if (spatial instanceof Geometry) {
+                Mesh mesh = ((Geometry) spatial).getMesh();
+                if (mesh != null) {
+                    meshes.add(mesh);
+                    Material material = materialHelper.getParticlesMaterial(((Geometry) spatial).getMaterial(), alphaFunction, dataRepository);
+                    emitter.setMaterial(material);// TODO: divide into several pieces
+                }
+            }
+        }
+        if (meshes.size() > 0 && emitterShape instanceof EmitterMeshVertexShape) {
+            ((EmitterMeshVertexShape) emitterShape).setMeshes(meshes);
+        }
+
+        node.attachChild(emitter);
+        return node;
+    }
+
+    /**
+     * This method applies ArmatureModifierData to the loaded object.
+     * @param node
+     *        the loaded object
+     * @param modifier
+     *        the modifier to apply
+     * @param dataRepository
+     *        the data repository
+     * @return the node to whom the modifier was applied
+     */
+    protected Node applyArmatureModifierData(Node node, Modifier modifier, DataRepository dataRepository) {
+        AnimData ad = (AnimData) modifier.getJmeModifierRepresentation();
+        ArrayList<BoneAnimation> animList = ad.anims;
+        Long modifierArmatureObject = (Long) modifier.getAdditionalData();
+        if (animList != null && animList.size() > 0) {
+            ConstraintHelper constraintHelper = dataRepository.getHelper(ConstraintHelper.class);
+            Constraint[] constraints = constraintHelper.getConstraints(modifierArmatureObject);
+            HashMap<String, BoneAnimation> anims = new HashMap<String, BoneAnimation>();
+            for (int i = 0; i < animList.size(); ++i) {
+                BoneAnimation boneAnimation = animList.get(i).clone();
+
+                // baking constraints into animations
+                if (constraints != null && constraints.length > 0) {
+                    for (Constraint constraint : constraints) {
+                        constraint.affectAnimation(ad.skeleton, boneAnimation);
+                    }
+                }
+
+                anims.put(boneAnimation.getName(), boneAnimation);
+            }
+
+            // getting meshes
+            Mesh[] meshes = null;
+            List<Mesh> meshesList = new ArrayList<Mesh>();
+            List<Spatial> children = node.getChildren();
+            for (Spatial child : children) {
+                if (child instanceof Geometry) {
+                    meshesList.add(((Geometry) child).getMesh());
+                }
+            }
+            if (meshesList.size() > 0) {
+                meshes = meshesList.toArray(new Mesh[meshesList.size()]);
+            }
+
+            // applying the control to the node
+            SkeletonControl skeletonControl = new SkeletonControl(meshes, ad.skeleton);
+            AnimControl control = node.getControl(AnimControl.class);
+
+            if (control == null) {
+                control = new AnimControl(ad.skeleton);
+            } else {
+                // merging skeletons
+                Skeleton controlSkeleton = control.getSkeleton();
+                int boneIndexIncrease = controlSkeleton.getBoneCount();
+                Skeleton skeleton = this.merge(controlSkeleton, ad.skeleton);
+
+                // merging animations
+                HashMap<String, BoneAnimation> animations = new HashMap<String, BoneAnimation>();
+                for (String animationName : control.getAnimationNames()) {
+                    animations.put(animationName, control.getAnim(animationName));
+                }
+                for (Entry<String, BoneAnimation> animEntry : anims.entrySet()) {
+                    BoneAnimation ba = animEntry.getValue();
+                    for (int i = 0; i < ba.getTracks().length; ++i) {
+                        BoneTrack bt = ba.getTracks()[i];
+                        int newBoneIndex = bt.getTargetBoneIndex() + boneIndexIncrease;
+                        ba.getTracks()[i] = new BoneTrack(newBoneIndex, bt.getTimes(), bt.getTranslations(), bt.getRotations(), bt.getScales());
+                    }
+                    animations.put(animEntry.getKey(), animEntry.getValue());
+                }
+
+                // replacing the control
+                node.removeControl(control);
+                control = new AnimControl(skeleton);
+            }
+            control.setAnimations(anims);
+            node.addControl(control);
+            node.addControl(skeletonControl);
+        }
+        return node;
+    }
+
+    protected Node applyObjectAnimationModifier(Node node, Modifier modifier, DataRepository dataRepository) {
+        AnimData ad = (AnimData) modifier.getJmeModifierRepresentation();
+        
+        // TODO: Why is this line here? Why is this needed? 
+        // Remove if necessary.
+        //ad.skeleton.getBone(0).setAttachNode(node);
+        
+        return this.applyArmatureModifierData(node, modifier, dataRepository);
+    }
+
+    /**
+     * This method applies the array modifier to the node.
+     * @param node
+     *        the object the modifier will be applied to
+     * @param modifier
+     *        the modifier to be applied
+     * @param dataRepository
+     *        the data repository
+     * @return object node with array modifier applied
+     */
+    @SuppressWarnings("unchecked")
+    protected Node applyArrayModifierData(Node node, Modifier modifier, DataRepository dataRepository) {
+        Map<String, Object> modifierData = (Map<String, Object>) modifier.getJmeModifierRepresentation();
+        int fittype = ((Number) modifierData.get("fittype")).intValue();
+        float[] offset = (float[]) modifierData.get("offset");
+        if (offset == null) {// the node will be repeated several times in the same place
+            offset = new float[]{0.0f, 0.0f, 0.0f};
+        }
+        float[] scale = (float[]) modifierData.get("scale");
+        if (scale == null) {// the node will be repeated several times in the same place
+            scale = new float[]{0.0f, 0.0f, 0.0f};
+        } else {
+            // getting bounding box
+            node.updateModelBound();
+            BoundingVolume boundingVolume = node.getWorldBound();
+            if (boundingVolume instanceof BoundingBox) {
+                scale[0] *= ((BoundingBox) boundingVolume).getXExtent() * 2.0f;
+                scale[1] *= ((BoundingBox) boundingVolume).getYExtent() * 2.0f;
+                scale[2] *= ((BoundingBox) boundingVolume).getZExtent() * 2.0f;
+            } else if (boundingVolume instanceof BoundingSphere) {
+                float radius = ((BoundingSphere) boundingVolume).getRadius();
+                scale[0] *= radius * 2.0f;
+                scale[1] *= radius * 2.0f;
+                scale[2] *= radius * 2.0f;
+            } else {
+                throw new IllegalStateException("Unknown bounding volume type: " + boundingVolume.getClass().getName());
+            }
+        }
+
+        // adding object's offset
+        float[] objectOffset = new float[]{0.0f, 0.0f, 0.0f};
+        Pointer pOffsetObject = (Pointer) modifierData.get("offsetob");
+        if (pOffsetObject != null) {
+            FileBlockHeader offsetObjectBlock = dataRepository.getFileBlock(pOffsetObject.getOldMemoryAddress());
+            ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
+            try {// we take the structure in case the object was not yet loaded
+                Structure offsetStructure = offsetObjectBlock.getStructure(dataRepository);
+                Vector3f translation = objectHelper.getTransformation(offsetStructure).getTranslation();
+                objectOffset[0] = translation.x;
+                objectOffset[1] = translation.y;
+                objectOffset[2] = translation.z;
+            } catch (BlenderFileException e) {
+                LOGGER.log(Level.WARNING, "Problems in blender file structure! Object offset cannot be applied! The problem: {0}", e.getMessage());
+            }
+        }
+
+        // getting start and end caps
+        Node[] caps = new Node[]{null, null};
+        Pointer[] pCaps = new Pointer[]{(Pointer) modifierData.get("startcap"), (Pointer) modifierData.get("endcap")};
+        for (int i = 0; i < pCaps.length; ++i) {
+            if (pCaps[i] != null) {
+                caps[i] = (Node) dataRepository.getLoadedFeature(pCaps[i].getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
+                if (caps[i] != null) {
+                    caps[i] = (Node) caps[i].clone();
+                } else {
+                    FileBlockHeader capBlock = dataRepository.getFileBlock(pOffsetObject.getOldMemoryAddress());
+                    try {// we take the structure in case the object was not yet loaded
+                        Structure capStructure = capBlock.getStructure(dataRepository);
+                        ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
+                        caps[i] = (Node) objectHelper.toObject(capStructure, dataRepository);
+                        if (caps[i] == null) {
+                            LOGGER.log(Level.WARNING, "Cap object ''{0}'' couldn''t be loaded!", capStructure.getName());
+                        }
+                    } catch (BlenderFileException e) {
+                        LOGGER.log(Level.WARNING, "Problems in blender file structure! Cap object cannot be applied! The problem: {0}", e.getMessage());
+                    }
+                }
+            }
+        }
+
+        Vector3f translationVector = new Vector3f(offset[0] + scale[0] + objectOffset[0], offset[1] + scale[1] + objectOffset[1], offset[2] + scale[2] + objectOffset[2]);
+
+        // getting/calculating repeats amount
+        int count = 0;
+        if (fittype == 0) {// Fixed count
+            count = ((Number) modifierData.get("count")).intValue() - 1;
+        } else if (fittype == 1) {// Fixed length
+            float length = ((Number) modifierData.get("length")).floatValue();
+            if (translationVector.length() > 0.0f) {
+                count = (int) (length / translationVector.length()) - 1;
+            }
+        } else if (fittype == 2) {// Fit curve
+            throw new IllegalStateException("Fit curve should be transformed to Fixed Length array type!");
+        } else {
+            throw new IllegalStateException("Unknown fit type: " + fittype);
+        }
+
+        // adding translated nodes and caps
+        if (count > 0) {
+            Node[] arrayNodes = new Node[count];
+            Vector3f newTranslation = new Vector3f();
+            for (int i = 0; i < count; ++i) {
+                newTranslation.addLocal(translationVector);
+                Node nodeClone = (Node) node.clone();
+                nodeClone.setLocalTranslation(newTranslation);
+                arrayNodes[i] = nodeClone;
+            }
+            for (Node nodeClone : arrayNodes) {
+                node.attachChild(nodeClone);
+            }
+            if (caps[0] != null) {
+                caps[0].getLocalTranslation().set(node.getLocalTranslation()).subtractLocal(translationVector);
+                node.attachChild(caps[0]);
+            }
+            if (caps[1] != null) {
+                caps[1].getLocalTranslation().set(newTranslation).addLocal(translationVector);
+                node.attachChild(caps[1]);
+            }
+        }
+        return node;
+    }
+
+    /**
+     * This method applies the mirror modifier to the node.
+     * @param node
+     *        the object the modifier will be applied to
+     * @param modifier
+     *        the modifier to be applied
+     * @param dataRepository
+     *        the data repository
+     * @return object node with mirror modifier applied
+     */
+    @SuppressWarnings("unchecked")
+    protected Node applyMirrorModifierData(Node node, Modifier modifier, DataRepository dataRepository) {
+        Map<String, Object> modifierData = (Map<String, Object>) modifier.getJmeModifierRepresentation();
+        int flag = ((Number) modifierData.get("flag")).intValue();
+        float[] mirrorFactor = new float[]{
+            (flag & 0x08) != 0 ? -1.0f : 1.0f,
+            (flag & 0x10) != 0 ? -1.0f : 1.0f,
+            (flag & 0x20) != 0 ? -1.0f : 1.0f
+        };
+        float[] center = new float[]{0.0f, 0.0f, 0.0f};
+        Pointer pObject = (Pointer) modifierData.get("mirrorob");
+        if (pObject != null) {
+            Structure objectStructure;
+            try {
+                objectStructure = pObject.fetchData(dataRepository.getInputStream()).get(0);
+                ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
+                Node object = (Node) objectHelper.toObject(objectStructure, dataRepository);
+                if (object != null) {
+                    Vector3f translation = object.getWorldTranslation();
+                    center[0] = translation.x;
+                    center[1] = translation.y;
+                    center[2] = translation.z;
+                }
+            } catch (BlenderFileException e) {
+                LOGGER.log(Level.SEVERE, "Cannot load mirror''s reference object. Cause: {0}", e.getLocalizedMessage());
+            }
+        }
+        float tolerance = ((Number) modifierData.get("tolerance")).floatValue();
+        boolean mirrorU = (flag & 0x01) != 0;
+        boolean mirrorV = (flag & 0x02) != 0;
 //		boolean mirrorVGroup = (flag & 0x20) != 0;
-		
-		List<Geometry> geometriesToAdd = new ArrayList<Geometry>();
-		for (int mirrorIndex = 0; mirrorIndex < 3; ++mirrorIndex) {
-			if (mirrorFactor[mirrorIndex] == -1.0f) {
-				for (Spatial spatial : node.getChildren()) {
-					if (spatial instanceof Geometry) {
-						Mesh mesh = ((Geometry) spatial).getMesh();
-						Mesh clone = mesh.deepClone();
-
-						// getting buffers
-						FloatBuffer position = mesh.getFloatBuffer(Type.Position);
-						FloatBuffer bindPosePosition = mesh.getFloatBuffer(Type.BindPosePosition);
-
-						FloatBuffer clonePosition = clone.getFloatBuffer(Type.Position);
-						FloatBuffer cloneBindPosePosition = clone.getFloatBuffer(Type.BindPosePosition);
-						FloatBuffer cloneNormals = clone.getFloatBuffer(Type.Normal);
-						FloatBuffer cloneBindPoseNormals = clone.getFloatBuffer(Type.BindPoseNormal);
-						ShortBuffer cloneIndexes = (ShortBuffer) clone.getBuffer(Type.Index).getData();
-						
-						// modyfying data
-						for (int i = mirrorIndex; i < clonePosition.limit(); i += 3) {
-							float value = clonePosition.get(i);
-							float d = center[mirrorIndex] - value;
-
-							if (Math.abs(d) <= tolerance) {
-								clonePosition.put(i, center[mirrorIndex]);
-								cloneBindPosePosition.put(i, center[mirrorIndex]);
-								position.put(i, center[mirrorIndex]);
-								bindPosePosition.put(i, center[mirrorIndex]);
-							} else {
-								clonePosition.put(i, value + 2.0f * d);
-								cloneBindPosePosition.put(i, value + 2.0f * d);
-							}
-							cloneNormals.put(i, -cloneNormals.get(i));
-							cloneBindPoseNormals.put(i, -cloneNormals.get(i));
-							
-							//modifying clone indexes
-							int vertexIndex = (i - mirrorIndex) / 3;
-							if(vertexIndex % 3 == 0) {
-								short index = cloneIndexes.get(vertexIndex + 2);
-								cloneIndexes.put(vertexIndex + 2, cloneIndexes.get(vertexIndex + 1));
-								cloneIndexes.put(vertexIndex + 1, index);
-							}
-						}
-						
-						if(mirrorU) {
-							FloatBuffer cloneUVs = (FloatBuffer) clone.getBuffer(Type.TexCoord).getData();
-							for(int i=0;i<cloneUVs.limit();i+=2) {
-								cloneUVs.put(i, 1.0f - cloneUVs.get(i));
-							}
-						}
-						if(mirrorV) {
-							FloatBuffer cloneUVs = (FloatBuffer) clone.getBuffer(Type.TexCoord).getData();
-							for(int i=1;i<cloneUVs.limit();i+=2) {
-								cloneUVs.put(i, 1.0f - cloneUVs.get(i));
-							}
-						}
-						
-						Geometry geometry = new Geometry(null, clone);
-						geometry.setMaterial(((Geometry) spatial).getMaterial());
-						geometriesToAdd.add(geometry);
-					}
-				}
-
-				// adding meshes to node
-				for (Geometry geometry : geometriesToAdd) {
-					node.attachChild(geometry);
-				}
-				geometriesToAdd.clear();
-			}
-		}
-		return node;
-	}
-
-	/**
-	 * This method merges two skeletons into one. I assume that each skeleton's 0-indexed bone is objectAnimationBone so
-	 * only one such bone should be placed in the result
-	 * @param s1
-	 *        first skeleton
-	 * @param s2
-	 *        second skeleton
-	 * @return merged skeleton
-	 */
-	protected Skeleton merge(Skeleton s1, Skeleton s2) {
-		List<Bone> bones = new ArrayList<Bone>(s1.getBoneCount() + s2.getBoneCount());
-		for (int i = 0; i < s1.getBoneCount(); ++i) {
-			bones.add(s1.getBone(i));
-		}
-		for (int i = 1; i < s2.getBoneCount(); ++i) {// ommit objectAnimationBone
-			bones.add(s2.getBone(i));
-		}
-		return new Skeleton(bones.toArray(new Bone[bones.size()]));
-	}
+
+        List<Geometry> geometriesToAdd = new ArrayList<Geometry>();
+        for (int mirrorIndex = 0; mirrorIndex < 3; ++mirrorIndex) {
+            if (mirrorFactor[mirrorIndex] == -1.0f) {
+                for (Spatial spatial : node.getChildren()) {
+                    if (spatial instanceof Geometry) {
+                        Mesh mesh = ((Geometry) spatial).getMesh();
+                        Mesh clone = mesh.deepClone();
+
+                        // getting buffers
+                        FloatBuffer position = mesh.getFloatBuffer(Type.Position);
+                        FloatBuffer bindPosePosition = mesh.getFloatBuffer(Type.BindPosePosition);
+
+                        FloatBuffer clonePosition = clone.getFloatBuffer(Type.Position);
+                        FloatBuffer cloneBindPosePosition = clone.getFloatBuffer(Type.BindPosePosition);
+                        FloatBuffer cloneNormals = clone.getFloatBuffer(Type.Normal);
+                        FloatBuffer cloneBindPoseNormals = clone.getFloatBuffer(Type.BindPoseNormal);
+                        ShortBuffer cloneIndexes = (ShortBuffer) clone.getBuffer(Type.Index).getData();
+
+                        // modyfying data
+                        for (int i = mirrorIndex; i < clonePosition.limit(); i += 3) {
+                            float value = clonePosition.get(i);
+                            float d = center[mirrorIndex] - value;
+
+                            if (Math.abs(d) <= tolerance) {
+                                clonePosition.put(i, center[mirrorIndex]);
+                                cloneBindPosePosition.put(i, center[mirrorIndex]);
+                                position.put(i, center[mirrorIndex]);
+                                bindPosePosition.put(i, center[mirrorIndex]);
+                            } else {
+                                clonePosition.put(i, value + 2.0f * d);
+                                cloneBindPosePosition.put(i, value + 2.0f * d);
+                            }
+                            cloneNormals.put(i, -cloneNormals.get(i));
+                            cloneBindPoseNormals.put(i, -cloneNormals.get(i));
+
+                            //modifying clone indexes
+                            int vertexIndex = (i - mirrorIndex) / 3;
+                            if (vertexIndex % 3 == 0) {
+                                short index = cloneIndexes.get(vertexIndex + 2);
+                                cloneIndexes.put(vertexIndex + 2, cloneIndexes.get(vertexIndex + 1));
+                                cloneIndexes.put(vertexIndex + 1, index);
+                            }
+                        }
+
+                        if (mirrorU) {
+                            FloatBuffer cloneUVs = (FloatBuffer) clone.getBuffer(Type.TexCoord).getData();
+                            for (int i = 0; i < cloneUVs.limit(); i += 2) {
+                                cloneUVs.put(i, 1.0f - cloneUVs.get(i));
+                            }
+                        }
+                        if (mirrorV) {
+                            FloatBuffer cloneUVs = (FloatBuffer) clone.getBuffer(Type.TexCoord).getData();
+                            for (int i = 1; i < cloneUVs.limit(); i += 2) {
+                                cloneUVs.put(i, 1.0f - cloneUVs.get(i));
+                            }
+                        }
+
+                        Geometry geometry = new Geometry(null, clone);
+                        geometry.setMaterial(((Geometry) spatial).getMaterial());
+                        geometriesToAdd.add(geometry);
+                    }
+                }
+
+                // adding meshes to node
+                for (Geometry geometry : geometriesToAdd) {
+                    node.attachChild(geometry);
+                }
+                geometriesToAdd.clear();
+            }
+        }
+        return node;
+    }
+
+    /**
+     * This method merges two skeletons into one. I assume that each skeleton's 0-indexed bone is objectAnimationBone so
+     * only one such bone should be placed in the result
+     * @param s1
+     *        first skeleton
+     * @param s2
+     *        second skeleton
+     * @return merged skeleton
+     */
+    protected Skeleton merge(Skeleton s1, Skeleton s2) {
+        List<Bone> bones = new ArrayList<Bone>(s1.getBoneCount() + s2.getBoneCount());
+        for (int i = 0; i < s1.getBoneCount(); ++i) {
+            bones.add(s1.getBone(i));
+        }
+        for (int i = 1; i < s2.getBoneCount(); ++i) {// ommit objectAnimationBone
+            bones.add(s2.getBone(i));
+        }
+        return new Skeleton(bones.toArray(new Bone[bones.size()]));
+    }
 }

+ 13 - 3
engine/src/blender/com/jme3/scene/plugins/blender/structures/Modifier.java

@@ -7,16 +7,26 @@ package com.jme3.scene.plugins.blender.structures;
  * @author Marcin Roguski (Kaelthas)
  */
 public class Modifier {
+
     public static final String ARRAY_MODIFIER_DATA = "ArrayModifierData";
     public static final String ARMATURE_MODIFIER_DATA = "ArmatureModifierData";
     public static final String PARTICLE_MODIFIER_DATA = "ParticleSystemModifierData";
     public static final String MIRROR_MODIFIER_DATA = "MirrorModifierData";
+    public static final String OBJECT_ANIMATION_MODIFIER_DATA = "ObjectAnimationModifierData";
     
-    /** Blender's type of modifier. */
+    /** 
+     * Blender's type of modifier. 
+     */
     private String type;
-    /** JME modifier representation object. */
+    
+    /** 
+     * JME modifier representation object. 
+     */
     private Object jmeModifierRepresentation;
-    /** Various additional data used by modifiers.*/
+    
+    /** 
+     * Various additional data used by modifiers.
+     */
     private Object additionalData;
 
     /**

+ 3 - 3
engine/src/core/com/jme3/asset/AssetKey.java

@@ -61,10 +61,10 @@ public class AssetKey<T> implements Savable {
     protected static String getExtension(String name){
         int idx = name.lastIndexOf('.');
         //workaround for filenames ending with xml and another dot ending before that (my.mesh.xml)
-        if(name.toLowerCase().indexOf(".xml")==name.length()-4){
+        if (name.toLowerCase().endsWith(".xml")) {
             idx = name.substring(0, idx).lastIndexOf('.');
-            if(idx==-1){
-                idx=name.lastIndexOf('.');
+            if (idx == -1) {
+                idx = name.lastIndexOf('.');
             }
         }
         if (idx <= 0 || idx == name.length() - 1)