Home | History | Annotate | Download | only in modifiers
      1 package com.jme3.scene.plugins.blender.modifiers;
      2 
      3 import com.jme3.bounding.BoundingBox;
      4 import com.jme3.bounding.BoundingSphere;
      5 import com.jme3.bounding.BoundingVolume;
      6 import com.jme3.math.Vector3f;
      7 import com.jme3.scene.Geometry;
      8 import com.jme3.scene.Mesh;
      9 import com.jme3.scene.Node;
     10 import com.jme3.scene.Spatial;
     11 import com.jme3.scene.plugins.blender.BlenderContext;
     12 import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
     13 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
     14 import com.jme3.scene.plugins.blender.file.DynamicArray;
     15 import com.jme3.scene.plugins.blender.file.FileBlockHeader;
     16 import com.jme3.scene.plugins.blender.file.Pointer;
     17 import com.jme3.scene.plugins.blender.file.Structure;
     18 import com.jme3.scene.plugins.blender.objects.ObjectHelper;
     19 import com.jme3.scene.shape.Curve;
     20 import java.util.HashMap;
     21 import java.util.HashSet;
     22 import java.util.Map;
     23 import java.util.Set;
     24 import java.util.logging.Level;
     25 import java.util.logging.Logger;
     26 
     27 /**
     28  * This modifier allows to array modifier to the object.
     29  *
     30  * @author Marcin Roguski (Kaelthas)
     31  */
     32 /*package*/ class ArrayModifier extends Modifier {
     33 	private static final Logger LOGGER = Logger.getLogger(ArrayModifier.class.getName());
     34 
     35 	/** Parameters of the modifier. */
     36 	private Map<String, Object> modifierData = new HashMap<String, Object>();
     37 
     38 	/**
     39 	 * This constructor reads array data from the modifier structure. The
     40 	 * stored data is a map of parameters for array modifier. No additional data
     41 	 * is loaded.
     42 	 *
     43 	 * @param objectStructure
     44 	 *            the structure of the object
     45 	 * @param modifierStructure
     46 	 *            the structure of the modifier
     47 	 * @param blenderContext
     48 	 *            the blender context
     49 	 * @throws BlenderFileException
     50 	 *             this exception is thrown when the blender file is somehow
     51 	 *             corrupted
     52 	 */
     53 	@SuppressWarnings("unchecked")
     54 	public ArrayModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
     55 		if(this.validate(modifierStructure, blenderContext)) {
     56 	        Number fittype = (Number) modifierStructure.getFieldValue("fit_type");
     57 	        modifierData.put("fittype", fittype);
     58 	        switch (fittype.intValue()) {
     59 	            case 0:// FIXED COUNT
     60 	            	modifierData.put("count", modifierStructure.getFieldValue("count"));
     61 	                break;
     62 	            case 1:// FIXED LENGTH
     63 	            	modifierData.put("length", modifierStructure.getFieldValue("length"));
     64 	                break;
     65 	            case 2:// FITCURVE
     66 	                Pointer pCurveOb = (Pointer) modifierStructure.getFieldValue("curve_ob");
     67 	                float length = 0;
     68 	                if (pCurveOb.isNotNull()) {
     69 	                    Structure curveStructure = pCurveOb.fetchData(blenderContext.getInputStream()).get(0);
     70 	                    ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
     71 	                    Node curveObject = (Node) objectHelper.toObject(curveStructure, blenderContext);
     72 	                    Set<Number> referencesToCurveLengths = new HashSet<Number>(curveObject.getChildren().size());
     73 	                    for (Spatial spatial : curveObject.getChildren()) {
     74 	                        if (spatial instanceof Geometry) {
     75 	                            Mesh mesh = ((Geometry) spatial).getMesh();
     76 	                            if (mesh instanceof Curve) {
     77 	                                length += ((Curve) mesh).getLength();
     78 	                            } else {
     79 	                                //if bevel object has several parts then each mesh will have the same reference
     80 	                                //to length value (and we should use only one)
     81 	                                Number curveLength = spatial.getUserData("curveLength");
     82 	                                if (curveLength != null && !referencesToCurveLengths.contains(curveLength)) {
     83 	                                    length += curveLength.floatValue();
     84 	                                    referencesToCurveLengths.add(curveLength);
     85 	                                }
     86 	                            }
     87 	                        }
     88 	                    }
     89 	                }
     90 	                modifierData.put("length", Float.valueOf(length));
     91 	                modifierData.put("fittype", Integer.valueOf(1));// treat it like FIXED LENGTH
     92 	                break;
     93 	            default:
     94 	                assert false : "Unknown array modifier fit type: " + fittype;
     95 	        }
     96 
     97 	        // offset parameters
     98 	        int offsettype = ((Number) modifierStructure.getFieldValue("offset_type")).intValue();
     99 	        if ((offsettype & 0x01) != 0) {// Constant offset
    100 	            DynamicArray<Number> offsetArray = (DynamicArray<Number>) modifierStructure.getFieldValue("offset");
    101 	            float[] offset = new float[]{offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue()};
    102 	            modifierData.put("offset", offset);
    103 	        }
    104 	        if ((offsettype & 0x02) != 0) {// Relative offset
    105 	            DynamicArray<Number> scaleArray = (DynamicArray<Number>) modifierStructure.getFieldValue("scale");
    106 	            float[] scale = new float[]{scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue()};
    107 	            modifierData.put("scale", scale);
    108 	        }
    109 	        if ((offsettype & 0x04) != 0) {// Object offset
    110 	            Pointer pOffsetObject = (Pointer) modifierStructure.getFieldValue("offset_ob");
    111 	            if (pOffsetObject.isNotNull()) {
    112 	            	modifierData.put("offsetob", pOffsetObject);
    113 	            }
    114 	        }
    115 
    116 	        // start cap and end cap
    117 	        Pointer pStartCap = (Pointer) modifierStructure.getFieldValue("start_cap");
    118 	        if (pStartCap.isNotNull()) {
    119 	        	modifierData.put("startcap", pStartCap);
    120 	        }
    121 	        Pointer pEndCap = (Pointer) modifierStructure.getFieldValue("end_cap");
    122 	        if (pEndCap.isNotNull()) {
    123 	        	modifierData.put("endcap", pEndCap);
    124 	        }
    125 		}
    126 	}
    127 
    128 	@Override
    129 	public Node apply(Node node, BlenderContext blenderContext) {
    130 		if(invalid) {
    131 			LOGGER.log(Level.WARNING, "Array modifier is invalid! Cannot be applied to: {0}", node.getName());
    132 			return node;
    133 		}
    134         int fittype = ((Number) modifierData.get("fittype")).intValue();
    135         float[] offset = (float[]) modifierData.get("offset");
    136         if (offset == null) {// the node will be repeated several times in the same place
    137             offset = new float[]{0.0f, 0.0f, 0.0f};
    138         }
    139         float[] scale = (float[]) modifierData.get("scale");
    140         if (scale == null) {// the node will be repeated several times in the same place
    141             scale = new float[]{0.0f, 0.0f, 0.0f};
    142         } else {
    143             // getting bounding box
    144             node.updateModelBound();
    145             BoundingVolume boundingVolume = node.getWorldBound();
    146             if (boundingVolume instanceof BoundingBox) {
    147                 scale[0] *= ((BoundingBox) boundingVolume).getXExtent() * 2.0f;
    148                 scale[1] *= ((BoundingBox) boundingVolume).getYExtent() * 2.0f;
    149                 scale[2] *= ((BoundingBox) boundingVolume).getZExtent() * 2.0f;
    150             } else if (boundingVolume instanceof BoundingSphere) {
    151                 float radius = ((BoundingSphere) boundingVolume).getRadius();
    152                 scale[0] *= radius * 2.0f;
    153                 scale[1] *= radius * 2.0f;
    154                 scale[2] *= radius * 2.0f;
    155             } else {
    156                 throw new IllegalStateException("Unknown bounding volume type: " + boundingVolume.getClass().getName());
    157             }
    158         }
    159 
    160         // adding object's offset
    161         float[] objectOffset = new float[]{0.0f, 0.0f, 0.0f};
    162         Pointer pOffsetObject = (Pointer) modifierData.get("offsetob");
    163         if (pOffsetObject != null) {
    164             FileBlockHeader offsetObjectBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress());
    165             ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
    166             try {// we take the structure in case the object was not yet loaded
    167                 Structure offsetStructure = offsetObjectBlock.getStructure(blenderContext);
    168                 Vector3f translation = objectHelper.getTransformation(offsetStructure, blenderContext).getTranslation();
    169                 objectOffset[0] = translation.x;
    170                 objectOffset[1] = translation.y;
    171                 objectOffset[2] = translation.z;
    172             } catch (BlenderFileException e) {
    173                 LOGGER.log(Level.WARNING, "Problems in blender file structure! Object offset cannot be applied! The problem: {0}", e.getMessage());
    174             }
    175         }
    176 
    177         // getting start and end caps
    178         Node[] caps = new Node[]{null, null};
    179         Pointer[] pCaps = new Pointer[]{(Pointer) modifierData.get("startcap"), (Pointer) modifierData.get("endcap")};
    180         for (int i = 0; i < pCaps.length; ++i) {
    181             if (pCaps[i] != null) {
    182                 caps[i] = (Node) blenderContext.getLoadedFeature(pCaps[i].getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
    183                 if (caps[i] != null) {
    184                     caps[i] = (Node) caps[i].clone();
    185                 } else {
    186                     FileBlockHeader capBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress());
    187                     try {// we take the structure in case the object was not yet loaded
    188                         Structure capStructure = capBlock.getStructure(blenderContext);
    189                         ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
    190                         caps[i] = (Node) objectHelper.toObject(capStructure, blenderContext);
    191                         if (caps[i] == null) {
    192                             LOGGER.log(Level.WARNING, "Cap object ''{0}'' couldn''t be loaded!", capStructure.getName());
    193                         }
    194                     } catch (BlenderFileException e) {
    195                         LOGGER.log(Level.WARNING, "Problems in blender file structure! Cap object cannot be applied! The problem: {0}", e.getMessage());
    196                     }
    197                 }
    198             }
    199         }
    200 
    201         Vector3f translationVector = new Vector3f(offset[0] + scale[0] + objectOffset[0], offset[1] + scale[1] + objectOffset[1], offset[2] + scale[2] + objectOffset[2]);
    202 
    203         // getting/calculating repeats amount
    204         int count = 0;
    205         if (fittype == 0) {// Fixed count
    206             count = ((Number) modifierData.get("count")).intValue() - 1;
    207         } else if (fittype == 1) {// Fixed length
    208             float length = ((Number) modifierData.get("length")).floatValue();
    209             if (translationVector.length() > 0.0f) {
    210                 count = (int) (length / translationVector.length()) - 1;
    211             }
    212         } else if (fittype == 2) {// Fit curve
    213             throw new IllegalStateException("Fit curve should be transformed to Fixed Length array type!");
    214         } else {
    215             throw new IllegalStateException("Unknown fit type: " + fittype);
    216         }
    217 
    218         // adding translated nodes and caps
    219         if (count > 0) {
    220             Node[] arrayNodes = new Node[count];
    221             Vector3f newTranslation = new Vector3f();
    222             for (int i = 0; i < count; ++i) {
    223                 newTranslation.addLocal(translationVector);
    224                 Node nodeClone = (Node) node.clone();
    225                 nodeClone.setLocalTranslation(newTranslation);
    226                 arrayNodes[i] = nodeClone;
    227             }
    228             for (Node nodeClone : arrayNodes) {
    229                 node.attachChild(nodeClone);
    230             }
    231             if (caps[0] != null) {
    232                 caps[0].getLocalTranslation().set(node.getLocalTranslation()).subtractLocal(translationVector);
    233                 node.attachChild(caps[0]);
    234             }
    235             if (caps[1] != null) {
    236                 caps[1].getLocalTranslation().set(newTranslation).addLocal(translationVector);
    237                 node.attachChild(caps[1]);
    238             }
    239         }
    240         return node;
    241 	}
    242 
    243 	@Override
    244 	public String getType() {
    245 		return ARRAY_MODIFIER_DATA;
    246 	}
    247 }
    248