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