• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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")
ArrayModifier(Structure modifierStructure, BlenderContext blenderContext)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
apply(Node node, BlenderContext blenderContext)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
getType()244 	public String getType() {
245 		return ARRAY_MODIFIER_DATA;
246 	}
247 }
248