package com.jme3.scene.plugins.blender.modifiers; import java.util.Collections; import java.util.logging.Level; import java.util.logging.Logger; import com.jme3.math.Matrix4f; import com.jme3.math.Vector3f; import com.jme3.scene.Node; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.meshes.Edge; import com.jme3.scene.plugins.blender.meshes.Face; import com.jme3.scene.plugins.blender.meshes.TemporalMesh; import com.jme3.scene.plugins.blender.objects.ObjectHelper; /** * This modifier allows to array modifier to the object. * * @author Marcin Roguski (Kaelthas) */ /* package */class MirrorModifier extends Modifier { private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName()); private static final int FLAG_MIRROR_X = 0x08; private static final int FLAG_MIRROR_Y = 0x10; private static final int FLAG_MIRROR_Z = 0x20; private static final int FLAG_MIRROR_U = 0x02; private static final int FLAG_MIRROR_V = 0x04; private static final int FLAG_MIRROR_VERTEX_GROUP = 0x40; private static final int FLAG_MIRROR_MERGE = 0x80; private boolean[] isMirrored; private boolean mirrorU, mirrorV; private boolean merge; private float tolerance; private Pointer pMirrorObject; private boolean mirrorVGroup; /** * This constructor reads mirror data from the modifier structure. The * stored data is a map of parameters for mirror modifier. No additional data * is loaded. * When the modifier is applied it is necessary to get the newly created node. * * @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 */ public MirrorModifier(Structure modifierStructure, BlenderContext blenderContext) { if (this.validate(modifierStructure, blenderContext)) { int flag = ((Number) modifierStructure.getFieldValue("flag")).intValue(); isMirrored = new boolean[] { (flag & FLAG_MIRROR_X) != 0, (flag & FLAG_MIRROR_Y) != 0, (flag & FLAG_MIRROR_Z) != 0 }; if (blenderContext.getBlenderKey().isFixUpAxis()) { boolean temp = isMirrored[1]; isMirrored[1] = isMirrored[2]; isMirrored[2] = temp; } mirrorU = (flag & FLAG_MIRROR_U) != 0; mirrorV = (flag & FLAG_MIRROR_V) != 0; mirrorVGroup = (flag & FLAG_MIRROR_VERTEX_GROUP) != 0; merge = (flag & FLAG_MIRROR_MERGE) == 0;// in this case we use == instead of != (this is not a mistake) tolerance = ((Number) modifierStructure.getFieldValue("tolerance")).floatValue(); pMirrorObject = (Pointer) modifierStructure.getFieldValue("mirror_ob"); if (mirrorVGroup) { LOGGER.warning("Mirroring vertex groups is currently not supported."); } } } @Override public void apply(Node node, BlenderContext blenderContext) { if (invalid) { LOGGER.log(Level.WARNING, "Mirror modifier is invalid! Cannot be applied to: {0}", node.getName()); } else { TemporalMesh temporalMesh = this.getTemporalMesh(node); if (temporalMesh != null) { LOGGER.log(Level.FINE, "Applying mirror modifier to: {0}", temporalMesh); Vector3f mirrorPlaneCenter = new Vector3f(); if (pMirrorObject.isNotNull()) { Structure objectStructure; try { objectStructure = pMirrorObject.fetchData().get(0); ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); Node object = (Node) objectHelper.toObject(objectStructure, blenderContext); if (object != null) { // compute the mirror object coordinates in node's local space mirrorPlaneCenter = this.getWorldMatrix(node).invertLocal().mult(object.getWorldTranslation()); } } catch (BlenderFileException e) { LOGGER.log(Level.SEVERE, "Cannot load mirror''s reference object. Cause: {0}", e.getLocalizedMessage()); LOGGER.log(Level.SEVERE, "Mirror modifier will not be applied to node named: {0}", node.getName()); return; } } LOGGER.finest("Allocating temporal variables."); float d; Vector3f mirrorPlaneNormal = new Vector3f(); Vector3f shiftVector = new Vector3f(); LOGGER.fine("Mirroring mesh."); for (int mirrorIndex = 0; mirrorIndex < 3; ++mirrorIndex) { if (isMirrored[mirrorIndex]) { boolean mirrorAtPoint0 = mirrorPlaneCenter.get(mirrorIndex) == 0; if (!mirrorAtPoint0) {// compute mirror's plane normal vector in node's space mirrorPlaneNormal.set(0, 0, 0).set(mirrorIndex, Math.signum(mirrorPlaneCenter.get(mirrorIndex))); } TemporalMesh mirror = temporalMesh.clone(); for (int i = 0; i < mirror.getVertexCount(); ++i) { Vector3f vertex = mirror.getVertices().get(i); Vector3f normal = mirror.getNormals().get(i); if (mirrorAtPoint0) { d = Math.abs(vertex.get(mirrorIndex)); shiftVector.set(0, 0, 0).set(mirrorIndex, -vertex.get(mirrorIndex)); } else { d = this.computeDistanceFromPlane(vertex, mirrorPlaneCenter, mirrorPlaneNormal); mirrorPlaneNormal.mult(d, shiftVector); } if (merge && d <= tolerance) { vertex.addLocal(shiftVector); normal.set(mirrorIndex, 0); temporalMesh.getVertices().get(i).addLocal(shiftVector); temporalMesh.getNormals().get(i).set(mirrorIndex, 0); } else { vertex.addLocal(shiftVector.multLocal(2)); normal.set(mirrorIndex, -normal.get(mirrorIndex)); } } // flipping the indexes for (Face face : mirror.getFaces()) { face.flipIndexes(); } for (Edge edge : mirror.getEdges()) { edge.flipIndexes(); } Collections.reverse(mirror.getPoints()); if (mirrorU || mirrorV) { for (Face face : mirror.getFaces()) { face.flipUV(mirrorU, mirrorV); } } temporalMesh.append(mirror); } } } else { LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node); } } } /** * Fetches the world matrix transformation of the given node. * @param node * the node * @return the node's world transformation matrix */ private Matrix4f getWorldMatrix(Node node) { Matrix4f result = new Matrix4f(); result.setTranslation(node.getWorldTranslation()); result.setRotationQuaternion(node.getWorldRotation()); result.setScale(node.getWorldScale()); return result; } /** * The method computes the distance between a point and a plane (described by point in space and normal vector). * @param p * the point in the space * @param c * mirror's plane center * @param n * mirror's plane normal (should be normalized) * @return the minimum distance from point to plane */ private float computeDistanceFromPlane(Vector3f p, Vector3f c, Vector3f n) { return Math.abs(n.dot(p) - c.dot(n)); } }