package com.jme3.scene.plugins.blender.modifiers;
import java.nio.Buffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
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.math.Matrix4f;
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.VertexBuffer.Type;
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.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;
/**
* 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;
// boolean 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");
}
}
@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 {
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();
Vector3f point = new Vector3f();
Vector3f normal = new Vector3f();
Set<Integer> modifiedIndexes = new HashSet<Integer>();
List<Geometry> geometriesToAdd = new ArrayList<Geometry>();
final char[] mirrorNames = new char[] { 'X', 'Y', 'Z' };
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)));
}
for (Spatial spatial : node.getChildren()) {
if (spatial instanceof Geometry) {
Mesh mesh = ((Geometry) spatial).getMesh();
Mesh clone = mesh.deepClone();
LOGGER.log(Level.FINEST, "Fetching buffers of cloned spatial: {0}", spatial.getName());
FloatBuffer position = mesh.getFloatBuffer(Type.Position);
FloatBuffer bindPosePosition = mesh.getFloatBuffer(Type.BindPosePosition);
FloatBuffer clonePosition = clone.getFloatBuffer(Type.Position);
FloatBuffer cloneBindPosePosition = clone.getFloatBuffer(Type.BindPosePosition);
FloatBuffer cloneNormals = clone.getFloatBuffer(Type.Normal);
FloatBuffer cloneBindPoseNormals = clone.getFloatBuffer(Type.BindPoseNormal);
Buffer cloneIndexes = clone.getBuffer(Type.Index).getData();
for (int i = 0; i < cloneIndexes.limit(); ++i) {
int index = cloneIndexes instanceof ShortBuffer ? ((ShortBuffer) cloneIndexes).get(i) : ((IntBuffer) cloneIndexes).get(i);
if (!modifiedIndexes.contains(index)) {
modifiedIndexes.add(index);
this.get(clonePosition, index, point);
if (mirrorAtPoint0) {
d = Math.abs(point.get(mirrorIndex));
shiftVector.set(0, 0, 0).set(mirrorIndex, -point.get(mirrorIndex));
} else {
d = this.computeDistanceFromPlane(point, mirrorPlaneCenter, mirrorPlaneNormal);
mirrorPlaneNormal.mult(d, shiftVector);
}
if (merge && d <= tolerance) {
point.addLocal(shiftVector);
this.set(index, point, clonePosition, cloneBindPosePosition, position, bindPosePosition);
if (cloneNormals != null) {
this.get(cloneNormals, index, normal);
normal.set(mirrorIndex, 0);
this.set(index, normal, cloneNormals, cloneBindPoseNormals);
}
} else {
point.addLocal(shiftVector.multLocal(2));
this.set(index, point, clonePosition, cloneBindPosePosition);
if (cloneNormals != null) {
this.get(cloneNormals, index, normal);
normal.set(mirrorIndex, -normal.get(mirrorIndex));
this.set(index, normal, cloneNormals, cloneBindPoseNormals);
}
}
}
}
modifiedIndexes.clear();
LOGGER.finer("Flipping index order.");
switch (mesh.getMode()) {
case Points:
cloneIndexes.flip();
break;
case Lines:
for (int i = 0; i < cloneIndexes.limit(); i += 2) {
if (cloneIndexes instanceof ShortBuffer) {
short index = ((ShortBuffer) cloneIndexes).get(i + 1);
((ShortBuffer) cloneIndexes).put(i + 1, ((ShortBuffer) cloneIndexes).get(i));
((ShortBuffer) cloneIndexes).put(i, index);
} else {
int index = ((IntBuffer) cloneIndexes).get(i + 1);
((IntBuffer) cloneIndexes).put(i + 1, ((IntBuffer) cloneIndexes).get(i));
((IntBuffer) cloneIndexes).put(i, index);
}
}
break;
case Triangles:
for (int i = 0; i < cloneIndexes.limit(); i += 3) {
if (cloneIndexes instanceof ShortBuffer) {
short index = ((ShortBuffer) cloneIndexes).get(i + 2);
((ShortBuffer) cloneIndexes).put(i + 2, ((ShortBuffer) cloneIndexes).get(i + 1));
((ShortBuffer) cloneIndexes).put(i + 1, index);
} else {
int index = ((IntBuffer) cloneIndexes).get(i + 2);
((IntBuffer) cloneIndexes).put(i + 2, ((IntBuffer) cloneIndexes).get(i + 1));
((IntBuffer) cloneIndexes).put(i + 1, index);
}
}
break;
default:
throw new IllegalStateException("Invalid mesh mode: " + mesh.getMode());
}
if (mirrorU && clone.getBuffer(Type.TexCoord) != null) {
LOGGER.finer("Mirroring U coordinates.");
FloatBuffer cloneUVs = (FloatBuffer) clone.getBuffer(Type.TexCoord).getData();
for (int i = 0; i < cloneUVs.limit(); i += 2) {
cloneUVs.put(i, 1.0f - cloneUVs.get(i));
}
}
if (mirrorV && clone.getBuffer(Type.TexCoord) != null) {
LOGGER.finer("Mirroring V coordinates.");
FloatBuffer cloneUVs = (FloatBuffer) clone.getBuffer(Type.TexCoord).getData();
for (int i = 1; i < cloneUVs.limit(); i += 2) {
cloneUVs.put(i, 1.0f - cloneUVs.get(i));
}
}
Geometry geometry = new Geometry(spatial.getName() + " - mirror " + mirrorNames[mirrorIndex], clone);
geometry.setMaterial(((Geometry) spatial).getMaterial());
geometriesToAdd.add(geometry);
}
}
LOGGER.log(Level.FINE, "Adding {0} geometries to current node.", geometriesToAdd.size());
for (Geometry geometry : geometriesToAdd) {
node.attachChild(geometry);
}
geometriesToAdd.clear();
}
}
}
}
/**
* 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));
}
/**
* Sets the given value (v) into every of the buffers at the given index.
* The index is cosidered to be an index of a vertex of the mesh.
* @param index
* the index of vertex of the mesh
* @param value
* the value to be set
* @param buffers
* the buffers where the value will be set
*/
private void set(int index, Vector3f value, FloatBuffer... buffers) {
index *= 3;
for (FloatBuffer buffer : buffers) {
if (buffer != null) {
buffer.put(index, value.x);
buffer.put(index + 1, value.y);
buffer.put(index + 2, value.z);
}
}
}
/**
* Fetches the vector's value from the given buffer at specified index.
* @param buffer
* the buffer we get the data from
* @param index
* the index of vertex of the mesh
* @param store
* the vector where the result will be set
*/
private void get(FloatBuffer buffer, int index, Vector3f store) {
index *= 3;
store.x = buffer.get(index);
store.y = buffer.get(index + 1);
store.z = buffer.get(index + 2);
}
}