package com.jme3.scene.plugins.blender.modifiers; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import com.jme3.bounding.BoundingBox; import com.jme3.bounding.BoundingSphere; import com.jme3.bounding.BoundingVolume; import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; import com.jme3.scene.Mesh; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.DynamicArray; import com.jme3.scene.plugins.blender.file.FileBlockHeader; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.meshes.MeshHelper; import com.jme3.scene.plugins.blender.meshes.TemporalMesh; import com.jme3.scene.plugins.blender.objects.ObjectHelper; import com.jme3.scene.shape.Curve; /** * This modifier allows to array modifier to the object. * * @author Marcin Roguski (Kaelthas) */ /* package */class ArrayModifier extends Modifier { private static final Logger LOGGER = Logger.getLogger(ArrayModifier.class.getName()); private int fittype; private int count; private float length; private float[] offset; private float[] scale; private Pointer pOffsetObject; private Pointer pStartCap; private Pointer pEndCap; /** * This constructor reads array data from the modifier structure. The * stored data is a map of parameters for array modifier. No additional data * is loaded. * * @param objectStructure * the structure of the object * @param modifierStructure * the structure of the modifier * @param blenderContext * the blender context * @throws BlenderFileException * this exception is thrown when the blender file is somehow * corrupted */ @SuppressWarnings("unchecked") public ArrayModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { if (this.validate(modifierStructure, blenderContext)) { fittype = ((Number) modifierStructure.getFieldValue("fit_type")).intValue(); switch (fittype) { case 0:// FIXED COUNT count = ((Number) modifierStructure.getFieldValue("count")).intValue(); break; case 1:// FIXED LENGTH length = ((Number) modifierStructure.getFieldValue("length")).floatValue(); break; case 2:// FITCURVE Pointer pCurveOb = (Pointer) modifierStructure.getFieldValue("curve_ob"); if (pCurveOb.isNotNull()) { Structure curveStructure = pCurveOb.fetchData().get(0); ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); Node curveObject = (Node) objectHelper.toObject(curveStructure, blenderContext); 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); } } } } } fittype = 1;// treat it like FIXED LENGTH break; default: assert false : "Unknown array modifier fit type: " + fittype; } // offset parameters int offsettype = ((Number) modifierStructure.getFieldValue("offset_type")).intValue(); if ((offsettype & 0x01) != 0) {// Constant offset DynamicArray<Number> offsetArray = (DynamicArray<Number>) modifierStructure.getFieldValue("offset"); offset = new float[] { offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue() }; } if ((offsettype & 0x02) != 0) {// Relative offset DynamicArray<Number> scaleArray = (DynamicArray<Number>) modifierStructure.getFieldValue("scale"); scale = new float[] { scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue() }; } if ((offsettype & 0x04) != 0) {// Object offset pOffsetObject = (Pointer) modifierStructure.getFieldValue("offset_ob"); } // start cap and end cap pStartCap = (Pointer) modifierStructure.getFieldValue("start_cap"); pEndCap = (Pointer) modifierStructure.getFieldValue("end_cap"); } } @Override public void apply(Node node, BlenderContext blenderContext) { if (invalid) { LOGGER.log(Level.WARNING, "Array modifier is invalid! Cannot be applied to: {0}", node.getName()); } else { TemporalMesh temporalMesh = this.getTemporalMesh(node); if (temporalMesh != null) { LOGGER.log(Level.FINE, "Applying array modifier to: {0}", temporalMesh); if (offset == null) {// the node will be repeated several times in the same place offset = new float[] { 0.0f, 0.0f, 0.0f }; } 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 temporalMesh.updateModelBound(); BoundingVolume boundingVolume = temporalMesh.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 }; if (pOffsetObject != null && pOffsetObject.isNotNull()) { FileBlockHeader offsetObjectBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress()); ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); try {// we take the structure in case the object was not yet loaded Structure offsetStructure = offsetObjectBlock.getStructure(blenderContext); Vector3f translation = objectHelper.getTransformation(offsetStructure, blenderContext).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 MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); TemporalMesh[] caps = new TemporalMesh[] { null, null }; Pointer[] pCaps = new Pointer[] { pStartCap, pEndCap }; for (int i = 0; i < pCaps.length; ++i) { if (pCaps[i].isNotNull()) { FileBlockHeader capBlock = blenderContext.getFileBlock(pCaps[i].getOldMemoryAddress()); try {// we take the structure in case the object was not yet loaded Structure capStructure = capBlock.getStructure(blenderContext); Pointer pMesh = (Pointer) capStructure.getFieldValue("data"); List<Structure> meshesArray = pMesh.fetchData(); caps[i] = meshHelper.toTemporalMesh(meshesArray.get(0), blenderContext); } 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]); if (blenderContext.getBlenderKey().isFixUpAxis()) { float y = translationVector.y; translationVector.y = translationVector.z; translationVector.z = y == 0 ? 0 : -y; } // getting/calculating repeats amount int count = 0; if (fittype == 0) {// Fixed count count = this.count - 1; } else if (fittype == 1) {// Fixed length float length = this.length; 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 Vector3f totalTranslation = new Vector3f(translationVector); if (count > 0) { TemporalMesh originalMesh = temporalMesh.clone(); for (int i = 0; i < count; ++i) { TemporalMesh clone = originalMesh.clone(); for (Vector3f v : clone.getVertices()) { v.addLocal(totalTranslation); } temporalMesh.append(clone); totalTranslation.addLocal(translationVector); } } if (caps[0] != null) { translationVector.multLocal(-1); TemporalMesh capsClone = caps[0].clone(); for (Vector3f v : capsClone.getVertices()) { v.addLocal(translationVector); } temporalMesh.append(capsClone); } if (caps[1] != null) { TemporalMesh capsClone = caps[1].clone(); for (Vector3f v : capsClone.getVertices()) { v.addLocal(totalTranslation); } temporalMesh.append(capsClone); } } else { LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node); } } } }