Refactoring: tabs replaced by spaces in blender importer sources. (4 spaces for each tab)

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10401 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
Kae..pl 12 years ago
parent 9f4544a3a8
commit ecc8d8387b
  1. 1414
      engine/src/blender/com/jme3/asset/BlenderKey.java
  2. 7
      engine/src/blender/com/jme3/asset/GeneratedTextureKey.java
  3. 220
      engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java
  4. 240
      engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java
  5. 1243
      engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java
  6. 306
      engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java
  7. 32
      engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java
  8. 400
      engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java
  9. 422
      engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java
  10. 210
      engine/src/blender/com/jme3/scene/plugins/blender/animations/CalculationBone.java
  11. 460
      engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java
  12. 356
      engine/src/blender/com/jme3/scene/plugins/blender/animations/IpoHelper.java
  13. 104
      engine/src/blender/com/jme3/scene/plugins/blender/cameras/CameraHelper.java
  14. 379
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java
  15. 210
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java
  16. 872
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java
  17. 35
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java
  18. 282
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java
  19. 447
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java
  20. 22
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionAction.java
  21. 22
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionCameraSolver.java
  22. 24
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionChildOf.java
  23. 24
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionClampTo.java
  24. 24
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDampTrack.java
  25. 94
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java
  26. 142
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java
  27. 24
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFollowPath.java
  28. 22
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFollowTrack.java
  29. 258
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionInverseKinematics.java
  30. 114
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java
  31. 114
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java
  32. 24
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLockTrack.java
  33. 24
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionMinMax.java
  34. 18
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java
  35. 22
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionObjectSolver.java
  36. 24
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionPivot.java
  37. 24
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionPython.java
  38. 24
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRigidBodyJoint.java
  39. 96
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java
  40. 136
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java
  41. 24
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSameVolume.java
  42. 99
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionShrinkWrap.java
  43. 76
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java
  44. 126
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java
  45. 24
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSplineInverseKinematic.java
  46. 22
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionStretchTo.java
  47. 20
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTrackTo.java
  48. 24
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransLike.java
  49. 22
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransform.java
  50. 62
      engine/src/blender/com/jme3/scene/plugins/blender/curves/BezierCurve.java
  51. 829
      engine/src/blender/com/jme3/scene/plugins/blender/curves/CurvesHelper.java
  52. 10
      engine/src/blender/com/jme3/scene/plugins/blender/exceptions/BlenderFileException.java
  53. 77
      engine/src/blender/com/jme3/scene/plugins/blender/file/BlenderInputStream.java
  54. 53
      engine/src/blender/com/jme3/scene/plugins/blender/file/DnaBlockData.java
  55. 20
      engine/src/blender/com/jme3/scene/plugins/blender/file/DynamicArray.java
  56. 64
      engine/src/blender/com/jme3/scene/plugins/blender/file/Field.java
  57. 59
      engine/src/blender/com/jme3/scene/plugins/blender/file/FileBlockHeader.java
  58. 37
      engine/src/blender/com/jme3/scene/plugins/blender/file/Pointer.java
  59. 82
      engine/src/blender/com/jme3/scene/plugins/blender/file/Structure.java
  60. 44
      engine/src/blender/com/jme3/scene/plugins/blender/lights/LightHelper.java
  61. 36
      engine/src/blender/com/jme3/scene/plugins/blender/materials/IAlphaMask.java
  62. 721
      engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialContext.java
  63. 813
      engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialHelper.java
  64. 490
      engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshBuilder.java
  65. 242
      engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshContext.java
  66. 468
      engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java
  67. 745
      engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java
  68. 226
      engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java
  69. 168
      engine/src/blender/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java
  70. 118
      engine/src/blender/com/jme3/scene/plugins/blender/modifiers/Modifier.java
  71. 264
      engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java
  72. 124
      engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ObjectAnimationModifier.java
  73. 135
      engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java
  74. 613
      engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java
  75. 690
      engine/src/blender/com/jme3/scene/plugins/blender/objects/Properties.java
  76. 350
      engine/src/blender/com/jme3/scene/plugins/blender/particles/ParticlesHelper.java
  77. 574
      engine/src/blender/com/jme3/scene/plugins/blender/textures/ColorBand.java
  78. 1015
      engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java
  79. 268
      engine/src/blender/com/jme3/scene/plugins/blender/textures/DDSTexelData.java
  80. 224
      engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java
  81. 164
      engine/src/blender/com/jme3/scene/plugins/blender/textures/ImageLoader.java
  82. 1238
      engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureHelper.java
  83. 706
      engine/src/blender/com/jme3/scene/plugins/blender/textures/TexturePixel.java
  84. 1286
      engine/src/blender/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java
  85. 812
      engine/src/blender/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java
  86. 455
      engine/src/blender/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java
  87. 236
      engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java
  88. 76
      engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlender.java
  89. 368
      engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java
  90. 204
      engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java
  91. 158
      engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java
  92. 440
      engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java
  93. 283
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/NoiseGenerator.java
  94. 116
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGenerator.java
  95. 164
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorBlend.java
  96. 122
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorClouds.java
  97. 78
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorDistnoise.java
  98. 66
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorFactory.java
  99. 226
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMagic.java
  100. 98
      engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMarble.java
  101. Some files were not shown because too many files have changed in this diff Show More

File diff suppressed because it is too large Load Diff

@ -36,7 +36,7 @@ package com.jme3.asset;
* This key is mostly used to distinguish between textures that are loaded from * This key is mostly used to distinguish between textures that are loaded from
* the given assets and those being generated automatically. Every generated * the given assets and those being generated automatically. Every generated
* texture will have this kind of key attached. * texture will have this kind of key attached.
* *
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class GeneratedTextureKey extends TextureKey { public class GeneratedTextureKey extends TextureKey {
@ -44,8 +44,9 @@ public class GeneratedTextureKey extends TextureKey {
/** /**
* Constructor. Stores the name. Extension and folder name are empty * Constructor. Stores the name. Extension and folder name are empty
* strings. * strings.
* *
* @param name the name of the texture * @param name
* the name of the texture
*/ */
public GeneratedTextureKey(String name) { public GeneratedTextureKey(String name) {
super(name); super(name);

@ -50,116 +50,116 @@ import com.jme3.scene.plugins.blender.objects.Properties;
*/ */
public abstract class AbstractBlenderHelper { public abstract class AbstractBlenderHelper {
/** The version of the blend file. */ /** The version of the blend file. */
protected final int blenderVersion; protected final int blenderVersion;
/** This variable indicates if the Y asxis is the UP axis or not. */ /** This variable indicates if the Y asxis is the UP axis or not. */
protected boolean fixUpAxis; protected boolean fixUpAxis;
/** Quaternion used to rotate data when Y is up axis. */ /** Quaternion used to rotate data when Y is up axis. */
protected Quaternion upAxisRotationQuaternion; protected Quaternion upAxisRotationQuaternion;
/**
* This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
* versions.
* @param blenderVersion
* the version read from the blend file
* @param fixUpAxis
* a variable that indicates if the Y asxis is the UP axis or not
*/
public AbstractBlenderHelper(String blenderVersion, boolean fixUpAxis) {
this.blenderVersion = Integer.parseInt(blenderVersion);
this.fixUpAxis = fixUpAxis;
if(fixUpAxis) {
upAxisRotationQuaternion = new Quaternion().fromAngles(-FastMath.HALF_PI, 0, 0);
}
}
/**
* This method clears the state of the helper so that it can be used for different calculations of another feature.
*/
public void clearState() {}
/** /**
* This method should be used to check if the text is blank. Avoid using text.trim().length()==0. This causes that more strings are * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
* being created and stored in the memory. It can be unwise especially inside loops. * versions.
* @param text * @param blenderVersion
* the text to be checked * the version read from the blend file
* @return <b>true</b> if the text is blank and <b>false</b> otherwise * @param fixUpAxis
*/ * a variable that indicates if the Y asxis is the UP axis or not
protected boolean isBlank(String text) { */
if (text != null) { public AbstractBlenderHelper(String blenderVersion, boolean fixUpAxis) {
for (int i = 0; i < text.length(); ++i) { this.blenderVersion = Integer.parseInt(blenderVersion);
if (!Character.isWhitespace(text.charAt(i))) { this.fixUpAxis = fixUpAxis;
return false; if (fixUpAxis) {
} upAxisRotationQuaternion = new Quaternion().fromAngles(-FastMath.HALF_PI, 0, 0);
} }
} }
return true;
}
/** /**
* This method loads the properties if they are available and defined for the structure. * This method clears the state of the helper so that it can be used for different calculations of another feature.
* @param structure */
* the structure we read the properties from public void clearState() {
* @param blenderContext }
* the blender context
* @return loaded properties or null if they are not available /**
* @throws BlenderFileException * This method should be used to check if the text is blank. Avoid using text.trim().length()==0. This causes that more strings are
* an exception is thrown when the blend file is somehow corrupted * being created and stored in the memory. It can be unwise especially inside loops.
*/ * @param text
protected Properties loadProperties(Structure structure, BlenderContext blenderContext) throws BlenderFileException { * the text to be checked
Properties properties = null; * @return <b>true</b> if the text is blank and <b>false</b> otherwise
Structure id = (Structure) structure.getFieldValue("ID"); */
if (id != null) { protected boolean isBlank(String text) {
Pointer pProperties = (Pointer) id.getFieldValue("properties"); if (text != null) {
if (pProperties.isNotNull()) { for (int i = 0; i < text.length(); ++i) {
Structure propertiesStructure = pProperties.fetchData(blenderContext.getInputStream()).get(0); if (!Character.isWhitespace(text.charAt(i))) {
properties = new Properties(); return false;
properties.load(propertiesStructure, blenderContext); }
} }
} }
return properties; return true;
} }
/** /**
* The method applies properties to the given spatial. The Properties * This method loads the properties if they are available and defined for the structure.
* instance cannot be directly applied because the end-user might not have * @param structure
* the blender plugin jar file and thus receive ClassNotFoundException. The * the structure we read the properties from
* values are set by name instead. * @param blenderContext
* * the blender context
* @param spatial * @return loaded properties or null if they are not available
* the spatial that is to have properties applied * @throws BlenderFileException
* @param properties * an exception is thrown when the blend file is somehow corrupted
* the properties to be applied */
*/ protected Properties loadProperties(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
protected void applyProperties(Spatial spatial, Properties properties) { Properties properties = null;
List<String> propertyNames = properties.getSubPropertiesNames(); Structure id = (Structure) structure.getFieldValue("ID");
if(propertyNames != null && propertyNames.size() > 0) { if (id != null) {
for(String propertyName : propertyNames) { Pointer pProperties = (Pointer) id.getFieldValue("properties");
Object value = properties.findValue(propertyName); if (pProperties.isNotNull()) {
if(value instanceof Savable || value instanceof Boolean || value instanceof String || Structure propertiesStructure = pProperties.fetchData(blenderContext.getInputStream()).get(0);
value instanceof Float || value instanceof Integer || value instanceof Long) { properties = new Properties();
spatial.setUserData(propertyName, value); properties.load(propertiesStructure, blenderContext);
} else if(value instanceof Double) { }
spatial.setUserData(propertyName, ((Double) value).floatValue()); }
} else if(value instanceof int[]) { return properties;
spatial.setUserData(propertyName, Arrays.toString((int[])value)); }
} else if(value instanceof float[]) {
spatial.setUserData(propertyName, Arrays.toString((float[])value)); /**
} else if(value instanceof double[]) { * The method applies properties to the given spatial. The Properties
spatial.setUserData(propertyName, Arrays.toString((double[])value)); * instance cannot be directly applied because the end-user might not have
} * the blender plugin jar file and thus receive ClassNotFoundException. The
} * values are set by name instead.
} *
} * @param spatial
* the spatial that is to have properties applied
/** * @param properties
* This method analyzes the given structure and the data contained within * the properties to be applied
* blender context and decides if the feature should be loaded. */
* @param structure protected void applyProperties(Spatial spatial, Properties properties) {
* structure to be analyzed List<String> propertyNames = properties.getSubPropertiesNames();
* @param blenderContext if (propertyNames != null && propertyNames.size() > 0) {
* the blender context for (String propertyName : propertyNames) {
* @return <b>true</b> if the feature should be loaded and false otherwise Object value = properties.findValue(propertyName);
*/ if (value instanceof Savable || value instanceof Boolean || value instanceof String || value instanceof Float || value instanceof Integer || value instanceof Long) {
public abstract boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext); spatial.setUserData(propertyName, value);
} else if (value instanceof Double) {
spatial.setUserData(propertyName, ((Double) value).floatValue());
} else if (value instanceof int[]) {
spatial.setUserData(propertyName, Arrays.toString((int[]) value));
} else if (value instanceof float[]) {
spatial.setUserData(propertyName, Arrays.toString((float[]) value));
} else if (value instanceof double[]) {
spatial.setUserData(propertyName, Arrays.toString((double[]) value));
}
}
}
}
/**
* This method analyzes the given structure and the data contained within
* blender context and decides if the feature should be loaded.
* @param structure
* structure to be analyzed
* @param blenderContext
* the blender context
* @return <b>true</b> if the feature should be loaded and false otherwise
*/
public abstract boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext);
} }

@ -57,134 +57,134 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
* This class converts blender file blocks into jMonkeyEngine data structures. * This class converts blender file blocks into jMonkeyEngine data structures.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */ abstract class AbstractBlenderLoader implements AssetLoader { /* package */abstract class AbstractBlenderLoader implements AssetLoader {
private static final Logger LOGGER = Logger.getLogger(AbstractBlenderLoader.class.getName()); private static final Logger LOGGER = Logger.getLogger(AbstractBlenderLoader.class.getName());
protected BlenderContext blenderContext;
/** protected BlenderContext blenderContext;
* This method converts the given structure to a scene node.
* @param structure /**
* structure of a scene * This method converts the given structure to a scene node.
* @return scene's node * @param structure
*/ * structure of a scene
public Node toScene(Structure structure) { * @return scene's node
if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.SCENES) == 0) { */
return null; public Node toScene(Structure structure) {
} if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.SCENES) == 0) {
Node result = new Node(structure.getName()); return null;
try { }
List<Structure> base = ((Structure)structure.getFieldValue("base")).evaluateListBase(blenderContext); Node result = new Node(structure.getName());
for(Structure b : base) { try {
Pointer pObject = (Pointer) b.getFieldValue("object"); List<Structure> base = ((Structure) structure.getFieldValue("base")).evaluateListBase(blenderContext);
if(pObject.isNotNull()) { for (Structure b : base) {
Structure objectStructure = pObject.fetchData(blenderContext.getInputStream()).get(0); Pointer pObject = (Pointer) b.getFieldValue("object");
Object object = this.toObject(objectStructure); if (pObject.isNotNull()) {
if(object instanceof LightNode && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { Structure objectStructure = pObject.fetchData(blenderContext.getInputStream()).get(0);
result.addLight(((LightNode)object).getLight()); Object object = this.toObject(objectStructure);
result.attachChild((LightNode) object); if (object instanceof LightNode && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {
} else if (object instanceof Node && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) { result.addLight(((LightNode) object).getLight());
LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); result.attachChild((LightNode) object);
if (((Node) object).getParent() == null) { } else if (object instanceof Node && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) {
result.attachChild((Spatial) object); LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() });
if (((Node) object).getParent() == null) {
result.attachChild((Spatial) object);
} }
} }
} }
} }
} catch (BlenderFileException e) { } catch (BlenderFileException e) {
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
} }
return result; return result;
} }
/** /**
* This method converts the given structure to a camera. * This method converts the given structure to a camera.
* @param structure * @param structure
* structure of a camera * structure of a camera
* @return camera's node * @return camera's node
*/ */
public CameraNode toCamera(Structure structure) throws BlenderFileException { public CameraNode toCamera(Structure structure) throws BlenderFileException {
CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class); CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class);
if (cameraHelper.shouldBeLoaded(structure, blenderContext)) { if (cameraHelper.shouldBeLoaded(structure, blenderContext)) {
return cameraHelper.toCamera(structure, blenderContext); return cameraHelper.toCamera(structure, blenderContext);
} }
return null; return null;
} }
/** /**
* This method converts the given structure to a light. * This method converts the given structure to a light.
* @param structure * @param structure
* structure of a light * structure of a light
* @return light's node * @return light's node
*/ */
public LightNode toLight(Structure structure) throws BlenderFileException { public LightNode toLight(Structure structure) throws BlenderFileException {
LightHelper lightHelper = blenderContext.getHelper(LightHelper.class); LightHelper lightHelper = blenderContext.getHelper(LightHelper.class);
if (lightHelper.shouldBeLoaded(structure, blenderContext)) { if (lightHelper.shouldBeLoaded(structure, blenderContext)) {
return lightHelper.toLight(structure, blenderContext); return lightHelper.toLight(structure, blenderContext);
} }
return null; return null;
} }
/** /**
* This method converts the given structure to a node. * This method converts the given structure to a node.
* @param structure * @param structure
* structure of an object * structure of an object
* @return object's node * @return object's node
*/ */
public Object toObject(Structure structure) throws BlenderFileException { public Object toObject(Structure structure) throws BlenderFileException {
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
if (objectHelper.shouldBeLoaded(structure, blenderContext)) { if (objectHelper.shouldBeLoaded(structure, blenderContext)) {
return objectHelper.toObject(structure, blenderContext); return objectHelper.toObject(structure, blenderContext);
} }
return null; return null;
} }
/** /**
* This method converts the given structure to a list of geometries. * This method converts the given structure to a list of geometries.
* @param structure * @param structure
* structure of a mesh * structure of a mesh
* @return list of geometries * @return list of geometries
*/ */
public List<Geometry> toMesh(Structure structure) throws BlenderFileException { public List<Geometry> toMesh(Structure structure) throws BlenderFileException {
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
if (meshHelper.shouldBeLoaded(structure, blenderContext)) { if (meshHelper.shouldBeLoaded(structure, blenderContext)) {
return meshHelper.toMesh(structure, blenderContext); return meshHelper.toMesh(structure, blenderContext);
} }
return null; return null;
} }
// /** // /**
// * This method converts the given structure to a material. // * This method converts the given structure to a material.
// * @param structure // * @param structure
// * structure of a material // * structure of a material
// * @return material's node // * @return material's node
// */ // */
// public Material toMaterial(Structure structure) throws BlenderFileException { // public Material toMaterial(Structure structure) throws BlenderFileException {
// MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); // MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
// if (materialHelper.shouldBeLoaded(structure, blenderContext)) { // if (materialHelper.shouldBeLoaded(structure, blenderContext)) {
// return materialHelper.toMaterial(structure, blenderContext); // return materialHelper.toMaterial(structure, blenderContext);
// } // }
// return null; // return null;
// } // }
/** /**
* This method returns the data read from the WORLD file block. The block contains data that can be stored as * This method returns the data read from the WORLD file block. The block contains data that can be stored as
* separate jme features and therefore cannot be returned as a single jME scene feature. * separate jme features and therefore cannot be returned as a single jME scene feature.
* @param structure * @param structure
* the structure with WORLD block data * the structure with WORLD block data
* @return data read from the WORLD block that can be added to the scene * @return data read from the WORLD block that can be added to the scene
*/ */
public WorldData toWorldData(Structure structure) { public WorldData toWorldData(Structure structure) {
WorldData result = new WorldData(); WorldData result = new WorldData();
// reading ambient light // reading ambient light
AmbientLight ambientLight = new AmbientLight(); AmbientLight ambientLight = new AmbientLight();
float ambr = ((Number) structure.getFieldValue("ambr")).floatValue(); float ambr = ((Number) structure.getFieldValue("ambr")).floatValue();
float ambg = ((Number) structure.getFieldValue("ambg")).floatValue(); float ambg = ((Number) structure.getFieldValue("ambg")).floatValue();
float ambb = ((Number) structure.getFieldValue("ambb")).floatValue(); float ambb = ((Number) structure.getFieldValue("ambb")).floatValue();
ambientLight.setColor(new ColorRGBA(ambr, ambg, ambb, 0.0f)); ambientLight.setColor(new ColorRGBA(ambr, ambg, ambb, 0.0f));
result.setAmbientLight(ambientLight); result.setAmbientLight(ambientLight);
return result; return result;
} }
} }

@ -70,158 +70,158 @@ import com.jme3.scene.plugins.blender.textures.TextureHelper;
*/ */
public class BlenderLoader extends AbstractBlenderLoader { public class BlenderLoader extends AbstractBlenderLoader {
private static final Logger LOGGER = Logger.getLogger(BlenderLoader.class.getName()); private static final Logger LOGGER = Logger.getLogger(BlenderLoader.class.getName());
/** The blocks read from the file. */ /** The blocks read from the file. */
protected List<FileBlockHeader> blocks; protected List<FileBlockHeader> blocks;
public Spatial load(AssetInfo assetInfo) throws IOException { public Spatial load(AssetInfo assetInfo) throws IOException {
try { try {
this.setup(assetInfo); this.setup(assetInfo);
List<FileBlockHeader> sceneBlocks = new ArrayList<FileBlockHeader>(); List<FileBlockHeader> sceneBlocks = new ArrayList<FileBlockHeader>();
BlenderKey blenderKey = blenderContext.getBlenderKey(); BlenderKey blenderKey = blenderContext.getBlenderKey();
LoadingResults loadingResults = blenderKey.prepareLoadingResults(); LoadingResults loadingResults = blenderKey.prepareLoadingResults();
WorldData worldData = null;// a set of data used in different scene aspects WorldData worldData = null;// a set of data used in different scene aspects
for (FileBlockHeader block : blocks) { for (FileBlockHeader block : blocks) {
switch (block.getCode()) { switch (block.getCode()) {
case FileBlockHeader.BLOCK_OB00:// Object case FileBlockHeader.BLOCK_OB00:// Object
Object object = this.toObject(block.getStructure(blenderContext)); Object object = this.toObject(block.getStructure(blenderContext));
if(object instanceof LightNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { if (object instanceof LightNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {
loadingResults.addLight((LightNode) object); loadingResults.addLight((LightNode) object);
} else if (object instanceof CameraNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.CAMERAS) != 0) { } else if (object instanceof CameraNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.CAMERAS) != 0) {
loadingResults.addCamera((CameraNode) object); loadingResults.addCamera((CameraNode) object);
} else if (object instanceof Node && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) { } else if (object instanceof Node && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) {
LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() });
if (this.isRootObject(loadingResults, (Node)object)) { if (this.isRootObject(loadingResults, (Node) object)) {
loadingResults.addObject((Node) object); loadingResults.addObject((Node) object);
} }
} }
break; break;
// case FileBlockHeader.BLOCK_MA00:// Material // case FileBlockHeader.BLOCK_MA00:// Material
// if (blenderKey.isLoadUnlinkedAssets() && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) { // if (blenderKey.isLoadUnlinkedAssets() && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {
// loadingResults.addMaterial(this.toMaterial(block.getStructure(blenderContext))); // loadingResults.addMaterial(this.toMaterial(block.getStructure(blenderContext)));
// } // }
// break; // break;
case FileBlockHeader.BLOCK_SC00:// Scene case FileBlockHeader.BLOCK_SC00:// Scene
if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.SCENES) != 0) { if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.SCENES) != 0) {
sceneBlocks.add(block); sceneBlocks.add(block);
} }
break; break;
case FileBlockHeader.BLOCK_WO00:// World case FileBlockHeader.BLOCK_WO00:// World
if (blenderKey.isLoadUnlinkedAssets() && worldData == null) {// onlu one world data is used if (blenderKey.isLoadUnlinkedAssets() && worldData == null) {// onlu one world data is used
Structure worldStructure = block.getStructure(blenderContext); Structure worldStructure = block.getStructure(blenderContext);
String worldName = worldStructure.getName(); String worldName = worldStructure.getName();
if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) { if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) {
worldData = this.toWorldData(worldStructure); worldData = this.toWorldData(worldStructure);
if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {
loadingResults.addLight(worldData.getAmbientLight()); loadingResults.addLight(worldData.getAmbientLight());
} }
} }
} }
break; break;
} }
} }
//bake constraints after everything is loaded // bake constraints after everything is loaded
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
constraintHelper.bakeConstraints(blenderContext); constraintHelper.bakeConstraints(blenderContext);
//load the scene at the very end so that the root nodes have no parent during loading or constraints applying // load the scene at the very end so that the root nodes have no parent during loading or constraints applying
for(FileBlockHeader sceneBlock : sceneBlocks) { for (FileBlockHeader sceneBlock : sceneBlocks) {
loadingResults.addScene(this.toScene(sceneBlock.getStructure(blenderContext))); loadingResults.addScene(this.toScene(sceneBlock.getStructure(blenderContext)));
} }
blenderContext.dispose(); blenderContext.dispose();
return loadingResults; return loadingResults;
} catch (BlenderFileException e) { } catch (BlenderFileException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e); LOGGER.log(Level.SEVERE, e.getMessage(), e);
} }
return null; return null;
} }
/** /**
* This method indicates if the given spatial is a root object. It means it * This method indicates if the given spatial is a root object. It means it
* has no parent or is directly attached to one of the already loaded scene * has no parent or is directly attached to one of the already loaded scene
* nodes. * nodes.
* *
* @param loadingResults * @param loadingResults
* loading results containing the scene nodes * loading results containing the scene nodes
* @param spatial * @param spatial
* spatial object * spatial object
* @return <b>true</b> if the given spatial is a root object and * @return <b>true</b> if the given spatial is a root object and
* <b>false</b> otherwise * <b>false</b> otherwise
*/ */
protected boolean isRootObject(LoadingResults loadingResults, Spatial spatial) { protected boolean isRootObject(LoadingResults loadingResults, Spatial spatial) {
if(spatial.getParent() == null) { if (spatial.getParent() == null) {
return true; return true;
} }
for(Node scene : loadingResults.getScenes()) { for (Node scene : loadingResults.getScenes()) {
if(spatial.getParent().equals(scene)) { if (spatial.getParent().equals(scene)) {
return true; return true;
} }
} }
return false; return false;
} }
/** /**
* This method sets up the loader. * This method sets up the loader.
* @param assetInfo * @param assetInfo
* the asset info * the asset info
* @throws BlenderFileException * @throws BlenderFileException
* an exception is throw when something wrong happens with blender file * an exception is throw when something wrong happens with blender file
*/ */
protected void setup(AssetInfo assetInfo) throws BlenderFileException { protected void setup(AssetInfo assetInfo) throws BlenderFileException {
// registering loaders // registering loaders
ModelKey modelKey = (ModelKey) assetInfo.getKey(); ModelKey modelKey = (ModelKey) assetInfo.getKey();
BlenderKey blenderKey; BlenderKey blenderKey;
if (modelKey instanceof BlenderKey) { if (modelKey instanceof BlenderKey) {
blenderKey = (BlenderKey) modelKey; blenderKey = (BlenderKey) modelKey;
} else { } else {
blenderKey = new BlenderKey(modelKey.getName()); blenderKey = new BlenderKey(modelKey.getName());
blenderKey.setAssetRootPath(modelKey.getFolder()); blenderKey.setAssetRootPath(modelKey.getFolder());
} }
// opening stream // opening stream
BlenderInputStream inputStream = new BlenderInputStream(assetInfo.openStream()); BlenderInputStream inputStream = new BlenderInputStream(assetInfo.openStream());
// reading blocks // reading blocks
blocks = new ArrayList<FileBlockHeader>(); blocks = new ArrayList<FileBlockHeader>();
FileBlockHeader fileBlock; FileBlockHeader fileBlock;
blenderContext = new BlenderContext(); blenderContext = new BlenderContext();
blenderContext.setBlenderVersion(inputStream.getVersionNumber()); blenderContext.setBlenderVersion(inputStream.getVersionNumber());
blenderContext.setAssetManager(assetInfo.getManager()); blenderContext.setAssetManager(assetInfo.getManager());
blenderContext.setInputStream(inputStream); blenderContext.setInputStream(inputStream);
blenderContext.setBlenderKey(blenderKey); blenderContext.setBlenderKey(blenderKey);
// creating helpers // creating helpers
blenderContext.putHelper(ArmatureHelper.class, new ArmatureHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); blenderContext.putHelper(ArmatureHelper.class, new ArmatureHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
blenderContext.putHelper(TextureHelper.class, new TextureHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); blenderContext.putHelper(TextureHelper.class, new TextureHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
blenderContext.putHelper(MeshHelper.class, new MeshHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); blenderContext.putHelper(MeshHelper.class, new MeshHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
blenderContext.putHelper(ObjectHelper.class, new ObjectHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); blenderContext.putHelper(ObjectHelper.class, new ObjectHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
blenderContext.putHelper(CurvesHelper.class, new CurvesHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); blenderContext.putHelper(CurvesHelper.class, new CurvesHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
blenderContext.putHelper(LightHelper.class, new LightHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); blenderContext.putHelper(LightHelper.class, new LightHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
blenderContext.putHelper(CameraHelper.class, new CameraHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); blenderContext.putHelper(CameraHelper.class, new CameraHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
blenderContext.putHelper(ModifierHelper.class, new ModifierHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); blenderContext.putHelper(ModifierHelper.class, new ModifierHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
blenderContext.putHelper(MaterialHelper.class, new MaterialHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); blenderContext.putHelper(MaterialHelper.class, new MaterialHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext, blenderKey.isFixUpAxis())); blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext, blenderKey.isFixUpAxis()));
blenderContext.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); blenderContext.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis()));
// reading the blocks (dna block is automatically saved in the blender context when found) // reading the blocks (dna block is automatically saved in the blender context when found)
FileBlockHeader sceneFileBlock = null; FileBlockHeader sceneFileBlock = null;
do { do {
fileBlock = new FileBlockHeader(inputStream, blenderContext); fileBlock = new FileBlockHeader(inputStream, blenderContext);
if (!fileBlock.isDnaBlock()) { if (!fileBlock.isDnaBlock()) {
blocks.add(fileBlock); blocks.add(fileBlock);
// save the scene's file block // save the scene's file block
if (fileBlock.getCode() == FileBlockHeader.BLOCK_SC00) { if (fileBlock.getCode() == FileBlockHeader.BLOCK_SC00) {
sceneFileBlock = fileBlock; sceneFileBlock = fileBlock;
} }
} }
} while (!fileBlock.isLastBlock()); } while (!fileBlock.isLastBlock());
if (sceneFileBlock != null) { if (sceneFileBlock != null) {
blenderContext.setSceneStructure(sceneFileBlock.getStructure(blenderContext)); blenderContext.setSceneStructure(sceneFileBlock.getStructure(blenderContext));
} }
} }
} }

@ -57,30 +57,30 @@ public class BlenderModelLoader extends BlenderLoader {
public Spatial load(AssetInfo assetInfo) throws IOException { public Spatial load(AssetInfo assetInfo) throws IOException {
try { try {
this.setup(assetInfo); this.setup(assetInfo);
BlenderKey blenderKey = blenderContext.getBlenderKey(); BlenderKey blenderKey = blenderContext.getBlenderKey();
Node modelRoot = new Node(blenderKey.getName()); Node modelRoot = new Node(blenderKey.getName());
for (FileBlockHeader block : blocks) { for (FileBlockHeader block : blocks) {
if (block.getCode() == FileBlockHeader.BLOCK_OB00) { if (block.getCode() == FileBlockHeader.BLOCK_OB00) {
Object object = this.toObject(block.getStructure(blenderContext)); Object object = this.toObject(block.getStructure(blenderContext));
if(object instanceof LightNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { if (object instanceof LightNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {
modelRoot.addLight(((LightNode)object).getLight()); modelRoot.addLight(((LightNode) object).getLight());
modelRoot.attachChild((LightNode)object); modelRoot.attachChild((LightNode) object);
} else if (object instanceof Node && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) { } else if (object instanceof Node && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) {
LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() });
if (((Node) object).getParent() == null) { if (((Node) object).getParent() == null) {
modelRoot.attachChild((Node)object); modelRoot.attachChild((Node) object);
} }
} }
} }
} }
//bake constraints after everything is loaded // bake constraints after everything is loaded
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
constraintHelper.bakeConstraints(blenderContext); constraintHelper.bakeConstraints(blenderContext);
blenderContext.dispose(); blenderContext.dispose();
return modelRoot; return modelRoot;
} catch (BlenderFileException e) { } catch (BlenderFileException e) {

@ -56,215 +56,215 @@ import java.util.logging.Logger;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class ArmatureHelper extends AbstractBlenderHelper { public class ArmatureHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(ArmatureHelper.class.getName()); private static final Logger LOGGER = Logger.getLogger(ArmatureHelper.class.getName());
public static final String ARMETURE_NODE_MARKER = "armeture-node"; public static final String ARMETURE_NODE_MARKER = "armeture-node";
/** A map of bones and their old memory addresses. */
private Map<Bone, Long> bonesOMAs = new HashMap<Bone, Long>();
/** /** A map of bones and their old memory addresses. */
* This constructor parses the given blender version and stores the result. private Map<Bone, Long> bonesOMAs = new HashMap<Bone, Long>();
* Some functionalities may differ in different blender versions.
*
* @param blenderVersion
* the version read from the blend file
* @param fixUpAxis
* a variable that indicates if the Y asxis is the UP axis or not
*/
public ArmatureHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis);
}
/** /**
* This method builds the object's bones structure. * This constructor parses the given blender version and stores the result.
* * Some functionalities may differ in different blender versions.
* @param boneStructure *
* the structure containing the bones' data * @param blenderVersion
* @param parent * the version read from the blend file
* the parent bone * @param fixUpAxis
* @param result * a variable that indicates if the Y asxis is the UP axis or not
* the list where the newly created bone will be added */
* @param bonesPoseChannels public ArmatureHelper(String blenderVersion, boolean fixUpAxis) {
* a map of bones poses channels super(blenderVersion, fixUpAxis);
* @param blenderContext }
* the blender context
* @throws BlenderFileException
* an exception is thrown when there is problem with the blender
* file
*/
public void buildBones(Long armatureObjectOMA, Structure boneStructure, Bone parent, List<Bone> result, Matrix4f arbt, final Map<Long, Structure> bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException {
BoneContext bc = new BoneContext(armatureObjectOMA, boneStructure, arbt, bonesPoseChannels, blenderContext);
bc.buildBone(result, bonesOMAs, blenderContext);
}
/** /**
* This method returns the old memory address of a bone. If the bone does * This method builds the object's bones structure.
* not exist in the blend file - zero is returned. *
* * @param boneStructure
* @param bone * the structure containing the bones' data
* the bone whose old memory address we seek * @param parent
* @return the old memory address of the given bone * the parent bone
*/ * @param result
public Long getBoneOMA(Bone bone) { * the list where the newly created bone will be added
Long result = bonesOMAs.get(bone); * @param bonesPoseChannels
if (result == null) { * a map of bones poses channels
result = Long.valueOf(0); * @param blenderContext
} * the blender context
return result; * @throws BlenderFileException
} * an exception is thrown when there is problem with the blender
* file
*/
public void buildBones(Long armatureObjectOMA, Structure boneStructure, Bone parent, List<Bone> result, Matrix4f arbt, final Map<Long, Structure> bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException {
BoneContext bc = new BoneContext(armatureObjectOMA, boneStructure, arbt, bonesPoseChannels, blenderContext);
bc.buildBone(result, bonesOMAs, blenderContext);
}
/** /**
* This method returns a map where the key is the object's group index that * This method returns the old memory address of a bone. If the bone does
* is used by a bone and the key is the bone index in the armature. * not exist in the blend file - zero is returned.
* *
* @param defBaseStructure * @param bone
* a bPose structure of the object * the bone whose old memory address we seek
* @return bone group-to-index map * @return the old memory address of the given bone
* @throws BlenderFileException */
* this exception is thrown when the blender file is somehow public Long getBoneOMA(Bone bone) {
* corrupted Long result = bonesOMAs.get(bone);
*/ if (result == null) {
public Map<Integer, Integer> getGroupToBoneIndexMap(Structure defBaseStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { result = Long.valueOf(0);
Map<Integer, Integer> result = null; }
if (skeleton.getBoneCount() != 0) { return result;
result = new HashMap<Integer, Integer>(); }
List<Structure> deformGroups = defBaseStructure.evaluateListBase(blenderContext);// bDeformGroup
int groupIndex = 0;
for (Structure deformGroup : deformGroups) {
String deformGroupName = deformGroup.getFieldValue("name").toString();
int boneIndex = this.getBoneIndex(skeleton, deformGroupName);
if (boneIndex >= 0) {
result.put(groupIndex, boneIndex);
}
++groupIndex;
}
}
return result;
}
@Override /**
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { * This method returns a map where the key is the object's group index that
return true; * is used by a bone and the key is the bone index in the armature.
} *
* @param defBaseStructure
* a bPose structure of the object
* @return bone group-to-index map
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
public Map<Integer, Integer> getGroupToBoneIndexMap(Structure defBaseStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException {
Map<Integer, Integer> result = null;
if (skeleton.getBoneCount() != 0) {
result = new HashMap<Integer, Integer>();
List<Structure> deformGroups = defBaseStructure.evaluateListBase(blenderContext);// bDeformGroup
int groupIndex = 0;
for (Structure deformGroup : deformGroups) {
String deformGroupName = deformGroup.getFieldValue("name").toString();
int boneIndex = this.getBoneIndex(skeleton, deformGroupName);
if (boneIndex >= 0) {
result.put(groupIndex, boneIndex);
}
++groupIndex;
}
}
return result;
}
/** @Override
* This method retuns the bone tracks for animation. public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
* return true;
* @param actionStructure }
* the structure containing the tracks
* @param blenderContext
* the blender context
* @return a list of tracks for the specified animation
* @throws BlenderFileException
* an exception is thrown when there are problems with the blend
* file
*/
public BoneTrack[] getTracks(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException {
if (blenderVersion < 250) {
return this.getTracks249(actionStructure, skeleton, blenderContext);
} else {
return this.getTracks250(actionStructure, skeleton, blenderContext);
}
}
/** /**
* This method retuns the bone tracks for animation for blender version 2.50 * This method retuns the bone tracks for animation.
* and higher. *
* * @param actionStructure
* @param actionStructure * the structure containing the tracks
* the structure containing the tracks * @param blenderContext
* @param blenderContext * the blender context
* the blender context * @return a list of tracks for the specified animation
* @return a list of tracks for the specified animation * @throws BlenderFileException
* @throws BlenderFileException * an exception is thrown when there are problems with the blend
* an exception is thrown when there are problems with the blend * file
* file */
*/ public BoneTrack[] getTracks(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException {
private BoneTrack[] getTracks250(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { if (blenderVersion < 250) {
LOGGER.log(Level.FINE, "Getting tracks!"); return this.getTracks249(actionStructure, skeleton, blenderContext);
IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); } else {
int fps = blenderContext.getBlenderKey().getFps(); return this.getTracks250(actionStructure, skeleton, blenderContext);
Structure groups = (Structure) actionStructure.getFieldValue("groups"); }
List<Structure> actionGroups = groups.evaluateListBase(blenderContext);// bActionGroup }
List<BoneTrack> tracks = new ArrayList<BoneTrack>();
for (Structure actionGroup : actionGroups) {
String name = actionGroup.getFieldValue("name").toString();
int boneIndex = this.getBoneIndex(skeleton, name);
if (boneIndex >= 0) {
List<Structure> channels = ((Structure) actionGroup.getFieldValue("channels")).evaluateListBase(blenderContext);
BezierCurve[] bezierCurves = new BezierCurve[channels.size()];
int channelCounter = 0;
for (Structure c : channels) {
int type = ipoHelper.getCurveType(c, blenderContext);
Pointer pBezTriple = (Pointer) c.getFieldValue("bezt");
List<Structure> bezTriples = pBezTriple.fetchData(blenderContext.getInputStream());
bezierCurves[channelCounter++] = new BezierCurve(type, bezTriples, 2);
}
Bone bone = skeleton.getBone(boneIndex); /**
Ipo ipo = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); * This method retuns the bone tracks for animation for blender version 2.50
tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, bone.getLocalRotation(), 0, ipo.getLastFrame(), fps, false)); * and higher.
} *
} * @param actionStructure
return tracks.toArray(new BoneTrack[tracks.size()]); * the structure containing the tracks
} * @param blenderContext
* the blender context
* @return a list of tracks for the specified animation
* @throws BlenderFileException
* an exception is thrown when there are problems with the blend
* file
*/
private BoneTrack[] getTracks250(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.log(Level.FINE, "Getting tracks!");
IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);
int fps = blenderContext.getBlenderKey().getFps();
Structure groups = (Structure) actionStructure.getFieldValue("groups");
List<Structure> actionGroups = groups.evaluateListBase(blenderContext);// bActionGroup
List<BoneTrack> tracks = new ArrayList<BoneTrack>();
for (Structure actionGroup : actionGroups) {
String name = actionGroup.getFieldValue("name").toString();
int boneIndex = this.getBoneIndex(skeleton, name);
if (boneIndex >= 0) {
List<Structure> channels = ((Structure) actionGroup.getFieldValue("channels")).evaluateListBase(blenderContext);
BezierCurve[] bezierCurves = new BezierCurve[channels.size()];
int channelCounter = 0;
for (Structure c : channels) {
int type = ipoHelper.getCurveType(c, blenderContext);
Pointer pBezTriple = (Pointer) c.getFieldValue("bezt");
List<Structure> bezTriples = pBezTriple.fetchData(blenderContext.getInputStream());
bezierCurves[channelCounter++] = new BezierCurve(type, bezTriples, 2);
}
/** Bone bone = skeleton.getBone(boneIndex);
* This method retuns the bone tracks for animation for blender version 2.49 Ipo ipo = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion());
* (and probably several lower versions too). tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, bone.getLocalRotation(), 0, ipo.getLastFrame(), fps, false));
* }
* @param actionStructure }
* the structure containing the tracks return tracks.toArray(new BoneTrack[tracks.size()]);
* @param blenderContext }
* the blender context
* @return a list of tracks for the specified animation
* @throws BlenderFileException
* an exception is thrown when there are problems with the blend
* file
*/
private BoneTrack[] getTracks249(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.log(Level.FINE, "Getting tracks!");
IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);
int fps = blenderContext.getBlenderKey().getFps();
Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase");
List<Structure> actionChannels = chanbase.evaluateListBase(blenderContext);// bActionChannel
List<BoneTrack> tracks = new ArrayList<BoneTrack>();
for (Structure bActionChannel : actionChannels) {
String name = bActionChannel.getFieldValue("name").toString();
int boneIndex = this.getBoneIndex(skeleton, name);
if (boneIndex >= 0) {
Pointer p = (Pointer) bActionChannel.getFieldValue("ipo");
if (!p.isNull()) {
Structure ipoStructure = p.fetchData(blenderContext.getInputStream()).get(0);
Bone bone = skeleton.getBone(boneIndex);
Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext);
if(ipo != null) {
tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, bone.getLocalRotation(), 0, ipo.getLastFrame(), fps, false));
}
}
}
}
return tracks.toArray(new BoneTrack[tracks.size()]);
}
/** /**
* This method returns the index of the bone in the given skeleton. * This method retuns the bone tracks for animation for blender version 2.49
* * (and probably several lower versions too).
* @param skeleton *
* the skeleton * @param actionStructure
* @param boneName * the structure containing the tracks
* the name of the bone * @param blenderContext
* @return the index of the bone * the blender context
*/ * @return a list of tracks for the specified animation
private int getBoneIndex(Skeleton skeleton, String boneName) { * @throws BlenderFileException
int result = -1; * an exception is thrown when there are problems with the blend
for (int i = 0; i < skeleton.getBoneCount() && result == -1; ++i) { * file
if (boneName.equals(skeleton.getBone(i).getName())) { */
result = i; private BoneTrack[] getTracks249(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException {
} LOGGER.log(Level.FINE, "Getting tracks!");
} IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);
return result; int fps = blenderContext.getBlenderKey().getFps();
} Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase");
List<Structure> actionChannels = chanbase.evaluateListBase(blenderContext);// bActionChannel
List<BoneTrack> tracks = new ArrayList<BoneTrack>();
for (Structure bActionChannel : actionChannels) {
String name = bActionChannel.getFieldValue("name").toString();
int boneIndex = this.getBoneIndex(skeleton, name);
if (boneIndex >= 0) {
Pointer p = (Pointer) bActionChannel.getFieldValue("ipo");
if (!p.isNull()) {
Structure ipoStructure = p.fetchData(blenderContext.getInputStream()).get(0);
Bone bone = skeleton.getBone(boneIndex);
Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext);
if (ipo != null) {
tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, bone.getLocalRotation(), 0, ipo.getLastFrame(), fps, false));
}
}
}
}
return tracks.toArray(new BoneTrack[tracks.size()]);
}
/**
* This method returns the index of the bone in the given skeleton.
*
* @param skeleton
* the skeleton
* @param boneName
* the name of the bone
* @return the index of the bone
*/
private int getBoneIndex(Skeleton skeleton, String boneName) {
int result = -1;
for (int i = 0; i < skeleton.getBoneCount() && result == -1; ++i) {
if (boneName.equals(skeleton.getBone(i).getName())) {
result = i;
}
}
return result;
}
} }

@ -21,215 +21,215 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class BoneContext { public class BoneContext {
/** The OMA of the bone's armature object. */ /** The OMA of the bone's armature object. */
private Long armatureObjectOMA; private Long armatureObjectOMA;
/** The structure of the bone. */ /** The structure of the bone. */
private Structure boneStructure; private Structure boneStructure;
/** Bone's pose channel structure. */ /** Bone's pose channel structure. */
private Structure poseChannel; private Structure poseChannel;
/** Bone's name. */ /** Bone's name. */
private String boneName; private String boneName;
/** This variable indicates if the Y axis should be the UP axis. */ /** This variable indicates if the Y axis should be the UP axis. */
private boolean fixUpAxis; private boolean fixUpAxis;
/** The bone's armature matrix. */ /** The bone's armature matrix. */
private Matrix4f armatureMatrix; private Matrix4f armatureMatrix;
/** The parent context. */ /** The parent context. */
private BoneContext parent; private BoneContext parent;
/** The children of this context. */ /** The children of this context. */
private List<BoneContext> children = new ArrayList<BoneContext>(); private List<BoneContext> children = new ArrayList<BoneContext>();
/** Created bone (available after calling 'buildBone' method). */ /** Created bone (available after calling 'buildBone' method). */
private Bone bone; private Bone bone;
/** Bone's pose transform (available after calling 'buildBone' method). */ /** Bone's pose transform (available after calling 'buildBone' method). */
private Transform poseTransform = new Transform(); private Transform poseTransform = new Transform();
/** The bone's rest matrix. */ /** The bone's rest matrix. */
private Matrix4f restMatrix; private Matrix4f restMatrix;
/** Bone's total inverse transformation. */ /** Bone's total inverse transformation. */
private Matrix4f inverseTotalTransformation; private Matrix4f inverseTotalTransformation;
/** Bone's parent inverse matrix. */ /** Bone's parent inverse matrix. */
private Matrix4f inverseParentMatrix; private Matrix4f inverseParentMatrix;
/** The length of the bone. */ /** The length of the bone. */
private float length; private float length;
/** /**
* Constructor. Creates the basic set of bone's data. * Constructor. Creates the basic set of bone's data.
* *
* @param armatureObjectOMA * @param armatureObjectOMA
* the OMA of the bone's armature object * the OMA of the bone's armature object
* @param boneStructure * @param boneStructure
* the bone's structure * the bone's structure
* @param objectToArmatureMatrix * @param objectToArmatureMatrix
* object-to-armature transformation matrix * object-to-armature transformation matrix
* @param bonesPoseChannels * @param bonesPoseChannels
* a map of pose channels for each bone OMA * a map of pose channels for each bone OMA
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
* an exception is thrown when problem with blender data reading * an exception is thrown when problem with blender data reading
* occurs * occurs
*/ */
public BoneContext(Long armatureObjectOMA, Structure boneStructure, Matrix4f objectToArmatureMatrix, final Map<Long, Structure> bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException { public BoneContext(Long armatureObjectOMA, Structure boneStructure, Matrix4f objectToArmatureMatrix, final Map<Long, Structure> bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException {
this(boneStructure, armatureObjectOMA, null, objectToArmatureMatrix, bonesPoseChannels, blenderContext); this(boneStructure, armatureObjectOMA, null, objectToArmatureMatrix, bonesPoseChannels, blenderContext);
} }
/** /**
* Constructor. Creates the basic set of bone's data. * Constructor. Creates the basic set of bone's data.
* *
* @param boneStructure * @param boneStructure
* the bone's structure * the bone's structure
* @param armatureObjectOMA * @param armatureObjectOMA
* the OMA of the bone's armature object * the OMA of the bone's armature object
* @param parent * @param parent
* bone's parent (null if the bone is the root bone) * bone's parent (null if the bone is the root bone)
* @param objectToArmatureMatrix * @param objectToArmatureMatrix
* object-to-armature transformation matrix * object-to-armature transformation matrix
* @param bonesPoseChannels * @param bonesPoseChannels
* a map of pose channels for each bone OMA * a map of pose channels for each bone OMA
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
* an exception is thrown when problem with blender data reading * an exception is thrown when problem with blender data reading
* occurs * occurs
*/ */
private BoneContext(Structure boneStructure, Long armatureObjectOMA, BoneContext parent, Matrix4f objectToArmatureMatrix, final Map<Long, Structure> bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException { private BoneContext(Structure boneStructure, Long armatureObjectOMA, BoneContext parent, Matrix4f objectToArmatureMatrix, final Map<Long, Structure> bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException {
this.parent = parent; this.parent = parent;
this.boneStructure = boneStructure; this.boneStructure = boneStructure;
this.armatureObjectOMA = armatureObjectOMA; this.armatureObjectOMA = armatureObjectOMA;
boneName = boneStructure.getFieldValue("name").toString(); boneName = boneStructure.getFieldValue("name").toString();
length = ((Number)boneStructure.getFieldValue("length")).floatValue(); length = ((Number) boneStructure.getFieldValue("length")).floatValue();
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
armatureMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", true); armatureMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", true);
fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis(); fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis();
this.computeRestMatrix(objectToArmatureMatrix); this.computeRestMatrix(objectToArmatureMatrix);
List<Structure> childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(blenderContext); List<Structure> childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(blenderContext);
for (Structure child : childbase) { for (Structure child : childbase) {
this.children.add(new BoneContext(child, armatureObjectOMA, this, objectToArmatureMatrix, bonesPoseChannels, blenderContext)); this.children.add(new BoneContext(child, armatureObjectOMA, this, objectToArmatureMatrix, bonesPoseChannels, blenderContext));
} }
poseChannel = bonesPoseChannels.get(boneStructure.getOldMemoryAddress()); poseChannel = bonesPoseChannels.get(boneStructure.getOldMemoryAddress());
blenderContext.setBoneContext(boneStructure.getOldMemoryAddress(), this); blenderContext.setBoneContext(boneStructure.getOldMemoryAddress(), this);
} }
/** /**
* This method computes the rest matrix for the bone. * This method computes the rest matrix for the bone.
* *
* @param objectToArmatureMatrix * @param objectToArmatureMatrix
* object-to-armature transformation matrix * object-to-armature transformation matrix
*/ */
private void computeRestMatrix(Matrix4f objectToArmatureMatrix) { private void computeRestMatrix(Matrix4f objectToArmatureMatrix) {
if (parent != null) { if (parent != null) {
inverseParentMatrix = parent.inverseTotalTransformation.clone(); inverseParentMatrix = parent.inverseTotalTransformation.clone();
} else if (fixUpAxis) { } else if (fixUpAxis) {
inverseParentMatrix = objectToArmatureMatrix.clone(); inverseParentMatrix = objectToArmatureMatrix.clone();
} else { } else {
inverseParentMatrix = Matrix4f.IDENTITY.clone(); inverseParentMatrix = Matrix4f.IDENTITY.clone();
} }
restMatrix = armatureMatrix.clone(); restMatrix = armatureMatrix.clone();
inverseTotalTransformation = restMatrix.invert(); inverseTotalTransformation = restMatrix.invert();
restMatrix = inverseParentMatrix.mult(restMatrix); restMatrix = inverseParentMatrix.mult(restMatrix);
for (BoneContext child : this.children) { for (BoneContext child : this.children) {
child.computeRestMatrix(objectToArmatureMatrix); child.computeRestMatrix(objectToArmatureMatrix);
} }
} }
/** /**
* This method computes the pose transform for the bone. * This method computes the pose transform for the bone.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void computePoseTransform() { private void computePoseTransform() {
DynamicArray<Number> loc = (DynamicArray<Number>) poseChannel.getFieldValue("loc"); DynamicArray<Number> loc = (DynamicArray<Number>) poseChannel.getFieldValue("loc");
DynamicArray<Number> size = (DynamicArray<Number>) poseChannel.getFieldValue("size"); DynamicArray<Number> size = (DynamicArray<Number>) poseChannel.getFieldValue("size");
DynamicArray<Number> quat = (DynamicArray<Number>) poseChannel.getFieldValue("quat"); DynamicArray<Number> quat = (DynamicArray<Number>) poseChannel.getFieldValue("quat");
if (fixUpAxis) { if (fixUpAxis) {
poseTransform.setTranslation(loc.get(0).floatValue(), -loc.get(2).floatValue(), loc.get(1).floatValue()); poseTransform.setTranslation(loc.get(0).floatValue(), -loc.get(2).floatValue(), loc.get(1).floatValue());
poseTransform.setRotation(new Quaternion(quat.get(1).floatValue(), quat.get(3).floatValue(), -quat.get(2).floatValue(), quat.get(0).floatValue())); poseTransform.setRotation(new Quaternion(quat.get(1).floatValue(), quat.get(3).floatValue(), -quat.get(2).floatValue(), quat.get(0).floatValue()));
poseTransform.setScale(size.get(0).floatValue(), size.get(2).floatValue(), size.get(1).floatValue()); poseTransform.setScale(size.get(0).floatValue(), size.get(2).floatValue(), size.get(1).floatValue());
} else { } else {
poseTransform.setTranslation(loc.get(0).floatValue(), loc.get(1).floatValue(), loc.get(2).floatValue()); poseTransform.setTranslation(loc.get(0).floatValue(), loc.get(1).floatValue(), loc.get(2).floatValue());
poseTransform.setRotation(new Quaternion(quat.get(0).floatValue(), quat.get(1).floatValue(), quat.get(2).floatValue(), quat.get(3).floatValue())); poseTransform.setRotation(new Quaternion(quat.get(0).floatValue(), quat.get(1).floatValue(), quat.get(2).floatValue(), quat.get(3).floatValue()));
poseTransform.setScale(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue()); poseTransform.setScale(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue());
} }
Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation()); Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());
localTransform.setScale(bone.getLocalScale()); localTransform.setScale(bone.getLocalScale());
localTransform.getTranslation().addLocal(poseTransform.getTranslation()); localTransform.getTranslation().addLocal(poseTransform.getTranslation());
localTransform.getRotation().multLocal(poseTransform.getRotation()); localTransform.getRotation().multLocal(poseTransform.getRotation());
localTransform.getScale().multLocal(poseTransform.getScale()); localTransform.getScale().multLocal(poseTransform.getScale());
poseTransform.set(localTransform); poseTransform.set(localTransform);
} }
/** /**
* This method builds the bone. It recursively builds the bone's children. * This method builds the bone. It recursively builds the bone's children.
* *
* @param bones * @param bones
* a list of bones where the newly created bone will be added * a list of bones where the newly created bone will be added
* @param boneOMAs * @param boneOMAs
* the map between bone and its old memory address * the map between bone and its old memory address
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @return newly created bone * @return newly created bone
*/ */
public Bone buildBone(List<Bone> bones, Map<Bone, Long> boneOMAs, BlenderContext blenderContext) { public Bone buildBone(List<Bone> bones, Map<Bone, Long> boneOMAs, BlenderContext blenderContext) {
Long boneOMA = boneStructure.getOldMemoryAddress(); Long boneOMA = boneStructure.getOldMemoryAddress();
bone = new Bone(boneName); bone = new Bone(boneName);
bones.add(bone); bones.add(bone);
boneOMAs.put(bone, boneOMA); boneOMAs.put(bone, boneOMA);
blenderContext.addLoadedFeatures(boneOMA, boneName, boneStructure, bone); blenderContext.addLoadedFeatures(boneOMA, boneName, boneStructure, bone);
Matrix4f pose = this.restMatrix.clone(); Matrix4f pose = this.restMatrix.clone();
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
Vector3f poseLocation = pose.toTranslationVector(); Vector3f poseLocation = pose.toTranslationVector();
Quaternion rotation = pose.toRotationQuat(); Quaternion rotation = pose.toRotationQuat();
Vector3f scale = objectHelper.getScale(pose); Vector3f scale = objectHelper.getScale(pose);
bone.setBindTransforms(poseLocation, rotation, scale); bone.setBindTransforms(poseLocation, rotation, scale);
for (BoneContext child : children) { for (BoneContext child : children) {
bone.addChild(child.buildBone(bones, boneOMAs, blenderContext)); bone.addChild(child.buildBone(bones, boneOMAs, blenderContext));
} }
this.computePoseTransform(); this.computePoseTransform();
return bone; return bone;
} }
/** /**
* @return bone's pose transformation * @return bone's pose transformation
*/ */
public Transform getPoseTransform() { public Transform getPoseTransform() {
return poseTransform; return poseTransform;
} }
/** /**
* @return built bone (available after calling 'buildBone' method) * @return built bone (available after calling 'buildBone' method)
*/ */
public Bone getBone() { public Bone getBone() {
return bone; return bone;
} }
/** /**
* @return the old memory address of the bone * @return the old memory address of the bone
*/ */
public Long getBoneOma() { public Long getBoneOma() {
return boneStructure.getOldMemoryAddress(); return boneStructure.getOldMemoryAddress();
} }
/** /**
* @return the length of the bone * @return the length of the bone
*/ */
public float getLength() { public float getLength() {
return length; return length;
} }
/** /**
* @return OMA of the bone's armature object * @return OMA of the bone's armature object
*/ */
public Long getArmatureObjectOMA() { public Long getArmatureObjectOMA() {
return armatureObjectOMA; return armatureObjectOMA;
} }
} }

@ -13,116 +13,116 @@ import java.util.Arrays;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class CalculationBone extends Node { public class CalculationBone extends Node {
private Bone bone; private Bone bone;
/** The bone's tracks. Will be altered at the end of calculation process. */ /** The bone's tracks. Will be altered at the end of calculation process. */
private BoneTrack track; private BoneTrack track;
/** The starting position of the bone. */ /** The starting position of the bone. */
private Vector3f startTranslation; private Vector3f startTranslation;
/** The starting rotation of the bone. */ /** The starting rotation of the bone. */
private Quaternion startRotation; private Quaternion startRotation;
/** The starting scale of the bone. */ /** The starting scale of the bone. */
private Vector3f startScale; private Vector3f startScale;
private Vector3f[] translations; private Vector3f[] translations;
private Quaternion[] rotations; private Quaternion[] rotations;
private Vector3f[] scales; private Vector3f[] scales;
public CalculationBone(Bone bone, int boneFramesCount) { public CalculationBone(Bone bone, int boneFramesCount) {
this.bone = bone; this.bone = bone;
this.startRotation = bone.getModelSpaceRotation().clone(); this.startRotation = bone.getModelSpaceRotation().clone();
this.startTranslation = bone.getModelSpacePosition().clone(); this.startTranslation = bone.getModelSpacePosition().clone();
this.startScale = bone.getModelSpaceScale().clone(); this.startScale = bone.getModelSpaceScale().clone();
this.reset(); this.reset();
if(boneFramesCount > 0) { if (boneFramesCount > 0) {
this.translations = new Vector3f[boneFramesCount]; this.translations = new Vector3f[boneFramesCount];
this.rotations = new Quaternion[boneFramesCount]; this.rotations = new Quaternion[boneFramesCount];
this.scales = new Vector3f[boneFramesCount]; this.scales = new Vector3f[boneFramesCount];
Arrays.fill(this.translations, 0, boneFramesCount, this.startTranslation);
Arrays.fill(this.rotations, 0, boneFramesCount, this.startRotation);
Arrays.fill(this.scales, 0, boneFramesCount, this.startScale);
}
}
/**
* Constructor. Stores the track, starting transformation and sets the transformation to the starting positions.
* @param bone
* the bone this class will imitate
* @param track
* the bone's tracks
*/
public CalculationBone(Bone bone, BoneTrack track) {
this(bone, 0);
this.track = track;
this.translations = track.getTranslations();
this.rotations = track.getRotations();
this.scales = track.getScales();
}
public int getBoneFramesCount() { Arrays.fill(this.translations, 0, boneFramesCount, this.startTranslation);
return this.translations==null ? 0 : this.translations.length; Arrays.fill(this.rotations, 0, boneFramesCount, this.startRotation);
} Arrays.fill(this.scales, 0, boneFramesCount, this.startScale);
}
/** }
* This method returns the end point of the bone. If the bone has parent it is calculated from the start point
* of parent to the start point of this bone. If the bone doesn't have a parent the end location is considered
* to be 1 point up along Y axis (scale is applied if set to != 1.0);
* @return the end point of this bone
*/
//TODO: set to Z axis if user defined it this way
public Vector3f getEndPoint() {
if (this.getParent() == null) {
return new Vector3f(0, this.getLocalScale().y, 0);
} else {
Node parent = this.getParent();
return parent.getWorldTranslation().subtract(this.getWorldTranslation()).multLocal(this.getWorldScale());
}
}
/** /**
* This method resets the calculation bone to the starting position. * Constructor. Stores the track, starting transformation and sets the transformation to the starting positions.
*/ * @param bone
public void reset() { * the bone this class will imitate
this.setLocalTranslation(startTranslation); * @param track
this.setLocalRotation(startRotation); * the bone's tracks
this.setLocalScale(startScale); */
} public CalculationBone(Bone bone, BoneTrack track) {
this(bone, 0);
this.track = track;
this.translations = track.getTranslations();
this.rotations = track.getRotations();
this.scales = track.getScales();
}
@Override public int getBoneFramesCount() {
public int attachChild(Spatial child) { return this.translations == null ? 0 : this.translations.length;
if (this.getChildren() != null && this.getChildren().size() > 1) { }
throw new IllegalStateException(this.getClass().getName() + " class instance can only have one child!");
}
return super.attachChild(child);
}
public Spatial rotate(Quaternion rot, int frame) { /**
Spatial spatial = super.rotate(rot); * This method returns the end point of the bone. If the bone has parent it is calculated from the start point
this.updateWorldTransforms(); * of parent to the start point of this bone. If the bone doesn't have a parent the end location is considered
if (this.getChildren() != null && this.getChildren().size() > 0) { * to be 1 point up along Y axis (scale is applied if set to != 1.0);
CalculationBone child = (CalculationBone) this.getChild(0); * @return the end point of this bone
child.updateWorldTransforms(); */
} // TODO: set to Z axis if user defined it this way
rotations[frame].set(this.getLocalRotation()); public Vector3f getEndPoint() {
translations[frame].set(this.getLocalTranslation()); if (this.getParent() == null) {
if (scales != null) { return new Vector3f(0, this.getLocalScale().y, 0);
scales[frame].set(this.getLocalScale()); } else {
} Node parent = this.getParent();
return spatial; return parent.getWorldTranslation().subtract(this.getWorldTranslation()).multLocal(this.getWorldScale());
} }
}
public void applyCalculatedTracks() { /**
if(track != null) { * This method resets the calculation bone to the starting position.
track.setKeyframes(track.getTimes(), translations, rotations, scales); */
} else { public void reset() {
bone.setUserControl(true); this.setLocalTranslation(startTranslation);
bone.setUserTransforms(translations[0], rotations[0], scales[0]); this.setLocalRotation(startRotation);
bone.setUserControl(false); this.setLocalScale(startScale);
bone.updateWorldVectors(); }
}
}
@Override @Override
public String toString() { public int attachChild(Spatial child) {
return bone.getName() + ": " + this.getLocalRotation() + " " + this.getLocalTranslation(); if (this.getChildren() != null && this.getChildren().size() > 1) {
} throw new IllegalStateException(this.getClass().getName() + " class instance can only have one child!");
}
return super.attachChild(child);
}
public Spatial rotate(Quaternion rot, int frame) {
Spatial spatial = super.rotate(rot);
this.updateWorldTransforms();
if (this.getChildren() != null && this.getChildren().size() > 0) {
CalculationBone child = (CalculationBone) this.getChild(0);
child.updateWorldTransforms();
}
rotations[frame].set(this.getLocalRotation());
translations[frame].set(this.getLocalTranslation());
if (scales != null) {
scales[frame].set(this.getLocalScale());
}
return spatial;
}
public void applyCalculatedTracks() {
if (track != null) {
track.setKeyframes(track.getTimes(), translations, rotations, scales);
} else {
bone.setUserControl(true);
bone.setUserTransforms(translations[0], rotations[0], scales[0]);
bone.setUserControl(false);
bone.updateWorldVectors();
}
}
@Override
public String toString() {
return bone.getName() + ": " + this.getLocalRotation() + " " + this.getLocalTranslation();
}
} }

@ -18,234 +18,234 @@ import com.jme3.scene.plugins.blender.curves.BezierCurve;
* @author Marcin Roguski * @author Marcin Roguski
*/ */
public class Ipo { public class Ipo {
private static final Logger LOGGER = Logger.getLogger(Ipo.class.getName()); private static final Logger LOGGER = Logger.getLogger(Ipo.class.getName());
public static final int AC_LOC_X = 1; public static final int AC_LOC_X = 1;
public static final int AC_LOC_Y = 2; public static final int AC_LOC_Y = 2;
public static final int AC_LOC_Z = 3; public static final int AC_LOC_Z = 3;
public static final int OB_ROT_X = 7; public static final int OB_ROT_X = 7;
public static final int OB_ROT_Y = 8; public static final int OB_ROT_Y = 8;
public static final int OB_ROT_Z = 9; public static final int OB_ROT_Z = 9;
public static final int AC_SIZE_X = 13; public static final int AC_SIZE_X = 13;
public static final int AC_SIZE_Y = 14; public static final int AC_SIZE_Y = 14;
public static final int AC_SIZE_Z = 15; public static final int AC_SIZE_Z = 15;
public static final int AC_QUAT_W = 25; public static final int AC_QUAT_W = 25;
public static final int AC_QUAT_X = 26; public static final int AC_QUAT_X = 26;
public static final int AC_QUAT_Y = 27; public static final int AC_QUAT_Y = 27;
public static final int AC_QUAT_Z = 28; public static final int AC_QUAT_Z = 28;
/** A list of bezier curves for this interpolation object. */ /** A list of bezier curves for this interpolation object. */
private BezierCurve[] bezierCurves; private BezierCurve[] bezierCurves;
/** Each ipo contains one bone track. */ /** Each ipo contains one bone track. */
private Track calculatedTrack; private Track calculatedTrack;
/** This variable indicates if the Y asxis is the UP axis or not. */ /** This variable indicates if the Y asxis is the UP axis or not. */
protected boolean fixUpAxis; protected boolean fixUpAxis;
/** Depending on the blender version rotations are stored in degrees or radians so we need to know the version that is used. */ /** Depending on the blender version rotations are stored in degrees or radians so we need to know the version that is used. */
protected final int blenderVersion; protected final int blenderVersion;
/** /**
* Constructor. Stores the bezier curves. * Constructor. Stores the bezier curves.
* *
* @param bezierCurves * @param bezierCurves
* a table of bezier curves * a table of bezier curves
* @param fixUpAxis * @param fixUpAxis
* indicates if the Y is the up axis or not * indicates if the Y is the up axis or not
* @param blenderVersion * @param blenderVersion
* the blender version that is currently used * the blender version that is currently used
*/ */
public Ipo(BezierCurve[] bezierCurves, boolean fixUpAxis, int blenderVersion) { public Ipo(BezierCurve[] bezierCurves, boolean fixUpAxis, int blenderVersion) {
this.bezierCurves = bezierCurves; this.bezierCurves = bezierCurves;
this.fixUpAxis = fixUpAxis; this.fixUpAxis = fixUpAxis;
this.blenderVersion = blenderVersion; this.blenderVersion = blenderVersion;
} }
/** /**
* This method calculates the ipo value for the first curve. * This method calculates the ipo value for the first curve.
* *
* @param frame * @param frame
* the frame for which the value is calculated * the frame for which the value is calculated
* @return calculated ipo value * @return calculated ipo value
*/ */
public float calculateValue(int frame) { public float calculateValue(int frame) {
return this.calculateValue(frame, 0); return this.calculateValue(frame, 0);
} }
/** /**
* This method calculates the ipo value for the curve of the specified * This method calculates the ipo value for the curve of the specified
* index. Make sure you do not exceed the curves amount. Alway chech the * index. Make sure you do not exceed the curves amount. Alway chech the
* amount of curves before calling this method. * amount of curves before calling this method.
* *
* @param frame * @param frame
* the frame for which the value is calculated * the frame for which the value is calculated
* @param curveIndex * @param curveIndex
* the index of the curve * the index of the curve
* @return calculated ipo value * @return calculated ipo value
*/ */
public float calculateValue(int frame, int curveIndex) { public float calculateValue(int frame, int curveIndex) {
return bezierCurves[curveIndex].evaluate(frame, BezierCurve.Y_VALUE); return bezierCurves[curveIndex].evaluate(frame, BezierCurve.Y_VALUE);
} }
/** /**
* This method returns the curves amount. * This method returns the curves amount.
* *
* @return the curves amount * @return the curves amount
*/ */
public int getCurvesAmount() { public int getCurvesAmount() {
return bezierCurves.length; return bezierCurves.length;
} }
/** /**
* This method returns the frame where last bezier triple center point of * This method returns the frame where last bezier triple center point of
* the specified bezier curve is located. * the specified bezier curve is located.
* *
* @return the frame number of the last defined bezier triple point for the * @return the frame number of the last defined bezier triple point for the
* specified ipo * specified ipo
*/ */
public int getLastFrame() { public int getLastFrame() {
int result = 1; int result = 1;
for (int i = 0; i < bezierCurves.length; ++i) { for (int i = 0; i < bezierCurves.length; ++i) {
int tempResult = bezierCurves[i].getLastFrame(); int tempResult = bezierCurves[i].getLastFrame();
if (tempResult > result) { if (tempResult > result) {
result = tempResult; result = tempResult;
} }
} }
return result; return result;
} }
/** /**
* This method calculates the value of the curves as a bone track between * This method calculates the value of the curves as a bone track between
* the specified frames. * the specified frames.
* *
* @param targetIndex * @param targetIndex
* the index of the target for which the method calculates the * the index of the target for which the method calculates the
* tracks IMPORTANT! Aet to -1 (or any negative number) if you * tracks IMPORTANT! Aet to -1 (or any negative number) if you
* want to load spatial animation. * want to load spatial animation.
* @param localQuaternionRotation * @param localQuaternionRotation
* the local rotation of the object/bone that will be animated by * the local rotation of the object/bone that will be animated by
* the track * the track
* @param startFrame * @param startFrame
* the firs frame of tracks (inclusive) * the firs frame of tracks (inclusive)
* @param stopFrame * @param stopFrame
* the last frame of the tracks (inclusive) * the last frame of the tracks (inclusive)
* @param fps * @param fps
* frame rate (frames per second) * frame rate (frames per second)
* @param spatialTrack * @param spatialTrack
* this flag indicates if the track belongs to a spatial or to a * this flag indicates if the track belongs to a spatial or to a
* bone; the diference is important because it appears that bones * bone; the diference is important because it appears that bones
* in blender have the same type of coordinate system (Y as UP) * in blender have the same type of coordinate system (Y as UP)
* as jme while other features have different one (Z is UP) * as jme while other features have different one (Z is UP)
* @return bone track for the specified bone * @return bone track for the specified bone
*/ */
public Track calculateTrack(int targetIndex, Quaternion localQuaternionRotation, int startFrame, int stopFrame, int fps, boolean spatialTrack) { public Track calculateTrack(int targetIndex, Quaternion localQuaternionRotation, int startFrame, int stopFrame, int fps, boolean spatialTrack) {
if (calculatedTrack == null) { if (calculatedTrack == null) {
// preparing data for track // preparing data for track
int framesAmount = stopFrame - startFrame; int framesAmount = stopFrame - startFrame;
float timeBetweenFrames = 1.0f / fps; float timeBetweenFrames = 1.0f / fps;
float[] times = new float[framesAmount + 1]; float[] times = new float[framesAmount + 1];
Vector3f[] translations = new Vector3f[framesAmount + 1]; Vector3f[] translations = new Vector3f[framesAmount + 1];
float[] translation = new float[3]; float[] translation = new float[3];
Quaternion[] rotations = new Quaternion[framesAmount + 1]; Quaternion[] rotations = new Quaternion[framesAmount + 1];
float[] quaternionRotation = new float[] { 0, 0, 0, 1 }; float[] quaternionRotation = new float[] { 0, 0, 0, 1 };
float[] objectRotation = new float[3]; float[] objectRotation = new float[3];
Vector3f[] scales = new Vector3f[framesAmount + 1]; Vector3f[] scales = new Vector3f[framesAmount + 1];
float[] scale = new float[] { 1.0f, 1.0f, 1.0f }; float[] scale = new float[] { 1.0f, 1.0f, 1.0f };
float degreeToRadiansFactor = 1; float degreeToRadiansFactor = 1;
if(blenderVersion < 250) {//in blender earlier than 2.50 the values are stored in degrees if (blenderVersion < 250) {// in blender earlier than 2.50 the values are stored in degrees
degreeToRadiansFactor *= FastMath.DEG_TO_RAD * 10;// the values in blender are divided by 10, so we need to mult it here degreeToRadiansFactor *= FastMath.DEG_TO_RAD * 10;// the values in blender are divided by 10, so we need to mult it here
} }
// calculating track data // calculating track data
for (int frame = startFrame; frame <= stopFrame; ++frame) { for (int frame = startFrame; frame <= stopFrame; ++frame) {
int index = frame - startFrame; int index = frame - startFrame;
times[index] = index * timeBetweenFrames;//start + (frame - 1) * timeBetweenFrames; times[index] = index * timeBetweenFrames;// start + (frame - 1) * timeBetweenFrames;
for (int j = 0; j < bezierCurves.length; ++j) { for (int j = 0; j < bezierCurves.length; ++j) {
double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE); double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE);
switch (bezierCurves[j].getType()) { switch (bezierCurves[j].getType()) {
// LOCATION // LOCATION
case AC_LOC_X: case AC_LOC_X:
translation[0] = (float) value; translation[0] = (float) value;
break; break;
case AC_LOC_Y: case AC_LOC_Y:
if (fixUpAxis) { if (fixUpAxis) {
translation[2] = (float) -value; translation[2] = (float) -value;
} else { } else {
translation[1] = (float) value; translation[1] = (float) value;
} }
break; break;
case AC_LOC_Z: case AC_LOC_Z:
translation[fixUpAxis ? 1 : 2] = (float) value; translation[fixUpAxis ? 1 : 2] = (float) value;
break; break;
// ROTATION (used with object animation) // ROTATION (used with object animation)
// the value here is in degrees divided by 10 (so in // the value here is in degrees divided by 10 (so in
// example: 9 = PI/2) // example: 9 = PI/2)
case OB_ROT_X: case OB_ROT_X:
objectRotation[0] = (float) value * degreeToRadiansFactor; objectRotation[0] = (float) value * degreeToRadiansFactor;
break; break;
case OB_ROT_Y: case OB_ROT_Y:
if (fixUpAxis) { if (fixUpAxis) {
objectRotation[2] = (float) -value * degreeToRadiansFactor; objectRotation[2] = (float) -value * degreeToRadiansFactor;
} else { } else {
objectRotation[1] = (float) value * degreeToRadiansFactor; objectRotation[1] = (float) value * degreeToRadiansFactor;
} }
break; break;
case OB_ROT_Z: case OB_ROT_Z:
objectRotation[fixUpAxis ? 1 : 2] = (float) value * degreeToRadiansFactor; objectRotation[fixUpAxis ? 1 : 2] = (float) value * degreeToRadiansFactor;
break; break;
// SIZE // SIZE
case AC_SIZE_X: case AC_SIZE_X:
scale[0] = (float) value; scale[0] = (float) value;
break; break;
case AC_SIZE_Y: case AC_SIZE_Y:
if (fixUpAxis) { if (fixUpAxis) {
scale[2] = (float) value; scale[2] = (float) value;
} else { } else {
scale[1] = (float) value; scale[1] = (float) value;
} }
break; break;
case AC_SIZE_Z: case AC_SIZE_Z:
scale[fixUpAxis ? 1 : 2] = (float) value; scale[fixUpAxis ? 1 : 2] = (float) value;
break; break;
// QUATERNION ROTATION (used with bone animation), dunno // QUATERNION ROTATION (used with bone animation), dunno
// why but here we shouldn't check the // why but here we shouldn't check the
// spatialTrack flag value // spatialTrack flag value
case AC_QUAT_W: case AC_QUAT_W:
quaternionRotation[3] = (float) value; quaternionRotation[3] = (float) value;
break; break;
case AC_QUAT_X: case AC_QUAT_X:
quaternionRotation[0] = (float) value; quaternionRotation[0] = (float) value;
break; break;
case AC_QUAT_Y: case AC_QUAT_Y:
if (fixUpAxis) { if (fixUpAxis) {
quaternionRotation[2] = -(float) value; quaternionRotation[2] = -(float) value;
} else { } else {
quaternionRotation[1] = (float) value; quaternionRotation[1] = (float) value;
} }
break; break;
case AC_QUAT_Z: case AC_QUAT_Z:
if (fixUpAxis) { if (fixUpAxis) {
quaternionRotation[1] = (float) value; quaternionRotation[1] = (float) value;
} else { } else {
quaternionRotation[2] = (float) value; quaternionRotation[2] = (float) value;
} }
break; break;
default: default:
LOGGER.warning("Unknown ipo curve type: " + bezierCurves[j].getType()); LOGGER.warning("Unknown ipo curve type: " + bezierCurves[j].getType());
} }
} }
translations[index] = localQuaternionRotation.multLocal(new Vector3f(translation[0], translation[1], translation[2])); translations[index] = localQuaternionRotation.multLocal(new Vector3f(translation[0], translation[1], translation[2]));
rotations[index] = spatialTrack ? new Quaternion().fromAngles(objectRotation) : new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]); rotations[index] = spatialTrack ? new Quaternion().fromAngles(objectRotation) : new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]);
scales[index] = new Vector3f(scale[0], scale[1], scale[2]); scales[index] = new Vector3f(scale[0], scale[1], scale[2]);
} }
if (spatialTrack) { if (spatialTrack) {
calculatedTrack = new SpatialTrack(times, translations, rotations, scales); calculatedTrack = new SpatialTrack(times, translations, rotations, scales);
} else { } else {
calculatedTrack = new BoneTrack(targetIndex, times, translations, rotations, scales); calculatedTrack = new BoneTrack(targetIndex, times, translations, rotations, scales);
} }
} }
return calculatedTrack; return calculatedTrack;
} }
} }

@ -21,182 +21,182 @@ import java.util.logging.Logger;
* @author Marcin Roguski * @author Marcin Roguski
*/ */
public class IpoHelper extends AbstractBlenderHelper { public class IpoHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(IpoHelper.class.getName()); private static final Logger LOGGER = Logger.getLogger(IpoHelper.class.getName());
/** /**
* This constructor parses the given blender version and stores the result. * This constructor parses the given blender version and stores the result.
* Some functionalities may differ in different blender versions. * Some functionalities may differ in different blender versions.
* *
* @param blenderVersion * @param blenderVersion
* the version read from the blend file * the version read from the blend file
* @param fixUpAxis * @param fixUpAxis
* a variable that indicates if the Y asxis is the UP axis or not * a variable that indicates if the Y asxis is the UP axis or not
*/ */
public IpoHelper(String blenderVersion, boolean fixUpAxis) { public IpoHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis); super(blenderVersion, fixUpAxis);
} }
/** /**
* This method creates an ipo object used for interpolation calculations. * This method creates an ipo object used for interpolation calculations.
* *
* @param ipoStructure * @param ipoStructure
* the structure with ipo definition * the structure with ipo definition
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @return the ipo object * @return the ipo object
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blender file is somehow * this exception is thrown when the blender file is somehow
* corrupted * corrupted
*/ */
public Ipo fromIpoStructure(Structure ipoStructure, BlenderContext blenderContext) throws BlenderFileException { public Ipo fromIpoStructure(Structure ipoStructure, BlenderContext blenderContext) throws BlenderFileException {
Structure curvebase = (Structure) ipoStructure.getFieldValue("curve"); Structure curvebase = (Structure) ipoStructure.getFieldValue("curve");
// preparing bezier curves // preparing bezier curves
Ipo result = null; Ipo result = null;
List<Structure> curves = curvebase.evaluateListBase(blenderContext);// IpoCurve List<Structure> curves = curvebase.evaluateListBase(blenderContext);// IpoCurve
if (curves.size() > 0) { if (curves.size() > 0) {
BezierCurve[] bezierCurves = new BezierCurve[curves.size()]; BezierCurve[] bezierCurves = new BezierCurve[curves.size()];
int frame = 0; int frame = 0;
for (Structure curve : curves) { for (Structure curve : curves) {
Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt"); Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt");
List<Structure> bezTriples = pBezTriple.fetchData(blenderContext.getInputStream()); List<Structure> bezTriples = pBezTriple.fetchData(blenderContext.getInputStream());
int type = ((Number) curve.getFieldValue("adrcode")).intValue(); int type = ((Number) curve.getFieldValue("adrcode")).intValue();
bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2); bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2);
} }
curves.clear(); curves.clear();
result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion());
blenderContext.addLoadedFeatures(ipoStructure.getOldMemoryAddress(), ipoStructure.getName(), ipoStructure, result); blenderContext.addLoadedFeatures(ipoStructure.getOldMemoryAddress(), ipoStructure.getName(), ipoStructure, result);
} }
return result; return result;
} }
/** /**
* This method creates an ipo object used for interpolation calculations. It * This method creates an ipo object used for interpolation calculations. It
* should be called for blender version 2.50 and higher. * should be called for blender version 2.50 and higher.
* *
* @param actionStructure * @param actionStructure
* the structure with action definition * the structure with action definition
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @return the ipo object * @return the ipo object
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blender file is somehow * this exception is thrown when the blender file is somehow
* corrupted * corrupted
*/ */
public Ipo fromAction(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException { public Ipo fromAction(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException {
Ipo result = null; Ipo result = null;
List<Structure> curves = ((Structure) actionStructure.getFieldValue("curves")).evaluateListBase(blenderContext);// FCurve List<Structure> curves = ((Structure) actionStructure.getFieldValue("curves")).evaluateListBase(blenderContext);// FCurve
if (curves.size() > 0) { if (curves.size() > 0) {
BezierCurve[] bezierCurves = new BezierCurve[curves.size()]; BezierCurve[] bezierCurves = new BezierCurve[curves.size()];
int frame = 0; int frame = 0;
for (Structure curve : curves) { for (Structure curve : curves) {
Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt"); Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt");
List<Structure> bezTriples = pBezTriple.fetchData(blenderContext.getInputStream()); List<Structure> bezTriples = pBezTriple.fetchData(blenderContext.getInputStream());
int type = this.getCurveType(curve, blenderContext); int type = this.getCurveType(curve, blenderContext);
bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2); bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2);
} }
curves.clear(); curves.clear();
result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion());
} }
return result; return result;
} }
/** /**
* This method returns the type of the ipo curve. * This method returns the type of the ipo curve.
* *
* @param structure * @param structure
* the structure must contain the 'rna_path' field and * the structure must contain the 'rna_path' field and
* 'array_index' field (the type is not important here) * 'array_index' field (the type is not important here)
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @return the type of the curve * @return the type of the curve
*/ */
public int getCurveType(Structure structure, BlenderContext blenderContext) { public int getCurveType(Structure structure, BlenderContext blenderContext) {
// reading rna path first // reading rna path first
BlenderInputStream bis = blenderContext.getInputStream(); BlenderInputStream bis = blenderContext.getInputStream();
int currentPosition = bis.getPosition(); int currentPosition = bis.getPosition();
Pointer pRnaPath = (Pointer) structure.getFieldValue("rna_path"); Pointer pRnaPath = (Pointer) structure.getFieldValue("rna_path");
FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pRnaPath.getOldMemoryAddress()); FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pRnaPath.getOldMemoryAddress());
bis.setPosition(dataFileBlock.getBlockPosition()); bis.setPosition(dataFileBlock.getBlockPosition());
String rnaPath = bis.readString(); String rnaPath = bis.readString();
bis.setPosition(currentPosition); bis.setPosition(currentPosition);
int arrayIndex = ((Number) structure.getFieldValue("array_index")).intValue(); int arrayIndex = ((Number) structure.getFieldValue("array_index")).intValue();
// determining the curve type // determining the curve type
if (rnaPath.endsWith("location")) { if (rnaPath.endsWith("location")) {
return Ipo.AC_LOC_X + arrayIndex; return Ipo.AC_LOC_X + arrayIndex;
} }
if (rnaPath.endsWith("rotation_quaternion")) { if (rnaPath.endsWith("rotation_quaternion")) {
return Ipo.AC_QUAT_W + arrayIndex; return Ipo.AC_QUAT_W + arrayIndex;
} }
if (rnaPath.endsWith("scale")) { if (rnaPath.endsWith("scale")) {
return Ipo.AC_SIZE_X + arrayIndex; return Ipo.AC_SIZE_X + arrayIndex;
} }
if (rnaPath.endsWith("rotation") || rnaPath.endsWith("rotation_euler")) { if (rnaPath.endsWith("rotation") || rnaPath.endsWith("rotation_euler")) {
return Ipo.OB_ROT_X + arrayIndex; return Ipo.OB_ROT_X + arrayIndex;
} }
LOGGER.warning("Unknown curve rna path: " + rnaPath); LOGGER.warning("Unknown curve rna path: " + rnaPath);
return -1; return -1;
} }
/** /**
* This method creates an ipo with only a single value. No track type is * This method creates an ipo with only a single value. No track type is
* specified so do not use it for calculating tracks. * specified so do not use it for calculating tracks.
* *
* @param constValue * @param constValue
* the value of this ipo * the value of this ipo
* @return constant ipo * @return constant ipo
*/ */
public Ipo fromValue(float constValue) { public Ipo fromValue(float constValue) {
return new ConstIpo(constValue); return new ConstIpo(constValue);
} }
@Override @Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return true; return true;
} }
/** /**
* Ipo constant curve. This is a curve with only one value and no specified * Ipo constant curve. This is a curve with only one value and no specified
* type. This type of ipo cannot be used to calculate tracks. It should only * type. This type of ipo cannot be used to calculate tracks. It should only
* be used to calculate single value for a given frame. * be used to calculate single value for a given frame.
* *
* @author Marcin Roguski * @author Marcin Roguski
*/ */
private class ConstIpo extends Ipo { private class ConstIpo extends Ipo {
/** The constant value of this ipo. */ /** The constant value of this ipo. */
private float constValue; private float constValue;
/** /**
* Constructor. Stores the constant value of this ipo. * Constructor. Stores the constant value of this ipo.
* *
* @param constValue * @param constValue
* the constant value of this ipo * the constant value of this ipo
*/ */
public ConstIpo(float constValue) { public ConstIpo(float constValue) {
super(null, false, 0);//the version is not important here super(null, false, 0);// the version is not important here
this.constValue = constValue; this.constValue = constValue;
} }
@Override @Override
public float calculateValue(int frame) { public float calculateValue(int frame) {
return constValue; return constValue;
} }
@Override @Override
public float calculateValue(int frame, int curveIndex) { public float calculateValue(int frame, int curveIndex) {
return constValue; return constValue;
} }
@Override @Override
public int getCurvesAmount() { public int getCurvesAmount() {
return 0; return 0;
} }
@Override @Override
public BoneTrack calculateTrack(int boneIndex, Quaternion localQuaternionRotation, int startFrame, int stopFrame, int fps, boolean boneTrack) { public BoneTrack calculateTrack(int boneIndex, Quaternion localQuaternionRotation, int startFrame, int stopFrame, int fps, boolean boneTrack) {
throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!"); throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!");
} }
} }
} }

@ -17,59 +17,59 @@ import java.util.logging.Logger;
*/ */
public class CameraHelper extends AbstractBlenderHelper { public class CameraHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(CameraHelper.class.getName()); private static final Logger LOGGER = Logger.getLogger(CameraHelper.class.getName());
protected static final int DEFAULT_CAM_WIDTH = 640; protected static final int DEFAULT_CAM_WIDTH = 640;
protected static final int DEFAULT_CAM_HEIGHT = 480; protected static final int DEFAULT_CAM_HEIGHT = 480;
/** /**
* This constructor parses the given blender version and stores the result. Some functionalities may differ in * This constructor parses the given blender version and stores the result. Some functionalities may differ in
* different blender versions. * different blender versions.
* @param blenderVersion * @param blenderVersion
* the version read from the blend file * the version read from the blend file
* @param fixUpAxis * @param fixUpAxis
* a variable that indicates if the Y asxis is the UP axis or not * a variable that indicates if the Y asxis is the UP axis or not
*/ */
public CameraHelper(String blenderVersion, boolean fixUpAxis) { public CameraHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis); super(blenderVersion, fixUpAxis);
} }
/** /**
* This method converts the given structure to jme camera. * This method converts the given structure to jme camera.
* *
* @param structure * @param structure
* camera structure * camera structure
* @return jme camera object * @return jme camera object
* @throws BlenderFileException * @throws BlenderFileException
* an exception is thrown when there are problems with the * an exception is thrown when there are problems with the
* blender file * blender file
*/ */
public CameraNode toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException { public CameraNode toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
if (blenderVersion >= 250) { if (blenderVersion >= 250) {
return this.toCamera250(structure, blenderContext.getSceneStructure()); return this.toCamera250(structure, blenderContext.getSceneStructure());
} else { } else {
return this.toCamera249(structure); return this.toCamera249(structure);
} }
} }
/** /**
* This method converts the given structure to jme camera. Should be used form blender 2.5+. * This method converts the given structure to jme camera. Should be used form blender 2.5+.
* *
* @param structure * @param structure
* camera structure * camera structure
* @param sceneStructure * @param sceneStructure
* scene structure * scene structure
* @return jme camera object * @return jme camera object
* @throws BlenderFileException * @throws BlenderFileException
* an exception is thrown when there are problems with the * an exception is thrown when there are problems with the
* blender file * blender file
*/ */
private CameraNode toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException { private CameraNode toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException {
int width = DEFAULT_CAM_WIDTH; int width = DEFAULT_CAM_WIDTH;
int height = DEFAULT_CAM_HEIGHT; int height = DEFAULT_CAM_HEIGHT;
if (sceneStructure != null) { if (sceneStructure != null) {
Structure renderData = (Structure)sceneStructure.getFieldValue("r"); Structure renderData = (Structure) sceneStructure.getFieldValue("r");
width = ((Number)renderData.getFieldValue("xsch")).shortValue(); width = ((Number) renderData.getFieldValue("xsch")).shortValue();
height = ((Number)renderData.getFieldValue("ysch")).shortValue(); height = ((Number) renderData.getFieldValue("ysch")).shortValue();
} }
Camera camera = new Camera(width, height); Camera camera = new Camera(width, height);
int type = ((Number) structure.getFieldValue("type")).intValue(); int type = ((Number) structure.getFieldValue("type")).intValue();
@ -77,9 +77,9 @@ public class CameraHelper extends AbstractBlenderHelper {
LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type); LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type);
type = 0; type = 0;
} }
//type==0 - perspective; type==1 - orthographic; perspective is used as default // type==0 - perspective; type==1 - orthographic; perspective is used as default
camera.setParallelProjection(type == 1); camera.setParallelProjection(type == 1);
float aspect = width / (float)height; float aspect = width / (float) height;
float fovY; // Vertical field of view in degrees float fovY; // Vertical field of view in degrees
float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue(); float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue();
float clipend = ((Number) structure.getFieldValue("clipend")).floatValue(); float clipend = ((Number) structure.getFieldValue("clipend")).floatValue();
@ -88,7 +88,7 @@ public class CameraHelper extends AbstractBlenderHelper {
// Default sensor size prior to 2.60 was 32. // Default sensor size prior to 2.60 was 32.
float sensor = 32.0f; float sensor = 32.0f;
boolean sensorVertical = false; boolean sensorVertical = false;
Number sensorFit = (Number)structure.getFieldValue("sensor_fit"); Number sensorFit = (Number) structure.getFieldValue("sensor_fit");
if (sensorFit != null) { if (sensorFit != null) {
// If sensor_fit is vert (2), then sensor_y is used // If sensor_fit is vert (2), then sensor_y is used
sensorVertical = sensorFit.byteValue() == 2; sensorVertical = sensorFit.byteValue() == 2;
@ -113,17 +113,17 @@ public class CameraHelper extends AbstractBlenderHelper {
camera.setFrustumPerspective(fovY, aspect, clipsta, clipend); camera.setFrustumPerspective(fovY, aspect, clipsta, clipend);
return new CameraNode(null, camera); return new CameraNode(null, camera);
} }
/** /**
* This method converts the given structure to jme camera. Should be used form blender 2.49. * This method converts the given structure to jme camera. Should be used form blender 2.49.
* *
* @param structure * @param structure
* camera structure * camera structure
* @return jme camera object * @return jme camera object
* @throws BlenderFileException * @throws BlenderFileException
* an exception is thrown when there are problems with the * an exception is thrown when there are problems with the
* blender file * blender file
*/ */
private CameraNode toCamera249(Structure structure) throws BlenderFileException { private CameraNode toCamera249(Structure structure) throws BlenderFileException {
Camera camera = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT); Camera camera = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT);
int type = ((Number) structure.getFieldValue("type")).intValue(); int type = ((Number) structure.getFieldValue("type")).intValue();
@ -131,7 +131,7 @@ public class CameraHelper extends AbstractBlenderHelper {
LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type); LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type);
type = 0; type = 0;
} }
//type==0 - perspective; type==1 - orthographic; perspective is used as default // type==0 - perspective; type==1 - orthographic; perspective is used as default
camera.setParallelProjection(type == 1); camera.setParallelProjection(type == 1);
float aspect = 0; float aspect = 0;
float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue(); float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue();
@ -145,8 +145,8 @@ public class CameraHelper extends AbstractBlenderHelper {
return new CameraNode(null, camera); return new CameraNode(null, camera);
} }
@Override @Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.CAMERAS) != 0; return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.CAMERAS) != 0;
} }
} }

@ -26,194 +26,193 @@ import com.jme3.scene.plugins.ogre.AnimData;
* Constraint applied on the bone. * Constraint applied on the bone.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class BoneConstraint extends Constraint { /* package */class BoneConstraint extends Constraint {
private static final Logger LOGGER = Logger.getLogger(BoneConstraint.class.getName()); private static final Logger LOGGER = Logger.getLogger(BoneConstraint.class.getName());
protected boolean isNodeTarget; protected boolean isNodeTarget;
/** /**
* The bone constraint constructor. * The bone constraint constructor.
* *
* @param constraintStructure * @param constraintStructure
* the constraint's structure * the constraint's structure
* @param ownerOMA * @param ownerOMA
* the OMA of the bone that owns the constraint * the OMA of the bone that owns the constraint
* @param influenceIpo * @param influenceIpo
* the influence interpolation curve * the influence interpolation curve
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
* exception thrown when problems with blender file occur * exception thrown when problems with blender file occur
*/ */
public BoneConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) public BoneConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
throws BlenderFileException { super(constraintStructure, ownerOMA, influenceIpo, blenderContext);
super(constraintStructure, ownerOMA, influenceIpo, blenderContext); }
}
@Override
@Override protected boolean validate() {
protected boolean validate() { if (targetOMA != null) {
if(targetOMA != null) { Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE);
Spatial nodeTarget = (Spatial)blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE); // the second part of the if expression verifies if the found node (if any) is an armature node
//the second part of the if expression verifies if the found node (if any) is an armature node if (nodeTarget == null || nodeTarget.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) != null) {
if(nodeTarget == null || nodeTarget.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) != null) { // if the target is not an object node then it is an Armature, so make sure the bone is in the current skeleton
//if the target is not an object node then it is an Armature, so make sure the bone is in the current skeleton BoneContext boneContext = blenderContext.getBoneContext(ownerOMA);
BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); if (targetOMA.longValue() != boneContext.getArmatureObjectOMA().longValue()) {
if(targetOMA.longValue() != boneContext.getArmatureObjectOMA().longValue()) { LOGGER.log(Level.WARNING, "Bone constraint {0} must target bone in the its own skeleton! Targeting bone in another skeleton is not supported!", name);
LOGGER.log(Level.WARNING, "Bone constraint {0} must target bone in the its own skeleton! Targeting bone in another skeleton is not supported!", name); return false;
return false; }
} } else {
} else { isNodeTarget = true;
isNodeTarget = true; }
} }
}
return true;
return true; }
}
@Override
@Override public void performBakingOperation() {
public void performBakingOperation() { Bone owner = blenderContext.getBoneContext(ownerOMA).getBone();
Bone owner = blenderContext.getBoneContext(ownerOMA).getBone();
if (targetOMA != null) {
if(targetOMA != null) { if (isNodeTarget) {
if(isNodeTarget) { Spatial target = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE);
Spatial target = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE); this.prepareTracksForApplyingConstraints();
this.prepareTracksForApplyingConstraints(); AnimData animData = blenderContext.getAnimData(ownerOMA);
AnimData animData = blenderContext.getAnimData(ownerOMA); if (animData != null) {
if(animData != null) { for (Animation animation : animData.anims) {
for(Animation animation : animData.anims) { Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner);
Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner); Transform targetTransform = constraintHelper.getNodeObjectTransform(targetSpace, targetOMA, blenderContext);
Transform targetTransform = constraintHelper.getNodeObjectTransform(targetSpace, targetOMA, blenderContext);
Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation);
Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation); Track targetTrack = constraintHelper.getTrack(target, animation);
Track targetTrack = constraintHelper.getTrack(target, animation);
constraintDefinition.bake(ownerTransform, targetTransform, boneTrack, targetTrack, this.ipo);
constraintDefinition.bake(ownerTransform, targetTransform, boneTrack, targetTrack, this.ipo); }
} }
} } else {
} else { BoneContext boneContext = blenderContext.getBoneByName(subtargetName);
BoneContext boneContext = blenderContext.getBoneByName(subtargetName); Bone target = boneContext.getBone();
Bone target = boneContext.getBone(); this.targetOMA = boneContext.getBoneOma();
this.targetOMA = boneContext.getBoneOma();
this.prepareTracksForApplyingConstraints();
this.prepareTracksForApplyingConstraints(); AnimData animData = blenderContext.getAnimData(ownerOMA);
AnimData animData = blenderContext.getAnimData(ownerOMA); if (animData != null) {
if(animData != null) { for (Animation animation : animData.anims) {
for(Animation animation : animData.anims) { Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner);
Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner); Transform targetTransform = constraintHelper.getBoneTransform(targetSpace, target);
Transform targetTransform = constraintHelper.getBoneTransform(targetSpace, target);
Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation);
Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation); Track targetTrack = constraintHelper.getTrack(target, animData.skeleton, animation);
Track targetTrack = constraintHelper.getTrack(target, animData.skeleton, animation);
constraintDefinition.bake(ownerTransform, targetTransform, boneTrack, targetTrack, this.ipo);
constraintDefinition.bake(ownerTransform, targetTransform, boneTrack, targetTrack, this.ipo); }
} }
} }
} } else {
} else { this.prepareTracksForApplyingConstraints();
this.prepareTracksForApplyingConstraints(); AnimData animData = blenderContext.getAnimData(ownerOMA);
AnimData animData = blenderContext.getAnimData(ownerOMA); if (animData != null) {
if(animData != null) { for (Animation animation : animData.anims) {
for(Animation animation : animData.anims) { Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner);
Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner); Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation);
Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation);
constraintDefinition.bake(ownerTransform, null, boneTrack, null, this.ipo);
constraintDefinition.bake(ownerTransform, null, boneTrack, null, this.ipo); }
} }
} }
} }
}
@Override
@Override protected void prepareTracksForApplyingConstraints() {
protected void prepareTracksForApplyingConstraints() { Long[] bonesOMAs = new Long[] { ownerOMA, targetOMA };
Long[] bonesOMAs = new Long[] { ownerOMA, targetOMA }; Space[] spaces = new Space[] { ownerSpace, targetSpace };
Space[] spaces = new Space[] { ownerSpace, targetSpace };
// creating animations for current objects if at least on of their parents have an animation
//creating animations for current objects if at least on of their parents have an animation for (int i = 0; i < bonesOMAs.length; ++i) {
for (int i = 0; i < bonesOMAs.length; ++i) { Long oma = bonesOMAs[i];
Long oma = bonesOMAs[i]; if (this.hasAnimation(oma)) {
if(this.hasAnimation(oma)) { Bone currentBone = blenderContext.getBoneContext(oma).getBone();
Bone currentBone = blenderContext.getBoneContext(oma).getBone(); Bone parent = currentBone.getParent();
Bone parent = currentBone.getParent(); boolean foundAnimation = false;
boolean foundAnimation = false; AnimData animData = null;
AnimData animData = null; while (parent != null && !foundAnimation) {
while(parent != null && !foundAnimation) { BoneContext boneContext = blenderContext.getBoneByName(parent.getName());
BoneContext boneContext = blenderContext.getBoneByName(parent.getName()); foundAnimation = this.hasAnimation(boneContext.getBoneOma());
foundAnimation = this.hasAnimation(boneContext.getBoneOma()); animData = blenderContext.getAnimData(boneContext.getBoneOma());
animData = blenderContext.getAnimData(boneContext.getBoneOma()); parent = parent.getParent();
parent = parent.getParent(); }
}
if (foundAnimation) {
if(foundAnimation) { this.applyAnimData(blenderContext.getBoneContext(oma), spaces[i], animData);
this.applyAnimData(blenderContext.getBoneContext(oma), spaces[i], animData); }
} }
} }
}
// creating animation for owner if it doesn't have one already and if the target has it
//creating animation for owner if it doesn't have one already and if the target has it if (!this.hasAnimation(ownerOMA) && this.hasAnimation(targetOMA)) {
if(!this.hasAnimation(ownerOMA) && this.hasAnimation(targetOMA)) { AnimData targetAnimData = blenderContext.getAnimData(targetOMA);
AnimData targetAnimData = blenderContext.getAnimData(targetOMA); this.applyAnimData(blenderContext.getBoneContext(ownerOMA), ownerSpace, targetAnimData);
this.applyAnimData(blenderContext.getBoneContext(ownerOMA), ownerSpace, targetAnimData); }
} }
}
/**
/** * The method determines if the bone has animations.
* The method determines if the bone has animations. *
* * @param animOwnerOMA
* @param animOwnerOMA * OMA of the animation's owner
* OMA of the animation's owner * @return <b>true</b> if the target has animations and <b>false</b> otherwise
* @return <b>true</b> if the target has animations and <b>false</b> otherwise */
*/ protected boolean hasAnimation(Long animOwnerOMA) {
protected boolean hasAnimation(Long animOwnerOMA) { AnimData animData = blenderContext.getAnimData(animOwnerOMA);
AnimData animData = blenderContext.getAnimData(animOwnerOMA); if (animData != null) {
if(animData != null) { if (!isNodeTarget) {
if(!isNodeTarget) { Bone bone = blenderContext.getBoneContext(animOwnerOMA).getBone();
Bone bone = blenderContext.getBoneContext(animOwnerOMA).getBone(); int boneIndex = animData.skeleton.getBoneIndex(bone);
int boneIndex = animData.skeleton.getBoneIndex(bone); for (Animation animation : animData.anims) {
for(Animation animation : animData.anims) { for (Track track : animation.getTracks()) {
for(Track track : animation.getTracks()) { if (track instanceof BoneTrack && ((BoneTrack) track).getTargetBoneIndex() == boneIndex) {
if(track instanceof BoneTrack && ((BoneTrack) track).getTargetBoneIndex() == boneIndex) { return true;
return true; }
} }
} }
} } else {
} else { return true;
return true; }
} }
} return false;
return false; }
}
/**
/** * The method applies bone's current position to all of the traces of the
* The method applies bone's current position to all of the traces of the * given animations.
* given animations. *
* * @param boneContext
* @param boneContext * the bone context
* the bone context * @param space
* @param space * the bone's evaluation space
* the bone's evaluation space * @param referenceAnimData
* @param referenceAnimData * the object containing the animations
* the object containing the animations */
*/ protected void applyAnimData(BoneContext boneContext, Space space, AnimData referenceAnimData) {
protected void applyAnimData(BoneContext boneContext, Space space, AnimData referenceAnimData) { ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); Transform transform = constraintHelper.getBoneTransform(space, boneContext.getBone());
Transform transform = constraintHelper.getBoneTransform(space, boneContext.getBone());
AnimData animData = blenderContext.getAnimData(boneContext.getBoneOma());
AnimData animData = blenderContext.getAnimData(boneContext.getBoneOma());
for (Animation animation : referenceAnimData.anims) {
for(Animation animation : referenceAnimData.anims) { BoneTrack parentTrack = (BoneTrack) animation.getTracks()[0];
BoneTrack parentTrack = (BoneTrack) animation.getTracks()[0];
float[] times = parentTrack.getTimes();
float[] times = parentTrack.getTimes(); Vector3f[] translations = new Vector3f[times.length];
Vector3f[] translations = new Vector3f[times.length]; Quaternion[] rotations = new Quaternion[times.length];
Quaternion[] rotations = new Quaternion[times.length]; Vector3f[] scales = new Vector3f[times.length];
Vector3f[] scales = new Vector3f[times.length]; Arrays.fill(translations, transform.getTranslation());
Arrays.fill(translations, transform.getTranslation()); Arrays.fill(rotations, transform.getRotation());
Arrays.fill(rotations, transform.getRotation()); Arrays.fill(scales, transform.getScale());
Arrays.fill(scales, transform.getScale()); for (Animation anim : animData.anims) {
for(Animation anim : animData.anims) { anim.addTrack(new BoneTrack(animData.skeleton.getBoneIndex(boneContext.getBone()), times, translations, rotations, scales));
anim.addTrack(new BoneTrack(animData.skeleton.getBoneIndex(boneContext.getBone()), times, translations, rotations, scales)); }
} }
} blenderContext.setAnimData(boneContext.getBoneOma(), animData);
blenderContext.setAnimData(boneContext.getBoneOma(), animData); }
}
} }

@ -19,109 +19,109 @@ import com.jme3.scene.plugins.blender.file.Structure;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public abstract class Constraint { public abstract class Constraint {
private static final Logger LOGGER = Logger.getLogger(Constraint.class.getName()); private static final Logger LOGGER = Logger.getLogger(Constraint.class.getName());
/** The name of this constraint. */ /** The name of this constraint. */
protected final String name; protected final String name;
/** Indicates if the constraint is already baked or not. */ /** Indicates if the constraint is already baked or not. */
protected boolean baked; protected boolean baked;
protected Space ownerSpace; protected Space ownerSpace;
protected final ConstraintDefinition constraintDefinition; protected final ConstraintDefinition constraintDefinition;
protected Long ownerOMA; protected Long ownerOMA;
protected Long targetOMA; protected Long targetOMA;
protected Space targetSpace; protected Space targetSpace;
protected String subtargetName; protected String subtargetName;
/** The ipo object defining influence. */ /** The ipo object defining influence. */
protected final Ipo ipo; protected final Ipo ipo;
/** The blender context. */ /** The blender context. */
protected final BlenderContext blenderContext; protected final BlenderContext blenderContext;
protected final ConstraintHelper constraintHelper; protected final ConstraintHelper constraintHelper;
/** /**
* This constructor creates the constraint instance. * This constructor creates the constraint instance.
* *
* @param constraintStructure * @param constraintStructure
* the constraint's structure (bConstraint clss in blender 2.49). * the constraint's structure (bConstraint clss in blender 2.49).
* @param ownerOMA * @param ownerOMA
* the old memory address of the constraint owner * the old memory address of the constraint owner
* @param influenceIpo * @param influenceIpo
* the ipo curve of the influence factor * the ipo curve of the influence factor
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blender file is somehow * this exception is thrown when the blender file is somehow
* corrupted * corrupted
*/ */
public Constraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { public Constraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
this.blenderContext = blenderContext; this.blenderContext = blenderContext;
this.name = constraintStructure.getFieldValue("name").toString(); this.name = constraintStructure.getFieldValue("name").toString();
Pointer pData = (Pointer) constraintStructure.getFieldValue("data"); Pointer pData = (Pointer) constraintStructure.getFieldValue("data");
if (pData.isNotNull()) { if (pData.isNotNull()) {
Structure data = pData.fetchData(blenderContext.getInputStream()).get(0); Structure data = pData.fetchData(blenderContext.getInputStream()).get(0);
constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, blenderContext); constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, blenderContext);
Pointer pTar = (Pointer)data.getFieldValue("tar"); Pointer pTar = (Pointer) data.getFieldValue("tar");
if(pTar!= null && pTar.isNotNull()) { if (pTar != null && pTar.isNotNull()) {
this.targetOMA = pTar.getOldMemoryAddress(); this.targetOMA = pTar.getOldMemoryAddress();
this.targetSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("tarspace")).byteValue()); this.targetSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("tarspace")).byteValue());
Object subtargetValue = data.getFieldValue("subtarget"); Object subtargetValue = data.getFieldValue("subtarget");
if(subtargetValue != null) {//not all constraint data have the subtarget field if (subtargetValue != null) {// not all constraint data have the subtarget field
subtargetName = subtargetValue.toString(); subtargetName = subtargetValue.toString();
} }
} }
} else { } else {
//Null constraint has no data, so create it here // Null constraint has no data, so create it here
constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, blenderContext); constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, blenderContext);
} }
this.ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue()); this.ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue());
this.ipo = influenceIpo; this.ipo = influenceIpo;
this.ownerOMA = ownerOMA; this.ownerOMA = ownerOMA;
this.constraintHelper = blenderContext.getHelper(ConstraintHelper.class); this.constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
} }
/** /**
* This method bakes the required sontraints into its owner. It checks if the constraint is invalid * This method bakes the required sontraints into its owner. It checks if the constraint is invalid
* or if it isn't yet baked. It also performs baking of its target constraints so that the proper baking * or if it isn't yet baked. It also performs baking of its target constraints so that the proper baking
* order is kept. * order is kept.
*/ */
public void bake() { public void bake() {
if(!this.validate()) { if (!this.validate()) {
LOGGER.warning("The constraint " + name + " is invalid and will not be applied."); LOGGER.warning("The constraint " + name + " is invalid and will not be applied.");
} else if(!baked) { } else if (!baked) {
if(targetOMA != null) { if (targetOMA != null) {
List<Constraint> targetConstraints = blenderContext.getConstraints(targetOMA); List<Constraint> targetConstraints = blenderContext.getConstraints(targetOMA);
if(targetConstraints != null && targetConstraints.size() > 0) { if (targetConstraints != null && targetConstraints.size() > 0) {
LOGGER.log(Level.FINE, "Baking target constraints of constraint: {0}", name); LOGGER.log(Level.FINE, "Baking target constraints of constraint: {0}", name);
for(Constraint targetConstraint : targetConstraints) { for (Constraint targetConstraint : targetConstraints) {
targetConstraint.bake(); targetConstraint.bake();
} }
} }
} }
LOGGER.log(Level.FINE, "Performing baking of constraint: {0}", name); LOGGER.log(Level.FINE, "Performing baking of constraint: {0}", name);
this.performBakingOperation(); this.performBakingOperation();
baked = true; baked = true;
} }
} }
/** /**
* Performs validation before baking. Checks factors that can prevent constraint from baking that could not be * Performs validation before baking. Checks factors that can prevent constraint from baking that could not be
* checked during constraint loading. * checked during constraint loading.
*/ */
protected abstract boolean validate(); protected abstract boolean validate();
/** /**
* This method should be overwridden and perform the baking opertion. * This method should be overwridden and perform the baking opertion.
*/ */
protected abstract void performBakingOperation(); protected abstract void performBakingOperation();
/** /**
* This method prepares the tracks for both owner and parent. If either owner or parent have no track while its parent has - * This method prepares the tracks for both owner and parent. If either owner or parent have no track while its parent has -
* the tracks are created. The tracks will not modify the owner/target movement but will be there ready for applying constraints. * the tracks are created. The tracks will not modify the owner/target movement but will be there ready for applying constraints.
* For example if the owner is a spatial and has no animation but its parent is moving then the track is created for the owner * For example if the owner is a spatial and has no animation but its parent is moving then the track is created for the owner
* that will have non modifying values for translation, rotation and scale and will have the same amount of frames as its parent has. * that will have non modifying values for translation, rotation and scale and will have the same amount of frames as its parent has.
*/ */
protected abstract void prepareTracksForApplyingConstraints(); protected abstract void prepareTracksForApplyingConstraints();
} }

@ -32,444 +32,444 @@ import com.jme3.scene.plugins.blender.file.Structure;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class ConstraintHelper extends AbstractBlenderHelper { public class ConstraintHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(ConstraintHelper.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintHelper.class.getName());
/** /**
* Helper constructor. It's main task is to generate the affection functions. These functions are common to all * Helper constructor. It's main task is to generate the affection functions. These functions are common to all
* ConstraintHelper instances. Unfortunately this constructor might grow large. If it becomes too large - I shall * ConstraintHelper instances. Unfortunately this constructor might grow large. If it becomes too large - I shall
* consider refactoring. The constructor parses the given blender version and stores the result. Some * consider refactoring. The constructor parses the given blender version and stores the result. Some
* functionalities may differ in different blender versions. * functionalities may differ in different blender versions.
* @param blenderVersion * @param blenderVersion
* the version read from the blend file * the version read from the blend file
* @param fixUpAxis * @param fixUpAxis
* a variable that indicates if the Y asxis is the UP axis or not * a variable that indicates if the Y asxis is the UP axis or not
*/ */
public ConstraintHelper(String blenderVersion, BlenderContext blenderContext, boolean fixUpAxis) { public ConstraintHelper(String blenderVersion, BlenderContext blenderContext, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis); super(blenderVersion, fixUpAxis);
} }
/** /**
* This method reads constraints for for the given structure. The * This method reads constraints for for the given structure. The
* constraints are loaded only once for object/bone. * constraints are loaded only once for object/bone.
* *
* @param objectStructure * @param objectStructure
* the structure we read constraint's for * the structure we read constraint's for
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
*/ */
public void loadConstraints(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { public void loadConstraints(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.fine("Loading constraints."); LOGGER.fine("Loading constraints.");
// reading influence ipos for the constraints // reading influence ipos for the constraints
IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);
Map<String, Map<String, Ipo>> constraintsIpos = new HashMap<String, Map<String, Ipo>>(); Map<String, Map<String, Ipo>> constraintsIpos = new HashMap<String, Map<String, Ipo>>();
Pointer pActions = (Pointer) objectStructure.getFieldValue("action"); Pointer pActions = (Pointer) objectStructure.getFieldValue("action");
if (pActions.isNotNull()) { if (pActions.isNotNull()) {
List<Structure> actions = pActions.fetchData(blenderContext.getInputStream()); List<Structure> actions = pActions.fetchData(blenderContext.getInputStream());
for (Structure action : actions) { for (Structure action : actions) {
Structure chanbase = (Structure) action.getFieldValue("chanbase"); Structure chanbase = (Structure) action.getFieldValue("chanbase");
List<Structure> actionChannels = chanbase.evaluateListBase(blenderContext); List<Structure> actionChannels = chanbase.evaluateListBase(blenderContext);
for (Structure actionChannel : actionChannels) { for (Structure actionChannel : actionChannels) {
Map<String, Ipo> ipos = new HashMap<String, Ipo>(); Map<String, Ipo> ipos = new HashMap<String, Ipo>();
Structure constChannels = (Structure) actionChannel.getFieldValue("constraintChannels"); Structure constChannels = (Structure) actionChannel.getFieldValue("constraintChannels");
List<Structure> constraintChannels = constChannels.evaluateListBase(blenderContext); List<Structure> constraintChannels = constChannels.evaluateListBase(blenderContext);
for (Structure constraintChannel : constraintChannels) { for (Structure constraintChannel : constraintChannels) {
Pointer pIpo = (Pointer) constraintChannel.getFieldValue("ipo"); Pointer pIpo = (Pointer) constraintChannel.getFieldValue("ipo");
if (pIpo.isNotNull()) { if (pIpo.isNotNull()) {
String constraintName = constraintChannel.getFieldValue("name").toString(); String constraintName = constraintChannel.getFieldValue("name").toString();
Ipo ipo = ipoHelper.fromIpoStructure(pIpo.fetchData(blenderContext.getInputStream()).get(0), blenderContext); Ipo ipo = ipoHelper.fromIpoStructure(pIpo.fetchData(blenderContext.getInputStream()).get(0), blenderContext);
ipos.put(constraintName, ipo); ipos.put(constraintName, ipo);
} }
} }
String actionName = actionChannel.getFieldValue("name").toString(); String actionName = actionChannel.getFieldValue("name").toString();
constraintsIpos.put(actionName, ipos); constraintsIpos.put(actionName, ipos);
} }
} }
} }
//loading constraints connected with the object's bones // loading constraints connected with the object's bones
Pointer pPose = (Pointer) objectStructure.getFieldValue("pose"); Pointer pPose = (Pointer) objectStructure.getFieldValue("pose");
if (pPose.isNotNull()) { if (pPose.isNotNull()) {
List<Structure> poseChannels = ((Structure) pPose.fetchData(blenderContext.getInputStream()).get(0).getFieldValue("chanbase")).evaluateListBase(blenderContext); List<Structure> poseChannels = ((Structure) pPose.fetchData(blenderContext.getInputStream()).get(0).getFieldValue("chanbase")).evaluateListBase(blenderContext);
for (Structure poseChannel : poseChannels) { for (Structure poseChannel : poseChannels) {
List<Constraint> constraintsList = new ArrayList<Constraint>(); List<Constraint> constraintsList = new ArrayList<Constraint>();
Long boneOMA = Long.valueOf(((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress()); Long boneOMA = Long.valueOf(((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress());
//the name is read directly from structure because bone might not yet be loaded // the name is read directly from structure because bone might not yet be loaded
String name = blenderContext.getFileBlock(boneOMA).getStructure(blenderContext).getFieldValue("name").toString(); String name = blenderContext.getFileBlock(boneOMA).getStructure(blenderContext).getFieldValue("name").toString();
List<Structure> constraints = ((Structure) poseChannel.getFieldValue("constraints")).evaluateListBase(blenderContext); List<Structure> constraints = ((Structure) poseChannel.getFieldValue("constraints")).evaluateListBase(blenderContext);
for (Structure constraint : constraints) { for (Structure constraint : constraints) {
String constraintName = constraint.getFieldValue("name").toString(); String constraintName = constraint.getFieldValue("name").toString();
Map<String, Ipo> ipoMap = constraintsIpos.get(name); Map<String, Ipo> ipoMap = constraintsIpos.get(name);
Ipo ipo = ipoMap==null ? null : ipoMap.get(constraintName); Ipo ipo = ipoMap == null ? null : ipoMap.get(constraintName);
if (ipo == null) { if (ipo == null) {
float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue(); float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue();
ipo = ipoHelper.fromValue(enforce); ipo = ipoHelper.fromValue(enforce);
} }
constraintsList.add(new BoneConstraint(constraint, boneOMA, ipo, blenderContext)); constraintsList.add(new BoneConstraint(constraint, boneOMA, ipo, blenderContext));
} }
blenderContext.addConstraints(boneOMA, constraintsList); blenderContext.addConstraints(boneOMA, constraintsList);
} }
} }
//loading constraints connected with the object itself // loading constraints connected with the object itself
List<Structure> constraints = ((Structure)objectStructure.getFieldValue("constraints")).evaluateListBase(blenderContext); List<Structure> constraints = ((Structure) objectStructure.getFieldValue("constraints")).evaluateListBase(blenderContext);
if(constraints != null && constraints.size() > 0) { if (constraints != null && constraints.size() > 0) {
Pointer pData = (Pointer) objectStructure.getFieldValue("data"); Pointer pData = (Pointer) objectStructure.getFieldValue("data");
String dataType = pData.isNotNull() ? pData.fetchData(blenderContext.getInputStream()).get(0).getType() : null; String dataType = pData.isNotNull() ? pData.fetchData(blenderContext.getInputStream()).get(0).getType() : null;
List<Constraint> constraintsList = new ArrayList<Constraint>(constraints.size()); List<Constraint> constraintsList = new ArrayList<Constraint>(constraints.size());
for(Structure constraint : constraints) { for (Structure constraint : constraints) {
String constraintName = constraint.getFieldValue("name").toString(); String constraintName = constraint.getFieldValue("name").toString();
String objectName = objectStructure.getName(); String objectName = objectStructure.getName();
Map<String, Ipo> objectConstraintsIpos = constraintsIpos.get(objectName); Map<String, Ipo> objectConstraintsIpos = constraintsIpos.get(objectName);
Ipo ipo = objectConstraintsIpos!=null ? objectConstraintsIpos.get(constraintName) : null; Ipo ipo = objectConstraintsIpos != null ? objectConstraintsIpos.get(constraintName) : null;
if (ipo == null) { if (ipo == null) {
float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue(); float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue();
ipo = ipoHelper.fromValue(enforce); ipo = ipoHelper.fromValue(enforce);
} }
constraintsList.add(this.getConstraint(dataType, constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext)); constraintsList.add(this.getConstraint(dataType, constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext));
} }
blenderContext.addConstraints(objectStructure.getOldMemoryAddress(), constraintsList); blenderContext.addConstraints(objectStructure.getOldMemoryAddress(), constraintsList);
} }
} }
/** /**
* This method creates a proper constraint object depending on the object's * This method creates a proper constraint object depending on the object's
* data type. Supported data types: <li>Mesh <li>Armature <li>Camera <li> * data type. Supported data types: <li>Mesh <li>Armature <li>Camera <li>
* Lamp Bone constraints are created in a different place. * Lamp Bone constraints are created in a different place.
* *
* @param dataType * @param dataType
* the type of the object's data * the type of the object's data
* @param constraintStructure * @param constraintStructure
* the constraint structure * the constraint structure
* @param ownerOMA * @param ownerOMA
* the owner OMA * the owner OMA
* @param influenceIpo * @param influenceIpo
* the influence interpolation curve * the influence interpolation curve
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @return constraint object for the required type * @return constraint object for the required type
* @throws BlenderFileException * @throws BlenderFileException
* thrown when problems with blender file occured * thrown when problems with blender file occured
*/ */
private Constraint getConstraint(String dataType, Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { private Constraint getConstraint(String dataType, Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
if(dataType == null || "Mesh".equalsIgnoreCase(dataType) || "Camera".equalsIgnoreCase(dataType) || "Lamp".equalsIgnoreCase(dataType)) { if (dataType == null || "Mesh".equalsIgnoreCase(dataType) || "Camera".equalsIgnoreCase(dataType) || "Lamp".equalsIgnoreCase(dataType)) {
return new SpatialConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext); return new SpatialConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext);
} else if("Armature".equalsIgnoreCase(dataType)) { } else if ("Armature".equalsIgnoreCase(dataType)) {
return new SkeletonConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext); return new SkeletonConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext);
} else { } else {
throw new IllegalArgumentException("Unsupported data type for applying constraints: " + dataType); throw new IllegalArgumentException("Unsupported data type for applying constraints: " + dataType);
} }
} }
/** /**
* The method bakes all available and valid constraints. * The method bakes all available and valid constraints.
* *
* @param blenderContext * @param blenderContext
* the blender context * the blender context
*/ */
public void bakeConstraints(BlenderContext blenderContext) { public void bakeConstraints(BlenderContext blenderContext) {
for(Constraint constraint : blenderContext.getAllConstraints()) { for (Constraint constraint : blenderContext.getAllConstraints()) {
constraint.bake(); constraint.bake();
} }
} }
/** /**
* The method returns track for bone. * The method returns track for bone.
* *
* @param bone * @param bone
* the bone * the bone
* @param skeleton * @param skeleton
* the bone's skeleton * the bone's skeleton
* @param animation * @param animation
* the bone's animation * the bone's animation
* @return track for the given bone that was found among the given * @return track for the given bone that was found among the given
* animations or null if none is found * animations or null if none is found
*/ */
/*package*/ BoneTrack getTrack(Bone bone, Skeleton skeleton, Animation animation) { /* package */BoneTrack getTrack(Bone bone, Skeleton skeleton, Animation animation) {
int boneIndex = skeleton.getBoneIndex(bone); int boneIndex = skeleton.getBoneIndex(bone);
for (Track track : animation.getTracks()) { for (Track track : animation.getTracks()) {
if (((BoneTrack) track).getTargetBoneIndex() == boneIndex) { if (((BoneTrack) track).getTargetBoneIndex() == boneIndex) {
return (BoneTrack) track; return (BoneTrack) track;
} }
} }
return null; return null;
} }
/** /**
* The method returns track for spatial. * The method returns track for spatial.
* *
* @param bone * @param bone
* the spatial * the spatial
* @param animation * @param animation
* the spatial's animation * the spatial's animation
* @return track for the given spatial that was found among the given * @return track for the given spatial that was found among the given
* animations or null if none is found * animations or null if none is found
*/ */
/*package*/ SpatialTrack getTrack(Spatial spatial, Animation animation) { /* package */SpatialTrack getTrack(Spatial spatial, Animation animation) {
Track[] tracks = animation.getTracks(); Track[] tracks = animation.getTracks();
if(tracks != null && tracks.length == 1) { if (tracks != null && tracks.length == 1) {
return (SpatialTrack)tracks[0]; return (SpatialTrack) tracks[0];
} }
return null; return null;
} }
/** /**
* This method returns the transform read directly from the blender * This method returns the transform read directly from the blender
* structure. This can be used to read transforms from one of the object * structure. This can be used to read transforms from one of the object
* types: <li>Spatial <li>Camera <li>Light * types: <li>Spatial <li>Camera <li>Light
* *
* @param space * @param space
* the space where transform is evaluated * the space where transform is evaluated
* @param spatialOMA * @param spatialOMA
* the OMA of the object * the OMA of the object
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @return the object's transform in a given space * @return the object's transform in a given space
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
/*package*/ Transform getNodeObjectTransform(Space space, Long spatialOMA, BlenderContext blenderContext) { /* package */Transform getNodeObjectTransform(Space space, Long spatialOMA, BlenderContext blenderContext) {
switch (space) { switch (space) {
case CONSTRAINT_SPACE_LOCAL: case CONSTRAINT_SPACE_LOCAL:
Structure targetStructure = (Structure) blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_STRUCTURE); Structure targetStructure = (Structure) blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_STRUCTURE);
DynamicArray<Number> locArray = ((DynamicArray<Number>) targetStructure.getFieldValue("loc")); DynamicArray<Number> locArray = ((DynamicArray<Number>) targetStructure.getFieldValue("loc"));
Vector3f loc = new Vector3f(locArray.get(0).floatValue(), locArray.get(1).floatValue(), locArray.get(2).floatValue()); Vector3f loc = new Vector3f(locArray.get(0).floatValue(), locArray.get(1).floatValue(), locArray.get(2).floatValue());
DynamicArray<Number> rotArray = ((DynamicArray<Number>) targetStructure.getFieldValue("rot")); DynamicArray<Number> rotArray = ((DynamicArray<Number>) targetStructure.getFieldValue("rot"));
Quaternion rot = new Quaternion(new float[] { rotArray.get(0).floatValue(), rotArray.get(1).floatValue(), rotArray.get(2).floatValue() }); Quaternion rot = new Quaternion(new float[] { rotArray.get(0).floatValue(), rotArray.get(1).floatValue(), rotArray.get(2).floatValue() });
DynamicArray<Number> sizeArray = ((DynamicArray<Number>) targetStructure.getFieldValue("size")); DynamicArray<Number> sizeArray = ((DynamicArray<Number>) targetStructure.getFieldValue("size"));
Vector3f size = new Vector3f(sizeArray.get(0).floatValue(), sizeArray.get(1).floatValue(), sizeArray.get(2).floatValue()); Vector3f size = new Vector3f(sizeArray.get(0).floatValue(), sizeArray.get(1).floatValue(), sizeArray.get(2).floatValue());
if (blenderContext.getBlenderKey().isFixUpAxis()) { if (blenderContext.getBlenderKey().isFixUpAxis()) {
float y = loc.y; float y = loc.y;
loc.y = loc.z; loc.y = loc.z;
loc.z = -y; loc.z = -y;
y = rot.getY(); y = rot.getY();
float z = rot.getZ(); float z = rot.getZ();
rot.set(rot.getX(), z, -y, rot.getW()); rot.set(rot.getX(), z, -y, rot.getW());
y = size.y; y = size.y;
size.y = size.z; size.y = size.z;
size.z = y; size.z = y;
} }
Transform result = new Transform(loc, rot); Transform result = new Transform(loc, rot);
result.setScale(size); result.setScale(size);
return result; return result;
case CONSTRAINT_SPACE_WORLD://TODO: get it from the object structure ??? case CONSTRAINT_SPACE_WORLD:// TODO: get it from the object structure ???
Object feature = blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_FEATURE); Object feature = blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_FEATURE);
if(feature instanceof Spatial) { if (feature instanceof Spatial) {
return ((Spatial) feature).getWorldTransform(); return ((Spatial) feature).getWorldTransform();
} else if(feature instanceof Skeleton) { } else if (feature instanceof Skeleton) {
LOGGER.warning("Trying to get transformation for skeleton. This is not supported. Returning null."); LOGGER.warning("Trying to get transformation for skeleton. This is not supported. Returning null.");
return null; return null;
} else { } else {
throw new IllegalArgumentException("Given old memory address does not point to a valid object type (spatial, camera or light)."); throw new IllegalArgumentException("Given old memory address does not point to a valid object type (spatial, camera or light).");
} }
default: default:
throw new IllegalStateException("Invalid space type for target object: " + space.toString()); throw new IllegalStateException("Invalid space type for target object: " + space.toString());
} }
} }
/** /**
* The method returns the transform for the given bone computed in the given * The method returns the transform for the given bone computed in the given
* space. * space.
* *
* @param space * @param space
* the computation space * the computation space
* @param bone * @param bone
* the bone we get the transform from * the bone we get the transform from
* @return the transform of the given bone * @return the transform of the given bone
*/ */
/*package*/ Transform getBoneTransform(Space space, Bone bone) { /* package */Transform getBoneTransform(Space space, Bone bone) {
switch (space) { switch (space) {
case CONSTRAINT_SPACE_LOCAL: case CONSTRAINT_SPACE_LOCAL:
Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation()); Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());
localTransform.setScale(bone.getLocalScale()); localTransform.setScale(bone.getLocalScale());
return localTransform; return localTransform;
case CONSTRAINT_SPACE_WORLD: case CONSTRAINT_SPACE_WORLD:
Transform worldTransform = new Transform(bone.getWorldBindPosition(), bone.getWorldBindRotation()); Transform worldTransform = new Transform(bone.getWorldBindPosition(), bone.getWorldBindRotation());
worldTransform.setScale(bone.getWorldBindScale()); worldTransform.setScale(bone.getWorldBindScale());
return worldTransform; return worldTransform;
case CONSTRAINT_SPACE_POSE: case CONSTRAINT_SPACE_POSE:
Transform poseTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation()); Transform poseTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());
poseTransform.setScale(bone.getLocalScale()); poseTransform.setScale(bone.getLocalScale());
return poseTransform; return poseTransform;
case CONSTRAINT_SPACE_PARLOCAL: case CONSTRAINT_SPACE_PARLOCAL:
Transform parentLocalTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation()); Transform parentLocalTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());
parentLocalTransform.setScale(bone.getLocalScale()); parentLocalTransform.setScale(bone.getLocalScale());
return parentLocalTransform; return parentLocalTransform;
default: default:
throw new IllegalStateException("Invalid space type for target object: " + space.toString()); throw new IllegalStateException("Invalid space type for target object: " + space.toString());
} }
} }
/** /**
* The method applies the transform for the given spatial, computed in the * The method applies the transform for the given spatial, computed in the
* given space. * given space.
* *
* @param spatial * @param spatial
* the spatial we apply the transform for * the spatial we apply the transform for
* @param space * @param space
* the computation space * the computation space
* @param transform * @param transform
* the transform being applied * the transform being applied
*/ */
/*package*/ void applyTransform(Spatial spatial, Space space, Transform transform) { /* package */void applyTransform(Spatial spatial, Space space, Transform transform) {
switch (space) { switch (space) {
case CONSTRAINT_SPACE_LOCAL: case CONSTRAINT_SPACE_LOCAL:
Transform ownerLocalTransform = spatial.getLocalTransform(); Transform ownerLocalTransform = spatial.getLocalTransform();
ownerLocalTransform.getTranslation().addLocal(transform.getTranslation()); ownerLocalTransform.getTranslation().addLocal(transform.getTranslation());
ownerLocalTransform.getRotation().multLocal(transform.getRotation()); ownerLocalTransform.getRotation().multLocal(transform.getRotation());
ownerLocalTransform.getScale().multLocal(transform.getScale()); ownerLocalTransform.getScale().multLocal(transform.getScale());
break; break;
case CONSTRAINT_SPACE_WORLD: case CONSTRAINT_SPACE_WORLD:
Matrix4f m = this.getParentWorldTransformMatrix(spatial); Matrix4f m = this.getParentWorldTransformMatrix(spatial);
m.invertLocal(); m.invertLocal();
Matrix4f matrix = this.toMatrix(transform); Matrix4f matrix = this.toMatrix(transform);
m.multLocal(matrix); m.multLocal(matrix);
float scaleX = (float) Math.sqrt(m.m00 * m.m00 + m.m10 * m.m10 + m.m20 * m.m20); float scaleX = (float) Math.sqrt(m.m00 * m.m00 + m.m10 * m.m10 + m.m20 * m.m20);
float scaleY = (float) Math.sqrt(m.m01 * m.m01 + m.m11 * m.m11 + m.m21 * m.m21); float scaleY = (float) Math.sqrt(m.m01 * m.m01 + m.m11 * m.m11 + m.m21 * m.m21);
float scaleZ = (float) Math.sqrt(m.m02 * m.m02 + m.m12 * m.m12 + m.m22 * m.m22); float scaleZ = (float) Math.sqrt(m.m02 * m.m02 + m.m12 * m.m12 + m.m22 * m.m22);
transform.setTranslation(m.toTranslationVector()); transform.setTranslation(m.toTranslationVector());
transform.setRotation(m.toRotationQuat()); transform.setRotation(m.toRotationQuat());
transform.setScale(scaleX, scaleY, scaleZ); transform.setScale(scaleX, scaleY, scaleZ);
spatial.setLocalTransform(transform); spatial.setLocalTransform(transform);
break; break;
case CONSTRAINT_SPACE_PARLOCAL: case CONSTRAINT_SPACE_PARLOCAL:
case CONSTRAINT_SPACE_POSE: case CONSTRAINT_SPACE_POSE:
throw new IllegalStateException("Invalid space type (" + space.toString() + ") for owner object."); throw new IllegalStateException("Invalid space type (" + space.toString() + ") for owner object.");
default: default:
throw new IllegalStateException("Invalid space type for target object: " + space.toString()); throw new IllegalStateException("Invalid space type for target object: " + space.toString());
} }
} }
/** /**
* The method applies the transform for the given bone, computed in the * The method applies the transform for the given bone, computed in the
* given space. * given space.
* *
* @param bone * @param bone
* the bone we apply the transform for * the bone we apply the transform for
* @param space * @param space
* the computation space * the computation space
* @param transform * @param transform
* the transform being applied * the transform being applied
*/ */
/*package*/ void applyTransform(Bone bone, Space space, Transform transform) { /* package */void applyTransform(Bone bone, Space space, Transform transform) {
switch (space) { switch (space) {
case CONSTRAINT_SPACE_LOCAL: case CONSTRAINT_SPACE_LOCAL:
bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale()); bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale());
break; break;
case CONSTRAINT_SPACE_WORLD: case CONSTRAINT_SPACE_WORLD:
Matrix4f m = this.getParentWorldTransformMatrix(bone); Matrix4f m = this.getParentWorldTransformMatrix(bone);
// m.invertLocal(); // m.invertLocal();
transform.setTranslation(m.mult(transform.getTranslation())); transform.setTranslation(m.mult(transform.getTranslation()));
transform.setRotation(m.mult(transform.getRotation(), null)); transform.setRotation(m.mult(transform.getRotation(), null));
transform.setScale(transform.getScale()); transform.setScale(transform.getScale());
bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale()); bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale());
// float x = FastMath.HALF_PI/2; // float x = FastMath.HALF_PI/2;
// float y = -FastMath.HALF_PI; // float y = -FastMath.HALF_PI;
// float z = -FastMath.HALF_PI/2; // float z = -FastMath.HALF_PI/2;
// bone.setBindTransforms(new Vector3f(0,0,0), new Quaternion().fromAngles(x, y, z), new Vector3f(1,1,1)); // bone.setBindTransforms(new Vector3f(0,0,0), new Quaternion().fromAngles(x, y, z), new Vector3f(1,1,1));
break; break;
case CONSTRAINT_SPACE_PARLOCAL: case CONSTRAINT_SPACE_PARLOCAL:
Vector3f parentLocalTranslation = bone.getLocalPosition().add(transform.getTranslation()); Vector3f parentLocalTranslation = bone.getLocalPosition().add(transform.getTranslation());
Quaternion parentLocalRotation = bone.getLocalRotation().mult(transform.getRotation()); Quaternion parentLocalRotation = bone.getLocalRotation().mult(transform.getRotation());
bone.setBindTransforms(parentLocalTranslation, parentLocalRotation, transform.getScale()); bone.setBindTransforms(parentLocalTranslation, parentLocalRotation, transform.getScale());
break; break;
case CONSTRAINT_SPACE_POSE: case CONSTRAINT_SPACE_POSE:
bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale()); bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale());
break; break;
default: default:
throw new IllegalStateException("Invalid space type for target object: " + space.toString()); throw new IllegalStateException("Invalid space type for target object: " + space.toString());
} }
} }
/** /**
* @return world transform matrix of the feature's parent or identity matrix * @return world transform matrix of the feature's parent or identity matrix
* if the feature has no parent * if the feature has no parent
*/ */
private Matrix4f getParentWorldTransformMatrix(Spatial spatial) { private Matrix4f getParentWorldTransformMatrix(Spatial spatial) {
Matrix4f result = new Matrix4f(); Matrix4f result = new Matrix4f();
if (spatial.getParent() != null) { if (spatial.getParent() != null) {
Transform t = spatial.getParent().getWorldTransform(); Transform t = spatial.getParent().getWorldTransform();
result.setTransform(t.getTranslation(), t.getScale(), t.getRotation().toRotationMatrix()); result.setTransform(t.getTranslation(), t.getScale(), t.getRotation().toRotationMatrix());
} }
return result; return result;
} }
/** /**
* @return world transform matrix of the feature's parent or identity matrix * @return world transform matrix of the feature's parent or identity matrix
* if the feature has no parent * if the feature has no parent
*/ */
private Matrix4f getParentWorldTransformMatrix(Bone bone) { private Matrix4f getParentWorldTransformMatrix(Bone bone) {
Matrix4f result = new Matrix4f(); Matrix4f result = new Matrix4f();
Bone parent = bone.getParent(); Bone parent = bone.getParent();
if (parent != null) { if (parent != null) {
result.setTransform(parent.getWorldBindPosition(), parent.getWorldBindScale(), parent.getWorldBindRotation().toRotationMatrix()); result.setTransform(parent.getWorldBindPosition(), parent.getWorldBindScale(), parent.getWorldBindRotation().toRotationMatrix());
} }
return result; return result;
} }
/** /**
* Converts given transform to the matrix. * Converts given transform to the matrix.
* *
* @param transform * @param transform
* the transform to be converted * the transform to be converted
* @return 4x4 matri that represents the given transform * @return 4x4 matri that represents the given transform
*/ */
private Matrix4f toMatrix(Transform transform) { private Matrix4f toMatrix(Transform transform) {
Matrix4f result = Matrix4f.IDENTITY; Matrix4f result = Matrix4f.IDENTITY;
if (transform != null) { if (transform != null) {
result = new Matrix4f(); result = new Matrix4f();
result.setTranslation(transform.getTranslation()); result.setTranslation(transform.getTranslation());
result.setRotationQuaternion(transform.getRotation()); result.setRotationQuaternion(transform.getRotation());
result.setScale(transform.getScale()); result.setScale(transform.getScale());
} }
return result; return result;
} }
@Override @Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return true; return true;
} }
/** /**
* The space of target or owner transformation. * The space of target or owner transformation.
* *
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public static enum Space { public static enum Space {
CONSTRAINT_SPACE_WORLD, CONSTRAINT_SPACE_LOCAL, CONSTRAINT_SPACE_POSE, CONSTRAINT_SPACE_PARLOCAL, CONSTRAINT_SPACE_INVALID; CONSTRAINT_SPACE_WORLD, CONSTRAINT_SPACE_LOCAL, CONSTRAINT_SPACE_POSE, CONSTRAINT_SPACE_PARLOCAL, CONSTRAINT_SPACE_INVALID;
/** /**
* This method returns the enum instance when given the appropriate * This method returns the enum instance when given the appropriate
* value from the blend file. * value from the blend file.
* *
* @param c * @param c
* the blender's value of the space modifier * the blender's value of the space modifier
* @return the scape enum instance * @return the scape enum instance
*/ */
public static Space valueOf(byte c) { public static Space valueOf(byte c) {
switch (c) { switch (c) {
case 0: case 0:
return CONSTRAINT_SPACE_WORLD; return CONSTRAINT_SPACE_WORLD;
case 1: case 1:
return CONSTRAINT_SPACE_LOCAL; return CONSTRAINT_SPACE_LOCAL;
case 2: case 2:
return CONSTRAINT_SPACE_POSE; return CONSTRAINT_SPACE_POSE;
case 3: case 3:
return CONSTRAINT_SPACE_PARLOCAL; return CONSTRAINT_SPACE_PARLOCAL;
default: default:
return CONSTRAINT_SPACE_INVALID; return CONSTRAINT_SPACE_INVALID;
} }
} }
} }
} }

@ -15,23 +15,24 @@ import com.jme3.scene.plugins.blender.file.Structure;
* *
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class SkeletonConstraint extends Constraint { /* package */class SkeletonConstraint extends Constraint {
private static final Logger LOGGER = Logger.getLogger(SkeletonConstraint.class.getName()); private static final Logger LOGGER = Logger.getLogger(SkeletonConstraint.class.getName());
public SkeletonConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
super(constraintStructure, ownerOMA, influenceIpo, blenderContext);
}
@Override public SkeletonConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
public void performBakingOperation() { super(constraintStructure, ownerOMA, influenceIpo, blenderContext);
LOGGER.warning("Applying constraints to skeleton is not supported."); }
}
@Override @Override
protected boolean validate() { public void performBakingOperation() {
return true; LOGGER.warning("Applying constraints to skeleton is not supported.");
} }
@Override @Override
protected void prepareTracksForApplyingConstraints() { } protected boolean validate() {
return true;
}
@Override
protected void prepareTracksForApplyingConstraints() {
}
} }

@ -28,146 +28,144 @@ import com.jme3.scene.plugins.ogre.AnimData;
* This includes: nodes, cameras nodes and light nodes. * This includes: nodes, cameras nodes and light nodes.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class SpatialConstraint extends BoneConstraint { /* package */class SpatialConstraint extends BoneConstraint {
private static final Logger LOGGER = Logger.getLogger(SpatialConstraint.class.getName()); private static final Logger LOGGER = Logger.getLogger(SpatialConstraint.class.getName());
/** The owner of the constraint. */ /** The owner of the constraint. */
private Spatial owner; private Spatial owner;
/** The target of the constraint. */ /** The target of the constraint. */
private Object target; private Object target;
public SpatialConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) public SpatialConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
throws BlenderFileException { super(constraintStructure, ownerOMA, influenceIpo, blenderContext);
super(constraintStructure, ownerOMA, influenceIpo, blenderContext); }
}
@Override
@Override public void performBakingOperation() {
public void performBakingOperation() { this.owner = (Spatial) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
this.owner = (Spatial) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE); this.target = targetOMA != null ? blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE) : null;
this.target = targetOMA != null ? blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE) : null; this.prepareTracksForApplyingConstraints();
this.prepareTracksForApplyingConstraints();
// apply static constraint
//apply static constraint Transform ownerTransform = constraintHelper.getNodeObjectTransform(ownerSpace, ownerOMA, blenderContext);
Transform ownerTransform = constraintHelper.getNodeObjectTransform(ownerSpace, ownerOMA, blenderContext); Transform targetTransform = targetOMA != null ? constraintHelper.getNodeObjectTransform(targetSpace, targetOMA, blenderContext) : null;
Transform targetTransform = targetOMA != null ? constraintHelper.getNodeObjectTransform(targetSpace, targetOMA, blenderContext) : null; constraintDefinition.bake(ownerTransform, targetTransform, null, null, this.ipo);
constraintDefinition.bake(ownerTransform, targetTransform, null, null, this.ipo); constraintHelper.applyTransform(owner, ownerSpace, ownerTransform);
constraintHelper.applyTransform(owner, ownerSpace, ownerTransform);
// apply dynamic constraint
//apply dynamic constraint AnimData animData = blenderContext.getAnimData(ownerOMA);
AnimData animData = blenderContext.getAnimData(ownerOMA); if (animData != null) {
if(animData != null) { for (Animation animation : animData.anims) {
for(Animation animation : animData.anims) { SpatialTrack ownerTrack = constraintHelper.getTrack(owner, animation);
SpatialTrack ownerTrack = constraintHelper.getTrack(owner, animation);
AnimData targetAnimData = blenderContext.getAnimData(targetOMA);
AnimData targetAnimData = blenderContext.getAnimData(targetOMA); SpatialTrack targetTrack = null;
SpatialTrack targetTrack = null; if (targetAnimData != null) {
if(targetAnimData != null) { targetTrack = constraintHelper.getTrack((Spatial) target, targetAnimData.anims.get(0));
targetTrack = constraintHelper.getTrack((Spatial)target, targetAnimData.anims.get(0)); }
}
constraintDefinition.bake(ownerTransform, targetTransform, ownerTrack, targetTrack, this.ipo);
constraintDefinition.bake(ownerTransform, targetTransform, ownerTrack, targetTrack, this.ipo); }
} }
} }
}
@Override
@Override protected void prepareTracksForApplyingConstraints() {
protected void prepareTracksForApplyingConstraints() { Long[] spatialsOMAs = new Long[] { ownerOMA, targetOMA };
Long[] spatialsOMAs = new Long[] { ownerOMA, targetOMA }; Space[] spaces = new Space[] { ownerSpace, targetSpace };
Space[] spaces = new Space[] { ownerSpace, targetSpace };
// creating animations for current objects if at least on of their parents have an animation
//creating animations for current objects if at least on of their parents have an animation for (int i = 0; i < spatialsOMAs.length; ++i) {
for (int i = 0; i < spatialsOMAs.length; ++i) { Long oma = spatialsOMAs[i];
Long oma = spatialsOMAs[i]; if (oma != null && oma > 0L) {
if(oma != null && oma > 0L) { AnimData animData = blenderContext.getAnimData(oma);
AnimData animData = blenderContext.getAnimData(oma); if (animData == null) {
if(animData == null) { Spatial currentSpatial = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE);
Spatial currentSpatial = (Spatial)blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE); if (currentSpatial != null) {
if(currentSpatial != null) { if (currentSpatial.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) == Boolean.TRUE) {// look for it among bones
if(currentSpatial.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) == Boolean.TRUE) {//look for it among bones BoneContext currentBoneContext = blenderContext.getBoneByName(subtargetName);
BoneContext currentBoneContext = blenderContext.getBoneByName(subtargetName); Bone currentBone = currentBoneContext.getBone();
Bone currentBone = currentBoneContext.getBone(); Bone parent = currentBone.getParent();
Bone parent = currentBone.getParent(); boolean foundAnimation = false;
boolean foundAnimation = false; while (parent != null && !foundAnimation) {
while(parent != null && !foundAnimation) { BoneContext boneContext = blenderContext.getBoneByName(parent.getName());
BoneContext boneContext = blenderContext.getBoneByName(parent.getName()); foundAnimation = this.hasAnimation(boneContext.getBoneOma());
foundAnimation = this.hasAnimation(boneContext.getBoneOma()); animData = blenderContext.getAnimData(boneContext.getBoneOma());
animData = blenderContext.getAnimData(boneContext.getBoneOma()); parent = parent.getParent();
parent = parent.getParent(); }
} if (foundAnimation) {
if(foundAnimation) { this.applyAnimData(currentBoneContext, spaces[i], animData);
this.applyAnimData(currentBoneContext, spaces[i], animData); }
} } else {
} else { Spatial parent = currentSpatial.getParent();
Spatial parent = currentSpatial.getParent(); while (parent != null && animData == null) {
while(parent != null && animData == null) { Structure parentStructure = (Structure) blenderContext.getLoadedFeature(parent.getName(), LoadedFeatureDataType.LOADED_STRUCTURE);
Structure parentStructure = (Structure)blenderContext.getLoadedFeature(parent.getName(), LoadedFeatureDataType.LOADED_STRUCTURE); if (parentStructure == null) {
if(parentStructure == null) { parent = null;
parent = null; } else {
} else { Long parentOma = parentStructure.getOldMemoryAddress();
Long parentOma = parentStructure.getOldMemoryAddress(); animData = blenderContext.getAnimData(parentOma);
animData = blenderContext.getAnimData(parentOma); parent = parent.getParent();
parent = parent.getParent(); }
} }
}
if (animData != null) {// create anim data for the current object
if(animData != null) {//create anim data for the current object this.applyAnimData(currentSpatial, oma, spaces[i], animData.anims.get(0));
this.applyAnimData(currentSpatial, oma, spaces[i], animData.anims.get(0)); }
} }
} } else {
} else { LOGGER.warning("Couldn't find target object for constraint: " + name + ". Make sure that the target is on layer that is defined to be loaded in blender key!");
LOGGER.warning("Couldn't find target object for constraint: " + name + }
". Make sure that the target is on layer that is defined to be loaded in blender key!"); }
} }
} }
}
} // creating animation for owner if it doesn't have one already and if the target has it
AnimData animData = blenderContext.getAnimData(ownerOMA);
//creating animation for owner if it doesn't have one already and if the target has it if (animData == null) {
AnimData animData = blenderContext.getAnimData(ownerOMA); AnimData targetAnimData = blenderContext.getAnimData(targetOMA);
if(animData == null) { if (targetAnimData != null) {
AnimData targetAnimData = blenderContext.getAnimData(targetOMA); this.applyAnimData(owner, ownerOMA, ownerSpace, targetAnimData.anims.get(0));
if(targetAnimData != null) { }
this.applyAnimData(owner, ownerOMA, ownerSpace, targetAnimData.anims.get(0)); }
} }
}
} /**
* This method applies spatial transform on each frame of the given
/** * animations.
* This method applies spatial transform on each frame of the given *
* animations. * @param spatial
* * the spatial
* @param spatial * @param spatialOma
* the spatial * the OMA of the given spatial
* @param spatialOma * @param space
* the OMA of the given spatial * the space we compute the transform in
* @param space * @param referenceAnimation
* the space we compute the transform in * the object containing the animations
* @param referenceAnimation */
* the object containing the animations private void applyAnimData(Spatial spatial, Long spatialOma, Space space, Animation referenceAnimation) {
*/ ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
private void applyAnimData(Spatial spatial, Long spatialOma, Space space, Animation referenceAnimation) { Transform transform = constraintHelper.getNodeObjectTransform(space, spatialOma, blenderContext);
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
Transform transform = constraintHelper.getNodeObjectTransform(space, spatialOma, blenderContext); SpatialTrack parentTrack = (SpatialTrack) referenceAnimation.getTracks()[0];
SpatialTrack parentTrack = (SpatialTrack) referenceAnimation.getTracks()[0]; HashMap<String, Animation> anims = new HashMap<String, Animation>(1);
Animation animation = new Animation(spatial.getName(), referenceAnimation.getLength());
HashMap<String, Animation> anims = new HashMap<String, Animation>(1); anims.put(spatial.getName(), animation);
Animation animation = new Animation(spatial.getName(), referenceAnimation.getLength());
anims.put(spatial.getName(), animation); float[] times = parentTrack.getTimes();
Vector3f[] translations = new Vector3f[times.length];
float[] times = parentTrack.getTimes(); Quaternion[] rotations = new Quaternion[times.length];
Vector3f[] translations = new Vector3f[times.length]; Vector3f[] scales = new Vector3f[times.length];
Quaternion[] rotations = new Quaternion[times.length]; Arrays.fill(translations, transform.getTranslation());
Vector3f[] scales = new Vector3f[times.length]; Arrays.fill(rotations, transform.getRotation());
Arrays.fill(translations, transform.getTranslation()); Arrays.fill(scales, transform.getScale());
Arrays.fill(rotations, transform.getRotation()); animation.addTrack(new SpatialTrack(times, translations, rotations, scales));
Arrays.fill(scales, transform.getScale());
animation.addTrack(new SpatialTrack(times, translations, rotations, scales)); AnimControl control = new AnimControl(null);
control.setAnimations(anims);
AnimControl control = new AnimControl(null); spatial.addControl(control);
control.setAnimations(anims);
spatial.addControl(control); blenderContext.setAnimData(spatialOma, new AnimData(null, new ArrayList<Animation>(anims.values())));
}
blenderContext.setAnimData(spatialOma, new AnimData(null, new ArrayList<Animation>(anims.values())));
}
} }

@ -19,230 +19,225 @@ import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
public abstract class ConstraintDefinition { public abstract class ConstraintDefinition {
protected int flag; protected int flag;
public ConstraintDefinition(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinition(Structure constraintData, BlenderContext blenderContext) {
if(constraintData != null) {//Null constraint has no data if (constraintData != null) {// Null constraint has no data
Number flag = (Number)constraintData.getFieldValue("flag"); Number flag = (Number) constraintData.getFieldValue("flag");
if(flag != null) { if (flag != null) {
this.flag = flag.intValue(); this.flag = flag.intValue();
} }
} }
} }
public void bake(Transform ownerTransform, Transform targetTransform, Track ownerTrack, Track targetTrack, Ipo influenceIpo) { public void bake(Transform ownerTransform, Transform targetTransform, Track ownerTrack, Track targetTrack, Ipo influenceIpo) {
TrackWrapper ownerWrapperTrack = ownerTrack != null ? new TrackWrapper(ownerTrack) : null; TrackWrapper ownerWrapperTrack = ownerTrack != null ? new TrackWrapper(ownerTrack) : null;
TrackWrapper targetWrapperTrack = targetTrack != null ? new TrackWrapper(targetTrack) : null; TrackWrapper targetWrapperTrack = targetTrack != null ? new TrackWrapper(targetTrack) : null;
//uruchamiamy bake dla transformat zalenie od tego, ktre argumenty s nullami, a ktre - nie // uruchamiamy bake dla transformat zalenie od tego, ktre argumenty s nullami, a ktre - nie
this.bake(ownerTransform, targetTransform, influenceIpo.calculateValue(0)); this.bake(ownerTransform, targetTransform, influenceIpo.calculateValue(0));
if(ownerWrapperTrack != null) { if (ownerWrapperTrack != null) {
float[] ownerTimes = ownerWrapperTrack.getTimes(); float[] ownerTimes = ownerWrapperTrack.getTimes();
Vector3f[] translations = ownerWrapperTrack.getTranslations(); Vector3f[] translations = ownerWrapperTrack.getTranslations();
Quaternion[] rotations = ownerWrapperTrack.getRotations(); Quaternion[] rotations = ownerWrapperTrack.getRotations();
Vector3f[] scales = ownerWrapperTrack.getScales(); Vector3f[] scales = ownerWrapperTrack.getScales();
float[] targetTimes = targetWrapperTrack == null ? null : targetWrapperTrack.getTimes(); float[] targetTimes = targetWrapperTrack == null ? null : targetWrapperTrack.getTimes();
Vector3f[] targetTranslations = targetWrapperTrack == null ? null : targetWrapperTrack.getTranslations(); Vector3f[] targetTranslations = targetWrapperTrack == null ? null : targetWrapperTrack.getTranslations();
Quaternion[] targetRotations = targetWrapperTrack == null ? null : targetWrapperTrack.getRotations(); Quaternion[] targetRotations = targetWrapperTrack == null ? null : targetWrapperTrack.getRotations();
Vector3f[] targetScales = targetWrapperTrack == null ? null : targetWrapperTrack.getScales(); Vector3f[] targetScales = targetWrapperTrack == null ? null : targetWrapperTrack.getScales();
Vector3f translation = new Vector3f(), scale = new Vector3f(); Vector3f translation = new Vector3f(), scale = new Vector3f();
Quaternion rotation = new Quaternion(); Quaternion rotation = new Quaternion();
Transform ownerTemp = new Transform(), targetTemp = new Transform(); Transform ownerTemp = new Transform(), targetTemp = new Transform();
for (int i = 0; i <ownerTimes.length; ++i) { for (int i = 0; i < ownerTimes.length; ++i) {
float t = ownerTimes[i]; float t = ownerTimes[i];
ownerTemp.setTranslation(translations[i]); ownerTemp.setTranslation(translations[i]);
ownerTemp.setRotation(rotations[i]); ownerTemp.setRotation(rotations[i]);
ownerTemp.setScale(scales[i]); ownerTemp.setScale(scales[i]);
if(targetWrapperTrack == null) { if (targetWrapperTrack == null) {
this.bake(ownerTemp, targetTransform, influenceIpo.calculateValue(i)); this.bake(ownerTemp, targetTransform, influenceIpo.calculateValue(i));
} else { } else {
//getting the values that are the interpolation of the target track for the time 't' // getting the values that are the interpolation of the target track for the time 't'
this.interpolate(targetTranslations, targetTimes, t, translation); this.interpolate(targetTranslations, targetTimes, t, translation);
this.interpolate(targetRotations, targetTimes, t, rotation); this.interpolate(targetRotations, targetTimes, t, rotation);
this.interpolate(targetScales, targetTimes, t, scale); this.interpolate(targetScales, targetTimes, t, scale);
targetTemp.setTranslation(translation); targetTemp.setTranslation(translation);
targetTemp.setRotation(rotation); targetTemp.setRotation(rotation);
targetTemp.setScale(scale); targetTemp.setScale(scale);
this.bake(ownerTemp, targetTemp, influenceIpo.calculateValue(i)); this.bake(ownerTemp, targetTemp, influenceIpo.calculateValue(i));
} }
//need to clone here because each of the arrays will reference the same instance if they hold the same value in the compact array // need to clone here because each of the arrays will reference the same instance if they hold the same value in the compact array
translations[i] = ownerTemp.getTranslation().clone(); translations[i] = ownerTemp.getTranslation().clone();
rotations[i] = ownerTemp.getRotation().clone(); rotations[i] = ownerTemp.getRotation().clone();
scales[i] = ownerTemp.getScale().clone(); scales[i] = ownerTemp.getScale().clone();
} }
ownerWrapperTrack.setKeyframes(ownerTimes, translations, rotations, scales); ownerWrapperTrack.setKeyframes(ownerTimes, translations, rotations, scales);
} }
} }
protected abstract void bake(Transform ownerTransform, Transform targetTransform, float influence); protected abstract void bake(Transform ownerTransform, Transform targetTransform, float influence);
private void interpolate(Vector3f[] targetVectors, float[] targetTimes, float currentTime, Vector3f result) { private void interpolate(Vector3f[] targetVectors, float[] targetTimes, float currentTime, Vector3f result) {
int index = 0; int index = 0;
for (int i = 1; i < targetTimes.length; ++i) { for (int i = 1; i < targetTimes.length; ++i) {
if(targetTimes[i] < currentTime) { if (targetTimes[i] < currentTime) {
++index; ++index;
} else { } else {
break; break;
} }
} }
if(index >= targetTimes.length - 1) { if (index >= targetTimes.length - 1) {
result.set(targetVectors[targetTimes.length - 1]); result.set(targetVectors[targetTimes.length - 1]);
} else { } else {
float delta = targetTimes[index + 1] - targetTimes[index]; float delta = targetTimes[index + 1] - targetTimes[index];
if(delta == 0.0f) { if (delta == 0.0f) {
result.set(targetVectors[index + 1]); result.set(targetVectors[index + 1]);
} else { } else {
float scale = (currentTime - targetTimes[index])/(targetTimes[index + 1] - targetTimes[index]); float scale = (currentTime - targetTimes[index]) / (targetTimes[index + 1] - targetTimes[index]);
FastMath.interpolateLinear(scale, targetVectors[index], targetVectors[index + 1], result); FastMath.interpolateLinear(scale, targetVectors[index], targetVectors[index + 1], result);
} }
} }
} }
private void interpolate(Quaternion[] targetQuaternions, float[] targetTimes, float currentTime, Quaternion result) { private void interpolate(Quaternion[] targetQuaternions, float[] targetTimes, float currentTime, Quaternion result) {
int index = 0; int index = 0;
for (int i = 1; i < targetTimes.length; ++i) { for (int i = 1; i < targetTimes.length; ++i) {
if(targetTimes[i] < currentTime) { if (targetTimes[i] < currentTime) {
++index; ++index;
} else { } else {
break; break;
} }
} }
if(index >= targetTimes.length - 1) { if (index >= targetTimes.length - 1) {
result.set(targetQuaternions[targetTimes.length - 1]); result.set(targetQuaternions[targetTimes.length - 1]);
} else { } else {
float delta = targetTimes[index + 1] - targetTimes[index]; float delta = targetTimes[index + 1] - targetTimes[index];
if(delta == 0.0f) { if (delta == 0.0f) {
result.set(targetQuaternions[index + 1]); result.set(targetQuaternions[index + 1]);
} else { } else {
float scale = (currentTime - targetTimes[index])/(targetTimes[index + 1] - targetTimes[index]); float scale = (currentTime - targetTimes[index]) / (targetTimes[index + 1] - targetTimes[index]);
result.slerp(targetQuaternions[index], targetQuaternions[index + 1], scale); result.slerp(targetQuaternions[index], targetQuaternions[index + 1], scale);
} }
} }
} }
/** /**
* This class holds either the bone track or spatial track. Is made to improve * This class holds either the bone track or spatial track. Is made to improve
* code readability. * code readability.
* *
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
private static class TrackWrapper implements Track { private static class TrackWrapper implements Track {
/** The spatial track. */ /** The spatial track. */
private SpatialTrack spatialTrack; private SpatialTrack spatialTrack;
/** The bone track. */ /** The bone track. */
private BoneTrack boneTrack; private BoneTrack boneTrack;
/** /**
* Constructs the object using the given track. The track must be of one of the types: * Constructs the object using the given track. The track must be of one of the types: <li>BoneTrack <li>SpatialTrack
* <li> BoneTrack *
* <li> SpatialTrack * @param track
* * the animation track
* @param track */
* the animation track public TrackWrapper(Track track) {
*/ if (track instanceof SpatialTrack) {
public TrackWrapper(Track track) { this.spatialTrack = (SpatialTrack) track;
if(track instanceof SpatialTrack) { } else if (track instanceof BoneTrack) {
this.spatialTrack = (SpatialTrack)track; this.boneTrack = (BoneTrack) track;
} else if(track instanceof BoneTrack) { } else {
this.boneTrack = (BoneTrack)track; throw new IllegalStateException("Unknown track type!");
} else { }
throw new IllegalStateException("Unknown track type!"); }
}
} /**
* @return the array of rotations of this track
/** */
* @return the array of rotations of this track public Quaternion[] getRotations() {
*/ if (boneTrack != null) {
public Quaternion[] getRotations() { return boneTrack.getRotations();
if (boneTrack != null) { }
return boneTrack.getRotations(); return spatialTrack.getRotations();
} }
return spatialTrack.getRotations();
} /**
* @return the array of scales for this track
/** */
* @return the array of scales for this track public Vector3f[] getScales() {
*/ if (boneTrack != null) {
public Vector3f[] getScales() { return boneTrack.getScales();
if (boneTrack != null) { }
return boneTrack.getScales(); return spatialTrack.getScales();
} }
return spatialTrack.getScales();
} /**
* @return the arrays of time for this track
/** */
* @return the arrays of time for this track public float[] getTimes() {
*/ if (boneTrack != null) {
public float[] getTimes() { return boneTrack.getTimes();
if (boneTrack != null) { }
return boneTrack.getTimes(); return spatialTrack.getTimes();
} }
return spatialTrack.getTimes();
} /**
* @return the array of translations of this track
/** */
* @return the array of translations of this track public Vector3f[] getTranslations() {
*/ if (boneTrack != null) {
public Vector3f[] getTranslations() { return boneTrack.getTranslations();
if (boneTrack != null) { }
return boneTrack.getTranslations(); return spatialTrack.getTranslations();
} }
return spatialTrack.getTranslations();
} /**
* Set the translations, rotations and scales for this bone track
/** *
* Set the translations, rotations and scales for this bone track * @param times
* * a float array with the time of each frame
* @param times * @param translations
* a float array with the time of each frame * the translation of the bone for each frame
* @param translations * @param rotations
* the translation of the bone for each frame * the rotation of the bone for each frame
* @param rotations * @param scales
* the rotation of the bone for each frame * the scale of the bone for each frame
* @param scales */
* the scale of the bone for each frame public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
*/ if (boneTrack != null) {
public void setKeyframes(float[] times, Vector3f[] translations, boneTrack.setKeyframes(times, translations, rotations, scales);
Quaternion[] rotations, Vector3f[] scales) { } else {
if (boneTrack != null) { spatialTrack.setKeyframes(times, translations, rotations, scales);
boneTrack.setKeyframes(times, translations, rotations, scales); }
} else { }
spatialTrack.setKeyframes(times, translations, rotations, scales);
} public void write(JmeExporter ex) throws IOException {
} // no need to implement this one (the TrackWrapper is used internally and never serialized)
}
public void write(JmeExporter ex) throws IOException {
//no need to implement this one (the TrackWrapper is used internally and never serialized) public void read(JmeImporter im) throws IOException {
} // no need to implement this one (the TrackWrapper is used internally and never serialized)
}
public void read(JmeImporter im) throws IOException {
//no need to implement this one (the TrackWrapper is used internally and never serialized) public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
} if (boneTrack != null) {
boneTrack.setTime(time, weight, control, channel, vars);
public void setTime(float time, float weight, AnimControl control, } else {
AnimChannel channel, TempVars vars) { spatialTrack.setTime(time, weight, control, channel, vars);
if (boneTrack != null) { }
boneTrack.setTime(time, weight, control, channel, vars); }
} else {
spatialTrack.setTime(time, weight, control, channel, vars); public float getLength() {
} return spatialTrack == null ? boneTrack.getLength() : spatialTrack.getLength();
} }
public float getLength() { @Override
return spatialTrack == null ? boneTrack.getLength() : spatialTrack public TrackWrapper clone() {
.getLength(); if (boneTrack != null) {
} return new TrackWrapper(boneTrack.clone());
}
@Override return new TrackWrapper(spatialTrack.clone());
public TrackWrapper clone() { }
if (boneTrack != null) { }
return new TrackWrapper(boneTrack.clone());
}
return new TrackWrapper(spatialTrack.clone());
}
}
} }

@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Action' constraint type in blender. * This class represents 'Action' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionAction extends ConstraintDefinition { /* package */class ConstraintDefinitionAction extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName());
public ConstraintDefinitionAction(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override public ConstraintDefinitionAction(Structure constraintData, BlenderContext blenderContext) {
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { super(constraintData, blenderContext);
// TODO: implement 'Action' constraint }
LOGGER.log(Level.WARNING, "'Action' constraint NOT implemented!");
} @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Action' constraint
LOGGER.log(Level.WARNING, "'Action' constraint NOT implemented!");
}
} }

@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Camera solver' constraint type in blender. * This class represents 'Camera solver' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionCameraSolver extends ConstraintDefinition { /* package */class ConstraintDefinitionCameraSolver extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName());
public ConstraintDefinitionCameraSolver(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override public ConstraintDefinitionCameraSolver(Structure constraintData, BlenderContext blenderContext) {
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { super(constraintData, blenderContext);
// TODO: implement 'Camera solver' constraint }
LOGGER.log(Level.WARNING, "'Camera solver' constraint NOT implemented!");
} @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Camera solver' constraint
LOGGER.log(Level.WARNING, "'Camera solver' constraint NOT implemented!");
}
} }

@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'ChildOf' constraint type in blender. * This class represents 'ChildOf' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionChildOf extends ConstraintDefinition { /* package */class ConstraintDefinitionChildOf extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionChildOf.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionChildOf.class.getName());
public ConstraintDefinitionChildOf(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionChildOf(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement ChildOf constraint // TODO: implement ChildOf constraint
LOGGER.log(Level.WARNING, "ChildOf constraint NOT implemented!"); LOGGER.log(Level.WARNING, "ChildOf constraint NOT implemented!");
} }
} }

@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Clamp to' constraint type in blender. * This class represents 'Clamp to' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionClampTo extends ConstraintDefinition { /* package */class ConstraintDefinitionClampTo extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionClampTo.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionClampTo.class.getName());
public ConstraintDefinitionClampTo(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionClampTo(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
//TODO: implement when curves are implemented // TODO: implement when curves are implemented
LOGGER.log(Level.WARNING, "'Clamp to' not yet implemented!"); LOGGER.log(Level.WARNING, "'Clamp to' not yet implemented!");
} }
} }

@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* The damp track constraint. Available for blender 2.50+. * The damp track constraint. Available for blender 2.50+.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionDampTrack extends ConstraintDefinition { /* package */class ConstraintDefinitionDampTrack extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionDampTrack.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionDampTrack.class.getName());
public ConstraintDefinitionDampTrack(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionDampTrack(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO Auto-generated method stub // TODO Auto-generated method stub
LOGGER.log(Level.WARNING, "'Damp Track' constraint NOT implemented!"); LOGGER.log(Level.WARNING, "'Damp Track' constraint NOT implemented!");
} }
} }

@ -9,51 +9,51 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Dist limit' constraint type in blender. * This class represents 'Dist limit' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionDistLimit extends ConstraintDefinition { /* package */class ConstraintDefinitionDistLimit extends ConstraintDefinition {
private static final int LIMITDIST_INSIDE = 0; private static final int LIMITDIST_INSIDE = 0;
private static final int LIMITDIST_OUTSIDE = 1; private static final int LIMITDIST_OUTSIDE = 1;
private static final int LIMITDIST_ONSURFACE = 2; private static final int LIMITDIST_ONSURFACE = 2;
protected int mode; protected int mode;
protected float dist; protected float dist;
public ConstraintDefinitionDistLimit(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionDistLimit(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
mode = ((Number) constraintData.getFieldValue("mode")).intValue(); mode = ((Number) constraintData.getFieldValue("mode")).intValue();
dist = ((Number) constraintData.getFieldValue("dist")).floatValue(); dist = ((Number) constraintData.getFieldValue("dist")).floatValue();
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
Vector3f v = ownerTransform.getTranslation().subtract(targetTransform.getTranslation()); Vector3f v = ownerTransform.getTranslation().subtract(targetTransform.getTranslation());
float currentDistance = v.length(); float currentDistance = v.length();
switch (mode) { switch (mode) {
case LIMITDIST_INSIDE: case LIMITDIST_INSIDE:
if (currentDistance >= dist) { if (currentDistance >= dist) {
v.normalizeLocal(); v.normalizeLocal();
v.multLocal(dist + (currentDistance - dist) * (1.0f - influence)); v.multLocal(dist + (currentDistance - dist) * (1.0f - influence));
ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation())); ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation()));
} }
break; break;
case LIMITDIST_ONSURFACE: case LIMITDIST_ONSURFACE:
if (currentDistance > dist) { if (currentDistance > dist) {
v.normalizeLocal(); v.normalizeLocal();
v.multLocal(dist + (currentDistance - dist) * (1.0f - influence)); v.multLocal(dist + (currentDistance - dist) * (1.0f - influence));
ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation())); ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation()));
} else if(currentDistance < dist) { } else if (currentDistance < dist) {
v.normalizeLocal().multLocal(dist * influence); v.normalizeLocal().multLocal(dist * influence);
ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v)); ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v));
} }
break; break;
case LIMITDIST_OUTSIDE: case LIMITDIST_OUTSIDE:
if (currentDistance <= dist) { if (currentDistance <= dist) {
v = targetTransform.getTranslation().subtract(ownerTransform.getTranslation()).normalizeLocal().multLocal(dist * influence); v = targetTransform.getTranslation().subtract(ownerTransform.getTranslation()).normalizeLocal().multLocal(dist * influence);
ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v)); ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v));
} }
break; break;
default: default:
throw new IllegalStateException("Unknown distance limit constraint mode: " + mode); throw new IllegalStateException("Unknown distance limit constraint mode: " + mode);
} }
} }
} }

@ -40,75 +40,75 @@ import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
public class ConstraintDefinitionFactory { public class ConstraintDefinitionFactory {
private static final Map<String, Class<? extends ConstraintDefinition>> CONSTRAINT_CLASSES = new HashMap<String, Class<? extends ConstraintDefinition>>(); private static final Map<String, Class<? extends ConstraintDefinition>> CONSTRAINT_CLASSES = new HashMap<String, Class<? extends ConstraintDefinition>>();
static { static {
CONSTRAINT_CLASSES.put("bActionConstraint", ConstraintDefinitionAction.class); CONSTRAINT_CLASSES.put("bActionConstraint", ConstraintDefinitionAction.class);
CONSTRAINT_CLASSES.put("bChildOfConstraint", ConstraintDefinitionChildOf.class); CONSTRAINT_CLASSES.put("bChildOfConstraint", ConstraintDefinitionChildOf.class);
CONSTRAINT_CLASSES.put("bClampToConstraint", ConstraintDefinitionClampTo.class); CONSTRAINT_CLASSES.put("bClampToConstraint", ConstraintDefinitionClampTo.class);
CONSTRAINT_CLASSES.put("bDistLimitConstraint", ConstraintDefinitionDistLimit.class); CONSTRAINT_CLASSES.put("bDistLimitConstraint", ConstraintDefinitionDistLimit.class);
CONSTRAINT_CLASSES.put("bFollowPathConstraint", ConstraintDefinitionFollowPath.class); CONSTRAINT_CLASSES.put("bFollowPathConstraint", ConstraintDefinitionFollowPath.class);
CONSTRAINT_CLASSES.put("bKinematicConstraint", ConstraintDefinitionInverseKinematics.class); CONSTRAINT_CLASSES.put("bKinematicConstraint", ConstraintDefinitionInverseKinematics.class);
CONSTRAINT_CLASSES.put("bLockTrackConstraint", ConstraintDefinitionLockTrack.class); CONSTRAINT_CLASSES.put("bLockTrackConstraint", ConstraintDefinitionLockTrack.class);
CONSTRAINT_CLASSES.put("bLocateLikeConstraint", ConstraintDefinitionLocLike.class); CONSTRAINT_CLASSES.put("bLocateLikeConstraint", ConstraintDefinitionLocLike.class);
CONSTRAINT_CLASSES.put("bLocLimitConstraint", ConstraintDefinitionLocLimit.class); CONSTRAINT_CLASSES.put("bLocLimitConstraint", ConstraintDefinitionLocLimit.class);
CONSTRAINT_CLASSES.put("bMinMaxConstraint", ConstraintDefinitionMinMax.class); CONSTRAINT_CLASSES.put("bMinMaxConstraint", ConstraintDefinitionMinMax.class);
CONSTRAINT_CLASSES.put("bNullConstraint", ConstraintDefinitionNull.class); CONSTRAINT_CLASSES.put("bNullConstraint", ConstraintDefinitionNull.class);
CONSTRAINT_CLASSES.put("bPythonConstraint", ConstraintDefinitionPython.class); CONSTRAINT_CLASSES.put("bPythonConstraint", ConstraintDefinitionPython.class);
CONSTRAINT_CLASSES.put("bRigidBodyJointConstraint", ConstraintDefinitionRigidBodyJoint.class); CONSTRAINT_CLASSES.put("bRigidBodyJointConstraint", ConstraintDefinitionRigidBodyJoint.class);
CONSTRAINT_CLASSES.put("bRotateLikeConstraint", ConstraintDefinitionRotLike.class); CONSTRAINT_CLASSES.put("bRotateLikeConstraint", ConstraintDefinitionRotLike.class);
CONSTRAINT_CLASSES.put("bShrinkWrapConstraint", ConstraintDefinitionShrinkWrap.class); CONSTRAINT_CLASSES.put("bShrinkWrapConstraint", ConstraintDefinitionShrinkWrap.class);
CONSTRAINT_CLASSES.put("bSizeLikeConstraint", ConstraintDefinitionSizeLike.class); CONSTRAINT_CLASSES.put("bSizeLikeConstraint", ConstraintDefinitionSizeLike.class);
CONSTRAINT_CLASSES.put("bSizeLimitConstraint", ConstraintDefinitionSizeLimit.class); CONSTRAINT_CLASSES.put("bSizeLimitConstraint", ConstraintDefinitionSizeLimit.class);
CONSTRAINT_CLASSES.put("bStretchToConstraint", ConstraintDefinitionStretchTo.class); CONSTRAINT_CLASSES.put("bStretchToConstraint", ConstraintDefinitionStretchTo.class);
CONSTRAINT_CLASSES.put("bTransformConstraint", ConstraintDefinitionTransform.class); CONSTRAINT_CLASSES.put("bTransformConstraint", ConstraintDefinitionTransform.class);
CONSTRAINT_CLASSES.put("bRotLimitConstraint", ConstraintDefinitionRotLimit.class); CONSTRAINT_CLASSES.put("bRotLimitConstraint", ConstraintDefinitionRotLimit.class);
//Blender 2.50+ // Blender 2.50+
CONSTRAINT_CLASSES.put("bSplineIKConstraint", ConstraintDefinitionSplineInverseKinematic.class); CONSTRAINT_CLASSES.put("bSplineIKConstraint", ConstraintDefinitionSplineInverseKinematic.class);
CONSTRAINT_CLASSES.put("bDampTrackConstraint", ConstraintDefinitionDampTrack.class); CONSTRAINT_CLASSES.put("bDampTrackConstraint", ConstraintDefinitionDampTrack.class);
CONSTRAINT_CLASSES.put("bPivotConstraint", ConstraintDefinitionDampTrack.class); CONSTRAINT_CLASSES.put("bPivotConstraint", ConstraintDefinitionDampTrack.class);
//Blender 2.56+ // Blender 2.56+
CONSTRAINT_CLASSES.put("bTrackToConstraint", ConstraintDefinitionTrackTo.class); CONSTRAINT_CLASSES.put("bTrackToConstraint", ConstraintDefinitionTrackTo.class);
CONSTRAINT_CLASSES.put("bSameVolumeConstraint", ConstraintDefinitionSameVolume.class); CONSTRAINT_CLASSES.put("bSameVolumeConstraint", ConstraintDefinitionSameVolume.class);
CONSTRAINT_CLASSES.put("bTransLikeConstraint", ConstraintDefinitionTransLike.class); CONSTRAINT_CLASSES.put("bTransLikeConstraint", ConstraintDefinitionTransLike.class);
//Blender 2.62+ // Blender 2.62+
CONSTRAINT_CLASSES.put("bCameraSolverConstraint", ConstraintDefinitionCameraSolver.class); CONSTRAINT_CLASSES.put("bCameraSolverConstraint", ConstraintDefinitionCameraSolver.class);
CONSTRAINT_CLASSES.put("bObjectSolverConstraint", ConstraintDefinitionObjectSolver.class); CONSTRAINT_CLASSES.put("bObjectSolverConstraint", ConstraintDefinitionObjectSolver.class);
CONSTRAINT_CLASSES.put("bFollowTrackConstraint", ConstraintDefinitionFollowTrack.class); CONSTRAINT_CLASSES.put("bFollowTrackConstraint", ConstraintDefinitionFollowTrack.class);
} }
/** /**
* This method creates the constraint instance. * This method creates the constraint instance.
* *
* @param constraintStructure * @param constraintStructure
* the constraint's structure (bConstraint clss in blender 2.49). If the value is null the NullConstraint is created. * the constraint's structure (bConstraint clss in blender 2.49). If the value is null the NullConstraint is created.
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blender file is somehow * this exception is thrown when the blender file is somehow
* corrupted * corrupted
*/ */
public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, BlenderContext blenderContext) throws BlenderFileException { public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, BlenderContext blenderContext) throws BlenderFileException {
if(constraintStructure == null) { if (constraintStructure == null) {
return new ConstraintDefinitionNull(null, blenderContext); return new ConstraintDefinitionNull(null, blenderContext);
} }
String constraintClassName = constraintStructure.getType(); String constraintClassName = constraintStructure.getType();
Class<? extends ConstraintDefinition> constraintDefinitionClass = CONSTRAINT_CLASSES.get(constraintClassName); Class<? extends ConstraintDefinition> constraintDefinitionClass = CONSTRAINT_CLASSES.get(constraintClassName);
if(constraintDefinitionClass != null) { if (constraintDefinitionClass != null) {
try { try {
return (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, blenderContext); return (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, blenderContext);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e); throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (SecurityException e) { } catch (SecurityException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e); throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (InstantiationException e) { } catch (InstantiationException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e); throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e); throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e); throw new BlenderFileException(e.getLocalizedMessage(), e);
} }
} else { } else {
throw new BlenderFileException("Unknown constraint type: " + constraintClassName); throw new BlenderFileException("Unknown constraint type: " + constraintClassName);
} }
} }
} }

@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Follow path' constraint type in blender. * This class represents 'Follow path' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionFollowPath extends ConstraintDefinition { /* package */class ConstraintDefinitionFollowPath extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionFollowPath.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionFollowPath.class.getName());
public ConstraintDefinitionFollowPath(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionFollowPath(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
//TODO: implement when curves are implemented // TODO: implement when curves are implemented
LOGGER.log(Level.WARNING, "'Follow path' not implemented! Curves not yet implemented!"); LOGGER.log(Level.WARNING, "'Follow path' not implemented! Curves not yet implemented!");
} }
} }

@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Follow track' constraint type in blender. * This class represents 'Follow track' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionFollowTrack extends ConstraintDefinition { /* package */class ConstraintDefinitionFollowTrack extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName());
public ConstraintDefinitionFollowTrack(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override public ConstraintDefinitionFollowTrack(Structure constraintData, BlenderContext blenderContext) {
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { super(constraintData, blenderContext);
// TODO: implement 'Follow track' constraint }
LOGGER.log(Level.WARNING, "'Follow track' constraint NOT implemented!");
} @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Follow track' constraint
LOGGER.log(Level.WARNING, "'Follow track' constraint NOT implemented!");
}
} }

@ -11,134 +11,134 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Inverse kinematics' constraint type in blender. * This class represents 'Inverse kinematics' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionInverseKinematics extends ConstraintDefinition { /* package */class ConstraintDefinitionInverseKinematics extends ConstraintDefinition {
//private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionInverseKinematics.class.getName()); // private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionInverseKinematics.class.getName());
//private static final float IK_SOLVER_ERROR = 0.5f; // private static final float IK_SOLVER_ERROR = 0.5f;
public ConstraintDefinitionInverseKinematics(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override public ConstraintDefinitionInverseKinematics(Structure constraintData, BlenderContext blenderContext) {
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { super(constraintData, blenderContext);
// try { }
// IK solver is only attached to bones
// Bone ownerBone = (Bone) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE); @Override
// AnimData animData = blenderContext.getAnimData(ownerOMA); public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// if(animData == null) { // try {
//TODO: to nie moxe byx null, utworzyx dane bez ruchu, w zalexnoxci czy target six rusza // IK solver is only attached to bones
// } // Bone ownerBone = (Bone) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
// AnimData animData = blenderContext.getAnimData(ownerOMA);
//prepare a list of all parents of this bone // if(animData == null) {
// CalculationBone[] bones = this.getBonesToCalculate(skeleton, boneAnimation); // TODO: to nie moxe byx null, utworzyx dane bez ruchu, w zalexnoxci czy target six rusza
// }
// get the target point
// Object targetObject = this.getTarget(LoadedFeatureDataType.LOADED_FEATURE); // prepare a list of all parents of this bone
// Vector3f pt = null;// Point Target // CalculationBone[] bones = this.getBonesToCalculate(skeleton, boneAnimation);
// if (targetObject instanceof Bone) {
// pt = ((Bone) targetObject).getModelSpacePosition(); // get the target point
// } else if (targetObject instanceof Spatial) { // Object targetObject = this.getTarget(LoadedFeatureDataType.LOADED_FEATURE);
// pt = ((Spatial) targetObject).getWorldTranslation(); // Vector3f pt = null;// Point Target
// } else if (targetObject instanceof Skeleton) { // if (targetObject instanceof Bone) {
// Structure armatureNodeStructure = (Structure) this.getTarget(LoadedFeatureDataType.LOADED_STRUCTURE); // pt = ((Bone) targetObject).getModelSpacePosition();
// ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); // } else if (targetObject instanceof Spatial) {
// Transform transform = objectHelper.getTransformation(armatureNodeStructure, blenderContext); // pt = ((Spatial) targetObject).getWorldTranslation();
// pt = transform.getTranslation(); // } else if (targetObject instanceof Skeleton) {
// } else { // Structure armatureNodeStructure = (Structure) this.getTarget(LoadedFeatureDataType.LOADED_STRUCTURE);
// throw new IllegalStateException( // ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
// "Unknown target object type! Should be Node, Bone or Skeleton and there is: " // Transform transform = objectHelper.getTransformation(armatureNodeStructure, blenderContext);
// + targetObject.getClass().getName()); // pt = transform.getTranslation();
// } // } else {
// throw new IllegalStateException(
//fetching the owner's bone track // "Unknown target object type! Should be Node, Bone or Skeleton and there is: "
// BoneTrack ownerBoneTrack = null; // + targetObject.getClass().getName());
// int boneIndex = skeleton.getBoneIndex(ownerBone); // }
// for (int i = 0; i < boneAnimation.getTracks().length; ++i) {
// if (boneAnimation.getTracks()[i].getTargetBoneIndex() == boneIndex) { // fetching the owner's bone track
// ownerBoneTrack = boneAnimation.getTracks()[i]; // BoneTrack ownerBoneTrack = null;
// break; // int boneIndex = skeleton.getBoneIndex(ownerBone);
// } // for (int i = 0; i < boneAnimation.getTracks().length; ++i) {
// } // if (boneAnimation.getTracks()[i].getTargetBoneIndex() == boneIndex) {
// int ownerBoneFramesCount = ownerBoneTrack==null ? 0 : ownerBoneTrack.getTimes().length; // ownerBoneTrack = boneAnimation.getTracks()[i];
// // break;
// // preparing data // }
// int maxIterations = ((Number) data.getFieldValue("iterations")).intValue(); // }
// CalculationBone[] bones = this.getBonesToCalculate(ownerBone, skeleton, boneAnimation); // int ownerBoneFramesCount = ownerBoneTrack==null ? 0 : ownerBoneTrack.getTimes().length;
// for (int i = 0; i < bones.length; ++i) { //
// System.out.println(Arrays.toString(bones[i].track.getTranslations())); // // preparing data
// System.out.println(Arrays.toString(bones[i].track.getRotations())); // int maxIterations = ((Number) data.getFieldValue("iterations")).intValue();
// System.out.println("==============================="); // CalculationBone[] bones = this.getBonesToCalculate(ownerBone, skeleton, boneAnimation);
// } // for (int i = 0; i < bones.length; ++i) {
// Quaternion rotation = new Quaternion(); // System.out.println(Arrays.toString(bones[i].track.getTranslations()));
// //all tracks should have the same amount of frames // System.out.println(Arrays.toString(bones[i].track.getRotations()));
// int framesCount = bones[0].getBoneFramesCount(); // System.out.println("===============================");
// assert framesCount >=1; // }
// for (int frame = 0; frame < framesCount; ++frame) { // Quaternion rotation = new Quaternion();
// float error = IK_SOLVER_ERROR; // //all tracks should have the same amount of frames
// int iteration = 0; // int framesCount = bones[0].getBoneFramesCount();
// while (error >= IK_SOLVER_ERROR && iteration <= maxIterations) { // assert framesCount >=1;
// // rotating the bones // for (int frame = 0; frame < framesCount; ++frame) {
// for (int i = 0; i < bones.length - 1; ++i) { // float error = IK_SOLVER_ERROR;
// Vector3f pe = bones[i].getEndPoint(); // int iteration = 0;
// Vector3f pc = bones[i + 1].getWorldTranslation().clone(); // while (error >= IK_SOLVER_ERROR && iteration <= maxIterations) {
// // // rotating the bones
// Vector3f peSUBpc = pe.subtract(pc).normalizeLocal(); // for (int i = 0; i < bones.length - 1; ++i) {
// Vector3f ptSUBpc = pt.subtract(pc).normalizeLocal(); // Vector3f pe = bones[i].getEndPoint();
// // Vector3f pc = bones[i + 1].getWorldTranslation().clone();
// float theta = FastMath.acos(peSUBpc.dot(ptSUBpc)); //
// Vector3f direction = peSUBpc.cross(ptSUBpc).normalizeLocal(); // Vector3f peSUBpc = pe.subtract(pc).normalizeLocal();
// bones[i].rotate(rotation.fromAngleAxis(theta, direction), frame); // Vector3f ptSUBpc = pt.subtract(pc).normalizeLocal();
// } //
// error = pt.subtract(bones[0].getEndPoint()).length(); // float theta = FastMath.acos(peSUBpc.dot(ptSUBpc));
// ++iteration; // Vector3f direction = peSUBpc.cross(ptSUBpc).normalizeLocal();
// } // bones[i].rotate(rotation.fromAngleAxis(theta, direction), frame);
// } // }
// // error = pt.subtract(bones[0].getEndPoint()).length();
// for (CalculationBone bone : bones) { // ++iteration;
// bone.applyCalculatedTracks(); // }
// } // }
// //
// System.out.println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"); // for (CalculationBone bone : bones) {
// for (int i = 0; i < bones.length; ++i) { // bone.applyCalculatedTracks();
// System.out.println(Arrays.toString(bones[i].track.getTranslations())); // }
// System.out.println(Arrays.toString(bones[i].track.getRotations())); //
// System.out.println("==============================="); // System.out.println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");
// } // for (int i = 0; i < bones.length; ++i) {
// } catch(BlenderFileException e) { // System.out.println(Arrays.toString(bones[i].track.getTranslations()));
// LOGGER.severe(e.getLocalizedMessage()); // System.out.println(Arrays.toString(bones[i].track.getRotations()));
// } // System.out.println("===============================");
} // }
// } catch(BlenderFileException e) {
/** // LOGGER.severe(e.getLocalizedMessage());
* This method returns bones used for rotation calculations. // }
* @param bone }
* the bone to which the constraint is applied
* @param skeleton /**
* the skeleton owning the bone and its ancestors * This method returns bones used for rotation calculations.
* @param boneAnimation * @param bone
* the bone animation data that stores the traces for the skeleton's bones * the bone to which the constraint is applied
* @return a list of bones to imitate the bone's movement during IK solving * @param skeleton
*/ * the skeleton owning the bone and its ancestors
private CalculationBone[] getBonesToCalculate(Skeleton skeleton, Animation boneAnimation) { * @param boneAnimation
// Bone ownerBone = (Bone) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE); * the bone animation data that stores the traces for the skeleton's bones
// List<CalculationBone> bonesList = new ArrayList<CalculationBone>(); * @return a list of bones to imitate the bone's movement during IK solving
// do { */
// bonesList.add(new CalculationBone(ownerBone, 1)); private CalculationBone[] getBonesToCalculate(Skeleton skeleton, Animation boneAnimation) {
// int boneIndex = skeleton.getBoneIndex(ownerBone); // Bone ownerBone = (Bone) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
// for (int i = 0; i < boneAnimation.getTracks().length; ++i) { // List<CalculationBone> bonesList = new ArrayList<CalculationBone>();
// if (((BoneTrack[])boneAnimation.getTracks())[i].getTargetBoneIndex() == boneIndex) { // do {
// bonesList.add(new CalculationBone(ownerBone, (BoneTrack)boneAnimation.getTracks()[i])); // bonesList.add(new CalculationBone(ownerBone, 1));
// break; // int boneIndex = skeleton.getBoneIndex(ownerBone);
// } // for (int i = 0; i < boneAnimation.getTracks().length; ++i) {
// } // if (((BoneTrack[])boneAnimation.getTracks())[i].getTargetBoneIndex() == boneIndex) {
// ownerBone = ownerBone.getParent(); // bonesList.add(new CalculationBone(ownerBone, (BoneTrack)boneAnimation.getTracks()[i]));
// } while (ownerBone != null); // break;
// //attaching children // }
// CalculationBone[] result = bonesList.toArray(new CalculationBone[bonesList.size()]); // }
// for (int i = result.length - 1; i > 0; --i) { // ownerBone = ownerBone.getParent();
// result[i].attachChild(result[i - 1]); // } while (ownerBone != null);
// } // //attaching children
// return result; // CalculationBone[] result = bonesList.toArray(new CalculationBone[bonesList.size()]);
return null; // for (int i = result.length - 1; i > 0; --i) {
} // result[i].attachChild(result[i - 1]);
// }
// return result;
return null;
}
} }

@ -9,66 +9,66 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Loc like' constraint type in blender. * This class represents 'Loc like' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionLocLike extends ConstraintDefinition { /* package */class ConstraintDefinitionLocLike extends ConstraintDefinition {
private static final int LOCLIKE_X = 0x01; private static final int LOCLIKE_X = 0x01;
private static final int LOCLIKE_Y = 0x02; private static final int LOCLIKE_Y = 0x02;
private static final int LOCLIKE_Z = 0x04; private static final int LOCLIKE_Z = 0x04;
//protected static final int LOCLIKE_TIP = 0x08;//this is deprecated in blender // protected static final int LOCLIKE_TIP = 0x08;//this is deprecated in blender
private static final int LOCLIKE_X_INVERT = 0x10; private static final int LOCLIKE_X_INVERT = 0x10;
private static final int LOCLIKE_Y_INVERT = 0x20; private static final int LOCLIKE_Y_INVERT = 0x20;
private static final int LOCLIKE_Z_INVERT = 0x40; private static final int LOCLIKE_Z_INVERT = 0x40;
private static final int LOCLIKE_OFFSET = 0x80; private static final int LOCLIKE_OFFSET = 0x80;
public ConstraintDefinitionLocLike(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionLocLike(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
if(blenderContext.getBlenderKey().isFixUpAxis()) { if (blenderContext.getBlenderKey().isFixUpAxis()) {
//swapping Y and X limits flag in the bitwise flag // swapping Y and X limits flag in the bitwise flag
int y = flag & LOCLIKE_Y; int y = flag & LOCLIKE_Y;
int invY = flag & LOCLIKE_Y_INVERT; int invY = flag & LOCLIKE_Y_INVERT;
int z = flag & LOCLIKE_Z; int z = flag & LOCLIKE_Z;
int invZ = flag & LOCLIKE_Z_INVERT; int invZ = flag & LOCLIKE_Z_INVERT;
flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET;//clear the other flags to swap them flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET;// clear the other flags to swap them
flag |= y << 1; flag |= y << 1;
flag |= invY << 1; flag |= invY << 1;
flag |= z >> 1; flag |= z >> 1;
flag |= invZ >> 1; flag |= invZ >> 1;
} }
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
Vector3f ownerLocation = ownerTransform.getTranslation(); Vector3f ownerLocation = ownerTransform.getTranslation();
Vector3f targetLocation = targetTransform.getTranslation(); Vector3f targetLocation = targetTransform.getTranslation();
Vector3f startLocation = ownerTransform.getTranslation().clone(); Vector3f startLocation = ownerTransform.getTranslation().clone();
Vector3f offset = Vector3f.ZERO; Vector3f offset = Vector3f.ZERO;
if ((flag & LOCLIKE_OFFSET) != 0) {//we add the original location to the copied location if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original location to the copied location
offset = startLocation; offset = startLocation;
} }
if ((flag & LOCLIKE_X) != 0) {
ownerLocation.x = targetLocation.x;
if ((flag & LOCLIKE_X_INVERT) != 0) {
ownerLocation.x = -ownerLocation.x;
}
}
if ((flag & LOCLIKE_Y) != 0) {
ownerLocation.y = targetLocation.y;
if ((flag & LOCLIKE_Y_INVERT) != 0) {
ownerLocation.y = -ownerLocation.y;
}
}
if ((flag & LOCLIKE_Z) != 0) {
ownerLocation.z = targetLocation.z;
if ((flag & LOCLIKE_Z_INVERT) != 0) {
ownerLocation.z = -ownerLocation.z;
}
}
ownerLocation.addLocal(offset);
if ((flag & LOCLIKE_X) != 0) { if (influence < 1.0f) {
ownerLocation.x = targetLocation.x; startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence);
if ((flag & LOCLIKE_X_INVERT) != 0) { ownerLocation.addLocal(startLocation);
ownerLocation.x = -ownerLocation.x; }
} }
}
if ((flag & LOCLIKE_Y) != 0) {
ownerLocation.y = targetLocation.y;
if ((flag & LOCLIKE_Y_INVERT) != 0) {
ownerLocation.y = -ownerLocation.y;
}
}
if ((flag & LOCLIKE_Z) != 0) {
ownerLocation.z = targetLocation.z;
if ((flag & LOCLIKE_Z_INVERT) != 0) {
ownerLocation.z = -ownerLocation.z;
}
}
ownerLocation.addLocal(offset);
if(influence < 1.0f) {
startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence);
ownerLocation.addLocal(startLocation);
}
}
} }

@ -9,67 +9,67 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Loc limit' constraint type in blender. * This class represents 'Loc limit' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionLocLimit extends ConstraintDefinition { /* package */class ConstraintDefinitionLocLimit extends ConstraintDefinition {
private static final int LIMIT_XMIN = 0x01; private static final int LIMIT_XMIN = 0x01;
private static final int LIMIT_XMAX = 0x02; private static final int LIMIT_XMAX = 0x02;
private static final int LIMIT_YMIN = 0x04; private static final int LIMIT_YMIN = 0x04;
private static final int LIMIT_YMAX = 0x08; private static final int LIMIT_YMAX = 0x08;
private static final int LIMIT_ZMIN = 0x10; private static final int LIMIT_ZMIN = 0x10;
private static final int LIMIT_ZMAX = 0x20; private static final int LIMIT_ZMAX = 0x20;
protected float[][] limits = new float[3][2]; protected float[][] limits = new float[3][2];
public ConstraintDefinitionLocLimit(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionLocLimit(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
if(blenderContext.getBlenderKey().isFixUpAxis()) { if (blenderContext.getBlenderKey().isFixUpAxis()) {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue(); limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue();
limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue(); limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue();
limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
//swapping Y and X limits flag in the bitwise flag // swapping Y and X limits flag in the bitwise flag
int ymin = flag & LIMIT_YMIN; int ymin = flag & LIMIT_YMIN;
int ymax = flag & LIMIT_YMAX; int ymax = flag & LIMIT_YMAX;
int zmin = flag & LIMIT_ZMIN; int zmin = flag & LIMIT_ZMIN;
int zmax = flag & LIMIT_ZMAX; int zmax = flag & LIMIT_ZMAX;
flag &= LIMIT_XMIN | LIMIT_XMAX;//clear the other flags to swap them flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap them
flag |= ymin << 2; flag |= ymin << 2;
flag |= ymax << 2; flag |= ymax << 2;
flag |= zmin >> 2; flag |= zmin >> 2;
flag |= zmax >> 2; flag |= zmax >> 2;
} else { } else {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue(); limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue();
limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue(); limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue();
limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
} }
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
Vector3f translation = ownerTransform.getTranslation(); Vector3f translation = ownerTransform.getTranslation();
if ((flag & LIMIT_XMIN) != 0 && translation.x < limits[0][0]) { if ((flag & LIMIT_XMIN) != 0 && translation.x < limits[0][0]) {
translation.x -= (translation.x - limits[0][0]) * influence; translation.x -= (translation.x - limits[0][0]) * influence;
} }
if ((flag & LIMIT_XMAX) != 0 && translation.x > limits[0][1]) { if ((flag & LIMIT_XMAX) != 0 && translation.x > limits[0][1]) {
translation.x -= (translation.x - limits[0][1]) * influence; translation.x -= (translation.x - limits[0][1]) * influence;
} }
if ((flag & LIMIT_YMIN) != 0 && translation.y < limits[1][0]) { if ((flag & LIMIT_YMIN) != 0 && translation.y < limits[1][0]) {
translation.y -= (translation.y - limits[1][0]) * influence; translation.y -= (translation.y - limits[1][0]) * influence;
} }
if ((flag & LIMIT_YMAX) != 0 && translation.y > limits[1][1]) { if ((flag & LIMIT_YMAX) != 0 && translation.y > limits[1][1]) {
translation.y -= (translation.y - limits[1][1]) * influence; translation.y -= (translation.y - limits[1][1]) * influence;
} }
if ((flag & LIMIT_ZMIN) != 0 && translation.z < limits[2][0]) { if ((flag & LIMIT_ZMIN) != 0 && translation.z < limits[2][0]) {
translation.z -= (translation.z - limits[2][0]) * influence; translation.z -= (translation.z - limits[2][0]) * influence;
} }
if ((flag & LIMIT_ZMAX) != 0 && translation.z > limits[2][1]) { if ((flag & LIMIT_ZMAX) != 0 && translation.z > limits[2][1]) {
translation.z -= (translation.z - limits[2][1]) * influence; translation.z -= (translation.z - limits[2][1]) * influence;
} }
} }
} }

@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Action' constraint type in blender. * This class represents 'Action' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionLockTrack extends ConstraintDefinition { /* package */class ConstraintDefinitionLockTrack extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionLockTrack.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionLockTrack.class.getName());
public ConstraintDefinitionLockTrack(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionLockTrack(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Lock track' constraint // TODO: implement 'Lock track' constraint
LOGGER.log(Level.WARNING, "'Lock track' constraint NOT implemented!"); LOGGER.log(Level.WARNING, "'Lock track' constraint NOT implemented!");
} }
} }

@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Min max' constraint type in blender. * This class represents 'Min max' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionMinMax extends ConstraintDefinition { /* package */class ConstraintDefinitionMinMax extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionMinMax.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionMinMax.class.getName());
public ConstraintDefinitionMinMax(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionMinMax(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Min max' constraint // TODO: implement 'Min max' constraint
LOGGER.log(Level.WARNING, "'Min max' constraint NOT implemented!"); LOGGER.log(Level.WARNING, "'Min max' constraint NOT implemented!");
} }
} }

@ -8,14 +8,14 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Null' constraint type in blender. * This class represents 'Null' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionNull extends ConstraintDefinition { /* package */class ConstraintDefinitionNull extends ConstraintDefinition {
public ConstraintDefinitionNull(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionNull(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
//null constraint does nothing so no need to implement this one // null constraint does nothing so no need to implement this one
} }
} }

@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Object solver' constraint type in blender. * This class represents 'Object solver' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionObjectSolver extends ConstraintDefinition { /* package */class ConstraintDefinitionObjectSolver extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName());
public ConstraintDefinitionObjectSolver(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override public ConstraintDefinitionObjectSolver(Structure constraintData, BlenderContext blenderContext) {
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { super(constraintData, blenderContext);
// TODO: implement 'Object solver' constraint }
LOGGER.log(Level.WARNING, "'Object solver' constraint NOT implemented!");
} @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Object solver' constraint
LOGGER.log(Level.WARNING, "'Object solver' constraint NOT implemented!");
}
} }

@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* The pivot constraint. Available for blender 2.50+. * The pivot constraint. Available for blender 2.50+.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionPivot extends ConstraintDefinition { /* package */class ConstraintDefinitionPivot extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionPivot.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionPivot.class.getName());
public ConstraintDefinitionPivot(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionPivot(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Pivot' constraint // TODO: implement 'Pivot' constraint
LOGGER.log(Level.WARNING, "'Pivot' constraint NOT implemented!"); LOGGER.log(Level.WARNING, "'Pivot' constraint NOT implemented!");
} }
} }

@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Python' constraint type in blender. * This class represents 'Python' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionPython extends ConstraintDefinition { /* package */class ConstraintDefinitionPython extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionPython.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionPython.class.getName());
public ConstraintDefinitionPython(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionPython(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Python' constraint // TODO: implement 'Python' constraint
LOGGER.log(Level.WARNING, "'Python' constraint NOT implemented!"); LOGGER.log(Level.WARNING, "'Python' constraint NOT implemented!");
} }
} }

@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Rigid body joint' constraint type in blender. * This class represents 'Rigid body joint' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionRigidBodyJoint extends ConstraintDefinition { /* package */class ConstraintDefinitionRigidBodyJoint extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionRigidBodyJoint.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionRigidBodyJoint.class.getName());
public ConstraintDefinitionRigidBodyJoint(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionRigidBodyJoint(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Rigid body joint' constraint // TODO: implement 'Rigid body joint' constraint
LOGGER.log(Level.WARNING, "'Rigid body joint' constraint NOT implemented!"); LOGGER.log(Level.WARNING, "'Rigid body joint' constraint NOT implemented!");
} }
} }

@ -9,59 +9,59 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Rot like' constraint type in blender. * This class represents 'Rot like' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionRotLike extends ConstraintDefinition { /* package */class ConstraintDefinitionRotLike extends ConstraintDefinition {
private static final int ROTLIKE_X = 0x01; private static final int ROTLIKE_X = 0x01;
private static final int ROTLIKE_Y = 0x02; private static final int ROTLIKE_Y = 0x02;
private static final int ROTLIKE_Z = 0x04; private static final int ROTLIKE_Z = 0x04;
private static final int ROTLIKE_X_INVERT = 0x10; private static final int ROTLIKE_X_INVERT = 0x10;
private static final int ROTLIKE_Y_INVERT = 0x20; private static final int ROTLIKE_Y_INVERT = 0x20;
private static final int ROTLIKE_Z_INVERT = 0x40; private static final int ROTLIKE_Z_INVERT = 0x40;
private static final int ROTLIKE_OFFSET = 0x80; private static final int ROTLIKE_OFFSET = 0x80;
private transient float[] ownerAngles = new float[3]; private transient float[] ownerAngles = new float[3];
private transient float[] targetAngles = new float[3]; private transient float[] targetAngles = new float[3];
public ConstraintDefinitionRotLike(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionRotLike(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
Quaternion ownerRotation = ownerTransform.getRotation(); Quaternion ownerRotation = ownerTransform.getRotation();
ownerAngles = ownerRotation.toAngles(ownerAngles); ownerAngles = ownerRotation.toAngles(ownerAngles);
targetAngles = targetTransform.getRotation().toAngles(targetAngles); targetAngles = targetTransform.getRotation().toAngles(targetAngles);
Quaternion startRotation = ownerRotation.clone(); Quaternion startRotation = ownerRotation.clone();
Quaternion offset = Quaternion.IDENTITY; Quaternion offset = Quaternion.IDENTITY;
if ((flag & ROTLIKE_OFFSET) != 0) {//we add the original rotation to the copied rotation if ((flag & ROTLIKE_OFFSET) != 0) {// we add the original rotation to the copied rotation
offset = startRotation; offset = startRotation;
} }
if ((flag & ROTLIKE_X) != 0) {
ownerAngles[0] = targetAngles[0];
if ((flag & ROTLIKE_X_INVERT) != 0) {
ownerAngles[0] = -ownerAngles[0];
}
}
if ((flag & ROTLIKE_Y) != 0) {
ownerAngles[1] = targetAngles[1];
if ((flag & ROTLIKE_Y_INVERT) != 0) {
ownerAngles[1] = -ownerAngles[1];
}
}
if ((flag & ROTLIKE_Z) != 0) {
ownerAngles[2] = targetAngles[2];
if ((flag & ROTLIKE_Z_INVERT) != 0) {
ownerAngles[2] = -ownerAngles[2];
}
}
ownerRotation.fromAngles(ownerAngles).multLocal(offset);
if ((flag & ROTLIKE_X) != 0) { if (influence < 1.0f) {
ownerAngles[0] = targetAngles[0];
if ((flag & ROTLIKE_X_INVERT) != 0) {
ownerAngles[0] = -ownerAngles[0];
}
}
if ((flag & ROTLIKE_Y) != 0) {
ownerAngles[1] = targetAngles[1];
if ((flag & ROTLIKE_Y_INVERT) != 0) {
ownerAngles[1] = -ownerAngles[1];
}
}
if ((flag & ROTLIKE_Z) != 0) {
ownerAngles[2] = targetAngles[2];
if ((flag & ROTLIKE_Z_INVERT) != 0) {
ownerAngles[2] = -ownerAngles[2];
}
}
ownerRotation.fromAngles(ownerAngles).multLocal(offset);
if(influence < 1.0f) { // startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence);
// ownerLocation.addLocal(startLocation);
// startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence); // TODO
// ownerLocation.addLocal(startLocation); }
//TODO
}
} }
} }

@ -11,75 +11,75 @@ import com.jme3.scene.plugins.blender.file.Structure;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class ConstraintDefinitionRotLimit extends ConstraintDefinition { /* package */class ConstraintDefinitionRotLimit extends ConstraintDefinition {
private static final int LIMIT_XROT = 0x01; private static final int LIMIT_XROT = 0x01;
private static final int LIMIT_YROT = 0x02; private static final int LIMIT_YROT = 0x02;
private static final int LIMIT_ZROT = 0x04; private static final int LIMIT_ZROT = 0x04;
private transient float[][] limits = new float[3][2]; private transient float[][] limits = new float[3][2];
private transient float[] angles = new float[3]; private transient float[] angles = new float[3];
public ConstraintDefinitionRotLimit(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
if (blenderContext.getBlenderKey().isFixUpAxis()/* && owner.spatial != null*/) {//FIXME: !!!!!!!!
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue();
limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue();
limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
// swapping Y and X limits flag in the bitwise flag public ConstraintDefinitionRotLimit(Structure constraintData, BlenderContext blenderContext) {
int limitY = flag & LIMIT_YROT; super(constraintData, blenderContext);
int limitZ = flag & LIMIT_ZROT; if (blenderContext.getBlenderKey().isFixUpAxis()/* && owner.spatial != null */) {// FIXME: !!!!!!!!
flag &= LIMIT_XROT;// clear the other flags to swap them limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
flag |= limitY << 1; limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
flag |= limitZ >> 1; limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue();
} else { limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue();
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue();
limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue();
limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
}
// until blender 2.49 the rotations values were stored in degrees // swapping Y and X limits flag in the bitwise flag
if (blenderContext.getBlenderVersion() <= 249) { int limitY = flag & LIMIT_YROT;
for (int i = 0; i < limits.length; ++i) { int limitZ = flag & LIMIT_ZROT;
limits[i][0] *= FastMath.DEG_TO_RAD; flag &= LIMIT_XROT;// clear the other flags to swap them
limits[i][1] *= FastMath.DEG_TO_RAD; flag |= limitY << 1;
} flag |= limitZ >> 1;
} } else {
} limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
@Override limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue();
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue();
if ((flag & LIMIT_XROT) != 0) { limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
float difference = 0.0f; limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
if (angles[0] < limits[0][0]) { }
difference = (angles[0] - limits[0][0]) * influence;
} else if (angles[0] > limits[0][1]) { // until blender 2.49 the rotations values were stored in degrees
difference = (angles[0] - limits[0][1]) * influence; if (blenderContext.getBlenderVersion() <= 249) {
} for (int i = 0; i < limits.length; ++i) {
angles[0] -= difference; limits[i][0] *= FastMath.DEG_TO_RAD;
} limits[i][1] *= FastMath.DEG_TO_RAD;
if ((flag & LIMIT_YROT) != 0) { }
float difference = 0.0f; }
if (angles[1] < limits[1][0]) { }
difference = (angles[1] - limits[1][0]) * influence;
} else if (angles[1] > limits[1][1]) { @Override
difference = (angles[1] - limits[1][1]) * influence; public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
} if ((flag & LIMIT_XROT) != 0) {
angles[1] -= difference; float difference = 0.0f;
} if (angles[0] < limits[0][0]) {
if ((flag & LIMIT_ZROT) != 0) { difference = (angles[0] - limits[0][0]) * influence;
float difference = 0.0f; } else if (angles[0] > limits[0][1]) {
if (angles[2] < limits[2][0]) { difference = (angles[0] - limits[0][1]) * influence;
difference = (angles[2] - limits[2][0]) * influence; }
} else if (angles[2] > limits[2][1]) { angles[0] -= difference;
difference = (angles[2] - limits[2][1]) * influence; }
} if ((flag & LIMIT_YROT) != 0) {
angles[2] -= difference; float difference = 0.0f;
} if (angles[1] < limits[1][0]) {
} difference = (angles[1] - limits[1][0]) * influence;
} else if (angles[1] > limits[1][1]) {
difference = (angles[1] - limits[1][1]) * influence;
}
angles[1] -= difference;
}
if ((flag & LIMIT_ZROT) != 0) {
float difference = 0.0f;
if (angles[2] < limits[2][0]) {
difference = (angles[2] - limits[2][0]) * influence;
} else if (angles[2] > limits[2][1]) {
difference = (angles[2] - limits[2][1]) * influence;
}
angles[2] -= difference;
}
}
} }

@ -12,16 +12,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* *
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionSameVolume extends ConstraintDefinition { /* package */class ConstraintDefinitionSameVolume extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionSameVolume.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionSameVolume.class.getName());
public ConstraintDefinitionSameVolume(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionSameVolume(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Same volume' constraint // TODO: implement 'Same volume' constraint
LOGGER.log(Level.WARNING, "'Same volume' constraint NOT implemented!"); LOGGER.log(Level.WARNING, "'Same volume' constraint NOT implemented!");
} }
} }

@ -8,55 +8,54 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Shrink wrap' constraint type in blender. * This class represents 'Shrink wrap' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionShrinkWrap extends ConstraintDefinition { /* package */class ConstraintDefinitionShrinkWrap extends ConstraintDefinition {
public ConstraintDefinitionShrinkWrap(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override public ConstraintDefinitionShrinkWrap(Structure constraintData, BlenderContext blenderContext) {
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { super(constraintData, blenderContext);
//loading mesh points (blender ensures that the target is a mesh-object) }
/*List<Vector3f> pts = new ArrayList<Vector3f>();
Node target = (Node) this.target.getObject(); @Override
for(Spatial spatial : target.getChildren()) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
if(spatial instanceof Geometry) { // loading mesh points (blender ensures that the target is a mesh-object)
Mesh mesh = ((Geometry) spatial).getMesh(); /*
FloatBuffer floatBuffer = mesh.getFloatBuffer(Type.Position); * List<Vector3f> pts = new ArrayList<Vector3f>();
for(int i=0;i<floatBuffer.limit();i+=3) { * Node target = (Node) this.target.getObject();
pts.add(new Vector3f(floatBuffer.get(i), floatBuffer.get(i + 1), floatBuffer.get(i + 2))); * for(Spatial spatial : target.getChildren()) {
} * if(spatial instanceof Geometry) {
} * Mesh mesh = ((Geometry) spatial).getMesh();
} * FloatBuffer floatBuffer = mesh.getFloatBuffer(Type.Position);
* for(int i=0;i<floatBuffer.limit();i+=3) {
AnimData animData = blenderContext.getAnimData(this.owner.getOma()); * pts.add(new Vector3f(floatBuffer.get(i), floatBuffer.get(i + 1), floatBuffer.get(i + 2)));
if(animData != null) { * }
Object owner = this.owner.getObject(); * }
for(Animation animation : animData.anims) { * }
BlenderTrack track = this.getTrack(owner, animData.skeleton, animation); * AnimData animData = blenderContext.getAnimData(this.owner.getOma());
Vector3f[] translations = track.getTranslations(); * if(animData != null) {
Quaternion[] rotations = track.getRotations(); * Object owner = this.owner.getObject();
int maxFrames = translations.length; * for(Animation animation : animData.anims) {
for (int frame = 0; frame < maxFrames; ++frame) { * BlenderTrack track = this.getTrack(owner, animData.skeleton, animation);
Vector3f currentTranslation = translations[frame]; * Vector3f[] translations = track.getTranslations();
* Quaternion[] rotations = track.getRotations();
//looking for minimum distanced point * int maxFrames = translations.length;
Vector3f minDistancePoint = null; * for (int frame = 0; frame < maxFrames; ++frame) {
float distance = Float.MAX_VALUE; * Vector3f currentTranslation = translations[frame];
for(Vector3f p : pts) { * //looking for minimum distanced point
float temp = currentTranslation.distance(p); * Vector3f minDistancePoint = null;
if(temp < distance) { * float distance = Float.MAX_VALUE;
distance = temp; * for(Vector3f p : pts) {
minDistancePoint = p; * float temp = currentTranslation.distance(p);
} * if(temp < distance) {
} * distance = temp;
translations[frame] = minDistancePoint.clone(); * minDistancePoint = p;
} * }
* }
track.setKeyframes(track.getTimes(), translations, rotations, track.getScales()); * translations[frame] = minDistancePoint.clone();
} * }
}*/ * track.setKeyframes(track.getTimes(), translations, rotations, track.getScales());
* }
//TODO: static constraint for spatials * }
} */
// TODO: static constraint for spatials
}
} }

@ -9,43 +9,43 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Size like' constraint type in blender. * This class represents 'Size like' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionSizeLike extends ConstraintDefinition { /* package */class ConstraintDefinitionSizeLike extends ConstraintDefinition {
private static final int SIZELIKE_X = 0x01; private static final int SIZELIKE_X = 0x01;
private static final int SIZELIKE_Y = 0x02; private static final int SIZELIKE_Y = 0x02;
private static final int SIZELIKE_Z = 0x04; private static final int SIZELIKE_Z = 0x04;
private static final int LOCLIKE_OFFSET = 0x80; private static final int LOCLIKE_OFFSET = 0x80;
public ConstraintDefinitionSizeLike(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
if(blenderContext.getBlenderKey().isFixUpAxis()) {
//swapping Y and X limits flag in the bitwise flag
int y = flag & SIZELIKE_Y;
int z = flag & SIZELIKE_Z;
flag &= SIZELIKE_X | LOCLIKE_OFFSET;//clear the other flags to swap them
flag |= y << 1;
flag |= z >> 1;
}
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
Vector3f ownerScale = ownerTransform.getScale();
Vector3f targetScale = targetTransform.getScale();
Vector3f offset = Vector3f.ZERO;
if ((flag & LOCLIKE_OFFSET) != 0) {//we add the original scale to the copied scale
offset = ownerScale.clone();
}
if ((flag & SIZELIKE_X) != 0) { public ConstraintDefinitionSizeLike(Structure constraintData, BlenderContext blenderContext) {
ownerScale.x = targetScale.x * influence + (1.0f - influence) * ownerScale.x; super(constraintData, blenderContext);
} if (blenderContext.getBlenderKey().isFixUpAxis()) {
if ((flag & SIZELIKE_Y) != 0) { // swapping Y and X limits flag in the bitwise flag
ownerScale.y = targetScale.y * influence + (1.0f - influence) * ownerScale.y; int y = flag & SIZELIKE_Y;
} int z = flag & SIZELIKE_Z;
if ((flag & SIZELIKE_Z) != 0) { flag &= SIZELIKE_X | LOCLIKE_OFFSET;// clear the other flags to swap them
ownerScale.z = targetScale.z * influence + (1.0f - influence) * ownerScale.z; flag |= y << 1;
} flag |= z >> 1;
ownerScale.addLocal(offset); }
} }
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
Vector3f ownerScale = ownerTransform.getScale();
Vector3f targetScale = targetTransform.getScale();
Vector3f offset = Vector3f.ZERO;
if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original scale to the copied scale
offset = ownerScale.clone();
}
if ((flag & SIZELIKE_X) != 0) {
ownerScale.x = targetScale.x * influence + (1.0f - influence) * ownerScale.x;
}
if ((flag & SIZELIKE_Y) != 0) {
ownerScale.y = targetScale.y * influence + (1.0f - influence) * ownerScale.y;
}
if ((flag & SIZELIKE_Z) != 0) {
ownerScale.z = targetScale.z * influence + (1.0f - influence) * ownerScale.z;
}
ownerScale.addLocal(offset);
}
} }

@ -9,67 +9,67 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Size limit' constraint type in blender. * This class represents 'Size limit' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionSizeLimit extends ConstraintDefinition { /* package */class ConstraintDefinitionSizeLimit extends ConstraintDefinition {
private static final int LIMIT_XMIN = 0x01; private static final int LIMIT_XMIN = 0x01;
private static final int LIMIT_XMAX = 0x02; private static final int LIMIT_XMAX = 0x02;
private static final int LIMIT_YMIN = 0x04; private static final int LIMIT_YMIN = 0x04;
private static final int LIMIT_YMAX = 0x08; private static final int LIMIT_YMAX = 0x08;
private static final int LIMIT_ZMIN = 0x10; private static final int LIMIT_ZMIN = 0x10;
private static final int LIMIT_ZMAX = 0x20; private static final int LIMIT_ZMAX = 0x20;
protected transient float[][] limits = new float[3][2]; protected transient float[][] limits = new float[3][2];
public ConstraintDefinitionSizeLimit(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionSizeLimit(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
if(blenderContext.getBlenderKey().isFixUpAxis()) { if (blenderContext.getBlenderKey().isFixUpAxis()) {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue(); limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue();
limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue(); limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue();
limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
//swapping Y and X limits flag in the bitwise flag // swapping Y and X limits flag in the bitwise flag
int ymin = flag & LIMIT_YMIN; int ymin = flag & LIMIT_YMIN;
int ymax = flag & LIMIT_YMAX; int ymax = flag & LIMIT_YMAX;
int zmin = flag & LIMIT_ZMIN; int zmin = flag & LIMIT_ZMIN;
int zmax = flag & LIMIT_ZMAX; int zmax = flag & LIMIT_ZMAX;
flag &= LIMIT_XMIN | LIMIT_XMAX;//clear the other flags to swap them flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap them
flag |= ymin << 2; flag |= ymin << 2;
flag |= ymax << 2; flag |= ymax << 2;
flag |= zmin >> 2; flag |= zmin >> 2;
flag |= zmax >> 2; flag |= zmax >> 2;
} else { } else {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue(); limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue();
limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue(); limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue();
limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
} }
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
Vector3f scale = ownerTransform.getScale(); Vector3f scale = ownerTransform.getScale();
if ((flag & LIMIT_XMIN) != 0 && scale.x < limits[0][0]) { if ((flag & LIMIT_XMIN) != 0 && scale.x < limits[0][0]) {
scale.x -= (scale.x - limits[0][0]) * influence; scale.x -= (scale.x - limits[0][0]) * influence;
} }
if ((flag & LIMIT_XMAX) != 0 && scale.x > limits[0][1]) { if ((flag & LIMIT_XMAX) != 0 && scale.x > limits[0][1]) {
scale.x -= (scale.x - limits[0][1]) * influence; scale.x -= (scale.x - limits[0][1]) * influence;
} }
if ((flag & LIMIT_YMIN) != 0 && scale.y < limits[1][0]) { if ((flag & LIMIT_YMIN) != 0 && scale.y < limits[1][0]) {
scale.y -= (scale.y - limits[1][0]) * influence; scale.y -= (scale.y - limits[1][0]) * influence;
} }
if ((flag & LIMIT_YMAX) != 0 && scale.y > limits[1][1]) { if ((flag & LIMIT_YMAX) != 0 && scale.y > limits[1][1]) {
scale.y -= (scale.y - limits[1][1]) * influence; scale.y -= (scale.y - limits[1][1]) * influence;
} }
if ((flag & LIMIT_ZMIN) != 0 && scale.z < limits[2][0]) { if ((flag & LIMIT_ZMIN) != 0 && scale.z < limits[2][0]) {
scale.z -= (scale.z - limits[2][0]) * influence; scale.z -= (scale.z - limits[2][0]) * influence;
} }
if ((flag & LIMIT_ZMAX) != 0 && scale.z > limits[2][1]) { if ((flag & LIMIT_ZMAX) != 0 && scale.z > limits[2][1]) {
scale.z -= (scale.z - limits[2][1]) * influence; scale.z -= (scale.z - limits[2][1]) * influence;
} }
} }
} }

@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* The spline inverse kinematic constraint. Available for blender 2.50+. * The spline inverse kinematic constraint. Available for blender 2.50+.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionSplineInverseKinematic extends ConstraintDefinition { /* package */class ConstraintDefinitionSplineInverseKinematic extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionSplineInverseKinematic.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionSplineInverseKinematic.class.getName());
public ConstraintDefinitionSplineInverseKinematic(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionSplineInverseKinematic(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO Auto-generated method stub // TODO Auto-generated method stub
LOGGER.log(Level.WARNING, "'Splie IK' constraint NOT implemented!"); LOGGER.log(Level.WARNING, "'Splie IK' constraint NOT implemented!");
} }
} }

@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Stretch to' constraint type in blender. * This class represents 'Stretch to' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionStretchTo extends ConstraintDefinition { /* package */class ConstraintDefinitionStretchTo extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionStretchTo.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionStretchTo.class.getName());
public ConstraintDefinitionStretchTo(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override public ConstraintDefinitionStretchTo(Structure constraintData, BlenderContext blenderContext) {
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { super(constraintData, blenderContext);
// TODO: implement 'Stretch to' constraint }
LOGGER.log(Level.WARNING, "'Stretch to' constraint NOT implemented!");
} @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Stretch to' constraint
LOGGER.log(Level.WARNING, "'Stretch to' constraint NOT implemented!");
}
} }

@ -13,15 +13,15 @@ import com.jme3.scene.plugins.blender.file.Structure;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class ConstraintDefinitionTrackTo extends ConstraintDefinition { /* package */class ConstraintDefinitionTrackTo extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionTrackTo.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionTrackTo.class.getName());
public ConstraintDefinitionTrackTo(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionTrackTo(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Track to' constraint // TODO: implement 'Track to' constraint
LOGGER.log(Level.WARNING, "'Track to' constraint NOT implemented!"); LOGGER.log(Level.WARNING, "'Track to' constraint NOT implemented!");
} }
} }

@ -12,16 +12,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* *
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionTransLike extends ConstraintDefinition { /* package */class ConstraintDefinitionTransLike extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionTransLike.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionTransLike.class.getName());
public ConstraintDefinitionTransLike(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionTransLike(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, blenderContext);
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Trans like' constraint // TODO: implement 'Trans like' constraint
LOGGER.log(Level.WARNING, "'Trans like' constraint NOT implemented!"); LOGGER.log(Level.WARNING, "'Trans like' constraint NOT implemented!");
} }
} }

@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
* This class represents 'Transform' constraint type in blender. * This class represents 'Transform' constraint type in blender.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ConstraintDefinitionTransform extends ConstraintDefinition { /* package */class ConstraintDefinitionTransform extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName()); private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName());
public ConstraintDefinitionTransform(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override public ConstraintDefinitionTransform(Structure constraintData, BlenderContext blenderContext) {
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { super(constraintData, blenderContext);
// TODO: implement 'Transform' constraint }
LOGGER.log(Level.WARNING, "'Transform' constraint NOT implemented!");
} @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Transform' constraint
LOGGER.log(Level.WARNING, "'Transform' constraint NOT implemented!");
}
} }

@ -16,17 +16,17 @@ public class BezierCurve {
public static final int X_VALUE = 0; public static final int X_VALUE = 0;
public static final int Y_VALUE = 1; public static final int Y_VALUE = 1;
public static final int Z_VALUE = 2; public static final int Z_VALUE = 2;
/** /**
* The type of the curve. Describes the data it modifies. * The type of the curve. Describes the data it modifies.
* Used in ipos calculations. * Used in ipos calculations.
*/ */
private int type; private int type;
/** The dimension of the curve. */ /** The dimension of the curve. */
private int dimension; private int dimension;
/** A table of the bezier points. */ /** A table of the bezier points. */
private float[][][] bezierPoints; private float[][][] bezierPoints;
/** Array that stores a radius for each bezier triple. */ /** Array that stores a radius for each bezier triple. */
private float[] radiuses; private float[] radiuses;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public BezierCurve(final int type, final List<Structure> bezTriples, final int dimension) { public BezierCurve(final int type, final List<Structure> bezTriples, final int dimension) {
@ -35,9 +35,9 @@ public class BezierCurve {
} }
this.type = type; this.type = type;
this.dimension = dimension; this.dimension = dimension;
//first index of the bezierPoints table has the length of triples amount // first index of the bezierPoints table has the length of triples amount
//the second index points to a table od three points of a bezier triple (handle, point, handle) // the second index points to a table od three points of a bezier triple (handle, point, handle)
//the third index specifies the coordinates of the specific point in a bezier triple // the third index specifies the coordinates of the specific point in a bezier triple
bezierPoints = new float[bezTriples.size()][3][dimension]; bezierPoints = new float[bezTriples.size()][3][dimension];
radiuses = new float[bezTriples.size()]; radiuses = new float[bezTriples.size()];
int i = 0, j, k; int i = 0, j, k;
@ -48,18 +48,18 @@ public class BezierCurve {
bezierPoints[i][j][k] = vec.get(j, k).floatValue(); bezierPoints[i][j][k] = vec.get(j, k).floatValue();
} }
} }
radiuses[i++] = ((Number)bezTriple.getFieldValue("radius")).floatValue(); radiuses[i++] = ((Number) bezTriple.getFieldValue("radius")).floatValue();
} }
} }
/** /**
* This method evaluates the data for the specified frame. The Y value is returned. * This method evaluates the data for the specified frame. The Y value is returned.
* @param frame * @param frame
* the frame for which the value is being calculated * the frame for which the value is being calculated
* @param valuePart * @param valuePart
* this param specifies wheather we should return the X, Y or Z part of the result value; it should have * this param specifies wheather we should return the X, Y or Z part of the result value; it should have
* one of the following values: X_VALUE - the X factor of the result Y_VALUE - the Y factor of the result * one of the following values: X_VALUE - the X factor of the result Y_VALUE - the Y factor of the result
* Z_VALUE - the Z factor of the result * Z_VALUE - the Z factor of the result
* @return the value of the curve * @return the value of the curve
*/ */
public float evaluate(int frame, int valuePart) { public float evaluate(int frame, int valuePart) {
@ -74,7 +74,7 @@ public class BezierCurve {
} }
if (frame < bezierPoints[0][1][0]) { if (frame < bezierPoints[0][1][0]) {
return bezierPoints[0][1][1]; return bezierPoints[0][1][1];
} else { //frame>bezierPoints[bezierPoints.length-1][1][0] } else { // frame>bezierPoints[bezierPoints.length-1][1][0]
return bezierPoints[bezierPoints.length - 1][1][1]; return bezierPoints[bezierPoints.length - 1][1][1];
} }
} }
@ -96,17 +96,17 @@ public class BezierCurve {
return type; return type;
} }
/** /**
* The method returns the radius for the required bezier triple. * The method returns the radius for the required bezier triple.
* *
* @param bezierTripleIndex * @param bezierTripleIndex
* index of the bezier triple * index of the bezier triple
* @return radius of the required bezier triple * @return radius of the required bezier triple
*/ */
public float getRadius(int bezierTripleIndex) { public float getRadius(int bezierTripleIndex) {
return radiuses[bezierTripleIndex]; return radiuses[bezierTripleIndex];
} }
/** /**
* This method returns a list of control points for this curve. * This method returns a list of control points for this curve.
* @return a list of control points for this curve. * @return a list of control points for this curve.
@ -133,18 +133,14 @@ public class BezierCurve {
/** /**
* This method converts the bezier triple of a specified index into text. * This method converts the bezier triple of a specified index into text.
* @param tripleIndex * @param tripleIndex
* index of the triple * index of the triple
* @return text representation of the triple * @return text representation of the triple
*/ */
private String toStringBezTriple(int tripleIndex) { private String toStringBezTriple(int tripleIndex) {
if (this.dimension == 2) { if (this.dimension == 2) {
return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ") (" return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ") (" + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ") (" + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ")]";
+ bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ") ("
+ bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ")]";
} else { } else {
return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ", " + bezierPoints[tripleIndex][0][2] + ") (" return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ", " + bezierPoints[tripleIndex][0][2] + ") (" + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ", " + bezierPoints[tripleIndex][1][2] + ") (" + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ", " + bezierPoints[tripleIndex][2][2] + ")]";
+ bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ", " + bezierPoints[tripleIndex][1][2] + ") ("
+ bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ", " + bezierPoints[tripleIndex][2][2] + ")]";
} }
} }
} }

@ -43,13 +43,13 @@ public class BlenderFileException extends Exception {
* Constructor. Creates an exception with no description. * Constructor. Creates an exception with no description.
*/ */
public BlenderFileException() { public BlenderFileException() {
//this constructor has no message // this constructor has no message
} }
/** /**
* Constructor. Creates an exception containing the given message. * Constructor. Creates an exception containing the given message.
* @param message * @param message
* the message describing the problem that occured * the message describing the problem that occured
*/ */
public BlenderFileException(String message) { public BlenderFileException(String message) {
super(message); super(message);
@ -58,7 +58,7 @@ public class BlenderFileException extends Exception {
/** /**
* Constructor. Creates an exception that is based upon other thrown object. It contains the whole stacktrace then. * Constructor. Creates an exception that is based upon other thrown object. It contains the whole stacktrace then.
* @param throwable * @param throwable
* an exception/error that occured * an exception/error that occured
*/ */
public BlenderFileException(Throwable throwable) { public BlenderFileException(Throwable throwable) {
super(throwable); super(throwable);
@ -67,9 +67,9 @@ public class BlenderFileException extends Exception {
/** /**
* Constructor. Creates an exception with both a message and stacktrace. * Constructor. Creates an exception with both a message and stacktrace.
* @param message * @param message
* the message describing the problem that occured * the message describing the problem that occured
* @param throwable * @param throwable
* an exception/error that occured * an exception/error that occured
*/ */
public BlenderFileException(String message, Throwable throwable) { public BlenderFileException(String message, Throwable throwable) {
super(message, throwable); super(message, throwable);

@ -46,35 +46,35 @@ import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
*/ */
public class BlenderInputStream extends InputStream { public class BlenderInputStream extends InputStream {
private static final Logger LOGGER = Logger.getLogger(BlenderInputStream.class.getName()); private static final Logger LOGGER = Logger.getLogger(BlenderInputStream.class.getName());
/** The default size of the blender buffer. */ /** The default size of the blender buffer. */
private static final int DEFAULT_BUFFER_SIZE = 1048576; //1MB private static final int DEFAULT_BUFFER_SIZE = 1048576; // 1MB
/** /**
* Size of a pointer; all pointers in the file are stored in this format. '_' means 4 bytes and '-' means 8 bytes. * Size of a pointer; all pointers in the file are stored in this format. '_' means 4 bytes and '-' means 8 bytes.
*/ */
private int pointerSize; private int pointerSize;
/** /**
* Type of byte ordering used; 'v' means little endian and 'V' means big endian. * Type of byte ordering used; 'v' means little endian and 'V' means big endian.
*/ */
private char endianess; private char endianess;
/** Version of Blender the file was created in; '248' means version 2.48. */ /** Version of Blender the file was created in; '248' means version 2.48. */
private String versionNumber; private String versionNumber;
/** The buffer we store the read data to. */ /** The buffer we store the read data to. */
protected byte[] cachedBuffer; protected byte[] cachedBuffer;
/** The total size of the stored data. */ /** The total size of the stored data. */
protected int size; protected int size;
/** The current position of the read cursor. */ /** The current position of the read cursor. */
protected int position; protected int position;
/** /**
* Constructor. The input stream is stored and used to read data. * Constructor. The input stream is stored and used to read data.
* @param inputStream * @param inputStream
* the stream we read data from * the stream we read data from
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown if the file header has some invalid data * this exception is thrown if the file header has some invalid data
*/ */
public BlenderInputStream(InputStream inputStream) throws BlenderFileException { public BlenderInputStream(InputStream inputStream) throws BlenderFileException {
//the size value will canche while reading the file; the available() method cannot be counted on // the size value will canche while reading the file; the available() method cannot be counted on
try { try {
size = inputStream.available(); size = inputStream.available();
} catch (IOException e) { } catch (IOException e) {
@ -84,7 +84,7 @@ public class BlenderInputStream extends InputStream {
size = BlenderInputStream.DEFAULT_BUFFER_SIZE; size = BlenderInputStream.DEFAULT_BUFFER_SIZE;
} }
//buffered input stream is used here for much faster file reading // buffered input stream is used here for much faster file reading
BufferedInputStream bufferedInputStream; BufferedInputStream bufferedInputStream;
if (inputStream instanceof BufferedInputStream) { if (inputStream instanceof BufferedInputStream) {
bufferedInputStream = (BufferedInputStream) inputStream; bufferedInputStream = (BufferedInputStream) inputStream;
@ -97,16 +97,16 @@ public class BlenderInputStream extends InputStream {
} catch (IOException e) { } catch (IOException e) {
throw new BlenderFileException("Problems occured while caching the file!", e); throw new BlenderFileException("Problems occured while caching the file!", e);
} finally { } finally {
try { try {
inputStream.close(); inputStream.close();
} catch (IOException e) { } catch (IOException e) {
LOGGER.warning("Unable to close stream with blender file."); LOGGER.warning("Unable to close stream with blender file.");
} }
} }
try { try {
this.readFileHeader(); this.readFileHeader();
} catch (BlenderFileException e) {//the file might be packed, don't panic, try one more time ;) } catch (BlenderFileException e) {// the file might be packed, don't panic, try one more time ;)
this.decompressFile(); this.decompressFile();
this.position = 0; this.position = 0;
this.readFileHeader(); this.readFileHeader();
@ -116,22 +116,22 @@ public class BlenderInputStream extends InputStream {
/** /**
* This method reads the whole stream into a buffer. * This method reads the whole stream into a buffer.
* @param inputStream * @param inputStream
* the stream to read the file data from * the stream to read the file data from
* @throws IOException * @throws IOException
* an exception is thrown when data read from the stream is invalid or there are problems with i/o * an exception is thrown when data read from the stream is invalid or there are problems with i/o
* operations * operations
*/ */
private void readStreamToCache(InputStream inputStream) throws IOException { private void readStreamToCache(InputStream inputStream) throws IOException {
int data = inputStream.read(); int data = inputStream.read();
cachedBuffer = new byte[size]; cachedBuffer = new byte[size];
size = 0;//this will count the actual size size = 0;// this will count the actual size
while (data != -1) { while (data != -1) {
if (size >= cachedBuffer.length) {//widen the cached array if (size >= cachedBuffer.length) {// widen the cached array
byte[] newBuffer = new byte[cachedBuffer.length + (cachedBuffer.length >> 1)]; byte[] newBuffer = new byte[cachedBuffer.length + (cachedBuffer.length >> 1)];
System.arraycopy(cachedBuffer, 0, newBuffer, 0, cachedBuffer.length); System.arraycopy(cachedBuffer, 0, newBuffer, 0, cachedBuffer.length);
cachedBuffer = newBuffer; cachedBuffer = newBuffer;
} }
cachedBuffer[size++] = (byte) data; cachedBuffer[size++] = (byte) data;
data = inputStream.read(); data = inputStream.read();
} }
} }
@ -146,8 +146,7 @@ public class BlenderInputStream extends InputStream {
gis = new GZIPInputStream(new ByteArrayInputStream(cachedBuffer)); gis = new GZIPInputStream(new ByteArrayInputStream(cachedBuffer));
this.readStreamToCache(gis); this.readStreamToCache(gis);
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("IO errors occured where they should NOT! " throw new IllegalStateException("IO errors occured where they should NOT! " + "The data is already buffered at this point!", e);
+ "The data is already buffered at this point!", e);
} finally { } finally {
try { try {
if (gis != null) { if (gis != null) {
@ -162,9 +161,9 @@ public class BlenderInputStream extends InputStream {
/** /**
* This method loads the header from the given stream during instance creation. * This method loads the header from the given stream during instance creation.
* @param inputStream * @param inputStream
* the stream we read the header from * the stream we read the header from
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown if the file header has some invalid data * this exception is thrown if the file header has some invalid data
*/ */
private void readFileHeader() throws BlenderFileException { private void readFileHeader() throws BlenderFileException {
byte[] identifier = new byte[7]; byte[] identifier = new byte[7];
@ -213,7 +212,7 @@ public class BlenderInputStream extends InputStream {
} }
/** /**
* This method reads a bytes number big enough to fill the table. * This method reads a bytes number big enough to fill the table.
* It does not throw exceptions so it is for internal use only. * It does not throw exceptions so it is for internal use only.
* @param bytes * @param bytes
* an array to be filled with data * an array to be filled with data
@ -319,7 +318,7 @@ public class BlenderInputStream extends InputStream {
/** /**
* This method sets the current position of the read cursor. * This method sets the current position of the read cursor.
* @param position * @param position
* the position of the read cursor * the position of the read cursor
*/ */
public void setPosition(int position) { public void setPosition(int position) {
this.position = position; this.position = position;
@ -352,7 +351,7 @@ public class BlenderInputStream extends InputStream {
/** /**
* This method aligns cursor position forward to a given amount of bytes. * This method aligns cursor position forward to a given amount of bytes.
* @param bytesAmount * @param bytesAmount
* the byte amount to which we aligh the cursor * the byte amount to which we aligh the cursor
*/ */
public void alignPosition(int bytesAmount) { public void alignPosition(int bytesAmount) {
if (bytesAmount <= 0) { if (bytesAmount <= 0) {
@ -365,17 +364,17 @@ public class BlenderInputStream extends InputStream {
} }
@Override @Override
public void close() throws IOException { public void close() throws IOException {
//this method is unimplemented because some loaders (ie. TGALoader) have flaws that close the stream given from the outside // this method is unimplemented because some loaders (ie. TGALoader) have flaws that close the stream given from the outside
//because the images can be stored directly in the blender file then this stream is properly positioned and given to the loader // because the images can be stored directly in the blender file then this stream is properly positioned and given to the loader
//to read the image file, that is why we do not want it to be closed before the reading is done // to read the image file, that is why we do not want it to be closed before the reading is done
//to properly close the stream use forceClose() method // to properly close the stream use forceClose() method
} }
/** /**
* This method should be used to close the stream because some loaders may close the stream while reading resources from it. * This method should be used to close the stream because some loaders may close the stream while reading resources from it.
*/ */
public void forceClose() { public void forceClose() {
cachedBuffer = null; cachedBuffer = null;
} }
} }

@ -42,39 +42,37 @@ import java.util.Map;
*/ */
public class DnaBlockData { public class DnaBlockData {
private static final int SDNA_ID = 'S' << 24 | 'D' << 16 | 'N' << 8 | 'A'; //SDNA private static final int SDNA_ID = 'S' << 24 | 'D' << 16 | 'N' << 8 | 'A'; // SDNA
private static final int NAME_ID = 'N' << 24 | 'A' << 16 | 'M' << 8 | 'E'; //NAME private static final int NAME_ID = 'N' << 24 | 'A' << 16 | 'M' << 8 | 'E'; // NAME
private static final int TYPE_ID = 'T' << 24 | 'Y' << 16 | 'P' << 8 | 'E'; //TYPE private static final int TYPE_ID = 'T' << 24 | 'Y' << 16 | 'P' << 8 | 'E'; // TYPE
private static final int TLEN_ID = 'T' << 24 | 'L' << 16 | 'E' << 8 | 'N'; //TLEN private static final int TLEN_ID = 'T' << 24 | 'L' << 16 | 'E' << 8 | 'N'; // TLEN
private static final int STRC_ID = 'S' << 24 | 'T' << 16 | 'R' << 8 | 'C'; //STRC private static final int STRC_ID = 'S' << 24 | 'T' << 16 | 'R' << 8 | 'C'; // STRC
/** Structures available inside the file. */ /** Structures available inside the file. */
private final Structure[] structures; private final Structure[] structures;
/** A map that helps finding a structure by type. */ /** A map that helps finding a structure by type. */
private final Map<String, Structure> structuresMap; private final Map<String, Structure> structuresMap;
/** /**
* Constructor. Loads the block from the given stream during instance creation. * Constructor. Loads the block from the given stream during instance creation.
* @param inputStream * @param inputStream
* the stream we read the block from * the stream we read the block from
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
* this exception is throw if the blend file is invalid or somehow corrupted * this exception is throw if the blend file is invalid or somehow corrupted
*/ */
public DnaBlockData(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException { public DnaBlockData(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException {
int identifier; int identifier;
//reading 'SDNA' identifier // reading 'SDNA' identifier
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
| inputStream.readByte() << 8 | inputStream.readByte();
if (identifier != SDNA_ID) { if (identifier != SDNA_ID) {
throw new BlenderFileException("Invalid identifier! '" + this.toString(SDNA_ID) + "' expected and found: " + this.toString(identifier)); throw new BlenderFileException("Invalid identifier! '" + this.toString(SDNA_ID) + "' expected and found: " + this.toString(identifier));
} }
//reading names // reading names
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
| inputStream.readByte() << 8 | inputStream.readByte();
if (identifier != NAME_ID) { if (identifier != NAME_ID) {
throw new BlenderFileException("Invalid identifier! '" + this.toString(NAME_ID) + "' expected and found: " + this.toString(identifier)); throw new BlenderFileException("Invalid identifier! '" + this.toString(NAME_ID) + "' expected and found: " + this.toString(identifier));
} }
@ -87,10 +85,9 @@ public class DnaBlockData {
names[i] = inputStream.readString(); names[i] = inputStream.readString();
} }
//reding types // reding types
inputStream.alignPosition(4); inputStream.alignPosition(4);
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
| inputStream.readByte() << 8 | inputStream.readByte();
if (identifier != TYPE_ID) { if (identifier != TYPE_ID) {
throw new BlenderFileException("Invalid identifier! '" + this.toString(TYPE_ID) + "' expected and found: " + this.toString(identifier)); throw new BlenderFileException("Invalid identifier! '" + this.toString(TYPE_ID) + "' expected and found: " + this.toString(identifier));
} }
@ -103,22 +100,20 @@ public class DnaBlockData {
types[i] = inputStream.readString(); types[i] = inputStream.readString();
} }
//reading lengths // reading lengths
inputStream.alignPosition(4); inputStream.alignPosition(4);
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
| inputStream.readByte() << 8 | inputStream.readByte();
if (identifier != TLEN_ID) { if (identifier != TLEN_ID) {
throw new BlenderFileException("Invalid identifier! '" + this.toString(TLEN_ID) + "' expected and found: " + this.toString(identifier)); throw new BlenderFileException("Invalid identifier! '" + this.toString(TLEN_ID) + "' expected and found: " + this.toString(identifier));
} }
int[] lengths = new int[amount];//theamount is the same as int types int[] lengths = new int[amount];// theamount is the same as int types
for (int i = 0; i < amount; ++i) { for (int i = 0; i < amount; ++i) {
lengths[i] = inputStream.readShort(); lengths[i] = inputStream.readShort();
} }
//reading structures // reading structures
inputStream.alignPosition(4); inputStream.alignPosition(4);
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
| inputStream.readByte() << 8 | inputStream.readByte();
if (identifier != STRC_ID) { if (identifier != STRC_ID) {
throw new BlenderFileException("Invalid identifier! '" + this.toString(STRC_ID) + "' expected and found: " + this.toString(identifier)); throw new BlenderFileException("Invalid identifier! '" + this.toString(STRC_ID) + "' expected and found: " + this.toString(identifier));
} }
@ -148,7 +143,7 @@ public class DnaBlockData {
/** /**
* This method returns the structure of the given index. * This method returns the structure of the given index.
* @param index * @param index
* the index of the structure * the index of the structure
* @return the structure of the given index * @return the structure of the given index
*/ */
public Structure getStructure(int index) { public Structure getStructure(int index) {
@ -162,7 +157,7 @@ public class DnaBlockData {
/** /**
* This method returns a structure of the given name. If the name does not exists then null is returned. * This method returns a structure of the given name. If the name does not exists then null is returned.
* @param name * @param name
* the name of the structure * the name of the structure
* @return the required structure or null if the given name is inapropriate * @return the required structure or null if the given name is inapropriate
*/ */
public Structure getStructure(String name) { public Structure getStructure(String name) {
@ -176,7 +171,7 @@ public class DnaBlockData {
/** /**
* This method indicates if the structure of the given name exists. * This method indicates if the structure of the given name exists.
* @param name * @param name
* the name of the structure * the name of the structure
* @return true if the structure exists and false otherwise * @return true if the structure exists and false otherwise
*/ */
public boolean hasStructure(String name) { public boolean hasStructure(String name) {
@ -186,7 +181,7 @@ public class DnaBlockData {
/** /**
* This method converts the given identifier code to string. * This method converts the given identifier code to string.
* @param code * @param code
* the code taht is to be converted * the code taht is to be converted
* @return the string value of the identifier * @return the string value of the identifier
*/ */
private String toString(int code) { private String toString(int code) {

@ -37,12 +37,12 @@ import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
* An array that can be dynamically modified/ * An array that can be dynamically modified/
* @author Marcin Roguski * @author Marcin Roguski
* @param <T> * @param <T>
* the type of stored data in the array * the type of stored data in the array
*/ */
public class DynamicArray<T> implements Cloneable { public class DynamicArray<T> implements Cloneable {
/** An array object that holds the required data. */ /** An array object that holds the required data. */
private T[] array; private T[] array;
/** /**
* This table holds the sizes of dimetions of the dynamic table. It's length specifies the table dimension or a * This table holds the sizes of dimetions of the dynamic table. It's length specifies the table dimension or a
* pointer level. For example: if tableSizes.length == 3 then it either specifies a dynamic table of fixed lengths: * pointer level. For example: if tableSizes.length == 3 then it either specifies a dynamic table of fixed lengths:
@ -53,9 +53,9 @@ public class DynamicArray<T> implements Cloneable {
/** /**
* Constructor. Builds an empty array of the specified sizes. * Constructor. Builds an empty array of the specified sizes.
* @param tableSizes * @param tableSizes
* the sizes of the table * the sizes of the table
* @throws BlenderFileException * @throws BlenderFileException
* an exception is thrown if one of the sizes is not a positive number * an exception is thrown if one of the sizes is not a positive number
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public DynamicArray(int[] tableSizes) throws BlenderFileException { public DynamicArray(int[] tableSizes) throws BlenderFileException {
@ -73,9 +73,9 @@ public class DynamicArray<T> implements Cloneable {
/** /**
* Constructor. Builds an empty array of the specified sizes. * Constructor. Builds an empty array of the specified sizes.
* @param tableSizes * @param tableSizes
* the sizes of the table * the sizes of the table
* @throws BlenderFileException * @throws BlenderFileException
* an exception is thrown if one of the sizes is not a positive number * an exception is thrown if one of the sizes is not a positive number
*/ */
public DynamicArray(int[] tableSizes, T[] data) throws BlenderFileException { public DynamicArray(int[] tableSizes, T[] data) throws BlenderFileException {
this.tableSizes = tableSizes; this.tableSizes = tableSizes;
@ -101,7 +101,7 @@ public class DynamicArray<T> implements Cloneable {
* This method returns a value on the specified position. The dimension of the table is not taken into * This method returns a value on the specified position. The dimension of the table is not taken into
* consideration. * consideration.
* @param position * @param position
* the position of the data * the position of the data
* @return required data * @return required data
*/ */
public T get(int position) { public T get(int position) {
@ -112,7 +112,7 @@ public class DynamicArray<T> implements Cloneable {
* This method returns a value on the specified position in multidimensional array. Be careful not to exceed the * This method returns a value on the specified position in multidimensional array. Be careful not to exceed the
* table boundaries. Check the table's dimension first. * table boundaries. Check the table's dimension first.
* @param position * @param position
* the position of the data indices of data position * the position of the data indices of data position
* @return required data required data * @return required data required data
*/ */
public T get(int... position) { public T get(int... position) {
@ -138,8 +138,8 @@ public class DynamicArray<T> implements Cloneable {
@Override @Override
public String toString() { public String toString() {
StringBuilder result = new StringBuilder(); StringBuilder result = new StringBuilder();
if (array instanceof Character[]) {//in case of character array we convert it to String if (array instanceof Character[]) {// in case of character array we convert it to String
for (int i = 0; i < array.length && (Character) array[i] != '\0'; ++i) {//strings are terminater with '0' for (int i = 0; i < array.length && (Character) array[i] != '\0'; ++i) {// strings are terminater with '0'
result.append(array[i]); result.append(array[i]);
} }
} else { } else {

@ -11,38 +11,38 @@ import java.util.List;
* another structure. * another structure.
* @author Marcin Roguski * @author Marcin Roguski
*/ */
/*package*/ /* package */
class Field implements Cloneable { class Field implements Cloneable {
private static final int NAME_LENGTH = 24; private static final int NAME_LENGTH = 24;
private static final int TYPE_LENGTH = 16; private static final int TYPE_LENGTH = 16;
/** The blender context. */ /** The blender context. */
public BlenderContext blenderContext; public BlenderContext blenderContext;
/** The type of the field. */ /** The type of the field. */
public String type; public String type;
/** The name of the field. */ /** The name of the field. */
public String name; public String name;
/** The value of the field. Filled during data reading. */ /** The value of the field. Filled during data reading. */
public Object value; public Object value;
/** This variable indicates the level of the pointer. */ /** This variable indicates the level of the pointer. */
public int pointerLevel; public int pointerLevel;
/** /**
* This variable determines the sizes of the array. If the value is null the n the field is not an array. * This variable determines the sizes of the array. If the value is null the n the field is not an array.
*/ */
public int[] tableSizes; public int[] tableSizes;
/** This variable indicates if the field is a function pointer. */ /** This variable indicates if the field is a function pointer. */
public boolean function; public boolean function;
/** /**
* Constructor. Saves the field data and parses its name. * Constructor. Saves the field data and parses its name.
* @param name * @param name
* the name of the field * the name of the field
* @param type * @param type
* the type of the field * the type of the field
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown if the names contain errors * this exception is thrown if the names contain errors
*/ */
public Field(String name, String type, BlenderContext blenderContext) throws BlenderFileException { public Field(String name, String type, BlenderContext blenderContext) throws BlenderFileException {
this.type = type; this.type = type;
@ -54,7 +54,7 @@ class Field implements Cloneable {
* Copy constructor. Used in clone method. Copying is not full. The value in the new object is not set so that we * Copy constructor. Used in clone method. Copying is not full. The value in the new object is not set so that we
* have a clead empty copy of the filed to fill with data. * have a clead empty copy of the filed to fill with data.
* @param field * @param field
* the object that we copy * the object that we copy
*/ */
private Field(Field field) { private Field(Field field) {
type = field.type; type = field.type;
@ -75,9 +75,9 @@ class Field implements Cloneable {
/** /**
* This method fills the field wth data read from the input stream. * This method fills the field wth data read from the input stream.
* @param blenderInputStream * @param blenderInputStream
* the stream we read data from * the stream we read data from
* @throws BlenderFileException * @throws BlenderFileException
* an exception is thrown when the blend file is somehow invalid or corrupted * an exception is thrown when the blend file is somehow invalid or corrupted
*/ */
public void fill(BlenderInputStream blenderInputStream) throws BlenderFileException { public void fill(BlenderInputStream blenderInputStream) throws BlenderFileException {
int dataToRead = 1; int dataToRead = 1;
@ -107,9 +107,9 @@ class Field implements Cloneable {
} }
break; break;
case CHARACTER: case CHARACTER:
//character is also stored as a number, because sometimes the new blender version uses // character is also stored as a number, because sometimes the new blender version uses
//other number type instead of character as a field type // other number type instead of character as a field type
//and characters are very often used as byte number stores instead of real chars // and characters are very often used as byte number stores instead of real chars
if (dataToRead == 1) { if (dataToRead == 1) {
value = Byte.valueOf((byte) blenderInputStream.readByte()); value = Byte.valueOf((byte) blenderInputStream.readByte());
} else { } else {
@ -200,28 +200,28 @@ class Field implements Cloneable {
/** /**
* This method parses the field name to determine how the field should be used. * This method parses the field name to determine how the field should be used.
* @param nameBuilder * @param nameBuilder
* the name of the field (given as StringBuilder) * the name of the field (given as StringBuilder)
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown if the names contain errors * this exception is thrown if the names contain errors
*/ */
private void parseField(StringBuilder nameBuilder) throws BlenderFileException { private void parseField(StringBuilder nameBuilder) throws BlenderFileException {
this.removeWhitespaces(nameBuilder); this.removeWhitespaces(nameBuilder);
//veryfying if the name is a pointer // veryfying if the name is a pointer
int pointerIndex = nameBuilder.indexOf("*"); int pointerIndex = nameBuilder.indexOf("*");
while (pointerIndex >= 0) { while (pointerIndex >= 0) {
++pointerLevel; ++pointerLevel;
nameBuilder.deleteCharAt(pointerIndex); nameBuilder.deleteCharAt(pointerIndex);
pointerIndex = nameBuilder.indexOf("*"); pointerIndex = nameBuilder.indexOf("*");
} }
//veryfying if the name is a function pointer // veryfying if the name is a function pointer
if (nameBuilder.indexOf("(") >= 0) { if (nameBuilder.indexOf("(") >= 0) {
function = true; function = true;
this.removeCharacter(nameBuilder, '('); this.removeCharacter(nameBuilder, '(');
this.removeCharacter(nameBuilder, ')'); this.removeCharacter(nameBuilder, ')');
} else { } else {
//veryfying if the name is a table // veryfying if the name is a table
int tableStartIndex = 0; int tableStartIndex = 0;
List<Integer> lengths = new ArrayList<Integer>(3);//3 dimensions will be enough in most cases List<Integer> lengths = new ArrayList<Integer>(3);// 3 dimensions will be enough in most cases
do { do {
tableStartIndex = nameBuilder.indexOf("["); tableStartIndex = nameBuilder.indexOf("[");
if (tableStartIndex > 0) { if (tableStartIndex > 0) {
@ -250,9 +250,9 @@ class Field implements Cloneable {
/** /**
* This method removes the required character from the text. * This method removes the required character from the text.
* @param text * @param text
* the text we remove characters from * the text we remove characters from
* @param toRemove * @param toRemove
* the character to be removed * the character to be removed
*/ */
private void removeCharacter(StringBuilder text, char toRemove) { private void removeCharacter(StringBuilder text, char toRemove) {
for (int i = 0; i < text.length(); ++i) { for (int i = 0; i < text.length(); ++i) {
@ -266,7 +266,7 @@ class Field implements Cloneable {
/** /**
* This method removes all whitespaces from the text. * This method removes all whitespaces from the text.
* @param text * @param text
* the text we remove whitespaces from * the text we remove whitespaces from
*/ */
private void removeWhitespaces(StringBuilder text) { private void removeWhitespaces(StringBuilder text) {
for (int i = 0; i < text.length(); ++i) { for (int i = 0; i < text.length(); ++i) {
@ -276,13 +276,13 @@ class Field implements Cloneable {
} }
} }
} }
/** /**
* This method builds the full name of the field (with function, pointer and table indications). * This method builds the full name of the field (with function, pointer and table indications).
* @return the full name of the field * @return the full name of the field
*/ */
public String getFullName() { public String getFullName() {
StringBuilder result = new StringBuilder(); StringBuilder result = new StringBuilder();
if (function) { if (function) {
result.append('('); result.append('(');
} }
@ -306,10 +306,10 @@ class Field implements Cloneable {
StringBuilder result = new StringBuilder(); StringBuilder result = new StringBuilder();
result.append(this.getFullName()); result.append(this.getFullName());
//insert appropriate amount of spaces to format the output corrently // insert appropriate amount of spaces to format the output corrently
int nameLength = result.length(); int nameLength = result.length();
result.append(' ');//at least one space is a must result.append(' ');// at least one space is a must
for (int i = 1; i < NAME_LENGTH - nameLength; ++i) {//we start from i=1 because one space is already added for (int i = 1; i < NAME_LENGTH - nameLength; ++i) {// we start from i=1 because one space is already added
result.append(' '); result.append(' ');
} }
result.append(type); result.append(type);

@ -41,52 +41,51 @@ import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
*/ */
public class FileBlockHeader { public class FileBlockHeader {
public static final int BLOCK_TE00 = 'T' << 24 | 'E' << 16; //TE00 public static final int BLOCK_TE00 = 'T' << 24 | 'E' << 16; // TE00
public static final int BLOCK_ME00 = 'M' << 24 | 'E' << 16; //ME00 public static final int BLOCK_ME00 = 'M' << 24 | 'E' << 16; // ME00
public static final int BLOCK_SR00 = 'S' << 24 | 'R' << 16; //SR00 public static final int BLOCK_SR00 = 'S' << 24 | 'R' << 16; // SR00
public static final int BLOCK_CA00 = 'C' << 24 | 'A' << 16; //CA00 public static final int BLOCK_CA00 = 'C' << 24 | 'A' << 16; // CA00
public static final int BLOCK_LA00 = 'L' << 24 | 'A' << 16; //LA00 public static final int BLOCK_LA00 = 'L' << 24 | 'A' << 16; // LA00
public static final int BLOCK_OB00 = 'O' << 24 | 'B' << 16; //OB00 public static final int BLOCK_OB00 = 'O' << 24 | 'B' << 16; // OB00
public static final int BLOCK_MA00 = 'M' << 24 | 'A' << 16; //MA00 public static final int BLOCK_MA00 = 'M' << 24 | 'A' << 16; // MA00
public static final int BLOCK_SC00 = 'S' << 24 | 'C' << 16; //SC00 public static final int BLOCK_SC00 = 'S' << 24 | 'C' << 16; // SC00
public static final int BLOCK_WO00 = 'W' << 24 | 'O' << 16; //WO00 public static final int BLOCK_WO00 = 'W' << 24 | 'O' << 16; // WO00
public static final int BLOCK_TX00 = 'T' << 24 | 'X' << 16; //TX00 public static final int BLOCK_TX00 = 'T' << 24 | 'X' << 16; // TX00
public static final int BLOCK_IP00 = 'I' << 24 | 'P' << 16; //IP00 public static final int BLOCK_IP00 = 'I' << 24 | 'P' << 16; // IP00
public static final int BLOCK_AC00 = 'A' << 24 | 'C' << 16; //AC00 public static final int BLOCK_AC00 = 'A' << 24 | 'C' << 16; // AC00
public static final int BLOCK_GLOB = 'G' << 24 | 'L' << 16 | 'O' << 8 | 'B'; //GLOB public static final int BLOCK_GLOB = 'G' << 24 | 'L' << 16 | 'O' << 8 | 'B'; // GLOB
public static final int BLOCK_REND = 'R' << 24 | 'E' << 16 | 'N' << 8 | 'D'; //REND public static final int BLOCK_REND = 'R' << 24 | 'E' << 16 | 'N' << 8 | 'D'; // REND
public static final int BLOCK_DATA = 'D' << 24 | 'A' << 16 | 'T' << 8 | 'A'; //DATA public static final int BLOCK_DATA = 'D' << 24 | 'A' << 16 | 'T' << 8 | 'A'; // DATA
public static final int BLOCK_DNA1 = 'D' << 24 | 'N' << 16 | 'A' << 8 | '1'; //DNA1 public static final int BLOCK_DNA1 = 'D' << 24 | 'N' << 16 | 'A' << 8 | '1'; // DNA1
public static final int BLOCK_ENDB = 'E' << 24 | 'N' << 16 | 'D' << 8 | 'B'; //ENDB public static final int BLOCK_ENDB = 'E' << 24 | 'N' << 16 | 'D' << 8 | 'B'; // ENDB
/** Identifier of the file-block [4 bytes]. */ /** Identifier of the file-block [4 bytes]. */
private int code; private int code;
/** Total length of the data after the file-block-header [4 bytes]. */ /** Total length of the data after the file-block-header [4 bytes]. */
private int size; private int size;
/** /**
* Memory address the structure was located when written to disk [4 or 8 bytes (defined in file header as a pointer * Memory address the structure was located when written to disk [4 or 8 bytes (defined in file header as a pointer
* size)]. * size)].
*/ */
private long oldMemoryAddress; private long oldMemoryAddress;
/** Index of the SDNA structure [4 bytes]. */ /** Index of the SDNA structure [4 bytes]. */
private int sdnaIndex; private int sdnaIndex;
/** Number of structure located in this file-block [4 bytes]. */ /** Number of structure located in this file-block [4 bytes]. */
private int count; private int count;
/** Start position of the block's data in the stream. */ /** Start position of the block's data in the stream. */
private int blockPosition; private int blockPosition;
/** /**
* Constructor. Loads the block header from the given stream during instance creation. * Constructor. Loads the block header from the given stream during instance creation.
* @param inputStream * @param inputStream
* the stream we read the block header from * the stream we read the block header from
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the pointer size is neither 4 nor 8 * this exception is thrown when the pointer size is neither 4 nor 8
*/ */
public FileBlockHeader(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException { public FileBlockHeader(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException {
inputStream.alignPosition(4); inputStream.alignPosition(4);
code = inputStream.readByte() << 24 | inputStream.readByte() << 16 code = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
| inputStream.readByte() << 8 | inputStream.readByte();
size = inputStream.readInt(); size = inputStream.readInt();
oldMemoryAddress = inputStream.readPointer(); oldMemoryAddress = inputStream.readPointer();
sdnaIndex = inputStream.readInt(); sdnaIndex = inputStream.readInt();
@ -103,7 +102,7 @@ public class FileBlockHeader {
/** /**
* This method returns the structure described by the header filled with appropriate data. * This method returns the structure described by the header filled with appropriate data.
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @return structure filled with data * @return structure filled with data
* @throws BlenderFileException * @throws BlenderFileException
*/ */
@ -186,7 +185,7 @@ public class FileBlockHeader {
/** /**
* This method transforms the coded bloch id into a string value. * This method transforms the coded bloch id into a string value.
* @param code * @param code
* the id of the block * the id of the block
* @return the string value of the block id * @return the string value of the block id
*/ */
protected String codeToString(int code) { protected String codeToString(int code) {

@ -45,20 +45,20 @@ public class Pointer {
/** The blender context. */ /** The blender context. */
private BlenderContext blenderContext; private BlenderContext blenderContext;
/** The level of the pointer. */ /** The level of the pointer. */
private int pointerLevel; private int pointerLevel;
/** The address in file it points to. */ /** The address in file it points to. */
private long oldMemoryAddress; private long oldMemoryAddress;
/** This variable indicates if the field is a function pointer. */ /** This variable indicates if the field is a function pointer. */
public boolean function; public boolean function;
/** /**
* Constructr. Stores the basic data about the pointer. * Constructr. Stores the basic data about the pointer.
* @param pointerLevel * @param pointerLevel
* the level of the pointer * the level of the pointer
* @param function * @param function
* this variable indicates if the field is a function pointer * this variable indicates if the field is a function pointer
* @param blenderContext * @param blenderContext
* the repository f data; used in fetching the value that the pointer points * the repository f data; used in fetching the value that the pointer points
*/ */
public Pointer(int pointerLevel, boolean function, BlenderContext blenderContext) { public Pointer(int pointerLevel, boolean function, BlenderContext blenderContext) {
this.pointerLevel = pointerLevel; this.pointerLevel = pointerLevel;
@ -70,7 +70,7 @@ public class Pointer {
* This method fills the pointer with its address value (it doesn't get the actual data yet. Use the 'fetch' method * This method fills the pointer with its address value (it doesn't get the actual data yet. Use the 'fetch' method
* for this. * for this.
* @param inputStream * @param inputStream
* the stream we read the pointer value from * the stream we read the pointer value from
*/ */
public void fill(BlenderInputStream inputStream) { public void fill(BlenderInputStream inputStream) {
oldMemoryAddress = inputStream.readPointer(); oldMemoryAddress = inputStream.readPointer();
@ -79,10 +79,10 @@ public class Pointer {
/** /**
* This method fetches the data stored under the given address. * This method fetches the data stored under the given address.
* @param inputStream * @param inputStream
* the stream we read data from * the stream we read data from
* @return the data read from the file * @return the data read from the file
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blend file structure is somehow invalid or corrupted * this exception is thrown when the blend file structure is somehow invalid or corrupted
*/ */
public List<Structure> fetchData(BlenderInputStream inputStream) throws BlenderFileException { public List<Structure> fetchData(BlenderInputStream inputStream) throws BlenderFileException {
if (oldMemoryAddress == 0) { if (oldMemoryAddress == 0) {
@ -90,9 +90,8 @@ public class Pointer {
} }
List<Structure> structures = null; List<Structure> structures = null;
FileBlockHeader dataFileBlock = blenderContext.getFileBlock(oldMemoryAddress); FileBlockHeader dataFileBlock = blenderContext.getFileBlock(oldMemoryAddress);
if(dataFileBlock == null) { if (dataFileBlock == null) {
throw new BlenderFileException("No data stored for address: " +oldMemoryAddress + throw new BlenderFileException("No data stored for address: " + oldMemoryAddress + ". Rarely blender makes mistakes when storing data. Try resaving the model after making minor changes. This usually helps.");
". Rarely blender makes mistakes when storing data. Try resaving the model after making minor changes. This usually helps.");
} }
if (pointerLevel > 1) { if (pointerLevel > 1) {
int pointersAmount = dataFileBlock.getSize() / inputStream.getPointerSize() * dataFileBlock.getCount(); int pointersAmount = dataFileBlock.getSize() / inputStream.getPointerSize() * dataFileBlock.getCount();
@ -108,12 +107,12 @@ public class Pointer {
structures.addAll(p.fetchData(inputStream)); structures.addAll(p.fetchData(inputStream));
} }
} else { } else {
//it is necessary to put null's if the pointer is null, ie. in materials array that is attached to the mesh, the index // it is necessary to put null's if the pointer is null, ie. in materials array that is attached to the mesh, the index
//of the material is important, that is why we need null's to indicate that some materials' slots are empty // of the material is important, that is why we need null's to indicate that some materials' slots are empty
if(structures == null) { if (structures == null) {
structures = new ArrayList<Structure>(); structures = new ArrayList<Structure>();
} }
structures.add(null); structures.add(null);
} }
} }
} else { } else {
@ -144,7 +143,7 @@ public class Pointer {
public boolean isNull() { public boolean isNull() {
return oldMemoryAddress == 0; return oldMemoryAddress == 0;
} }
/** /**
* This method indicates if this is a null-pointer or not. * This method indicates if this is a null-pointer or not.
* @return <b>true</b> if the pointer is not null and <b>false</b> otherwise * @return <b>true</b> if the pointer is not null and <b>false</b> otherwise

@ -49,20 +49,20 @@ public class Structure implements Cloneable {
/** The address of the block that fills the structure. */ /** The address of the block that fills the structure. */
private transient Long oldMemoryAddress; private transient Long oldMemoryAddress;
/** The type of the structure. */ /** The type of the structure. */
private String type; private String type;
/** /**
* The fields of the structure. Each field consists of a pair: name-type. * The fields of the structure. Each field consists of a pair: name-type.
*/ */
private Field[] fields; private Field[] fields;
/** /**
* Constructor that copies the data of the structure. * Constructor that copies the data of the structure.
* @param structure * @param structure
* the structure to copy. * the structure to copy.
* @param blenderContext * @param blenderContext
* the blender context of the structure * the blender context of the structure
* @throws CloneNotSupportedException * @throws CloneNotSupportedException
* this exception should never be thrown * this exception should never be thrown
*/ */
private Structure(Structure structure, BlenderContext blenderContext) throws CloneNotSupportedException { private Structure(Structure structure, BlenderContext blenderContext) throws CloneNotSupportedException {
type = structure.type; type = structure.type;
@ -77,15 +77,15 @@ public class Structure implements Cloneable {
/** /**
* Constructor. Loads the structure from the given stream during instance creation. * Constructor. Loads the structure from the given stream during instance creation.
* @param inputStream * @param inputStream
* the stream we read the structure from * the stream we read the structure from
* @param names * @param names
* the names from which the name of structure and its fields will be taken * the names from which the name of structure and its fields will be taken
* @param types * @param types
* the names of types for the structure * the names of types for the structure
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
* this exception occurs if the amount of fields, defined in the file, is negative * this exception occurs if the amount of fields, defined in the file, is negative
*/ */
public Structure(BlenderInputStream inputStream, String[] names, String[] types, BlenderContext blenderContext) throws BlenderFileException { public Structure(BlenderInputStream inputStream, String[] names, String[] types, BlenderContext blenderContext) throws BlenderFileException {
int nameIndex = inputStream.readShort(); int nameIndex = inputStream.readShort();
@ -109,10 +109,10 @@ public class Structure implements Cloneable {
/** /**
* This method fills the structure with data. * This method fills the structure with data.
* @param inputStream * @param inputStream
* the stream we read data from, its read cursor should be placed at the start position of the data for the * the stream we read data from, its read cursor should be placed at the start position of the data for the
* structure * structure
* @throws BlenderFileException * @throws BlenderFileException
* an exception is thrown when the blend file is somehow invalid or corrupted * an exception is thrown when the blend file is somehow invalid or corrupted
*/ */
public void fill(BlenderInputStream inputStream) throws BlenderFileException { public void fill(BlenderInputStream inputStream) throws BlenderFileException {
int position = inputStream.getPosition(); int position = inputStream.getPosition();
@ -127,7 +127,7 @@ public class Structure implements Cloneable {
/** /**
* This method returns the value of the filed with a given name. * This method returns the value of the filed with a given name.
* @param fieldName * @param fieldName
* the name of the field * the name of the field
* @return the value of the field or null if no field with a given name is found * @return the value of the field or null if no field with a given name is found
*/ */
public Object getFieldValue(String fieldName) { public Object getFieldValue(String fieldName) {
@ -143,7 +143,7 @@ public class Structure implements Cloneable {
* This method returns the value of the filed with a given name. The structure is considered to have flat fields * This method returns the value of the filed with a given name. The structure is considered to have flat fields
* only (no substructures). * only (no substructures).
* @param fieldName * @param fieldName
* the name of the field * the name of the field
* @return the value of the field or null if no field with a given name is found * @return the value of the field or null if no field with a given name is found
*/ */
public Object getFlatFieldValue(String fieldName) { public Object getFlatFieldValue(String fieldName) {
@ -153,7 +153,7 @@ public class Structure implements Cloneable {
return value; return value;
} else if (value instanceof Structure) { } else if (value instanceof Structure) {
value = ((Structure) value).getFlatFieldValue(fieldName); value = ((Structure) value).getFlatFieldValue(fieldName);
if (value != null) {//we can compare references here, since we use one static object as a NULL field value if (value != null) {// we can compare references here, since we use one static object as a NULL field value
return value; return value;
} }
} }
@ -165,12 +165,12 @@ public class Structure implements Cloneable {
* This methos should be used on structures that are of a 'ListBase' type. It creates a List of structures that are * This methos should be used on structures that are of a 'ListBase' type. It creates a List of structures that are
* held by this structure within the blend file. * held by this structure within the blend file.
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @return a list of filled structures * @return a list of filled structures
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blend file structure is somehow invalid or corrupted * this exception is thrown when the blend file structure is somehow invalid or corrupted
* @throws IllegalArgumentException * @throws IllegalArgumentException
* this exception is thrown if the type of the structure is not 'ListBase' * this exception is thrown if the type of the structure is not 'ListBase'
*/ */
public List<Structure> evaluateListBase(BlenderContext blenderContext) throws BlenderFileException { public List<Structure> evaluateListBase(BlenderContext blenderContext) throws BlenderFileException {
if (!"ListBase".equals(this.type)) { if (!"ListBase".equals(this.type)) {
@ -209,17 +209,17 @@ public class Structure implements Cloneable {
/** /**
* This method returns the field name of the given index. * This method returns the field name of the given index.
* @param fieldIndex * @param fieldIndex
* the index of the field * the index of the field
* @return the field name of the given index * @return the field name of the given index
*/ */
public String getFieldName(int fieldIndex) { public String getFieldName(int fieldIndex) {
return fields[fieldIndex].name; return fields[fieldIndex].name;
} }
/** /**
* This method returns the full field name of the given index. * This method returns the full field name of the given index.
* @param fieldIndex * @param fieldIndex
* the index of the field * the index of the field
* @return the full field name of the given index * @return the full field name of the given index
*/ */
public String getFieldFullName(int fieldIndex) { public String getFieldFullName(int fieldIndex) {
@ -229,7 +229,7 @@ public class Structure implements Cloneable {
/** /**
* This method returns the field type of the given index. * This method returns the field type of the given index.
* @param fieldIndex * @param fieldIndex
* the index of the field * the index of the field
* @return the field type of the given index * @return the field type of the given index
*/ */
public String getFieldType(int fieldIndex) { public String getFieldType(int fieldIndex) {
@ -248,20 +248,20 @@ public class Structure implements Cloneable {
return oldMemoryAddress; return oldMemoryAddress;
} }
/** /**
* This method returns the name of the structure. If the structure has an ID field then the name is returned. * This method returns the name of the structure. If the structure has an ID field then the name is returned.
* Otherwise the name does not exists and the method returns null. * Otherwise the name does not exists and the method returns null.
* @return the name of the structure read from the ID field or null * @return the name of the structure read from the ID field or null
*/ */
public String getName() { public String getName() {
Object fieldValue = this.getFieldValue("ID"); Object fieldValue = this.getFieldValue("ID");
if(fieldValue instanceof Structure) { if (fieldValue instanceof Structure) {
Structure id = (Structure)fieldValue; Structure id = (Structure) fieldValue;
return id == null ? null : id.getFieldValue("name").toString().substring(2);//blender adds 2-charactes as a name prefix return id == null ? null : id.getFieldValue("name").toString().substring(2);// blender adds 2-charactes as a name prefix
} }
return null; return null;
} }
@Override @Override
public String toString() { public String toString() {
StringBuilder result = new StringBuilder("struct ").append(type).append(" {\n"); StringBuilder result = new StringBuilder("struct ").append(type).append(" {\n");
@ -280,7 +280,7 @@ public class Structure implements Cloneable {
* This enum enumerates all known data types that can be found in the blend file. * This enum enumerates all known data types that can be found in the blend file.
* @author Marcin Roguski * @author Marcin Roguski
*/ */
/*package*/ /* package */
static enum DataType { static enum DataType {
CHARACTER, SHORT, INTEGER, LONG, FLOAT, DOUBLE, VOID, STRUCTURE, POINTER; CHARACTER, SHORT, INTEGER, LONG, FLOAT, DOUBLE, VOID, STRUCTURE, POINTER;
@ -305,12 +305,12 @@ public class Structure implements Cloneable {
* This method returns the data type that is appropriate to the given type name. WARNING! The type recognition * This method returns the data type that is appropriate to the given type name. WARNING! The type recognition
* is case sensitive! * is case sensitive!
* @param type * @param type
* the type name of the data * the type name of the data
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @return appropriate enum value to the given type name * @return appropriate enum value to the given type name
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown if the given type name does not exist in the blend file * this exception is thrown if the given type name does not exist in the blend file
*/ */
public static DataType getDataType(String type, BlenderContext blenderContext) throws BlenderFileException { public static DataType getDataType(String type, BlenderContext blenderContext) throws BlenderFileException {
DataType result = PRIMARY_TYPES.get(type); DataType result = PRIMARY_TYPES.get(type);

@ -59,49 +59,49 @@ public class LightHelper extends AbstractBlenderHelper {
* This constructor parses the given blender version and stores the result. Some functionalities may differ in * This constructor parses the given blender version and stores the result. Some functionalities may differ in
* different blender versions. * different blender versions.
* @param blenderVersion * @param blenderVersion
* the version read from the blend file * the version read from the blend file
* @param fixUpAxis * @param fixUpAxis
* a variable that indicates if the Y asxis is the UP axis or not * a variable that indicates if the Y asxis is the UP axis or not
*/ */
public LightHelper(String blenderVersion, boolean fixUpAxis) { public LightHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis); super(blenderVersion, fixUpAxis);
} }
public LightNode toLight(Structure structure, BlenderContext blenderContext) throws BlenderFileException { public LightNode toLight(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
LightNode result = (LightNode) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); LightNode result = (LightNode) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
if (result != null) { if (result != null) {
return result; return result;
} }
Light light = null; Light light = null;
int type = ((Number) structure.getFieldValue("type")).intValue(); int type = ((Number) structure.getFieldValue("type")).intValue();
switch (type) { switch (type) {
case 0://Lamp case 0:// Lamp
light = new PointLight(); light = new PointLight();
float distance = ((Number) structure.getFieldValue("dist")).floatValue(); float distance = ((Number) structure.getFieldValue("dist")).floatValue();
((PointLight) light).setRadius(distance); ((PointLight) light).setRadius(distance);
break; break;
case 1://Sun case 1:// Sun
LOGGER.log(Level.WARNING, "'Sun' lamp is not supported in jMonkeyEngine."); LOGGER.log(Level.WARNING, "'Sun' lamp is not supported in jMonkeyEngine.");
break; break;
case 2://Spot case 2:// Spot
light = new SpotLight(); light = new SpotLight();
//range // range
((SpotLight)light).setSpotRange(((Number) structure.getFieldValue("dist")).floatValue()); ((SpotLight) light).setSpotRange(((Number) structure.getFieldValue("dist")).floatValue());
//outer angle // outer angle
float outerAngle = ((Number) structure.getFieldValue("spotsize")).floatValue()*FastMath.DEG_TO_RAD * 0.5f; float outerAngle = ((Number) structure.getFieldValue("spotsize")).floatValue() * FastMath.DEG_TO_RAD * 0.5f;
((SpotLight)light).setSpotOuterAngle(outerAngle); ((SpotLight) light).setSpotOuterAngle(outerAngle);
//inner angle // inner angle
float spotblend = ((Number) structure.getFieldValue("spotblend")).floatValue(); float spotblend = ((Number) structure.getFieldValue("spotblend")).floatValue();
spotblend = FastMath.clamp(spotblend, 0, 1); spotblend = FastMath.clamp(spotblend, 0, 1);
float innerAngle = outerAngle * (1 - spotblend); float innerAngle = outerAngle * (1 - spotblend);
((SpotLight)light).setSpotInnerAngle(innerAngle); ((SpotLight) light).setSpotInnerAngle(innerAngle);
break; break;
case 3://Hemi case 3:// Hemi
LOGGER.log(Level.WARNING, "'Hemi' lamp is not supported in jMonkeyEngine."); LOGGER.log(Level.WARNING, "'Hemi' lamp is not supported in jMonkeyEngine.");
break; break;
case 4://Area case 4:// Area
light = new DirectionalLight(); light = new DirectionalLight();
break; break;
default: default:
throw new BlenderFileException("Unknown light source type: " + type); throw new BlenderFileException("Unknown light source type: " + type);
@ -115,9 +115,9 @@ public class LightHelper extends AbstractBlenderHelper {
} }
return result; return result;
} }
@Override @Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0; return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0;
} }
} }

@ -4,23 +4,23 @@ package com.jme3.scene.plugins.blender.materials;
* An interface used in calculating alpha mask during particles' texture calculations. * An interface used in calculating alpha mask during particles' texture calculations.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ interface IAlphaMask { /* package */interface IAlphaMask {
/** /**
* This method sets the size of the texture's image. * This method sets the size of the texture's image.
* @param width * @param width
* the width of the image * the width of the image
* @param height * @param height
* the height of the image * the height of the image
*/ */
void setImageSize(int width, int height); void setImageSize(int width, int height);
/** /**
* This method returns the alpha value for the specified texture position. * This method returns the alpha value for the specified texture position.
* @param x * @param x
* the X coordinate of the texture position * the X coordinate of the texture position
* @param y * @param y
* the Y coordinate of the texture position * the Y coordinate of the texture position
* @return the alpha value for the specified texture position * @return the alpha value for the specified texture position
*/ */
byte getAlpha(float x, float y); byte getAlpha(float x, float y);
} }

@ -36,378 +36,377 @@ import com.jme3.util.BufferUtils;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public final class MaterialContext { public final class MaterialContext {
private static final Logger LOGGER = Logger.getLogger(MaterialContext.class.getName()); private static final Logger LOGGER = Logger.getLogger(MaterialContext.class.getName());
//texture mapping types // texture mapping types
public static final int MTEX_COL = 0x01; public static final int MTEX_COL = 0x01;
public static final int MTEX_NOR = 0x02; public static final int MTEX_NOR = 0x02;
public static final int MTEX_SPEC = 0x04; public static final int MTEX_SPEC = 0x04;
public static final int MTEX_EMIT = 0x40; public static final int MTEX_EMIT = 0x40;
public static final int MTEX_ALPHA = 0x80; public static final int MTEX_ALPHA = 0x80;
/* package */final String name; /* package */final String name;
/* package */final Map<Number, CombinedTexture> loadedTextures; /* package */final Map<Number, CombinedTexture> loadedTextures;
/* package */final ColorRGBA diffuseColor; /* package */final ColorRGBA diffuseColor;
/* package */final DiffuseShader diffuseShader; /* package */final DiffuseShader diffuseShader;
/* package */final SpecularShader specularShader; /* package */final SpecularShader specularShader;
/* package */final ColorRGBA specularColor; /* package */final ColorRGBA specularColor;
/* package */final ColorRGBA ambientColor; /* package */final ColorRGBA ambientColor;
/* package */final float shininess; /* package */final float shininess;
/* package */final boolean shadeless; /* package */final boolean shadeless;
/* package */final boolean vertexColor; /* package */final boolean vertexColor;
/* package */final boolean transparent; /* package */final boolean transparent;
/* package */final boolean vTangent; /* package */final boolean vTangent;
/* package */FaceCullMode faceCullMode; /* package */FaceCullMode faceCullMode;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
/* package */MaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException { /* package */MaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
name = structure.getName(); name = structure.getName();
int mode = ((Number) structure.getFieldValue("mode")).intValue(); int mode = ((Number) structure.getFieldValue("mode")).intValue();
shadeless = (mode & 0x4) != 0; shadeless = (mode & 0x4) != 0;
vertexColor = (mode & 0x80) != 0; vertexColor = (mode & 0x80) != 0;
vTangent = (mode & 0x4000000) != 0; // NOTE: Requires tangents vTangent = (mode & 0x4000000) != 0; // NOTE: Requires tangents
int diff_shader = ((Number) structure.getFieldValue("diff_shader")).intValue(); int diff_shader = ((Number) structure.getFieldValue("diff_shader")).intValue();
diffuseShader = DiffuseShader.values()[diff_shader]; diffuseShader = DiffuseShader.values()[diff_shader];
if(this.shadeless) { if (this.shadeless) {
float r = ((Number) structure.getFieldValue("r")).floatValue(); float r = ((Number) structure.getFieldValue("r")).floatValue();
float g = ((Number) structure.getFieldValue("g")).floatValue(); float g = ((Number) structure.getFieldValue("g")).floatValue();
float b = ((Number) structure.getFieldValue("b")).floatValue(); float b = ((Number) structure.getFieldValue("b")).floatValue();
float alpha = ((Number) structure.getFieldValue("alpha")).floatValue(); float alpha = ((Number) structure.getFieldValue("alpha")).floatValue();
diffuseColor = new ColorRGBA(r, g, b, alpha); diffuseColor = new ColorRGBA(r, g, b, alpha);
specularShader = null; specularShader = null;
specularColor = ambientColor = null; specularColor = ambientColor = null;
shininess = 0.0f; shininess = 0.0f;
} else { } else {
diffuseColor = this.readDiffuseColor(structure, diffuseShader); diffuseColor = this.readDiffuseColor(structure, diffuseShader);
int spec_shader = ((Number) structure.getFieldValue("spec_shader")).intValue(); int spec_shader = ((Number) structure.getFieldValue("spec_shader")).intValue();
specularShader = SpecularShader.values()[spec_shader]; specularShader = SpecularShader.values()[spec_shader];
specularColor = this.readSpecularColor(structure, specularShader); specularColor = this.readSpecularColor(structure, specularShader);
float r = ((Number) structure.getFieldValue("ambr")).floatValue(); float r = ((Number) structure.getFieldValue("ambr")).floatValue();
float g = ((Number) structure.getFieldValue("ambg")).floatValue(); float g = ((Number) structure.getFieldValue("ambg")).floatValue();
float b = ((Number) structure.getFieldValue("ambb")).floatValue(); float b = ((Number) structure.getFieldValue("ambb")).floatValue();
float alpha = ((Number) structure.getFieldValue("alpha")).floatValue(); float alpha = ((Number) structure.getFieldValue("alpha")).floatValue();
ambientColor = new ColorRGBA(r, g, b, alpha); ambientColor = new ColorRGBA(r, g, b, alpha);
float shininess = ((Number) structure.getFieldValue("emit")).floatValue(); float shininess = ((Number) structure.getFieldValue("emit")).floatValue();
this.shininess = shininess > 0.0f ? shininess : MaterialHelper.DEFAULT_SHININESS; this.shininess = shininess > 0.0f ? shininess : MaterialHelper.DEFAULT_SHININESS;
} }
DynamicArray<Pointer> mtexsArray = (DynamicArray<Pointer>) structure.getFieldValue("mtex"); DynamicArray<Pointer> mtexsArray = (DynamicArray<Pointer>) structure.getFieldValue("mtex");
int separatedTextures = ((Number) structure.getFieldValue("septex")).intValue(); int separatedTextures = ((Number) structure.getFieldValue("septex")).intValue();
List<TextureData> texturesList = new ArrayList<TextureData>(); List<TextureData> texturesList = new ArrayList<TextureData>();
for (int i = 0; i < mtexsArray.getTotalSize(); ++i) { for (int i = 0; i < mtexsArray.getTotalSize(); ++i) {
Pointer p = mtexsArray.get(i); Pointer p = mtexsArray.get(i);
if (p.isNotNull() && (separatedTextures & 1 << i) == 0) { if (p.isNotNull() && (separatedTextures & 1 << i) == 0) {
TextureData textureData = new TextureData(); TextureData textureData = new TextureData();
textureData.mtex = p.fetchData(blenderContext.getInputStream()).get(0); textureData.mtex = p.fetchData(blenderContext.getInputStream()).get(0);
textureData.uvCoordinatesType = ((Number) textureData.mtex.getFieldValue("texco")).intValue(); textureData.uvCoordinatesType = ((Number) textureData.mtex.getFieldValue("texco")).intValue();
textureData.projectionType = ((Number) textureData.mtex.getFieldValue("mapping")).intValue(); textureData.projectionType = ((Number) textureData.mtex.getFieldValue("mapping")).intValue();
Pointer pTex = (Pointer) textureData.mtex.getFieldValue("tex"); Pointer pTex = (Pointer) textureData.mtex.getFieldValue("tex");
if (pTex.isNotNull()) { if (pTex.isNotNull()) {
Structure tex = pTex.fetchData(blenderContext.getInputStream()).get(0); Structure tex = pTex.fetchData(blenderContext.getInputStream()).get(0);
textureData.textureStructure = tex; textureData.textureStructure = tex;
texturesList.add(textureData); texturesList.add(textureData);
} }
} }
} }
//loading the textures and merging them // loading the textures and merging them
Map<Number, List<TextureData>> textureDataMap = this.sortAndFilterTextures(texturesList); Map<Number, List<TextureData>> textureDataMap = this.sortAndFilterTextures(texturesList);
loadedTextures = new HashMap<Number, CombinedTexture>(); loadedTextures = new HashMap<Number, CombinedTexture>();
float[] diffuseColorArray = new float[] {diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a}; float[] diffuseColorArray = new float[] { diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a };
TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
for(Entry<Number, List<TextureData>> entry : textureDataMap.entrySet()) { for (Entry<Number, List<TextureData>> entry : textureDataMap.entrySet()) {
if(entry.getValue().size()>0) { if (entry.getValue().size() > 0) {
CombinedTexture combinedTexture = new CombinedTexture(entry.getKey().intValue()); CombinedTexture combinedTexture = new CombinedTexture(entry.getKey().intValue());
for(TextureData textureData : entry.getValue()) { for (TextureData textureData : entry.getValue()) {
int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue(); int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue();
boolean negateTexture = (texflag & 0x04) != 0; boolean negateTexture = (texflag & 0x04) != 0;
Texture texture = textureHelper.getTexture(textureData.textureStructure, textureData.mtex, blenderContext); Texture texture = textureHelper.getTexture(textureData.textureStructure, textureData.mtex, blenderContext);
if(texture != null) { if (texture != null) {
int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue(); int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue();
float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(), float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(), ((Number) textureData.mtex.getFieldValue("g")).floatValue(), ((Number) textureData.mtex.getFieldValue("b")).floatValue() };
((Number) textureData.mtex.getFieldValue("g")).floatValue(), float colfac = ((Number) textureData.mtex.getFieldValue("colfac")).floatValue();
((Number) textureData.mtex.getFieldValue("b")).floatValue() }; TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat(), texflag, negateTexture, blendType, diffuseColorArray, color, colfac);
float colfac = ((Number) textureData.mtex.getFieldValue("colfac")).floatValue(); combinedTexture.add(texture, textureBlender, textureData.uvCoordinatesType, textureData.projectionType, textureData.textureStructure, blenderContext);
TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat(), }
texflag, negateTexture, blendType, diffuseColorArray, color, colfac); }
combinedTexture.add(texture, textureBlender, textureData.uvCoordinatesType, textureData.projectionType, textureData.textureStructure, blenderContext); if (combinedTexture.getTexturesCount() > 0) {
} loadedTextures.put(entry.getKey(), combinedTexture);
} }
if(combinedTexture.getTexturesCount() > 0) { }
loadedTextures.put(entry.getKey(), combinedTexture); }
}
} // veryfying if the transparency is present
} // (in blender transparent mask is 0x10000 but its better to verify it because blender can indicate transparency when
// it is not required
//veryfying if the transparency is present boolean transparent = false;
//(in blender transparent mask is 0x10000 but its better to verify it because blender can indicate transparency when if (diffuseColor != null) {
//it is not required transparent = diffuseColor.a < 1.0f;
boolean transparent = false; if (textureDataMap.size() > 0) {// texutre covers the material color
if(diffuseColor != null) { diffuseColor.set(1, 1, 1, 1);
transparent = diffuseColor.a < 1.0f; }
if(textureDataMap.size() > 0) {//texutre covers the material color }
diffuseColor.set(1, 1, 1, 1); if (specularColor != null) {
} transparent = transparent || specularColor.a < 1.0f;
} }
if(specularColor != null) { if (ambientColor != null) {
transparent = transparent || specularColor.a < 1.0f; transparent = transparent || ambientColor.a < 1.0f;
} }
if(ambientColor != null) { this.transparent = transparent;
transparent = transparent || ambientColor.a < 1.0f; }
}
this.transparent = transparent; public void applyMaterial(Geometry geometry, Long geometriesOMA, List<Vector2f> userDefinedUVCoordinates, BlenderContext blenderContext) {
} Material material = null;
if (shadeless) {
public void applyMaterial(Geometry geometry, Long geometriesOMA, List<Vector2f> userDefinedUVCoordinates, BlenderContext blenderContext) { material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
Material material = null;
if (shadeless) {
material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
if (!transparent) { if (!transparent) {
diffuseColor.a = 1; diffuseColor.a = 1;
} }
material.setColor("Color", diffuseColor); material.setColor("Color", diffuseColor);
} else { } else {
material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md"); material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
material.setBoolean("UseMaterialColors", Boolean.TRUE); material.setBoolean("UseMaterialColors", Boolean.TRUE);
// setting the colors // setting the colors
material.setBoolean("Minnaert", diffuseShader == DiffuseShader.MINNAERT); material.setBoolean("Minnaert", diffuseShader == DiffuseShader.MINNAERT);
if (!transparent) { if (!transparent) {
diffuseColor.a = 1; diffuseColor.a = 1;
} }
material.setColor("Diffuse", diffuseColor); material.setColor("Diffuse", diffuseColor);
material.setBoolean("WardIso", specularShader == SpecularShader.WARDISO); material.setBoolean("WardIso", specularShader == SpecularShader.WARDISO);
material.setColor("Specular", specularColor); material.setColor("Specular", specularColor);
material.setColor("Ambient", ambientColor); material.setColor("Ambient", ambientColor);
material.setFloat("Shininess", shininess); material.setFloat("Shininess", shininess);
} }
//applying textures // applying textures
if(loadedTextures != null && loadedTextures.size() > 0) { if (loadedTextures != null && loadedTextures.size() > 0) {
Entry<Number, CombinedTexture> basicUVSOwner = null; Entry<Number, CombinedTexture> basicUVSOwner = null;
for(Entry<Number, CombinedTexture> entry : loadedTextures.entrySet()) { for (Entry<Number, CombinedTexture> entry : loadedTextures.entrySet()) {
CombinedTexture combinedTexture = entry.getValue(); CombinedTexture combinedTexture = entry.getValue();
combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext); combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext);
if(basicUVSOwner == null) { if (basicUVSOwner == null) {
basicUVSOwner = entry; basicUVSOwner = entry;
} else { } else {
combinedTexture.castToUVS(basicUVSOwner.getValue(), blenderContext); combinedTexture.castToUVS(basicUVSOwner.getValue(), blenderContext);
this.setTexture(material, entry.getKey().intValue(), combinedTexture.getResultTexture()); this.setTexture(material, entry.getKey().intValue(), combinedTexture.getResultTexture());
} }
} }
if(basicUVSOwner != null) { if (basicUVSOwner != null) {
this.setTexture(material, basicUVSOwner.getKey().intValue(), basicUVSOwner.getValue().getResultTexture()); this.setTexture(material, basicUVSOwner.getKey().intValue(), basicUVSOwner.getValue().getResultTexture());
List<Vector2f> basicUVS = basicUVSOwner.getValue().getResultUVS(); List<Vector2f> basicUVS = basicUVSOwner.getValue().getResultUVS();
VertexBuffer uvCoordsBuffer = new VertexBuffer(VertexBuffer.Type.TexCoord); VertexBuffer uvCoordsBuffer = new VertexBuffer(VertexBuffer.Type.TexCoord);
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(basicUVS.toArray(new Vector2f[basicUVS.size()]))); uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(basicUVS.toArray(new Vector2f[basicUVS.size()])));
geometry.getMesh().setBuffer(uvCoordsBuffer); geometry.getMesh().setBuffer(uvCoordsBuffer);
} }
} else if(userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) { } else if (userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) {
VertexBuffer uvCoordsBuffer = new VertexBuffer(VertexBuffer.Type.TexCoord); VertexBuffer uvCoordsBuffer = new VertexBuffer(VertexBuffer.Type.TexCoord);
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(userDefinedUVCoordinates.toArray(new Vector2f[userDefinedUVCoordinates.size()])));
BufferUtils.createFloatBuffer(userDefinedUVCoordinates.toArray(new Vector2f[userDefinedUVCoordinates.size()]))); geometry.getMesh().setBuffer(uvCoordsBuffer);
geometry.getMesh().setBuffer(uvCoordsBuffer); }
}
// applying additional data
//applying additional data material.setName(name);
material.setName(name); if (vertexColor) {
if (vertexColor) { material.setBoolean(shadeless ? "VertexColor" : "UseVertexColor", true);
material.setBoolean(shadeless ? "VertexColor" : "UseVertexColor", true); }
} if (this.faceCullMode != null) {
if(this.faceCullMode != null) { material.getAdditionalRenderState().setFaceCullMode(faceCullMode);
material.getAdditionalRenderState().setFaceCullMode(faceCullMode); } else {
} else { material.getAdditionalRenderState().setFaceCullMode(blenderContext.getBlenderKey().getFaceCullMode());
material.getAdditionalRenderState().setFaceCullMode(blenderContext.getBlenderKey().getFaceCullMode()); }
}
if (transparent) { if (transparent) {
material.setTransparent(true); material.setTransparent(true);
material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
geometry.setQueueBucket(Bucket.Transparent); geometry.setQueueBucket(Bucket.Transparent);
} }
geometry.setMaterial(material); geometry.setMaterial(material);
} }
/** /**
* Sets the texture to the given material. * Sets the texture to the given material.
* *
* @param material * @param material
* the material that we add texture to * the material that we add texture to
* @param mapTo * @param mapTo
* the texture mapping type * the texture mapping type
* @param texture * @param texture
* the added texture * the added texture
*/ */
private void setTexture(Material material, int mapTo, Texture texture) { private void setTexture(Material material, int mapTo, Texture texture) {
switch (mapTo) { switch (mapTo) {
case MTEX_COL: case MTEX_COL:
material.setTexture(shadeless ? MaterialHelper.TEXTURE_TYPE_COLOR : MaterialHelper.TEXTURE_TYPE_DIFFUSE, texture); material.setTexture(shadeless ? MaterialHelper.TEXTURE_TYPE_COLOR : MaterialHelper.TEXTURE_TYPE_DIFFUSE, texture);
break; break;
case MTEX_NOR: case MTEX_NOR:
material.setTexture(MaterialHelper.TEXTURE_TYPE_NORMAL, texture); material.setTexture(MaterialHelper.TEXTURE_TYPE_NORMAL, texture);
break; break;
case MTEX_SPEC: case MTEX_SPEC:
material.setTexture(MaterialHelper.TEXTURE_TYPE_SPECULAR, texture); material.setTexture(MaterialHelper.TEXTURE_TYPE_SPECULAR, texture);
break; break;
case MTEX_EMIT: case MTEX_EMIT:
material.setTexture(MaterialHelper.TEXTURE_TYPE_GLOW, texture); material.setTexture(MaterialHelper.TEXTURE_TYPE_GLOW, texture);
break; break;
case MTEX_ALPHA: case MTEX_ALPHA:
if(!shadeless) { if (!shadeless) {
material.setTexture(MaterialHelper.TEXTURE_TYPE_ALPHA, texture); material.setTexture(MaterialHelper.TEXTURE_TYPE_ALPHA, texture);
} else { } else {
LOGGER.warning("JME does not support alpha map on unshaded material. Material name is " + name); LOGGER.warning("JME does not support alpha map on unshaded material. Material name is " + name);
} }
break; break;
default: default:
LOGGER.severe("Unknown mapping type: " + mapTo); LOGGER.severe("Unknown mapping type: " + mapTo);
} }
} }
/** /**
* @return <b>true</b> if the material has at least one generated texture and <b>false</b> otherwise * @return <b>true</b> if the material has at least one generated texture and <b>false</b> otherwise
*/ */
public boolean hasGeneratedTextures() { public boolean hasGeneratedTextures() {
if(loadedTextures != null) { if (loadedTextures != null) {
for(Entry<Number, CombinedTexture> entry : loadedTextures.entrySet()) { for (Entry<Number, CombinedTexture> entry : loadedTextures.entrySet()) {
if(entry.getValue().hasGeneratedTextures()) { if (entry.getValue().hasGeneratedTextures()) {
return true; return true;
} }
} }
} }
return false; return false;
} }
/** /**
* This method sorts the textures by their mapping type. In each group only * This method sorts the textures by their mapping type. In each group only
* textures of one type are put (either two- or three-dimensional). If the * textures of one type are put (either two- or three-dimensional). If the
* mapping type is MTEX_COL then if the texture has no alpha channel then * mapping type is MTEX_COL then if the texture has no alpha channel then
* all textures before it are discarded and will not be loaded and merged * all textures before it are discarded and will not be loaded and merged
* because texture with no alpha will cover them anyway. * because texture with no alpha will cover them anyway.
* *
* @return a map with sorted and filtered textures * @return a map with sorted and filtered textures
*/ */
private Map<Number, List<TextureData>> sortAndFilterTextures(List<TextureData> textures) { private Map<Number, List<TextureData>> sortAndFilterTextures(List<TextureData> textures) {
int[] mappings = new int[] { MTEX_COL, MTEX_NOR, MTEX_EMIT, MTEX_SPEC, MTEX_ALPHA }; int[] mappings = new int[] { MTEX_COL, MTEX_NOR, MTEX_EMIT, MTEX_SPEC, MTEX_ALPHA };
Map<Number, List<TextureData>> result = new HashMap<Number, List<TextureData>>(); Map<Number, List<TextureData>> result = new HashMap<Number, List<TextureData>>();
for (TextureData data : textures) { for (TextureData data : textures) {
Number mapto = (Number) data.mtex.getFieldValue("mapto"); Number mapto = (Number) data.mtex.getFieldValue("mapto");
for (int i = 0; i < mappings.length; ++i) { for (int i = 0; i < mappings.length; ++i) {
if((mappings[i] & mapto.intValue()) != 0) { if ((mappings[i] & mapto.intValue()) != 0) {
List<TextureData> datas = result.get(mappings[i]); List<TextureData> datas = result.get(mappings[i]);
if (datas == null) { if (datas == null) {
datas = new ArrayList<TextureData>(); datas = new ArrayList<TextureData>();
result.put(mappings[i], datas); result.put(mappings[i], datas);
} }
datas.add(data); datas.add(data);
} }
} }
} }
return result; return result;
} }
/** /**
* This method sets the face cull mode. * This method sets the face cull mode.
* @param faceCullMode the face cull mode * @param faceCullMode
*/ * the face cull mode
public void setFaceCullMode(FaceCullMode faceCullMode) { */
this.faceCullMode = faceCullMode; public void setFaceCullMode(FaceCullMode faceCullMode) {
} this.faceCullMode = faceCullMode;
}
/**
* @return the face cull mode /**
*/ * @return the face cull mode
public FaceCullMode getFaceCullMode() { */
return faceCullMode; public FaceCullMode getFaceCullMode() {
} return faceCullMode;
}
/**
* This method returns the diffuse color. /**
* * This method returns the diffuse color.
* @param materialStructure the material structure *
* @param diffuseShader the diffuse shader * @param materialStructure
* @return the diffuse color * the material structure
*/ * @param diffuseShader
private ColorRGBA readDiffuseColor(Structure materialStructure, DiffuseShader diffuseShader) { * the diffuse shader
// bitwise 'or' of all textures mappings * @return the diffuse color
int commonMapto = ((Number) materialStructure.getFieldValue("mapto")).intValue(); */
private ColorRGBA readDiffuseColor(Structure materialStructure, DiffuseShader diffuseShader) {
// diffuse color // bitwise 'or' of all textures mappings
float r = ((Number) materialStructure.getFieldValue("r")).floatValue(); int commonMapto = ((Number) materialStructure.getFieldValue("mapto")).intValue();
float g = ((Number) materialStructure.getFieldValue("g")).floatValue();
float b = ((Number) materialStructure.getFieldValue("b")).floatValue(); // diffuse color
float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue(); float r = ((Number) materialStructure.getFieldValue("r")).floatValue();
if ((commonMapto & 0x01) == 0x01) {// Col float g = ((Number) materialStructure.getFieldValue("g")).floatValue();
return new ColorRGBA(r, g, b, alpha); float b = ((Number) materialStructure.getFieldValue("b")).floatValue();
} else { float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
switch (diffuseShader) { if ((commonMapto & 0x01) == 0x01) {// Col
case FRESNEL: return new ColorRGBA(r, g, b, alpha);
case ORENNAYAR: } else {
case TOON: switch (diffuseShader) {
break;// TODO: find what is the proper modification case FRESNEL:
case MINNAERT: case ORENNAYAR:
case LAMBERT:// TODO: check if that is correct case TOON:
float ref = ((Number) materialStructure.getFieldValue("ref")).floatValue(); break;// TODO: find what is the proper modification
r *= ref; case MINNAERT:
g *= ref; case LAMBERT:// TODO: check if that is correct
b *= ref; float ref = ((Number) materialStructure.getFieldValue("ref")).floatValue();
break; r *= ref;
default: g *= ref;
throw new IllegalStateException("Unknown diffuse shader type: " + diffuseShader.toString()); b *= ref;
} break;
return new ColorRGBA(r, g, b, alpha); default:
} throw new IllegalStateException("Unknown diffuse shader type: " + diffuseShader.toString());
} }
return new ColorRGBA(r, g, b, alpha);
/** }
* This method returns a specular color used by the material. }
*
* @param materialStructure /**
* the material structure filled with data * This method returns a specular color used by the material.
* @return a specular color used by the material *
*/ * @param materialStructure
private ColorRGBA readSpecularColor(Structure materialStructure, SpecularShader specularShader) { * the material structure filled with data
float r = ((Number) materialStructure.getFieldValue("specr")).floatValue(); * @return a specular color used by the material
float g = ((Number) materialStructure.getFieldValue("specg")).floatValue(); */
float b = ((Number) materialStructure.getFieldValue("specb")).floatValue(); private ColorRGBA readSpecularColor(Structure materialStructure, SpecularShader specularShader) {
float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue(); float r = ((Number) materialStructure.getFieldValue("specr")).floatValue();
switch (specularShader) { float g = ((Number) materialStructure.getFieldValue("specg")).floatValue();
case BLINN: float b = ((Number) materialStructure.getFieldValue("specb")).floatValue();
case COOKTORRENCE: float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
case TOON: switch (specularShader) {
case WARDISO:// TODO: find what is the proper modification case BLINN:
break; case COOKTORRENCE:
case PHONG:// TODO: check if that is correct case TOON:
float spec = ((Number) materialStructure.getFieldValue("spec")).floatValue(); case WARDISO:// TODO: find what is the proper modification
r *= spec * 0.5f; break;
g *= spec * 0.5f; case PHONG:// TODO: check if that is correct
b *= spec * 0.5f; float spec = ((Number) materialStructure.getFieldValue("spec")).floatValue();
break; r *= spec * 0.5f;
default: g *= spec * 0.5f;
throw new IllegalStateException("Unknown specular shader type: " + specularShader.toString()); b *= spec * 0.5f;
} break;
return new ColorRGBA(r, g, b, alpha); default:
} throw new IllegalStateException("Unknown specular shader type: " + specularShader.toString());
}
private static class TextureData { return new ColorRGBA(r, g, b, alpha);
Structure mtex; }
Structure textureStructure;
int uvCoordinatesType; private static class TextureData {
int projectionType; Structure mtex;
} Structure textureStructure;
int uvCoordinatesType;
int projectionType;
}
} }

@ -57,410 +57,411 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
public class MaterialHelper extends AbstractBlenderHelper { public class MaterialHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(MaterialHelper.class.getName()); private static final Logger LOGGER = Logger.getLogger(MaterialHelper.class.getName());
protected static final float DEFAULT_SHININESS = 20.0f; protected static final float DEFAULT_SHININESS = 20.0f;
public static final String TEXTURE_TYPE_COLOR = "ColorMap"; public static final String TEXTURE_TYPE_COLOR = "ColorMap";
public static final String TEXTURE_TYPE_DIFFUSE = "DiffuseMap"; public static final String TEXTURE_TYPE_DIFFUSE = "DiffuseMap";
public static final String TEXTURE_TYPE_NORMAL = "NormalMap"; public static final String TEXTURE_TYPE_NORMAL = "NormalMap";
public static final String TEXTURE_TYPE_SPECULAR = "SpecularMap"; public static final String TEXTURE_TYPE_SPECULAR = "SpecularMap";
public static final String TEXTURE_TYPE_GLOW = "GlowMap"; public static final String TEXTURE_TYPE_GLOW = "GlowMap";
public static final String TEXTURE_TYPE_ALPHA = "AlphaMap"; public static final String TEXTURE_TYPE_ALPHA = "AlphaMap";
public static final Integer ALPHA_MASK_NONE = Integer.valueOf(0); public static final Integer ALPHA_MASK_NONE = Integer.valueOf(0);
public static final Integer ALPHA_MASK_CIRCLE = Integer.valueOf(1); public static final Integer ALPHA_MASK_CIRCLE = Integer.valueOf(1);
public static final Integer ALPHA_MASK_CONE = Integer.valueOf(2); public static final Integer ALPHA_MASK_CONE = Integer.valueOf(2);
public static final Integer ALPHA_MASK_HYPERBOLE = Integer.valueOf(3); public static final Integer ALPHA_MASK_HYPERBOLE = Integer.valueOf(3);
protected final Map<Integer, IAlphaMask> alphaMasks = new HashMap<Integer, IAlphaMask>(); protected final Map<Integer, IAlphaMask> alphaMasks = new HashMap<Integer, IAlphaMask>();
/** /**
* The type of the material's diffuse shader. * The type of the material's diffuse shader.
*/ */
public static enum DiffuseShader { public static enum DiffuseShader {
LAMBERT, ORENNAYAR, TOON, MINNAERT, FRESNEL LAMBERT, ORENNAYAR, TOON, MINNAERT, FRESNEL
} }
/** /**
* The type of the material's specular shader. * The type of the material's specular shader.
*/ */
public static enum SpecularShader { public static enum SpecularShader {
COOKTORRENCE, PHONG, BLINN, TOON, WARDISO COOKTORRENCE, PHONG, BLINN, TOON, WARDISO
} }
/** /**
* This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
* versions. * versions.
* *
* @param blenderVersion * @param blenderVersion
* the version read from the blend file * the version read from the blend file
* @param fixUpAxis * @param fixUpAxis
* a variable that indicates if the Y asxis is the UP axis or not * a variable that indicates if the Y asxis is the UP axis or not
*/ */
public MaterialHelper(String blenderVersion, boolean fixUpAxis) { public MaterialHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, false); super(blenderVersion, false);
// setting alpha masks // setting alpha masks
alphaMasks.put(ALPHA_MASK_NONE, new IAlphaMask() { alphaMasks.put(ALPHA_MASK_NONE, new IAlphaMask() {
public void setImageSize(int width, int height) {} public void setImageSize(int width, int height) {
}
public byte getAlpha(float x, float y) {
return (byte) 255; public byte getAlpha(float x, float y) {
} return (byte) 255;
}); }
alphaMasks.put(ALPHA_MASK_CIRCLE, new IAlphaMask() { });
private float r; alphaMasks.put(ALPHA_MASK_CIRCLE, new IAlphaMask() {
private float[] center; private float r;
private float[] center;
public void setImageSize(int width, int height) {
r = Math.min(width, height) * 0.5f; public void setImageSize(int width, int height) {
center = new float[] { width * 0.5f, height * 0.5f }; r = Math.min(width, height) * 0.5f;
} center = new float[] { width * 0.5f, height * 0.5f };
}
public byte getAlpha(float x, float y) {
float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))); public byte getAlpha(float x, float y) {
return (byte) (d >= r ? 0 : 255); float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1])));
} return (byte) (d >= r ? 0 : 255);
}); }
alphaMasks.put(ALPHA_MASK_CONE, new IAlphaMask() { });
private float r; alphaMasks.put(ALPHA_MASK_CONE, new IAlphaMask() {
private float[] center; private float r;
private float[] center;
public void setImageSize(int width, int height) {
r = Math.min(width, height) * 0.5f; public void setImageSize(int width, int height) {
center = new float[] { width * 0.5f, height * 0.5f }; r = Math.min(width, height) * 0.5f;
} center = new float[] { width * 0.5f, height * 0.5f };
}
public byte getAlpha(float x, float y) {
float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))); public byte getAlpha(float x, float y) {
return (byte) (d >= r ? 0 : -255.0f * d / r + 255.0f); float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1])));
} return (byte) (d >= r ? 0 : -255.0f * d / r + 255.0f);
}); }
alphaMasks.put(ALPHA_MASK_HYPERBOLE, new IAlphaMask() { });
private float r; alphaMasks.put(ALPHA_MASK_HYPERBOLE, new IAlphaMask() {
private float[] center; private float r;
private float[] center;
public void setImageSize(int width, int height) {
r = Math.min(width, height) * 0.5f; public void setImageSize(int width, int height) {
center = new float[] { width * 0.5f, height * 0.5f }; r = Math.min(width, height) * 0.5f;
} center = new float[] { width * 0.5f, height * 0.5f };
}
public byte getAlpha(float x, float y) {
float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))) / r; public byte getAlpha(float x, float y) {
return d >= 1.0f ? 0 : (byte) ((-FastMath.sqrt((2.0f - d) * d) + 1.0f) * 255.0f); float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))) / r;
} return d >= 1.0f ? 0 : (byte) ((-FastMath.sqrt((2.0f - d) * d) + 1.0f) * 255.0f);
}); }
} });
}
/**
* This method converts the material structure to jme Material. /**
* @param structure * This method converts the material structure to jme Material.
* structure with material data * @param structure
* @param blenderContext * structure with material data
* the blender context * @param blenderContext
* @return jme material * the blender context
* @throws BlenderFileException * @return jme material
* an exception is throw when problems with blend file occur * @throws BlenderFileException
*/ * an exception is throw when problems with blend file occur
public MaterialContext toMaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException { */
LOGGER.log(Level.FINE, "Loading material."); public MaterialContext toMaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); LOGGER.log(Level.FINE, "Loading material.");
if (result != null) { MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
return result; if (result != null) {
} return result;
}
result = new MaterialContext(structure, blenderContext);
LOGGER.log(Level.FINE, "Material''s name: {0}", result.name); result = new MaterialContext(structure, blenderContext);
blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, result); LOGGER.log(Level.FINE, "Material''s name: {0}", result.name);
return result; blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, result);
} return result;
}
/**
* This method returns a material similar to the one given but without textures. If the material has no textures it is not cloned but /**
* returned itself. * This method returns a material similar to the one given but without textures. If the material has no textures it is not cloned but
* * returned itself.
* @param material *
* a material to be cloned without textures * @param material
* @param imageType * a material to be cloned without textures
* type of image defined by blender; the constants are defined in TextureHelper * @param imageType
* @return material without textures of a specified type * type of image defined by blender; the constants are defined in TextureHelper
*/ * @return material without textures of a specified type
public Material getNonTexturedMaterial(Material material, int imageType) { */
String[] textureParamNames = new String[] { TEXTURE_TYPE_DIFFUSE, TEXTURE_TYPE_NORMAL, TEXTURE_TYPE_GLOW, TEXTURE_TYPE_SPECULAR, TEXTURE_TYPE_ALPHA }; public Material getNonTexturedMaterial(Material material, int imageType) {
Map<String, Texture> textures = new HashMap<String, Texture>(textureParamNames.length); String[] textureParamNames = new String[] { TEXTURE_TYPE_DIFFUSE, TEXTURE_TYPE_NORMAL, TEXTURE_TYPE_GLOW, TEXTURE_TYPE_SPECULAR, TEXTURE_TYPE_ALPHA };
for (String textureParamName : textureParamNames) { Map<String, Texture> textures = new HashMap<String, Texture>(textureParamNames.length);
MatParamTexture matParamTexture = material.getTextureParam(textureParamName); for (String textureParamName : textureParamNames) {
if (matParamTexture != null) { MatParamTexture matParamTexture = material.getTextureParam(textureParamName);
textures.put(textureParamName, matParamTexture.getTextureValue()); if (matParamTexture != null) {
} textures.put(textureParamName, matParamTexture.getTextureValue());
} }
if (textures.isEmpty()) { }
return material; if (textures.isEmpty()) {
} else { return material;
// clear all textures first so that wo de not waste resources cloning them } else {
for (Entry<String, Texture> textureParamName : textures.entrySet()) { // clear all textures first so that wo de not waste resources cloning them
String name = textureParamName.getValue().getName(); for (Entry<String, Texture> textureParamName : textures.entrySet()) {
try { String name = textureParamName.getValue().getName();
int type = Integer.parseInt(name); try {
if (type == imageType) { int type = Integer.parseInt(name);
material.clearParam(textureParamName.getKey()); if (type == imageType) {
} material.clearParam(textureParamName.getKey());
} catch (NumberFormatException e) { }
LOGGER.log(Level.WARNING, "The name of the texture does not contain the texture type value! {0} will not be removed!", name); } catch (NumberFormatException e) {
} LOGGER.log(Level.WARNING, "The name of the texture does not contain the texture type value! {0} will not be removed!", name);
} }
Material result = material.clone(); }
// put the textures back in place Material result = material.clone();
for (Entry<String, Texture> textureEntry : textures.entrySet()) { // put the textures back in place
material.setTexture(textureEntry.getKey(), textureEntry.getValue()); for (Entry<String, Texture> textureEntry : textures.entrySet()) {
} material.setTexture(textureEntry.getKey(), textureEntry.getValue());
return result; }
} return result;
} }
}
/**
* This method converts the given material into particles-usable material. /**
* The texture and glow color are being copied. * This method converts the given material into particles-usable material.
* The method assumes it receives the Lighting type of material. * The texture and glow color are being copied.
* @param material * The method assumes it receives the Lighting type of material.
* the source material * @param material
* @param blenderContext * the source material
* the blender context * @param blenderContext
* @return material converted into particles-usable material * the blender context
*/ * @return material converted into particles-usable material
public Material getParticlesMaterial(Material material, Integer alphaMaskIndex, BlenderContext blenderContext) { */
Material result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); public Material getParticlesMaterial(Material material, Integer alphaMaskIndex, BlenderContext blenderContext) {
Material result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
// copying texture
MatParam diffuseMap = material.getParam("DiffuseMap"); // copying texture
if (diffuseMap != null) { MatParam diffuseMap = material.getParam("DiffuseMap");
Texture texture = ((Texture) diffuseMap.getValue()).clone(); if (diffuseMap != null) {
Texture texture = ((Texture) diffuseMap.getValue()).clone();
// applying alpha mask to the texture
Image image = texture.getImage(); // applying alpha mask to the texture
ByteBuffer sourceBB = image.getData(0); Image image = texture.getImage();
sourceBB.rewind(); ByteBuffer sourceBB = image.getData(0);
int w = image.getWidth(); sourceBB.rewind();
int h = image.getHeight(); int w = image.getWidth();
ByteBuffer bb = BufferUtils.createByteBuffer(w * h * 4); int h = image.getHeight();
IAlphaMask iAlphaMask = alphaMasks.get(alphaMaskIndex); ByteBuffer bb = BufferUtils.createByteBuffer(w * h * 4);
iAlphaMask.setImageSize(w, h); IAlphaMask iAlphaMask = alphaMasks.get(alphaMaskIndex);
iAlphaMask.setImageSize(w, h);
for (int x = 0; x < w; ++x) {
for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) {
bb.put(sourceBB.get()); for (int y = 0; y < h; ++y) {
bb.put(sourceBB.get()); bb.put(sourceBB.get());
bb.put(sourceBB.get()); bb.put(sourceBB.get());
bb.put(iAlphaMask.getAlpha(x, y)); bb.put(sourceBB.get());
} bb.put(iAlphaMask.getAlpha(x, y));
} }
}
image = new Image(Format.RGBA8, w, h, bb);
texture.setImage(image); image = new Image(Format.RGBA8, w, h, bb);
texture.setImage(image);
result.setTextureParam("Texture", VarType.Texture2D, texture);
} result.setTextureParam("Texture", VarType.Texture2D, texture);
}
// copying glow color
MatParam glowColor = material.getParam("GlowColor"); // copying glow color
if (glowColor != null) { MatParam glowColor = material.getParam("GlowColor");
ColorRGBA color = (ColorRGBA) glowColor.getValue(); if (glowColor != null) {
result.setParam("GlowColor", VarType.Vector3, color); ColorRGBA color = (ColorRGBA) glowColor.getValue();
} result.setParam("GlowColor", VarType.Vector3, color);
return result; }
} return result;
}
/**
* This method indicates if the material has any kind of texture. /**
* * This method indicates if the material has any kind of texture.
* @param material *
* the material * @param material
* @return <b>true</b> if the texture exists in the material and <B>false</b> otherwise * the material
*/ * @return <b>true</b> if the texture exists in the material and <B>false</b> otherwise
public boolean hasTexture(Material material) { */
if (material != null) { public boolean hasTexture(Material material) {
if (material.getTextureParam(TEXTURE_TYPE_ALPHA) != null) { if (material != null) {
return true; if (material.getTextureParam(TEXTURE_TYPE_ALPHA) != null) {
} return true;
if (material.getTextureParam(TEXTURE_TYPE_COLOR) != null) { }
return true; if (material.getTextureParam(TEXTURE_TYPE_COLOR) != null) {
} return true;
if (material.getTextureParam(TEXTURE_TYPE_DIFFUSE) != null) { }
return true; if (material.getTextureParam(TEXTURE_TYPE_DIFFUSE) != null) {
} return true;
if (material.getTextureParam(TEXTURE_TYPE_GLOW) != null) { }
return true; if (material.getTextureParam(TEXTURE_TYPE_GLOW) != null) {
} return true;
if (material.getTextureParam(TEXTURE_TYPE_NORMAL) != null) { }
return true; if (material.getTextureParam(TEXTURE_TYPE_NORMAL) != null) {
} return true;
if (material.getTextureParam(TEXTURE_TYPE_SPECULAR) != null) { }
return true; if (material.getTextureParam(TEXTURE_TYPE_SPECULAR) != null) {
} return true;
} }
return false; }
} return false;
}
/**
* This method indicates if the material has a texture of a specified type. /**
* * This method indicates if the material has a texture of a specified type.
* @param material *
* the material * @param material
* @param textureType * the material
* the type of the texture * @param textureType
* @return <b>true</b> if the texture exists in the material and <B>false</b> otherwise * the type of the texture
*/ * @return <b>true</b> if the texture exists in the material and <B>false</b> otherwise
public boolean hasTexture(Material material, String textureType) { */
if (material != null) { public boolean hasTexture(Material material, String textureType) {
return material.getTextureParam(textureType) != null; if (material != null) {
} return material.getTextureParam(textureType) != null;
return false; }
} return false;
}
/**
* This method returns the table of materials connected to the specified structure. The given structure can be of any type (ie. mesh or /**
* curve) but needs to have 'mat' field/ * This method returns the table of materials connected to the specified structure. The given structure can be of any type (ie. mesh or
* * curve) but needs to have 'mat' field/
* @param structureWithMaterials *
* the structure containing the mesh data * @param structureWithMaterials
* @param blenderContext * the structure containing the mesh data
* the blender context * @param blenderContext
* @return a list of vertices colors, each color belongs to a single vertex * the blender context
* @throws BlenderFileException * @return a list of vertices colors, each color belongs to a single vertex
* this exception is thrown when the blend file structure is somehow invalid or corrupted * @throws BlenderFileException
*/ * this exception is thrown when the blend file structure is somehow invalid or corrupted
public MaterialContext[] getMaterials(Structure structureWithMaterials, BlenderContext blenderContext) throws BlenderFileException { */
Pointer ppMaterials = (Pointer) structureWithMaterials.getFieldValue("mat"); public MaterialContext[] getMaterials(Structure structureWithMaterials, BlenderContext blenderContext) throws BlenderFileException {
MaterialContext[] materials = null; Pointer ppMaterials = (Pointer) structureWithMaterials.getFieldValue("mat");
if (ppMaterials.isNotNull()) { MaterialContext[] materials = null;
List<Structure> materialStructures = ppMaterials.fetchData(blenderContext.getInputStream()); if (ppMaterials.isNotNull()) {
if (materialStructures != null && materialStructures.size() > 0) { List<Structure> materialStructures = ppMaterials.fetchData(blenderContext.getInputStream());
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); if (materialStructures != null && materialStructures.size() > 0) {
materials = new MaterialContext[materialStructures.size()]; MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
int i = 0; materials = new MaterialContext[materialStructures.size()];
for (Structure s : materialStructures) { int i = 0;
materials[i++] = s == null ? null : materialHelper.toMaterialContext(s, blenderContext); for (Structure s : materialStructures) {
} materials[i++] = s == null ? null : materialHelper.toMaterialContext(s, blenderContext);
} }
} }
return materials; }
} return materials;
}
/**
* This method converts rgb values to hsv values. /**
* * This method converts rgb values to hsv values.
* @param r *
* red value of the color * @param r
* @param g * red value of the color
* green value of the color * @param g
* @param b * green value of the color
* blue value of the color * @param b
* @param hsv * blue value of the color
* hsv values of a color (this table contains the result of the transformation) * @param hsv
*/ * hsv values of a color (this table contains the result of the transformation)
public void rgbToHsv(float r, float g, float b, float[] hsv) { */
float cmax = r; public void rgbToHsv(float r, float g, float b, float[] hsv) {
float cmin = r; float cmax = r;
cmax = g > cmax ? g : cmax; float cmin = r;
cmin = g < cmin ? g : cmin; cmax = g > cmax ? g : cmax;
cmax = b > cmax ? b : cmax; cmin = g < cmin ? g : cmin;
cmin = b < cmin ? b : cmin; cmax = b > cmax ? b : cmax;
cmin = b < cmin ? b : cmin;
hsv[2] = cmax; /* value */
if (cmax != 0.0) { hsv[2] = cmax; /* value */
hsv[1] = (cmax - cmin) / cmax; if (cmax != 0.0) {
} else { hsv[1] = (cmax - cmin) / cmax;
hsv[1] = 0.0f; } else {
hsv[0] = 0.0f; hsv[1] = 0.0f;
} hsv[0] = 0.0f;
if (hsv[1] == 0.0) { }
hsv[0] = -1.0f; if (hsv[1] == 0.0) {
} else { hsv[0] = -1.0f;
float cdelta = cmax - cmin; } else {
float rc = (cmax - r) / cdelta; float cdelta = cmax - cmin;
float gc = (cmax - g) / cdelta; float rc = (cmax - r) / cdelta;
float bc = (cmax - b) / cdelta; float gc = (cmax - g) / cdelta;
if (r == cmax) { float bc = (cmax - b) / cdelta;
hsv[0] = bc - gc; if (r == cmax) {
} else if (g == cmax) { hsv[0] = bc - gc;
hsv[0] = 2.0f + rc - bc; } else if (g == cmax) {
} else { hsv[0] = 2.0f + rc - bc;
hsv[0] = 4.0f + gc - rc; } else {
} hsv[0] = 4.0f + gc - rc;
hsv[0] *= 60.0f; }
if (hsv[0] < 0.0f) { hsv[0] *= 60.0f;
hsv[0] += 360.0f; if (hsv[0] < 0.0f) {
} hsv[0] += 360.0f;
} }
}
hsv[0] /= 360.0f;
if (hsv[0] < 0.0f) { hsv[0] /= 360.0f;
hsv[0] = 0.0f; if (hsv[0] < 0.0f) {
} hsv[0] = 0.0f;
} }
}
/**
* This method converts rgb values to hsv values. /**
* * This method converts rgb values to hsv values.
* @param h *
* hue * @param h
* @param s * hue
* saturation * @param s
* @param v * saturation
* value * @param v
* @param rgb * value
* rgb result vector (should have 3 elements) * @param rgb
*/ * rgb result vector (should have 3 elements)
public void hsvToRgb(float h, float s, float v, float[] rgb) { */
h *= 360.0f; public void hsvToRgb(float h, float s, float v, float[] rgb) {
if (s == 0.0) { h *= 360.0f;
rgb[0] = rgb[1] = rgb[2] = v; if (s == 0.0) {
} else { rgb[0] = rgb[1] = rgb[2] = v;
if (h == 360) { } else {
h = 0; if (h == 360) {
} else { h = 0;
h /= 60; } else {
} h /= 60;
int i = (int) Math.floor(h); }
float f = h - i; int i = (int) Math.floor(h);
float p = v * (1.0f - s); float f = h - i;
float q = v * (1.0f - s * f); float p = v * (1.0f - s);
float t = v * (1.0f - s * (1.0f - f)); float q = v * (1.0f - s * f);
switch (i) { float t = v * (1.0f - s * (1.0f - f));
case 0: switch (i) {
rgb[0] = v; case 0:
rgb[1] = t; rgb[0] = v;
rgb[2] = p; rgb[1] = t;
break; rgb[2] = p;
case 1: break;
rgb[0] = q; case 1:
rgb[1] = v; rgb[0] = q;
rgb[2] = p; rgb[1] = v;
break; rgb[2] = p;
case 2: break;
rgb[0] = p; case 2:
rgb[1] = v; rgb[0] = p;
rgb[2] = t; rgb[1] = v;
break; rgb[2] = t;
case 3: break;
rgb[0] = p; case 3:
rgb[1] = q; rgb[0] = p;
rgb[2] = v; rgb[1] = q;
break; rgb[2] = v;
case 4: break;
rgb[0] = t; case 4:
rgb[1] = p; rgb[0] = t;
rgb[2] = v; rgb[1] = p;
break; rgb[2] = v;
case 5: break;
rgb[0] = v; case 5:
rgb[1] = p; rgb[0] = v;
rgb[2] = q; rgb[1] = p;
break; rgb[2] = q;
} break;
} }
} }
}
@Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { @Override
return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0; public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
} return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0;
}
} }

@ -12,85 +12,103 @@ import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
/*package*/ class MeshBuilder { /*package*/class MeshBuilder {
private static final Logger LOGGER = Logger.getLogger(MeshBuilder.class.getName()); private static final Logger LOGGER = Logger.getLogger(MeshBuilder.class.getName());
/** An array of reference vertices. */ /** An array of reference vertices. */
private Vector3f[][] verticesAndNormals; private Vector3f[][] verticesAndNormals;
/** An list of vertices colors. */ /** An list of vertices colors. */
private List<byte[]> verticesColors; private List<byte[]> verticesColors;
/** A variable that indicates if the model uses generated textures. */ /** A variable that indicates if the model uses generated textures. */
private boolean usesGeneratedTextures; private boolean usesGeneratedTextures;
/** This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList' /**
positions (it simply tells which vertex is referenced where in the result list). */ * This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList'
* positions (it simply tells which vertex is referenced where in the result list).
*/
private Map<Integer, Map<Integer, List<Integer>>> globalVertexReferenceMap; private Map<Integer, Map<Integer, List<Integer>>> globalVertexReferenceMap;
/** A map between vertex index and its UV coordinates. */ /** A map between vertex index and its UV coordinates. */
private Map<Integer, Vector2f> uvsMap = new HashMap<Integer, Vector2f>(); private Map<Integer, Vector2f> uvsMap = new HashMap<Integer, Vector2f>();
/** The following map sorts vertices by material number (because in jme Mesh can have only one material). */ /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */
private Map<Integer, List<Vector3f>> normalMap = new HashMap<Integer, List<Vector3f>>(); private Map<Integer, List<Vector3f>> normalMap = new HashMap<Integer, List<Vector3f>>();
/** The following map sorts vertices by material number (because in jme Mesh can have only one material). */ /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */
private Map<Integer, List<Vector3f>> vertexMap = new HashMap<Integer, List<Vector3f>>(); private Map<Integer, List<Vector3f>> vertexMap = new HashMap<Integer, List<Vector3f>>();
/** The following map sorts vertices colors by material number (because in jme Mesh can have only one material). */ /** The following map sorts vertices colors by material number (because in jme Mesh can have only one material). */
private Map<Integer, List<byte[]>> vertexColorsMap = new HashMap<Integer, List<byte[]>>(); private Map<Integer, List<byte[]>> vertexColorsMap = new HashMap<Integer, List<byte[]>>();
/** The following map sorts indexes by material number (because in jme Mesh can have only one material). */ /** The following map sorts indexes by material number (because in jme Mesh can have only one material). */
private Map<Integer, List<Integer>> indexMap = new HashMap<Integer, List<Integer>>(); private Map<Integer, List<Integer>> indexMap = new HashMap<Integer, List<Integer>>();
/** A map between material number and UV coordinates of mesh that has this material applied. */ /** A map between material number and UV coordinates of mesh that has this material applied. */
private Map<Integer, List<Vector2f>> uvCoordinates = new HashMap<Integer, List<Vector2f>>();//<material_number; list of uv coordinates for mesh's vertices> private Map<Integer, List<Vector2f>> uvCoordinates = new HashMap<Integer, List<Vector2f>>(); // <material_number; list of uv coordinates for mesh's vertices>
/** /**
* Constructor. Stores the given array (not copying it). * Constructor. Stores the given array (not copying it).
* The second argument describes if the model uses generated textures. If yes then no vertex amount optimisation is applied. * The second argument describes if the model uses generated textures. If yes then no vertex amount optimisation is applied.
* The amount of vertices is always faceCount * 3. * The amount of vertices is always faceCount * 3.
* @param verticesAndNormals the reference vertices and normals array * @param verticesAndNormals
* @param usesGeneratedTextures a variable that indicates if the model uses generated textures or not * the reference vertices and normals array
* @param usesGeneratedTextures
* a variable that indicates if the model uses generated textures or not
*/ */
public MeshBuilder(Vector3f[][] verticesAndNormals, List<byte[]> verticesColors, boolean usesGeneratedTextures) { public MeshBuilder(Vector3f[][] verticesAndNormals, List<byte[]> verticesColors, boolean usesGeneratedTextures) {
this.verticesAndNormals = verticesAndNormals; this.verticesAndNormals = verticesAndNormals;
this.verticesColors = verticesColors; this.verticesColors = verticesColors;
this.usesGeneratedTextures = usesGeneratedTextures; this.usesGeneratedTextures = usesGeneratedTextures;
globalVertexReferenceMap = new HashMap<Integer, Map<Integer, List<Integer>>>(verticesAndNormals.length); globalVertexReferenceMap = new HashMap<Integer, Map<Integer, List<Integer>>>(verticesAndNormals.length);
} }
/** /**
* This method adds a point to the mesh. * This method adds a point to the mesh.
* @param coordinates the coordinates of the point * @param coordinates
* @param normal the point's normal vector * the coordinates of the point
* @param materialNumber the material number for this point * @param normal
*/ * the point's normal vector
public void appendPoint(Vector3f coordinates, Vector3f normal, int materialNumber) { * @param materialNumber
LOGGER.warning("Appending single point not yet supported!");//TODO * the material number for this point
} */
public void appendPoint(Vector3f coordinates, Vector3f normal, int materialNumber) {
/** LOGGER.warning("Appending single point not yet supported!");// TODO
* This method adds a line to the mesh. }
* @param v1 index of the 1'st vertex from the reference vertex table
* @param v2 index of the 2'nd vertex from the reference vertex table /**
* @param smooth indicates if this face should have smooth shading or flat shading * This method adds a line to the mesh.
*/ * @param v1
public void appendEdge(int v1, int v2, boolean smooth) { * index of the 1'st vertex from the reference vertex table
LOGGER.warning("Appending single line not yet supported!");//TODO * @param v2
} * index of the 2'nd vertex from the reference vertex table
* @param smooth
/** * indicates if this face should have smooth shading or flat shading
* This method adds a face to the mesh. */
* @param v1 index of the 1'st vertex from the reference vertex table public void appendEdge(int v1, int v2, boolean smooth) {
* @param v2 index of the 2'nd vertex from the reference vertex table LOGGER.warning("Appending single line not yet supported!");// TODO
* @param v3 index of the 3'rd vertex from the reference vertex table }
* @param smooth indicates if this face should have smooth shading or flat shading
* @param materialNumber the material number for this face /**
* @param uvs a 3-element array of vertices UV coordinates * This method adds a face to the mesh.
* @param quad indicates if the appended face is a part of a quad face (used for creating vertex colors buffer) * @param v1
* @param faceIndex the face index (used for creating vertex colors buffer) * index of the 1'st vertex from the reference vertex table
*/ * @param v2
public void appendFace(int v1, int v2, int v3, boolean smooth, int materialNumber, Vector2f[] uvs, boolean quad, int faceIndex) { * index of the 2'nd vertex from the reference vertex table
if(uvs != null && uvs.length != 3) { * @param v3
throw new IllegalArgumentException("UV coordinates must be a 3-element array!"); * index of the 3'rd vertex from the reference vertex table
} * @param smooth
* indicates if this face should have smooth shading or flat shading
//getting the required lists * @param materialNumber
List<Integer> indexList = indexMap.get(materialNumber); * the material number for this face
* @param uvs
* a 3-element array of vertices UV coordinates
* @param quad
* indicates if the appended face is a part of a quad face (used for creating vertex colors buffer)
* @param faceIndex
* the face index (used for creating vertex colors buffer)
*/
public void appendFace(int v1, int v2, int v3, boolean smooth, int materialNumber, Vector2f[] uvs, boolean quad, int faceIndex) {
if (uvs != null && uvs.length != 3) {
throw new IllegalArgumentException("UV coordinates must be a 3-element array!");
}
// getting the required lists
List<Integer> indexList = indexMap.get(materialNumber);
if (indexList == null) { if (indexList == null) {
indexList = new ArrayList<Integer>(); indexList = new ArrayList<Integer>();
indexMap.put(materialNumber, indexList); indexMap.put(materialNumber, indexList);
@ -102,187 +120,187 @@ import com.jme3.util.BufferUtils;
} }
List<byte[]> vertexColorsList = vertexColorsMap != null ? vertexColorsMap.get(materialNumber) : null; List<byte[]> vertexColorsList = vertexColorsMap != null ? vertexColorsMap.get(materialNumber) : null;
int[] vertexColorIndex = new int[] { 0, 1, 2 }; int[] vertexColorIndex = new int[] { 0, 1, 2 };
if(vertexColorsList == null && vertexColorsMap != null) { if (vertexColorsList == null && vertexColorsMap != null) {
vertexColorsList = new ArrayList<byte[]>(); vertexColorsList = new ArrayList<byte[]>();
vertexColorsMap.put(materialNumber, vertexColorsList); vertexColorsMap.put(materialNumber, vertexColorsList);
} }
List<Vector3f> normalList = normalMap.get(materialNumber); List<Vector3f> normalList = normalMap.get(materialNumber);
if (normalList == null) { if (normalList == null) {
normalList = new ArrayList<Vector3f>(); normalList = new ArrayList<Vector3f>();
normalMap.put(materialNumber, normalList); normalMap.put(materialNumber, normalList);
} }
Map<Integer, List<Integer>> vertexReferenceMap = globalVertexReferenceMap.get(materialNumber); Map<Integer, List<Integer>> vertexReferenceMap = globalVertexReferenceMap.get(materialNumber);
if(vertexReferenceMap == null) { if (vertexReferenceMap == null) {
vertexReferenceMap = new HashMap<Integer, List<Integer>>(); vertexReferenceMap = new HashMap<Integer, List<Integer>>();
globalVertexReferenceMap.put(materialNumber, vertexReferenceMap); globalVertexReferenceMap.put(materialNumber, vertexReferenceMap);
} }
List<Vector2f> uvCoordinatesList = null; List<Vector2f> uvCoordinatesList = null;
if(uvs != null) { if (uvs != null) {
uvCoordinatesList = uvCoordinates.get(Integer.valueOf(materialNumber)); uvCoordinatesList = uvCoordinates.get(Integer.valueOf(materialNumber));
if(uvCoordinatesList == null) { if (uvCoordinatesList == null) {
uvCoordinatesList = new ArrayList<Vector2f>(); uvCoordinatesList = new ArrayList<Vector2f>();
uvCoordinates.put(Integer.valueOf(materialNumber), uvCoordinatesList); uvCoordinates.put(Integer.valueOf(materialNumber), uvCoordinatesList);
} }
} }
faceIndex *= 4; faceIndex *= 4;
if(quad) { if (quad) {
vertexColorIndex[1] = 2; vertexColorIndex[1] = 2;
vertexColorIndex[2] = 3; vertexColorIndex[2] = 3;
} }
//creating faces // creating faces
Integer[] index = new Integer[] {v1, v2, v3}; Integer[] index = new Integer[] { v1, v2, v3 };
if(smooth && !usesGeneratedTextures) { if (smooth && !usesGeneratedTextures) {
for (int i = 0; i < 3; ++i) { for (int i = 0; i < 3; ++i) {
if(!vertexReferenceMap.containsKey(index[i])) { if (!vertexReferenceMap.containsKey(index[i])) {
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
vertexList.add(verticesAndNormals[index[i]][0]); vertexList.add(verticesAndNormals[index[i]][0]);
if(verticesColors != null) { if (verticesColors != null) {
vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i])); vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i]));
} }
normalList.add(verticesAndNormals[index[i]][1]); normalList.add(verticesAndNormals[index[i]][1]);
if(uvCoordinatesList != null) { if (uvCoordinatesList != null) {
uvsMap.put(vertexList.size(), uvs[i]); uvsMap.put(vertexList.size(), uvs[i]);
uvCoordinatesList.add(uvs[i]); uvCoordinatesList.add(uvs[i]);
} }
index[i] = vertexList.size() - 1; index[i] = vertexList.size() - 1;
} else if(uvCoordinatesList != null) { } else if (uvCoordinatesList != null) {
boolean vertexAlreadyUsed = false; boolean vertexAlreadyUsed = false;
for(Integer vertexIndex : vertexReferenceMap.get(index[i])) { for (Integer vertexIndex : vertexReferenceMap.get(index[i])) {
if(uvs[i].equals(uvsMap.get(vertexIndex))) { if (uvs[i].equals(uvsMap.get(vertexIndex))) {
vertexAlreadyUsed = true; vertexAlreadyUsed = true;
index[i] = vertexIndex; index[i] = vertexIndex;
break; break;
} }
} }
if(!vertexAlreadyUsed) { if (!vertexAlreadyUsed) {
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
uvsMap.put(vertexList.size(), uvs[i]); uvsMap.put(vertexList.size(), uvs[i]);
vertexList.add(verticesAndNormals[index[i]][0]); vertexList.add(verticesAndNormals[index[i]][0]);
if(verticesColors != null) { if (verticesColors != null) {
vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i])); vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i]));
} }
normalList.add(verticesAndNormals[index[i]][1]); normalList.add(verticesAndNormals[index[i]][1]);
uvCoordinatesList.add(uvs[i]); uvCoordinatesList.add(uvs[i]);
index[i] = vertexList.size() - 1; index[i] = vertexList.size() - 1;
} }
} else { } else {
index[i] = vertexList.indexOf(verticesAndNormals[index[i]][0]); index[i] = vertexList.indexOf(verticesAndNormals[index[i]][0]);
} }
indexList.add(index[i]); indexList.add(index[i]);
} }
} else { } else {
Vector3f n = smooth ? null : FastMath.computeNormal(verticesAndNormals[v1][0], verticesAndNormals[v2][0], verticesAndNormals[v3][0]); Vector3f n = smooth ? null : FastMath.computeNormal(verticesAndNormals[v1][0], verticesAndNormals[v2][0], verticesAndNormals[v3][0]);
for (int i = 0; i < 3; ++i) { for (int i = 0; i < 3; ++i) {
indexList.add(vertexList.size()); indexList.add(vertexList.size());
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
if(uvCoordinatesList != null) { if (uvCoordinatesList != null) {
uvCoordinatesList.add(uvs[i]); uvCoordinatesList.add(uvs[i]);
uvsMap.put(vertexList.size(), uvs[i]); uvsMap.put(vertexList.size(), uvs[i]);
} }
vertexList.add(verticesAndNormals[index[i]][0]); vertexList.add(verticesAndNormals[index[i]][0]);
if(verticesColors != null) { if (verticesColors != null) {
vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i])); vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i]));
} }
normalList.add(smooth ? verticesAndNormals[index[i]][1] : n); normalList.add(smooth ? verticesAndNormals[index[i]][1] : n);
} }
} }
} }
/** /**
* @return a map that maps vertex index from reference array to its indices in the result list * @return a map that maps vertex index from reference array to its indices in the result list
*/ */
public Map<Integer, Map<Integer, List<Integer>>> getVertexReferenceMap() { public Map<Integer, Map<Integer, List<Integer>>> getVertexReferenceMap() {
return globalVertexReferenceMap; return globalVertexReferenceMap;
} }
/** /**
* @param materialNumber * @param materialNumber
* the material index * the material index
* @return result vertices array * @return result vertices array
*/ */
public Vector3f[] getVertices(int materialNumber) { public Vector3f[] getVertices(int materialNumber) {
return vertexMap.get(materialNumber).toArray(new Vector3f[vertexMap.get(materialNumber).size()]); return vertexMap.get(materialNumber).toArray(new Vector3f[vertexMap.get(materialNumber).size()]);
} }
/** /**
* @param materialNumber * @param materialNumber
* the material index * the material index
* @return the amount of result vertices * @return the amount of result vertices
*/ */
public int getVerticesAmount(int materialNumber) { public int getVerticesAmount(int materialNumber) {
return vertexMap.get(materialNumber).size(); return vertexMap.get(materialNumber).size();
} }
/** /**
* @param materialNumber * @param materialNumber
* the material index * the material index
* @return normals result array * @return normals result array
*/ */
public Vector3f[] getNormals(int materialNumber) { public Vector3f[] getNormals(int materialNumber) {
return normalMap.get(materialNumber).toArray(new Vector3f[normalMap.get(materialNumber).size()]); return normalMap.get(materialNumber).toArray(new Vector3f[normalMap.get(materialNumber).size()]);
} }
/** /**
* @param materialNumber * @param materialNumber
* the material index * the material index
* @return the vertices colors buffer or null if no vertex colors is set * @return the vertices colors buffer or null if no vertex colors is set
*/ */
public ByteBuffer getVertexColorsBuffer(int materialNumber) { public ByteBuffer getVertexColorsBuffer(int materialNumber) {
ByteBuffer result = null; ByteBuffer result = null;
if (verticesColors != null && vertexColorsMap.get(materialNumber) != null) { if (verticesColors != null && vertexColorsMap.get(materialNumber) != null) {
List<byte[]> data = vertexColorsMap.get(materialNumber); List<byte[]> data = vertexColorsMap.get(materialNumber);
result = BufferUtils.createByteBuffer(4 * data.size()); result = BufferUtils.createByteBuffer(4 * data.size());
for (byte[] v : data) { for (byte[] v : data) {
if (v != null) { if (v != null) {
result.put(v[0]).put(v[1]).put(v[2]).put(v[3]); result.put(v[0]).put(v[1]).put(v[2]).put(v[3]);
} else { } else {
result.put((byte)0).put((byte)0).put((byte)0).put((byte)0); result.put((byte) 0).put((byte) 0).put((byte) 0).put((byte) 0);
} }
} }
result.flip(); result.flip();
} }
return result; return result;
} }
/** /**
* @return a map between material number and the mesh part vertices indices * @return a map between material number and the mesh part vertices indices
*/ */
public Map<Integer, List<Integer>> getMeshesMap() { public Map<Integer, List<Integer>> getMeshesMap() {
return indexMap; return indexMap;
} }
/** /**
* @return the amount of meshes the source mesh was split into (depends on the applied materials count) * @return the amount of meshes the source mesh was split into (depends on the applied materials count)
*/ */
public int getMeshesPartAmount() { public int getMeshesPartAmount() {
return indexMap.size(); return indexMap.size();
} }
/** /**
* @param materialNumber * @param materialNumber
* the material number that is appied to the mesh * the material number that is appied to the mesh
* @return UV coordinates of vertices that belong to the required mesh part * @return UV coordinates of vertices that belong to the required mesh part
*/ */
public List<Vector2f> getUVCoordinates(int materialNumber) { public List<Vector2f> getUVCoordinates(int materialNumber) {
return uvCoordinates.get(materialNumber); return uvCoordinates.get(materialNumber);
} }
/** /**
* @return indicates if the mesh has UV coordinates * @return indicates if the mesh has UV coordinates
*/ */
public boolean hasUVCoordinates() { public boolean hasUVCoordinates() {
return uvCoordinates.size() > 0; return uvCoordinates.size() > 0;
} }
/** /**
* @return <b>true</b> if the mesh has no vertices and <b>false</b> otherwise * @return <b>true</b> if the mesh has no vertices and <b>false</b> otherwise
*/ */
public boolean isEmpty() { public boolean isEmpty() {
return vertexMap.size() == 0; return vertexMap.size() == 0;
} }
/** /**
* This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created * This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created
* to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key * to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key

@ -14,131 +14,135 @@ import com.jme3.scene.VertexBuffer;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class MeshContext { public class MeshContext {
/** A map between material index and the geometry. */ /** A map between material index and the geometry. */
private Map<Integer, Geometry> geometries = new HashMap<Integer, Geometry>(); private Map<Integer, Geometry> geometries = new HashMap<Integer, Geometry>();
/** The vertex reference map. */ /** The vertex reference map. */
private Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap; private Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap;
/** The UV-coordinates for each of the geometries. */ /** The UV-coordinates for each of the geometries. */
private Map<Geometry, VertexBuffer> uvCoordinates = new HashMap<Geometry, VertexBuffer>(); private Map<Geometry, VertexBuffer> uvCoordinates = new HashMap<Geometry, VertexBuffer>();
/** Bind buffer for vertices is stored here and applied when required. */ /** Bind buffer for vertices is stored here and applied when required. */
private Map<Integer, VertexBuffer> bindPoseBuffer = new HashMap<Integer, VertexBuffer>(); private Map<Integer, VertexBuffer> bindPoseBuffer = new HashMap<Integer, VertexBuffer>();
/** Bind buffer for normals is stored here and applied when required. */ /** Bind buffer for normals is stored here and applied when required. */
private Map<Integer, VertexBuffer> bindNormalBuffer = new HashMap<Integer, VertexBuffer>(); private Map<Integer, VertexBuffer> bindNormalBuffer = new HashMap<Integer, VertexBuffer>();
/** /**
* Adds a geometry for the specified material index. * Adds a geometry for the specified material index.
* @param materialIndex the material index * @param materialIndex
* @param geometry the geometry * the material index
*/ * @param geometry
public void putGeometry(Integer materialIndex, Geometry geometry) { * the geometry
geometries.put(materialIndex, geometry); */
} public void putGeometry(Integer materialIndex, Geometry geometry) {
geometries.put(materialIndex, geometry);
/** }
* @param materialIndex the material index
* @return vertices amount that is used by mesh with the specified material
*/
public int getVertexCount(int materialIndex) {
return geometries.get(materialIndex).getVertexCount();
}
/**
* Returns material index for the geometry.
* @param geometry the geometry
* @return material index
* @throws IllegalStateException this exception is thrown when no material is found for the specified geometry
*/
public int getMaterialIndex(Geometry geometry) {
for(Entry<Integer, Geometry> entry : geometries.entrySet()) {
if(entry.getValue().equals(geometry)) {
return entry.getKey();
}
}
throw new IllegalStateException("Cannot find material index for the given geometry: " + geometry);
}
/**
* This method returns the vertex reference map.
*
* @return the vertex reference map
*/
public Map<Integer, List<Integer>> getVertexReferenceMap(int materialIndex) {
return vertexReferenceMap.get(materialIndex);
}
/** /**
* This method sets the vertex reference map. * @param materialIndex
* * the material index
* @param vertexReferenceMap * @return vertices amount that is used by mesh with the specified material
* the vertex reference map */
*/ public int getVertexCount(int materialIndex) {
public void setVertexReferenceMap(Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap) { return geometries.get(materialIndex).getVertexCount();
this.vertexReferenceMap = vertexReferenceMap; }
}
/** /**
* This method adds the mesh's UV-coordinates. * Returns material index for the geometry.
* * @param geometry
* @param geometry * the geometry
* the mesh that has the UV-coordinates * @return material index
* @param vertexBuffer * @throws IllegalStateException
* the mesh's UV-coordinates * this exception is thrown when no material is found for the specified geometry
*/ */
public void addUVCoordinates(Geometry geometry, VertexBuffer vertexBuffer) { public int getMaterialIndex(Geometry geometry) {
uvCoordinates.put(geometry, vertexBuffer); for (Entry<Integer, Geometry> entry : geometries.entrySet()) {
} if (entry.getValue().equals(geometry)) {
return entry.getKey();
}
}
throw new IllegalStateException("Cannot find material index for the given geometry: " + geometry);
}
/** /**
* This method returns the mesh's UV-coordinates. * This method returns the vertex reference map.
* *
* @param geometry * @return the vertex reference map
* the mesh */
* @return the mesh's UV-coordinates public Map<Integer, List<Integer>> getVertexReferenceMap(int materialIndex) {
*/ return vertexReferenceMap.get(materialIndex);
public VertexBuffer getUVCoordinates(Geometry geometry) { }
return uvCoordinates.get(geometry);
}
/** /**
* This method sets the bind buffer for vertices. * This method sets the vertex reference map.
* *
* @param materialIndex * @param vertexReferenceMap
* the index of the mesh's material * the vertex reference map
* @param bindNormalBuffer */
* the bind buffer for vertices public void setVertexReferenceMap(Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap) {
*/ this.vertexReferenceMap = vertexReferenceMap;
public void setBindNormalBuffer(int materialIndex, }
VertexBuffer bindNormalBuffer) {
this.bindNormalBuffer.put(materialIndex, bindNormalBuffer);
}
/** /**
* @param materialIndex * This method adds the mesh's UV-coordinates.
* the index of the mesh's material *
* @return the bind buffer for vertices * @param geometry
*/ * the mesh that has the UV-coordinates
public VertexBuffer getBindNormalBuffer(int materialIndex) { * @param vertexBuffer
return bindNormalBuffer.get(materialIndex); * the mesh's UV-coordinates
} */
public void addUVCoordinates(Geometry geometry, VertexBuffer vertexBuffer) {
uvCoordinates.put(geometry, vertexBuffer);
}
/** /**
* This method sets the bind buffer for normals. * This method returns the mesh's UV-coordinates.
* *
* @param materialIndex * @param geometry
* the index of the mesh's material * the mesh
* @param bindNormalBuffer * @return the mesh's UV-coordinates
* the bind buffer for normals */
*/ public VertexBuffer getUVCoordinates(Geometry geometry) {
public void setBindPoseBuffer(int materialIndex, VertexBuffer bindPoseBuffer) { return uvCoordinates.get(geometry);
this.bindPoseBuffer.put(materialIndex, bindPoseBuffer); }
}
/** /**
* @param materialIndex * This method sets the bind buffer for vertices.
* the index of the mesh's material *
* @return the bind buffer for normals * @param materialIndex
*/ * the index of the mesh's material
public VertexBuffer getBindPoseBuffer(int materialIndex) { * @param bindNormalBuffer
return bindPoseBuffer.get(materialIndex); * the bind buffer for vertices
} */
public void setBindNormalBuffer(int materialIndex, VertexBuffer bindNormalBuffer) {
this.bindNormalBuffer.put(materialIndex, bindNormalBuffer);
}
/**
* @param materialIndex
* the index of the mesh's material
* @return the bind buffer for vertices
*/
public VertexBuffer getBindNormalBuffer(int materialIndex) {
return bindNormalBuffer.get(materialIndex);
}
/**
* This method sets the bind buffer for normals.
*
* @param materialIndex
* the index of the mesh's material
* @param bindNormalBuffer
* the bind buffer for normals
*/
public void setBindPoseBuffer(int materialIndex, VertexBuffer bindPoseBuffer) {
this.bindPoseBuffer.put(materialIndex, bindPoseBuffer);
}
/**
* @param materialIndex
* the index of the mesh's material
* @return the bind buffer for normals
*/
public VertexBuffer getBindPoseBuffer(int materialIndex) {
return bindPoseBuffer.get(materialIndex);
}
} }

@ -63,8 +63,8 @@ import com.jme3.util.BufferUtils;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class MeshHelper extends AbstractBlenderHelper { public class MeshHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(MeshHelper.class.getName()); private static final Logger LOGGER = Logger.getLogger(MeshHelper.class.getName());
/** /**
* This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
* versions. * versions.
@ -72,10 +72,10 @@ public class MeshHelper extends AbstractBlenderHelper {
* @param blenderVersion * @param blenderVersion
* the version read from the blend file * the version read from the blend file
* @param fixUpAxis * @param fixUpAxis
* a variable that indicates if the Y asxis is the UP axis or not * a variable that indicates if the Y asxis is the UP axis or not
*/ */
public MeshHelper(String blenderVersion, boolean fixUpAxis) { public MeshHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion,fixUpAxis); super(blenderVersion, fixUpAxis);
} }
/** /**
@ -107,26 +107,26 @@ public class MeshHelper extends AbstractBlenderHelper {
if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) { if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {
materials = materialHelper.getMaterials(structure, blenderContext); materials = materialHelper.getMaterials(structure, blenderContext);
} }
// reading vertices and their colors // reading vertices and their colors
Vector3f[][] verticesAndNormals = this.getVerticesAndNormals(structure, blenderContext); Vector3f[][] verticesAndNormals = this.getVerticesAndNormals(structure, blenderContext);
List<byte[]> verticesColors = this.getVerticesColors(structure, blenderContext); List<byte[]> verticesColors = this.getVerticesColors(structure, blenderContext);
MeshBuilder meshBuilder = new MeshBuilder(verticesAndNormals, verticesColors, this.areGeneratedTexturesPresent(materials)); MeshBuilder meshBuilder = new MeshBuilder(verticesAndNormals, verticesColors, this.areGeneratedTexturesPresent(materials));
if(this.isBMeshCompatible(structure)) { if (this.isBMeshCompatible(structure)) {
this.readBMesh(meshBuilder, structure, blenderContext); this.readBMesh(meshBuilder, structure, blenderContext);
} else { } else {
this.readTraditionalFaces(meshBuilder, structure, blenderContext); this.readTraditionalFaces(meshBuilder, structure, blenderContext);
} }
if(meshBuilder.isEmpty()) { if (meshBuilder.isEmpty()) {
geometries = new ArrayList<Geometry>(0); geometries = new ArrayList<Geometry>(0);
blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries); blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries);
blenderContext.setMeshContext(structure.getOldMemoryAddress(), meshContext); blenderContext.setMeshContext(structure.getOldMemoryAddress(), meshContext);
return geometries; return geometries;
} }
meshContext.setVertexReferenceMap(meshBuilder.getVertexReferenceMap()); meshContext.setVertexReferenceMap(meshBuilder.getVertexReferenceMap());
// reading vertices groups (from the parent) // reading vertices groups (from the parent)
@ -142,32 +142,32 @@ public class MeshHelper extends AbstractBlenderHelper {
// creating the result meshes // creating the result meshes
geometries = new ArrayList<Geometry>(meshBuilder.getMeshesPartAmount()); geometries = new ArrayList<Geometry>(meshBuilder.getMeshesPartAmount());
//reading custom properties // reading custom properties
Properties properties = this.loadProperties(structure, blenderContext); Properties properties = this.loadProperties(structure, blenderContext);
// generating meshes // generating meshes
for (Entry<Integer, List<Integer>> meshEntry : meshBuilder.getMeshesMap().entrySet()) { for (Entry<Integer, List<Integer>> meshEntry : meshBuilder.getMeshesMap().entrySet()) {
int materialIndex = meshEntry.getKey(); int materialIndex = meshEntry.getKey();
//key is the material index (or -1 if the material has no texture) // key is the material index (or -1 if the material has no texture)
//value is a list of vertex indices // value is a list of vertex indices
Mesh mesh = new Mesh(); Mesh mesh = new Mesh();
// creating vertices indices for this mesh // creating vertices indices for this mesh
List<Integer> indexList = meshEntry.getValue(); List<Integer> indexList = meshEntry.getValue();
if(meshBuilder.getVerticesAmount(materialIndex) <= Short.MAX_VALUE) { if (meshBuilder.getVerticesAmount(materialIndex) <= Short.MAX_VALUE) {
short[] indices = new short[indexList.size()]; short[] indices = new short[indexList.size()];
for (int i = 0; i < indexList.size(); ++i) { for (int i = 0; i < indexList.size(); ++i) {
indices[i] = indexList.get(i).shortValue(); indices[i] = indexList.get(i).shortValue();
} }
mesh.setBuffer(Type.Index, 1, indices); mesh.setBuffer(Type.Index, 1, indices);
} else { } else {
int[] indices = new int[indexList.size()]; int[] indices = new int[indexList.size()];
for (int i = 0; i < indexList.size(); ++i) { for (int i = 0; i < indexList.size(); ++i) {
indices[i] = indexList.get(i).intValue(); indices[i] = indexList.get(i).intValue();
} }
mesh.setBuffer(Type.Index, 1, indices); mesh.setBuffer(Type.Index, 1, indices);
} }
VertexBuffer verticesBuffer = new VertexBuffer(Type.Position); VertexBuffer verticesBuffer = new VertexBuffer(Type.Position);
verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(meshBuilder.getVertices(materialIndex))); verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(meshBuilder.getVertices(materialIndex)));
@ -181,9 +181,9 @@ public class MeshHelper extends AbstractBlenderHelper {
// initial normals position (used with animation) // initial normals position (used with animation)
VertexBuffer normalsBind = new VertexBuffer(Type.BindPoseNormal); VertexBuffer normalsBind = new VertexBuffer(Type.BindPoseNormal);
normalsBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.createFloatBuffer(meshBuilder.getNormals(materialIndex))); normalsBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.createFloatBuffer(meshBuilder.getNormals(materialIndex)));
mesh.setBuffer(verticesBuffer); mesh.setBuffer(verticesBuffer);
meshContext.setBindPoseBuffer(materialIndex, verticesBind);//this is stored in the context and applied when needed (when animation is applied to the mesh) meshContext.setBindPoseBuffer(materialIndex, verticesBind);// this is stored in the context and applied when needed (when animation is applied to the mesh)
// setting vertices colors // setting vertices colors
if (verticesColors != null) { if (verticesColors != null) {
@ -193,227 +193,225 @@ public class MeshHelper extends AbstractBlenderHelper {
// setting faces' normals // setting faces' normals
mesh.setBuffer(normalsBuffer); mesh.setBuffer(normalsBuffer);
meshContext.setBindNormalBuffer(materialIndex, normalsBind);//this is stored in the context and applied when needed (when animation is applied to the mesh) meshContext.setBindNormalBuffer(materialIndex, normalsBind);// this is stored in the context and applied when needed (when animation is applied to the mesh)
// creating the result // creating the result
Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh); Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh);
if (properties != null && properties.getValue() != null) { if (properties != null && properties.getValue() != null) {
this.applyProperties(geometry, properties); this.applyProperties(geometry, properties);
} }
geometries.add(geometry); geometries.add(geometry);
meshContext.putGeometry(materialIndex, geometry); meshContext.putGeometry(materialIndex, geometry);
} }
//store the data in blender context before applying the material // store the data in blender context before applying the material
blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries); blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries);
blenderContext.setMeshContext(structure.getOldMemoryAddress(), meshContext); blenderContext.setMeshContext(structure.getOldMemoryAddress(), meshContext);
//apply materials only when all geometries are in place // apply materials only when all geometries are in place
if(materials != null) { if (materials != null) {
for(Geometry geometry : geometries) { for (Geometry geometry : geometries) {
int materialNumber = meshContext.getMaterialIndex(geometry); int materialNumber = meshContext.getMaterialIndex(geometry);
if(materials[materialNumber] != null) { if (materials[materialNumber] != null) {
List<Vector2f> uvCoordinates = meshBuilder.getUVCoordinates(materialNumber); List<Vector2f> uvCoordinates = meshBuilder.getUVCoordinates(materialNumber);
MaterialContext materialContext = materials[materialNumber]; MaterialContext materialContext = materials[materialNumber];
materialContext.applyMaterial(geometry, structure.getOldMemoryAddress(), uvCoordinates, blenderContext); materialContext.applyMaterial(geometry, structure.getOldMemoryAddress(), uvCoordinates, blenderContext);
} else { } else {
geometry.setMaterial(blenderContext.getDefaultMaterial()); geometry.setMaterial(blenderContext.getDefaultMaterial());
LOGGER.warning("The importer came accross mesh that points to a null material. Default material is used to prevent loader from crashing, " + LOGGER.warning("The importer came accross mesh that points to a null material. Default material is used to prevent loader from crashing, " + "but the model might look not the way it should. Sometimes blender does not assign materials properly. " + "Enter the edit mode and assign materials once more to your faces.");
"but the model might look not the way it should. Sometimes blender does not assign materials properly. " +
"Enter the edit mode and assign materials once more to your faces.");
} }
} }
} else { } else {
//add UV coordinates if they are defined even if the material is not applied to the model // add UV coordinates if they are defined even if the material is not applied to the model
VertexBuffer uvCoordsBuffer = null; VertexBuffer uvCoordsBuffer = null;
if(meshBuilder.hasUVCoordinates()) { if (meshBuilder.hasUVCoordinates()) {
List<Vector2f> uvs = meshBuilder.getUVCoordinates(0); List<Vector2f> uvs = meshBuilder.getUVCoordinates(0);
uvCoordsBuffer = new VertexBuffer(Type.TexCoord); uvCoordsBuffer = new VertexBuffer(Type.TexCoord);
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()]))); uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()])));
} }
for(Geometry geometry : geometries) { for (Geometry geometry : geometries) {
geometry.setMaterial(blenderContext.getDefaultMaterial()); geometry.setMaterial(blenderContext.getDefaultMaterial());
if(uvCoordsBuffer != null) { if (uvCoordsBuffer != null) {
geometry.getMesh().setBuffer(uvCoordsBuffer); geometry.getMesh().setBuffer(uvCoordsBuffer);
} }
} }
} }
return geometries; return geometries;
} }
/** /**
* Tells if the given mesh structure supports BMesh. * Tells if the given mesh structure supports BMesh.
* *
* @param meshStructure * @param meshStructure
* the mesh structure * the mesh structure
* @return <b>true</b> if BMesh is supported and <b>false</b> otherwise * @return <b>true</b> if BMesh is supported and <b>false</b> otherwise
*/ */
private boolean isBMeshCompatible(Structure meshStructure) { private boolean isBMeshCompatible(Structure meshStructure) {
Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop"); Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop");
Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly"); Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly");
return pMLoop != null && pMPoly != null && pMLoop.isNotNull() && pMPoly.isNotNull(); return pMLoop != null && pMPoly != null && pMLoop.isNotNull() && pMPoly.isNotNull();
}
/**
* This method reads the mesh from the new BMesh system.
*
* @param meshBuilder
* the mesh builder
* @param meshStructure
* the mesh structure
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
@SuppressWarnings("unchecked")
private void readBMesh(MeshBuilder meshBuilder, Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop");
Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly");
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
Pointer pMLoopUV = (Pointer) meshStructure.getFieldValue("mloopuv");
Vector2f[] uvCoordinatesForFace = new Vector2f[3];
if (pMPoly.isNotNull() && pMLoop.isNotNull() && pMEdge.isNotNull()) {
int faceIndex = 0;
List<Structure> polys = pMPoly.fetchData(blenderContext.getInputStream());
List<Structure> loops = pMLoop.fetchData(blenderContext.getInputStream());
List<Structure> loopuvs = pMLoopUV.isNotNull() ? pMLoopUV.fetchData(blenderContext.getInputStream()) : null;
for (Structure poly : polys) {
int materialNumber = ((Number) poly.getFieldValue("mat_nr")).intValue();
int loopStart = ((Number) poly.getFieldValue("loopstart")).intValue();
int totLoop = ((Number) poly.getFieldValue("totloop")).intValue();
boolean smooth = (((Number) poly.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
int[] vertexIndexes = new int[totLoop];
Vector2f[] uvs = loopuvs != null ? new Vector2f[totLoop] : null;
for (int i = loopStart; i < loopStart + totLoop; ++i) {
vertexIndexes[i - loopStart] = ((Number) loops.get(i).getFieldValue("v")).intValue();
if (uvs != null) {
DynamicArray<Number> loopUVS = (DynamicArray<Number>) loopuvs.get(i).getFieldValue("uv");
uvs[i - loopStart] = new Vector2f(loopUVS.get(0).floatValue(), loopUVS.get(1).floatValue());
}
}
int i = 0;
while (i < totLoop - 2) {
int v1 = vertexIndexes[0];
int v2 = vertexIndexes[i + 1];
int v3 = vertexIndexes[i + 2];
if (uvs != null) {// uvs always must be added wheater we
// have texture or not
uvCoordinatesForFace[0] = uvs[0];
uvCoordinatesForFace[1] = uvs[i + 1];
uvCoordinatesForFace[2] = uvs[i + 2];
}
meshBuilder.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, false, faceIndex);
++i;
}
++faceIndex;
}
}
} }
/**
* This method reads the mesh from the new BMesh system.
*
* @param meshBuilder
* the mesh builder
* @param meshStructure
* the mesh structure
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
@SuppressWarnings("unchecked")
private void readBMesh(MeshBuilder meshBuilder, Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop");
Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly");
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
Pointer pMLoopUV = (Pointer) meshStructure.getFieldValue("mloopuv");
Vector2f[] uvCoordinatesForFace = new Vector2f[3];
if (pMPoly.isNotNull() && pMLoop.isNotNull() && pMEdge.isNotNull()) {
int faceIndex = 0;
List<Structure> polys = pMPoly.fetchData(blenderContext.getInputStream());
List<Structure> loops = pMLoop.fetchData(blenderContext.getInputStream());
List<Structure> loopuvs = pMLoopUV.isNotNull() ? pMLoopUV.fetchData(blenderContext.getInputStream()) : null;
for (Structure poly : polys) {
int materialNumber = ((Number) poly.getFieldValue("mat_nr")).intValue();
int loopStart = ((Number) poly.getFieldValue("loopstart")).intValue();
int totLoop = ((Number) poly.getFieldValue("totloop")).intValue();
boolean smooth = (((Number) poly.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
int[] vertexIndexes = new int[totLoop];
Vector2f[] uvs = loopuvs != null ? new Vector2f[totLoop] : null;
for (int i = loopStart; i < loopStart + totLoop; ++i) {
vertexIndexes[i - loopStart] = ((Number) loops.get(i).getFieldValue("v")).intValue();
if (uvs != null) {
DynamicArray<Number> loopUVS = (DynamicArray<Number>) loopuvs.get(i).getFieldValue("uv");
uvs[i - loopStart] = new Vector2f(loopUVS.get(0).floatValue(), loopUVS.get(1).floatValue());
}
}
int i = 0;
while (i < totLoop - 2) {
int v1 = vertexIndexes[0];
int v2 = vertexIndexes[i + 1];
int v3 = vertexIndexes[i + 2];
if (uvs != null) {// uvs always must be added wheater we
// have texture or not
uvCoordinatesForFace[0] = uvs[0];
uvCoordinatesForFace[1] = uvs[i + 1];
uvCoordinatesForFace[2] = uvs[i + 2];
}
meshBuilder.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, false, faceIndex);
++i;
}
++faceIndex;
}
}
}
/**
* This method reads the mesh from traditional triangle/quad storing
* structures.
*
* @param meshBuilder
* the mesh builder
* @param meshStructure
* the mesh structure
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
@SuppressWarnings("unchecked")
private void readTraditionalFaces(MeshBuilder meshBuilder, Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
Pointer pMFace = (Pointer) meshStructure.getFieldValue("mface");
List<Structure> mFaces = pMFace.isNotNull() ? pMFace.fetchData(blenderContext.getInputStream()) : null;
if (mFaces != null && mFaces.size() > 0) {
Pointer pMTFace = (Pointer) meshStructure.getFieldValue("mtface");
List<Structure> mtFaces = null;
if (pMTFace.isNotNull()) {
mtFaces = pMTFace.fetchData(blenderContext.getInputStream());
int facesAmount = ((Number) meshStructure.getFieldValue("totface")).intValue();
if (mtFaces.size() != facesAmount) {
throw new BlenderFileException("The amount of faces uv coordinates is not equal to faces amount!");
}
}
// indicates if the material with the specified number should have a
// texture attached
Vector2f[] uvCoordinatesForFace = new Vector2f[3];
for (int i = 0; i < mFaces.size(); ++i) {
Structure mFace = mFaces.get(i);
int materialNumber = ((Number) mFace.getFieldValue("mat_nr")).intValue();
boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
DynamicArray<Number> uvs = null;
if (mtFaces != null) {
Structure mtFace = mtFaces.get(i);
// uvs always must be added wheater we have texture or not
uvs = (DynamicArray<Number>) mtFace.getFieldValue("uv");
uvCoordinatesForFace[0] = new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue());
uvCoordinatesForFace[1] = new Vector2f(uvs.get(1, 0).floatValue(), uvs.get(1, 1).floatValue());
uvCoordinatesForFace[2] = new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue());
}
int v1 = ((Number) mFace.getFieldValue("v1")).intValue();
int v2 = ((Number) mFace.getFieldValue("v2")).intValue();
int v3 = ((Number) mFace.getFieldValue("v3")).intValue();
int v4 = ((Number) mFace.getFieldValue("v4")).intValue();
meshBuilder.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, false, i);
if (v4 > 0) {
if (uvs != null) {
uvCoordinatesForFace[0] = new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue());
uvCoordinatesForFace[1] = new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue());
uvCoordinatesForFace[2] = new Vector2f(uvs.get(3, 0).floatValue(), uvs.get(3, 1).floatValue());
}
meshBuilder.appendFace(v1, v3, v4, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, true, i);
}
}
} else {
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
List<Structure> mEdges = pMEdge.isNotNull() ? pMEdge.fetchData(blenderContext.getInputStream()) : null;
if (mEdges != null && mEdges.size() > 0) {
for (int i = 0; i < mEdges.size(); ++i) {
Structure mEdge = mEdges.get(i);
boolean smooth = (((Number) mEdge.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
int v1 = ((Number) mEdge.getFieldValue("v1")).intValue();
int v2 = ((Number) mEdge.getFieldValue("v2")).intValue();
meshBuilder.appendEdge(v1, v2, smooth);
}
}
}
}
/** /**
* @return <b>true</b> if the material has at least one generated component and <b>false</b> otherwise * This method reads the mesh from traditional triangle/quad storing
*/ * structures.
*
* @param meshBuilder
* the mesh builder
* @param meshStructure
* the mesh structure
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
@SuppressWarnings("unchecked")
private void readTraditionalFaces(MeshBuilder meshBuilder, Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
Pointer pMFace = (Pointer) meshStructure.getFieldValue("mface");
List<Structure> mFaces = pMFace.isNotNull() ? pMFace.fetchData(blenderContext.getInputStream()) : null;
if (mFaces != null && mFaces.size() > 0) {
Pointer pMTFace = (Pointer) meshStructure.getFieldValue("mtface");
List<Structure> mtFaces = null;
if (pMTFace.isNotNull()) {
mtFaces = pMTFace.fetchData(blenderContext.getInputStream());
int facesAmount = ((Number) meshStructure.getFieldValue("totface")).intValue();
if (mtFaces.size() != facesAmount) {
throw new BlenderFileException("The amount of faces uv coordinates is not equal to faces amount!");
}
}
// indicates if the material with the specified number should have a
// texture attached
Vector2f[] uvCoordinatesForFace = new Vector2f[3];
for (int i = 0; i < mFaces.size(); ++i) {
Structure mFace = mFaces.get(i);
int materialNumber = ((Number) mFace.getFieldValue("mat_nr")).intValue();
boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
DynamicArray<Number> uvs = null;
if (mtFaces != null) {
Structure mtFace = mtFaces.get(i);
// uvs always must be added wheater we have texture or not
uvs = (DynamicArray<Number>) mtFace.getFieldValue("uv");
uvCoordinatesForFace[0] = new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue());
uvCoordinatesForFace[1] = new Vector2f(uvs.get(1, 0).floatValue(), uvs.get(1, 1).floatValue());
uvCoordinatesForFace[2] = new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue());
}
int v1 = ((Number) mFace.getFieldValue("v1")).intValue();
int v2 = ((Number) mFace.getFieldValue("v2")).intValue();
int v3 = ((Number) mFace.getFieldValue("v3")).intValue();
int v4 = ((Number) mFace.getFieldValue("v4")).intValue();
meshBuilder.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, false, i);
if (v4 > 0) {
if (uvs != null) {
uvCoordinatesForFace[0] = new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue());
uvCoordinatesForFace[1] = new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue());
uvCoordinatesForFace[2] = new Vector2f(uvs.get(3, 0).floatValue(), uvs.get(3, 1).floatValue());
}
meshBuilder.appendFace(v1, v3, v4, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, true, i);
}
}
} else {
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
List<Structure> mEdges = pMEdge.isNotNull() ? pMEdge.fetchData(blenderContext.getInputStream()) : null;
if (mEdges != null && mEdges.size() > 0) {
for (int i = 0; i < mEdges.size(); ++i) {
Structure mEdge = mEdges.get(i);
boolean smooth = (((Number) mEdge.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
int v1 = ((Number) mEdge.getFieldValue("v1")).intValue();
int v2 = ((Number) mEdge.getFieldValue("v2")).intValue();
meshBuilder.appendEdge(v1, v2, smooth);
}
}
}
}
/**
* @return <b>true</b> if the material has at least one generated component and <b>false</b> otherwise
*/
private boolean areGeneratedTexturesPresent(MaterialContext[] materials) { private boolean areGeneratedTexturesPresent(MaterialContext[] materials) {
if(materials != null) { if (materials != null) {
for(MaterialContext material : materials) { for (MaterialContext material : materials) {
if(material != null && material.hasGeneratedTextures()) { if (material != null && material.hasGeneratedTextures()) {
return true; return true;
} }
} }
} }
return false; return false;
} }
/** /**
* This method returns the vertices colors. Each vertex is stored in byte[4] array. * This method returns the vertices colors. Each vertex is stored in byte[4] array.
* *
@ -433,11 +431,11 @@ public class MeshHelper extends AbstractBlenderHelper {
verticesColors = new ArrayList<byte[]>(); verticesColors = new ArrayList<byte[]>();
mCol = pMCol.fetchData(blenderContext.getInputStream()); mCol = pMCol.fetchData(blenderContext.getInputStream());
for (Structure color : mCol) { for (Structure color : mCol) {
byte r = ((Number)color.getFieldValue("r")).byteValue(); byte r = ((Number) color.getFieldValue("r")).byteValue();
byte g = ((Number)color.getFieldValue("g")).byteValue(); byte g = ((Number) color.getFieldValue("g")).byteValue();
byte b = ((Number)color.getFieldValue("b")).byteValue(); byte b = ((Number) color.getFieldValue("b")).byteValue();
byte a = ((Number)color.getFieldValue("a")).byteValue(); byte a = ((Number) color.getFieldValue("a")).byteValue();
verticesColors.add(new byte[]{b, g, r, a}); verticesColors.add(new byte[] { b, g, r, a });
} }
} }
return verticesColors; return verticesColors;
@ -464,21 +462,21 @@ public class MeshHelper extends AbstractBlenderHelper {
Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert"); Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert");
List<Structure> mVerts = pMVert.fetchData(blenderContext.getInputStream()); List<Structure> mVerts = pMVert.fetchData(blenderContext.getInputStream());
if(this.fixUpAxis) { if (this.fixUpAxis) {
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co"); DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");
result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue()); result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue());
DynamicArray<Number> normals = (DynamicArray<Number>) mVerts.get(i).getFieldValue("no"); DynamicArray<Number> normals = (DynamicArray<Number>) mVerts.get(i).getFieldValue("no");
result[i][1] = new Vector3f(normals.get(0).shortValue()/32767.0f, normals.get(2).shortValue()/32767.0f, -normals.get(1).shortValue()/32767.0f); result[i][1] = new Vector3f(normals.get(0).shortValue() / 32767.0f, normals.get(2).shortValue() / 32767.0f, -normals.get(1).shortValue() / 32767.0f);
} }
} else { } else {
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co"); DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");
result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue()); result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue());
DynamicArray<Number> normals = (DynamicArray<Number>) mVerts.get(i).getFieldValue("no"); DynamicArray<Number> normals = (DynamicArray<Number>) mVerts.get(i).getFieldValue("no");
result[i][1] = new Vector3f(normals.get(0).shortValue()/32767.0f, normals.get(1).shortValue()/32767.0f, normals.get(2).shortValue()/32767.0f); result[i][1] = new Vector3f(normals.get(0).shortValue() / 32767.0f, normals.get(1).shortValue() / 32767.0f, normals.get(2).shortValue() / 32767.0f);
} }
} }
return result; return result;

@ -43,378 +43,375 @@ import com.jme3.util.BufferUtils;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class ArmatureModifier extends Modifier { /* package */class ArmatureModifier extends Modifier {
private static final Logger LOGGER = Logger.getLogger(ArmatureModifier.class.getName()); private static final Logger LOGGER = Logger.getLogger(ArmatureModifier.class.getName());
private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4;//JME limitation private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; // JME limitation
private Skeleton skeleton; private Skeleton skeleton;
private Structure objectStructure; private Structure objectStructure;
private Structure meshStructure; private Structure meshStructure;
/** Loaded animation data. */ /** Loaded animation data. */
private AnimData animData; private AnimData animData;
/** Old memory address of the mesh that will have the skeleton applied. */ /** Old memory address of the mesh that will have the skeleton applied. */
private Long meshOMA; private Long meshOMA;
/** /**
* This constructor reads animation data from the object structore. The * This constructor reads animation data from the object structore. The
* stored data is the AnimData and additional data is armature's OMA. * stored data is the AnimData and additional data is armature's OMA.
* *
* @param objectStructure * @param objectStructure
* the structure of the object * the structure of the object
* @param modifierStructure * @param modifierStructure
* the structure of the modifier * the structure of the modifier
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blender file is somehow * this exception is thrown when the blender file is somehow
* corrupted * corrupted
*/ */
public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0); Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0);
Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
// if pDvert==null then there are not vertex groups and no need to load // if pDvert==null then there are not vertex groups and no need to load
// skeleton (untill bone envelopes are supported) // skeleton (untill bone envelopes are supported)
if (this.validate(modifierStructure, blenderContext) && pDvert.isNotNull()) { if (this.validate(modifierStructure, blenderContext) && pDvert.isNotNull()) {
Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object"); Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");
if (pArmatureObject.isNotNull()) { if (pArmatureObject.isNotNull()) {
ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class); ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class);
Structure armatureObject = pArmatureObject.fetchData(blenderContext.getInputStream()).get(0); Structure armatureObject = pArmatureObject.fetchData(blenderContext.getInputStream()).get(0);
// load skeleton // load skeleton
Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0); Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0);
Structure pose = ((Pointer) armatureObject.getFieldValue("pose")).fetchData(blenderContext.getInputStream()).get(0); Structure pose = ((Pointer) armatureObject.getFieldValue("pose")).fetchData(blenderContext.getInputStream()).get(0);
List<Structure> chanbase = ((Structure) pose.getFieldValue("chanbase")).evaluateListBase(blenderContext); List<Structure> chanbase = ((Structure) pose.getFieldValue("chanbase")).evaluateListBase(blenderContext);
Map<Long, Structure> bonesPoseChannels = new HashMap<Long, Structure>(chanbase.size()); Map<Long, Structure> bonesPoseChannels = new HashMap<Long, Structure>(chanbase.size());
for (Structure poseChannel : chanbase) { for (Structure poseChannel : chanbase) {
Pointer pBone = (Pointer) poseChannel.getFieldValue("bone"); Pointer pBone = (Pointer) poseChannel.getFieldValue("bone");
bonesPoseChannels.put(pBone.getOldMemoryAddress(), poseChannel); bonesPoseChannels.put(pBone.getOldMemoryAddress(), poseChannel);
} }
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
Matrix4f armatureObjectMatrix = objectHelper.getMatrix(armatureObject, "obmat", true); Matrix4f armatureObjectMatrix = objectHelper.getMatrix(armatureObject, "obmat", true);
Matrix4f inverseMeshObjectMatrix = objectHelper.getMatrix(objectStructure, "obmat", true).invertLocal(); Matrix4f inverseMeshObjectMatrix = objectHelper.getMatrix(objectStructure, "obmat", true).invertLocal();
Matrix4f objectToArmatureTransformation = armatureObjectMatrix.multLocal(inverseMeshObjectMatrix); Matrix4f objectToArmatureTransformation = armatureObjectMatrix.multLocal(inverseMeshObjectMatrix);
List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase(blenderContext); List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase(blenderContext);
List<Bone> bonesList = new ArrayList<Bone>(); List<Bone> bonesList = new ArrayList<Bone>();
for (int i = 0; i < bonebase.size(); ++i) { for (int i = 0; i < bonebase.size(); ++i) {
armatureHelper.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectToArmatureTransformation, bonesPoseChannels, blenderContext); armatureHelper.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectToArmatureTransformation, bonesPoseChannels, blenderContext);
} }
bonesList.add(0, new Bone("")); bonesList.add(0, new Bone(""));
Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]); Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]);
skeleton = new Skeleton(bones); skeleton = new Skeleton(bones);
blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton); blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton);
this.objectStructure = objectStructure; this.objectStructure = objectStructure;
this.meshStructure = meshStructure; this.meshStructure = meshStructure;
// read mesh indexes // read mesh indexes
this.meshOMA = meshStructure.getOldMemoryAddress(); this.meshOMA = meshStructure.getOldMemoryAddress();
// read animations // read animations
ArrayList<Animation> animations = new ArrayList<Animation>(); ArrayList<Animation> animations = new ArrayList<Animation>();
List<FileBlockHeader> actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00)); List<FileBlockHeader> actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00));
if (actionHeaders != null) {// it may happen that the model has armature with no actions if (actionHeaders != null) {// it may happen that the model has armature with no actions
for (FileBlockHeader header : actionHeaders) { for (FileBlockHeader header : actionHeaders) {
Structure actionStructure = header.getStructure(blenderContext); Structure actionStructure = header.getStructure(blenderContext);
String actionName = actionStructure.getName(); String actionName = actionStructure.getName();
BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, skeleton, blenderContext); BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, skeleton, blenderContext);
if(tracks != null && tracks.length > 0) { if (tracks != null && tracks.length > 0) {
// determining the animation time // determining the animation time
float maximumTrackLength = 0; float maximumTrackLength = 0;
for (BoneTrack track : tracks) { for (BoneTrack track : tracks) {
float length = track.getLength(); float length = track.getLength();
if (length > maximumTrackLength) { if (length > maximumTrackLength) {
maximumTrackLength = length; maximumTrackLength = length;
} }
} }
Animation boneAnimation = new Animation(actionName, maximumTrackLength); Animation boneAnimation = new Animation(actionName, maximumTrackLength);
boneAnimation.setTracks(tracks); boneAnimation.setTracks(tracks);
animations.add(boneAnimation); animations.add(boneAnimation);
} }
} }
} }
//fetching action defined in object // fetching action defined in object
Pointer pAction = (Pointer) objectStructure.getFieldValue("action"); Pointer pAction = (Pointer) objectStructure.getFieldValue("action");
if (pAction.isNotNull()) { if (pAction.isNotNull()) {
Structure actionStructure = pAction.fetchData(blenderContext.getInputStream()).get(0); Structure actionStructure = pAction.fetchData(blenderContext.getInputStream()).get(0);
String actionName = actionStructure.getName(); String actionName = actionStructure.getName();
BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, skeleton, blenderContext); BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, skeleton, blenderContext);
if(tracks != null && tracks.length > 0) { if (tracks != null && tracks.length > 0) {
// determining the animation time // determining the animation time
float maximumTrackLength = 0; float maximumTrackLength = 0;
for (BoneTrack track : tracks) { for (BoneTrack track : tracks) {
float length = track.getLength(); float length = track.getLength();
if (length > maximumTrackLength) { if (length > maximumTrackLength) {
maximumTrackLength = length; maximumTrackLength = length;
} }
} }
Animation boneAnimation = new Animation(actionName, maximumTrackLength); Animation boneAnimation = new Animation(actionName, maximumTrackLength);
boneAnimation.setTracks(tracks); boneAnimation.setTracks(tracks);
animations.add(boneAnimation); animations.add(boneAnimation);
} }
} }
animData = new AnimData(skeleton, animations); animData = new AnimData(skeleton, animations);
// store the animation data for each bone // store the animation data for each bone
for (Bone bone : bones) { for (Bone bone : bones) {
Long boneOma = armatureHelper.getBoneOMA(bone); Long boneOma = armatureHelper.getBoneOMA(bone);
if (boneOma != null) { if (boneOma != null) {
blenderContext.setAnimData(boneOma, animData); blenderContext.setAnimData(boneOma, animData);
} }
} }
} else { } else {
modifying = false; modifying = false;
} }
} }
} }
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Node apply(Node node, BlenderContext blenderContext) { public Node apply(Node node, BlenderContext blenderContext) {
if (invalid) { if (invalid) {
LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName()); LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName());
}// if invalid, animData will be null }// if invalid, animData will be null
if (animData == null || skeleton == null) { if (animData == null || skeleton == null) {
return node; return node;
} }
// setting weights for bones // setting weights for bones
List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE); List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE);
MeshContext meshContext = blenderContext.getMeshContext(meshOMA); MeshContext meshContext = blenderContext.getMeshContext(meshOMA);
int[] bonesGroups = new int[] { 0 }; int[] bonesGroups = new int[] { 0 };
for (Geometry geom : geomList) { for (Geometry geom : geomList) {
int materialIndex = meshContext.getMaterialIndex(geom); int materialIndex = meshContext.getMaterialIndex(geom);
Mesh mesh = geom.getMesh(); Mesh mesh = geom.getMesh();
try { try {
VertexBuffer[] buffers = this.readVerticesWeightsData(objectStructure, meshStructure, skeleton, materialIndex, bonesGroups, blenderContext); VertexBuffer[] buffers = this.readVerticesWeightsData(objectStructure, meshStructure, skeleton, materialIndex, bonesGroups, blenderContext);
if (buffers != null) { if (buffers != null) {
mesh.setMaxNumWeights(bonesGroups[0]); mesh.setMaxNumWeights(bonesGroups[0]);
mesh.setBuffer(buffers[0]); mesh.setBuffer(buffers[0]);
mesh.setBuffer(buffers[1]); mesh.setBuffer(buffers[1]);
VertexBuffer bindNormalBuffer = (meshContext.getBindNormalBuffer(materialIndex)); VertexBuffer bindNormalBuffer = (meshContext.getBindNormalBuffer(materialIndex));
if(bindNormalBuffer != null) { if (bindNormalBuffer != null) {
mesh.setBuffer(bindNormalBuffer); mesh.setBuffer(bindNormalBuffer);
} }
VertexBuffer bindPoseBuffer = (meshContext.getBindPoseBuffer(materialIndex)); VertexBuffer bindPoseBuffer = (meshContext.getBindPoseBuffer(materialIndex));
if(bindPoseBuffer != null) { if (bindPoseBuffer != null) {
mesh.setBuffer(bindPoseBuffer); mesh.setBuffer(bindPoseBuffer);
} }
//change the usage type of vertex and normal buffers from Static to Stream // change the usage type of vertex and normal buffers from Static to Stream
mesh.getBuffer(Type.Position).setUsage(Usage.Stream); mesh.getBuffer(Type.Position).setUsage(Usage.Stream);
mesh.getBuffer(Type.Normal).setUsage(Usage.Stream); mesh.getBuffer(Type.Normal).setUsage(Usage.Stream);
} }
} catch (BlenderFileException e) { } catch (BlenderFileException e) {
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
this.invalid = true; this.invalid = true;
return node; return node;
} }
} }
// applying animations // applying animations
AnimControl control = new AnimControl(animData.skeleton); AnimControl control = new AnimControl(animData.skeleton);
ArrayList<Animation> animList = animData.anims; ArrayList<Animation> animList = animData.anims;
if (animList != null && animList.size() > 0) { if (animList != null && animList.size() > 0) {
HashMap<String, Animation> anims = new HashMap<String, Animation>(animList.size()); HashMap<String, Animation> anims = new HashMap<String, Animation>(animList.size());
for (int i = 0; i < animList.size(); ++i) { for (int i = 0; i < animList.size(); ++i) {
Animation animation = animList.get(i); Animation animation = animList.get(i);
anims.put(animation.getName(), animation); anims.put(animation.getName(), animation);
} }
control.setAnimations(anims); control.setAnimations(anims);
} }
node.addControl(control); node.addControl(control);
node.addControl(new SkeletonControl(animData.skeleton)); node.addControl(new SkeletonControl(animData.skeleton));
return node; return node;
} }
/** /**
* This method reads mesh indexes * This method reads mesh indexes
* *
* @param objectStructure * @param objectStructure
* structure of the object that has the armature modifier applied * structure of the object that has the armature modifier applied
* @param meshStructure * @param meshStructure
* the structure of the object's mesh * the structure of the object's mesh
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blend file structure is * this exception is thrown when the blend file structure is
* somehow invalid or corrupted * somehow invalid or corrupted
*/ */
private VertexBuffer[] readVerticesWeightsData(Structure objectStructure, Structure meshStructure, Skeleton skeleton, int materialIndex, private VertexBuffer[] readVerticesWeightsData(Structure objectStructure, Structure meshStructure, Skeleton skeleton, int materialIndex, int[] bonesGroups, BlenderContext blenderContext) throws BlenderFileException {
int[] bonesGroups, BlenderContext blenderContext) throws BlenderFileException { ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class);
ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class); Structure defBase = (Structure) objectStructure.getFieldValue("defbase");
Structure defBase = (Structure) objectStructure.getFieldValue("defbase"); Map<Integer, Integer> groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, skeleton, blenderContext);
Map<Integer, Integer> groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, skeleton, blenderContext);
MeshContext meshContext = blenderContext.getMeshContext(meshStructure.getOldMemoryAddress());
MeshContext meshContext = blenderContext.getMeshContext(meshStructure.getOldMemoryAddress());
return this.getBoneWeightAndIndexBuffer(meshStructure, meshContext.getVertexCount(materialIndex), bonesGroups, meshContext.getVertexReferenceMap(materialIndex), groupToBoneIndexMap, blenderContext);
return this.getBoneWeightAndIndexBuffer(meshStructure, meshContext.getVertexCount(materialIndex), }
bonesGroups, meshContext.getVertexReferenceMap(materialIndex), groupToBoneIndexMap, blenderContext);
} /**
* This method returns an array of size 2. The first element is a vertex
/** * buffer holding bone weights for every vertex in the model. The second
* This method returns an array of size 2. The first element is a vertex * element is a vertex buffer holding bone indices for vertices (the indices
* buffer holding bone weights for every vertex in the model. The second * of bones the vertices are assigned to).
* element is a vertex buffer holding bone indices for vertices (the indices *
* of bones the vertices are assigned to). * @param meshStructure
* * the mesh structure object
* @param meshStructure * @param vertexListSize
* the mesh structure object * a number of vertices in the model
* @param vertexListSize * @param bonesGroups
* a number of vertices in the model * this is an output parameter, it should be a one-sized array;
* @param bonesGroups * the maximum amount of weights per vertex (up to
* this is an output parameter, it should be a one-sized array; * MAXIMUM_WEIGHTS_PER_VERTEX) is stored there
* the maximum amount of weights per vertex (up to * @param vertexReferenceMap
* MAXIMUM_WEIGHTS_PER_VERTEX) is stored there * this reference map allows to map the original vertices read
* @param vertexReferenceMap * from blender to vertices that are really in the model; one
* this reference map allows to map the original vertices read * vertex may appear several times in the result model
* from blender to vertices that are really in the model; one * @param groupToBoneIndexMap
* vertex may appear several times in the result model * this object maps the group index (to which a vertices in
* @param groupToBoneIndexMap * blender belong) to bone index of the model
* this object maps the group index (to which a vertices in * @param blenderContext
* blender belong) to bone index of the model * the blender context
* @param blenderContext * @return arrays of vertices weights and their bone indices and (as an
* the blender context * output parameter) the maximum amount of weights for a vertex
* @return arrays of vertices weights and their bone indices and (as an * @throws BlenderFileException
* output parameter) the maximum amount of weights for a vertex * this exception is thrown when the blend file structure is
* @throws BlenderFileException * somehow invalid or corrupted
* this exception is thrown when the blend file structure is */
* somehow invalid or corrupted private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, BlenderContext blenderContext) throws BlenderFileException {
*/ bonesGroups[0] = 0;
private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, BlenderContext blenderContext) Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
throws BlenderFileException { FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
bonesGroups[0] = 0; ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); if (pDvert.isNotNull()) {// assigning weights and bone indices
ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); boolean warnAboutTooManyVertexWeights = false;
List<Structure> dverts = pDvert.fetchData(blenderContext.getInputStream());// dverts.size() == verticesAmount (one dvert per vertex in blender)
if (pDvert.isNotNull()) {// assigning weights and bone indices int vertexIndex = 0;
boolean warnAboutTooManyVertexWeights = false; // use tree map to sort weights from the lowest to the highest ones
List<Structure> dverts = pDvert.fetchData(blenderContext.getInputStream());// dverts.size() == verticesAmount (one dvert per vertex in blender) TreeMap<Float, Integer> weightToIndexMap = new TreeMap<Float, Integer>();
int vertexIndex = 0;
//use tree map to sort weights from the lowest to the highest ones for (Structure dvert : dverts) {
TreeMap<Float, Integer> weightToIndexMap = new TreeMap<Float, Integer>(); List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here
if (vertexIndices != null) {
for (Structure dvert : dverts) { int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total amount of weights assignet to the vertex (max. 4 in JME)
List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here Pointer pDW = (Pointer) dvert.getFieldValue("dw");
if(vertexIndices != null) { if (totweight > 0 && groupToBoneIndexMap != null) {
int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total amount of weights assignet to the vertex (max. 4 in JME) weightToIndexMap.clear();
Pointer pDW = (Pointer) dvert.getFieldValue("dw"); int weightIndex = 0;
if (totweight > 0 && groupToBoneIndexMap != null) { List<Structure> dw = pDW.fetchData(blenderContext.getInputStream());
weightToIndexMap.clear(); for (Structure deformWeight : dw) {
int weightIndex = 0; Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue());
List<Structure> dw = pDW.fetchData(blenderContext.getInputStream()); // null here means that we came accross group that has no bone attached to
for (Structure deformWeight : dw) { if (boneIndex != null) {
Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue()); float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
// null here means that we came accross group that has no bone attached to if (weightIndex < MAXIMUM_WEIGHTS_PER_VERTEX) {
if (boneIndex != null) { if (weight == 0.0f) {
float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue(); boneIndex = Integer.valueOf(0);
if(weightIndex < MAXIMUM_WEIGHTS_PER_VERTEX) { }
if (weight == 0.0f) { // we apply the weight to all referenced vertices
boneIndex = Integer.valueOf(0); for (Integer index : vertexIndices) {
} weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight);
// we apply the weight to all referenced vertices indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue());
for (Integer index : vertexIndices) { }
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight); weightToIndexMap.put(weight, weightIndex);
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue()); bonesGroups[0] = Math.max(bonesGroups[0], weightIndex + 1);
} } else if (weight > 0) {// if weight is zero the simply ignore it
weightToIndexMap.put(weight, weightIndex); warnAboutTooManyVertexWeights = true;
bonesGroups[0] = Math.max(bonesGroups[0], weightIndex + 1); Entry<Float, Integer> lowestWeightAndIndex = weightToIndexMap.firstEntry();
} else if(weight > 0) {//if weight is zero the simply ignore it if (lowestWeightAndIndex != null && lowestWeightAndIndex.getKey() < weight) {
warnAboutTooManyVertexWeights = true; weightsFloatData.put(lowestWeightAndIndex.getValue(), weight);
Entry<Float, Integer> lowestWeightAndIndex = weightToIndexMap.firstEntry(); indicesData.put(lowestWeightAndIndex.getValue(), boneIndex.byteValue());
if(lowestWeightAndIndex != null && lowestWeightAndIndex.getKey() < weight) { weightToIndexMap.remove(lowestWeightAndIndex.getKey());
weightsFloatData.put(lowestWeightAndIndex.getValue(), weight); weightToIndexMap.put(weight, lowestWeightAndIndex.getValue());
indicesData.put(lowestWeightAndIndex.getValue(), boneIndex.byteValue()); }
weightToIndexMap.remove(lowestWeightAndIndex.getKey()); }
weightToIndexMap.put(weight, lowestWeightAndIndex.getValue()); }
} ++weightIndex;
} }
} } else {
++weightIndex; // 0.0 weight indicates, do not transform this vertex, but keep it in bind pose.
} for (Integer index : vertexIndices) {
} else { weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 0.0f);
// 0.0 weight indicates, do not transform this vertex, but keep it in bind pose. indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
for (Integer index : vertexIndices) { }
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 0.0f); }
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0); }
} ++vertexIndex;
} }
}
++vertexIndex; if (warnAboutTooManyVertexWeights) {
} LOGGER.log(Level.WARNING, "{0} has vertices with more than 4 weights assigned. The model may not behave as it should.", meshStructure.getName());
}
if(warnAboutTooManyVertexWeights) { } else {
LOGGER.log(Level.WARNING, "{0} has vertices with more than 4 weights assigned. The model may not behave as it should.", meshStructure.getName()); // always bind all vertices to 0-indexed bone
} // this bone makes the model look normally if vertices have no bone
} else { // assigned and it is used in object animation, so if we come accross object
// always bind all vertices to 0-indexed bone // animation we can use the 0-indexed bone for this
// this bone makes the model look normally if vertices have no bone for (List<Integer> vertexIndexList : vertexReferenceMap.values()) {
// assigned and it is used in object animation, so if we come accross object // we apply the weight to all referenced vertices
// animation we can use the 0-indexed bone for this for (Integer index : vertexIndexList) {
for (List<Integer> vertexIndexList : vertexReferenceMap.values()) { weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
// we apply the weight to all referenced vertices indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
for (Integer index : vertexIndexList) { }
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f); }
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0); }
}
} bonesGroups[0] = Math.max(bonesGroups[0], 1);
}
this.endBoneAssigns(vertexListSize, weightsFloatData);
bonesGroups[0] = Math.max(bonesGroups[0], 1); VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData);
this.endBoneAssigns(vertexListSize, weightsFloatData);
VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight); VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);
verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData); verticesWeightsIndices.setupData(Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData);
return new VertexBuffer[] { verticesWeights, verticesWeightsIndices };
VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex); }
verticesWeightsIndices.setupData(Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData);
return new VertexBuffer[] { verticesWeights, verticesWeightsIndices }; /**
} * Normalizes weights if needed and finds largest amount of weights used for
* all vertices in the buffer.
/** *
* Normalizes weights if needed and finds largest amount of weights used for * @param vertCount
* all vertices in the buffer. * amount of vertices
* * @param weightsFloatData
* @param vertCount * weights for vertices
* amount of vertices */
* @param weightsFloatData private void endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) {
* weights for vertices weightsFloatData.rewind();
*/ float[] weights = new float[MAXIMUM_WEIGHTS_PER_VERTEX];
private void endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) { for (int v = 0; v < vertCount; ++v) {
weightsFloatData.rewind(); float sum = 0;
float[] weights = new float[MAXIMUM_WEIGHTS_PER_VERTEX]; for (int i = 0; i < MAXIMUM_WEIGHTS_PER_VERTEX; ++i) {
for (int v = 0; v < vertCount; ++v) { weights[i] = weightsFloatData.get();
float sum = 0; sum += weights[i];
for (int i = 0; i < MAXIMUM_WEIGHTS_PER_VERTEX; ++i) { }
weights[i] = weightsFloatData.get(); if (sum != 1f && sum != 0.0f) {
sum += weights[i]; weightsFloatData.position(weightsFloatData.position() - MAXIMUM_WEIGHTS_PER_VERTEX);
} // compute new vals based on sum
if (sum != 1f && sum != 0.0f) { float sumToB = 1f / sum;
weightsFloatData.position(weightsFloatData.position() - MAXIMUM_WEIGHTS_PER_VERTEX); for (int i = 0; i < MAXIMUM_WEIGHTS_PER_VERTEX; ++i) {
// compute new vals based on sum weightsFloatData.put(weights[i] * sumToB);
float sumToB = 1f / sum; }
for (int i = 0; i < MAXIMUM_WEIGHTS_PER_VERTEX; ++i) { }
weightsFloatData.put(weights[i] * sumToB); }
} weightsFloatData.rewind();
} }
}
weightsFloatData.rewind(); @Override
} public String getType() {
return Modifier.ARMATURE_MODIFIER_DATA;
@Override }
public String getType() {
return Modifier.ARMATURE_MODIFIER_DATA;
}
} }

@ -29,116 +29,116 @@ import java.util.logging.Logger;
* *
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ArrayModifier extends Modifier { /* package */class ArrayModifier extends Modifier {
private static final Logger LOGGER = Logger.getLogger(ArrayModifier.class.getName()); private static final Logger LOGGER = Logger.getLogger(ArrayModifier.class.getName());
/** Parameters of the modifier. */ /** Parameters of the modifier. */
private Map<String, Object> modifierData = new HashMap<String, Object>(); private Map<String, Object> modifierData = new HashMap<String, Object>();
/** /**
* This constructor reads array data from the modifier structure. The * This constructor reads array data from the modifier structure. The
* stored data is a map of parameters for array modifier. No additional data * stored data is a map of parameters for array modifier. No additional data
* is loaded. * is loaded.
* *
* @param objectStructure * @param objectStructure
* the structure of the object * the structure of the object
* @param modifierStructure * @param modifierStructure
* the structure of the modifier * the structure of the modifier
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blender file is somehow * this exception is thrown when the blender file is somehow
* corrupted * corrupted
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public ArrayModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { public ArrayModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
if(this.validate(modifierStructure, blenderContext)) { if (this.validate(modifierStructure, blenderContext)) {
Number fittype = (Number) modifierStructure.getFieldValue("fit_type"); Number fittype = (Number) modifierStructure.getFieldValue("fit_type");
modifierData.put("fittype", fittype); modifierData.put("fittype", fittype);
switch (fittype.intValue()) { switch (fittype.intValue()) {
case 0:// FIXED COUNT case 0:// FIXED COUNT
modifierData.put("count", modifierStructure.getFieldValue("count")); modifierData.put("count", modifierStructure.getFieldValue("count"));
break; break;
case 1:// FIXED LENGTH case 1:// FIXED LENGTH
modifierData.put("length", modifierStructure.getFieldValue("length")); modifierData.put("length", modifierStructure.getFieldValue("length"));
break; break;
case 2:// FITCURVE case 2:// FITCURVE
Pointer pCurveOb = (Pointer) modifierStructure.getFieldValue("curve_ob"); Pointer pCurveOb = (Pointer) modifierStructure.getFieldValue("curve_ob");
float length = 0; float length = 0;
if (pCurveOb.isNotNull()) { if (pCurveOb.isNotNull()) {
Structure curveStructure = pCurveOb.fetchData(blenderContext.getInputStream()).get(0); Structure curveStructure = pCurveOb.fetchData(blenderContext.getInputStream()).get(0);
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
Node curveObject = (Node) objectHelper.toObject(curveStructure, blenderContext); Node curveObject = (Node) objectHelper.toObject(curveStructure, blenderContext);
Set<Number> referencesToCurveLengths = new HashSet<Number>(curveObject.getChildren().size()); Set<Number> referencesToCurveLengths = new HashSet<Number>(curveObject.getChildren().size());
for (Spatial spatial : curveObject.getChildren()) { for (Spatial spatial : curveObject.getChildren()) {
if (spatial instanceof Geometry) { if (spatial instanceof Geometry) {
Mesh mesh = ((Geometry) spatial).getMesh(); Mesh mesh = ((Geometry) spatial).getMesh();
if (mesh instanceof Curve) { if (mesh instanceof Curve) {
length += ((Curve) mesh).getLength(); length += ((Curve) mesh).getLength();
} else { } else {
//if bevel object has several parts then each mesh will have the same reference // if bevel object has several parts then each mesh will have the same reference
//to length value (and we should use only one) // to length value (and we should use only one)
Number curveLength = spatial.getUserData("curveLength"); Number curveLength = spatial.getUserData("curveLength");
if (curveLength != null && !referencesToCurveLengths.contains(curveLength)) { if (curveLength != null && !referencesToCurveLengths.contains(curveLength)) {
length += curveLength.floatValue(); length += curveLength.floatValue();
referencesToCurveLengths.add(curveLength); referencesToCurveLengths.add(curveLength);
} }
} }
} }
} }
} }
modifierData.put("length", Float.valueOf(length)); modifierData.put("length", Float.valueOf(length));
modifierData.put("fittype", Integer.valueOf(1));// treat it like FIXED LENGTH modifierData.put("fittype", Integer.valueOf(1));// treat it like FIXED LENGTH
break; break;
default: default:
assert false : "Unknown array modifier fit type: " + fittype; assert false : "Unknown array modifier fit type: " + fittype;
} }
// offset parameters // offset parameters
int offsettype = ((Number) modifierStructure.getFieldValue("offset_type")).intValue(); int offsettype = ((Number) modifierStructure.getFieldValue("offset_type")).intValue();
if ((offsettype & 0x01) != 0) {// Constant offset if ((offsettype & 0x01) != 0) {// Constant offset
DynamicArray<Number> offsetArray = (DynamicArray<Number>) modifierStructure.getFieldValue("offset"); DynamicArray<Number> offsetArray = (DynamicArray<Number>) modifierStructure.getFieldValue("offset");
float[] offset = new float[]{offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue()}; float[] offset = new float[] { offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue() };
modifierData.put("offset", offset); modifierData.put("offset", offset);
} }
if ((offsettype & 0x02) != 0) {// Relative offset if ((offsettype & 0x02) != 0) {// Relative offset
DynamicArray<Number> scaleArray = (DynamicArray<Number>) modifierStructure.getFieldValue("scale"); DynamicArray<Number> scaleArray = (DynamicArray<Number>) modifierStructure.getFieldValue("scale");
float[] scale = new float[]{scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue()}; float[] scale = new float[] { scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue() };
modifierData.put("scale", scale); modifierData.put("scale", scale);
} }
if ((offsettype & 0x04) != 0) {// Object offset if ((offsettype & 0x04) != 0) {// Object offset
Pointer pOffsetObject = (Pointer) modifierStructure.getFieldValue("offset_ob"); Pointer pOffsetObject = (Pointer) modifierStructure.getFieldValue("offset_ob");
if (pOffsetObject.isNotNull()) { if (pOffsetObject.isNotNull()) {
modifierData.put("offsetob", pOffsetObject); modifierData.put("offsetob", pOffsetObject);
} }
} }
// start cap and end cap // start cap and end cap
Pointer pStartCap = (Pointer) modifierStructure.getFieldValue("start_cap"); Pointer pStartCap = (Pointer) modifierStructure.getFieldValue("start_cap");
if (pStartCap.isNotNull()) { if (pStartCap.isNotNull()) {
modifierData.put("startcap", pStartCap); modifierData.put("startcap", pStartCap);
} }
Pointer pEndCap = (Pointer) modifierStructure.getFieldValue("end_cap"); Pointer pEndCap = (Pointer) modifierStructure.getFieldValue("end_cap");
if (pEndCap.isNotNull()) { if (pEndCap.isNotNull()) {
modifierData.put("endcap", pEndCap); modifierData.put("endcap", pEndCap);
} }
} }
} }
@Override @Override
public Node apply(Node node, BlenderContext blenderContext) { public Node apply(Node node, BlenderContext blenderContext) {
if(invalid) { if (invalid) {
LOGGER.log(Level.WARNING, "Array modifier is invalid! Cannot be applied to: {0}", node.getName()); LOGGER.log(Level.WARNING, "Array modifier is invalid! Cannot be applied to: {0}", node.getName());
return node; return node;
} }
int fittype = ((Number) modifierData.get("fittype")).intValue(); int fittype = ((Number) modifierData.get("fittype")).intValue();
float[] offset = (float[]) modifierData.get("offset"); float[] offset = (float[]) modifierData.get("offset");
if (offset == null) {// the node will be repeated several times in the same place if (offset == null) {// the node will be repeated several times in the same place
offset = new float[]{0.0f, 0.0f, 0.0f}; offset = new float[] { 0.0f, 0.0f, 0.0f };
} }
float[] scale = (float[]) modifierData.get("scale"); float[] scale = (float[]) modifierData.get("scale");
if (scale == null) {// the node will be repeated several times in the same place if (scale == null) {// the node will be repeated several times in the same place
scale = new float[]{0.0f, 0.0f, 0.0f}; scale = new float[] { 0.0f, 0.0f, 0.0f };
} else { } else {
// getting bounding box // getting bounding box
node.updateModelBound(); node.updateModelBound();
@ -158,7 +158,7 @@ import java.util.logging.Logger;
} }
// adding object's offset // adding object's offset
float[] objectOffset = new float[]{0.0f, 0.0f, 0.0f}; float[] objectOffset = new float[] { 0.0f, 0.0f, 0.0f };
Pointer pOffsetObject = (Pointer) modifierData.get("offsetob"); Pointer pOffsetObject = (Pointer) modifierData.get("offsetob");
if (pOffsetObject != null) { if (pOffsetObject != null) {
FileBlockHeader offsetObjectBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress()); FileBlockHeader offsetObjectBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress());
@ -175,8 +175,8 @@ import java.util.logging.Logger;
} }
// getting start and end caps // getting start and end caps
Node[] caps = new Node[]{null, null}; Node[] caps = new Node[] { null, null };
Pointer[] pCaps = new Pointer[]{(Pointer) modifierData.get("startcap"), (Pointer) modifierData.get("endcap")}; Pointer[] pCaps = new Pointer[] { (Pointer) modifierData.get("startcap"), (Pointer) modifierData.get("endcap") };
for (int i = 0; i < pCaps.length; ++i) { for (int i = 0; i < pCaps.length; ++i) {
if (pCaps[i] != null) { if (pCaps[i] != null) {
caps[i] = (Node) blenderContext.getLoadedFeature(pCaps[i].getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); caps[i] = (Node) blenderContext.getLoadedFeature(pCaps[i].getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
@ -238,10 +238,10 @@ import java.util.logging.Logger;
} }
} }
return node; return node;
} }
@Override @Override
public String getType() { public String getType() {
return ARRAY_MODIFIER_DATA; return ARRAY_MODIFIER_DATA;
} }
} }

@ -30,58 +30,54 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
* *
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class MirrorModifier extends Modifier { /* package */class MirrorModifier extends Modifier {
private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName()); private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName());
/** Parameters of the modifier. */ /** Parameters of the modifier. */
private Map<String, Object> modifierData = new HashMap<String, Object>(); private Map<String, Object> modifierData = new HashMap<String, Object>();
/** /**
* This constructor reads mirror data from the modifier structure. The * This constructor reads mirror data from the modifier structure. The
* stored data is a map of parameters for mirror modifier. No additional data * stored data is a map of parameters for mirror modifier. No additional data
* is loaded. * is loaded.
* When the modifier is applied it is necessary to get the newly created node. * When the modifier is applied it is necessary to get the newly created node.
* *
* @param objectStructure * @param objectStructure
* the structure of the object * the structure of the object
* @param modifierStructure * @param modifierStructure
* the structure of the modifier * the structure of the modifier
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blender file is somehow * this exception is thrown when the blender file is somehow
* corrupted * corrupted
*/ */
public MirrorModifier(Structure modifierStructure, BlenderContext blenderContext) { public MirrorModifier(Structure modifierStructure, BlenderContext blenderContext) {
if(this.validate(modifierStructure, blenderContext)) { if (this.validate(modifierStructure, blenderContext)) {
modifierData.put("flag", modifierStructure.getFieldValue("flag")); modifierData.put("flag", modifierStructure.getFieldValue("flag"));
modifierData.put("tolerance", modifierStructure.getFieldValue("tolerance")); modifierData.put("tolerance", modifierStructure.getFieldValue("tolerance"));
Pointer pMirrorOb = (Pointer) modifierStructure.getFieldValue("mirror_ob"); Pointer pMirrorOb = (Pointer) modifierStructure.getFieldValue("mirror_ob");
if (pMirrorOb.isNotNull()) { if (pMirrorOb.isNotNull()) {
modifierData.put("mirrorob", pMirrorOb); modifierData.put("mirrorob", pMirrorOb);
} }
} }
} }
@Override @Override
public Node apply(Node node, BlenderContext blenderContext) { public Node apply(Node node, BlenderContext blenderContext) {
if(invalid) { if (invalid) {
LOGGER.log(Level.WARNING, "Mirror modifier is invalid! Cannot be applied to: {0}", node.getName()); LOGGER.log(Level.WARNING, "Mirror modifier is invalid! Cannot be applied to: {0}", node.getName());
return node; return node;
} }
int flag = ((Number) modifierData.get("flag")).intValue(); int flag = ((Number) modifierData.get("flag")).intValue();
float[] mirrorFactor = new float[]{ float[] mirrorFactor = new float[] { (flag & 0x08) != 0 ? -1.0f : 1.0f, (flag & 0x10) != 0 ? -1.0f : 1.0f, (flag & 0x20) != 0 ? -1.0f : 1.0f };
(flag & 0x08) != 0 ? -1.0f : 1.0f, if (blenderContext.getBlenderKey().isFixUpAxis()) {
(flag & 0x10) != 0 ? -1.0f : 1.0f, float temp = mirrorFactor[1];
(flag & 0x20) != 0 ? -1.0f : 1.0f mirrorFactor[1] = mirrorFactor[2];
}; mirrorFactor[2] = temp;
if(blenderContext.getBlenderKey().isFixUpAxis()) {
float temp = mirrorFactor[1];
mirrorFactor[1] = mirrorFactor[2];
mirrorFactor[2] = temp;
} }
float[] center = new float[]{0.0f, 0.0f, 0.0f}; float[] center = new float[] { 0.0f, 0.0f, 0.0f };
Pointer pObject = (Pointer) modifierData.get("mirrorob"); Pointer pObject = (Pointer) modifierData.get("mirrorob");
if (pObject != null) { if (pObject != null) {
Structure objectStructure; Structure objectStructure;
@ -102,7 +98,7 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
float tolerance = ((Number) modifierData.get("tolerance")).floatValue(); float tolerance = ((Number) modifierData.get("tolerance")).floatValue();
boolean mirrorU = (flag & 0x01) != 0; boolean mirrorU = (flag & 0x01) != 0;
boolean mirrorV = (flag & 0x02) != 0; boolean mirrorV = (flag & 0x02) != 0;
// boolean mirrorVGroup = (flag & 0x20) != 0; // boolean mirrorVGroup = (flag & 0x20) != 0;
Set<Integer> modifiedIndexes = new HashSet<Integer>(); Set<Integer> modifiedIndexes = new HashSet<Integer>();
List<Geometry> geometriesToAdd = new ArrayList<Geometry>(); List<Geometry> geometriesToAdd = new ArrayList<Geometry>();
@ -122,50 +118,50 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
FloatBuffer cloneNormals = clone.getFloatBuffer(Type.Normal); FloatBuffer cloneNormals = clone.getFloatBuffer(Type.Normal);
FloatBuffer cloneBindPoseNormals = clone.getFloatBuffer(Type.BindPoseNormal); FloatBuffer cloneBindPoseNormals = clone.getFloatBuffer(Type.BindPoseNormal);
Buffer cloneIndexes = clone.getBuffer(Type.Index).getData(); Buffer cloneIndexes = clone.getBuffer(Type.Index).getData();
for (int i = 0; i < cloneIndexes.limit(); ++i) { for (int i = 0; i < cloneIndexes.limit(); ++i) {
int index = cloneIndexes instanceof ShortBuffer ? ((ShortBuffer)cloneIndexes).get(i) : ((IntBuffer)cloneIndexes).get(i); int index = cloneIndexes instanceof ShortBuffer ? ((ShortBuffer) cloneIndexes).get(i) : ((IntBuffer) cloneIndexes).get(i);
if(!modifiedIndexes.contains((int)index)) { if (!modifiedIndexes.contains((int) index)) {
modifiedIndexes.add((int)index); modifiedIndexes.add((int) index);
int valueIndex = index * 3 + mirrorIndex; int valueIndex = index * 3 + mirrorIndex;
float value = clonePosition.get(valueIndex); float value = clonePosition.get(valueIndex);
float d = center[mirrorIndex] - value; float d = center[mirrorIndex] - value;
if (Math.abs(d) <= tolerance) { if (Math.abs(d) <= tolerance) {
clonePosition.put(valueIndex, center[mirrorIndex]); clonePosition.put(valueIndex, center[mirrorIndex]);
if(cloneBindPosePosition != null) { if (cloneBindPosePosition != null) {
cloneBindPosePosition.put(valueIndex, center[mirrorIndex]); cloneBindPosePosition.put(valueIndex, center[mirrorIndex]);
} }
position.put(valueIndex, center[mirrorIndex]); position.put(valueIndex, center[mirrorIndex]);
if(bindPosePosition != null) { if (bindPosePosition != null) {
bindPosePosition.put(valueIndex, center[mirrorIndex]); bindPosePosition.put(valueIndex, center[mirrorIndex]);
} }
} else { } else {
clonePosition.put(valueIndex, value + 2.0f * d); clonePosition.put(valueIndex, value + 2.0f * d);
if(cloneBindPosePosition != null) { if (cloneBindPosePosition != null) {
cloneBindPosePosition.put(valueIndex, value + 2.0f * d); cloneBindPosePosition.put(valueIndex, value + 2.0f * d);
} }
} }
cloneNormals.put(valueIndex, -cloneNormals.get(valueIndex)); cloneNormals.put(valueIndex, -cloneNormals.get(valueIndex));
if(cloneBindPoseNormals != null) { if (cloneBindPoseNormals != null) {
cloneBindPoseNormals.put(valueIndex, -cloneNormals.get(valueIndex)); cloneBindPoseNormals.put(valueIndex, -cloneNormals.get(valueIndex));
} }
} }
} }
modifiedIndexes.clear(); modifiedIndexes.clear();
//flipping index order // flipping index order
for (int i = 0; i < cloneIndexes.limit(); i += 3) { for (int i = 0; i < cloneIndexes.limit(); i += 3) {
if(cloneIndexes instanceof ShortBuffer) { if (cloneIndexes instanceof ShortBuffer) {
short index = ((ShortBuffer)cloneIndexes).get(i + 2); short index = ((ShortBuffer) cloneIndexes).get(i + 2);
((ShortBuffer)cloneIndexes).put(i + 2, ((ShortBuffer)cloneIndexes).get(i + 1)); ((ShortBuffer) cloneIndexes).put(i + 2, ((ShortBuffer) cloneIndexes).get(i + 1));
((ShortBuffer)cloneIndexes).put(i + 1, index); ((ShortBuffer) cloneIndexes).put(i + 1, index);
} else { } else {
int index = ((IntBuffer)cloneIndexes).get(i + 2); int index = ((IntBuffer) cloneIndexes).get(i + 2);
((IntBuffer)cloneIndexes).put(i + 2, ((IntBuffer)cloneIndexes).get(i + 1)); ((IntBuffer) cloneIndexes).put(i + 2, ((IntBuffer) cloneIndexes).get(i + 1));
((IntBuffer)cloneIndexes).put(i + 1, index); ((IntBuffer) cloneIndexes).put(i + 1, index);
} }
} }
if (mirrorU && clone.getBuffer(Type.TexCoord) != null) { if (mirrorU && clone.getBuffer(Type.TexCoord) != null) {
@ -195,10 +191,10 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
} }
} }
return node; return node;
} }
@Override @Override
public String getType() { public String getType() {
return Modifier.MIRROR_MODIFIER_DATA; return Modifier.MIRROR_MODIFIER_DATA;
} }
} }

@ -14,65 +14,65 @@ import com.jme3.scene.plugins.blender.file.Structure;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public abstract class Modifier { public abstract class Modifier {
public static final String ARRAY_MODIFIER_DATA = "ArrayModifierData"; public static final String ARRAY_MODIFIER_DATA = "ArrayModifierData";
public static final String ARMATURE_MODIFIER_DATA = "ArmatureModifierData"; public static final String ARMATURE_MODIFIER_DATA = "ArmatureModifierData";
public static final String PARTICLE_MODIFIER_DATA = "ParticleSystemModifierData"; public static final String PARTICLE_MODIFIER_DATA = "ParticleSystemModifierData";
public static final String MIRROR_MODIFIER_DATA = "MirrorModifierData"; public static final String MIRROR_MODIFIER_DATA = "MirrorModifierData";
public static final String SUBSURF_MODIFIER_DATA = "SubsurfModifierData"; public static final String SUBSURF_MODIFIER_DATA = "SubsurfModifierData";
public static final String OBJECT_ANIMATION_MODIFIER_DATA = "ObjectAnimationModifierData"; public static final String OBJECT_ANIMATION_MODIFIER_DATA = "ObjectAnimationModifierData";
/** This variable indicates if the modifier is invalid (<b>true</b>) or not (<b>false</b>). */ /** This variable indicates if the modifier is invalid (<b>true</b>) or not (<b>false</b>). */
protected boolean invalid; protected boolean invalid;
/** /**
* A variable that tells if the modifier causes modification. Some modifiers like ArmatureModifier might have no * A variable that tells if the modifier causes modification. Some modifiers like ArmatureModifier might have no
* Armature object attached and thus not really modifying the feature. In such cases it is good to know if it is * Armature object attached and thus not really modifying the feature. In such cases it is good to know if it is
* sense to add the modifier to the list of object's modifiers. * sense to add the modifier to the list of object's modifiers.
*/ */
protected boolean modifying = true; protected boolean modifying = true;
/**
* This method applies the modifier to the given node.
*
* @param node
* the node that will have modifier applied
* @param blenderContext
* the blender context
* @return the node with applied modifier
*/
public abstract Node apply(Node node, BlenderContext blenderContext);
/** /**
* This method returns blender's type of modifier. * This method applies the modifier to the given node.
* *
* @return blender's type of modifier * @param node
*/ * the node that will have modifier applied
public abstract String getType(); * @param blenderContext
* the blender context
/** * @return the node with applied modifier
* Determines if the modifier can be applied multiple times over one mesh. */
* At this moment only armature and object animation modifiers cannot be public abstract Node apply(Node node, BlenderContext blenderContext);
* applied multiple times.
* /**
* @param modifierType * This method returns blender's type of modifier.
* the type name of the modifier *
* @return <b>true</b> if the modifier can be applied many times and * @return blender's type of modifier
* <b>false</b> otherwise */
*/ public abstract String getType();
public static boolean canBeAppliedMultipleTimes(String modifierType) {
return !(ARMATURE_MODIFIER_DATA.equals(modifierType) || OBJECT_ANIMATION_MODIFIER_DATA.equals(modifierType)); /**
} * Determines if the modifier can be applied multiple times over one mesh.
* At this moment only armature and object animation modifiers cannot be
protected boolean validate(Structure modifierStructure, BlenderContext blenderContext) { * applied multiple times.
Structure modifierData = (Structure)modifierStructure.getFieldValue("modifier"); *
Pointer pError = (Pointer) modifierData.getFieldValue("error"); * @param modifierType
invalid = pError.isNotNull(); * the type name of the modifier
return !invalid; * @return <b>true</b> if the modifier can be applied many times and
} * <b>false</b> otherwise
*/
/** public static boolean canBeAppliedMultipleTimes(String modifierType) {
* @return <b>true</b> if the modifier causes feature's modification or <b>false</b> if not return !(ARMATURE_MODIFIER_DATA.equals(modifierType) || OBJECT_ANIMATION_MODIFIER_DATA.equals(modifierType));
*/ }
public boolean isModifying() {
return modifying; protected boolean validate(Structure modifierStructure, BlenderContext blenderContext) {
} Structure modifierData = (Structure) modifierStructure.getFieldValue("modifier");
Pointer pError = (Pointer) modifierData.getFieldValue("error");
invalid = pError.isNotNull();
return !invalid;
}
/**
* @return <b>true</b> if the modifier causes feature's modification or <b>false</b> if not
*/
public boolean isModifying() {
return modifying;
}
} }

@ -53,143 +53,143 @@ import java.util.logging.Logger;
*/ */
public class ModifierHelper extends AbstractBlenderHelper { public class ModifierHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(ModifierHelper.class.getName()); private static final Logger LOGGER = Logger.getLogger(ModifierHelper.class.getName());
/** /**
* This constructor parses the given blender version and stores the result. * This constructor parses the given blender version and stores the result.
* Some functionalities may differ in different blender versions. * Some functionalities may differ in different blender versions.
* *
* @param blenderVersion * @param blenderVersion
* the version read from the blend file * the version read from the blend file
* @param fixUpAxis * @param fixUpAxis
* a variable that indicates if the Y asxis is the UP axis or not * a variable that indicates if the Y asxis is the UP axis or not
*/ */
public ModifierHelper(String blenderVersion, boolean fixUpAxis) { public ModifierHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis); super(blenderVersion, fixUpAxis);
} }
/** /**
* This method reads the given object's modifiers. * This method reads the given object's modifiers.
* *
* @param objectStructure * @param objectStructure
* the object structure * the object structure
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blender file is somehow * this exception is thrown when the blender file is somehow
* corrupted * corrupted
*/ */
public Collection<Modifier> readModifiers(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { public Collection<Modifier> readModifiers(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
Set<String> alreadyReadModifiers = new HashSet<String>(); Set<String> alreadyReadModifiers = new HashSet<String>();
Collection<Modifier> result = new ArrayList<Modifier>(); Collection<Modifier> result = new ArrayList<Modifier>();
Structure modifiersListBase = (Structure) objectStructure.getFieldValue("modifiers"); Structure modifiersListBase = (Structure) objectStructure.getFieldValue("modifiers");
List<Structure> modifiers = modifiersListBase.evaluateListBase(blenderContext); List<Structure> modifiers = modifiersListBase.evaluateListBase(blenderContext);
for (Structure modifierStructure : modifiers) { for (Structure modifierStructure : modifiers) {
String modifierType = modifierStructure.getType(); String modifierType = modifierStructure.getType();
if(!Modifier.canBeAppliedMultipleTimes(modifierType) && alreadyReadModifiers.contains(modifierType)) { if (!Modifier.canBeAppliedMultipleTimes(modifierType) && alreadyReadModifiers.contains(modifierType)) {
LOGGER.log(Level.WARNING, "Modifier {0} can only be applied once to object: {1}", new Object[] { modifierType, objectStructure.getName() }); LOGGER.log(Level.WARNING, "Modifier {0} can only be applied once to object: {1}", new Object[] { modifierType, objectStructure.getName() });
} else { } else {
Modifier modifier = null; Modifier modifier = null;
if (Modifier.ARRAY_MODIFIER_DATA.equals(modifierStructure.getType())) { if (Modifier.ARRAY_MODIFIER_DATA.equals(modifierStructure.getType())) {
modifier = new ArrayModifier(modifierStructure, blenderContext); modifier = new ArrayModifier(modifierStructure, blenderContext);
} else if (Modifier.MIRROR_MODIFIER_DATA.equals(modifierStructure.getType())) { } else if (Modifier.MIRROR_MODIFIER_DATA.equals(modifierStructure.getType())) {
modifier = new MirrorModifier(modifierStructure, blenderContext); modifier = new MirrorModifier(modifierStructure, blenderContext);
} else if (Modifier.ARMATURE_MODIFIER_DATA.equals(modifierStructure.getType())) { } else if (Modifier.ARMATURE_MODIFIER_DATA.equals(modifierStructure.getType())) {
modifier = new ArmatureModifier(objectStructure, modifierStructure, blenderContext); modifier = new ArmatureModifier(objectStructure, modifierStructure, blenderContext);
} else if (Modifier.PARTICLE_MODIFIER_DATA.equals(modifierStructure.getType())) { } else if (Modifier.PARTICLE_MODIFIER_DATA.equals(modifierStructure.getType())) {
modifier = new ParticlesModifier(modifierStructure, blenderContext); modifier = new ParticlesModifier(modifierStructure, blenderContext);
} }
if (modifier != null) { if (modifier != null) {
if(modifier.isModifying()) { if (modifier.isModifying()) {
result.add(modifier); result.add(modifier);
blenderContext.addModifier(objectStructure.getOldMemoryAddress(), modifier); blenderContext.addModifier(objectStructure.getOldMemoryAddress(), modifier);
alreadyReadModifiers.add(modifierType); alreadyReadModifiers.add(modifierType);
} else { } else {
LOGGER.log(Level.WARNING, "The modifier {0} will cause no changes in the model. It will be ignored!", modifierStructure.getName()); LOGGER.log(Level.WARNING, "The modifier {0} will cause no changes in the model. It will be ignored!", modifierStructure.getName());
} }
} else { } else {
LOGGER.log(Level.WARNING, "Unsupported modifier type: {0}", modifierStructure.getType()); LOGGER.log(Level.WARNING, "Unsupported modifier type: {0}", modifierStructure.getType());
} }
} }
} }
// at the end read object's animation modifier (object animation is // at the end read object's animation modifier (object animation is
// either described by action or by ipo of the object) // either described by action or by ipo of the object)
Modifier modifier; Modifier modifier;
if (blenderVersion <= 249) { if (blenderVersion <= 249) {
modifier = this.readAnimationModifier249(objectStructure, blenderContext); modifier = this.readAnimationModifier249(objectStructure, blenderContext);
} else { } else {
modifier = this.readAnimationModifier250(objectStructure, blenderContext); modifier = this.readAnimationModifier250(objectStructure, blenderContext);
} }
if (modifier != null) { if (modifier != null) {
result.add(modifier); result.add(modifier);
} }
return result; return result;
} }
@Override @Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return true; return true;
} }
/** /**
* This method reads the object's animation modifier for blender version * This method reads the object's animation modifier for blender version
* 2.49 and lower. * 2.49 and lower.
* *
* @param objectStructure * @param objectStructure
* the object's structure * the object's structure
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @return loaded modifier * @return loaded modifier
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blender file is somehow * this exception is thrown when the blender file is somehow
* corrupted * corrupted
*/ */
private Modifier readAnimationModifier249(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { private Modifier readAnimationModifier249(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
Modifier result = null; Modifier result = null;
Pointer pIpo = (Pointer) objectStructure.getFieldValue("ipo"); Pointer pIpo = (Pointer) objectStructure.getFieldValue("ipo");
if (pIpo.isNotNull()) { if (pIpo.isNotNull()) {
IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);
Structure ipoStructure = pIpo.fetchData(blenderContext.getInputStream()).get(0); Structure ipoStructure = pIpo.fetchData(blenderContext.getInputStream()).get(0);
Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext); Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext);
if(ipo != null) { if (ipo != null) {
result = new ObjectAnimationModifier(ipo, objectStructure.getName(), objectStructure.getOldMemoryAddress(), blenderContext); result = new ObjectAnimationModifier(ipo, objectStructure.getName(), objectStructure.getOldMemoryAddress(), blenderContext);
blenderContext.addModifier(objectStructure.getOldMemoryAddress(), result); blenderContext.addModifier(objectStructure.getOldMemoryAddress(), result);
} }
} }
return result; return result;
} }
/** /**
* This method reads the object's animation modifier for blender version * This method reads the object's animation modifier for blender version
* 2.50 and higher. * 2.50 and higher.
* *
* @param objectStructure * @param objectStructure
* the object's structure * the object's structure
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @return loaded modifier * @return loaded modifier
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blender file is somehow * this exception is thrown when the blender file is somehow
* corrupted * corrupted
*/ */
private Modifier readAnimationModifier250(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { private Modifier readAnimationModifier250(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
Modifier result = null; Modifier result = null;
Pointer pAnimData = (Pointer) objectStructure.getFieldValue("adt"); Pointer pAnimData = (Pointer) objectStructure.getFieldValue("adt");
if (pAnimData.isNotNull()) { if (pAnimData.isNotNull()) {
Structure animData = pAnimData.fetchData(blenderContext.getInputStream()).get(0); Structure animData = pAnimData.fetchData(blenderContext.getInputStream()).get(0);
Pointer pAction = (Pointer) animData.getFieldValue("action"); Pointer pAction = (Pointer) animData.getFieldValue("action");
if (pAction.isNotNull()) { if (pAction.isNotNull()) {
Structure actionStructure = pAction.fetchData(blenderContext.getInputStream()).get(0); Structure actionStructure = pAction.fetchData(blenderContext.getInputStream()).get(0);
IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);
Ipo ipo = ipoHelper.fromAction(actionStructure, blenderContext); Ipo ipo = ipoHelper.fromAction(actionStructure, blenderContext);
if(ipo != null) {//ipo can be null if it has no curves applied, ommit such modifier then if (ipo != null) {// ipo can be null if it has no curves applied, ommit such modifier then
result = new ObjectAnimationModifier(ipo, actionStructure.getName(), objectStructure.getOldMemoryAddress(), blenderContext); result = new ObjectAnimationModifier(ipo, actionStructure.getName(), objectStructure.getOldMemoryAddress(), blenderContext);
blenderContext.addModifier(objectStructure.getOldMemoryAddress(), result); blenderContext.addModifier(objectStructure.getOldMemoryAddress(), result);
} }
} }
} }
return result; return result;
} }
} }

@ -22,73 +22,73 @@ import com.jme3.scene.plugins.ogre.AnimData;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class ObjectAnimationModifier extends Modifier { /* package */class ObjectAnimationModifier extends Modifier {
private static final Logger LOGGER = Logger.getLogger(ObjectAnimationModifier.class.getName()); private static final Logger LOGGER = Logger.getLogger(ObjectAnimationModifier.class.getName());
/** Loaded animation data. */ /** Loaded animation data. */
private AnimData animData; private AnimData animData;
/** /**
* This constructor reads animation of the object itself (without bones) and * This constructor reads animation of the object itself (without bones) and
* stores it as an ArmatureModifierData modifier. The animation is returned * stores it as an ArmatureModifierData modifier. The animation is returned
* as a modifier. It should be later applied regardless other modifiers. The * as a modifier. It should be later applied regardless other modifiers. The
* reason for this is that object may not have modifiers added but it's * reason for this is that object may not have modifiers added but it's
* animation should be working. The stored modifier is an anim data and * animation should be working. The stored modifier is an anim data and
* additional data is given object's OMA. * additional data is given object's OMA.
* *
* @param ipo * @param ipo
* the object's interpolation curves * the object's interpolation curves
* @param objectAnimationName * @param objectAnimationName
* the name of object's animation * the name of object's animation
* @param objectOMA * @param objectOMA
* the OMA of the object * the OMA of the object
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blender file is somehow * this exception is thrown when the blender file is somehow
* corrupted * corrupted
*/ */
public ObjectAnimationModifier(Ipo ipo, String objectAnimationName, Long objectOMA, BlenderContext blenderContext) throws BlenderFileException { public ObjectAnimationModifier(Ipo ipo, String objectAnimationName, Long objectOMA, BlenderContext blenderContext) throws BlenderFileException {
int fps = blenderContext.getBlenderKey().getFps(); int fps = blenderContext.getBlenderKey().getFps();
Spatial object = (Spatial) blenderContext.getLoadedFeature(objectOMA, LoadedFeatureDataType.LOADED_FEATURE);
// calculating track
SpatialTrack track = (SpatialTrack) ipo.calculateTrack(-1, object.getLocalRotation(), 0, ipo.getLastFrame(), fps, true);
Animation animation = new Animation(objectAnimationName, ipo.getLastFrame() / (float)fps); Spatial object = (Spatial) blenderContext.getLoadedFeature(objectOMA, LoadedFeatureDataType.LOADED_FEATURE);
animation.setTracks(new SpatialTrack[] { track }); // calculating track
ArrayList<Animation> animations = new ArrayList<Animation>(1); SpatialTrack track = (SpatialTrack) ipo.calculateTrack(-1, object.getLocalRotation(), 0, ipo.getLastFrame(), fps, true);
animations.add(animation);
animData = new AnimData(null, animations); Animation animation = new Animation(objectAnimationName, ipo.getLastFrame() / (float) fps);
blenderContext.setAnimData(objectOMA, animData); animation.setTracks(new SpatialTrack[] { track });
} ArrayList<Animation> animations = new ArrayList<Animation>(1);
animations.add(animation);
@Override animData = new AnimData(null, animations);
public Node apply(Node node, BlenderContext blenderContext) { blenderContext.setAnimData(objectOMA, animData);
if (invalid) { }
LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName());
}// if invalid, animData will be null
if (animData != null) {
// INFO: constraints for this modifier are applied in the
// ObjectHelper when the whole object is loaded
ArrayList<Animation> animList = animData.anims;
if (animList != null && animList.size() > 0) {
HashMap<String, Animation> anims = new HashMap<String, Animation>();
for (int i = 0; i < animList.size(); ++i) {
Animation animation = animList.get(i);
anims.put(animation.getName(), animation);
}
AnimControl control = new AnimControl(null); @Override
control.setAnimations(anims); public Node apply(Node node, BlenderContext blenderContext) {
node.addControl(control); if (invalid) {
} LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName());
} }// if invalid, animData will be null
return node; if (animData != null) {
} // INFO: constraints for this modifier are applied in the
// ObjectHelper when the whole object is loaded
ArrayList<Animation> animList = animData.anims;
if (animList != null && animList.size() > 0) {
HashMap<String, Animation> anims = new HashMap<String, Animation>();
for (int i = 0; i < animList.size(); ++i) {
Animation animation = animList.get(i);
anims.put(animation.getName(), animation);
}
@Override AnimControl control = new AnimControl(null);
public String getType() { control.setAnimations(anims);
return Modifier.OBJECT_ANIMATION_MODIFIER_DATA; node.addControl(control);
} }
}
return node;
}
@Override
public String getType() {
return Modifier.OBJECT_ANIMATION_MODIFIER_DATA;
}
} }

@ -25,77 +25,76 @@ import java.util.logging.Logger;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class ParticlesModifier extends Modifier { /* package */class ParticlesModifier extends Modifier {
private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName()); private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName());
/** Loaded particles emitter. */
private ParticleEmitter particleEmitter;
/**
* This constructor reads the particles system structure and stores it in
* order to apply it later to the node.
*
* @param modifierStructure
* the structure of the modifier
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is throw wneh there are problems with the
* blender file
*/
public ParticlesModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
if(this.validate(modifierStructure, blenderContext)) {
Pointer pParticleSystem = (Pointer) modifierStructure.getFieldValue("psys");
if (pParticleSystem.isNotNull()) {
ParticlesHelper particlesHelper = blenderContext.getHelper(ParticlesHelper.class);
Structure particleSystem = pParticleSystem.fetchData(blenderContext.getInputStream()).get(0);
particleEmitter = particlesHelper.toParticleEmitter(particleSystem, blenderContext);
}
}
}
@Override /** Loaded particles emitter. */
public Node apply(Node node, BlenderContext blenderContext) { private ParticleEmitter particleEmitter;
if(invalid) {
LOGGER.log(Level.WARNING, "Particles modifier is invalid! Cannot be applied to: {0}", node.getName());
return node;
}
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
ParticleEmitter emitter = particleEmitter.clone();
// veryfying the alpha function for particles' texture /**
Integer alphaFunction = MaterialHelper.ALPHA_MASK_HYPERBOLE; * This constructor reads the particles system structure and stores it in
char nameSuffix = emitter.getName().charAt(emitter.getName().length() - 1); * order to apply it later to the node.
if (nameSuffix == 'B' || nameSuffix == 'N') { *
alphaFunction = MaterialHelper.ALPHA_MASK_NONE; * @param modifierStructure
} * the structure of the modifier
// removing the type suffix from the name * @param blenderContext
emitter.setName(emitter.getName().substring(0, emitter.getName().length() - 1)); * the blender context
* @throws BlenderFileException
* an exception is throw wneh there are problems with the
* blender file
*/
public ParticlesModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
if (this.validate(modifierStructure, blenderContext)) {
Pointer pParticleSystem = (Pointer) modifierStructure.getFieldValue("psys");
if (pParticleSystem.isNotNull()) {
ParticlesHelper particlesHelper = blenderContext.getHelper(ParticlesHelper.class);
Structure particleSystem = pParticleSystem.fetchData(blenderContext.getInputStream()).get(0);
particleEmitter = particlesHelper.toParticleEmitter(particleSystem, blenderContext);
}
}
}
// applying emitter shape @Override
EmitterShape emitterShape = emitter.getShape(); public Node apply(Node node, BlenderContext blenderContext) {
List<Mesh> meshes = new ArrayList<Mesh>(); if (invalid) {
for (Spatial spatial : node.getChildren()) { LOGGER.log(Level.WARNING, "Particles modifier is invalid! Cannot be applied to: {0}", node.getName());
if (spatial instanceof Geometry) { return node;
Mesh mesh = ((Geometry) spatial).getMesh(); }
if (mesh != null) {
meshes.add(mesh);
Material material = materialHelper.getParticlesMaterial(
((Geometry) spatial).getMaterial(), alphaFunction, blenderContext);
emitter.setMaterial(material);// TODO: divide into several pieces
}
}
}
if (meshes.size() > 0 && emitterShape instanceof EmitterMeshVertexShape) {
((EmitterMeshVertexShape) emitterShape).setMeshes(meshes);
}
node.attachChild(emitter); MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
return node; ParticleEmitter emitter = particleEmitter.clone();
}
@Override // veryfying the alpha function for particles' texture
public String getType() { Integer alphaFunction = MaterialHelper.ALPHA_MASK_HYPERBOLE;
return Modifier.PARTICLE_MODIFIER_DATA; char nameSuffix = emitter.getName().charAt(emitter.getName().length() - 1);
} if (nameSuffix == 'B' || nameSuffix == 'N') {
alphaFunction = MaterialHelper.ALPHA_MASK_NONE;
}
// removing the type suffix from the name
emitter.setName(emitter.getName().substring(0, emitter.getName().length() - 1));
// applying emitter shape
EmitterShape emitterShape = emitter.getShape();
List<Mesh> meshes = new ArrayList<Mesh>();
for (Spatial spatial : node.getChildren()) {
if (spatial instanceof Geometry) {
Mesh mesh = ((Geometry) spatial).getMesh();
if (mesh != null) {
meshes.add(mesh);
Material material = materialHelper.getParticlesMaterial(((Geometry) spatial).getMaterial(), alphaFunction, blenderContext);
emitter.setMaterial(material);// TODO: divide into several pieces
}
}
}
if (meshes.size() > 0 && emitterShape instanceof EmitterMeshVertexShape) {
((EmitterMeshVertexShape) emitterShape).setMeshes(meshes);
}
node.attachChild(emitter);
return node;
}
@Override
public String getType() {
return Modifier.PARTICLE_MODIFIER_DATA;
}
} }

@ -68,317 +68,316 @@ import com.jme3.scene.plugins.blender.modifiers.ModifierHelper;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class ObjectHelper extends AbstractBlenderHelper { public class ObjectHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(ObjectHelper.class.getName()); private static final Logger LOGGER = Logger.getLogger(ObjectHelper.class.getName());
protected static final int OBJECT_TYPE_EMPTY = 0; protected static final int OBJECT_TYPE_EMPTY = 0;
protected static final int OBJECT_TYPE_MESH = 1; protected static final int OBJECT_TYPE_MESH = 1;
protected static final int OBJECT_TYPE_CURVE = 2; protected static final int OBJECT_TYPE_CURVE = 2;
protected static final int OBJECT_TYPE_SURF = 3; protected static final int OBJECT_TYPE_SURF = 3;
protected static final int OBJECT_TYPE_TEXT = 4; protected static final int OBJECT_TYPE_TEXT = 4;
protected static final int OBJECT_TYPE_METABALL = 5; protected static final int OBJECT_TYPE_METABALL = 5;
protected static final int OBJECT_TYPE_LAMP = 10; protected static final int OBJECT_TYPE_LAMP = 10;
protected static final int OBJECT_TYPE_CAMERA = 11; protected static final int OBJECT_TYPE_CAMERA = 11;
protected static final int OBJECT_TYPE_WAVE = 21; protected static final int OBJECT_TYPE_WAVE = 21;
protected static final int OBJECT_TYPE_LATTICE = 22; protected static final int OBJECT_TYPE_LATTICE = 22;
protected static final int OBJECT_TYPE_ARMATURE = 25; protected static final int OBJECT_TYPE_ARMATURE = 25;
/** /**
* This constructor parses the given blender version and stores the result. Some functionalities may differ in * This constructor parses the given blender version and stores the result. Some functionalities may differ in
* different blender versions. * different blender versions.
* @param blenderVersion * @param blenderVersion
* the version read from the blend file * the version read from the blend file
* @param fixUpAxis * @param fixUpAxis
* a variable that indicates if the Y asxis is the UP axis or not * a variable that indicates if the Y asxis is the UP axis or not
*/ */
public ObjectHelper(String blenderVersion, boolean fixUpAxis) { public ObjectHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis); super(blenderVersion, fixUpAxis);
} }
/** /**
* This method reads the given structure and createn an object that represents the data. * This method reads the given structure and createn an object that represents the data.
* @param objectStructure * @param objectStructure
* the object's structure * the object's structure
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @return blener's object representation * @return blener's object representation
* @throws BlenderFileException * @throws BlenderFileException
* an exception is thrown when the given data is inapropriate * an exception is thrown when the given data is inapropriate
*/ */
public Object toObject(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { public Object toObject(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
if(loadedResult != null) { if (loadedResult != null) {
return loadedResult; return loadedResult;
} }
blenderContext.pushParent(objectStructure); blenderContext.pushParent(objectStructure);
//get object data // get object data
int type = ((Number)objectStructure.getFieldValue("type")).intValue(); int type = ((Number) objectStructure.getFieldValue("type")).intValue();
String name = objectStructure.getName(); String name = objectStructure.getName();
LOGGER.log(Level.FINE, "Loading obejct: {0}", name); LOGGER.log(Level.FINE, "Loading obejct: {0}", name);
int restrictflag = ((Number)objectStructure.getFieldValue("restrictflag")).intValue(); int restrictflag = ((Number) objectStructure.getFieldValue("restrictflag")).intValue();
boolean visible = (restrictflag & 0x01) != 0; boolean visible = (restrictflag & 0x01) != 0;
Node result = null; Node result = null;
Pointer pParent = (Pointer)objectStructure.getFieldValue("parent"); Pointer pParent = (Pointer) objectStructure.getFieldValue("parent");
Object parent = blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); Object parent = blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
if(parent == null && pParent.isNotNull()) { if (parent == null && pParent.isNotNull()) {
Structure parentStructure = pParent.fetchData(blenderContext.getInputStream()).get(0); Structure parentStructure = pParent.fetchData(blenderContext.getInputStream()).get(0);
parent = this.toObject(parentStructure, blenderContext); parent = this.toObject(parentStructure, blenderContext);
} }
Transform t = this.getTransformation(objectStructure, blenderContext); Transform t = this.getTransformation(objectStructure, blenderContext);
try { try {
switch(type) { switch (type) {
case OBJECT_TYPE_EMPTY: case OBJECT_TYPE_EMPTY:
LOGGER.log(Level.FINE, "Importing empty."); LOGGER.log(Level.FINE, "Importing empty.");
Node empty = new Node(name); Node empty = new Node(name);
empty.setLocalTransform(t); empty.setLocalTransform(t);
if(parent instanceof Node) { if (parent instanceof Node) {
((Node) parent).attachChild(empty); ((Node) parent).attachChild(empty);
} }
result = empty; result = empty;
break; break;
case OBJECT_TYPE_MESH: case OBJECT_TYPE_MESH:
LOGGER.log(Level.FINE, "Importing mesh."); LOGGER.log(Level.FINE, "Importing mesh.");
Node node = new Node(name); Node node = new Node(name);
node.setCullHint(visible ? CullHint.Always : CullHint.Inherit); node.setCullHint(visible ? CullHint.Always : CullHint.Inherit);
//reading mesh // reading mesh
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
Pointer pMesh = (Pointer)objectStructure.getFieldValue("data"); Pointer pMesh = (Pointer) objectStructure.getFieldValue("data");
List<Structure> meshesArray = pMesh.fetchData(blenderContext.getInputStream()); List<Structure> meshesArray = pMesh.fetchData(blenderContext.getInputStream());
List<Geometry> geometries = meshHelper.toMesh(meshesArray.get(0), blenderContext); List<Geometry> geometries = meshHelper.toMesh(meshesArray.get(0), blenderContext);
if (geometries != null){ if (geometries != null) {
for(Geometry geometry : geometries) { for (Geometry geometry : geometries) {
node.attachChild(geometry); node.attachChild(geometry);
} }
} }
node.setLocalTransform(t); node.setLocalTransform(t);
//setting the parent // setting the parent
if(parent instanceof Node) { if (parent instanceof Node) {
((Node)parent).attachChild(node); ((Node) parent).attachChild(node);
} }
result = node; result = node;
break; break;
case OBJECT_TYPE_SURF: case OBJECT_TYPE_SURF:
case OBJECT_TYPE_CURVE: case OBJECT_TYPE_CURVE:
LOGGER.log(Level.FINE, "Importing curve/nurb."); LOGGER.log(Level.FINE, "Importing curve/nurb.");
Pointer pCurve = (Pointer)objectStructure.getFieldValue("data"); Pointer pCurve = (Pointer) objectStructure.getFieldValue("data");
if(pCurve.isNotNull()) { if (pCurve.isNotNull()) {
CurvesHelper curvesHelper = blenderContext.getHelper(CurvesHelper.class); CurvesHelper curvesHelper = blenderContext.getHelper(CurvesHelper.class);
Structure curveData = pCurve.fetchData(blenderContext.getInputStream()).get(0); Structure curveData = pCurve.fetchData(blenderContext.getInputStream()).get(0);
List<Geometry> curves = curvesHelper.toCurve(curveData, blenderContext); List<Geometry> curves = curvesHelper.toCurve(curveData, blenderContext);
result = new Node(name); result = new Node(name);
for(Geometry curve : curves) { for (Geometry curve : curves) {
((Node)result).attachChild(curve); ((Node) result).attachChild(curve);
} }
((Node)result).setLocalTransform(t); ((Node) result).setLocalTransform(t);
} }
break; break;
case OBJECT_TYPE_LAMP: case OBJECT_TYPE_LAMP:
LOGGER.log(Level.FINE, "Importing lamp."); LOGGER.log(Level.FINE, "Importing lamp.");
Pointer pLamp = (Pointer)objectStructure.getFieldValue("data"); Pointer pLamp = (Pointer) objectStructure.getFieldValue("data");
if(pLamp.isNotNull()) { if (pLamp.isNotNull()) {
LightHelper lightHelper = blenderContext.getHelper(LightHelper.class); LightHelper lightHelper = blenderContext.getHelper(LightHelper.class);
List<Structure> lampsArray = pLamp.fetchData(blenderContext.getInputStream()); List<Structure> lampsArray = pLamp.fetchData(blenderContext.getInputStream());
LightNode light = lightHelper.toLight(lampsArray.get(0), blenderContext); LightNode light = lightHelper.toLight(lampsArray.get(0), blenderContext);
if(light!=null) { if (light != null) {
light.setName(name); light.setName(name);
light.setLocalTransform(t); light.setLocalTransform(t);
} }
result = light; result = light;
} }
break; break;
case OBJECT_TYPE_CAMERA: case OBJECT_TYPE_CAMERA:
Pointer pCamera = (Pointer)objectStructure.getFieldValue("data"); Pointer pCamera = (Pointer) objectStructure.getFieldValue("data");
if(pCamera.isNotNull()) { if (pCamera.isNotNull()) {
CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class); CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class);
List<Structure> camerasArray = pCamera.fetchData(blenderContext.getInputStream()); List<Structure> camerasArray = pCamera.fetchData(blenderContext.getInputStream());
CameraNode camera = cameraHelper.toCamera(camerasArray.get(0), blenderContext); CameraNode camera = cameraHelper.toCamera(camerasArray.get(0), blenderContext);
camera.setName(name); camera.setName(name);
camera.setLocalTransform(t); camera.setLocalTransform(t);
result = camera; result = camera;
} }
break; break;
case OBJECT_TYPE_ARMATURE: case OBJECT_TYPE_ARMATURE:
//need to create an empty node to properly create parent-children relationships between nodes // need to create an empty node to properly create parent-children relationships between nodes
Node armature = new Node(name); Node armature = new Node(name);
armature.setLocalTransform(t); armature.setLocalTransform(t);
armature.setUserData(ArmatureHelper.ARMETURE_NODE_MARKER, Boolean.TRUE); armature.setUserData(ArmatureHelper.ARMETURE_NODE_MARKER, Boolean.TRUE);
if(parent instanceof Node) { if (parent instanceof Node) {
((Node)parent).attachChild(armature); ((Node) parent).attachChild(armature);
} }
result = armature; result = armature;
break; break;
default: default:
LOGGER.log(Level.WARNING, "Unknown object type: {0}", type); LOGGER.log(Level.WARNING, "Unknown object type: {0}", type);
} }
} finally { } finally {
blenderContext.popParent(); blenderContext.popParent();
} }
if(result != null) { if (result != null) {
result.updateModelBound();//I prefer do compute bounding box here than read it from the file result.updateModelBound();// I prefer do compute bounding box here than read it from the file
blenderContext.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result); blenderContext.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result);
//applying modifiers // applying modifiers
LOGGER.log(Level.FINE, "Reading and applying object's modifiers."); LOGGER.log(Level.FINE, "Reading and applying object's modifiers.");
ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class); ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class);
Collection<Modifier> modifiers = modifierHelper.readModifiers(objectStructure, blenderContext); Collection<Modifier> modifiers = modifierHelper.readModifiers(objectStructure, blenderContext);
for(Modifier modifier : modifiers) { for (Modifier modifier : modifiers) {
modifier.apply(result, blenderContext); modifier.apply(result, blenderContext);
} }
//loading constraints connected with this object // loading constraints connected with this object
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
constraintHelper.loadConstraints(objectStructure, blenderContext); constraintHelper.loadConstraints(objectStructure, blenderContext);
//reading custom properties // reading custom properties
if(blenderContext.getBlenderKey().isLoadObjectProperties()) { if (blenderContext.getBlenderKey().isLoadObjectProperties()) {
Properties properties = this.loadProperties(objectStructure, blenderContext); Properties properties = this.loadProperties(objectStructure, blenderContext);
//the loaded property is a group property, so we need to get each value and set it to Spatial // the loaded property is a group property, so we need to get each value and set it to Spatial
if(result instanceof Spatial && properties != null && properties.getValue() != null) { if (result instanceof Spatial && properties != null && properties.getValue() != null) {
this.applyProperties((Spatial) result, properties); this.applyProperties((Spatial) result, properties);
} }
} }
} }
return result; return result;
} }
/** /**
* This method calculates local transformation for the object. Parentage is taken under consideration. * This method calculates local transformation for the object. Parentage is taken under consideration.
* @param objectStructure * @param objectStructure
* the object's structure * the object's structure
* @return objects transformation relative to its parent * @return objects transformation relative to its parent
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Transform getTransformation(Structure objectStructure, BlenderContext blenderContext) { public Transform getTransformation(Structure objectStructure, BlenderContext blenderContext) {
//these are transformations in global space // these are transformations in global space
DynamicArray<Number> loc = (DynamicArray<Number>)objectStructure.getFieldValue("loc"); DynamicArray<Number> loc = (DynamicArray<Number>) objectStructure.getFieldValue("loc");
DynamicArray<Number> size = (DynamicArray<Number>)objectStructure.getFieldValue("size"); DynamicArray<Number> size = (DynamicArray<Number>) objectStructure.getFieldValue("size");
DynamicArray<Number> rot = (DynamicArray<Number>)objectStructure.getFieldValue("rot"); DynamicArray<Number> rot = (DynamicArray<Number>) objectStructure.getFieldValue("rot");
//load parent inverse matrix // load parent inverse matrix
Pointer pParent = (Pointer) objectStructure.getFieldValue("parent"); Pointer pParent = (Pointer) objectStructure.getFieldValue("parent");
Matrix4f parentInv = pParent.isNull() ? Matrix4f.IDENTITY : this.getMatrix(objectStructure, "parentinv"); Matrix4f parentInv = pParent.isNull() ? Matrix4f.IDENTITY : this.getMatrix(objectStructure, "parentinv");
//create the global matrix (without the scale) // create the global matrix (without the scale)
Matrix4f globalMatrix = new Matrix4f(); Matrix4f globalMatrix = new Matrix4f();
globalMatrix.setTranslation(loc.get(0).floatValue(), loc.get(1).floatValue(), loc.get(2).floatValue()); globalMatrix.setTranslation(loc.get(0).floatValue(), loc.get(1).floatValue(), loc.get(2).floatValue());
globalMatrix.setRotationQuaternion(new Quaternion().fromAngles(rot.get(0).floatValue(), rot.get(1).floatValue(), rot.get(2).floatValue())); globalMatrix.setRotationQuaternion(new Quaternion().fromAngles(rot.get(0).floatValue(), rot.get(1).floatValue(), rot.get(2).floatValue()));
//compute local matrix // compute local matrix
Matrix4f localMatrix = parentInv.mult(globalMatrix); Matrix4f localMatrix = parentInv.mult(globalMatrix);
Vector3f translation = localMatrix.toTranslationVector(); Vector3f translation = localMatrix.toTranslationVector();
Quaternion rotation = localMatrix.toRotationQuat(); Quaternion rotation = localMatrix.toRotationQuat();
Vector3f scale = this.getScale(parentInv).multLocal(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue()); Vector3f scale = this.getScale(parentInv).multLocal(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue());
if(fixUpAxis) { if (fixUpAxis) {
float y = translation.y; float y = translation.y;
translation.y = translation.z; translation.y = translation.z;
translation.z = -y; translation.z = -y;
y = rotation.getY(); y = rotation.getY();
float z = rotation.getZ(); float z = rotation.getZ();
rotation.set(rotation.getX(), z, -y, rotation.getW()); rotation.set(rotation.getX(), z, -y, rotation.getW());
y=scale.y; y = scale.y;
scale.y = scale.z; scale.y = scale.z;
scale.z = y; scale.z = y;
} }
//create the result // create the result
Transform t = new Transform(translation, rotation); Transform t = new Transform(translation, rotation);
t.setScale(scale); t.setScale(scale);
return t; return t;
} }
/** /**
* This method returns the matrix of a given name for the given structure. * This method returns the matrix of a given name for the given structure.
* The matrix is NOT transformed if Y axis is up - the raw data is loaded from the blender file. * The matrix is NOT transformed if Y axis is up - the raw data is loaded from the blender file.
* @param structure * @param structure
* the structure with matrix data * the structure with matrix data
* @param matrixName * @param matrixName
* the name of the matrix * the name of the matrix
* @return the required matrix * @return the required matrix
*/ */
public Matrix4f getMatrix(Structure structure, String matrixName) { public Matrix4f getMatrix(Structure structure, String matrixName) {
return this.getMatrix(structure, matrixName, false); return this.getMatrix(structure, matrixName, false);
} }
/** /**
* This method returns the matrix of a given name for the given structure. * This method returns the matrix of a given name for the given structure.
* It takes up axis into consideration. * It takes up axis into consideration.
* @param structure * @param structure
* the structure with matrix data * the structure with matrix data
* @param matrixName * @param matrixName
* the name of the matrix * the name of the matrix
* @return the required matrix * @return the required matrix
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Matrix4f getMatrix(Structure structure, String matrixName, boolean applyFixUpAxis) { public Matrix4f getMatrix(Structure structure, String matrixName, boolean applyFixUpAxis) {
Matrix4f result = new Matrix4f(); Matrix4f result = new Matrix4f();
DynamicArray<Number> obmat = (DynamicArray<Number>)structure.getFieldValue(matrixName); DynamicArray<Number> obmat = (DynamicArray<Number>) structure.getFieldValue(matrixName);
int rowAndColumnSize = Math.abs((int)Math.sqrt(obmat.getTotalSize()));//the matrix must be square int rowAndColumnSize = Math.abs((int) Math.sqrt(obmat.getTotalSize()));// the matrix must be square
for(int i = 0; i < rowAndColumnSize; ++i) { for (int i = 0; i < rowAndColumnSize; ++i) {
for(int j = 0; j < rowAndColumnSize; ++j) { for (int j = 0; j < rowAndColumnSize; ++j) {
result.set(i, j, obmat.get(j, i).floatValue()); result.set(i, j, obmat.get(j, i).floatValue());
} }
} }
if(applyFixUpAxis && fixUpAxis) { if (applyFixUpAxis && fixUpAxis) {
Vector3f translation = result.toTranslationVector(); Vector3f translation = result.toTranslationVector();
Quaternion rotation = result.toRotationQuat(); Quaternion rotation = result.toRotationQuat();
Vector3f scale = this.getScale(result); Vector3f scale = this.getScale(result);
float y = translation.y; float y = translation.y;
translation.y = translation.z; translation.y = translation.z;
translation.z = -y; translation.z = -y;
y = rotation.getY(); y = rotation.getY();
float z = rotation.getZ(); float z = rotation.getZ();
rotation.set(rotation.getX(), z, -y, rotation.getW()); rotation.set(rotation.getX(), z, -y, rotation.getW());
y=scale.y; y = scale.y;
scale.y = scale.z; scale.y = scale.z;
scale.z = y; scale.z = y;
result.loadIdentity(); result.loadIdentity();
result.setTranslation(translation); result.setTranslation(translation);
result.setRotationQuaternion(rotation); result.setRotationQuaternion(rotation);
result.setScale(scale); result.setScale(scale);
} }
return result; return result;
} }
/** /**
* This method returns the scale from the given matrix. * This method returns the scale from the given matrix.
* *
* @param matrix * @param matrix
* the transformation matrix * the transformation matrix
* @return the scale from the given matrix * @return the scale from the given matrix
*/ */
public Vector3f getScale(Matrix4f matrix) { public Vector3f getScale(Matrix4f matrix) {
float scaleX = (float) Math.sqrt(matrix.m00 * matrix.m00 + matrix.m10 * matrix.m10 + matrix.m20 * matrix.m20); float scaleX = (float) Math.sqrt(matrix.m00 * matrix.m00 + matrix.m10 * matrix.m10 + matrix.m20 * matrix.m20);
float scaleY = (float) Math.sqrt(matrix.m01 * matrix.m01 + matrix.m11 * matrix.m11 + matrix.m21 * matrix.m21); float scaleY = (float) Math.sqrt(matrix.m01 * matrix.m01 + matrix.m11 * matrix.m11 + matrix.m21 * matrix.m21);
float scaleZ = (float) Math.sqrt(matrix.m02 * matrix.m02 + matrix.m12 * matrix.m12 + matrix.m22 * matrix.m22); float scaleZ = (float) Math.sqrt(matrix.m02 * matrix.m02 + matrix.m12 * matrix.m12 + matrix.m22 * matrix.m22);
return new Vector3f(scaleX, scaleY, scaleZ); return new Vector3f(scaleX, scaleY, scaleZ);
} }
@Override @Override
public void clearState() { public void clearState() {
fixUpAxis = false; fixUpAxis = false;
} }
@Override @Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
int lay = ((Number) structure.getFieldValue("lay")).intValue(); int lay = ((Number) structure.getFieldValue("lay")).intValue();
return (lay & blenderContext.getBlenderKey().getLayersToLoad()) != 0 return (lay & blenderContext.getBlenderKey().getLayersToLoad()) != 0 && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0;
&& (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0; }
}
} }

@ -17,364 +17,364 @@ import java.util.Map;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class Properties implements Cloneable { public class Properties implements Cloneable {
// property type // property type
public static final int IDP_STRING = 0; public static final int IDP_STRING = 0;
public static final int IDP_INT = 1; public static final int IDP_INT = 1;
public static final int IDP_FLOAT = 2; public static final int IDP_FLOAT = 2;
public static final int IDP_ARRAY = 5; public static final int IDP_ARRAY = 5;
public static final int IDP_GROUP = 6; public static final int IDP_GROUP = 6;
// public static final int IDP_ID = 7;//this is not implemented in blender (yet) // public static final int IDP_ID = 7;//this is not implemented in blender (yet)
public static final int IDP_DOUBLE = 8; public static final int IDP_DOUBLE = 8;
// the following are valid for blender 2.5x+ // the following are valid for blender 2.5x+
public static final int IDP_IDPARRAY = 9; public static final int IDP_IDPARRAY = 9;
public static final int IDP_NUMTYPES = 10; public static final int IDP_NUMTYPES = 10;
protected static final String RNA_PROPERTY_NAME = "_RNA_UI"; protected static final String RNA_PROPERTY_NAME = "_RNA_UI";
/** Default name of the property (used if the name is not specified in blender file). */ /** Default name of the property (used if the name is not specified in blender file). */
protected static final String DEFAULT_NAME = "Unnamed property"; protected static final String DEFAULT_NAME = "Unnamed property";
/** The name of the property. */ /** The name of the property. */
private String name; private String name;
/** The type of the property. */ /** The type of the property. */
private int type; private int type;
/** The subtype of the property. Defines the type of array's elements. */ /** The subtype of the property. Defines the type of array's elements. */
private int subType; private int subType;
/** The value of the property. */ /** The value of the property. */
private Object value; private Object value;
/** The description of the property. */ /** The description of the property. */
private String description; private String description;
/** /**
* This method loads the property from the belnder file. * This method loads the property from the belnder file.
* @param idPropertyStructure * @param idPropertyStructure
* the ID structure constining the property * the ID structure constining the property
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
* an exception is thrown when the belnder file is somehow invalid * an exception is thrown when the belnder file is somehow invalid
*/ */
public void load(Structure idPropertyStructure, BlenderContext blenderContext) throws BlenderFileException { public void load(Structure idPropertyStructure, BlenderContext blenderContext) throws BlenderFileException {
name = idPropertyStructure.getFieldValue("name").toString(); name = idPropertyStructure.getFieldValue("name").toString();
if (name == null || name.length() == 0) { if (name == null || name.length() == 0) {
name = DEFAULT_NAME; name = DEFAULT_NAME;
} }
subType = ((Number) idPropertyStructure.getFieldValue("subtype")).intValue(); subType = ((Number) idPropertyStructure.getFieldValue("subtype")).intValue();
type = ((Number) idPropertyStructure.getFieldValue("type")).intValue(); type = ((Number) idPropertyStructure.getFieldValue("type")).intValue();
// reading the data // reading the data
Structure data = (Structure) idPropertyStructure.getFieldValue("data"); Structure data = (Structure) idPropertyStructure.getFieldValue("data");
int len = ((Number) idPropertyStructure.getFieldValue("len")).intValue(); int len = ((Number) idPropertyStructure.getFieldValue("len")).intValue();
switch (type) { switch (type) {
case IDP_STRING: { case IDP_STRING: {
Pointer pointer = (Pointer) data.getFieldValue("pointer"); Pointer pointer = (Pointer) data.getFieldValue("pointer");
BlenderInputStream bis = blenderContext.getInputStream(); BlenderInputStream bis = blenderContext.getInputStream();
FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress()); FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress());
bis.setPosition(dataFileBlock.getBlockPosition()); bis.setPosition(dataFileBlock.getBlockPosition());
value = bis.readString(); value = bis.readString();
break; break;
} }
case IDP_INT: case IDP_INT:
int intValue = ((Number) data.getFieldValue("val")).intValue(); int intValue = ((Number) data.getFieldValue("val")).intValue();
value = Integer.valueOf(intValue); value = Integer.valueOf(intValue);
break; break;
case IDP_FLOAT: case IDP_FLOAT:
int floatValue = ((Number) data.getFieldValue("val")).intValue(); int floatValue = ((Number) data.getFieldValue("val")).intValue();
value = Float.valueOf(Float.intBitsToFloat(floatValue)); value = Float.valueOf(Float.intBitsToFloat(floatValue));
break; break;
case IDP_ARRAY: { case IDP_ARRAY: {
Pointer pointer = (Pointer) data.getFieldValue("pointer"); Pointer pointer = (Pointer) data.getFieldValue("pointer");
BlenderInputStream bis = blenderContext.getInputStream(); BlenderInputStream bis = blenderContext.getInputStream();
FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress()); FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress());
bis.setPosition(dataFileBlock.getBlockPosition()); bis.setPosition(dataFileBlock.getBlockPosition());
int elementAmount = dataFileBlock.getSize(); int elementAmount = dataFileBlock.getSize();
switch (subType) { switch (subType) {
case IDP_INT: case IDP_INT:
elementAmount /= 4; elementAmount /= 4;
int[] intList = new int[elementAmount]; int[] intList = new int[elementAmount];
for (int i = 0; i < elementAmount; ++i) { for (int i = 0; i < elementAmount; ++i) {
intList[i] = bis.readInt(); intList[i] = bis.readInt();
} }
value = intList; value = intList;
break; break;
case IDP_FLOAT: case IDP_FLOAT:
elementAmount /= 4; elementAmount /= 4;
float[] floatList = new float[elementAmount]; float[] floatList = new float[elementAmount];
for (int i = 0; i < elementAmount; ++i) { for (int i = 0; i < elementAmount; ++i) {
floatList[i] = bis.readFloat(); floatList[i] = bis.readFloat();
} }
value = floatList; value = floatList;
break; break;
case IDP_DOUBLE: case IDP_DOUBLE:
elementAmount /= 8; elementAmount /= 8;
double[] doubleList = new double[elementAmount]; double[] doubleList = new double[elementAmount];
for (int i = 0; i < elementAmount; ++i) { for (int i = 0; i < elementAmount; ++i) {
doubleList[i] = bis.readDouble(); doubleList[i] = bis.readDouble();
} }
value = doubleList; value = doubleList;
break; break;
default: default:
throw new IllegalStateException("Invalid array subtype: " + subType); throw new IllegalStateException("Invalid array subtype: " + subType);
} }
} }
case IDP_GROUP: case IDP_GROUP:
Structure group = (Structure) data.getFieldValue("group"); Structure group = (Structure) data.getFieldValue("group");
List<Structure> dataList = group.evaluateListBase(blenderContext); List<Structure> dataList = group.evaluateListBase(blenderContext);
List<Properties> subProperties = new ArrayList<Properties>(len); List<Properties> subProperties = new ArrayList<Properties>(len);
for (Structure d : dataList) { for (Structure d : dataList) {
Properties properties = new Properties(); Properties properties = new Properties();
properties.load(d, blenderContext); properties.load(d, blenderContext);
subProperties.add(properties); subProperties.add(properties);
} }
value = subProperties; value = subProperties;
break; break;
case IDP_DOUBLE: case IDP_DOUBLE:
int doublePart1 = ((Number) data.getFieldValue("val")).intValue(); int doublePart1 = ((Number) data.getFieldValue("val")).intValue();
int doublePart2 = ((Number) data.getFieldValue("val2")).intValue(); int doublePart2 = ((Number) data.getFieldValue("val2")).intValue();
long doubleVal = (long) doublePart2 << 32 | doublePart1; long doubleVal = (long) doublePart2 << 32 | doublePart1;
value = Double.valueOf(Double.longBitsToDouble(doubleVal)); value = Double.valueOf(Double.longBitsToDouble(doubleVal));
break; break;
case IDP_IDPARRAY: { case IDP_IDPARRAY: {
Pointer pointer = (Pointer) data.getFieldValue("pointer"); Pointer pointer = (Pointer) data.getFieldValue("pointer");
List<Structure> arrays = pointer.fetchData(blenderContext.getInputStream()); List<Structure> arrays = pointer.fetchData(blenderContext.getInputStream());
List<Object> result = new ArrayList<Object>(arrays.size()); List<Object> result = new ArrayList<Object>(arrays.size());
Properties temp = new Properties(); Properties temp = new Properties();
for (Structure array : arrays) { for (Structure array : arrays) {
temp.load(array, blenderContext); temp.load(array, blenderContext);
result.add(temp.value); result.add(temp.value);
} }
this.value = result; this.value = result;
break; break;
} }
case IDP_NUMTYPES: case IDP_NUMTYPES:
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
// case IDP_ID://not yet implemented in blender // case IDP_ID://not yet implemented in blender
// return null; // return null;
default: default:
throw new IllegalStateException("Unknown custom property type: " + type); throw new IllegalStateException("Unknown custom property type: " + type);
} }
this.completeLoading(); this.completeLoading();
} }
/** /**
* This method returns the name of the property. * This method returns the name of the property.
* @return the name of the property * @return the name of the property
*/ */
public String getName() { public String getName() {
return name; return name;
} }
/** /**
* This method returns the description of the property. * This method returns the description of the property.
* @return the description of the property * @return the description of the property
*/ */
public String getDescription() { public String getDescription() {
return description; return description;
} }
/** /**
* This method returns the type of the property. * This method returns the type of the property.
* @return the type of the property * @return the type of the property
*/ */
public int getType() { public int getType() {
return type; return type;
} }
/** /**
* This method returns the value of the property. * This method returns the value of the property.
* The type of the value depends on the type of the property. * The type of the value depends on the type of the property.
* @return the value of the property * @return the value of the property
*/ */
public Object getValue() { public Object getValue() {
return value; return value;
} }
/**
* @return the names of properties that are stored withing this property
* (assuming this property is of IDP_GROUP type)
*/
@SuppressWarnings("unchecked")
public List<String> getSubPropertiesNames() {
List<String> result = null;
if(this.type == IDP_GROUP) {
List<Properties> properties = (List<Properties>)this.value;
if(properties != null && properties.size() > 0) {
result = new ArrayList<String>(properties.size());
for(Properties property : properties) {
result.add(property.getName());
}
}
}
return result;
}
/**
* This method returns the same as getValue if the current property is of
* other type than IDP_GROUP and its name matches 'propertyName' param. If
* this property is a group property the method tries to find subproperty
* value of the given name. The first found value is returnes os <b>use this
* method wisely</b>. If no property of a given name is foung - <b>null</b>
* is returned.
*
* @param propertyName
* the name of the property
* @return found property value or <b>null</b>
*/
@SuppressWarnings("unchecked")
public Object findValue(String propertyName) {
if (name.equals(propertyName)) {
return value;
} else {
if (type == IDP_GROUP) {
List<Properties> props = (List<Properties>) value;
for (Properties p : props) {
Object v = p.findValue(propertyName);
if (v != null) {
return v;
}
}
}
}
return null;
}
@Override /**
public String toString() { * @return the names of properties that are stored withing this property
StringBuilder sb = new StringBuilder(); * (assuming this property is of IDP_GROUP type)
this.append(sb, new StringBuilder()); */
return sb.toString(); @SuppressWarnings("unchecked")
} public List<String> getSubPropertiesNames() {
List<String> result = null;
if (this.type == IDP_GROUP) {
List<Properties> properties = (List<Properties>) this.value;
if (properties != null && properties.size() > 0) {
result = new ArrayList<String>(properties.size());
for (Properties property : properties) {
result.add(property.getName());
}
}
}
return result;
}
/** /**
* This method appends the data of the property to the given string buffer. * This method returns the same as getValue if the current property is of
* @param sb * other type than IDP_GROUP and its name matches 'propertyName' param. If
* string buffer * this property is a group property the method tries to find subproperty
* @param indent * value of the given name. The first found value is returnes os <b>use this
* indent buffer * method wisely</b>. If no property of a given name is foung - <b>null</b>
*/ * is returned.
@SuppressWarnings("unchecked") *
private void append(StringBuilder sb, StringBuilder indent) { * @param propertyName
sb.append(indent).append("name: ").append(name).append("\n\r"); * the name of the property
sb.append(indent).append("type: ").append(type).append("\n\r"); * @return found property value or <b>null</b>
sb.append(indent).append("subType: ").append(subType).append("\n\r"); */
sb.append(indent).append("description: ").append(description).append("\n\r"); @SuppressWarnings("unchecked")
indent.append('\t'); public Object findValue(String propertyName) {
sb.append(indent).append("value: "); if (name.equals(propertyName)) {
if (value instanceof Properties) { return value;
((Properties) value).append(sb, indent); } else {
} else if (value instanceof List) { if (type == IDP_GROUP) {
for (Object v : (List<Object>) value) { List<Properties> props = (List<Properties>) value;
if (v instanceof Properties) { for (Properties p : props) {
sb.append(indent).append("{\n\r"); Object v = p.findValue(propertyName);
indent.append('\t'); if (v != null) {
((Properties) v).append(sb, indent); return v;
indent.deleteCharAt(indent.length() - 1); }
sb.append(indent).append("}\n\r"); }
} else { }
sb.append(v); }
} return null;
} }
} else {
sb.append(value);
}
sb.append("\n\r");
indent.deleteCharAt(indent.length() - 1);
}
/** @Override
* This method should be called after the properties loading. public String toString() {
* It loads the properties from the _RNA_UI property and removes this property from the StringBuilder sb = new StringBuilder();
* result list. this.append(sb, new StringBuilder());
*/ return sb.toString();
@SuppressWarnings("unchecked") }
protected void completeLoading() {
if (this.type == IDP_GROUP) {
List<Properties> groupProperties = (List<Properties>) this.value;
Properties rnaUI = null;
for (Properties properties : groupProperties) {
if (properties.name.equals(RNA_PROPERTY_NAME) && properties.type == IDP_GROUP) {
rnaUI = properties;
break;
}
}
if (rnaUI != null) {
// removing the RNA from the result list
groupProperties.remove(rnaUI);
// loading the descriptions /**
Map<String, String> descriptions = new HashMap<String, String>(groupProperties.size()); * This method appends the data of the property to the given string buffer.
List<Properties> propertiesRNA = (List<Properties>) rnaUI.value; * @param sb
for (Properties properties : propertiesRNA) { * string buffer
String name = properties.name; * @param indent
String description = null; * indent buffer
List<Properties> rnaData = (List<Properties>) properties.value; */
for (Properties rna : rnaData) { @SuppressWarnings("unchecked")
if ("description".equalsIgnoreCase(rna.name)) { private void append(StringBuilder sb, StringBuilder indent) {
description = (String) rna.value; sb.append(indent).append("name: ").append(name).append("\n\r");
break; sb.append(indent).append("type: ").append(type).append("\n\r");
} sb.append(indent).append("subType: ").append(subType).append("\n\r");
} sb.append(indent).append("description: ").append(description).append("\n\r");
descriptions.put(name, description); indent.append('\t');
} sb.append(indent).append("value: ");
if (value instanceof Properties) {
((Properties) value).append(sb, indent);
} else if (value instanceof List) {
for (Object v : (List<Object>) value) {
if (v instanceof Properties) {
sb.append(indent).append("{\n\r");
indent.append('\t');
((Properties) v).append(sb, indent);
indent.deleteCharAt(indent.length() - 1);
sb.append(indent).append("}\n\r");
} else {
sb.append(v);
}
}
} else {
sb.append(value);
}
sb.append("\n\r");
indent.deleteCharAt(indent.length() - 1);
}
// applying the descriptions /**
for (Properties properties : groupProperties) { * This method should be called after the properties loading.
properties.description = descriptions.get(properties.name); * It loads the properties from the _RNA_UI property and removes this property from the
} * result list.
} */
} @SuppressWarnings("unchecked")
} protected void completeLoading() {
if (this.type == IDP_GROUP) {
List<Properties> groupProperties = (List<Properties>) this.value;
Properties rnaUI = null;
for (Properties properties : groupProperties) {
if (properties.name.equals(RNA_PROPERTY_NAME) && properties.type == IDP_GROUP) {
rnaUI = properties;
break;
}
}
if (rnaUI != null) {
// removing the RNA from the result list
groupProperties.remove(rnaUI);
@Override // loading the descriptions
public int hashCode() { Map<String, String> descriptions = new HashMap<String, String>(groupProperties.size());
final int prime = 31; List<Properties> propertiesRNA = (List<Properties>) rnaUI.value;
int result = 1; for (Properties properties : propertiesRNA) {
result = prime * result + (description == null ? 0 : description.hashCode()); String name = properties.name;
result = prime * result + (name == null ? 0 : name.hashCode()); String description = null;
result = prime * result + subType; List<Properties> rnaData = (List<Properties>) properties.value;
result = prime * result + type; for (Properties rna : rnaData) {
result = prime * result + (value == null ? 0 : value.hashCode()); if ("description".equalsIgnoreCase(rna.name)) {
return result; description = (String) rna.value;
} break;
}
}
descriptions.put(name, description);
}
@Override // applying the descriptions
public boolean equals(Object obj) { for (Properties properties : groupProperties) {
if (this == obj) { properties.description = descriptions.get(properties.name);
return true; }
} }
if (obj == null) { }
return false; }
}
if (this.getClass() != obj.getClass()) { @Override
return false; public int hashCode() {
} final int prime = 31;
Properties other = (Properties) obj; int result = 1;
if (description == null) { result = prime * result + (description == null ? 0 : description.hashCode());
if (other.description != null) { result = prime * result + (name == null ? 0 : name.hashCode());
return false; result = prime * result + subType;
} result = prime * result + type;
} else if (!description.equals(other.description)) { result = prime * result + (value == null ? 0 : value.hashCode());
return false; return result;
} }
if (name == null) {
if (other.name != null) { @Override
return false; public boolean equals(Object obj) {
} if (this == obj) {
} else if (!name.equals(other.name)) { return true;
return false; }
} if (obj == null) {
if (subType != other.subType) { return false;
return false; }
} if (this.getClass() != obj.getClass()) {
if (type != other.type) { return false;
return false; }
} Properties other = (Properties) obj;
if (value == null) { if (description == null) {
if (other.value != null) { if (other.description != null) {
return false; return false;
} }
} else if (!value.equals(other.value)) { } else if (!description.equals(other.description)) {
return false; return false;
} }
return true; if (name == null) {
} if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
return false;
}
if (subType != other.subType) {
return false;
}
if (type != other.type) {
return false;
}
if (value == null) {
if (other.value != null) {
return false;
}
} else if (!value.equals(other.value)) {
return false;
}
return true;
}
} }

@ -18,179 +18,179 @@ import com.jme3.scene.plugins.blender.file.Structure;
import java.util.logging.Logger; import java.util.logging.Logger;
public class ParticlesHelper extends AbstractBlenderHelper { public class ParticlesHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(ParticlesHelper.class.getName()); private static final Logger LOGGER = Logger.getLogger(ParticlesHelper.class.getName());
// part->type // part->type
public static final int PART_EMITTER = 0; public static final int PART_EMITTER = 0;
public static final int PART_REACTOR = 1; public static final int PART_REACTOR = 1;
public static final int PART_HAIR = 2; public static final int PART_HAIR = 2;
public static final int PART_FLUID = 3; public static final int PART_FLUID = 3;
// part->flag // part->flag
public static final int PART_REACT_STA_END =1; public static final int PART_REACT_STA_END = 1;
public static final int PART_REACT_MULTIPLE =2; public static final int PART_REACT_MULTIPLE = 2;
public static final int PART_LOOP =4; public static final int PART_LOOP = 4;
//public static final int PART_LOOP_INSTANT =8; // public static final int PART_LOOP_INSTANT =8;
public static final int PART_HAIR_GEOMETRY =16; public static final int PART_HAIR_GEOMETRY = 16;
public static final int PART_UNBORN =32; //show unborn particles public static final int PART_UNBORN = 32; // show unborn particles
public static final int PART_DIED =64; //show died particles public static final int PART_DIED = 64; // show died particles
public static final int PART_TRAND =128; public static final int PART_TRAND = 128;
public static final int PART_EDISTR =256; // particle/face from face areas public static final int PART_EDISTR = 256; // particle/face from face areas
public static final int PART_STICKY =512; //collided particles can stick to collider public static final int PART_STICKY = 512; // collided particles can stick to collider
public static final int PART_DIE_ON_COL =1<<12; public static final int PART_DIE_ON_COL = 1 << 12;
public static final int PART_SIZE_DEFL =1<<13; // swept sphere deflections public static final int PART_SIZE_DEFL = 1 << 13; // swept sphere deflections
public static final int PART_ROT_DYN =1<<14; // dynamic rotation public static final int PART_ROT_DYN = 1 << 14; // dynamic rotation
public static final int PART_SIZEMASS =1<<16; public static final int PART_SIZEMASS = 1 << 16;
public static final int PART_ABS_LENGTH =1<<15; public static final int PART_ABS_LENGTH = 1 << 15;
public static final int PART_ABS_TIME =1<<17; public static final int PART_ABS_TIME = 1 << 17;
public static final int PART_GLOB_TIME =1<<18; public static final int PART_GLOB_TIME = 1 << 18;
public static final int PART_BOIDS_2D =1<<19; public static final int PART_BOIDS_2D = 1 << 19;
public static final int PART_BRANCHING =1<<20; public static final int PART_BRANCHING = 1 << 20;
public static final int PART_ANIM_BRANCHING =1<<21; public static final int PART_ANIM_BRANCHING = 1 << 21;
public static final int PART_SELF_EFFECT =1<<22; public static final int PART_SELF_EFFECT = 1 << 22;
public static final int PART_SYMM_BRANCHING =1<<24; public static final int PART_SYMM_BRANCHING = 1 << 24;
public static final int PART_HAIR_BSPLINE =1024; public static final int PART_HAIR_BSPLINE = 1024;
public static final int PART_GRID_INVERT =1<<26; public static final int PART_GRID_INVERT = 1 << 26;
public static final int PART_CHILD_EFFECT =1<<27; public static final int PART_CHILD_EFFECT = 1 << 27;
public static final int PART_CHILD_SEAMS =1<<28; public static final int PART_CHILD_SEAMS = 1 << 28;
public static final int PART_CHILD_RENDER =1<<29; public static final int PART_CHILD_RENDER = 1 << 29;
public static final int PART_CHILD_GUIDE =1<<30; public static final int PART_CHILD_GUIDE = 1 << 30;
// part->from // part->from
public static final int PART_FROM_VERT =0; public static final int PART_FROM_VERT = 0;
public static final int PART_FROM_FACE =1; public static final int PART_FROM_FACE = 1;
public static final int PART_FROM_VOLUME =2; public static final int PART_FROM_VOLUME = 2;
public static final int PART_FROM_PARTICLE =3; public static final int PART_FROM_PARTICLE = 3;
public static final int PART_FROM_CHILD =4; public static final int PART_FROM_CHILD = 4;
// part->phystype // part->phystype
public static final int PART_PHYS_NO = 0; public static final int PART_PHYS_NO = 0;
public static final int PART_PHYS_NEWTON= 1; public static final int PART_PHYS_NEWTON = 1;
public static final int PART_PHYS_KEYED = 2; public static final int PART_PHYS_KEYED = 2;
public static final int PART_PHYS_BOIDS = 3; public static final int PART_PHYS_BOIDS = 3;
// part->draw_as // part->draw_as
public static final int PART_DRAW_NOT = 0; public static final int PART_DRAW_NOT = 0;
public static final int PART_DRAW_DOT = 1; public static final int PART_DRAW_DOT = 1;
public static final int PART_DRAW_CIRC = 2; public static final int PART_DRAW_CIRC = 2;
public static final int PART_DRAW_CROSS = 3; public static final int PART_DRAW_CROSS = 3;
public static final int PART_DRAW_AXIS = 4; public static final int PART_DRAW_AXIS = 4;
public static final int PART_DRAW_LINE = 5; public static final int PART_DRAW_LINE = 5;
public static final int PART_DRAW_PATH = 6; public static final int PART_DRAW_PATH = 6;
public static final int PART_DRAW_OB = 7; public static final int PART_DRAW_OB = 7;
public static final int PART_DRAW_GR = 8; public static final int PART_DRAW_GR = 8;
public static final int PART_DRAW_BB = 9; public static final int PART_DRAW_BB = 9;
/** /**
* This constructor parses the given blender version and stores the result. Some functionalities may differ in * This constructor parses the given blender version and stores the result. Some functionalities may differ in
* different blender versions. * different blender versions.
* @param blenderVersion * @param blenderVersion
* the version read from the blend file * the version read from the blend file
* @param fixUpAxis * @param fixUpAxis
* a variable that indicates if the Y asxis is the UP axis or not * a variable that indicates if the Y asxis is the UP axis or not
*/ */
public ParticlesHelper(String blenderVersion, boolean fixUpAxis) { public ParticlesHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis); super(blenderVersion, fixUpAxis);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public ParticleEmitter toParticleEmitter(Structure particleSystem, BlenderContext blenderContext) throws BlenderFileException { public ParticleEmitter toParticleEmitter(Structure particleSystem, BlenderContext blenderContext) throws BlenderFileException {
ParticleEmitter result = null; ParticleEmitter result = null;
Pointer pParticleSettings = (Pointer) particleSystem.getFieldValue("part"); Pointer pParticleSettings = (Pointer) particleSystem.getFieldValue("part");
if(pParticleSettings.isNotNull()) { if (pParticleSettings.isNotNull()) {
Structure particleSettings = pParticleSettings.fetchData(blenderContext.getInputStream()).get(0); Structure particleSettings = pParticleSettings.fetchData(blenderContext.getInputStream()).get(0);
int totPart = ((Number) particleSettings.getFieldValue("totpart")).intValue(); int totPart = ((Number) particleSettings.getFieldValue("totpart")).intValue();
//draw type will be stored temporarily in the name (it is used during modifier applying operation) // draw type will be stored temporarily in the name (it is used during modifier applying operation)
int drawAs = ((Number)particleSettings.getFieldValue("draw_as")).intValue(); int drawAs = ((Number) particleSettings.getFieldValue("draw_as")).intValue();
char nameSuffix;//P - point, L - line, N - None, B - Bilboard char nameSuffix;// P - point, L - line, N - None, B - Bilboard
switch(drawAs) { switch (drawAs) {
case PART_DRAW_NOT: case PART_DRAW_NOT:
nameSuffix = 'N'; nameSuffix = 'N';
totPart = 0;//no need to generate particles in this case totPart = 0;// no need to generate particles in this case
break; break;
case PART_DRAW_BB: case PART_DRAW_BB:
nameSuffix = 'B'; nameSuffix = 'B';
break; break;
case PART_DRAW_OB: case PART_DRAW_OB:
case PART_DRAW_GR: case PART_DRAW_GR:
nameSuffix = 'P'; nameSuffix = 'P';
LOGGER.warning("Neither object nor group particles supported yet! Using point representation instead!");//TODO: support groups and aobjects LOGGER.warning("Neither object nor group particles supported yet! Using point representation instead!");// TODO: support groups and aobjects
break; break;
case PART_DRAW_LINE: case PART_DRAW_LINE:
nameSuffix = 'L'; nameSuffix = 'L';
LOGGER.warning("Lines not yet supported! Using point representation instead!");//TODO: support lines LOGGER.warning("Lines not yet supported! Using point representation instead!");// TODO: support lines
default://all others are rendered as points in blender default:// all others are rendered as points in blender
nameSuffix = 'P'; nameSuffix = 'P';
} }
result = new ParticleEmitter(particleSettings.getName()+nameSuffix, Type.Triangle, totPart); result = new ParticleEmitter(particleSettings.getName() + nameSuffix, Type.Triangle, totPart);
if(nameSuffix=='N') { if (nameSuffix == 'N') {
return result;//no need to set anything else return result;// no need to set anything else
} }
//setting the emitters shape (the shapes meshes will be set later during modifier applying operation) // setting the emitters shape (the shapes meshes will be set later during modifier applying operation)
int from = ((Number)particleSettings.getFieldValue("from")).intValue(); int from = ((Number) particleSettings.getFieldValue("from")).intValue();
switch(from) { switch (from) {
case PART_FROM_VERT: case PART_FROM_VERT:
result.setShape(new EmitterMeshVertexShape()); result.setShape(new EmitterMeshVertexShape());
break; break;
case PART_FROM_FACE: case PART_FROM_FACE:
result.setShape(new EmitterMeshFaceShape()); result.setShape(new EmitterMeshFaceShape());
break; break;
case PART_FROM_VOLUME: case PART_FROM_VOLUME:
result.setShape(new EmitterMeshConvexHullShape()); result.setShape(new EmitterMeshConvexHullShape());
break; break;
default: default:
LOGGER.warning("Default shape used! Unknown emitter shape value ('from' parameter: " + from + ')'); LOGGER.warning("Default shape used! Unknown emitter shape value ('from' parameter: " + from + ')');
} }
//reading acceleration // reading acceleration
DynamicArray<Number> acc = (DynamicArray<Number>) particleSettings.getFieldValue("acc"); DynamicArray<Number> acc = (DynamicArray<Number>) particleSettings.getFieldValue("acc");
result.setGravity(-acc.get(0).floatValue(), -acc.get(1).floatValue(), -acc.get(2).floatValue()); result.setGravity(-acc.get(0).floatValue(), -acc.get(1).floatValue(), -acc.get(2).floatValue());
//setting the colors // setting the colors
result.setEndColor(new ColorRGBA(1f, 1f, 1f, 1f)); result.setEndColor(new ColorRGBA(1f, 1f, 1f, 1f));
result.setStartColor(new ColorRGBA(1f, 1f, 1f, 1f)); result.setStartColor(new ColorRGBA(1f, 1f, 1f, 1f));
//reading size // reading size
float sizeFactor = nameSuffix=='B' ? 1.0f : 0.3f; float sizeFactor = nameSuffix == 'B' ? 1.0f : 0.3f;
float size = ((Number)particleSettings.getFieldValue("size")).floatValue() * sizeFactor; float size = ((Number) particleSettings.getFieldValue("size")).floatValue() * sizeFactor;
result.setStartSize(size); result.setStartSize(size);
result.setEndSize(size); result.setEndSize(size);
//reading lifetime // reading lifetime
int fps = blenderContext.getBlenderKey().getFps(); int fps = blenderContext.getBlenderKey().getFps();
float lifetime = ((Number)particleSettings.getFieldValue("lifetime")).floatValue() / fps; float lifetime = ((Number) particleSettings.getFieldValue("lifetime")).floatValue() / fps;
float randlife = ((Number)particleSettings.getFieldValue("randlife")).floatValue() / fps; float randlife = ((Number) particleSettings.getFieldValue("randlife")).floatValue() / fps;
result.setLowLife(lifetime * (1.0f - randlife)); result.setLowLife(lifetime * (1.0f - randlife));
result.setHighLife(lifetime); result.setHighLife(lifetime);
//preparing influencer // preparing influencer
ParticleInfluencer influencer; ParticleInfluencer influencer;
int phystype = ((Number)particleSettings.getFieldValue("phystype")).intValue(); int phystype = ((Number) particleSettings.getFieldValue("phystype")).intValue();
switch(phystype) { switch (phystype) {
case PART_PHYS_NEWTON: case PART_PHYS_NEWTON:
influencer = new NewtonianParticleInfluencer(); influencer = new NewtonianParticleInfluencer();
((NewtonianParticleInfluencer)influencer).setNormalVelocity(((Number)particleSettings.getFieldValue("normfac")).floatValue()); ((NewtonianParticleInfluencer) influencer).setNormalVelocity(((Number) particleSettings.getFieldValue("normfac")).floatValue());
((NewtonianParticleInfluencer)influencer).setVelocityVariation(((Number)particleSettings.getFieldValue("randfac")).floatValue()); ((NewtonianParticleInfluencer) influencer).setVelocityVariation(((Number) particleSettings.getFieldValue("randfac")).floatValue());
((NewtonianParticleInfluencer)influencer).setSurfaceTangentFactor(((Number)particleSettings.getFieldValue("tanfac")).floatValue()); ((NewtonianParticleInfluencer) influencer).setSurfaceTangentFactor(((Number) particleSettings.getFieldValue("tanfac")).floatValue());
((NewtonianParticleInfluencer)influencer).setSurfaceTangentRotation(((Number)particleSettings.getFieldValue("tanphase")).floatValue()); ((NewtonianParticleInfluencer) influencer).setSurfaceTangentRotation(((Number) particleSettings.getFieldValue("tanphase")).floatValue());
break; break;
case PART_PHYS_BOIDS: case PART_PHYS_BOIDS:
case PART_PHYS_KEYED://TODO: support other influencers case PART_PHYS_KEYED:// TODO: support other influencers
LOGGER.warning("Boids and Keyed particles physic not yet supported! Empty influencer used!"); LOGGER.warning("Boids and Keyed particles physic not yet supported! Empty influencer used!");
case PART_PHYS_NO: case PART_PHYS_NO:
default: default:
influencer = new EmptyParticleInfluencer(); influencer = new EmptyParticleInfluencer();
} }
result.setParticleInfluencer(influencer); result.setParticleInfluencer(influencer);
} }
return result; return result;
} }
@Override @Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return true; return true;
} }
} }

@ -48,310 +48,310 @@ import java.util.logging.Logger;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class ColorBand { public class ColorBand {
private static final Logger LOGGER = Logger.getLogger(ColorBand.class.getName()); private static final Logger LOGGER = Logger.getLogger(ColorBand.class.getName());
// interpolation types // interpolation types
public static final int IPO_LINEAR = 0; public static final int IPO_LINEAR = 0;
public static final int IPO_EASE = 1; public static final int IPO_EASE = 1;
public static final int IPO_BSPLINE = 2; public static final int IPO_BSPLINE = 2;
public static final int IPO_CARDINAL = 3; public static final int IPO_CARDINAL = 3;
public static final int IPO_CONSTANT = 4; public static final int IPO_CONSTANT = 4;
private int cursorsAmount, ipoType; private int cursorsAmount, ipoType;
private ColorBandData[] data; private ColorBandData[] data;
/** /**
* Constructor. Loads the data from the given structure. * Constructor. Loads the data from the given structure.
* @param tex * @param tex
* @param blenderContext * @param blenderContext
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public ColorBand(Structure tex, BlenderContext blenderContext) { public ColorBand(Structure tex, BlenderContext blenderContext) {
int flag = ((Number) tex.getFieldValue("flag")).intValue(); int flag = ((Number) tex.getFieldValue("flag")).intValue();
if ((flag & GeneratedTexture.TEX_COLORBAND) != 0) { if ((flag & GeneratedTexture.TEX_COLORBAND) != 0) {
Pointer pColorband = (Pointer) tex.getFieldValue("coba"); Pointer pColorband = (Pointer) tex.getFieldValue("coba");
try { try {
Structure colorbandStructure = pColorband.fetchData(blenderContext.getInputStream()).get(0); Structure colorbandStructure = pColorband.fetchData(blenderContext.getInputStream()).get(0);
this.cursorsAmount = ((Number) colorbandStructure.getFieldValue("tot")).intValue(); this.cursorsAmount = ((Number) colorbandStructure.getFieldValue("tot")).intValue();
this.ipoType = ((Number) colorbandStructure.getFieldValue("ipotype")).intValue(); this.ipoType = ((Number) colorbandStructure.getFieldValue("ipotype")).intValue();
this.data = new ColorBandData[this.cursorsAmount]; this.data = new ColorBandData[this.cursorsAmount];
DynamicArray<Structure> data = (DynamicArray<Structure>) colorbandStructure.getFieldValue("data"); DynamicArray<Structure> data = (DynamicArray<Structure>) colorbandStructure.getFieldValue("data");
for (int i = 0; i < this.cursorsAmount; ++i) { for (int i = 0; i < this.cursorsAmount; ++i) {
this.data[i] = new ColorBandData(data.get(i)); this.data[i] = new ColorBandData(data.get(i));
} }
} catch (BlenderFileException e) { } catch (BlenderFileException e) {
LOGGER.log(Level.WARNING, "Cannot fetch the colorband structure. The reason: {0}", e.getLocalizedMessage()); LOGGER.log(Level.WARNING, "Cannot fetch the colorband structure. The reason: {0}", e.getLocalizedMessage());
} }
} }
} }
/**
* This method determines if the colorband has any transparencies or is not
* transparent at all.
*
* @return <b>true</b> if the colorband has transparencies and <b>false</b>
* otherwise
*/
public boolean hasTransparencies() {
if (data != null) {
for (ColorBandData colorBandData : data) {
if (colorBandData.a < 1.0f) {
return true;
}
}
}
return false;
}
/** /**
* This method determines if the colorband has any transparencies or is not * This method computes the values of the colorband.
* transparent at all. *
* * @return an array of 1001 elements and each element is float[4] object
* @return <b>true</b> if the colorband has transparencies and <b>false</b> * containing rgba values
* otherwise */
*/ public float[][] computeValues() {
public boolean hasTransparencies() { float[][] result = null;
if (data != null) { if (data != null) {
for (ColorBandData colorBandData : data) { result = new float[1001][4];// 1001 - amount of possible cursor
if (colorBandData.a < 1.0f) { // positions; 4 = [r, g, b, a]
return true;
}
}
}
return false;
}
/** if (data.length == 1) {// special case; use only one color for all
* This method computes the values of the colorband. // types of colorband interpolation
* for (int i = 0; i < result.length; ++i) {
* @return an array of 1001 elements and each element is float[4] object result[i][0] = data[0].r;
* containing rgba values result[i][1] = data[0].g;
*/ result[i][2] = data[0].b;
public float[][] computeValues() { result[i][3] = data[0].a;
float[][] result = null; }
if (data != null) { } else {
result = new float[1001][4];// 1001 - amount of possible cursor int currentCursor = 0;
// positions; 4 = [r, g, b, a] ColorBandData currentData = data[0];
ColorBandData nextData = data[0];
switch (ipoType) {
case ColorBand.IPO_LINEAR:
float rDiff = 0,
gDiff = 0,
bDiff = 0,
aDiff = 0,
posDiff;
for (int i = 0; i < result.length; ++i) {
posDiff = i - currentData.pos;
result[i][0] = currentData.r + rDiff * posDiff;
result[i][1] = currentData.g + gDiff * posDiff;
result[i][2] = currentData.b + bDiff * posDiff;
result[i][3] = currentData.a + aDiff * posDiff;
if (nextData.pos == i) {
currentData = data[currentCursor++];
if (currentCursor < data.length) {
nextData = data[currentCursor];
// calculate differences
int d = nextData.pos - currentData.pos;
rDiff = (nextData.r - currentData.r) / d;
gDiff = (nextData.g - currentData.g) / d;
bDiff = (nextData.b - currentData.b) / d;
aDiff = (nextData.a - currentData.a) / d;
} else {
rDiff = gDiff = bDiff = aDiff = 0;
}
}
}
break;
case ColorBand.IPO_BSPLINE:
case ColorBand.IPO_CARDINAL:
Map<Integer, ColorBandData> cbDataMap = new TreeMap<Integer, ColorBandData>();
for (int i = 0; i < data.length; ++i) {
cbDataMap.put(Integer.valueOf(i), data[i]);
}
if (data.length == 1) {// special case; use only one color for all if (data[0].pos == 0) {
// types of colorband interpolation cbDataMap.put(Integer.valueOf(-1), data[0]);
for (int i = 0; i < result.length; ++i) { } else {
result[i][0] = data[0].r; ColorBandData cbData = data[0].clone();
result[i][1] = data[0].g; cbData.pos = 0;
result[i][2] = data[0].b; cbDataMap.put(Integer.valueOf(-1), cbData);
result[i][3] = data[0].a; cbDataMap.put(Integer.valueOf(-2), cbData);
} }
} else {
int currentCursor = 0;
ColorBandData currentData = data[0];
ColorBandData nextData = data[0];
switch (ipoType) {
case ColorBand.IPO_LINEAR:
float rDiff = 0,
gDiff = 0,
bDiff = 0,
aDiff = 0,
posDiff;
for (int i = 0; i < result.length; ++i) {
posDiff = i - currentData.pos;
result[i][0] = currentData.r + rDiff * posDiff;
result[i][1] = currentData.g + gDiff * posDiff;
result[i][2] = currentData.b + bDiff * posDiff;
result[i][3] = currentData.a + aDiff * posDiff;
if (nextData.pos == i) {
currentData = data[currentCursor++];
if (currentCursor < data.length) {
nextData = data[currentCursor];
// calculate differences
int d = nextData.pos - currentData.pos;
rDiff = (nextData.r - currentData.r) / d;
gDiff = (nextData.g - currentData.g) / d;
bDiff = (nextData.b - currentData.b) / d;
aDiff = (nextData.a - currentData.a) / d;
} else {
rDiff = gDiff = bDiff = aDiff = 0;
}
}
}
break;
case ColorBand.IPO_BSPLINE:
case ColorBand.IPO_CARDINAL:
Map<Integer, ColorBandData> cbDataMap = new TreeMap<Integer, ColorBandData>();
for (int i = 0; i < data.length; ++i) {
cbDataMap.put(Integer.valueOf(i), data[i]);
}
if (data[0].pos == 0) { if (data[data.length - 1].pos == 1000) {
cbDataMap.put(Integer.valueOf(-1), data[0]); cbDataMap.put(Integer.valueOf(data.length), data[data.length - 1]);
} else { } else {
ColorBandData cbData = data[0].clone(); ColorBandData cbData = data[data.length - 1].clone();
cbData.pos = 0; cbData.pos = 1000;
cbDataMap.put(Integer.valueOf(-1), cbData); cbDataMap.put(Integer.valueOf(data.length), cbData);
cbDataMap.put(Integer.valueOf(-2), cbData); cbDataMap.put(Integer.valueOf(data.length + 1), cbData);
} }
if (data[data.length - 1].pos == 1000) { float[] ipoFactors = new float[4];
cbDataMap.put(Integer.valueOf(data.length), data[data.length - 1]); float f;
} else {
ColorBandData cbData = data[data.length - 1].clone();
cbData.pos = 1000;
cbDataMap.put(Integer.valueOf(data.length), cbData);
cbDataMap.put(Integer.valueOf(data.length + 1), cbData);
}
float[] ipoFactors = new float[4]; ColorBandData data0 = this.getColorbandData(currentCursor - 2, cbDataMap);
float f; ColorBandData data1 = this.getColorbandData(currentCursor - 1, cbDataMap);
ColorBandData data2 = this.getColorbandData(currentCursor, cbDataMap);
ColorBandData data3 = this.getColorbandData(currentCursor + 1, cbDataMap);
ColorBandData data0 = this.getColorbandData(currentCursor - 2, cbDataMap); for (int i = 0; i < result.length; ++i) {
ColorBandData data1 = this.getColorbandData(currentCursor - 1, cbDataMap); if (data2.pos != data1.pos) {
ColorBandData data2 = this.getColorbandData(currentCursor, cbDataMap); f = (i - data2.pos) / (float) (data1.pos - data2.pos);
ColorBandData data3 = this.getColorbandData(currentCursor + 1, cbDataMap); f = FastMath.clamp(f, 0.0f, 1.0f);
} else {
f = 0.0f;
}
this.getIpoData(f, ipoFactors);
result[i][0] = ipoFactors[3] * data0.r + ipoFactors[2] * data1.r + ipoFactors[1] * data2.r + ipoFactors[0] * data3.r;
result[i][1] = ipoFactors[3] * data0.g + ipoFactors[2] * data1.g + ipoFactors[1] * data2.g + ipoFactors[0] * data3.g;
result[i][2] = ipoFactors[3] * data0.b + ipoFactors[2] * data1.b + ipoFactors[1] * data2.b + ipoFactors[0] * data3.b;
result[i][3] = ipoFactors[3] * data0.a + ipoFactors[2] * data1.a + ipoFactors[1] * data2.a + ipoFactors[0] * data3.a;
result[i][0] = FastMath.clamp(result[i][0], 0.0f, 1.0f);
result[i][1] = FastMath.clamp(result[i][1], 0.0f, 1.0f);
result[i][2] = FastMath.clamp(result[i][2], 0.0f, 1.0f);
result[i][3] = FastMath.clamp(result[i][3], 0.0f, 1.0f);
for (int i = 0; i < result.length; ++i) { if (nextData.pos == i) {
if (data2.pos != data1.pos) { ++currentCursor;
f = (i - data2.pos) / (float) (data1.pos - data2.pos); data0 = cbDataMap.get(currentCursor - 2);
f = FastMath.clamp(f, 0.0f, 1.0f); data1 = cbDataMap.get(currentCursor - 1);
} else { data2 = cbDataMap.get(currentCursor);
f = 0.0f; data3 = cbDataMap.get(currentCursor + 1);
} }
this.getIpoData(f, ipoFactors); }
result[i][0] = ipoFactors[3] * data0.r + ipoFactors[2] * data1.r + ipoFactors[1] * data2.r + ipoFactors[0] * data3.r; break;
result[i][1] = ipoFactors[3] * data0.g + ipoFactors[2] * data1.g + ipoFactors[1] * data2.g + ipoFactors[0] * data3.g; case ColorBand.IPO_EASE:
result[i][2] = ipoFactors[3] * data0.b + ipoFactors[2] * data1.b + ipoFactors[1] * data2.b + ipoFactors[0] * data3.b; float d,
result[i][3] = ipoFactors[3] * data0.a + ipoFactors[2] * data1.a + ipoFactors[1] * data2.a + ipoFactors[0] * data3.a; a,
result[i][0] = FastMath.clamp(result[i][0], 0.0f, 1.0f); b,
result[i][1] = FastMath.clamp(result[i][1], 0.0f, 1.0f); d2;
result[i][2] = FastMath.clamp(result[i][2], 0.0f, 1.0f); for (int i = 0; i < result.length; ++i) {
result[i][3] = FastMath.clamp(result[i][3], 0.0f, 1.0f); if (nextData.pos != currentData.pos) {
d = (i - currentData.pos) / (float) (nextData.pos - currentData.pos);
d2 = d * d;
a = 3.0f * d2 - 2.0f * d * d2;
b = 1.0f - a;
} else {
d = a = 0.0f;
b = 1.0f;
}
if (nextData.pos == i) { result[i][0] = b * currentData.r + a * nextData.r;
++currentCursor; result[i][1] = b * currentData.g + a * nextData.g;
data0 = cbDataMap.get(currentCursor - 2); result[i][2] = b * currentData.b + a * nextData.b;
data1 = cbDataMap.get(currentCursor - 1); result[i][3] = b * currentData.a + a * nextData.a;
data2 = cbDataMap.get(currentCursor); if (nextData.pos == i) {
data3 = cbDataMap.get(currentCursor + 1); currentData = data[currentCursor++];
} if (currentCursor < data.length) {
} nextData = data[currentCursor];
break; }
case ColorBand.IPO_EASE: }
float d, }
a, break;
b, case ColorBand.IPO_CONSTANT:
d2; for (int i = 0; i < result.length; ++i) {
for (int i = 0; i < result.length; ++i) { result[i][0] = currentData.r;
if (nextData.pos != currentData.pos) { result[i][1] = currentData.g;
d = (i - currentData.pos) / (float) (nextData.pos - currentData.pos); result[i][2] = currentData.b;
d2 = d * d; result[i][3] = currentData.a;
a = 3.0f * d2 - 2.0f * d * d2; if (nextData.pos == i) {
b = 1.0f - a; currentData = data[currentCursor++];
} else { if (currentCursor < data.length) {
d = a = 0.0f; nextData = data[currentCursor];
b = 1.0f; }
} }
}
break;
default:
throw new IllegalStateException("Unknown interpolation type: " + ipoType);
}
}
}
return result;
}
result[i][0] = b * currentData.r + a * nextData.r; private ColorBandData getColorbandData(int index, Map<Integer, ColorBandData> cbDataMap) {
result[i][1] = b * currentData.g + a * nextData.g; ColorBandData result = cbDataMap.get(index);
result[i][2] = b * currentData.b + a * nextData.b; if (result == null) {
result[i][3] = b * currentData.a + a * nextData.a; result = new ColorBandData();
if (nextData.pos == i) { }
currentData = data[currentCursor++]; return result;
if (currentCursor < data.length) { }
nextData = data[currentCursor];
}
}
}
break;
case ColorBand.IPO_CONSTANT:
for (int i = 0; i < result.length; ++i) {
result[i][0] = currentData.r;
result[i][1] = currentData.g;
result[i][2] = currentData.b;
result[i][3] = currentData.a;
if (nextData.pos == i) {
currentData = data[currentCursor++];
if (currentCursor < data.length) {
nextData = data[currentCursor];
}
}
}
break;
default:
throw new IllegalStateException("Unknown interpolation type: " + ipoType);
}
}
}
return result;
}
private ColorBandData getColorbandData(int index, Map<Integer, ColorBandData> cbDataMap) {
ColorBandData result = cbDataMap.get(index);
if(result == null) {
result = new ColorBandData();
}
return result;
}
/** /**
* This method returns the data for either B-spline of Cardinal * This method returns the data for either B-spline of Cardinal
* interpolation. * interpolation.
* *
* @param d * @param d
* distance factor for the current intensity * distance factor for the current intensity
* @param ipoFactors * @param ipoFactors
* table to store the results (size of the table must be at least * table to store the results (size of the table must be at least
* 4) * 4)
*/ */
private void getIpoData(float d, float[] ipoFactors) { private void getIpoData(float d, float[] ipoFactors) {
float d2 = d * d; float d2 = d * d;
float d3 = d2 * d; float d3 = d2 * d;
if (ipoType == ColorBand.IPO_BSPLINE) { if (ipoType == ColorBand.IPO_BSPLINE) {
ipoFactors[0] = -0.71f * d3 + 1.42f * d2 - 0.71f * d; ipoFactors[0] = -0.71f * d3 + 1.42f * d2 - 0.71f * d;
ipoFactors[1] = 1.29f * d3 - 2.29f * d2 + 1.0f; ipoFactors[1] = 1.29f * d3 - 2.29f * d2 + 1.0f;
ipoFactors[2] = -1.29f * d3 + 1.58f * d2 + 0.71f * d; ipoFactors[2] = -1.29f * d3 + 1.58f * d2 + 0.71f * d;
ipoFactors[3] = 0.71f * d3 - 0.71f * d2; ipoFactors[3] = 0.71f * d3 - 0.71f * d2;
} else if (ipoType == ColorBand.IPO_CARDINAL) { } else if (ipoType == ColorBand.IPO_CARDINAL) {
ipoFactors[0] = -0.16666666f * d3 + 0.5f * d2 - 0.5f * d + 0.16666666f; ipoFactors[0] = -0.16666666f * d3 + 0.5f * d2 - 0.5f * d + 0.16666666f;
ipoFactors[1] = 0.5f * d3 - d2 + 0.6666666f; ipoFactors[1] = 0.5f * d3 - d2 + 0.6666666f;
ipoFactors[2] = -0.5f * d3 + 0.5f * d2 + 0.5f * d + 0.16666666f; ipoFactors[2] = -0.5f * d3 + 0.5f * d2 + 0.5f * d + 0.16666666f;
ipoFactors[3] = 0.16666666f * d3; ipoFactors[3] = 0.16666666f * d3;
} else { } else {
throw new IllegalStateException("Cannot get interpolation data for other colorband types than B-spline and Cardinal!"); throw new IllegalStateException("Cannot get interpolation data for other colorband types than B-spline and Cardinal!");
} }
} }
/** /**
* Class to store the single colorband cursor data. * Class to store the single colorband cursor data.
* *
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
private static class ColorBandData implements Cloneable { private static class ColorBandData implements Cloneable {
public final float r, g, b, a; public final float r, g, b, a;
public int pos; public int pos;
public ColorBandData() { public ColorBandData() {
r = g = b = 0; r = g = b = 0;
a = 1; a = 1;
} }
/**
* Copy constructor.
*/
private ColorBandData(ColorBandData data) {
this.r = data.r;
this.g = data.g;
this.b = data.b;
this.a = data.a;
this.pos = data.pos;
}
/** /**
* Constructor. Loads the data from the given structure. * Copy constructor.
* */
* @param cbdataStructure private ColorBandData(ColorBandData data) {
* the structure containing the CBData object this.r = data.r;
*/ this.g = data.g;
public ColorBandData(Structure cbdataStructure) { this.b = data.b;
this.r = ((Number) cbdataStructure.getFieldValue("r")).floatValue(); this.a = data.a;
this.g = ((Number) cbdataStructure.getFieldValue("g")).floatValue(); this.pos = data.pos;
this.b = ((Number) cbdataStructure.getFieldValue("b")).floatValue(); }
this.a = ((Number) cbdataStructure.getFieldValue("a")).floatValue();
this.pos = (int) (((Number) cbdataStructure.getFieldValue("pos")).floatValue() * 1000.0f); /**
} * Constructor. Loads the data from the given structure.
*
* @param cbdataStructure
* the structure containing the CBData object
*/
public ColorBandData(Structure cbdataStructure) {
this.r = ((Number) cbdataStructure.getFieldValue("r")).floatValue();
this.g = ((Number) cbdataStructure.getFieldValue("g")).floatValue();
this.b = ((Number) cbdataStructure.getFieldValue("b")).floatValue();
this.a = ((Number) cbdataStructure.getFieldValue("a")).floatValue();
this.pos = (int) (((Number) cbdataStructure.getFieldValue("pos")).floatValue() * 1000.0f);
}
@Override @Override
public ColorBandData clone() { public ColorBandData clone() {
try { try {
return (ColorBandData) super.clone(); return (ColorBandData) super.clone();
} catch (CloneNotSupportedException e) { } catch (CloneNotSupportedException e) {
return new ColorBandData(this); return new ColorBandData(this);
} }
} }
@Override @Override
public String toString() { public String toString() {
return "P: " + this.pos + " [" + this.r + ", " + this.g + ", " + this.b + ", " + this.a + "]"; return "P: " + this.pos + " [" + this.r + ", " + this.g + ", " + this.b + ", " + this.a + "]";
} }
} }
} }

@ -9,149 +9,149 @@ import com.jme3.texture.Image.Format;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class DDSTexelData { /* package */class DDSTexelData {
/** The colors of the texes. */ /** The colors of the texes. */
private TexturePixel[][] colors; private TexturePixel[][] colors;
/** The indexes of the texels. */ /** The indexes of the texels. */
private long[] indexes; private long[] indexes;
/** The alphas of the texels (might be null). */ /** The alphas of the texels (might be null). */
private float[][] alphas; private float[][] alphas;
/** The indexels of texels alpha values (might be null). */ /** The indexels of texels alpha values (might be null). */
private long[] alphaIndexes; private long[] alphaIndexes;
/** The counter of texel x column. */ /** The counter of texel x column. */
private int xCounter; private int xCounter;
/** The counter of texel y row. */ /** The counter of texel y row. */
private int yCounter; private int yCounter;
/** The width of the image in pixels. */ /** The width of the image in pixels. */
private int widthInPixels; private int widthInPixels;
/** The height of the image in pixels. */ /** The height of the image in pixels. */
private int heightInPixels; private int heightInPixels;
/** The total texel count. */ /** The total texel count. */
private int xTexelCount; private int xTexelCount;
/** /**
* Constructor. Allocates memory for data structures. * Constructor. Allocates memory for data structures.
* *
* @param compressedSize * @param compressedSize
* the size of compressed image (or its mipmap) * the size of compressed image (or its mipmap)
* @param widthToHeightRatio * @param widthToHeightRatio
* width/height ratio for the image * width/height ratio for the image
* @param format * @param format
* the format of the image * the format of the image
*/ */
public DDSTexelData(int compressedSize, float widthToHeightRatio, Format format) { public DDSTexelData(int compressedSize, float widthToHeightRatio, Format format) {
int texelsCount = compressedSize * 8 / format.getBitsPerPixel() / 16; int texelsCount = compressedSize * 8 / format.getBitsPerPixel() / 16;
this.colors = new TexturePixel[texelsCount][]; this.colors = new TexturePixel[texelsCount][];
this.indexes = new long[texelsCount]; this.indexes = new long[texelsCount];
this.widthInPixels = (int) (0.5f * (float) Math.sqrt(this.getSizeInBytes() / widthToHeightRatio)); this.widthInPixels = (int) (0.5f * (float) Math.sqrt(this.getSizeInBytes() / widthToHeightRatio));
this.heightInPixels = (int) (this.widthInPixels / widthToHeightRatio); this.heightInPixels = (int) (this.widthInPixels / widthToHeightRatio);
this.xTexelCount = widthInPixels >> 2; this.xTexelCount = widthInPixels >> 2;
this.yCounter = (heightInPixels >> 2) - 1;// xCounter is 0 for now this.yCounter = (heightInPixels >> 2) - 1;// xCounter is 0 for now
if (format == Format.DXT3 || format == Format.DXT5) { if (format == Format.DXT3 || format == Format.DXT5) {
this.alphas = new float[texelsCount][]; this.alphas = new float[texelsCount][];
this.alphaIndexes = new long[texelsCount]; this.alphaIndexes = new long[texelsCount];
} }
} }
/** /**
* This method adds a color and indexes for a texel. * This method adds a color and indexes for a texel.
* *
* @param colors * @param colors
* the colors of the texel * the colors of the texel
* @param indexes * @param indexes
* the indexes of the texel * the indexes of the texel
*/ */
public void add(TexturePixel[] colors, int indexes) { public void add(TexturePixel[] colors, int indexes) {
this.add(colors, indexes, null, 0); this.add(colors, indexes, null, 0);
} }
/** /**
* This method adds a color, color indexes and alha values (with their * This method adds a color, color indexes and alha values (with their
* indexes) for a texel. * indexes) for a texel.
* *
* @param colors * @param colors
* the colors of the texel * the colors of the texel
* @param indexes * @param indexes
* the indexes of the texel * the indexes of the texel
* @param alphas * @param alphas
* the alpha values * the alpha values
* @param alphaIndexes * @param alphaIndexes
* the indexes of the given alpha values * the indexes of the given alpha values
*/ */
public void add(TexturePixel[] colors, int indexes, float[] alphas, long alphaIndexes) { public void add(TexturePixel[] colors, int indexes, float[] alphas, long alphaIndexes) {
int index = yCounter * xTexelCount + xCounter; int index = yCounter * xTexelCount + xCounter;
this.colors[index] = colors; this.colors[index] = colors;
this.indexes[index] = indexes; this.indexes[index] = indexes;
if (alphas != null) { if (alphas != null) {
this.alphas[index] = alphas; this.alphas[index] = alphas;
this.alphaIndexes[index] = alphaIndexes; this.alphaIndexes[index] = alphaIndexes;
} }
++this.xCounter; ++this.xCounter;
if (this.xCounter >= this.xTexelCount) { if (this.xCounter >= this.xTexelCount) {
this.xCounter = 0; this.xCounter = 0;
--this.yCounter; --this.yCounter;
} }
} }
/** /**
* This method returns the values of the pixel located on the given * This method returns the values of the pixel located on the given
* coordinates on the result image. * coordinates on the result image.
* *
* @param x * @param x
* the x coordinate of the pixel * the x coordinate of the pixel
* @param y * @param y
* the y coordinate of the pixel * the y coordinate of the pixel
* @param result * @param result
* the table where the result is stored * the table where the result is stored
* @return <b>true</b> if the pixel was correctly read and <b>false</b> if * @return <b>true</b> if the pixel was correctly read and <b>false</b> if
* the position was outside the image sizes * the position was outside the image sizes
*/ */
public boolean getRGBA8(int x, int y, byte[] result) { public boolean getRGBA8(int x, int y, byte[] result) {
int xTexetlIndex = x % widthInPixels / 4; int xTexetlIndex = x % widthInPixels / 4;
int yTexelIndex = y % heightInPixels / 4; int yTexelIndex = y % heightInPixels / 4;
int texelIndex = yTexelIndex * xTexelCount + xTexetlIndex; int texelIndex = yTexelIndex * xTexelCount + xTexetlIndex;
if (texelIndex < colors.length) { if (texelIndex < colors.length) {
TexturePixel[] colors = this.colors[texelIndex]; TexturePixel[] colors = this.colors[texelIndex];
// coordinates of the pixel in the selected texel // coordinates of the pixel in the selected texel
x = x - 4 * xTexetlIndex;// pixels are arranged from left to right x = x - 4 * xTexetlIndex;// pixels are arranged from left to right
y = 3 - y - 4 * yTexelIndex;// pixels are arranged from bottom to top (that is why '3 - ...' is at the start) y = 3 - y - 4 * yTexelIndex;// pixels are arranged from bottom to top (that is why '3 - ...' is at the start)
int pixelIndexInTexel = (y * 4 + x) * (int) FastMath.log(colors.length, 2); int pixelIndexInTexel = (y * 4 + x) * (int) FastMath.log(colors.length, 2);
int alphaIndexInTexel = alphas != null ? (y * 4 + x) * (int) FastMath.log(alphas.length, 2) : 0; int alphaIndexInTexel = alphas != null ? (y * 4 + x) * (int) FastMath.log(alphas.length, 2) : 0;
// getting the pixel // getting the pixel
int indexMask = colors.length - 1; int indexMask = colors.length - 1;
int colorIndex = (int) (this.indexes[texelIndex] >> pixelIndexInTexel & indexMask); int colorIndex = (int) (this.indexes[texelIndex] >> pixelIndexInTexel & indexMask);
float alpha = this.alphas != null ? this.alphas[texelIndex][(int) (this.alphaIndexes[texelIndex] >> alphaIndexInTexel & 0x07)] : colors[colorIndex].alpha; float alpha = this.alphas != null ? this.alphas[texelIndex][(int) (this.alphaIndexes[texelIndex] >> alphaIndexInTexel & 0x07)] : colors[colorIndex].alpha;
result[0] = (byte) (colors[colorIndex].red * 255.0f); result[0] = (byte) (colors[colorIndex].red * 255.0f);
result[1] = (byte) (colors[colorIndex].green * 255.0f); result[1] = (byte) (colors[colorIndex].green * 255.0f);
result[2] = (byte) (colors[colorIndex].blue * 255.0f); result[2] = (byte) (colors[colorIndex].blue * 255.0f);
result[3] = (byte) (alpha * 255.0f); result[3] = (byte) (alpha * 255.0f);
return true; return true;
} }
return false; return false;
} }
/** /**
* @return the size of the decompressed texel (in bytes) * @return the size of the decompressed texel (in bytes)
*/ */
public int getSizeInBytes() { public int getSizeInBytes() {
// indexes.length == count of texels // indexes.length == count of texels
return indexes.length * 16 * 4; return indexes.length * 16 * 4;
} }
/** /**
* @return image (mipmap) width * @return image (mipmap) width
*/ */
public int getPixelWidth() { public int getPixelWidth() {
return widthInPixels; return widthInPixels;
} }
/** /**
* @return image (mipmap) height * @return image (mipmap) height
*/ */
public int getPixelHeight() { public int getPixelHeight() {
return heightInPixels; return heightInPixels;
} }
} }

@ -25,127 +25,127 @@ import java.util.TreeSet;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class GeneratedTexture extends Texture { /* package */class GeneratedTexture extends Texture {
// flag values // flag values
public static final int TEX_COLORBAND = 1; public static final int TEX_COLORBAND = 1;
public static final int TEX_FLIPBLEND = 2; public static final int TEX_FLIPBLEND = 2;
public static final int TEX_NEGALPHA = 4; public static final int TEX_NEGALPHA = 4;
public static final int TEX_CHECKER_ODD = 8; public static final int TEX_CHECKER_ODD = 8;
public static final int TEX_CHECKER_EVEN = 16; public static final int TEX_CHECKER_EVEN = 16;
public static final int TEX_PRV_ALPHA = 32; public static final int TEX_PRV_ALPHA = 32;
public static final int TEX_PRV_NOR = 64; public static final int TEX_PRV_NOR = 64;
public static final int TEX_REPEAT_XMIR = 128; public static final int TEX_REPEAT_XMIR = 128;
public static final int TEX_REPEAT_YMIR = 256; public static final int TEX_REPEAT_YMIR = 256;
public static final int TEX_FLAG_MASK = TEX_COLORBAND | TEX_FLIPBLEND | TEX_NEGALPHA | TEX_CHECKER_ODD | TEX_CHECKER_EVEN | TEX_PRV_ALPHA | TEX_PRV_NOR | TEX_REPEAT_XMIR | TEX_REPEAT_YMIR; public static final int TEX_FLAG_MASK = TEX_COLORBAND | TEX_FLIPBLEND | TEX_NEGALPHA | TEX_CHECKER_ODD | TEX_CHECKER_EVEN | TEX_PRV_ALPHA | TEX_PRV_NOR | TEX_REPEAT_XMIR | TEX_REPEAT_YMIR;
/** Material-texture link structure. */ /** Material-texture link structure. */
private final Structure mTex; private final Structure mTex;
/** Texture generateo for the specified texture type. */ /** Texture generateo for the specified texture type. */
private final TextureGenerator textureGenerator; private final TextureGenerator textureGenerator;
/** /**
* Constructor. Reads the required data from the 'tex' structure. * Constructor. Reads the required data from the 'tex' structure.
* *
* @param tex * @param tex
* the texture structure * the texture structure
* @param mTex * @param mTex
* the material-texture link data structure * the material-texture link data structure
* @param textureGenerator * @param textureGenerator
* the generator for the required texture type * the generator for the required texture type
* @param blenderContext * @param blenderContext
* the blender context * the blender context
*/ */
public GeneratedTexture(Structure tex, Structure mTex, TextureGenerator textureGenerator, BlenderContext blenderContext) { public GeneratedTexture(Structure tex, Structure mTex, TextureGenerator textureGenerator, BlenderContext blenderContext) {
this.mTex = mTex; this.mTex = mTex;
this.textureGenerator = textureGenerator; this.textureGenerator = textureGenerator;
this.textureGenerator.readData(tex, blenderContext); this.textureGenerator.readData(tex, blenderContext);
super.setImage(new GeneratedTextureImage(textureGenerator.getImageFormat())); super.setImage(new GeneratedTextureImage(textureGenerator.getImageFormat()));
} }
/** /**
* This method computes the textyre color/intensity at the specified (u, v, * This method computes the textyre color/intensity at the specified (u, v,
* s) position in 3D space. * s) position in 3D space.
* *
* @param pixel * @param pixel
* the pixel where the result is stored * the pixel where the result is stored
* @param u * @param u
* the U factor * the U factor
* @param v * @param v
* the V factor * the V factor
* @param s * @param s
* the S factor * the S factor
*/ */
public void getPixel(TexturePixel pixel, float u, float v, float s) { public void getPixel(TexturePixel pixel, float u, float v, float s) {
textureGenerator.getPixel(pixel, u, v, s); textureGenerator.getPixel(pixel, u, v, s);
} }
/** /**
* This method triangulates the texture. In the result we get a set of small * This method triangulates the texture. In the result we get a set of small
* flat textures for each face of the given mesh. This can be later merged * flat textures for each face of the given mesh. This can be later merged
* into one flat texture. * into one flat texture.
* *
* @param mesh * @param mesh
* the mesh we create the texture for * the mesh we create the texture for
* @param geometriesOMA * @param geometriesOMA
* the old memory address of the geometries group that the given * the old memory address of the geometries group that the given
* mesh belongs to (required for bounding box calculations) * mesh belongs to (required for bounding box calculations)
* @param coordinatesType * @param coordinatesType
* the types of UV coordinates * the types of UV coordinates
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @return triangulated texture * @return triangulated texture
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public TriangulatedTexture triangulate(Mesh mesh, Long geometriesOMA, UVCoordinatesType coordinatesType, BlenderContext blenderContext) { public TriangulatedTexture triangulate(Mesh mesh, Long geometriesOMA, UVCoordinatesType coordinatesType, BlenderContext blenderContext) {
List<Geometry> geometries = (List<Geometry>) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE); List<Geometry> geometries = (List<Geometry>) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE);
int[] coordinatesSwappingIndexes = new int[] { ((Number) mTex.getFieldValue("projx")).intValue(), ((Number) mTex.getFieldValue("projy")).intValue(), ((Number) mTex.getFieldValue("projz")).intValue() }; int[] coordinatesSwappingIndexes = new int[] { ((Number) mTex.getFieldValue("projx")).intValue(), ((Number) mTex.getFieldValue("projy")).intValue(), ((Number) mTex.getFieldValue("projz")).intValue() };
List<Vector3f> uvs = UVCoordinatesGenerator.generateUVCoordinatesFor3DTexture(mesh, coordinatesType, coordinatesSwappingIndexes, geometries); List<Vector3f> uvs = UVCoordinatesGenerator.generateUVCoordinatesFor3DTexture(mesh, coordinatesType, coordinatesSwappingIndexes, geometries);
Vector3f[] uvsArray = uvs.toArray(new Vector3f[uvs.size()]); Vector3f[] uvsArray = uvs.toArray(new Vector3f[uvs.size()]);
BoundingBox boundingBox = UVCoordinatesGenerator.getBoundingBox(geometries); BoundingBox boundingBox = UVCoordinatesGenerator.getBoundingBox(geometries);
Set<TriangleTextureElement> triangleTextureElements = new TreeSet<TriangleTextureElement>(new Comparator<TriangleTextureElement>() { Set<TriangleTextureElement> triangleTextureElements = new TreeSet<TriangleTextureElement>(new Comparator<TriangleTextureElement>() {
public int compare(TriangleTextureElement o1, TriangleTextureElement o2) { public int compare(TriangleTextureElement o1, TriangleTextureElement o2) {
return o1.faceIndex - o2.faceIndex; return o1.faceIndex - o2.faceIndex;
} }
}); });
int[] indices = new int[3]; int[] indices = new int[3];
for (int i = 0; i < mesh.getTriangleCount(); ++i) { for (int i = 0; i < mesh.getTriangleCount(); ++i) {
mesh.getTriangle(i, indices); mesh.getTriangle(i, indices);
triangleTextureElements.add(new TriangleTextureElement(i, boundingBox, this, uvsArray, indices, blenderContext)); triangleTextureElements.add(new TriangleTextureElement(i, boundingBox, this, uvsArray, indices, blenderContext));
} }
return new TriangulatedTexture(triangleTextureElements, blenderContext); return new TriangulatedTexture(triangleTextureElements, blenderContext);
} }
@Override @Override
public void setWrap(WrapAxis axis, WrapMode mode) { public void setWrap(WrapAxis axis, WrapMode mode) {
} }
@Override @Override
public void setWrap(WrapMode mode) { public void setWrap(WrapMode mode) {
} }
@Override @Override
public WrapMode getWrap(WrapAxis axis) { public WrapMode getWrap(WrapAxis axis) {
return null; return null;
} }
@Override @Override
public Type getType() { public Type getType() {
return Type.ThreeDimensional; return Type.ThreeDimensional;
} }
@Override @Override
public Texture createSimpleClone() { public Texture createSimpleClone() {
return null; return null;
} }
/** /**
* Private class to give the format of the 'virtual' 3D texture image. * Private class to give the format of the 'virtual' 3D texture image.
* *
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
private static class GeneratedTextureImage extends Image { private static class GeneratedTextureImage extends Image {
public GeneratedTextureImage(Format imageFormat) { public GeneratedTextureImage(Format imageFormat) {
super.format = imageFormat; super.format = imageFormat;
} }
} }
} }

@ -45,91 +45,91 @@ import java.util.logging.Logger;
* *
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class ImageLoader extends AWTLoader { /* package */class ImageLoader extends AWTLoader {
private static final Logger LOGGER = Logger.getLogger(ImageLoader.class.getName()); private static final Logger LOGGER = Logger.getLogger(ImageLoader.class.getName());
protected DDSLoader ddsLoader = new DDSLoader(); // DirectX image loader protected DDSLoader ddsLoader = new DDSLoader(); // DirectX image loader
/** /**
* This method loads the image from the blender file itself. It tries each loader to load the image. * This method loads the image from the blender file itself. It tries each loader to load the image.
* *
* @param inputStream * @param inputStream
* blender input stream * blender input stream
* @param startPosition * @param startPosition
* position in the stream where the image data starts * position in the stream where the image data starts
* @param flipY * @param flipY
* if the image should be flipped (does not work with DirectX image) * if the image should be flipped (does not work with DirectX image)
* @return loaded image or null if it could not be loaded * @return loaded image or null if it could not be loaded
*/ */
public Image loadImage(BlenderInputStream inputStream, int startPosition, boolean flipY) { public Image loadImage(BlenderInputStream inputStream, int startPosition, boolean flipY) {
// loading using AWT loader // loading using AWT loader
inputStream.setPosition(startPosition); inputStream.setPosition(startPosition);
Image result = this.loadImage(inputStream, ImageType.AWT, flipY); Image result = this.loadImage(inputStream, ImageType.AWT, flipY);
// loading using TGA loader // loading using TGA loader
if (result == null) { if (result == null) {
inputStream.setPosition(startPosition); inputStream.setPosition(startPosition);
result = this.loadImage(inputStream, ImageType.TGA, flipY); result = this.loadImage(inputStream, ImageType.TGA, flipY);
} }
// loading using DDS loader // loading using DDS loader
if (result == null) { if (result == null) {
inputStream.setPosition(startPosition); inputStream.setPosition(startPosition);
result = this.loadImage(inputStream, ImageType.DDS, flipY); result = this.loadImage(inputStream, ImageType.DDS, flipY);
} }
if (result == null) { if (result == null) {
LOGGER.warning("Image could not be loaded by none of available loaders!"); LOGGER.warning("Image could not be loaded by none of available loaders!");
} }
return result; return result;
} }
/** /**
* This method loads an image of a specified type from the given input stream. * This method loads an image of a specified type from the given input stream.
* *
* @param inputStream * @param inputStream
* the input stream we read the image from * the input stream we read the image from
* @param imageType * @param imageType
* the type of the image {@link ImageType} * the type of the image {@link ImageType}
* @param flipY * @param flipY
* if the image should be flipped (does not work with DirectX image) * if the image should be flipped (does not work with DirectX image)
* @return loaded image or null if it could not be loaded * @return loaded image or null if it could not be loaded
*/ */
public Image loadImage(InputStream inputStream, ImageType imageType, boolean flipY) { public Image loadImage(InputStream inputStream, ImageType imageType, boolean flipY) {
Image result = null; Image result = null;
switch (imageType) { switch (imageType) {
case AWT: case AWT:
try { try {
result = this.load(inputStream, flipY); result = this.load(inputStream, flipY);
} catch (Exception e) { } catch (Exception e) {
LOGGER.warning("Unable to load image using AWT loader!"); LOGGER.warning("Unable to load image using AWT loader!");
} }
break; break;
case DDS: case DDS:
try { try {
result = ddsLoader.load(inputStream); result = ddsLoader.load(inputStream);
} catch (Exception e) { } catch (Exception e) {
LOGGER.warning("Unable to load image using DDS loader!"); LOGGER.warning("Unable to load image using DDS loader!");
} }
break; break;
case TGA: case TGA:
try { try {
result = TGALoader.load(inputStream, flipY); result = TGALoader.load(inputStream, flipY);
} catch (Exception e) { } catch (Exception e) {
LOGGER.warning("Unable to load image using TGA loader!"); LOGGER.warning("Unable to load image using TGA loader!");
} }
break; break;
default: default:
throw new IllegalStateException("Unknown image type: " + imageType); throw new IllegalStateException("Unknown image type: " + imageType);
} }
return result; return result;
} }
/** /**
* Image types that can be loaded. AWT: png, jpg, jped or bmp TGA: tga DDS: DirectX image files * Image types that can be loaded. AWT: png, jpg, jped or bmp TGA: tga DDS: DirectX image files
* *
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
private static enum ImageType { private static enum ImageType {
AWT, TGA, DDS; AWT, TGA, DDS;
} }
} }

@ -9,357 +9,357 @@ import com.jme3.math.FastMath;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class TexturePixel implements Cloneable { public class TexturePixel implements Cloneable {
/** The pixel data. */ /** The pixel data. */
public float intensity, red, green, blue, alpha; public float intensity, red, green, blue, alpha;
/** /**
* Copies the values from the given pixel. * Copies the values from the given pixel.
* *
* @param pixel * @param pixel
* the pixel that we read from * the pixel that we read from
*/ */
public void fromPixel(TexturePixel pixel) { public void fromPixel(TexturePixel pixel) {
this.intensity = pixel.intensity; this.intensity = pixel.intensity;
this.red = pixel.red; this.red = pixel.red;
this.green = pixel.green; this.green = pixel.green;
this.blue = pixel.blue; this.blue = pixel.blue;
this.alpha = pixel.alpha; this.alpha = pixel.alpha;
} }
/** /**
* Copies the values from the given color. * Copies the values from the given color.
* *
* @param colorRGBA * @param colorRGBA
* the color that we read from * the color that we read from
*/ */
public void fromColor(ColorRGBA colorRGBA) { public void fromColor(ColorRGBA colorRGBA) {
this.red = colorRGBA.r; this.red = colorRGBA.r;
this.green = colorRGBA.g; this.green = colorRGBA.g;
this.blue = colorRGBA.b; this.blue = colorRGBA.b;
this.alpha = colorRGBA.a; this.alpha = colorRGBA.a;
} }
/** /**
* Copies the values from the given values. * Copies the values from the given values.
* *
* @param a * @param a
* the alpha value * the alpha value
* @param r * @param r
* the red value * the red value
* @param g * @param g
* the green value * the green value
* @param b * @param b
* the blue value * the blue value
*/ */
public void fromARGB(float a, float r, float g, float b) { public void fromARGB(float a, float r, float g, float b) {
this.alpha = a; this.alpha = a;
this.red = r; this.red = r;
this.green = g; this.green = g;
this.blue = b; this.blue = b;
} }
/** /**
* Copies the values from the given values. * Copies the values from the given values.
* *
* @param a * @param a
* the alpha value * the alpha value
* @param r * @param r
* the red value * the red value
* @param g * @param g
* the green value * the green value
* @param b * @param b
* the blue value * the blue value
*/ */
public void fromARGB8(byte a, byte r, byte g, byte b) { public void fromARGB8(byte a, byte r, byte g, byte b) {
this.alpha = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f; this.alpha = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f;
this.red = r >= 0 ? r / 255.0f : 1.0f - ~r / 255.0f; this.red = r >= 0 ? r / 255.0f : 1.0f - ~r / 255.0f;
this.green = g >= 0 ? g / 255.0f : 1.0f - ~g / 255.0f; this.green = g >= 0 ? g / 255.0f : 1.0f - ~g / 255.0f;
this.blue = b >= 0 ? b / 255.0f : 1.0f - ~b / 255.0f; this.blue = b >= 0 ? b / 255.0f : 1.0f - ~b / 255.0f;
} }
/** /**
* Copies the values from the given values. * Copies the values from the given values.
* *
* @param a * @param a
* the alpha value * the alpha value
* @param r * @param r
* the red value * the red value
* @param g * @param g
* the green value * the green value
* @param b * @param b
* the blue value * the blue value
*/ */
public void fromARGB16(short a, short r, short g, short b) { public void fromARGB16(short a, short r, short g, short b) {
this.alpha = a >= 0 ? a / 65535.0f : 1.0f - ~a / 65535.0f; this.alpha = a >= 0 ? a / 65535.0f : 1.0f - ~a / 65535.0f;
this.red = r >= 0 ? r / 65535.0f : 1.0f - ~r / 65535.0f; this.red = r >= 0 ? r / 65535.0f : 1.0f - ~r / 65535.0f;
this.green = g >= 0 ? g / 65535.0f : 1.0f - ~g / 65535.0f; this.green = g >= 0 ? g / 65535.0f : 1.0f - ~g / 65535.0f;
this.blue = b >= 0 ? b / 65535.0f : 1.0f - ~b / 65535.0f; this.blue = b >= 0 ? b / 65535.0f : 1.0f - ~b / 65535.0f;
} }
/** /**
* Copies the intensity from the given value. * Copies the intensity from the given value.
* *
* @param intensity * @param intensity
* the intensity value * the intensity value
*/ */
public void fromIntensity(byte intensity) { public void fromIntensity(byte intensity) {
this.intensity = intensity >= 0 ? intensity / 255.0f : 1.0f - ~intensity / 255.0f; this.intensity = intensity >= 0 ? intensity / 255.0f : 1.0f - ~intensity / 255.0f;
} }
/** /**
* Copies the intensity from the given value. * Copies the intensity from the given value.
* *
* @param intensity * @param intensity
* the intensity value * the intensity value
*/ */
public void fromIntensity(short intensity) { public void fromIntensity(short intensity) {
this.intensity = intensity >= 0 ? intensity / 65535.0f : 1.0f - ~intensity / 65535.0f; this.intensity = intensity >= 0 ? intensity / 65535.0f : 1.0f - ~intensity / 65535.0f;
} }
/** /**
* This method sets the alpha value (converts it to float number from range * This method sets the alpha value (converts it to float number from range
* <0, 1>). * <0, 1>).
* *
* @param alpha * @param alpha
* the alpha value * the alpha value
*/ */
public void setAlpha(byte alpha) { public void setAlpha(byte alpha) {
this.alpha = alpha >= 0 ? alpha / 255.0f : 1.0f - ~alpha / 255.0f; this.alpha = alpha >= 0 ? alpha / 255.0f : 1.0f - ~alpha / 255.0f;
} }
/** /**
* This method sets the alpha value (converts it to float number from range * This method sets the alpha value (converts it to float number from range
* <0, 1>). * <0, 1>).
* *
* @param alpha * @param alpha
* the alpha value * the alpha value
*/ */
public void setAlpha(short alpha) { public void setAlpha(short alpha) {
this.alpha = alpha >= 0 ? alpha / 65535.0f : 1.0f - ~alpha / 65535.0f; this.alpha = alpha >= 0 ? alpha / 65535.0f : 1.0f - ~alpha / 65535.0f;
} }
/** /**
* Copies the values from the given integer that stores the ARGB8 data. * Copies the values from the given integer that stores the ARGB8 data.
* *
* @param argb8 * @param argb8
* the data stored in an integer * the data stored in an integer
*/ */
public void fromARGB8(int argb8) { public void fromARGB8(int argb8) {
byte pixelValue = (byte) ((argb8 & 0xFF000000) >> 24); byte pixelValue = (byte) ((argb8 & 0xFF000000) >> 24);
this.alpha = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; this.alpha = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f;
pixelValue = (byte) ((argb8 & 0xFF0000) >> 16); pixelValue = (byte) ((argb8 & 0xFF0000) >> 16);
this.red = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; this.red = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f;
pixelValue = (byte) ((argb8 & 0xFF00) >> 8); pixelValue = (byte) ((argb8 & 0xFF00) >> 8);
this.green = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; this.green = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f;
pixelValue = (byte) (argb8 & 0xFF); pixelValue = (byte) (argb8 & 0xFF);
this.blue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; this.blue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f;
} }
/** /**
* Stores RGBA values in the given array. * Stores RGBA values in the given array.
* *
* @param result * @param result
* the array to store values * the array to store values
*/ */
public void toRGBA(float[] result) { public void toRGBA(float[] result) {
result[0] = this.red; result[0] = this.red;
result[1] = this.green; result[1] = this.green;
result[2] = this.blue; result[2] = this.blue;
result[3] = this.alpha; result[3] = this.alpha;
} }
/** /**
* Stores the data in the given table. * Stores the data in the given table.
* *
* @param result * @param result
* the result table * the result table
*/ */
public void toRGBA8(byte[] result) { public void toRGBA8(byte[] result) {
result[0] = (byte) (this.red * 255.0f); result[0] = (byte) (this.red * 255.0f);
result[1] = (byte) (this.green * 255.0f); result[1] = (byte) (this.green * 255.0f);
result[2] = (byte) (this.blue * 255.0f); result[2] = (byte) (this.blue * 255.0f);
result[3] = (byte) (this.alpha * 255.0f); result[3] = (byte) (this.alpha * 255.0f);
} }
/** /**
* Stores the pixel values in the integer. * Stores the pixel values in the integer.
* *
* @return the integer that stores the pixel values * @return the integer that stores the pixel values
*/ */
public int toARGB8() { public int toARGB8() {
int result = 0; int result = 0;
int b = (int) (this.alpha * 255.0f); int b = (int) (this.alpha * 255.0f);
result |= b << 24; result |= b << 24;
b = (int) (this.red * 255.0f); b = (int) (this.red * 255.0f);
result |= b << 16; result |= b << 16;
b = (int) (this.green * 255.0f); b = (int) (this.green * 255.0f);
result |= b << 8; result |= b << 8;
b = (int) (this.blue * 255.0f); b = (int) (this.blue * 255.0f);
result |= b; result |= b;
return result; return result;
} }
/** /**
* @return the intensity of the pixel * @return the intensity of the pixel
*/ */
public byte getInt() { public byte getInt() {
return (byte) (this.intensity * 255.0f); return (byte) (this.intensity * 255.0f);
} }
/** /**
* @return the alpha value of the pixel * @return the alpha value of the pixel
*/ */
public byte getA8() { public byte getA8() {
return (byte) (this.alpha * 255.0f); return (byte) (this.alpha * 255.0f);
} }
/** /**
* @return the alpha red of the pixel * @return the alpha red of the pixel
*/ */
public byte getR8() { public byte getR8() {
return (byte) (this.red * 255.0f); return (byte) (this.red * 255.0f);
} }
/** /**
* @return the green value of the pixel * @return the green value of the pixel
*/ */
public byte getG8() { public byte getG8() {
return (byte) (this.green * 255.0f); return (byte) (this.green * 255.0f);
} }
/** /**
* @return the blue value of the pixel * @return the blue value of the pixel
*/ */
public byte getB8() { public byte getB8() {
return (byte) (this.blue * 255.0f); return (byte) (this.blue * 255.0f);
} }
/** /**
* @return the alpha value of the pixel * @return the alpha value of the pixel
*/ */
public short getA16() { public short getA16() {
return (byte) (this.alpha * 65535.0f); return (byte) (this.alpha * 65535.0f);
} }
/** /**
* @return the alpha red of the pixel * @return the alpha red of the pixel
*/ */
public short getR16() { public short getR16() {
return (byte) (this.red * 65535.0f); return (byte) (this.red * 65535.0f);
} }
/** /**
* @return the green value of the pixel * @return the green value of the pixel
*/ */
public short getG16() { public short getG16() {
return (byte) (this.green * 65535.0f); return (byte) (this.green * 65535.0f);
} }
/** /**
* @return the blue value of the pixel * @return the blue value of the pixel
*/ */
public short getB16() { public short getB16() {
return (byte) (this.blue * 65535.0f); return (byte) (this.blue * 65535.0f);
} }
/** /**
* Merges two pixels (adds the values of each color). * Merges two pixels (adds the values of each color).
* *
* @param pixel * @param pixel
* the pixel we merge with * the pixel we merge with
*/ */
public void merge(TexturePixel pixel) { public void merge(TexturePixel pixel) {
float oneMinusAlpha = 1 - pixel.alpha; float oneMinusAlpha = 1 - pixel.alpha;
this.red = oneMinusAlpha * this.red + pixel.alpha * pixel.red; this.red = oneMinusAlpha * this.red + pixel.alpha * pixel.red;
this.green = oneMinusAlpha * this.green + pixel.alpha * pixel.green; this.green = oneMinusAlpha * this.green + pixel.alpha * pixel.green;
this.blue = oneMinusAlpha * this.blue + pixel.alpha * pixel.blue; this.blue = oneMinusAlpha * this.blue + pixel.alpha * pixel.blue;
this.alpha = (this.alpha + pixel.alpha) * 0.5f; this.alpha = (this.alpha + pixel.alpha) * 0.5f;
} }
/** /**
* This method negates the colors. * This method negates the colors.
*/ */
public void negate() { public void negate() {
this.red = 1.0f - this.red; this.red = 1.0f - this.red;
this.green = 1.0f - this.green; this.green = 1.0f - this.green;
this.blue = 1.0f - this.blue; this.blue = 1.0f - this.blue;
this.alpha = 1.0f - this.alpha; this.alpha = 1.0f - this.alpha;
} }
/** /**
* This method clears the pixel values. * This method clears the pixel values.
*/ */
public void clear() { public void clear() {
this.intensity = this.blue = this.red = this.green = this.alpha = 0.0f; this.intensity = this.blue = this.red = this.green = this.alpha = 0.0f;
} }
/** /**
* This method adds the calues of the given pixel to the current pixel. * This method adds the calues of the given pixel to the current pixel.
* *
* @param pixel * @param pixel
* the pixel we add * the pixel we add
*/ */
public void add(TexturePixel pixel) { public void add(TexturePixel pixel) {
this.red += pixel.red; this.red += pixel.red;
this.green += pixel.green; this.green += pixel.green;
this.blue += pixel.blue; this.blue += pixel.blue;
this.alpha += pixel.alpha; this.alpha += pixel.alpha;
this.intensity += pixel.intensity; this.intensity += pixel.intensity;
} }
/** /**
* This method multiplies the values of the given pixel by the given value. * This method multiplies the values of the given pixel by the given value.
* *
* @param value * @param value
* multiplication factor * multiplication factor
*/ */
public void mult(float value) { public void mult(float value) {
this.red *= value; this.red *= value;
this.green *= value; this.green *= value;
this.blue *= value; this.blue *= value;
this.alpha *= value; this.alpha *= value;
this.intensity *= value; this.intensity *= value;
} }
/** /**
* This method divides the values of the given pixel by the given value. * This method divides the values of the given pixel by the given value.
* ATTENTION! Beware of the zero value. This will cause you NaN's in the * ATTENTION! Beware of the zero value. This will cause you NaN's in the
* pixel values. * pixel values.
* *
* @param value * @param value
* division factor * division factor
*/ */
public void divide(float value) { public void divide(float value) {
this.red /= value; this.red /= value;
this.green /= value; this.green /= value;
this.blue /= value; this.blue /= value;
this.alpha /= value; this.alpha /= value;
this.intensity /= value; this.intensity /= value;
} }
/** /**
* This method clamps the pixel values to the given borders. * This method clamps the pixel values to the given borders.
* *
* @param min * @param min
* the minimum value * the minimum value
* @param max * @param max
* the maximum value * the maximum value
*/ */
public void clamp(float min, float max) { public void clamp(float min, float max) {
this.red = FastMath.clamp(this.red, min, max); this.red = FastMath.clamp(this.red, min, max);
this.green = FastMath.clamp(this.green, min, max); this.green = FastMath.clamp(this.green, min, max);
this.blue = FastMath.clamp(this.blue, min, max); this.blue = FastMath.clamp(this.blue, min, max);
this.alpha = FastMath.clamp(this.alpha, min, max); this.alpha = FastMath.clamp(this.alpha, min, max);
this.intensity = FastMath.clamp(this.intensity, min, max); this.intensity = FastMath.clamp(this.intensity, min, max);
} }
@Override @Override
public Object clone() throws CloneNotSupportedException { public Object clone() throws CloneNotSupportedException {
return super.clone(); return super.clone();
} }
@Override @Override
public String toString() { public String toString() {
return "[" + red + ", " + green + ", " + blue + ", " + alpha + " {" + intensity + "}]"; return "[" + red + ", " + green + ", " + blue + ", " + alpha + " {" + intensity + "}]";
} }
} }

@ -52,442 +52,428 @@ import java.util.logging.Logger;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class UVCoordinatesGenerator { public class UVCoordinatesGenerator {
private static final Logger LOGGER = Logger.getLogger(UVCoordinatesGenerator.class.getName()); private static final Logger LOGGER = Logger.getLogger(UVCoordinatesGenerator.class.getName());
public static enum UVCoordinatesType { public static enum UVCoordinatesType {
TEXCO_ORCO(1), TEXCO_ORCO(1), TEXCO_REFL(2), TEXCO_NORM(4), TEXCO_GLOB(8), TEXCO_UV(16), TEXCO_OBJECT(32), TEXCO_LAVECTOR(64), TEXCO_VIEW(128), TEXCO_STICKY(256), TEXCO_OSA(512), TEXCO_WINDOW(1024), NEED_UV(2048), TEXCO_TANGENT(4096),
TEXCO_REFL(2), // still stored in vertex->accum, 1 D
TEXCO_NORM(4), TEXCO_PARTICLE_OR_STRAND(8192), TEXCO_STRESS(16384), TEXCO_SPEED(32768);
TEXCO_GLOB(8),
TEXCO_UV(16),
TEXCO_OBJECT(32),
TEXCO_LAVECTOR(64),
TEXCO_VIEW(128),
TEXCO_STICKY(256),
TEXCO_OSA(512),
TEXCO_WINDOW(1024),
NEED_UV(2048),
TEXCO_TANGENT(4096),
// still stored in vertex->accum, 1 D
TEXCO_PARTICLE_OR_STRAND(8192),
TEXCO_STRESS(16384),
TEXCO_SPEED(32768);
public final int blenderValue; public final int blenderValue;
private UVCoordinatesType(int blenderValue) { private UVCoordinatesType(int blenderValue) {
this.blenderValue = blenderValue; this.blenderValue = blenderValue;
} }
public static UVCoordinatesType valueOf(int blenderValue) { public static UVCoordinatesType valueOf(int blenderValue) {
for (UVCoordinatesType coordinatesType : UVCoordinatesType.values()) { for (UVCoordinatesType coordinatesType : UVCoordinatesType.values()) {
if (coordinatesType.blenderValue == blenderValue) { if (coordinatesType.blenderValue == blenderValue) {
return coordinatesType; return coordinatesType;
} }
} }
return null; return null;
} }
} }
/** /**
* Generates a UV coordinates for 2D texture. * Generates a UV coordinates for 2D texture.
* *
* @param mesh * @param mesh
* the mesh we generate UV's for * the mesh we generate UV's for
* @param texco * @param texco
* UV coordinates type * UV coordinates type
* @param projection * @param projection
* projection type * projection type
* @param geometries * @param geometries
* the geometris the given mesh belongs to (required to compute * the geometris the given mesh belongs to (required to compute
* bounding box) * bounding box)
* @return UV coordinates for the given mesh * @return UV coordinates for the given mesh
*/ */
public static List<Vector2f> generateUVCoordinatesFor2DTexture(Mesh mesh, UVCoordinatesType texco, UVProjectionType projection, List<Geometry> geometries) { public static List<Vector2f> generateUVCoordinatesFor2DTexture(Mesh mesh, UVCoordinatesType texco, UVProjectionType projection, List<Geometry> geometries) {
List<Vector2f> result = new ArrayList<Vector2f>(); List<Vector2f> result = new ArrayList<Vector2f>();
BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries); BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries);
float[] inputData = null;// positions, normals, reflection vectors, etc. float[] inputData = null;// positions, normals, reflection vectors, etc.
switch (texco) { switch (texco) {
case TEXCO_ORCO: case TEXCO_ORCO:
inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position)); inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position));
break; break;
case TEXCO_UV:// this should be used if not defined by user explicitly case TEXCO_UV:// this should be used if not defined by user explicitly
Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) }; Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) };
for (int i = 0; i < mesh.getVertexCount(); ++i) { for (int i = 0; i < mesh.getVertexCount(); ++i) {
result.add(data[i % 3]); result.add(data[i % 3]);
} }
break; break;
case TEXCO_NORM: case TEXCO_NORM:
inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal)); inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal));
break; break;
case TEXCO_REFL: case TEXCO_REFL:
case TEXCO_GLOB: case TEXCO_GLOB:
case TEXCO_TANGENT: case TEXCO_TANGENT:
case TEXCO_STRESS: case TEXCO_STRESS:
case TEXCO_LAVECTOR: case TEXCO_LAVECTOR:
case TEXCO_OBJECT: case TEXCO_OBJECT:
case TEXCO_OSA: case TEXCO_OSA:
case TEXCO_PARTICLE_OR_STRAND: case TEXCO_PARTICLE_OR_STRAND:
case TEXCO_SPEED: case TEXCO_SPEED:
case TEXCO_STICKY: case TEXCO_STICKY:
case TEXCO_VIEW: case TEXCO_VIEW:
case TEXCO_WINDOW: case TEXCO_WINDOW:
LOGGER.warning("Texture coordinates type not currently supported: " + texco); LOGGER.warning("Texture coordinates type not currently supported: " + texco);
break; break;
default: default:
throw new IllegalStateException("Unknown texture coordinates value: " + texco); throw new IllegalStateException("Unknown texture coordinates value: " + texco);
} }
if (inputData != null) {// make projection calculations if (inputData != null) {// make projection calculations
switch (projection) { switch (projection) {
case PROJECTION_FLAT: case PROJECTION_FLAT:
inputData = UVProjectionGenerator.flatProjection(inputData, bb); inputData = UVProjectionGenerator.flatProjection(inputData, bb);
break; break;
case PROJECTION_CUBE: case PROJECTION_CUBE:
inputData = UVProjectionGenerator.cubeProjection(inputData, bb); inputData = UVProjectionGenerator.cubeProjection(inputData, bb);
break; break;
case PROJECTION_TUBE: case PROJECTION_TUBE:
BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometries); BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometries);
inputData = UVProjectionGenerator.tubeProjection(inputData, bt); inputData = UVProjectionGenerator.tubeProjection(inputData, bt);
break; break;
case PROJECTION_SPHERE: case PROJECTION_SPHERE:
BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometries); BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometries);
inputData = UVProjectionGenerator.sphereProjection(inputData, bs); inputData = UVProjectionGenerator.sphereProjection(inputData, bs);
break; break;
default: default:
throw new IllegalStateException("Unknown projection type: " + projection); throw new IllegalStateException("Unknown projection type: " + projection);
} }
for (int i = 0; i < inputData.length; i += 2) { for (int i = 0; i < inputData.length; i += 2) {
result.add(new Vector2f(inputData[i], inputData[i + 1])); result.add(new Vector2f(inputData[i], inputData[i + 1]));
} }
} }
return result; return result;
} }
/** /**
* Generates a UV coordinates for 3D texture. * Generates a UV coordinates for 3D texture.
* *
* @param mesh * @param mesh
* the mesh we generate UV's for * the mesh we generate UV's for
* @param texco * @param texco
* UV coordinates type * UV coordinates type
* @param coordinatesSwappingIndexes * @param coordinatesSwappingIndexes
* coordinates swapping indexes * coordinates swapping indexes
* @param geometries * @param geometries
* the geometris the given mesh belongs to (required to compute * the geometris the given mesh belongs to (required to compute
* bounding box) * bounding box)
* @return UV coordinates for the given mesh * @return UV coordinates for the given mesh
*/ */
public static List<Vector3f> generateUVCoordinatesFor3DTexture(Mesh mesh, UVCoordinatesType texco, int[] coordinatesSwappingIndexes, List<Geometry> geometries) { public static List<Vector3f> generateUVCoordinatesFor3DTexture(Mesh mesh, UVCoordinatesType texco, int[] coordinatesSwappingIndexes, List<Geometry> geometries) {
List<Vector3f> result = new ArrayList<Vector3f>(); List<Vector3f> result = new ArrayList<Vector3f>();
BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries); BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries);
float[] inputData = null;// positions, normals, reflection vectors, etc. float[] inputData = null;// positions, normals, reflection vectors, etc.
switch (texco) { switch (texco) {
case TEXCO_ORCO: case TEXCO_ORCO:
inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position)); inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position));
break; break;
case TEXCO_UV: case TEXCO_UV:
Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) }; Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) };
for (int i = 0; i < mesh.getVertexCount(); ++i) { for (int i = 0; i < mesh.getVertexCount(); ++i) {
Vector2f uv = data[i % 3]; Vector2f uv = data[i % 3];
result.add(new Vector3f(uv.x, uv.y, 0)); result.add(new Vector3f(uv.x, uv.y, 0));
} }
break; break;
case TEXCO_NORM: case TEXCO_NORM:
inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal)); inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal));
break; break;
case TEXCO_REFL: case TEXCO_REFL:
case TEXCO_GLOB: case TEXCO_GLOB:
case TEXCO_TANGENT: case TEXCO_TANGENT:
case TEXCO_STRESS: case TEXCO_STRESS:
case TEXCO_LAVECTOR: case TEXCO_LAVECTOR:
case TEXCO_OBJECT: case TEXCO_OBJECT:
case TEXCO_OSA: case TEXCO_OSA:
case TEXCO_PARTICLE_OR_STRAND: case TEXCO_PARTICLE_OR_STRAND:
case TEXCO_SPEED: case TEXCO_SPEED:
case TEXCO_STICKY: case TEXCO_STICKY:
case TEXCO_VIEW: case TEXCO_VIEW:
case TEXCO_WINDOW: case TEXCO_WINDOW:
LOGGER.warning("Texture coordinates type not currently supported: " + texco); LOGGER.warning("Texture coordinates type not currently supported: " + texco);
break; break;
default: default:
throw new IllegalStateException("Unknown texture coordinates value: " + texco); throw new IllegalStateException("Unknown texture coordinates value: " + texco);
} }
if (inputData != null) {// make calculations if (inputData != null) {// make calculations
Vector3f min = bb.getMin(null); Vector3f min = bb.getMin(null);
float[] uvCoordsResults = new float[4];// used for coordinates swapping float[] uvCoordsResults = new float[4];// used for coordinates swapping
float[] ext = new float[] { bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2 }; float[] ext = new float[] { bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2 };
for (int i = 0; i < ext.length; ++i) { for (int i = 0; i < ext.length; ++i) {
if (ext[i] == 0) { if (ext[i] == 0) {
ext[i] = 1; ext[i] = 1;
} }
} }
// now transform the coordinates so that they are in the range of // now transform the coordinates so that they are in the range of
// <0; 1> // <0; 1>
for (int i = 0; i < inputData.length; i += 3) { for (int i = 0; i < inputData.length; i += 3) {
uvCoordsResults[1] = (inputData[i] - min.x) / ext[0]; uvCoordsResults[1] = (inputData[i] - min.x) / ext[0];
uvCoordsResults[2] = (inputData[i + 1] - min.y) / ext[1]; uvCoordsResults[2] = (inputData[i + 1] - min.y) / ext[1];
uvCoordsResults[3] = (inputData[i + 2] - min.z) / ext[2]; uvCoordsResults[3] = (inputData[i + 2] - min.z) / ext[2];
result.add(new Vector3f(uvCoordsResults[coordinatesSwappingIndexes[0]], uvCoordsResults[coordinatesSwappingIndexes[1]], uvCoordsResults[coordinatesSwappingIndexes[2]])); result.add(new Vector3f(uvCoordsResults[coordinatesSwappingIndexes[0]], uvCoordsResults[coordinatesSwappingIndexes[1]], uvCoordsResults[coordinatesSwappingIndexes[2]]));
} }
} }
return result; return result;
} }
/**
* This method should be used to determine if the texture will ever be
* computed. If the texture coordinates are not supported then the try of
* flattening the texture might result in runtime exceptions occurence.
*
* @param texco
* the texture coordinates type
* @return <b>true</b> if the type is supported and false otherwise
*/
public static boolean isTextureCoordinateTypeSupported(UVCoordinatesType texco) {
switch (texco) {
case TEXCO_ORCO:
case TEXCO_UV:
case TEXCO_NORM:
return true;
case TEXCO_REFL:
case TEXCO_GLOB:
case TEXCO_TANGENT:
case TEXCO_STRESS:
case TEXCO_LAVECTOR:
case TEXCO_OBJECT:
case TEXCO_OSA:
case TEXCO_PARTICLE_OR_STRAND:
case TEXCO_SPEED:
case TEXCO_STICKY:
case TEXCO_VIEW:
case TEXCO_WINDOW:
return false;
default:
throw new IllegalStateException("Unknown texture coordinates value: " + texco);
}
}
/** /**
* This method returns the bounding box of the given geometries. * This method should be used to determine if the texture will ever be
* * computed. If the texture coordinates are not supported then the try of
* @param geometries * flattening the texture might result in runtime exceptions occurence.
* the list of geometries *
* @return bounding box of the given geometries * @param texco
*/ * the texture coordinates type
public static BoundingBox getBoundingBox(List<Geometry> geometries) { * @return <b>true</b> if the type is supported and false otherwise
BoundingBox result = null; */
for (Geometry geometry : geometries) { public static boolean isTextureCoordinateTypeSupported(UVCoordinatesType texco) {
BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometry.getMesh()); switch (texco) {
if (result == null) { case TEXCO_ORCO:
result = bb; case TEXCO_UV:
} else { case TEXCO_NORM:
result.merge(bb); return true;
} case TEXCO_REFL:
} case TEXCO_GLOB:
return result; case TEXCO_TANGENT:
} case TEXCO_STRESS:
case TEXCO_LAVECTOR:
case TEXCO_OBJECT:
case TEXCO_OSA:
case TEXCO_PARTICLE_OR_STRAND:
case TEXCO_SPEED:
case TEXCO_STICKY:
case TEXCO_VIEW:
case TEXCO_WINDOW:
return false;
default:
throw new IllegalStateException("Unknown texture coordinates value: " + texco);
}
}
/** /**
* This method returns the bounding box of the given mesh. * This method returns the bounding box of the given geometries.
* *
* @param mesh * @param geometries
* the mesh * the list of geometries
* @return bounding box of the given mesh * @return bounding box of the given geometries
*/ */
/* package */static BoundingBox getBoundingBox(Mesh mesh) { public static BoundingBox getBoundingBox(List<Geometry> geometries) {
mesh.updateBound(); BoundingBox result = null;
BoundingVolume bv = mesh.getBound(); for (Geometry geometry : geometries) {
if (bv instanceof BoundingBox) { BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometry.getMesh());
return (BoundingBox) bv; if (result == null) {
} else if (bv instanceof BoundingSphere) { result = bb;
BoundingSphere bs = (BoundingSphere) bv; } else {
float r = bs.getRadius(); result.merge(bb);
return new BoundingBox(bs.getCenter(), r, r, r); }
} else { }
throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName()); return result;
} }
}
/** /**
* This method returns the bounding sphere of the given geometries. * This method returns the bounding box of the given mesh.
* *
* @param geometries * @param mesh
* the list of geometries * the mesh
* @return bounding sphere of the given geometries * @return bounding box of the given mesh
*/ */
/* package */static BoundingSphere getBoundingSphere(List<Geometry> geometries) { /* package */static BoundingBox getBoundingBox(Mesh mesh) {
BoundingSphere result = null; mesh.updateBound();
for (Geometry geometry : geometries) { BoundingVolume bv = mesh.getBound();
BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometry.getMesh()); if (bv instanceof BoundingBox) {
if (result == null) { return (BoundingBox) bv;
result = bs; } else if (bv instanceof BoundingSphere) {
} else { BoundingSphere bs = (BoundingSphere) bv;
result.merge(bs); float r = bs.getRadius();
} return new BoundingBox(bs.getCenter(), r, r, r);
} } else {
return result; throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName());
} }
}
/** /**
* This method returns the bounding sphere of the given mesh. * This method returns the bounding sphere of the given geometries.
* *
* @param mesh * @param geometries
* the mesh * the list of geometries
* @return bounding sphere of the given mesh * @return bounding sphere of the given geometries
*/ */
/* package */static BoundingSphere getBoundingSphere(Mesh mesh) { /* package */static BoundingSphere getBoundingSphere(List<Geometry> geometries) {
mesh.updateBound(); BoundingSphere result = null;
BoundingVolume bv = mesh.getBound(); for (Geometry geometry : geometries) {
if (bv instanceof BoundingBox) { BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometry.getMesh());
BoundingBox bb = (BoundingBox) bv; if (result == null) {
float r = Math.max(bb.getXExtent(), bb.getYExtent()); result = bs;
r = Math.max(r, bb.getZExtent()); } else {
return new BoundingSphere(r, bb.getCenter()); result.merge(bs);
} else if (bv instanceof BoundingSphere) { }
return (BoundingSphere) bv; }
} else { return result;
throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName()); }
}
}
/** /**
* This method returns the bounding tube of the given mesh. * This method returns the bounding sphere of the given mesh.
* *
* @param mesh * @param mesh
* the mesh * the mesh
* @return bounding tube of the given mesh * @return bounding sphere of the given mesh
*/ */
/* package */static BoundingTube getBoundingTube(Mesh mesh) { /* package */static BoundingSphere getBoundingSphere(Mesh mesh) {
Vector3f center = new Vector3f(); mesh.updateBound();
float maxx = -Float.MAX_VALUE, minx = Float.MAX_VALUE; BoundingVolume bv = mesh.getBound();
float maxy = -Float.MAX_VALUE, miny = Float.MAX_VALUE; if (bv instanceof BoundingBox) {
float maxz = -Float.MAX_VALUE, minz = Float.MAX_VALUE; BoundingBox bb = (BoundingBox) bv;
float r = Math.max(bb.getXExtent(), bb.getYExtent());
r = Math.max(r, bb.getZExtent());
return new BoundingSphere(r, bb.getCenter());
} else if (bv instanceof BoundingSphere) {
return (BoundingSphere) bv;
} else {
throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName());
}
}
FloatBuffer positions = mesh.getFloatBuffer(VertexBuffer.Type.Position); /**
int limit = positions.limit(); * This method returns the bounding tube of the given mesh.
for (int i = 0; i < limit; i += 3) { *
float x = positions.get(i); * @param mesh
float y = positions.get(i + 1); * the mesh
float z = positions.get(i + 2); * @return bounding tube of the given mesh
center.addLocal(x, y, z); */
maxx = x > maxx ? x : maxx; /* package */static BoundingTube getBoundingTube(Mesh mesh) {
minx = x < minx ? x : minx; Vector3f center = new Vector3f();
maxy = y > maxy ? y : maxy; float maxx = -Float.MAX_VALUE, minx = Float.MAX_VALUE;
miny = y < miny ? y : miny; float maxy = -Float.MAX_VALUE, miny = Float.MAX_VALUE;
maxz = z > maxz ? z : maxz; float maxz = -Float.MAX_VALUE, minz = Float.MAX_VALUE;
minz = z < minz ? z : minz;
}
center.divideLocal(limit / 3);
float radius = Math.max(maxx - minx, maxy - miny) * 0.5f; FloatBuffer positions = mesh.getFloatBuffer(VertexBuffer.Type.Position);
return new BoundingTube(radius, maxz - minz, center); int limit = positions.limit();
} for (int i = 0; i < limit; i += 3) {
float x = positions.get(i);
float y = positions.get(i + 1);
float z = positions.get(i + 2);
center.addLocal(x, y, z);
maxx = x > maxx ? x : maxx;
minx = x < minx ? x : minx;
maxy = y > maxy ? y : maxy;
miny = y < miny ? y : miny;
maxz = z > maxz ? z : maxz;
minz = z < minz ? z : minz;
}
center.divideLocal(limit / 3);
/** float radius = Math.max(maxx - minx, maxy - miny) * 0.5f;
* This method returns the bounding tube of the given geometries. return new BoundingTube(radius, maxz - minz, center);
* }
* @param geometries
* the list of geometries
* @return bounding tube of the given geometries
*/
/* package */static BoundingTube getBoundingTube(List<Geometry> geometries) {
BoundingTube result = null;
for (Geometry geometry : geometries) {
BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometry.getMesh());
if (result == null) {
result = bt;
} else {
result.merge(bt);
}
}
return result;
}
/** /**
* A very simple bounding tube. Id holds only the basic data bout the * This method returns the bounding tube of the given geometries.
* bounding tube and does not provide full functionality of a *
* BoundingVolume. Should be replaced with a bounding tube that extends the * @param geometries
* BoundingVolume if it is ever created. * the list of geometries
* * @return bounding tube of the given geometries
* @author Marcin Roguski (Kaelthas) */
*/ /* package */static BoundingTube getBoundingTube(List<Geometry> geometries) {
/* package */static class BoundingTube { BoundingTube result = null;
private float radius; for (Geometry geometry : geometries) {
private float height; BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometry.getMesh());
private Vector3f center; if (result == null) {
result = bt;
} else {
result.merge(bt);
}
}
return result;
}
/** /**
* Constructor creates the tube with the given params. * A very simple bounding tube. Id holds only the basic data bout the
* * bounding tube and does not provide full functionality of a
* @param radius * BoundingVolume. Should be replaced with a bounding tube that extends the
* the radius of the tube * BoundingVolume if it is ever created.
* @param height *
* the height of the tube * @author Marcin Roguski (Kaelthas)
* @param center */
* the center of the tube /* package */static class BoundingTube {
*/ private float radius;
public BoundingTube(float radius, float height, Vector3f center) { private float height;
this.radius = radius; private Vector3f center;
this.height = height;
this.center = center;
}
/** /**
* This method merges two bounding tubes. * Constructor creates the tube with the given params.
* *
* @param boundingTube * @param radius
* bounding tube to be merged woth the current one * the radius of the tube
* @return new instance of bounding tube representing the tubes' merge * @param height
*/ * the height of the tube
public BoundingTube merge(BoundingTube boundingTube) { * @param center
// get tubes (tube1.radius >= tube2.radius) * the center of the tube
BoundingTube tube1, tube2; */
if (this.radius >= boundingTube.radius) { public BoundingTube(float radius, float height, Vector3f center) {
tube1 = this; this.radius = radius;
tube2 = boundingTube; this.height = height;
} else { this.center = center;
tube1 = boundingTube; }
tube2 = this;
}
float r1 = tube1.radius;
float r2 = tube2.radius;
float minZ = Math.min(tube1.center.z - tube1.height * 0.5f, tube2.center.z - tube2.height * 0.5f); /**
float maxZ = Math.max(tube1.center.z + tube1.height * 0.5f, tube2.center.z + tube2.height * 0.5f); * This method merges two bounding tubes.
float height = maxZ - minZ; *
Vector3f distance = tube2.center.subtract(tube1.center); * @param boundingTube
Vector3f center = tube1.center.add(distance.mult(0.5f)); * bounding tube to be merged woth the current one
distance.z = 0;// projecting this vector on XY plane * @return new instance of bounding tube representing the tubes' merge
float d = distance.length(); */
// d <= r1 - r2: tube2 is inside tube1 or touches tube1 from the public BoundingTube merge(BoundingTube boundingTube) {
// inside // get tubes (tube1.radius >= tube2.radius)
// d > r1 - r2: tube2 is outside or touches tube1 or crosses tube1 BoundingTube tube1, tube2;
float radius = d <= r1 - r2 ? tube1.radius : (d + r1 + r2) * 0.5f; if (this.radius >= boundingTube.radius) {
return new BoundingTube(radius, height, center); tube1 = this;
} tube2 = boundingTube;
} else {
tube1 = boundingTube;
tube2 = this;
}
float r1 = tube1.radius;
float r2 = tube2.radius;
/** float minZ = Math.min(tube1.center.z - tube1.height * 0.5f, tube2.center.z - tube2.height * 0.5f);
* @return the radius of the tube float maxZ = Math.max(tube1.center.z + tube1.height * 0.5f, tube2.center.z + tube2.height * 0.5f);
*/ float height = maxZ - minZ;
public float getRadius() { Vector3f distance = tube2.center.subtract(tube1.center);
return radius; Vector3f center = tube1.center.add(distance.mult(0.5f));
} distance.z = 0;// projecting this vector on XY plane
float d = distance.length();
// d <= r1 - r2: tube2 is inside tube1 or touches tube1 from the
// inside
// d > r1 - r2: tube2 is outside or touches tube1 or crosses tube1
float radius = d <= r1 - r2 ? tube1.radius : (d + r1 + r2) * 0.5f;
return new BoundingTube(radius, height, center);
}
/** /**
* @return the height of the tube * @return the radius of the tube
*/ */
public float getHeight() { public float getRadius() {
return height; return radius;
} }
/** /**
* @return the center of the tube * @return the height of the tube
*/ */
public Vector3f getCenter() { public float getHeight() {
return center; return height;
} }
}
/**
* @return the center of the tube
*/
public Vector3f getCenter() {
return center;
}
}
} }

@ -13,235 +13,228 @@ import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.BoundingTu
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class UVProjectionGenerator { /* package */class UVProjectionGenerator {
/** /**
* 2D texture mapping (projection) * 2D texture mapping (projection)
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public static enum UVProjectionType { public static enum UVProjectionType {
PROJECTION_FLAT(0), PROJECTION_FLAT(0), PROJECTION_CUBE(1), PROJECTION_TUBE(2), PROJECTION_SPHERE(3);
PROJECTION_CUBE(1),
PROJECTION_TUBE(2), public final int blenderValue;
PROJECTION_SPHERE(3);
private UVProjectionType(int blenderValue) {
public final int blenderValue; this.blenderValue = blenderValue;
}
private UVProjectionType(int blenderValue) {
this.blenderValue = blenderValue; public static UVProjectionType valueOf(int blenderValue) {
} for (UVProjectionType projectionType : UVProjectionType.values()) {
if (projectionType.blenderValue == blenderValue) {
public static UVProjectionType valueOf(int blenderValue) { return projectionType;
for(UVProjectionType projectionType : UVProjectionType.values()) { }
if(projectionType.blenderValue == blenderValue) { }
return projectionType; return null;
} }
} }
return null;
} /**
} * Flat projection for 2D textures.
*
/** * @param mesh
* Flat projection for 2D textures. * mesh that is to be projected
* * @param bb
* @param mesh * the bounding box for projecting
* mesh that is to be projected * @return UV coordinates after the projection
* @param bb */
* the bounding box for projecting public static float[] flatProjection(float[] positions, BoundingBox bb) {
* @return UV coordinates after the projection Vector3f min = bb.getMin(null);
*/ float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getZExtent() * 2.0f };
public static float[] flatProjection(float[] positions, BoundingBox bb) { float[] uvCoordinates = new float[positions.length / 3 * 2];
Vector3f min = bb.getMin(null); for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) {
float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getZExtent() * 2.0f }; uvCoordinates[j] = (positions[i] - min.x) / ext[0];
float[] uvCoordinates = new float[positions.length / 3 * 2]; // skip the Y-coordinate
for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) { uvCoordinates[j + 1] = (positions[i + 2] - min.z) / ext[1];
uvCoordinates[j] = (positions[i] - min.x) / ext[0]; }
// skip the Y-coordinate return uvCoordinates;
uvCoordinates[j + 1] = (positions[i + 2] - min.z) / ext[1]; }
}
return uvCoordinates; /**
} * Cube projection for 2D textures.
*
/** * @param positions
* Cube projection for 2D textures. * points to be projected
* * @param bb
* @param positions * the bounding box for projecting
* points to be projected * @return UV coordinates after the projection
* @param bb */
* the bounding box for projecting public static float[] cubeProjection(float[] positions, BoundingBox bb) {
* @return UV coordinates after the projection Triangle triangle = new Triangle();
*/ Vector3f x = new Vector3f(1, 0, 0);
public static float[] cubeProjection(float[] positions, BoundingBox bb) { Vector3f y = new Vector3f(0, 1, 0);
Triangle triangle = new Triangle(); Vector3f z = new Vector3f(0, 0, 1);
Vector3f x = new Vector3f(1, 0, 0); Vector3f min = bb.getMin(null);
Vector3f y = new Vector3f(0, 1, 0); float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getYExtent() * 2.0f, bb.getZExtent() * 2.0f };
Vector3f z = new Vector3f(0, 0, 1);
Vector3f min = bb.getMin(null); float[] uvCoordinates = new float[positions.length / 3 * 2];
float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getYExtent() * 2.0f, bb.getZExtent() * 2.0f }; float borderAngle = (float) Math.sqrt(2.0f) / 2.0f;
for (int i = 0, pointIndex = 0; i < positions.length; i += 9) {
float[] uvCoordinates = new float[positions.length/3*2]; triangle.set(0, positions[i], positions[i + 1], positions[i + 2]);
float borderAngle = (float) Math.sqrt(2.0f) / 2.0f; triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]);
for (int i = 0, pointIndex = 0; i < positions.length; i+=9) { triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]);
triangle.set(0, positions[i], positions[i + 1], positions[i + 2]); Vector3f n = triangle.getNormal();
triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]); float dotNX = Math.abs(n.dot(x));
triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]); float dorNY = Math.abs(n.dot(y));
Vector3f n = triangle.getNormal(); float dotNZ = Math.abs(n.dot(z));
float dotNX = Math.abs(n.dot(x)); if (dotNX > borderAngle) {
float dorNY = Math.abs(n.dot(y)); if (dotNZ < borderAngle) {// discard X-coordinate
float dotNZ = Math.abs(n.dot(z)); uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1];
if (dotNX > borderAngle) { uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2];
if (dotNZ < borderAngle) {// discard X-coordinate uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1];
uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1]; uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2];
uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2]; uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1];
uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1]; uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2];
uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2]; } else {// discard Z-coordinate
uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1]; uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2]; uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1];
} else {// discard Z-coordinate uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0]; uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1];
uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1]; uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0]; uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1];
uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1]; }
uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0]; } else {
uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1]; if (dorNY > borderAngle) {// discard Y-coordinate
} uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0];
} else { uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2];
if (dorNY > borderAngle) {// discard Y-coordinate uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0]; uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2];
uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2]; uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0]; uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2];
uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2]; } else {// discard Z-coordinate
uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0]; uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2]; uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1];
} else {// discard Z-coordinate uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0]; uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1];
uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1]; uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0]; uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1];
uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1]; }
uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0]; }
uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1]; triangle.setNormal(null);// clear the previous normal vector
} }
} return uvCoordinates;
triangle.setNormal(null);// clear the previous normal vector }
}
return uvCoordinates; /**
} * Tube projection for 2D textures.
*
/** * @param positions
* Tube projection for 2D textures. * points to be projected
* * @param bt
* @param positions * the bounding tube for projecting
* points to be projected * @return UV coordinates after the projection
* @param bt */
* the bounding tube for projecting public static float[] tubeProjection(float[] positions, BoundingTube bt) {
* @return UV coordinates after the projection float[] uvCoordinates = new float[positions.length / 3 * 2];
*/ Vector3f v = new Vector3f();
public static float[] tubeProjection(float[] positions, BoundingTube bt) { float cx = bt.getCenter().x, cz = bt.getCenter().z;
float[] uvCoordinates = new float[positions.length / 3 * 2]; Vector3f uBase = new Vector3f(0, 0, -1);
Vector3f v = new Vector3f();
float cx = bt.getCenter().x, cz = bt.getCenter().z; float vBase = bt.getCenter().y - bt.getHeight() * 0.5f;
Vector3f uBase = new Vector3f(0, 0, -1); for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) {
// calculating U
float vBase = bt.getCenter().y - bt.getHeight() * 0.5f; v.set(positions[i] - cx, 0, positions[i + 2] - cz);
for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) { v.normalizeLocal();
// calculating U float angle = v.angleBetween(uBase);// result between [0; PI]
v.set(positions[i]-cx, 0, positions[i + 2]-cz); if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then
v.normalizeLocal(); angle = FastMath.TWO_PI - angle;
float angle = v.angleBetween(uBase);// result between [0; PI] }
if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then uvCoordinates[j] = angle / FastMath.TWO_PI;
angle = FastMath.TWO_PI - angle;
} // calculating V
uvCoordinates[j] = angle / FastMath.TWO_PI; float y = positions[i + 1];
uvCoordinates[j + 1] = (y - vBase) / bt.getHeight();
// calculating V }
float y = positions[i + 1];
uvCoordinates[j + 1] = (y - vBase) / bt.getHeight(); // looking for splitted triangles
} Triangle triangle = new Triangle();
for (int i = 0; i < positions.length; i += 9) {
//looking for splitted triangles triangle.set(0, positions[i], positions[i + 1], positions[i + 2]);
Triangle triangle = new Triangle(); triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]);
for (int i = 0; i < positions.length; i+=9) { triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]);
triangle.set(0, positions[i], positions[i + 1], positions[i + 2]); float sgn1 = Math.signum(triangle.get1().x - cx);
triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]); float sgn2 = Math.signum(triangle.get2().x - cx);
triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]); float sgn3 = Math.signum(triangle.get3().x - cx);
float sgn1 = Math.signum(triangle.get1().x-cx); float xSideFactor = sgn1 + sgn2 + sgn3;
float sgn2 = Math.signum(triangle.get2().x-cx); float ySideFactor = Math.signum(triangle.get1().z - cz) + Math.signum(triangle.get2().z - cz) + Math.signum(triangle.get3().z - cz);
float sgn3 = Math.signum(triangle.get3().x-cx); if ((xSideFactor > -3 || xSideFactor < 3) && ySideFactor < 0) {// the triangle is on the splitting plane
float xSideFactor = sgn1 + sgn2 + sgn3; if (sgn1 == 1.0f) {
float ySideFactor = Math.signum(triangle.get1().z-cz)+ uvCoordinates[i / 3 * 2] += 1.0f;
Math.signum(triangle.get2().z-cz)+ }
Math.signum(triangle.get3().z-cz); if (sgn2 == 1.0f) {
if((xSideFactor>-3 || xSideFactor<3) && ySideFactor<0) {//the triangle is on the splitting plane uvCoordinates[(i / 3 + 1) * 2] += 1.0f;
if(sgn1==1.0f) { }
uvCoordinates[i/3*2] += 1.0f; if (sgn3 == 1.0f) {
} uvCoordinates[(i / 3 + 2) * 2] += 1.0f;
if(sgn2==1.0f) { }
uvCoordinates[(i/3+1)*2] += 1.0f; }
} }
if(sgn3==1.0f) { return uvCoordinates;
uvCoordinates[(i/3+2)*2] += 1.0f; }
}
} /**
} * Sphere projection for 2D textures.
return uvCoordinates; *
} * @param positions
* points to be projected
/** * @param bb
* Sphere projection for 2D textures. * the bounding box for projecting
* * @return UV coordinates after the projection
* @param positions */
* points to be projected public static float[] sphereProjection(float[] positions, BoundingSphere bs) {// TODO: rotate it to be vertical
* @param bb float[] uvCoordinates = new float[positions.length / 3 * 2];
* the bounding box for projecting Vector3f v = new Vector3f();
* @return UV coordinates after the projection float cx = bs.getCenter().x, cy = bs.getCenter().y, cz = bs.getCenter().z;
*/ Vector3f uBase = new Vector3f(0, -1, 0);
public static float[] sphereProjection(float[] positions, BoundingSphere bs) {//TODO: rotate it to be vertical Vector3f vBase = new Vector3f(0, 0, -1);
float[] uvCoordinates = new float[positions.length / 3 * 2];
Vector3f v = new Vector3f(); for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) {
float cx = bs.getCenter().x, cy = bs.getCenter().y, cz = bs.getCenter().z; // calculating U
Vector3f uBase = new Vector3f(0, -1, 0); v.set(positions[i] - cx, positions[i + 1] - cy, 0);
Vector3f vBase = new Vector3f(0, 0, -1); v.normalizeLocal();
float angle = v.angleBetween(uBase);// result between [0; PI]
for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) { if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then
// calculating U angle = FastMath.TWO_PI - angle;
v.set(positions[i]-cx, positions[i + 1] - cy, 0); }
v.normalizeLocal(); uvCoordinates[j] = angle / FastMath.TWO_PI;
float angle = v.angleBetween(uBase);// result between [0; PI]
if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then // calculating V
angle = FastMath.TWO_PI - angle; v.set(positions[i] - cx, positions[i + 1] - cy, positions[i + 2] - cz);
} v.normalizeLocal();
uvCoordinates[j] = angle / FastMath.TWO_PI; angle = v.angleBetween(vBase);// result between [0; PI]
uvCoordinates[j + 1] = angle / FastMath.PI;
// calculating V }
v.set(positions[i]-cx, positions[i + 1]-cy, positions[i + 2]-cz);
v.normalizeLocal(); // looking for splitted triangles
angle = v.angleBetween(vBase);// result between [0; PI] Triangle triangle = new Triangle();
uvCoordinates[j+1] = angle / FastMath.PI; for (int i = 0; i < positions.length; i += 9) {
} triangle.set(0, positions[i], positions[i + 1], positions[i + 2]);
triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]);
//looking for splitted triangles triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]);
Triangle triangle = new Triangle(); float sgn1 = Math.signum(triangle.get1().x - cx);
for (int i = 0; i < positions.length; i+=9) { float sgn2 = Math.signum(triangle.get2().x - cx);
triangle.set(0, positions[i], positions[i + 1], positions[i + 2]); float sgn3 = Math.signum(triangle.get3().x - cx);
triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]); float xSideFactor = sgn1 + sgn2 + sgn3;
triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]); float ySideFactor = Math.signum(triangle.get1().y - cy) + Math.signum(triangle.get2().y - cy) + Math.signum(triangle.get3().y - cy);
float sgn1 = Math.signum(triangle.get1().x-cx); if ((xSideFactor > -3 || xSideFactor < 3) && ySideFactor < 0) {// the triangle is on the splitting plane
float sgn2 = Math.signum(triangle.get2().x-cx); if (sgn1 == 1.0f) {
float sgn3 = Math.signum(triangle.get3().x-cx); uvCoordinates[i / 3 * 2] += 1.0f;
float xSideFactor = sgn1 + sgn2 + sgn3; }
float ySideFactor = Math.signum(triangle.get1().y-cy)+ if (sgn2 == 1.0f) {
Math.signum(triangle.get2().y-cy)+ uvCoordinates[(i / 3 + 1) * 2] += 1.0f;
Math.signum(triangle.get3().y-cy); }
if((xSideFactor>-3 || xSideFactor<3) && ySideFactor<0) {//the triangle is on the splitting plane if (sgn3 == 1.0f) {
if(sgn1==1.0f) { uvCoordinates[(i / 3 + 2) * 2] += 1.0f;
uvCoordinates[i/3*2] += 1.0f; }
} }
if(sgn2==1.0f) { }
uvCoordinates[(i/3+1)*2] += 1.0f; return uvCoordinates;
} }
if(sgn3==1.0f) {
uvCoordinates[(i/3+2)*2] += 1.0f;
}
}
}
return uvCoordinates;
}
} }

@ -13,124 +13,124 @@ import jme3tools.converters.MipMapGenerator;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */abstract class AbstractTextureBlender implements TextureBlender { /* package */abstract class AbstractTextureBlender implements TextureBlender {
private static final Logger LOGGER = Logger.getLogger(AbstractTextureBlender.class.getName()); private static final Logger LOGGER = Logger.getLogger(AbstractTextureBlender.class.getName());
protected int flag;
protected boolean negateTexture;
protected int blendType;
protected float[] materialColor;
protected float[] color;
protected float blendFactor;
public AbstractTextureBlender(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) {
this.flag = flag;
this.negateTexture = negateTexture;
this.blendType = blendType;
this.materialColor = materialColor;
this.color = color;
this.blendFactor = blendFactor;
}
/** protected int flag;
* The method that performs the ramp blending. protected boolean negateTexture;
* protected int blendType;
* @param type protected float[] materialColor;
* the blend type protected float[] color;
* @param materialRGB protected float blendFactor;
* the rgb value of the material, here the result is stored too
* @param fac
* color affection factor
* @param pixelColor
* the texture color
* @param blenderContext
* the blender context
*/
protected void blendHSV(int type, float[] materialRGB, float fac, float[] pixelColor, BlenderContext blenderContext) {
float oneMinusFactor = 1.0f - fac;
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
switch (type) { public AbstractTextureBlender(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) {
case MTEX_BLEND_HUE: {// FIXME: not working well for image textures (works fine for generated textures) this.flag = flag;
float[] colorTransformResult = new float[3]; this.negateTexture = negateTexture;
materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult); this.blendType = blendType;
if (colorTransformResult[0] != 0.0f) { this.materialColor = materialColor;
float colH = colorTransformResult[0]; this.color = color;
materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult); this.blendFactor = blendFactor;
materialHelper.hsvToRgb(colH, colorTransformResult[1], colorTransformResult[2], colorTransformResult); }
materialRGB[0] = oneMinusFactor * materialRGB[0] + fac * colorTransformResult[0];
materialRGB[1] = oneMinusFactor * materialRGB[1] + fac * colorTransformResult[1]; /**
materialRGB[2] = oneMinusFactor * materialRGB[2] + fac * colorTransformResult[2]; * The method that performs the ramp blending.
} *
break; * @param type
} * the blend type
case MTEX_BLEND_SAT: { * @param materialRGB
float[] colorTransformResult = new float[3]; * the rgb value of the material, here the result is stored too
materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult); * @param fac
float h = colorTransformResult[0]; * color affection factor
float s = colorTransformResult[1]; * @param pixelColor
float v = colorTransformResult[2]; * the texture color
if (s != 0.0f) { * @param blenderContext
materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult); * the blender context
materialHelper.hsvToRgb(h, oneMinusFactor * s + fac * colorTransformResult[1], v, materialRGB); */
} protected void blendHSV(int type, float[] materialRGB, float fac, float[] pixelColor, BlenderContext blenderContext) {
break; float oneMinusFactor = 1.0f - fac;
} MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
case MTEX_BLEND_VAL: {
float[] rgbToHsv = new float[3]; switch (type) {
float[] colToHsv = new float[3]; case MTEX_BLEND_HUE: {// FIXME: not working well for image textures (works fine for generated textures)
materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv); float[] colorTransformResult = new float[3];
materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv); materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult);
materialHelper.hsvToRgb(rgbToHsv[0], rgbToHsv[1], oneMinusFactor * rgbToHsv[2] + fac * colToHsv[2], materialRGB); if (colorTransformResult[0] != 0.0f) {
break; float colH = colorTransformResult[0];
} materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult);
case MTEX_BLEND_COLOR: {// FIXME: not working well for image textures (works fine for generated textures) materialHelper.hsvToRgb(colH, colorTransformResult[1], colorTransformResult[2], colorTransformResult);
float[] rgbToHsv = new float[3]; materialRGB[0] = oneMinusFactor * materialRGB[0] + fac * colorTransformResult[0];
float[] colToHsv = new float[3]; materialRGB[1] = oneMinusFactor * materialRGB[1] + fac * colorTransformResult[1];
materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv); materialRGB[2] = oneMinusFactor * materialRGB[2] + fac * colorTransformResult[2];
if (colToHsv[2] != 0) { }
materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv); break;
materialHelper.hsvToRgb(colToHsv[0], colToHsv[1], rgbToHsv[2], rgbToHsv); }
materialRGB[0] = oneMinusFactor * materialRGB[0] + fac * rgbToHsv[0]; case MTEX_BLEND_SAT: {
materialRGB[1] = oneMinusFactor * materialRGB[1] + fac * rgbToHsv[1]; float[] colorTransformResult = new float[3];
materialRGB[2] = oneMinusFactor * materialRGB[2] + fac * rgbToHsv[2]; materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult);
} float h = colorTransformResult[0];
break; float s = colorTransformResult[1];
} float v = colorTransformResult[2];
default: if (s != 0.0f) {
throw new IllegalStateException("Unknown ramp type: " + type); materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult);
} materialHelper.hsvToRgb(h, oneMinusFactor * s + fac * colorTransformResult[1], v, materialRGB);
} }
break;
public void copyBlendingData(TextureBlender textureBlender) { }
if(textureBlender instanceof AbstractTextureBlender) { case MTEX_BLEND_VAL: {
this.flag = ((AbstractTextureBlender) textureBlender).flag; float[] rgbToHsv = new float[3];
this.negateTexture = ((AbstractTextureBlender) textureBlender).negateTexture; float[] colToHsv = new float[3];
this.blendType = ((AbstractTextureBlender) textureBlender).blendType; materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv);
this.materialColor = ((AbstractTextureBlender) textureBlender).materialColor.clone(); materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv);
this.color = ((AbstractTextureBlender) textureBlender).color.clone(); materialHelper.hsvToRgb(rgbToHsv[0], rgbToHsv[1], oneMinusFactor * rgbToHsv[2] + fac * colToHsv[2], materialRGB);
this.blendFactor = ((AbstractTextureBlender) textureBlender).blendFactor; break;
} else { }
LOGGER.warning("Cannot copy blending data from other types than " + this.getClass()); case MTEX_BLEND_COLOR: {// FIXME: not working well for image textures (works fine for generated textures)
} float[] rgbToHsv = new float[3];
} float[] colToHsv = new float[3];
materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv);
/** if (colToHsv[2] != 0) {
* The method prepares images for blending. It generates mipmaps if one of materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv);
* the images has them defined and the other one has not. materialHelper.hsvToRgb(colToHsv[0], colToHsv[1], rgbToHsv[2], rgbToHsv);
* materialRGB[0] = oneMinusFactor * materialRGB[0] + fac * rgbToHsv[0];
* @param target materialRGB[1] = oneMinusFactor * materialRGB[1] + fac * rgbToHsv[1];
* the image where the blending result is stored materialRGB[2] = oneMinusFactor * materialRGB[2] + fac * rgbToHsv[2];
* @param source }
* the image that is being read only break;
*/ }
protected void prepareImagesForBlending(Image target, Image source) { default:
LOGGER.fine("Generating mipmaps if needed!"); throw new IllegalStateException("Unknown ramp type: " + type);
boolean targetHasMipmaps = target == null ? false : target.getMipMapSizes() != null && target.getMipMapSizes().length > 0; }
boolean sourceHasMipmaps = source == null ? false : source.getMipMapSizes() != null && source.getMipMapSizes().length > 0; }
if (target != null && !targetHasMipmaps && sourceHasMipmaps) {
MipMapGenerator.generateMipMaps(target); public void copyBlendingData(TextureBlender textureBlender) {
} else if (source != null && !sourceHasMipmaps && targetHasMipmaps) { if (textureBlender instanceof AbstractTextureBlender) {
MipMapGenerator.generateMipMaps(source); this.flag = ((AbstractTextureBlender) textureBlender).flag;
} this.negateTexture = ((AbstractTextureBlender) textureBlender).negateTexture;
} this.blendType = ((AbstractTextureBlender) textureBlender).blendType;
this.materialColor = ((AbstractTextureBlender) textureBlender).materialColor.clone();
this.color = ((AbstractTextureBlender) textureBlender).color.clone();
this.blendFactor = ((AbstractTextureBlender) textureBlender).blendFactor;
} else {
LOGGER.warning("Cannot copy blending data from other types than " + this.getClass());
}
}
/**
* The method prepares images for blending. It generates mipmaps if one of
* the images has them defined and the other one has not.
*
* @param target
* the image where the blending result is stored
* @param source
* the image that is being read only
*/
protected void prepareImagesForBlending(Image target, Image source) {
LOGGER.fine("Generating mipmaps if needed!");
boolean targetHasMipmaps = target == null ? false : target.getMipMapSizes() != null && target.getMipMapSizes().length > 0;
boolean sourceHasMipmaps = source == null ? false : source.getMipMapSizes() != null && source.getMipMapSizes().length > 0;
if (target != null && !targetHasMipmaps && sourceHasMipmaps) {
MipMapGenerator.generateMipMaps(target);
} else if (source != null && !sourceHasMipmaps && targetHasMipmaps) {
MipMapGenerator.generateMipMaps(source);
}
}
} }

@ -10,44 +10,44 @@ import com.jme3.texture.Image;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public interface TextureBlender { public interface TextureBlender {
// types of blending // types of blending
int MTEX_BLEND = 0; int MTEX_BLEND = 0;
int MTEX_MUL = 1; int MTEX_MUL = 1;
int MTEX_ADD = 2; int MTEX_ADD = 2;
int MTEX_SUB = 3; int MTEX_SUB = 3;
int MTEX_DIV = 4; int MTEX_DIV = 4;
int MTEX_DARK = 5; int MTEX_DARK = 5;
int MTEX_DIFF = 6; int MTEX_DIFF = 6;
int MTEX_LIGHT = 7; int MTEX_LIGHT = 7;
int MTEX_SCREEN = 8; int MTEX_SCREEN = 8;
int MTEX_OVERLAY = 9; int MTEX_OVERLAY = 9;
int MTEX_BLEND_HUE = 10; int MTEX_BLEND_HUE = 10;
int MTEX_BLEND_SAT = 11; int MTEX_BLEND_SAT = 11;
int MTEX_BLEND_VAL = 12; int MTEX_BLEND_VAL = 12;
int MTEX_BLEND_COLOR = 13; int MTEX_BLEND_COLOR = 13;
int MTEX_NUM_BLENDTYPES = 14; int MTEX_NUM_BLENDTYPES = 14;
/** /**
* This method blends the given texture with material color and the defined * This method blends the given texture with material color and the defined
* color in 'map to' panel. As a result of this method a new texture is * color in 'map to' panel. As a result of this method a new texture is
* created. The input texture is NOT. * created. The input texture is NOT.
* *
* @param image * @param image
* the image we use in blending * the image we use in blending
* @param baseImage * @param baseImage
* the texture that is underneath the current texture (its pixels * the texture that is underneath the current texture (its pixels
* will be used instead of material color) * will be used instead of material color)
* @param blenderContext * @param blenderContext
* the blender context * the blender context
* @return new image that was created after the blending * @return new image that was created after the blending
*/ */
Image blend(Image image, Image baseImage, BlenderContext blenderContext); Image blend(Image image, Image baseImage, BlenderContext blenderContext);
/** /**
* Copies blending data. Used for blending type format changing. * Copies blending data. Used for blending type format changing.
* *
* @param textureBlender * @param textureBlender
* the blend data that should be copied * the blend data that should be copied
*/ */
void copyBlendingData(TextureBlender textureBlender); void copyBlendingData(TextureBlender textureBlender);
} }

@ -43,202 +43,184 @@ import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
/** /**
* The class that is responsible for blending the following texture types: * The class that is responsible for blending the following texture types: <li>RGBA8 <li>ABGR8 <li>BGR8 <li>RGB8 Not yet supported (but will be): <li>ARGB4444: <li>RGB10: <li>RGB111110F: <li>RGB16: <li>RGB16F: <li>RGB16F_to_RGB111110F: <li>RGB16F_to_RGB9E5: <li>RGB32F: <li>RGB565: <li>RGB5A1: <li>RGB9E5: <li>RGBA16: <li>RGBA16F
* <li> RGBA8
* <li> ABGR8
* <li> BGR8
* <li> RGB8
* Not yet supported (but will be):
* <li> ARGB4444:
* <li> RGB10:
* <li> RGB111110F:
* <li> RGB16:
* <li> RGB16F:
* <li> RGB16F_to_RGB111110F:
* <li> RGB16F_to_RGB9E5:
* <li> RGB32F:
* <li> RGB565:
* <li> RGB5A1:
* <li> RGB9E5:
* <li> RGBA16:
* <li> RGBA16F
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class TextureBlenderAWT extends AbstractTextureBlender { public class TextureBlenderAWT extends AbstractTextureBlender {
public TextureBlenderAWT(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { public TextureBlenderAWT(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) {
super(flag, negateTexture, blendType, materialColor, color, blendFactor); super(flag, negateTexture, blendType, materialColor, color, blendFactor);
} }
public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
this.prepareImagesForBlending(image, baseImage);
float[] pixelColor = new float[] { color[0], color[1], color[2], 1.0f };
Format format = image.getFormat();
PixelInputOutput basePixelIO = null, pixelReader = PixelIOFactory.getPixelIO(format);
TexturePixel basePixel = null, pixel = new TexturePixel();
float[] materialColor = this.materialColor;
if(baseImage != null) {
basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat());
materialColor = new float[this.materialColor.length];
basePixel = new TexturePixel();
}
int width = image.getWidth();
int height = image.getHeight();
int depth = image.getDepth();
if (depth == 0) {
depth = 1;
}
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(depth);
float[] resultPixel = new float[4];
for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) {
ByteBuffer data = image.getData(dataLayerIndex);
data.rewind();
ByteBuffer newData = BufferUtils.createByteBuffer(width * height * 4);
int dataIndex = 0, x = 0, y = 0, index = 0;
while (index < data.limit()) {
//getting the proper material color if the base texture is applied
if(basePixelIO != null) {
basePixelIO.read(baseImage, dataLayerIndex, basePixel, x, y);
basePixel.toRGBA(materialColor);
}
//reading the current texture's pixel
pixelReader.read(image, dataLayerIndex, pixel, index);
index += image.getFormat().getBitsPerPixel() >> 3;
pixel.toRGBA(pixelColor);
if (negateTexture) {
pixel.negate();
}
this.blendPixel(resultPixel, materialColor, pixelColor, blenderContext);
newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f));
newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f));
newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f));
newData.put(dataIndex++, (byte) (pixelColor[3] * 255.0f));
++x;
if(x >= width) {
x = 0;
++y;
}
}
dataArray.add(newData);
}
Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray) : new Image(Format.RGBA8, width, height, dataArray.get(0));
if(image.getMipMapSizes() != null) {
result.setMipMapSizes(image.getMipMapSizes().clone());
}
return result;
}
/** public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
* This method blends the single pixel depending on the blending type. this.prepareImagesForBlending(image, baseImage);
*
* @param result
* the result pixel
* @param materialColor
* the material color
* @param pixelColor
* the pixel color
* @param blenderContext
* the blender context
*/
protected void blendPixel(float[] result, float[] materialColor, float[] pixelColor, BlenderContext blenderContext) {
float blendFactor = this.blendFactor * pixelColor[3];
float oneMinusFactor = 1.0f - blendFactor, col;
switch (blendType) { float[] pixelColor = new float[] { color[0], color[1], color[2], 1.0f };
case MTEX_BLEND: Format format = image.getFormat();
result[0] = blendFactor * pixelColor[0] + oneMinusFactor * materialColor[0];
result[1] = blendFactor * pixelColor[1] + oneMinusFactor * materialColor[1]; PixelInputOutput basePixelIO = null, pixelReader = PixelIOFactory.getPixelIO(format);
result[2] = blendFactor * pixelColor[2] + oneMinusFactor * materialColor[2]; TexturePixel basePixel = null, pixel = new TexturePixel();
break; float[] materialColor = this.materialColor;
case MTEX_MUL: if (baseImage != null) {
result[0] = (oneMinusFactor + blendFactor * materialColor[0]) * pixelColor[0]; basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat());
result[1] = (oneMinusFactor + blendFactor * materialColor[1]) * pixelColor[1]; materialColor = new float[this.materialColor.length];
result[2] = (oneMinusFactor + blendFactor * materialColor[2]) * pixelColor[2]; basePixel = new TexturePixel();
break; }
case MTEX_DIV:
if (pixelColor[0] != 0.0) { int width = image.getWidth();
result[0] = (oneMinusFactor * materialColor[0] + blendFactor * materialColor[0] / pixelColor[0]) * 0.5f; int height = image.getHeight();
} int depth = image.getDepth();
if (pixelColor[1] != 0.0) { if (depth == 0) {
result[1] = (oneMinusFactor * materialColor[1] + blendFactor * materialColor[1] / pixelColor[1]) * 0.5f; depth = 1;
} }
if (pixelColor[2] != 0.0) { ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(depth);
result[2] = (oneMinusFactor * materialColor[2] + blendFactor * materialColor[2] / pixelColor[2]) * 0.5f;
} float[] resultPixel = new float[4];
break; for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) {
case MTEX_SCREEN: ByteBuffer data = image.getData(dataLayerIndex);
result[0] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]); data.rewind();
result[1] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]); ByteBuffer newData = BufferUtils.createByteBuffer(width * height * 4);
result[2] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]);
break; int dataIndex = 0, x = 0, y = 0, index = 0;
case MTEX_OVERLAY: while (index < data.limit()) {
if (materialColor[0] < 0.5f) { // getting the proper material color if the base texture is applied
result[0] = pixelColor[0] * (oneMinusFactor + 2.0f * blendFactor * materialColor[0]); if (basePixelIO != null) {
} else { basePixelIO.read(baseImage, dataLayerIndex, basePixel, x, y);
result[0] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]); basePixel.toRGBA(materialColor);
} }
if (materialColor[1] < 0.5f) {
result[1] = pixelColor[1] * (oneMinusFactor + 2.0f * blendFactor * materialColor[1]); // reading the current texture's pixel
} else { pixelReader.read(image, dataLayerIndex, pixel, index);
result[1] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]); index += image.getFormat().getBitsPerPixel() >> 3;
} pixel.toRGBA(pixelColor);
if (materialColor[2] < 0.5f) { if (negateTexture) {
result[2] = pixelColor[2] * (oneMinusFactor + 2.0f * blendFactor * materialColor[2]); pixel.negate();
} else { }
result[2] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]);
} this.blendPixel(resultPixel, materialColor, pixelColor, blenderContext);
break; newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f));
case MTEX_SUB: newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f));
result[0] = materialColor[0] - blendFactor * pixelColor[0]; newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f));
result[1] = materialColor[1] - blendFactor * pixelColor[1]; newData.put(dataIndex++, (byte) (pixelColor[3] * 255.0f));
result[2] = materialColor[2] - blendFactor * pixelColor[2];
result[0] = FastMath.clamp(result[0], 0.0f, 1.0f); ++x;
result[1] = FastMath.clamp(result[1], 0.0f, 1.0f); if (x >= width) {
result[2] = FastMath.clamp(result[2], 0.0f, 1.0f); x = 0;
break; ++y;
case MTEX_ADD: }
result[0] = (blendFactor * pixelColor[0] + materialColor[0]) * 0.5f; }
result[1] = (blendFactor * pixelColor[1] + materialColor[1]) * 0.5f; dataArray.add(newData);
result[2] = (blendFactor * pixelColor[2] + materialColor[2]) * 0.5f; }
break;
case MTEX_DIFF: Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray) : new Image(Format.RGBA8, width, height, dataArray.get(0));
result[0] = oneMinusFactor * materialColor[0] + blendFactor * Math.abs(materialColor[0] - pixelColor[0]); if (image.getMipMapSizes() != null) {
result[1] = oneMinusFactor * materialColor[1] + blendFactor * Math.abs(materialColor[1] - pixelColor[1]); result.setMipMapSizes(image.getMipMapSizes().clone());
result[2] = oneMinusFactor * materialColor[2] + blendFactor * Math.abs(materialColor[2] - pixelColor[2]); }
break; return result;
case MTEX_DARK: }
col = blendFactor * pixelColor[0];
result[0] = col < materialColor[0] ? col : materialColor[0]; /**
col = blendFactor * pixelColor[1]; * This method blends the single pixel depending on the blending type.
result[1] = col < materialColor[1] ? col : materialColor[1]; *
col = blendFactor * pixelColor[2]; * @param result
result[2] = col < materialColor[2] ? col : materialColor[2]; * the result pixel
break; * @param materialColor
case MTEX_LIGHT: * the material color
col = blendFactor * pixelColor[0]; * @param pixelColor
result[0] = col > materialColor[0] ? col : materialColor[0]; * the pixel color
col = blendFactor * pixelColor[1]; * @param blenderContext
result[1] = col > materialColor[1] ? col : materialColor[1]; * the blender context
col = blendFactor * pixelColor[2]; */
result[2] = col > materialColor[2] ? col : materialColor[2]; protected void blendPixel(float[] result, float[] materialColor, float[] pixelColor, BlenderContext blenderContext) {
break; float blendFactor = this.blendFactor * pixelColor[3];
case MTEX_BLEND_HUE: float oneMinusFactor = 1.0f - blendFactor, col;
case MTEX_BLEND_SAT:
case MTEX_BLEND_VAL: switch (blendType) {
case MTEX_BLEND_COLOR: case MTEX_BLEND:
System.arraycopy(materialColor, 0, result, 0, 3); result[0] = blendFactor * pixelColor[0] + oneMinusFactor * materialColor[0];
this.blendHSV(blendType, result, blendFactor, pixelColor, blenderContext); result[1] = blendFactor * pixelColor[1] + oneMinusFactor * materialColor[1];
break; result[2] = blendFactor * pixelColor[2] + oneMinusFactor * materialColor[2];
default: break;
throw new IllegalStateException("Unknown blend type: " + blendType); case MTEX_MUL:
} result[0] = (oneMinusFactor + blendFactor * materialColor[0]) * pixelColor[0];
} result[1] = (oneMinusFactor + blendFactor * materialColor[1]) * pixelColor[1];
result[2] = (oneMinusFactor + blendFactor * materialColor[2]) * pixelColor[2];
break;
case MTEX_DIV:
if (pixelColor[0] != 0.0) {
result[0] = (oneMinusFactor * materialColor[0] + blendFactor * materialColor[0] / pixelColor[0]) * 0.5f;
}
if (pixelColor[1] != 0.0) {
result[1] = (oneMinusFactor * materialColor[1] + blendFactor * materialColor[1] / pixelColor[1]) * 0.5f;
}
if (pixelColor[2] != 0.0) {
result[2] = (oneMinusFactor * materialColor[2] + blendFactor * materialColor[2] / pixelColor[2]) * 0.5f;
}
break;
case MTEX_SCREEN:
result[0] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]);
result[1] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]);
result[2] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]);
break;
case MTEX_OVERLAY:
if (materialColor[0] < 0.5f) {
result[0] = pixelColor[0] * (oneMinusFactor + 2.0f * blendFactor * materialColor[0]);
} else {
result[0] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]);
}
if (materialColor[1] < 0.5f) {
result[1] = pixelColor[1] * (oneMinusFactor + 2.0f * blendFactor * materialColor[1]);
} else {
result[1] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]);
}
if (materialColor[2] < 0.5f) {
result[2] = pixelColor[2] * (oneMinusFactor + 2.0f * blendFactor * materialColor[2]);
} else {
result[2] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]);
}
break;
case MTEX_SUB:
result[0] = materialColor[0] - blendFactor * pixelColor[0];
result[1] = materialColor[1] - blendFactor * pixelColor[1];
result[2] = materialColor[2] - blendFactor * pixelColor[2];
result[0] = FastMath.clamp(result[0], 0.0f, 1.0f);
result[1] = FastMath.clamp(result[1], 0.0f, 1.0f);
result[2] = FastMath.clamp(result[2], 0.0f, 1.0f);
break;
case MTEX_ADD:
result[0] = (blendFactor * pixelColor[0] + materialColor[0]) * 0.5f;
result[1] = (blendFactor * pixelColor[1] + materialColor[1]) * 0.5f;
result[2] = (blendFactor * pixelColor[2] + materialColor[2]) * 0.5f;
break;
case MTEX_DIFF:
result[0] = oneMinusFactor * materialColor[0] + blendFactor * Math.abs(materialColor[0] - pixelColor[0]);
result[1] = oneMinusFactor * materialColor[1] + blendFactor * Math.abs(materialColor[1] - pixelColor[1]);
result[2] = oneMinusFactor * materialColor[2] + blendFactor * Math.abs(materialColor[2] - pixelColor[2]);
break;
case MTEX_DARK:
col = blendFactor * pixelColor[0];
result[0] = col < materialColor[0] ? col : materialColor[0];
col = blendFactor * pixelColor[1];
result[1] = col < materialColor[1] ? col : materialColor[1];
col = blendFactor * pixelColor[2];
result[2] = col < materialColor[2] ? col : materialColor[2];
break;
case MTEX_LIGHT:
col = blendFactor * pixelColor[0];
result[0] = col > materialColor[0] ? col : materialColor[0];
col = blendFactor * pixelColor[1];
result[1] = col > materialColor[1] ? col : materialColor[1];
col = blendFactor * pixelColor[2];
result[2] = col > materialColor[2] ? col : materialColor[2];
break;
case MTEX_BLEND_HUE:
case MTEX_BLEND_SAT:
case MTEX_BLEND_VAL:
case MTEX_BLEND_COLOR:
System.arraycopy(materialColor, 0, result, 0, 3);
this.blendHSV(blendType, result, blendFactor, pixelColor, blenderContext);
break;
default:
throw new IllegalStateException("Unknown blend type: " + blendType);
}
}
} }

@ -12,113 +12,109 @@ import java.util.ArrayList;
import jme3tools.converters.RGB565; import jme3tools.converters.RGB565;
/** /**
* The class that is responsible for blending the following texture types: * The class that is responsible for blending the following texture types: <li>DXT1 <li>DXT1A <li>DXT3 <li>DXT5
* <li> DXT1
* <li> DXT1A
* <li> DXT3
* <li> DXT5
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class TextureBlenderDDS extends TextureBlenderAWT { public class TextureBlenderDDS extends TextureBlenderAWT {
public TextureBlenderDDS(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { public TextureBlenderDDS(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) {
super(flag, negateTexture, blendType, materialColor, color, blendFactor); super(flag, negateTexture, blendType, materialColor, color, blendFactor);
} }
@Override
public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
this.prepareImagesForBlending(image, baseImage);
Format format = image.getFormat();
int width = image.getWidth();
int height = image.getHeight();
int depth = image.getDepth();
if (depth == 0) {
depth = 1;
}
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(depth);
PixelInputOutput basePixelIO = null;
float[][] compressedMaterialColor = null;
TexturePixel[] baseTextureColors = null;
if(baseImage != null) {
basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat());
compressedMaterialColor = new float[2][4];
baseTextureColors = new TexturePixel[] { new TexturePixel(), new TexturePixel() };
}
float[] resultPixel = new float[4];
float[] pixelColor = new float[4];
TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel() };
int baseXTexelIndex = 0, baseYTexelIndex = 0;
float[] alphas = new float[] {1, 1};
for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) {
ByteBuffer data = image.getData(dataLayerIndex);
data.rewind();
ByteBuffer newData = BufferUtils.createByteBuffer(data.remaining());
while (data.hasRemaining()) {
if(format == Format.DXT3) {
long alpha = data.getLong();
//get alpha for first and last pixel that is compressed in the texel
byte alpha0 = (byte)(alpha << 4 & 0xFF);
byte alpha1 = (byte)(alpha >> 60 & 0xFF);
alphas[0] = alpha0 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f;
alphas[1] = alpha1 >= 0 ? alpha1 / 255.0f : 1.0f - ~alpha1 / 255.0f;
newData.putLong(alpha);
} else if(format == Format.DXT5) {
byte alpha0 = data.get();
byte alpha1 = data.get();
alphas[0] = alpha0 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f;
alphas[1] = alpha1 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f;
newData.put(alpha0);
newData.put(alpha1);
//only read the next 6 bytes (these are alpha indexes)
newData.putInt(data.getInt());
newData.putShort(data.getShort());
}
int col0 = RGB565.RGB565_to_ARGB8(data.getShort());
int col1 = RGB565.RGB565_to_ARGB8(data.getShort());
colors[0].fromARGB8(col0);
colors[1].fromARGB8(col1);
//compressing 16 pixels from the base texture as if they belonged to a texel
if(baseImage != null) {
//reading pixels (first and last of the 16 colors array)
basePixelIO.read(baseImage, dataLayerIndex, baseTextureColors[0], baseXTexelIndex << 2, baseYTexelIndex << 2);//first pixel
basePixelIO.read(baseImage, dataLayerIndex, baseTextureColors[1], baseXTexelIndex << 2 + 4, baseYTexelIndex << 2 + 4);//last pixel
baseTextureColors[0].toRGBA(compressedMaterialColor[0]);
baseTextureColors[1].toRGBA(compressedMaterialColor[1]);
}
// blending colors @Override
for (int i = 0; i < colors.length; ++i) { public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
if (negateTexture) { this.prepareImagesForBlending(image, baseImage);
colors[i].negate();
}
colors[i].toRGBA(pixelColor);
pixelColor[3] = alphas[i];
this.blendPixel(resultPixel, compressedMaterialColor != null ? compressedMaterialColor[i] : materialColor, pixelColor, blenderContext);
colors[i].fromARGB(1, resultPixel[0], resultPixel[1], resultPixel[2]);
int argb8 = colors[i].toARGB8();
short rgb565 = RGB565.ARGB8_to_RGB565(argb8);
newData.putShort(rgb565);
}
// just copy the remaining 4 bytes of the current texel Format format = image.getFormat();
newData.putInt(data.getInt()); int width = image.getWidth();
int height = image.getHeight();
++baseXTexelIndex; int depth = image.getDepth();
if(baseXTexelIndex > image.getWidth() >> 2) { if (depth == 0) {
baseXTexelIndex = 0; depth = 1;
++baseYTexelIndex; }
} ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(depth);
}
dataArray.add(newData); PixelInputOutput basePixelIO = null;
} float[][] compressedMaterialColor = null;
TexturePixel[] baseTextureColors = null;
Image result = dataArray.size() > 1 ? new Image(format, width, height, depth, dataArray) : new Image(format, width, height, dataArray.get(0)); if (baseImage != null) {
if(image.getMipMapSizes() != null) { basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat());
result.setMipMapSizes(image.getMipMapSizes().clone()); compressedMaterialColor = new float[2][4];
} baseTextureColors = new TexturePixel[] { new TexturePixel(), new TexturePixel() };
return result; }
}
float[] resultPixel = new float[4];
float[] pixelColor = new float[4];
TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel() };
int baseXTexelIndex = 0, baseYTexelIndex = 0;
float[] alphas = new float[] { 1, 1 };
for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) {
ByteBuffer data = image.getData(dataLayerIndex);
data.rewind();
ByteBuffer newData = BufferUtils.createByteBuffer(data.remaining());
while (data.hasRemaining()) {
if (format == Format.DXT3) {
long alpha = data.getLong();
// get alpha for first and last pixel that is compressed in the texel
byte alpha0 = (byte) (alpha << 4 & 0xFF);
byte alpha1 = (byte) (alpha >> 60 & 0xFF);
alphas[0] = alpha0 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f;
alphas[1] = alpha1 >= 0 ? alpha1 / 255.0f : 1.0f - ~alpha1 / 255.0f;
newData.putLong(alpha);
} else if (format == Format.DXT5) {
byte alpha0 = data.get();
byte alpha1 = data.get();
alphas[0] = alpha0 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f;
alphas[1] = alpha1 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f;
newData.put(alpha0);
newData.put(alpha1);
// only read the next 6 bytes (these are alpha indexes)
newData.putInt(data.getInt());
newData.putShort(data.getShort());
}
int col0 = RGB565.RGB565_to_ARGB8(data.getShort());
int col1 = RGB565.RGB565_to_ARGB8(data.getShort());
colors[0].fromARGB8(col0);
colors[1].fromARGB8(col1);
// compressing 16 pixels from the base texture as if they belonged to a texel
if (baseImage != null) {
// reading pixels (first and last of the 16 colors array)
basePixelIO.read(baseImage, dataLayerIndex, baseTextureColors[0], baseXTexelIndex << 2, baseYTexelIndex << 2);// first pixel
basePixelIO.read(baseImage, dataLayerIndex, baseTextureColors[1], baseXTexelIndex << 2 + 4, baseYTexelIndex << 2 + 4);// last pixel
baseTextureColors[0].toRGBA(compressedMaterialColor[0]);
baseTextureColors[1].toRGBA(compressedMaterialColor[1]);
}
// blending colors
for (int i = 0; i < colors.length; ++i) {
if (negateTexture) {
colors[i].negate();
}
colors[i].toRGBA(pixelColor);
pixelColor[3] = alphas[i];
this.blendPixel(resultPixel, compressedMaterialColor != null ? compressedMaterialColor[i] : materialColor, pixelColor, blenderContext);
colors[i].fromARGB(1, resultPixel[0], resultPixel[1], resultPixel[2]);
int argb8 = colors[i].toARGB8();
short rgb565 = RGB565.ARGB8_to_RGB565(argb8);
newData.putShort(rgb565);
}
// just copy the remaining 4 bytes of the current texel
newData.putInt(data.getInt());
++baseXTexelIndex;
if (baseXTexelIndex > image.getWidth() >> 2) {
baseXTexelIndex = 0;
++baseYTexelIndex;
}
}
dataArray.add(newData);
}
Image result = dataArray.size() > 1 ? new Image(format, width, height, depth, dataArray) : new Image(format, width, height, dataArray.get(0));
if (image.getMipMapSizes() != null) {
result.setMipMapSizes(image.getMipMapSizes().clone());
}
return result;
}
} }

@ -43,86 +43,86 @@ import java.util.logging.Logger;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class TextureBlenderFactory { public class TextureBlenderFactory {
private static final Logger LOGGER = Logger.getLogger(TextureBlenderFactory.class.getName()); private static final Logger LOGGER = Logger.getLogger(TextureBlenderFactory.class.getName());
/** /**
* This method creates the blending class. * This method creates the blending class.
* *
* @param format * @param format
* the texture format * the texture format
* @return texture blending class * @return texture blending class
*/ */
public static TextureBlender createTextureBlender(Format format, int flag, boolean negate, int blendType, float[] materialColor, float[] color, float colfac) { public static TextureBlender createTextureBlender(Format format, int flag, boolean negate, int blendType, float[] materialColor, float[] color, float colfac) {
switch (format) { switch (format) {
case Luminance8: case Luminance8:
case Luminance8Alpha8: case Luminance8Alpha8:
case Luminance16: case Luminance16:
case Luminance16Alpha16: case Luminance16Alpha16:
case Luminance16F: case Luminance16F:
case Luminance16FAlpha16F: case Luminance16FAlpha16F:
case Luminance32F: case Luminance32F:
return new TextureBlenderLuminance(flag, negate, blendType, materialColor, color, colfac); return new TextureBlenderLuminance(flag, negate, blendType, materialColor, color, colfac);
case RGBA8: case RGBA8:
case ABGR8: case ABGR8:
case BGR8: case BGR8:
case RGB8: case RGB8:
case RGB10: case RGB10:
case RGB111110F: case RGB111110F:
case RGB16: case RGB16:
case RGB16F: case RGB16F:
case RGB16F_to_RGB111110F: case RGB16F_to_RGB111110F:
case RGB16F_to_RGB9E5: case RGB16F_to_RGB9E5:
case RGB32F: case RGB32F:
case RGB565: case RGB565:
case RGB5A1: case RGB5A1:
case RGB9E5: case RGB9E5:
case RGBA16: case RGBA16:
case RGBA16F: case RGBA16F:
case RGBA32F: case RGBA32F:
return new TextureBlenderAWT(flag, negate, blendType, materialColor, color, colfac); return new TextureBlenderAWT(flag, negate, blendType, materialColor, color, colfac);
case DXT1: case DXT1:
case DXT1A: case DXT1A:
case DXT3: case DXT3:
case DXT5: case DXT5:
return new TextureBlenderDDS(flag, negate, blendType, materialColor, color, colfac); return new TextureBlenderDDS(flag, negate, blendType, materialColor, color, colfac);
case Alpha16: case Alpha16:
case Alpha8: case Alpha8:
case ARGB4444: case ARGB4444:
case Depth: case Depth:
case Depth16: case Depth16:
case Depth24: case Depth24:
case Depth32: case Depth32:
case Depth32F: case Depth32F:
case Intensity16: case Intensity16:
case Intensity8: case Intensity8:
case LATC: case LATC:
case LTC: case LTC:
LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}. Returning a blender that does not change the texture.", format); LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}. Returning a blender that does not change the texture.", format);
return new TextureBlender() { return new TextureBlender() {
public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
return image; return image;
} }
public void copyBlendingData(TextureBlender textureBlender) { public void copyBlendingData(TextureBlender textureBlender) {
} }
}; };
default: default:
throw new IllegalStateException("Unknown image format type: " + format); throw new IllegalStateException("Unknown image format type: " + format);
} }
} }
/** /**
* This method changes the image format in the texture blender. * This method changes the image format in the texture blender.
* *
* @param format * @param format
* the new image format * the new image format
* @param textureBlender * @param textureBlender
* the texture blender that will be altered * the texture blender that will be altered
* @return altered texture blender * @return altered texture blender
*/ */
public static TextureBlender alterTextureType(Format format, TextureBlender textureBlender) { public static TextureBlender alterTextureType(Format format, TextureBlender textureBlender) {
TextureBlender result = TextureBlenderFactory.createTextureBlender(format, 0, false, 0, null, null, 0); TextureBlender result = TextureBlenderFactory.createTextureBlender(format, 0, false, 0, null, null, 0);
result.copyBlendingData(textureBlender); result.copyBlendingData(textureBlender);
return result; return result;
} }
} }

@ -14,236 +14,228 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
/** /**
* The class that is responsible for blending the following texture types: * The class that is responsible for blending the following texture types: <li>Luminance8 <li>Luminance8Alpha8 Not yet supported (but will be): <li>Luminance16: <li>Luminance16Alpha16: <li>Luminance16F: <li>Luminance16FAlpha16F: <li>Luminance32F:
* <li> Luminance8
* <li> Luminance8Alpha8
* Not yet supported (but will be):
* <li> Luminance16:
* <li> Luminance16Alpha16:
* <li> Luminance16F:
* <li> Luminance16FAlpha16F:
* <li> Luminance32F:
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class TextureBlenderLuminance extends AbstractTextureBlender { public class TextureBlenderLuminance extends AbstractTextureBlender {
private static final Logger LOGGER = Logger.getLogger(TextureBlenderLuminance.class.getName()); private static final Logger LOGGER = Logger.getLogger(TextureBlenderLuminance.class.getName());
public TextureBlenderLuminance(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { public TextureBlenderLuminance(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) {
super(flag, negateTexture, blendType, materialColor, color, blendFactor); super(flag, negateTexture, blendType, materialColor, color, blendFactor);
} }
public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
this.prepareImagesForBlending(image, baseImage);
Format format = image.getFormat();
PixelInputOutput basePixelIO = null;
TexturePixel basePixel = null;
float[] materialColor = this.materialColor;
if(baseImage != null) {
basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat());
materialColor = new float[this.materialColor.length];
basePixel = new TexturePixel();
}
int width = image.getWidth();
int height = image.getHeight();
int depth = image.getDepth();
if (depth == 0) {
depth = 1;
}
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(depth);
float[] resultPixel = new float[4]; public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
float[] tinAndAlpha = new float[2]; this.prepareImagesForBlending(image, baseImage);
for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) {
ByteBuffer data = image.getData(dataLayerIndex);
data.rewind();
ByteBuffer newData = BufferUtils.createByteBuffer(width * height * 4);
int dataIndex = 0, x = 0, y = 0;
while (data.hasRemaining()) {
//getting the proper material color if the base texture is applied
if(basePixelIO != null) {
basePixelIO.read(baseImage, dataLayerIndex, basePixel, x, y);
basePixel.toRGBA(materialColor);
}
this.getTinAndAlpha(data, format, negateTexture, tinAndAlpha);
this.blendPixel(resultPixel, materialColor, color, tinAndAlpha[0], blendFactor, blendType, blenderContext);
newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f));
newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f));
newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f));
newData.put(dataIndex++, (byte) (tinAndAlpha[1] * 255.0f));
++x;
if(x >= width) {
x = 0;
++y;
}
}
dataArray.add(newData);
}
Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray) : new Image(Format.RGBA8, width, height, dataArray.get(0));
if(image.getMipMapSizes() != null) {
result.setMipMapSizes(image.getMipMapSizes().clone());
}
return result;
}
/** Format format = image.getFormat();
* This method return texture intensity and alpha value. PixelInputOutput basePixelIO = null;
* TexturePixel basePixel = null;
* @param data float[] materialColor = this.materialColor;
* the texture data if (baseImage != null) {
* @param imageFormat basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat());
* the image format materialColor = new float[this.materialColor.length];
* @param neg basePixel = new TexturePixel();
* indicates if the texture is negated }
* @param result
* the table (2 elements) where the result is being stored
*/
protected void getTinAndAlpha(ByteBuffer data, Format imageFormat, boolean neg, float[] result) {
byte pixelValue = data.get();// at least one byte is always taken
float firstPixelValue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f;
switch (imageFormat) {
case Luminance8:
result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue;
result[1] = 1.0f;
break;
case Luminance8Alpha8:
result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue;
pixelValue = data.get();
result[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f;
break;
case Luminance16:
case Luminance16Alpha16:
case Luminance16F:
case Luminance16FAlpha16F:
case Luminance32F:
LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}", imageFormat);
break;
default:
throw new IllegalStateException("Invalid image format type for Luminance texture blender: " + imageFormat);
}
}
/** int width = image.getWidth();
* This method blends the texture with an appropriate color. int height = image.getHeight();
* int depth = image.getDepth();
* @param result if (depth == 0) {
* the result color (variable 'in' in blender source code) depth = 1;
* @param materialColor }
* the texture color (variable 'out' in blender source coude) ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(depth);
* @param color
* the previous color (variable 'tex' in blender source code)
* @param textureIntensity
* texture intensity (variable 'fact' in blender source code)
* @param textureFactor
* texture affection factor (variable 'facg' in blender source
* code)
* @param blendtype
* the blend type
* @param blenderContext
* the blender context
*/
protected void blendPixel(float[] result, float[] materialColor, float[] color, float textureIntensity, float textureFactor, int blendtype, BlenderContext blenderContext) {
float oneMinusFactor, col;
textureIntensity *= textureFactor;
switch (blendtype) { float[] resultPixel = new float[4];
case MTEX_BLEND: float[] tinAndAlpha = new float[2];
oneMinusFactor = 1.0f - textureIntensity; for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) {
result[0] = textureIntensity * color[0] + oneMinusFactor * materialColor[0]; ByteBuffer data = image.getData(dataLayerIndex);
result[1] = textureIntensity * color[1] + oneMinusFactor * materialColor[1]; data.rewind();
result[2] = textureIntensity * color[2] + oneMinusFactor * materialColor[2]; ByteBuffer newData = BufferUtils.createByteBuffer(width * height * 4);
break;
case MTEX_MUL: int dataIndex = 0, x = 0, y = 0;
oneMinusFactor = 1.0f - textureFactor; while (data.hasRemaining()) {
result[0] = (oneMinusFactor + textureIntensity * materialColor[0]) * color[0]; // getting the proper material color if the base texture is applied
result[1] = (oneMinusFactor + textureIntensity * materialColor[1]) * color[1]; if (basePixelIO != null) {
result[2] = (oneMinusFactor + textureIntensity * materialColor[2]) * color[2]; basePixelIO.read(baseImage, dataLayerIndex, basePixel, x, y);
break; basePixel.toRGBA(materialColor);
case MTEX_DIV: }
oneMinusFactor = 1.0f - textureIntensity;
if (color[0] != 0.0) { this.getTinAndAlpha(data, format, negateTexture, tinAndAlpha);
result[0] = (oneMinusFactor * materialColor[0] + textureIntensity * materialColor[0] / color[0]) * 0.5f; this.blendPixel(resultPixel, materialColor, color, tinAndAlpha[0], blendFactor, blendType, blenderContext);
} newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f));
if (color[1] != 0.0) { newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f));
result[1] = (oneMinusFactor * materialColor[1] + textureIntensity * materialColor[1] / color[1]) * 0.5f; newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f));
} newData.put(dataIndex++, (byte) (tinAndAlpha[1] * 255.0f));
if (color[2] != 0.0) {
result[2] = (oneMinusFactor * materialColor[2] + textureIntensity * materialColor[2] / color[2]) * 0.5f; ++x;
} if (x >= width) {
break; x = 0;
case MTEX_SCREEN: ++y;
oneMinusFactor = 1.0f - textureFactor; }
result[0] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]); }
result[1] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]); dataArray.add(newData);
result[2] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]); }
break;
case MTEX_OVERLAY: Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray) : new Image(Format.RGBA8, width, height, dataArray.get(0));
oneMinusFactor = 1.0f - textureFactor; if (image.getMipMapSizes() != null) {
if (materialColor[0] < 0.5f) { result.setMipMapSizes(image.getMipMapSizes().clone());
result[0] = color[0] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[0]); }
} else { return result;
result[0] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]); }
}
if (materialColor[1] < 0.5f) { /**
result[1] = color[1] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[1]); * This method return texture intensity and alpha value.
} else { *
result[1] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]); * @param data
} * the texture data
if (materialColor[2] < 0.5f) { * @param imageFormat
result[2] = color[2] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[2]); * the image format
} else { * @param neg
result[2] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]); * indicates if the texture is negated
} * @param result
break; * the table (2 elements) where the result is being stored
case MTEX_SUB: */
result[0] = materialColor[0] - textureIntensity * color[0]; protected void getTinAndAlpha(ByteBuffer data, Format imageFormat, boolean neg, float[] result) {
result[1] = materialColor[1] - textureIntensity * color[1]; byte pixelValue = data.get();// at least one byte is always taken
result[2] = materialColor[2] - textureIntensity * color[2]; float firstPixelValue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f;
result[0] = FastMath.clamp(result[0], 0.0f, 1.0f); switch (imageFormat) {
result[1] = FastMath.clamp(result[1], 0.0f, 1.0f); case Luminance8:
result[2] = FastMath.clamp(result[2], 0.0f, 1.0f); result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue;
break; result[1] = 1.0f;
case MTEX_ADD: break;
result[0] = (textureIntensity * color[0] + materialColor[0]) * 0.5f; case Luminance8Alpha8:
result[1] = (textureIntensity * color[1] + materialColor[1]) * 0.5f; result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue;
result[2] = (textureIntensity * color[2] + materialColor[2]) * 0.5f; pixelValue = data.get();
break; result[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f;
case MTEX_DIFF: break;
oneMinusFactor = 1.0f - textureIntensity; case Luminance16:
result[0] = oneMinusFactor * materialColor[0] + textureIntensity * Math.abs(materialColor[0] - color[0]); case Luminance16Alpha16:
result[1] = oneMinusFactor * materialColor[1] + textureIntensity * Math.abs(materialColor[1] - color[1]); case Luminance16F:
result[2] = oneMinusFactor * materialColor[2] + textureIntensity * Math.abs(materialColor[2] - color[2]); case Luminance16FAlpha16F:
break; case Luminance32F:
case MTEX_DARK: LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}", imageFormat);
col = textureIntensity * color[0]; break;
result[0] = col < materialColor[0] ? col : materialColor[0]; default:
col = textureIntensity * color[1]; throw new IllegalStateException("Invalid image format type for Luminance texture blender: " + imageFormat);
result[1] = col < materialColor[1] ? col : materialColor[1]; }
col = textureIntensity * color[2]; }
result[2] = col < materialColor[2] ? col : materialColor[2];
break; /**
case MTEX_LIGHT: * This method blends the texture with an appropriate color.
col = textureIntensity * color[0]; *
result[0] = col > materialColor[0] ? col : materialColor[0]; * @param result
col = textureIntensity * color[1]; * the result color (variable 'in' in blender source code)
result[1] = col > materialColor[1] ? col : materialColor[1]; * @param materialColor
col = textureIntensity * color[2]; * the texture color (variable 'out' in blender source coude)
result[2] = col > materialColor[2] ? col : materialColor[2]; * @param color
break; * the previous color (variable 'tex' in blender source code)
case MTEX_BLEND_HUE: * @param textureIntensity
case MTEX_BLEND_SAT: * texture intensity (variable 'fact' in blender source code)
case MTEX_BLEND_VAL: * @param textureFactor
case MTEX_BLEND_COLOR: * texture affection factor (variable 'facg' in blender source
System.arraycopy(materialColor, 0, result, 0, 3); * code)
this.blendHSV(blendtype, result, textureIntensity, color, blenderContext); * @param blendtype
break; * the blend type
default: * @param blenderContext
throw new IllegalStateException("Unknown blend type: " + blendtype); * the blender context
} */
} protected void blendPixel(float[] result, float[] materialColor, float[] color, float textureIntensity, float textureFactor, int blendtype, BlenderContext blenderContext) {
float oneMinusFactor, col;
textureIntensity *= textureFactor;
switch (blendtype) {
case MTEX_BLEND:
oneMinusFactor = 1.0f - textureIntensity;
result[0] = textureIntensity * color[0] + oneMinusFactor * materialColor[0];
result[1] = textureIntensity * color[1] + oneMinusFactor * materialColor[1];
result[2] = textureIntensity * color[2] + oneMinusFactor * materialColor[2];
break;
case MTEX_MUL:
oneMinusFactor = 1.0f - textureFactor;
result[0] = (oneMinusFactor + textureIntensity * materialColor[0]) * color[0];
result[1] = (oneMinusFactor + textureIntensity * materialColor[1]) * color[1];
result[2] = (oneMinusFactor + textureIntensity * materialColor[2]) * color[2];
break;
case MTEX_DIV:
oneMinusFactor = 1.0f - textureIntensity;
if (color[0] != 0.0) {
result[0] = (oneMinusFactor * materialColor[0] + textureIntensity * materialColor[0] / color[0]) * 0.5f;
}
if (color[1] != 0.0) {
result[1] = (oneMinusFactor * materialColor[1] + textureIntensity * materialColor[1] / color[1]) * 0.5f;
}
if (color[2] != 0.0) {
result[2] = (oneMinusFactor * materialColor[2] + textureIntensity * materialColor[2] / color[2]) * 0.5f;
}
break;
case MTEX_SCREEN:
oneMinusFactor = 1.0f - textureFactor;
result[0] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]);
result[1] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]);
result[2] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]);
break;
case MTEX_OVERLAY:
oneMinusFactor = 1.0f - textureFactor;
if (materialColor[0] < 0.5f) {
result[0] = color[0] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[0]);
} else {
result[0] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]);
}
if (materialColor[1] < 0.5f) {
result[1] = color[1] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[1]);
} else {
result[1] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]);
}
if (materialColor[2] < 0.5f) {
result[2] = color[2] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[2]);
} else {
result[2] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]);
}
break;
case MTEX_SUB:
result[0] = materialColor[0] - textureIntensity * color[0];
result[1] = materialColor[1] - textureIntensity * color[1];
result[2] = materialColor[2] - textureIntensity * color[2];
result[0] = FastMath.clamp(result[0], 0.0f, 1.0f);
result[1] = FastMath.clamp(result[1], 0.0f, 1.0f);
result[2] = FastMath.clamp(result[2], 0.0f, 1.0f);
break;
case MTEX_ADD:
result[0] = (textureIntensity * color[0] + materialColor[0]) * 0.5f;
result[1] = (textureIntensity * color[1] + materialColor[1]) * 0.5f;
result[2] = (textureIntensity * color[2] + materialColor[2]) * 0.5f;
break;
case MTEX_DIFF:
oneMinusFactor = 1.0f - textureIntensity;
result[0] = oneMinusFactor * materialColor[0] + textureIntensity * Math.abs(materialColor[0] - color[0]);
result[1] = oneMinusFactor * materialColor[1] + textureIntensity * Math.abs(materialColor[1] - color[1]);
result[2] = oneMinusFactor * materialColor[2] + textureIntensity * Math.abs(materialColor[2] - color[2]);
break;
case MTEX_DARK:
col = textureIntensity * color[0];
result[0] = col < materialColor[0] ? col : materialColor[0];
col = textureIntensity * color[1];
result[1] = col < materialColor[1] ? col : materialColor[1];
col = textureIntensity * color[2];
result[2] = col < materialColor[2] ? col : materialColor[2];
break;
case MTEX_LIGHT:
col = textureIntensity * color[0];
result[0] = col > materialColor[0] ? col : materialColor[0];
col = textureIntensity * color[1];
result[1] = col > materialColor[1] ? col : materialColor[1];
col = textureIntensity * color[2];
result[2] = col > materialColor[2] ? col : materialColor[2];
break;
case MTEX_BLEND_HUE:
case MTEX_BLEND_SAT:
case MTEX_BLEND_VAL:
case MTEX_BLEND_COLOR:
System.arraycopy(materialColor, 0, result, 0, 3);
this.blendHSV(blendtype, result, textureIntensity, color, blenderContext);
break;
default:
throw new IllegalStateException("Unknown blend type: " + blendtype);
}
}
} }

@ -51,37 +51,37 @@ import java.util.logging.Logger;
* It is only used by TextureHelper. * It is only used by TextureHelper.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/*package*/ class NoiseGenerator extends AbstractBlenderHelper { /* package */class NoiseGenerator extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(NoiseGenerator.class.getName()); private static final Logger LOGGER = Logger.getLogger(NoiseGenerator.class.getName());
// tex->stype // tex->stype
protected static final int TEX_PLASTIC = 0; protected static final int TEX_PLASTIC = 0;
protected static final int TEX_WALLIN = 1; protected static final int TEX_WALLIN = 1;
protected static final int TEX_WALLOUT = 2; protected static final int TEX_WALLOUT = 2;
// musgrave stype // musgrave stype
protected static final int TEX_MFRACTAL = 0; protected static final int TEX_MFRACTAL = 0;
protected static final int TEX_RIDGEDMF = 1; protected static final int TEX_RIDGEDMF = 1;
protected static final int TEX_HYBRIDMF = 2; protected static final int TEX_HYBRIDMF = 2;
protected static final int TEX_FBM = 3; protected static final int TEX_FBM = 3;
protected static final int TEX_HTERRAIN = 4; protected static final int TEX_HTERRAIN = 4;
// keyblock->type // keyblock->type
protected static final int KEY_LINEAR = 0; protected static final int KEY_LINEAR = 0;
protected static final int KEY_CARDINAL = 1; protected static final int KEY_CARDINAL = 1;
protected static final int KEY_BSPLINE = 2; protected static final int KEY_BSPLINE = 2;
// CONSTANTS (read from file) // CONSTANTS (read from file)
protected static float[] hashpntf; protected static float[] hashpntf;
protected static short[] hash; protected static short[] hash;
protected static float[] hashvectf; protected static float[] hashvectf;
protected static short[] p; protected static short[] p;
protected static float[][] g; protected static float[][] g;
/** /**
* Constructor. Stores the blender version number and loads the constants needed for computations. * Constructor. Stores the blender version number and loads the constants needed for computations.
* @param blenderVersion * @param blenderVersion
* the number of blender version * the number of blender version
*/ */
public NoiseGenerator(String blenderVersion) { public NoiseGenerator(String blenderVersion) {
super(blenderVersion, false); super(blenderVersion, false);
@ -116,11 +116,11 @@ import java.util.logging.Logger;
} }
} }
} }
protected static Map<Integer, NoiseFunction> noiseFunctions = new HashMap<Integer, NoiseFunction>(); protected static Map<Integer, NoiseFunction> noiseFunctions = new HashMap<Integer, NoiseFunction>();
static { static {
noiseFunctions.put(Integer.valueOf(0), new NoiseFunction() { noiseFunctions.put(Integer.valueOf(0), new NoiseFunction() {
// originalBlenderNoise // originalBlenderNoise
public float execute(float x, float y, float z) { public float execute(float x, float y, float z) {
return NoiseFunctions.originalBlenderNoise(x, y, z); return NoiseFunctions.originalBlenderNoise(x, y, z);
} }
@ -130,7 +130,7 @@ import java.util.logging.Logger;
} }
}); });
noiseFunctions.put(Integer.valueOf(1), new NoiseFunction() { noiseFunctions.put(Integer.valueOf(1), new NoiseFunction() {
// orgPerlinNoise // orgPerlinNoise
public float execute(float x, float y, float z) { public float execute(float x, float y, float z) {
return 0.5f + 0.5f * NoiseFunctions.noise3Perlin(x, y, z); return 0.5f + 0.5f * NoiseFunctions.noise3Perlin(x, y, z);
} }
@ -140,7 +140,7 @@ import java.util.logging.Logger;
} }
}); });
noiseFunctions.put(Integer.valueOf(2), new NoiseFunction() { noiseFunctions.put(Integer.valueOf(2), new NoiseFunction() {
// newPerlin // newPerlin
public float execute(float x, float y, float z) { public float execute(float x, float y, float z) {
return 0.5f + 0.5f * NoiseFunctions.newPerlin(x, y, z); return 0.5f + 0.5f * NoiseFunctions.newPerlin(x, y, z);
} }
@ -150,7 +150,7 @@ import java.util.logging.Logger;
} }
}); });
noiseFunctions.put(Integer.valueOf(3), new NoiseFunction() { noiseFunctions.put(Integer.valueOf(3), new NoiseFunction() {
// voronoi_F1 // voronoi_F1
public float execute(float x, float y, float z) { public float execute(float x, float y, float z) {
float[] da = new float[4], pa = new float[12]; float[] da = new float[4], pa = new float[12];
NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0); NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0);
@ -164,7 +164,7 @@ import java.util.logging.Logger;
} }
}); });
noiseFunctions.put(Integer.valueOf(4), new NoiseFunction() { noiseFunctions.put(Integer.valueOf(4), new NoiseFunction() {
// voronoi_F2 // voronoi_F2
public float execute(float x, float y, float z) { public float execute(float x, float y, float z) {
float[] da = new float[4], pa = new float[12]; float[] da = new float[4], pa = new float[12];
NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0); NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0);
@ -178,7 +178,7 @@ import java.util.logging.Logger;
} }
}); });
noiseFunctions.put(Integer.valueOf(5), new NoiseFunction() { noiseFunctions.put(Integer.valueOf(5), new NoiseFunction() {
// voronoi_F3 // voronoi_F3
public float execute(float x, float y, float z) { public float execute(float x, float y, float z) {
float[] da = new float[4], pa = new float[12]; float[] da = new float[4], pa = new float[12];
NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0); NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0);
@ -192,7 +192,7 @@ import java.util.logging.Logger;
} }
}); });
noiseFunctions.put(Integer.valueOf(6), new NoiseFunction() { noiseFunctions.put(Integer.valueOf(6), new NoiseFunction() {
// voronoi_F4 // voronoi_F4
public float execute(float x, float y, float z) { public float execute(float x, float y, float z) {
float[] da = new float[4], pa = new float[12]; float[] da = new float[4], pa = new float[12];
NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0); NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0);
@ -206,7 +206,7 @@ import java.util.logging.Logger;
} }
}); });
noiseFunctions.put(Integer.valueOf(7), new NoiseFunction() { noiseFunctions.put(Integer.valueOf(7), new NoiseFunction() {
// voronoi_F1F2 // voronoi_F1F2
public float execute(float x, float y, float z) { public float execute(float x, float y, float z) {
float[] da = new float[4], pa = new float[12]; float[] da = new float[4], pa = new float[12];
NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0); NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0);
@ -220,7 +220,7 @@ import java.util.logging.Logger;
} }
}); });
noiseFunctions.put(Integer.valueOf(8), new NoiseFunction() { noiseFunctions.put(Integer.valueOf(8), new NoiseFunction() {
// voronoi_Cr // voronoi_Cr
public float execute(float x, float y, float z) { public float execute(float x, float y, float z) {
float t = 10 * noiseFunctions.get(Integer.valueOf(7)).execute(x, y, z);// voronoi_F1F2 float t = 10 * noiseFunctions.get(Integer.valueOf(7)).execute(x, y, z);// voronoi_F1F2
return t > 1.0f ? 1.0f : t; return t > 1.0f ? 1.0f : t;
@ -232,7 +232,7 @@ import java.util.logging.Logger;
} }
}); });
noiseFunctions.put(Integer.valueOf(14), new NoiseFunction() { noiseFunctions.put(Integer.valueOf(14), new NoiseFunction() {
// cellNoise // cellNoise
public float execute(float x, float y, float z) { public float execute(float x, float y, float z) {
int xi = (int) Math.floor(x); int xi = (int) Math.floor(x);
int yi = (int) Math.floor(y); int yi = (int) Math.floor(y);
@ -252,25 +252,25 @@ import java.util.logging.Logger;
static { static {
distanceFunctions.put(Integer.valueOf(0), new DistanceFunction() { distanceFunctions.put(Integer.valueOf(0), new DistanceFunction() {
// real distance // real distance
public float execute(float x, float y, float z, float e) { public float execute(float x, float y, float z, float e) {
return (float) Math.sqrt(x * x + y * y + z * z); return (float) Math.sqrt(x * x + y * y + z * z);
} }
}); });
distanceFunctions.put(Integer.valueOf(1), new DistanceFunction() { distanceFunctions.put(Integer.valueOf(1), new DistanceFunction() {
// distance squared // distance squared
public float execute(float x, float y, float z, float e) { public float execute(float x, float y, float z, float e) {
return x * x + y * y + z * z; return x * x + y * y + z * z;
} }
}); });
distanceFunctions.put(Integer.valueOf(2), new DistanceFunction() { distanceFunctions.put(Integer.valueOf(2), new DistanceFunction() {
// manhattan/taxicab/cityblock distance // manhattan/taxicab/cityblock distance
public float execute(float x, float y, float z, float e) { public float execute(float x, float y, float z, float e) {
return FastMath.abs(x) + FastMath.abs(y) + FastMath.abs(z); return FastMath.abs(x) + FastMath.abs(y) + FastMath.abs(z);
} }
}); });
distanceFunctions.put(Integer.valueOf(3), new DistanceFunction() { distanceFunctions.put(Integer.valueOf(3), new DistanceFunction() {
// Chebychev // Chebychev
public float execute(float x, float y, float z, float e) { public float execute(float x, float y, float z, float e) {
x = FastMath.abs(x); x = FastMath.abs(x);
y = FastMath.abs(y); y = FastMath.abs(y);
@ -280,14 +280,14 @@ import java.util.logging.Logger;
} }
}); });
distanceFunctions.put(Integer.valueOf(4), new DistanceFunction() { distanceFunctions.put(Integer.valueOf(4), new DistanceFunction() {
// Minkovsky, preset exponent 0.5 (MinkovskyH) // Minkovsky, preset exponent 0.5 (MinkovskyH)
public float execute(float x, float y, float z, float e) { public float execute(float x, float y, float z, float e) {
float d = (float) (Math.sqrt(FastMath.abs(x)) + Math.sqrt(FastMath.abs(y)) + Math.sqrt(FastMath.abs(z))); float d = (float) (Math.sqrt(FastMath.abs(x)) + Math.sqrt(FastMath.abs(y)) + Math.sqrt(FastMath.abs(z)));
return d * d; return d * d;
} }
}); });
distanceFunctions.put(Integer.valueOf(5), new DistanceFunction() { distanceFunctions.put(Integer.valueOf(5), new DistanceFunction() {
// Minkovsky, preset exponent 0.25 (Minkovsky4) // Minkovsky, preset exponent 0.25 (Minkovsky4)
public float execute(float x, float y, float z, float e) { public float execute(float x, float y, float z, float e) {
x *= x; x *= x;
y *= y; y *= y;
@ -296,13 +296,13 @@ import java.util.logging.Logger;
} }
}); });
distanceFunctions.put(Integer.valueOf(6), new DistanceFunction() { distanceFunctions.put(Integer.valueOf(6), new DistanceFunction() {
// Minkovsky, general case // Minkovsky, general case
public float execute(float x, float y, float z, float e) { public float execute(float x, float y, float z, float e) {
return (float) Math.pow(Math.pow(FastMath.abs(x), e) + Math.pow(FastMath.abs(y), e) + Math.pow(FastMath.abs(z), e), 1.0f / e); return (float) Math.pow(Math.pow(FastMath.abs(x), e) + Math.pow(FastMath.abs(y), e) + Math.pow(FastMath.abs(z), e), 1.0f / e);
} }
}); });
} }
protected static Map<Integer, MusgraveFunction> musgraveFunctions = new HashMap<Integer, NoiseGenerator.MusgraveFunction>(); protected static Map<Integer, MusgraveFunction> musgraveFunctions = new HashMap<Integer, NoiseGenerator.MusgraveFunction>();
static { static {
musgraveFunctions.put(Integer.valueOf(TEX_MFRACTAL), new MusgraveFunction() { musgraveFunctions.put(Integer.valueOf(TEX_MFRACTAL), new MusgraveFunction() {
@ -460,64 +460,64 @@ import java.util.logging.Logger;
} }
}); });
} }
public static class NoiseFunctions { public static class NoiseFunctions {
public static float noise(float x, float y, float z, float noiseSize, int noiseDepth, int noiseBasis, boolean isHard) { public static float noise(float x, float y, float z, float noiseSize, int noiseDepth, int noiseBasis, boolean isHard) {
NoiseFunction abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noiseBasis)); NoiseFunction abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noiseBasis));
if (abstractNoiseFunc == null) { if (abstractNoiseFunc == null) {
abstractNoiseFunc = noiseFunctions.get(0); abstractNoiseFunc = noiseFunctions.get(0);
noiseBasis = 0; noiseBasis = 0;
} }
if (noiseBasis == 0) { if (noiseBasis == 0) {
++x; ++x;
++y; ++y;
++z; ++z;
} }
if (noiseSize != 0.0) { if (noiseSize != 0.0) {
noiseSize = 1.0f / noiseSize; noiseSize = 1.0f / noiseSize;
x *= noiseSize; x *= noiseSize;
y *= noiseSize; y *= noiseSize;
z *= noiseSize; z *= noiseSize;
} }
float result = abstractNoiseFunc.execute(x, y, z); float result = abstractNoiseFunc.execute(x, y, z);
return isHard ? Math.abs(2.0f * result - 1.0f) : result; return isHard ? Math.abs(2.0f * result - 1.0f) : result;
} }
public static float turbulence(float x, float y, float z, float noiseSize, int noiseDepth, int noiseBasis, boolean isHard) { public static float turbulence(float x, float y, float z, float noiseSize, int noiseDepth, int noiseBasis, boolean isHard) {
NoiseFunction abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noiseBasis)); NoiseFunction abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noiseBasis));
if (abstractNoiseFunc == null) { if (abstractNoiseFunc == null) {
abstractNoiseFunc = noiseFunctions.get(0); abstractNoiseFunc = noiseFunctions.get(0);
noiseBasis = 0; noiseBasis = 0;
} }
if (noiseBasis == 0) { if (noiseBasis == 0) {
++x; ++x;
++y; ++y;
++z; ++z;
} }
if (noiseSize != 0.0) { if (noiseSize != 0.0) {
noiseSize = 1.0f / noiseSize; noiseSize = 1.0f / noiseSize;
x *= noiseSize; x *= noiseSize;
y *= noiseSize; y *= noiseSize;
z *= noiseSize; z *= noiseSize;
} }
float sum = 0, t, amp = 1, fscale = 1; float sum = 0, t, amp = 1, fscale = 1;
for (int i = 0; i <= noiseDepth; ++i, amp *= 0.5, fscale *= 2) { for (int i = 0; i <= noiseDepth; ++i, amp *= 0.5, fscale *= 2) {
t = abstractNoiseFunc.execute(fscale * x, fscale * y, fscale * z); t = abstractNoiseFunc.execute(fscale * x, fscale * y, fscale * z);
if (isHard) { if (isHard) {
t = FastMath.abs(2.0f * t - 1.0f); t = FastMath.abs(2.0f * t - 1.0f);
} }
sum += t * amp; sum += t * amp;
} }
sum *= (float) (1 << noiseDepth) / (float) ((1 << noiseDepth + 1) - 1); sum *= (float) (1 << noiseDepth) / (float) ((1 << noiseDepth + 1) - 1);
return sum; return sum;
} }
/** /**
* Not 'pure' Worley, but the results are virtually the same. Returns distances in da and point coords in pa * Not 'pure' Worley, but the results are virtually the same. Returns distances in da and point coords in pa
*/ */
public static void voronoi(float x, float y, float z, float[] da, float[] pa, float distanceExponent, int distanceType) { public static void voronoi(float x, float y, float z, float[] da, float[] pa, float distanceExponent, int distanceType) {
@ -531,7 +531,7 @@ import java.util.logging.Logger;
int xi = (int) FastMath.floor(x); int xi = (int) FastMath.floor(x);
int yi = (int) FastMath.floor(y); int yi = (int) FastMath.floor(y);
int zi = (int) FastMath.floor(z); int zi = (int) FastMath.floor(z);
da[0] = da[1] = da[2] = da[3] = Float.MAX_VALUE;//1e10f; da[0] = da[1] = da[2] = da[3] = Float.MAX_VALUE;// 1e10f;
for (int i = xi - 1; i <= xi + 1; ++i) { for (int i = xi - 1; i <= xi + 1; ++i) {
for (int j = yi - 1; j <= yi + 1; ++j) { for (int j = yi - 1; j <= yi + 1; ++j) {
for (int k = zi - 1; k <= zi + 1; ++k) { for (int k = zi - 1; k <= zi + 1; ++k) {
@ -589,7 +589,7 @@ import java.util.logging.Logger;
} }
} }
} }
// instead of adding another permutation array, just use hash table defined above // instead of adding another permutation array, just use hash table defined above
public static float newPerlin(float x, float y, float z) { public static float newPerlin(float x, float y, float z) {
int A, AA, AB, B, BA, BB; int A, AA, AB, B, BA, BB;
@ -598,7 +598,7 @@ import java.util.logging.Logger;
x -= floorX; x -= floorX;
y -= floorY; y -= floorY;
z -= floorZ; z -= floorZ;
//computing fading curves // computing fading curves
floorX = NoiseMath.npfade(x); floorX = NoiseMath.npfade(x);
floorY = NoiseMath.npfade(y); floorY = NoiseMath.npfade(y);
floorZ = NoiseMath.npfade(z); floorZ = NoiseMath.npfade(z);
@ -608,14 +608,8 @@ import java.util.logging.Logger;
B = hash[intX + 1] + intY; B = hash[intX + 1] + intY;
BA = hash[B] + intZ; BA = hash[B] + intZ;
BB = hash[B + 1] + intZ; BB = hash[B + 1] + intZ;
return NoiseMath.lerp(floorZ, NoiseMath.lerp(floorY, NoiseMath.lerp(floorX, NoiseMath.grad(hash[AA], x, y, z), return NoiseMath.lerp(floorZ, NoiseMath.lerp(floorY, NoiseMath.lerp(floorX, NoiseMath.grad(hash[AA], x, y, z), NoiseMath.grad(hash[BA], x - 1, y, z)), NoiseMath.lerp(floorX, NoiseMath.grad(hash[AB], x, y - 1, z), NoiseMath.grad(hash[BB], x - 1, y - 1, z))),
NoiseMath.grad(hash[BA], x - 1, y, z)), NoiseMath.lerp(floorY, NoiseMath.lerp(floorX, NoiseMath.grad(hash[AA + 1], x, y, z - 1), NoiseMath.grad(hash[BA + 1], x - 1, y, z - 1)), NoiseMath.lerp(floorX, NoiseMath.grad(hash[AB + 1], x, y - 1, z - 1), NoiseMath.grad(hash[BB + 1], x - 1, y - 1, z - 1))));
NoiseMath.lerp(floorX, NoiseMath.grad(hash[AB], x, y - 1, z),
NoiseMath.grad(hash[BB], x - 1, y - 1, z))),
NoiseMath.lerp(floorY, NoiseMath.lerp(floorX, NoiseMath.grad(hash[AA + 1], x, y, z - 1),
NoiseMath.grad(hash[BA + 1], x - 1, y, z - 1)),
NoiseMath.lerp(floorX, NoiseMath.grad(hash[AB + 1], x, y - 1, z - 1),
NoiseMath.grad(hash[BB + 1], x - 1, y - 1, z - 1))));
} }
public static float noise3Perlin(float x, float y, float z) { public static float noise3Perlin(float x, float y, float z) {
@ -624,13 +618,13 @@ import java.util.logging.Logger;
int bx1 = bx0 + 1 & 0xFF; int bx1 = bx0 + 1 & 0xFF;
float rx0 = t - (int) t; float rx0 = t - (int) t;
float rx1 = rx0 - 1.0f; float rx1 = rx0 - 1.0f;
t = y + 10000.0f; t = y + 10000.0f;
int by0 = (int) t & 0xFF; int by0 = (int) t & 0xFF;
int by1 = by0 + 1 & 0xFF; int by1 = by0 + 1 & 0xFF;
float ry0 = t - (int) t; float ry0 = t - (int) t;
float ry1 = ry0 - 1.0f; float ry1 = ry0 - 1.0f;
t = z + 10000.0f; t = z + 10000.0f;
int bz0 = (int) t & 0xFF; int bz0 = (int) t & 0xFF;
int bz1 = bz0 + 1 & 0xFF; int bz1 = bz0 + 1 & 0xFF;
@ -685,7 +679,7 @@ import java.util.logging.Logger;
int ix = (int) Math.floor(x); int ix = (int) Math.floor(x);
int iy = (int) Math.floor(y); int iy = (int) Math.floor(y);
int iz = (int) Math.floor(z); int iz = (int) Math.floor(z);
float ox = x - ix; float ox = x - ix;
float oy = y - iy; float oy = y - iy;
float oz = z - iz; float oz = z - iz;
@ -707,24 +701,23 @@ import java.util.logging.Logger;
cn4 = 1.0f - 3.0f * cn4 - 2.0f * cn4 * jx; cn4 = 1.0f - 3.0f * cn4 - 2.0f * cn4 * jx;
cn5 = 1.0f - 3.0f * cn5 - 2.0f * cn5 * jy; cn5 = 1.0f - 3.0f * cn5 - 2.0f * cn5 * jy;
cn6 = 1.0f - 3.0f * cn6 - 2.0f * cn6 * jz; cn6 = 1.0f - 3.0f * cn6 - 2.0f * cn6 * jz;
float[] cn = new float[] {cn1 * cn2 * cn3, cn1 * cn2 * cn6, cn1 * cn5 * cn3, cn1 * cn5 * cn6, float[] cn = new float[] { cn1 * cn2 * cn3, cn1 * cn2 * cn6, cn1 * cn5 * cn3, cn1 * cn5 * cn6, cn4 * cn2 * cn3, cn4 * cn2 * cn6, cn4 * cn5 * cn3, cn4 * cn5 * cn6, };
cn4 * cn2 * cn3, cn4 * cn2 * cn6, cn4 * cn5 * cn3, cn4 * cn5 * cn6,};
int b00 = hash[hash[ix & 0xFF] + (iy & 0xFF)]; int b00 = hash[hash[ix & 0xFF] + (iy & 0xFF)];
int b01 = hash[hash[ix & 0xFF] + (iy + 1 & 0xFF)]; int b01 = hash[hash[ix & 0xFF] + (iy + 1 & 0xFF)];
int b10 = hash[hash[ix + 1 & 0xFF] + (iy & 0xFF)]; int b10 = hash[hash[ix + 1 & 0xFF] + (iy & 0xFF)];
int b11 = hash[hash[ix + 1 & 0xFF] + (iy + 1 & 0xFF)]; int b11 = hash[hash[ix + 1 & 0xFF] + (iy + 1 & 0xFF)];
int[] b1 = new int[] {b00, b00, b01, b01, b10, b10, b11, b11}; int[] b1 = new int[] { b00, b00, b01, b01, b10, b10, b11, b11 };
int[] b2 = new int[] {iz & 0xFF, iz + 1 & 0xFF}; int[] b2 = new int[] { iz & 0xFF, iz + 1 & 0xFF };
float[] xFactor = new float[] {ox, ox, ox, ox, jx, jx, jx, jx}; float[] xFactor = new float[] { ox, ox, ox, ox, jx, jx, jx, jx };
float[] yFactor = new float[] {oy, oy, jy, jy, oy, oy, jy, jy}; float[] yFactor = new float[] { oy, oy, jy, jy, oy, oy, jy, jy };
float[] zFactor = new float[] {oz, jz, oz, jz, oz, jz, oz, jz}; float[] zFactor = new float[] { oz, jz, oz, jz, oz, jz, oz, jz };
for(int i=0;i<8;++i) { for (int i = 0; i < 8; ++i) {
int hIndex = 3 * hash[b1[i] + b2[i%2]]; int hIndex = 3 * hash[b1[i] + b2[i % 2]];
n += cn[i] * (hashvectf[hIndex] * xFactor[i] + hashvectf[hIndex + 1] * yFactor[i] + hashvectf[hIndex + 2] * zFactor[i]); n += cn[i] * (hashvectf[hIndex] * xFactor[i] + hashvectf[hIndex + 1] * yFactor[i] + hashvectf[hIndex + 2] * zFactor[i]);
} }
if (n < 0.0f) { if (n < 0.0f) {
@ -746,11 +739,11 @@ import java.util.logging.Logger;
/** /**
* This method calculates the unsigned value of the noise. * This method calculates the unsigned value of the noise.
* @param x * @param x
* the x texture coordinate * the x texture coordinate
* @param y * @param y
* the y texture coordinate * the y texture coordinate
* @param z * @param z
* the z texture coordinate * the z texture coordinate
* @return value of the noise * @return value of the noise
*/ */
float execute(float x, float y, float z); float execute(float x, float y, float z);
@ -758,49 +751,49 @@ import java.util.logging.Logger;
/** /**
* This method calculates the signed value of the noise. * This method calculates the signed value of the noise.
* @param x * @param x
* the x texture coordinate * the x texture coordinate
* @param y * @param y
* the y texture coordinate * the y texture coordinate
* @param z * @param z
* the z texture coordinate * the z texture coordinate
* @return value of the noise * @return value of the noise
*/ */
float executeSigned(float x, float y, float z); float executeSigned(float x, float y, float z);
} }
public static class NoiseMath { public static class NoiseMath {
public static float lerp(float t, float a, float b) { public static float lerp(float t, float a, float b) {
return a + t * (b - a); return a + t * (b - a);
} }
public static float npfade(float t) { public static float npfade(float t) {
return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f);
} }
public static float grad(int hash, float x, float y, float z) { public static float grad(int hash, float x, float y, float z) {
int h = hash & 0x0F; int h = hash & 0x0F;
float u = h < 8 ? x : y, v = h < 4 ? y : h == 12 || h == 14 ? x : z; float u = h < 8 ? x : y, v = h < 4 ? y : h == 12 || h == 14 ? x : z;
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
} }
public static float surve(float t) { public static float surve(float t) {
return t * t * (3.0f - 2.0f * t); return t * t * (3.0f - 2.0f * t);
} }
public static float at(float x, float y, float z, float[] q) { public static float at(float x, float y, float z, float[] q) {
return x * q[0] + y * q[1] + z * q[2]; return x * q[0] + y * q[1] + z * q[2];
} }
public static void hash(int x, int y, int z, float[] result) { public static void hash(int x, int y, int z, float[] result) {
result[0] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF]]; result[0] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF]];
result[1] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF] + 1]; result[1] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF] + 1];
result[2] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF] + 2]; result[2] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF] + 2];
} }
} }
@Override @Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return true; return true;
} }
/** /**
@ -812,13 +805,13 @@ import java.util.logging.Logger;
/** /**
* This method calculates the distance for voronoi algorithms. * This method calculates the distance for voronoi algorithms.
* @param x * @param x
* the x coordinate * the x coordinate
* @param y * @param y
* the y coordinate * the y coordinate
* @param z * @param z
* the z coordinate * the z coordinate
* @param e * @param e
* this parameter used in Monkovsky (no idea what it really is ;) * this parameter used in Monkovsky (no idea what it really is ;)
* @return * @return
*/ */
float execute(float x, float y, float z, float e); float execute(float x, float y, float z, float e);

@ -42,43 +42,44 @@ import com.jme3.texture.Image.Format;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public abstract class TextureGenerator { public abstract class TextureGenerator {
protected NoiseGenerator noiseGenerator; protected NoiseGenerator noiseGenerator;
protected int flag; protected int flag;
protected float[][] colorBand; protected float[][] colorBand;
protected BrightnessAndContrastData bacd; protected BrightnessAndContrastData bacd;
protected Format imageFormat; protected Format imageFormat;
public TextureGenerator(NoiseGenerator noiseGenerator, Format imageFormat) {
this.noiseGenerator = noiseGenerator;
this.imageFormat = imageFormat;
}
public Format getImageFormat() { public TextureGenerator(NoiseGenerator noiseGenerator, Format imageFormat) {
return imageFormat; this.noiseGenerator = noiseGenerator;
} this.imageFormat = imageFormat;
}
public void readData(Structure tex, BlenderContext blenderContext) {
flag = ((Number) tex.getFieldValue("flag")).intValue(); public Format getImageFormat() {
colorBand = new ColorBand(tex, blenderContext).computeValues(); return imageFormat;
bacd = new BrightnessAndContrastData(tex); }
if(colorBand != null) {
imageFormat = Format.RGBA8; public void readData(Structure tex, BlenderContext blenderContext) {
} flag = ((Number) tex.getFieldValue("flag")).intValue();
} colorBand = new ColorBand(tex, blenderContext).computeValues();
bacd = new BrightnessAndContrastData(tex);
public abstract void getPixel(TexturePixel pixel, float x, float y, float z); if (colorBand != null) {
imageFormat = Format.RGBA8;
/** }
* This method applies brightness and contrast for RGB textures. }
* @param tex texture structure
* @param texres public abstract void getPixel(TexturePixel pixel, float x, float y, float z);
*/
protected void applyBrightnessAndContrast(BrightnessAndContrastData bacd, TexturePixel texres) { /**
* This method applies brightness and contrast for RGB textures.
* @param tex
* texture structure
* @param texres
*/
protected void applyBrightnessAndContrast(BrightnessAndContrastData bacd, TexturePixel texres) {
texres.red = (texres.red - 0.5f) * bacd.contrast + bacd.brightness; texres.red = (texres.red - 0.5f) * bacd.contrast + bacd.brightness;
if (texres.red < 0.0f) { if (texres.red < 0.0f) {
texres.red = 0.0f; texres.red = 0.0f;
} }
texres.green =(texres.green - 0.5f) * bacd.contrast + bacd.brightness; texres.green = (texres.green - 0.5f) * bacd.contrast + bacd.brightness;
if (texres.green < 0.0f) { if (texres.green < 0.0f) {
texres.green = 0.0f; texres.green = 0.0f;
} }
@ -87,14 +88,14 @@ public abstract class TextureGenerator {
texres.blue = 0.0f; texres.blue = 0.0f;
} }
} }
/** /**
* This method applies brightness and contrast for Luminance textures. * This method applies brightness and contrast for Luminance textures.
* @param texres * @param texres
* @param contrast * @param contrast
* @param brightness * @param brightness
*/ */
protected void applyBrightnessAndContrast(TexturePixel texres, float contrast, float brightness) { protected void applyBrightnessAndContrast(TexturePixel texres, float contrast, float brightness) {
texres.intensity = (texres.intensity - 0.5f) * contrast + brightness; texres.intensity = (texres.intensity - 0.5f) * contrast + brightness;
if (texres.intensity < 0.0f) { if (texres.intensity < 0.0f) {
texres.intensity = 0.0f; texres.intensity = 0.0f;
@ -102,28 +103,29 @@ public abstract class TextureGenerator {
texres.intensity = 1.0f; texres.intensity = 1.0f;
} }
} }
/** /**
* This class contains brightness and contrast data. * This class contains brightness and contrast data.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
protected static class BrightnessAndContrastData { protected static class BrightnessAndContrastData {
public final float contrast; public final float contrast;
public final float brightness; public final float brightness;
public final float rFactor; public final float rFactor;
public final float gFactor; public final float gFactor;
public final float bFactor; public final float bFactor;
/** /**
* Constructor reads the required data from the given structure. * Constructor reads the required data from the given structure.
* @param tex texture structure * @param tex
* texture structure
*/ */
public BrightnessAndContrastData(Structure tex) { public BrightnessAndContrastData(Structure tex) {
contrast = ((Number) tex.getFieldValue("contrast")).floatValue(); contrast = ((Number) tex.getFieldValue("contrast")).floatValue();
brightness = ((Number) tex.getFieldValue("bright")).floatValue() - 0.5f; brightness = ((Number) tex.getFieldValue("bright")).floatValue() - 0.5f;
rFactor = ((Number) tex.getFieldValue("rfac")).floatValue(); rFactor = ((Number) tex.getFieldValue("rfac")).floatValue();
gFactor = ((Number) tex.getFieldValue("gfac")).floatValue(); gFactor = ((Number) tex.getFieldValue("gfac")).floatValue();
bFactor = ((Number) tex.getFieldValue("bfac")).floatValue(); bFactor = ((Number) tex.getFieldValue("bfac")).floatValue();
} }
} }
} }

@ -42,90 +42,90 @@ import com.jme3.texture.Image.Format;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public final class TextureGeneratorBlend extends TextureGenerator { public final class TextureGeneratorBlend extends TextureGenerator {
private static final IntensityFunction INTENSITY_FUNCTION[] = new IntensityFunction[7]; private static final IntensityFunction INTENSITY_FUNCTION[] = new IntensityFunction[7];
static { static {
INTENSITY_FUNCTION[0] = new IntensityFunction() {//Linear: stype = 0 (TEX_LIN) INTENSITY_FUNCTION[0] = new IntensityFunction() {// Linear: stype = 0 (TEX_LIN)
public float getIntensity(float x, float y, float z) { public float getIntensity(float x, float y, float z) {
return (1.0f + x) * 0.5f; return (1.0f + x) * 0.5f;
} }
}; };
INTENSITY_FUNCTION[1] = new IntensityFunction() {//Quad: stype = 1 (TEX_QUAD) INTENSITY_FUNCTION[1] = new IntensityFunction() {// Quad: stype = 1 (TEX_QUAD)
public float getIntensity(float x, float y, float z) { public float getIntensity(float x, float y, float z) {
float result = (1.0f + x) * 0.5f; float result = (1.0f + x) * 0.5f;
return result * result; return result * result;
} }
}; };
INTENSITY_FUNCTION[2] = new IntensityFunction() {//Ease: stype = 2 (TEX_EASE) INTENSITY_FUNCTION[2] = new IntensityFunction() {// Ease: stype = 2 (TEX_EASE)
public float getIntensity(float x, float y, float z) { public float getIntensity(float x, float y, float z) {
float result = (1.0f + x) * 0.5f; float result = (1.0f + x) * 0.5f;
if (result <= 0.0f) { if (result <= 0.0f) {
return 0.0f; return 0.0f;
} else if (result >= 1.0f) { } else if (result >= 1.0f) {
return 1.0f; return 1.0f;
} else { } else {
return result * result *(3.0f - 2.0f * result); return result * result * (3.0f - 2.0f * result);
} }
} }
}; };
INTENSITY_FUNCTION[3] = new IntensityFunction() {//Diagonal: stype = 3 (TEX_DIAG) INTENSITY_FUNCTION[3] = new IntensityFunction() {// Diagonal: stype = 3 (TEX_DIAG)
public float getIntensity(float x, float y, float z) { public float getIntensity(float x, float y, float z) {
return (2.0f + x + y) * 0.25f; return (2.0f + x + y) * 0.25f;
} }
}; };
INTENSITY_FUNCTION[4] = new IntensityFunction() {//Sphere: stype = 4 (TEX_SPHERE) INTENSITY_FUNCTION[4] = new IntensityFunction() {// Sphere: stype = 4 (TEX_SPHERE)
public float getIntensity(float x, float y, float z) { public float getIntensity(float x, float y, float z) {
float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z); float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z);
return result < 0.0f ? 0.0f : result; return result < 0.0f ? 0.0f : result;
} }
}; };
INTENSITY_FUNCTION[5] = new IntensityFunction() {//Halo: stype = 5 (TEX_HALO) INTENSITY_FUNCTION[5] = new IntensityFunction() {// Halo: stype = 5 (TEX_HALO)
public float getIntensity(float x, float y, float z) { public float getIntensity(float x, float y, float z) {
float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z); float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z);
return result <= 0.0f ? 0.0f : result * result; return result <= 0.0f ? 0.0f : result * result;
} }
}; };
INTENSITY_FUNCTION[6] = new IntensityFunction() {//Radial: stype = 6 (TEX_RAD) INTENSITY_FUNCTION[6] = new IntensityFunction() {// Radial: stype = 6 (TEX_RAD)
public float getIntensity(float x, float y, float z) { public float getIntensity(float x, float y, float z) {
return (float) Math.atan2(y, x) * FastMath.INV_TWO_PI + 0.5f; return (float) Math.atan2(y, x) * FastMath.INV_TWO_PI + 0.5f;
} }
}; };
}
/**
* Constructor stores the given noise generator.
* @param noiseGenerator
* the noise generator
*/
public TextureGeneratorBlend(NoiseGenerator noiseGenerator) {
super(noiseGenerator, Format.Luminance8);
}
protected int stype;
@Override
public void readData(Structure tex, BlenderContext blenderContext) {
super.readData(tex, blenderContext);
stype = ((Number) tex.getFieldValue("stype")).intValue();
} }
/**
* Constructor stores the given noise generator.
* @param noiseGenerator
* the noise generator
*/
public TextureGeneratorBlend(NoiseGenerator noiseGenerator) {
super(noiseGenerator, Format.Luminance8);
}
protected int stype;
@Override
public void readData(Structure tex, BlenderContext blenderContext) {
super.readData(tex, blenderContext);
stype = ((Number) tex.getFieldValue("stype")).intValue();
}
@Override
public void getPixel(TexturePixel pixel, float x, float y, float z) {
pixel.intensity = INTENSITY_FUNCTION[stype].getIntensity(x, y, z);
if (colorBand != null) {
int colorbandIndex = (int) (pixel.intensity * 1000.0f);
pixel.red = colorBand[colorbandIndex][0];
pixel.green = colorBand[colorbandIndex][1];
pixel.blue = colorBand[colorbandIndex][2];
this.applyBrightnessAndContrast(bacd, pixel);
} else {
this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness);
}
}
private static interface IntensityFunction { @Override
float getIntensity(float x, float y, float z); public void getPixel(TexturePixel pixel, float x, float y, float z) {
} pixel.intensity = INTENSITY_FUNCTION[stype].getIntensity(x, y, z);
if (colorBand != null) {
int colorbandIndex = (int) (pixel.intensity * 1000.0f);
pixel.red = colorBand[colorbandIndex][0];
pixel.green = colorBand[colorbandIndex][1];
pixel.blue = colorBand[colorbandIndex][2];
this.applyBrightnessAndContrast(bacd, pixel);
} else {
this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness);
}
}
private static interface IntensityFunction {
float getIntensity(float x, float y, float z);
}
} }

@ -42,68 +42,68 @@ import com.jme3.texture.Image.Format;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class TextureGeneratorClouds extends TextureGenerator { public class TextureGeneratorClouds extends TextureGenerator {
// noiseType // noiseType
protected static final int TEX_NOISESOFT = 0; protected static final int TEX_NOISESOFT = 0;
protected static final int TEX_NOISEPERL = 1; protected static final int TEX_NOISEPERL = 1;
// sType // sType
protected static final int TEX_DEFAULT = 0; protected static final int TEX_DEFAULT = 0;
protected static final int TEX_COLOR = 1; protected static final int TEX_COLOR = 1;
protected float noisesize; protected float noisesize;
protected int noiseDepth; protected int noiseDepth;
protected int noiseBasis; protected int noiseBasis;
protected int noiseType; protected int noiseType;
protected boolean isHard; protected boolean isHard;
protected int sType; protected int sType;
/** /**
* Constructor stores the given noise generator. * Constructor stores the given noise generator.
* @param noiseGenerator * @param noiseGenerator
* the noise generator * the noise generator
*/ */
public TextureGeneratorClouds(NoiseGenerator noiseGenerator) { public TextureGeneratorClouds(NoiseGenerator noiseGenerator) {
super(noiseGenerator, Format.Luminance8); super(noiseGenerator, Format.Luminance8);
} }
@Override @Override
public void readData(Structure tex, BlenderContext blenderContext) { public void readData(Structure tex, BlenderContext blenderContext) {
super.readData(tex, blenderContext); super.readData(tex, blenderContext);
noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
noiseDepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); noiseDepth = ((Number) tex.getFieldValue("noisedepth")).intValue();
noiseBasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); noiseBasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
noiseType = ((Number) tex.getFieldValue("noisetype")).intValue(); noiseType = ((Number) tex.getFieldValue("noisetype")).intValue();
isHard = noiseType != TEX_NOISESOFT; isHard = noiseType != TEX_NOISESOFT;
sType = ((Number) tex.getFieldValue("stype")).intValue(); sType = ((Number) tex.getFieldValue("stype")).intValue();
if(sType == TEX_COLOR) { if (sType == TEX_COLOR) {
this.imageFormat = Format.RGBA8; this.imageFormat = Format.RGBA8;
} }
} }
@Override @Override
public void getPixel(TexturePixel pixel, float x, float y, float z) { public void getPixel(TexturePixel pixel, float x, float y, float z) {
pixel.intensity = NoiseGenerator.NoiseFunctions.turbulence(x, y, z, noisesize, noiseDepth, noiseBasis, isHard); pixel.intensity = NoiseGenerator.NoiseFunctions.turbulence(x, y, z, noisesize, noiseDepth, noiseBasis, isHard);
pixel.intensity = FastMath.clamp(pixel.intensity, 0.0f, 1.0f); pixel.intensity = FastMath.clamp(pixel.intensity, 0.0f, 1.0f);
if (colorBand != null) { if (colorBand != null) {
int colorbandIndex = (int) (pixel.intensity * 1000.0f); int colorbandIndex = (int) (pixel.intensity * 1000.0f);
pixel.red = colorBand[colorbandIndex][0]; pixel.red = colorBand[colorbandIndex][0];
pixel.green = colorBand[colorbandIndex][1]; pixel.green = colorBand[colorbandIndex][1];
pixel.blue = colorBand[colorbandIndex][2]; pixel.blue = colorBand[colorbandIndex][2];
pixel.alpha = colorBand[colorbandIndex][3]; pixel.alpha = colorBand[colorbandIndex][3];
this.applyBrightnessAndContrast(bacd, pixel); this.applyBrightnessAndContrast(bacd, pixel);
} else if (sType == TEX_COLOR) { } else if (sType == TEX_COLOR) {
pixel.red = pixel.intensity; pixel.red = pixel.intensity;
pixel.green = NoiseGenerator.NoiseFunctions.turbulence(y, x, z, noisesize, noiseDepth, noiseBasis, isHard); pixel.green = NoiseGenerator.NoiseFunctions.turbulence(y, x, z, noisesize, noiseDepth, noiseBasis, isHard);
pixel.blue = NoiseGenerator.NoiseFunctions.turbulence(y, z, x, noisesize, noiseDepth, noiseBasis, isHard); pixel.blue = NoiseGenerator.NoiseFunctions.turbulence(y, z, x, noisesize, noiseDepth, noiseBasis, isHard);
pixel.green = FastMath.clamp(pixel.green, 0.0f, 1.0f); pixel.green = FastMath.clamp(pixel.green, 0.0f, 1.0f);
pixel.blue = FastMath.clamp(pixel.blue, 0.0f, 1.0f); pixel.blue = FastMath.clamp(pixel.blue, 0.0f, 1.0f);
pixel.alpha = 1.0f; pixel.alpha = 1.0f;
this.applyBrightnessAndContrast(bacd, pixel); this.applyBrightnessAndContrast(bacd, pixel);
} else { } else {
this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness);
} }
} }
} }

@ -43,46 +43,46 @@ import com.jme3.texture.Image.Format;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class TextureGeneratorDistnoise extends TextureGenerator { public class TextureGeneratorDistnoise extends TextureGenerator {
protected float noisesize; protected float noisesize;
protected float distAmount; protected float distAmount;
protected int noisebasis; protected int noisebasis;
protected int noisebasis2; protected int noisebasis2;
/**
* Constructor stores the given noise generator.
* @param noiseGenerator
* the noise generator
*/
public TextureGeneratorDistnoise(NoiseGenerator noiseGenerator) {
super(noiseGenerator, Format.Luminance8);
}
@Override
public void readData(Structure tex, BlenderContext blenderContext) {
super.readData(tex, blenderContext);
noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
distAmount = ((Number) tex.getFieldValue("dist_amount")).floatValue();
noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
noisebasis2 = ((Number) tex.getFieldValue("noisebasis2")).intValue();
}
@Override
public void getPixel(TexturePixel pixel, float x, float y, float z) {
pixel.intensity = this.musgraveVariableLunacrityNoise(x * 4, y * 4, z * 4, distAmount, noisebasis, noisebasis2);
pixel.intensity = FastMath.clamp(pixel.intensity, 0.0f, 1.0f);
if (colorBand != null) {
int colorbandIndex = (int) (pixel.intensity * 1000.0f);
pixel.red = colorBand[colorbandIndex][0];
pixel.green = colorBand[colorbandIndex][1];
pixel.blue = colorBand[colorbandIndex][2];
this.applyBrightnessAndContrast(bacd, pixel); /**
} else { * Constructor stores the given noise generator.
this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); * @param noiseGenerator
} * the noise generator
} */
public TextureGeneratorDistnoise(NoiseGenerator noiseGenerator) {
super(noiseGenerator, Format.Luminance8);
}
@Override
public void readData(Structure tex, BlenderContext blenderContext) {
super.readData(tex, blenderContext);
noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
distAmount = ((Number) tex.getFieldValue("dist_amount")).floatValue();
noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
noisebasis2 = ((Number) tex.getFieldValue("noisebasis2")).intValue();
}
@Override
public void getPixel(TexturePixel pixel, float x, float y, float z) {
pixel.intensity = this.musgraveVariableLunacrityNoise(x * 4, y * 4, z * 4, distAmount, noisebasis, noisebasis2);
pixel.intensity = FastMath.clamp(pixel.intensity, 0.0f, 1.0f);
if (colorBand != null) {
int colorbandIndex = (int) (pixel.intensity * 1000.0f);
pixel.red = colorBand[colorbandIndex][0];
pixel.green = colorBand[colorbandIndex][1];
pixel.blue = colorBand[colorbandIndex][2];
this.applyBrightnessAndContrast(bacd, pixel);
} else {
this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness);
}
}
/** /**
* "Variable Lacunarity Noise" A distorted variety of Perlin noise. This method is used to calculate distorted noise * "Variable Lacunarity Noise" A distorted variety of Perlin noise. This method is used to calculate distorted noise
* texture. * texture.
* @param x * @param x
@ -106,6 +106,6 @@ public class TextureGeneratorDistnoise extends TextureGenerator {
float rx = abstractNoiseFunc1.execute(x + 13.5f, y + 13.5f, z + 13.5f) * distortion; float rx = abstractNoiseFunc1.execute(x + 13.5f, y + 13.5f, z + 13.5f) * distortion;
float ry = abstractNoiseFunc1.execute(x, y, z) * distortion; float ry = abstractNoiseFunc1.execute(x, y, z) * distortion;
float rz = abstractNoiseFunc1.execute(x - 13.5f, y - 13.5f, z - 13.5f) * distortion; float rz = abstractNoiseFunc1.execute(x - 13.5f, y - 13.5f, z - 13.5f) * distortion;
return abstractNoiseFunc2.executeSigned(x + rx, y + ry, z + rz); //distorted-domain noise return abstractNoiseFunc2.executeSigned(x + rx, y + ry, z + rz); // distorted-domain noise
} }
} }

@ -3,37 +3,37 @@ package com.jme3.scene.plugins.blender.textures.generating;
import com.jme3.scene.plugins.blender.textures.TextureHelper; import com.jme3.scene.plugins.blender.textures.TextureHelper;
public class TextureGeneratorFactory { public class TextureGeneratorFactory {
private NoiseGenerator noiseGenerator; private NoiseGenerator noiseGenerator;
public TextureGeneratorFactory(String blenderVersion) { public TextureGeneratorFactory(String blenderVersion) {
noiseGenerator = new NoiseGenerator(blenderVersion); noiseGenerator = new NoiseGenerator(blenderVersion);
} }
public TextureGenerator createTextureGenerator(int generatedTexture) { public TextureGenerator createTextureGenerator(int generatedTexture) {
switch(generatedTexture) { switch (generatedTexture) {
case TextureHelper.TEX_BLEND: case TextureHelper.TEX_BLEND:
return new TextureGeneratorBlend(noiseGenerator); return new TextureGeneratorBlend(noiseGenerator);
case TextureHelper.TEX_CLOUDS: case TextureHelper.TEX_CLOUDS:
return new TextureGeneratorClouds(noiseGenerator); return new TextureGeneratorClouds(noiseGenerator);
case TextureHelper.TEX_DISTNOISE: case TextureHelper.TEX_DISTNOISE:
return new TextureGeneratorDistnoise(noiseGenerator); return new TextureGeneratorDistnoise(noiseGenerator);
case TextureHelper.TEX_MAGIC: case TextureHelper.TEX_MAGIC:
return new TextureGeneratorMagic(noiseGenerator); return new TextureGeneratorMagic(noiseGenerator);
case TextureHelper.TEX_MARBLE: case TextureHelper.TEX_MARBLE:
return new TextureGeneratorMarble(noiseGenerator); return new TextureGeneratorMarble(noiseGenerator);
case TextureHelper.TEX_MUSGRAVE: case TextureHelper.TEX_MUSGRAVE:
return new TextureGeneratorMusgrave(noiseGenerator); return new TextureGeneratorMusgrave(noiseGenerator);
case TextureHelper.TEX_NOISE: case TextureHelper.TEX_NOISE:
return new TextureGeneratorNoise(noiseGenerator); return new TextureGeneratorNoise(noiseGenerator);
case TextureHelper.TEX_STUCCI: case TextureHelper.TEX_STUCCI:
return new TextureGeneratorStucci(noiseGenerator); return new TextureGeneratorStucci(noiseGenerator);
case TextureHelper.TEX_VORONOI: case TextureHelper.TEX_VORONOI:
return new TextureGeneratorVoronoi(noiseGenerator); return new TextureGeneratorVoronoi(noiseGenerator);
case TextureHelper.TEX_WOOD: case TextureHelper.TEX_WOOD:
return new TextureGeneratorWood(noiseGenerator); return new TextureGeneratorWood(noiseGenerator);
default: default:
throw new IllegalStateException("Unknown generated texture type: " + generatedTexture); throw new IllegalStateException("Unknown generated texture type: " + generatedTexture);
} }
} }
} }

@ -42,119 +42,119 @@ import com.jme3.texture.Image.Format;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class TextureGeneratorMagic extends TextureGenerator { public class TextureGeneratorMagic extends TextureGenerator {
private static NoiseDepthFunction[] noiseDepthFunctions = new NoiseDepthFunction[10]; private static NoiseDepthFunction[] noiseDepthFunctions = new NoiseDepthFunction[10];
static { static {
noiseDepthFunctions[0] = new NoiseDepthFunction() { noiseDepthFunctions[0] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) { public void compute(float[] xyz, float turbulence) {
xyz[1] = -(float) Math.cos(xyz[0] - xyz[1] + xyz[2]) * turbulence; xyz[1] = -(float) Math.cos(xyz[0] - xyz[1] + xyz[2]) * turbulence;
} }
}; };
noiseDepthFunctions[1] = new NoiseDepthFunction() { noiseDepthFunctions[1] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) { public void compute(float[] xyz, float turbulence) {
xyz[0] = (float) Math.cos(xyz[0] - xyz[1] - xyz[2]) * turbulence; xyz[0] = (float) Math.cos(xyz[0] - xyz[1] - xyz[2]) * turbulence;
} }
}; };
noiseDepthFunctions[2] = new NoiseDepthFunction() { noiseDepthFunctions[2] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) { public void compute(float[] xyz, float turbulence) {
xyz[2] = (float) Math.sin(-xyz[0] - xyz[1] - xyz[2]) * turbulence; xyz[2] = (float) Math.sin(-xyz[0] - xyz[1] - xyz[2]) * turbulence;
} }
}; };
noiseDepthFunctions[3] = new NoiseDepthFunction() { noiseDepthFunctions[3] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) { public void compute(float[] xyz, float turbulence) {
xyz[0] = -(float) Math.cos(-xyz[0] + xyz[1] - xyz[2]) * turbulence; xyz[0] = -(float) Math.cos(-xyz[0] + xyz[1] - xyz[2]) * turbulence;
} }
}; };
noiseDepthFunctions[4] = new NoiseDepthFunction() { noiseDepthFunctions[4] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) { public void compute(float[] xyz, float turbulence) {
xyz[1] = -(float) Math.sin(-xyz[0] + xyz[1] + xyz[2]) * turbulence; xyz[1] = -(float) Math.sin(-xyz[0] + xyz[1] + xyz[2]) * turbulence;
} }
}; };
noiseDepthFunctions[5] = new NoiseDepthFunction() { noiseDepthFunctions[5] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) { public void compute(float[] xyz, float turbulence) {
xyz[1] = -(float) Math.cos(-xyz[0] + xyz[1] + xyz[2]) * turbulence; xyz[1] = -(float) Math.cos(-xyz[0] + xyz[1] + xyz[2]) * turbulence;
} }
}; };
noiseDepthFunctions[6] = new NoiseDepthFunction() { noiseDepthFunctions[6] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) { public void compute(float[] xyz, float turbulence) {
xyz[0] = (float) Math.cos(xyz[0] + xyz[1] + xyz[2]) * turbulence; xyz[0] = (float) Math.cos(xyz[0] + xyz[1] + xyz[2]) * turbulence;
} }
}; };
noiseDepthFunctions[7] = new NoiseDepthFunction() { noiseDepthFunctions[7] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) { public void compute(float[] xyz, float turbulence) {
xyz[2] = (float) Math.sin(xyz[0] + xyz[1] - xyz[2]) * turbulence; xyz[2] = (float) Math.sin(xyz[0] + xyz[1] - xyz[2]) * turbulence;
} }
}; };
noiseDepthFunctions[8] = new NoiseDepthFunction() { noiseDepthFunctions[8] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) { public void compute(float[] xyz, float turbulence) {
xyz[0] = -(float) Math.cos(-xyz[0] - xyz[1] + xyz[2]) * turbulence; xyz[0] = -(float) Math.cos(-xyz[0] - xyz[1] + xyz[2]) * turbulence;
} }
}; };
noiseDepthFunctions[9] = new NoiseDepthFunction() { noiseDepthFunctions[9] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) { public void compute(float[] xyz, float turbulence) {
xyz[1] = -(float) Math.sin(xyz[0] - xyz[1] + xyz[2]) * turbulence; xyz[1] = -(float) Math.sin(xyz[0] - xyz[1] + xyz[2]) * turbulence;
} }
}; };
} }
protected int noisedepth;
protected float turbul;
protected float[] xyz = new float[3];
/**
* Constructor stores the given noise generator.
* @param noiseGenerator
* the noise generator
*/
public TextureGeneratorMagic(NoiseGenerator noiseGenerator) {
super(noiseGenerator, Format.RGBA8);
}
@Override
public void readData(Structure tex, BlenderContext blenderContext) {
super.readData(tex, blenderContext);
noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue();
turbul = ((Number) tex.getFieldValue("turbul")).floatValue() / 5.0f;
}
@Override
public void getPixel(TexturePixel pixel, float x, float y, float z) {
float turb = turbul;
xyz[0] = (float) Math.sin((x + y + z) * 5.0f);
xyz[1] = (float) Math.cos((-x + y - z) * 5.0f);
xyz[2] = -(float) Math.cos((-x - y + z) * 5.0f);
if (colorBand != null) { protected int noisedepth;
pixel.intensity = FastMath.clamp(0.3333f * (xyz[0] + xyz[1] + xyz[2]), 0.0f, 1.0f); protected float turbul;
int colorbandIndex = (int) (pixel.intensity * 1000.0f); protected float[] xyz = new float[3];
pixel.red = colorBand[colorbandIndex][0];
pixel.green = colorBand[colorbandIndex][1];
pixel.blue = colorBand[colorbandIndex][2];
pixel.alpha = colorBand[colorbandIndex][3];
} else {
if (noisedepth > 0) {
xyz[0] *= turb;
xyz[1] *= turb;
xyz[2] *= turb;
for (int m=0;m<noisedepth;++m) {
noiseDepthFunctions[m].compute(xyz, turb);
}
}
if (turb != 0.0f) { /**
turb *= 2.0f; * Constructor stores the given noise generator.
xyz[0] /= turb; * @param noiseGenerator
xyz[1] /= turb; * the noise generator
xyz[2] /= turb; */
} public TextureGeneratorMagic(NoiseGenerator noiseGenerator) {
pixel.red = 0.5f - xyz[0]; super(noiseGenerator, Format.RGBA8);
pixel.green = 0.5f - xyz[1]; }
pixel.blue = 0.5f - xyz[2];
pixel.alpha = 1.0f; @Override
} public void readData(Structure tex, BlenderContext blenderContext) {
this.applyBrightnessAndContrast(bacd, pixel); super.readData(tex, blenderContext);
} noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue();
turbul = ((Number) tex.getFieldValue("turbul")).floatValue() / 5.0f;
private static interface NoiseDepthFunction { }
void compute(float[] xyz, float turbulence);
} @Override
public void getPixel(TexturePixel pixel, float x, float y, float z) {
float turb = turbul;
xyz[0] = (float) Math.sin((x + y + z) * 5.0f);
xyz[1] = (float) Math.cos((-x + y - z) * 5.0f);
xyz[2] = -(float) Math.cos((-x - y + z) * 5.0f);
if (colorBand != null) {
pixel.intensity = FastMath.clamp(0.3333f * (xyz[0] + xyz[1] + xyz[2]), 0.0f, 1.0f);
int colorbandIndex = (int) (pixel.intensity * 1000.0f);
pixel.red = colorBand[colorbandIndex][0];
pixel.green = colorBand[colorbandIndex][1];
pixel.blue = colorBand[colorbandIndex][2];
pixel.alpha = colorBand[colorbandIndex][3];
} else {
if (noisedepth > 0) {
xyz[0] *= turb;
xyz[1] *= turb;
xyz[2] *= turb;
for (int m = 0; m < noisedepth; ++m) {
noiseDepthFunctions[m].compute(xyz, turb);
}
}
if (turb != 0.0f) {
turb *= 2.0f;
xyz[0] /= turb;
xyz[1] /= turb;
xyz[2] /= turb;
}
pixel.red = 0.5f - xyz[0];
pixel.green = 0.5f - xyz[1];
pixel.blue = 0.5f - xyz[2];
pixel.alpha = 1.0f;
}
this.applyBrightnessAndContrast(bacd, pixel);
}
private static interface NoiseDepthFunction {
void compute(float[] xyz, float turbulence);
}
} }

@ -40,50 +40,50 @@ import com.jme3.scene.plugins.blender.textures.TexturePixel;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class TextureGeneratorMarble extends TextureGeneratorWood { public class TextureGeneratorMarble extends TextureGeneratorWood {
// tex->stype // tex->stype
protected static final int TEX_SOFT = 0; protected static final int TEX_SOFT = 0;
protected static final int TEX_SHARP = 1; protected static final int TEX_SHARP = 1;
protected static final int TEX_SHARPER = 2; protected static final int TEX_SHARPER = 2;
protected MarbleData marbleData;
/** protected MarbleData marbleData;
* Constructor stores the given noise generator.
* @param noiseGenerator /**
* the noise generator * Constructor stores the given noise generator.
*/ * @param noiseGenerator
public TextureGeneratorMarble(NoiseGenerator noiseGenerator) { * the noise generator
super(noiseGenerator); */
} public TextureGeneratorMarble(NoiseGenerator noiseGenerator) {
super(noiseGenerator);
@Override }
public void readData(Structure tex, BlenderContext blenderContext) {
super.readData(tex, blenderContext); @Override
marbleData = new MarbleData(tex); public void readData(Structure tex, BlenderContext blenderContext) {
} super.readData(tex, blenderContext);
marbleData = new MarbleData(tex);
@Override }
public void getPixel(TexturePixel pixel, float x, float y, float z) {
pixel.intensity = this.marbleInt(marbleData, x, y, z); @Override
if (colorBand != null) { public void getPixel(TexturePixel pixel, float x, float y, float z) {
int colorbandIndex = (int) (pixel.intensity * 1000.0f); pixel.intensity = this.marbleInt(marbleData, x, y, z);
pixel.red = colorBand[colorbandIndex][0]; if (colorBand != null) {
pixel.green = colorBand[colorbandIndex][1]; int colorbandIndex = (int) (pixel.intensity * 1000.0f);
pixel.blue = colorBand[colorbandIndex][2]; pixel.red = colorBand[colorbandIndex][0];
pixel.green = colorBand[colorbandIndex][1];
this.applyBrightnessAndContrast(bacd, pixel); pixel.blue = colorBand[colorbandIndex][2];
pixel.alpha = colorBand[colorbandIndex][3];
} else { this.applyBrightnessAndContrast(bacd, pixel);
this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); pixel.alpha = colorBand[colorbandIndex][3];
} } else {
} this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness);
}
}
public float marbleInt(MarbleData marbleData, float x, float y, float z) { public float marbleInt(MarbleData marbleData, float x, float y, float z) {
int waveform; int waveform;
if (marbleData.waveform > TEX_TRI || marbleData.waveform < TEX_SIN) { if (marbleData.waveform > TEX_TRI || marbleData.waveform < TEX_SIN) {
waveform = 0; waveform = 0;
} else { } else {
waveform = marbleData.waveform; waveform = marbleData.waveform;
} }
float n = 5.0f * (x + y + z); float n = 5.0f * (x + y + z);
@ -99,18 +99,18 @@ public class TextureGeneratorMarble extends TextureGeneratorWood {
} }
return mi; return mi;
} }
private static class MarbleData { private static class MarbleData {
public final float noisesize; public final float noisesize;
public final int noisebasis; public final int noisebasis;
public final int noisedepth; public final int noisedepth;
public final int stype; public final int stype;
public final float turbul; public final float turbul;
public final int waveform; public final int waveform;
public final boolean isHard; public final boolean isHard;
public MarbleData(Structure tex) { public MarbleData(Structure tex) {
noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue();
stype = ((Number) tex.getFieldValue("stype")).intValue(); stype = ((Number) tex.getFieldValue("stype")).intValue();
@ -118,6 +118,6 @@ public class TextureGeneratorMarble extends TextureGeneratorWood {
int noisetype = ((Number) tex.getFieldValue("noisetype")).intValue(); int noisetype = ((Number) tex.getFieldValue("noisetype")).intValue();
waveform = ((Number) tex.getFieldValue("noisebasis2")).intValue(); waveform = ((Number) tex.getFieldValue("noisebasis2")).intValue();
isHard = noisetype != TEX_NOISESOFT; isHard = noisetype != TEX_NOISESOFT;
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save