1package com.jme3.scene.plugins.blender.modifiers;
2
3import com.jme3.bounding.BoundingBox;
4import com.jme3.bounding.BoundingSphere;
5import com.jme3.bounding.BoundingVolume;
6import com.jme3.math.Vector3f;
7import com.jme3.scene.Geometry;
8import com.jme3.scene.Mesh;
9import com.jme3.scene.Node;
10import com.jme3.scene.Spatial;
11import com.jme3.scene.plugins.blender.BlenderContext;
12import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
13import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
14import com.jme3.scene.plugins.blender.file.DynamicArray;
15import com.jme3.scene.plugins.blender.file.FileBlockHeader;
16import com.jme3.scene.plugins.blender.file.Pointer;
17import com.jme3.scene.plugins.blender.file.Structure;
18import com.jme3.scene.plugins.blender.objects.ObjectHelper;
19import com.jme3.scene.shape.Curve;
20import java.util.HashMap;
21import java.util.HashSet;
22import java.util.Map;
23import java.util.Set;
24import java.util.logging.Level;
25import 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