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
* the given assets and those being generated automatically. Every generated
* texture will have this kind of key attached.
*
*
* @author Marcin Roguski (Kaelthas)
*/
public class GeneratedTextureKey extends TextureKey {
@ -44,8 +44,9 @@ public class GeneratedTextureKey extends TextureKey {
/**
* Constructor. Stores the name. Extension and folder name are empty
* strings.
*
* @param name the name of the texture
*
* @param name
* the name of the texture
*/
public GeneratedTextureKey(String name) {
super(name);

@ -50,116 +50,116 @@ import com.jme3.scene.plugins.blender.objects.Properties;
*/
public abstract class AbstractBlenderHelper {
/** The version of the blend file. */
protected final int blenderVersion;
/** This variable indicates if the Y asxis is the UP axis or not. */
protected boolean fixUpAxis;
/** Quaternion used to rotate data when Y is up axis. */
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() {}
/** The version of the blend file. */
protected final int blenderVersion;
/** This variable indicates if the Y asxis is the UP axis or not. */
protected boolean fixUpAxis;
/** Quaternion used to rotate data when Y is up axis. */
protected Quaternion upAxisRotationQuaternion;
/**
* This method should be used to check if the text is blank. Avoid using text.trim().length()==0. This causes that more strings are
* being created and stored in the memory. It can be unwise especially inside loops.
* @param text
* the text to be checked
* @return <b>true</b> if the text is blank and <b>false</b> otherwise
*/
protected boolean isBlank(String text) {
if (text != null) {
for (int i = 0; i < text.length(); ++i) {
if (!Character.isWhitespace(text.charAt(i))) {
return false;
}
}
}
return true;
}
/**
* 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 loads the properties if they are available and defined for the structure.
* @param structure
* the structure we read the properties from
* @param blenderContext
* the blender context
* @return loaded properties or null if they are not available
* @throws BlenderFileException
* an exception is thrown when the blend file is somehow corrupted
*/
protected Properties loadProperties(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
Properties properties = null;
Structure id = (Structure) structure.getFieldValue("ID");
if (id != null) {
Pointer pProperties = (Pointer) id.getFieldValue("properties");
if (pProperties.isNotNull()) {
Structure propertiesStructure = pProperties.fetchData(blenderContext.getInputStream()).get(0);
properties = new Properties();
properties.load(propertiesStructure, blenderContext);
}
}
return properties;
}
/**
* The method applies properties to the given spatial. The Properties
* 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
* the properties to be applied
*/
protected void applyProperties(Spatial spatial, Properties properties) {
List<String> propertyNames = properties.getSubPropertiesNames();
if(propertyNames != null && propertyNames.size() > 0) {
for(String propertyName : propertyNames) {
Object value = properties.findValue(propertyName);
if(value instanceof Savable || value instanceof Boolean || value instanceof String ||
value instanceof Float || value instanceof Integer || value instanceof Long) {
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);
/**
* 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
* being created and stored in the memory. It can be unwise especially inside loops.
* @param text
* the text to be checked
* @return <b>true</b> if the text is blank and <b>false</b> otherwise
*/
protected boolean isBlank(String text) {
if (text != null) {
for (int i = 0; i < text.length(); ++i) {
if (!Character.isWhitespace(text.charAt(i))) {
return false;
}
}
}
return true;
}
/**
* This method loads the properties if they are available and defined for the structure.
* @param structure
* the structure we read the properties from
* @param blenderContext
* the blender context
* @return loaded properties or null if they are not available
* @throws BlenderFileException
* an exception is thrown when the blend file is somehow corrupted
*/
protected Properties loadProperties(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
Properties properties = null;
Structure id = (Structure) structure.getFieldValue("ID");
if (id != null) {
Pointer pProperties = (Pointer) id.getFieldValue("properties");
if (pProperties.isNotNull()) {
Structure propertiesStructure = pProperties.fetchData(blenderContext.getInputStream()).get(0);
properties = new Properties();
properties.load(propertiesStructure, blenderContext);
}
}
return properties;
}
/**
* The method applies properties to the given spatial. The Properties
* 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
* the properties to be applied
*/
protected void applyProperties(Spatial spatial, Properties properties) {
List<String> propertyNames = properties.getSubPropertiesNames();
if (propertyNames != null && propertyNames.size() > 0) {
for (String propertyName : propertyNames) {
Object value = properties.findValue(propertyName);
if (value instanceof Savable || value instanceof Boolean || value instanceof String || value instanceof Float || value instanceof Integer || value instanceof Long) {
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.
* @author Marcin Roguski (Kaelthas)
*/
/* package */ abstract class AbstractBlenderLoader implements AssetLoader {
private static final Logger LOGGER = Logger.getLogger(AbstractBlenderLoader.class.getName());
protected BlenderContext blenderContext;
/* package */abstract class AbstractBlenderLoader implements AssetLoader {
private static final Logger LOGGER = Logger.getLogger(AbstractBlenderLoader.class.getName());
/**
* This method converts the given structure to a scene node.
* @param structure
* structure of a scene
* @return scene's node
*/
public Node toScene(Structure structure) {
if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.SCENES) == 0) {
return null;
}
Node result = new Node(structure.getName());
try {
List<Structure> base = ((Structure)structure.getFieldValue("base")).evaluateListBase(blenderContext);
for(Structure b : base) {
Pointer pObject = (Pointer) b.getFieldValue("object");
if(pObject.isNotNull()) {
Structure objectStructure = pObject.fetchData(blenderContext.getInputStream()).get(0);
Object object = this.toObject(objectStructure);
if(object instanceof LightNode && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {
result.addLight(((LightNode)object).getLight());
result.attachChild((LightNode) object);
} else if (object instanceof Node && (blenderContext.getBlenderKey().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() });
if (((Node) object).getParent() == null) {
result.attachChild((Spatial) object);
protected BlenderContext blenderContext;
/**
* This method converts the given structure to a scene node.
* @param structure
* structure of a scene
* @return scene's node
*/
public Node toScene(Structure structure) {
if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.SCENES) == 0) {
return null;
}
Node result = new Node(structure.getName());
try {
List<Structure> base = ((Structure) structure.getFieldValue("base")).evaluateListBase(blenderContext);
for (Structure b : base) {
Pointer pObject = (Pointer) b.getFieldValue("object");
if (pObject.isNotNull()) {
Structure objectStructure = pObject.fetchData(blenderContext.getInputStream()).get(0);
Object object = this.toObject(objectStructure);
if (object instanceof LightNode && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {
result.addLight(((LightNode) object).getLight());
result.attachChild((LightNode) object);
} else if (object instanceof Node && (blenderContext.getBlenderKey().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() });
if (((Node) object).getParent() == null) {
result.attachChild((Spatial) object);
}
}
}
}
} catch (BlenderFileException e) {
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
}
return result;
}
}
}
}
} catch (BlenderFileException e) {
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
}
return result;
}
/**
* This method converts the given structure to a camera.
* @param structure
* structure of a camera
* @return camera's node
*/
public CameraNode toCamera(Structure structure) throws BlenderFileException {
CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class);
if (cameraHelper.shouldBeLoaded(structure, blenderContext)) {
return cameraHelper.toCamera(structure, blenderContext);
}
return null;
}
/**
* This method converts the given structure to a camera.
* @param structure
* structure of a camera
* @return camera's node
*/
public CameraNode toCamera(Structure structure) throws BlenderFileException {
CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class);
if (cameraHelper.shouldBeLoaded(structure, blenderContext)) {
return cameraHelper.toCamera(structure, blenderContext);
}
return null;
}
/**
* This method converts the given structure to a light.
* @param structure
* structure of a light
* @return light's node
*/
public LightNode toLight(Structure structure) throws BlenderFileException {
LightHelper lightHelper = blenderContext.getHelper(LightHelper.class);
if (lightHelper.shouldBeLoaded(structure, blenderContext)) {
return lightHelper.toLight(structure, blenderContext);
}
return null;
}
/**
* This method converts the given structure to a light.
* @param structure
* structure of a light
* @return light's node
*/
public LightNode toLight(Structure structure) throws BlenderFileException {
LightHelper lightHelper = blenderContext.getHelper(LightHelper.class);
if (lightHelper.shouldBeLoaded(structure, blenderContext)) {
return lightHelper.toLight(structure, blenderContext);
}
return null;
}
/**
* This method converts the given structure to a node.
* @param structure
* structure of an object
* @return object's node
*/
public Object toObject(Structure structure) throws BlenderFileException {
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
if (objectHelper.shouldBeLoaded(structure, blenderContext)) {
return objectHelper.toObject(structure, blenderContext);
}
return null;
}
/**
* This method converts the given structure to a node.
* @param structure
* structure of an object
* @return object's node
*/
public Object toObject(Structure structure) throws BlenderFileException {
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
if (objectHelper.shouldBeLoaded(structure, blenderContext)) {
return objectHelper.toObject(structure, blenderContext);
}
return null;
}
/**
* This method converts the given structure to a list of geometries.
* @param structure
* structure of a mesh
* @return list of geometries
*/
public List<Geometry> toMesh(Structure structure) throws BlenderFileException {
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
if (meshHelper.shouldBeLoaded(structure, blenderContext)) {
return meshHelper.toMesh(structure, blenderContext);
}
return null;
}
/**
* This method converts the given structure to a list of geometries.
* @param structure
* structure of a mesh
* @return list of geometries
*/
public List<Geometry> toMesh(Structure structure) throws BlenderFileException {
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
if (meshHelper.shouldBeLoaded(structure, blenderContext)) {
return meshHelper.toMesh(structure, blenderContext);
}
return null;
}
// /**
// * This method converts the given structure to a material.
// * @param structure
// * structure of a material
// * @return material's node
// */
// public Material toMaterial(Structure structure) throws BlenderFileException {
// MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
// if (materialHelper.shouldBeLoaded(structure, blenderContext)) {
// return materialHelper.toMaterial(structure, blenderContext);
// }
// return null;
// }
// /**
// * This method converts the given structure to a material.
// * @param structure
// * structure of a material
// * @return material's node
// */
// public Material toMaterial(Structure structure) throws BlenderFileException {
// MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
// if (materialHelper.shouldBeLoaded(structure, blenderContext)) {
// return materialHelper.toMaterial(structure, blenderContext);
// }
// return null;
// }
/**
* 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.
* @param structure
* the structure with WORLD block data
* @return data read from the WORLD block that can be added to the scene
*/
public WorldData toWorldData(Structure structure) {
WorldData result = new WorldData();
/**
* 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.
* @param structure
* the structure with WORLD block data
* @return data read from the WORLD block that can be added to the scene
*/
public WorldData toWorldData(Structure structure) {
WorldData result = new WorldData();
// reading ambient light
AmbientLight ambientLight = new AmbientLight();
float ambr = ((Number) structure.getFieldValue("ambr")).floatValue();
float ambg = ((Number) structure.getFieldValue("ambg")).floatValue();
float ambb = ((Number) structure.getFieldValue("ambb")).floatValue();
ambientLight.setColor(new ColorRGBA(ambr, ambg, ambb, 0.0f));
result.setAmbientLight(ambientLight);
// reading ambient light
AmbientLight ambientLight = new AmbientLight();
float ambr = ((Number) structure.getFieldValue("ambr")).floatValue();
float ambg = ((Number) structure.getFieldValue("ambg")).floatValue();
float ambb = ((Number) structure.getFieldValue("ambb")).floatValue();
ambientLight.setColor(new ColorRGBA(ambr, ambg, ambb, 0.0f));
result.setAmbientLight(ambientLight);
return result;
}
return result;
}
}

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

@ -57,30 +57,30 @@ public class BlenderModelLoader extends BlenderLoader {
public Spatial load(AssetInfo assetInfo) throws IOException {
try {
this.setup(assetInfo);
BlenderKey blenderKey = blenderContext.getBlenderKey();
Node modelRoot = new Node(blenderKey.getName());
for (FileBlockHeader block : blocks) {
if (block.getCode() == FileBlockHeader.BLOCK_OB00) {
Object object = this.toObject(block.getStructure(blenderContext));
if(object instanceof LightNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {
modelRoot.addLight(((LightNode)object).getLight());
modelRoot.attachChild((LightNode)object);
} 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() });
if (((Node) object).getParent() == null) {
modelRoot.attachChild((Node)object);
if (object instanceof LightNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {
modelRoot.addLight(((LightNode) object).getLight());
modelRoot.attachChild((LightNode) object);
} 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() });
if (((Node) object).getParent() == null) {
modelRoot.attachChild((Node) object);
}
}
}
}
}
//bake constraints after everything is loaded
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
constraintHelper.bakeConstraints(blenderContext);
// bake constraints after everything is loaded
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
constraintHelper.bakeConstraints(blenderContext);
blenderContext.dispose();
return modelRoot;
} catch (BlenderFileException e) {

@ -56,215 +56,215 @@ import java.util.logging.Logger;
* @author Marcin Roguski (Kaelthas)
*/
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";
/** A map of bones and their old memory addresses. */
private Map<Bone, Long> bonesOMAs = new HashMap<Bone, Long>();
public static final String ARMETURE_NODE_MARKER = "armeture-node";
/**
* 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 ArmatureHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis);
}
/** A map of bones and their old memory addresses. */
private Map<Bone, Long> bonesOMAs = new HashMap<Bone, Long>();
/**
* This method builds the object's bones structure.
*
* @param boneStructure
* the structure containing the bones' data
* @param parent
* the parent bone
* @param result
* the list where the newly created bone will be added
* @param bonesPoseChannels
* a map of bones poses channels
* @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 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 ArmatureHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis);
}
/**
* This method returns the old memory address of a bone. If the bone does
* not exist in the blend file - zero is returned.
*
* @param bone
* the bone whose old memory address we seek
* @return the old memory address of the given bone
*/
public Long getBoneOMA(Bone bone) {
Long result = bonesOMAs.get(bone);
if (result == null) {
result = Long.valueOf(0);
}
return result;
}
/**
* This method builds the object's bones structure.
*
* @param boneStructure
* the structure containing the bones' data
* @param parent
* the parent bone
* @param result
* the list where the newly created bone will be added
* @param bonesPoseChannels
* a map of bones poses channels
* @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 a map where the key is the object's group index that
* 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;
}
/**
* This method returns the old memory address of a bone. If the bone does
* not exist in the blend file - zero is returned.
*
* @param bone
* the bone whose old memory address we seek
* @return the old memory address of the given bone
*/
public Long getBoneOMA(Bone bone) {
Long result = bonesOMAs.get(bone);
if (result == null) {
result = Long.valueOf(0);
}
return result;
}
@Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return true;
}
/**
* This method returns a map where the key is the object's group index that
* 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;
}
/**
* This method retuns the bone tracks for animation.
*
* @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);
}
}
@Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return true;
}
/**
* This method retuns the bone tracks for animation for blender version 2.50
* and higher.
*
* @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
*/
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);
}
/**
* This method retuns the bone tracks for animation.
*
* @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);
}
}
Bone bone = skeleton.getBone(boneIndex);
Ipo ipo = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion());
tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, bone.getLocalRotation(), 0, ipo.getLastFrame(), fps, false));
}
}
return tracks.toArray(new BoneTrack[tracks.size()]);
}
/**
* This method retuns the bone tracks for animation for blender version 2.50
* and higher.
*
* @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
*/
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);
}
/**
* This method retuns the bone tracks for animation for blender version 2.49
* (and probably several lower versions too).
*
* @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
*/
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()]);
}
Bone bone = skeleton.getBone(boneIndex);
Ipo ipo = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion());
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;
}
/**
* This method retuns the bone tracks for animation for blender version 2.49
* (and probably several lower versions too).
*
* @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
*/
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.
*
* @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)
*/
public class BoneContext {
/** The OMA of the bone's armature object. */
private Long armatureObjectOMA;
/** The structure of the bone. */
private Structure boneStructure;
/** Bone's pose channel structure. */
private Structure poseChannel;
/** Bone's name. */
private String boneName;
/** This variable indicates if the Y axis should be the UP axis. */
private boolean fixUpAxis;
/** The bone's armature matrix. */
private Matrix4f armatureMatrix;
/** The parent context. */
private BoneContext parent;
/** The children of this context. */
private List<BoneContext> children = new ArrayList<BoneContext>();
/** Created bone (available after calling 'buildBone' method). */
private Bone bone;
/** Bone's pose transform (available after calling 'buildBone' method). */
private Transform poseTransform = new Transform();
/** The bone's rest matrix. */
private Matrix4f restMatrix;
/** Bone's total inverse transformation. */
private Matrix4f inverseTotalTransformation;
/** Bone's parent inverse matrix. */
private Matrix4f inverseParentMatrix;
/** The length of the bone. */
private float length;
/**
* Constructor. Creates the basic set of bone's data.
*
* @param armatureObjectOMA
* the OMA of the bone's armature object
* @param boneStructure
* the bone's structure
* @param objectToArmatureMatrix
* object-to-armature transformation matrix
* @param bonesPoseChannels
* a map of pose channels for each bone OMA
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when problem with blender data reading
* occurs
*/
public BoneContext(Long armatureObjectOMA, Structure boneStructure, Matrix4f objectToArmatureMatrix, final Map<Long, Structure> bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException {
this(boneStructure, armatureObjectOMA, null, objectToArmatureMatrix, bonesPoseChannels, blenderContext);
}
/**
* Constructor. Creates the basic set of bone's data.
*
* @param boneStructure
* the bone's structure
* @param armatureObjectOMA
* the OMA of the bone's armature object
* @param parent
* bone's parent (null if the bone is the root bone)
* @param objectToArmatureMatrix
* object-to-armature transformation matrix
* @param bonesPoseChannels
* a map of pose channels for each bone OMA
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when problem with blender data reading
* occurs
*/
private BoneContext(Structure boneStructure, Long armatureObjectOMA, BoneContext parent, Matrix4f objectToArmatureMatrix, final Map<Long, Structure> bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException {
this.parent = parent;
this.boneStructure = boneStructure;
this.armatureObjectOMA = armatureObjectOMA;
boneName = boneStructure.getFieldValue("name").toString();
length = ((Number)boneStructure.getFieldValue("length")).floatValue();
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
armatureMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", true);
fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis();
this.computeRestMatrix(objectToArmatureMatrix);
List<Structure> childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(blenderContext);
for (Structure child : childbase) {
this.children.add(new BoneContext(child, armatureObjectOMA, this, objectToArmatureMatrix, bonesPoseChannels, blenderContext));
}
poseChannel = bonesPoseChannels.get(boneStructure.getOldMemoryAddress());
blenderContext.setBoneContext(boneStructure.getOldMemoryAddress(), this);
}
/**
* This method computes the rest matrix for the bone.
*
* @param objectToArmatureMatrix
* object-to-armature transformation matrix
*/
private void computeRestMatrix(Matrix4f objectToArmatureMatrix) {
if (parent != null) {
inverseParentMatrix = parent.inverseTotalTransformation.clone();
} else if (fixUpAxis) {
inverseParentMatrix = objectToArmatureMatrix.clone();
} else {
inverseParentMatrix = Matrix4f.IDENTITY.clone();
}
restMatrix = armatureMatrix.clone();
inverseTotalTransformation = restMatrix.invert();
restMatrix = inverseParentMatrix.mult(restMatrix);
for (BoneContext child : this.children) {
child.computeRestMatrix(objectToArmatureMatrix);
}
}
/**
* This method computes the pose transform for the bone.
*/
@SuppressWarnings("unchecked")
private void computePoseTransform() {
DynamicArray<Number> loc = (DynamicArray<Number>) poseChannel.getFieldValue("loc");
DynamicArray<Number> size = (DynamicArray<Number>) poseChannel.getFieldValue("size");
DynamicArray<Number> quat = (DynamicArray<Number>) poseChannel.getFieldValue("quat");
if (fixUpAxis) {
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.setScale(size.get(0).floatValue(), size.get(2).floatValue(), size.get(1).floatValue());
} else {
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.setScale(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue());
}
Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());
localTransform.setScale(bone.getLocalScale());
localTransform.getTranslation().addLocal(poseTransform.getTranslation());
localTransform.getRotation().multLocal(poseTransform.getRotation());
localTransform.getScale().multLocal(poseTransform.getScale());
poseTransform.set(localTransform);
}
/**
* This method builds the bone. It recursively builds the bone's children.
*
* @param bones
* a list of bones where the newly created bone will be added
* @param boneOMAs
* the map between bone and its old memory address
* @param blenderContext
* the blender context
* @return newly created bone
*/
public Bone buildBone(List<Bone> bones, Map<Bone, Long> boneOMAs, BlenderContext blenderContext) {
Long boneOMA = boneStructure.getOldMemoryAddress();
bone = new Bone(boneName);
bones.add(bone);
boneOMAs.put(bone, boneOMA);
blenderContext.addLoadedFeatures(boneOMA, boneName, boneStructure, bone);
Matrix4f pose = this.restMatrix.clone();
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
Vector3f poseLocation = pose.toTranslationVector();
Quaternion rotation = pose.toRotationQuat();
Vector3f scale = objectHelper.getScale(pose);
bone.setBindTransforms(poseLocation, rotation, scale);
for (BoneContext child : children) {
bone.addChild(child.buildBone(bones, boneOMAs, blenderContext));
}
this.computePoseTransform();
return bone;
}
/**
* @return bone's pose transformation
*/
public Transform getPoseTransform() {
return poseTransform;
}
/**
* @return built bone (available after calling 'buildBone' method)
*/
public Bone getBone() {
return bone;
}
/**
* @return the old memory address of the bone
*/
public Long getBoneOma() {
return boneStructure.getOldMemoryAddress();
}
/**
* @return the length of the bone
*/
public float getLength() {
return length;
}
/**
* @return OMA of the bone's armature object
*/
public Long getArmatureObjectOMA() {
return armatureObjectOMA;
}
/** The OMA of the bone's armature object. */
private Long armatureObjectOMA;
/** The structure of the bone. */
private Structure boneStructure;
/** Bone's pose channel structure. */
private Structure poseChannel;
/** Bone's name. */
private String boneName;
/** This variable indicates if the Y axis should be the UP axis. */
private boolean fixUpAxis;
/** The bone's armature matrix. */
private Matrix4f armatureMatrix;
/** The parent context. */
private BoneContext parent;
/** The children of this context. */
private List<BoneContext> children = new ArrayList<BoneContext>();
/** Created bone (available after calling 'buildBone' method). */
private Bone bone;
/** Bone's pose transform (available after calling 'buildBone' method). */
private Transform poseTransform = new Transform();
/** The bone's rest matrix. */
private Matrix4f restMatrix;
/** Bone's total inverse transformation. */
private Matrix4f inverseTotalTransformation;
/** Bone's parent inverse matrix. */
private Matrix4f inverseParentMatrix;
/** The length of the bone. */
private float length;
/**
* Constructor. Creates the basic set of bone's data.
*
* @param armatureObjectOMA
* the OMA of the bone's armature object
* @param boneStructure
* the bone's structure
* @param objectToArmatureMatrix
* object-to-armature transformation matrix
* @param bonesPoseChannels
* a map of pose channels for each bone OMA
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when problem with blender data reading
* occurs
*/
public BoneContext(Long armatureObjectOMA, Structure boneStructure, Matrix4f objectToArmatureMatrix, final Map<Long, Structure> bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException {
this(boneStructure, armatureObjectOMA, null, objectToArmatureMatrix, bonesPoseChannels, blenderContext);
}
/**
* Constructor. Creates the basic set of bone's data.
*
* @param boneStructure
* the bone's structure
* @param armatureObjectOMA
* the OMA of the bone's armature object
* @param parent
* bone's parent (null if the bone is the root bone)
* @param objectToArmatureMatrix
* object-to-armature transformation matrix
* @param bonesPoseChannels
* a map of pose channels for each bone OMA
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when problem with blender data reading
* occurs
*/
private BoneContext(Structure boneStructure, Long armatureObjectOMA, BoneContext parent, Matrix4f objectToArmatureMatrix, final Map<Long, Structure> bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException {
this.parent = parent;
this.boneStructure = boneStructure;
this.armatureObjectOMA = armatureObjectOMA;
boneName = boneStructure.getFieldValue("name").toString();
length = ((Number) boneStructure.getFieldValue("length")).floatValue();
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
armatureMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", true);
fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis();
this.computeRestMatrix(objectToArmatureMatrix);
List<Structure> childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(blenderContext);
for (Structure child : childbase) {
this.children.add(new BoneContext(child, armatureObjectOMA, this, objectToArmatureMatrix, bonesPoseChannels, blenderContext));
}
poseChannel = bonesPoseChannels.get(boneStructure.getOldMemoryAddress());
blenderContext.setBoneContext(boneStructure.getOldMemoryAddress(), this);
}
/**
* This method computes the rest matrix for the bone.
*
* @param objectToArmatureMatrix
* object-to-armature transformation matrix
*/
private void computeRestMatrix(Matrix4f objectToArmatureMatrix) {
if (parent != null) {
inverseParentMatrix = parent.inverseTotalTransformation.clone();
} else if (fixUpAxis) {
inverseParentMatrix = objectToArmatureMatrix.clone();
} else {
inverseParentMatrix = Matrix4f.IDENTITY.clone();
}
restMatrix = armatureMatrix.clone();
inverseTotalTransformation = restMatrix.invert();
restMatrix = inverseParentMatrix.mult(restMatrix);
for (BoneContext child : this.children) {
child.computeRestMatrix(objectToArmatureMatrix);
}
}
/**
* This method computes the pose transform for the bone.
*/
@SuppressWarnings("unchecked")
private void computePoseTransform() {
DynamicArray<Number> loc = (DynamicArray<Number>) poseChannel.getFieldValue("loc");
DynamicArray<Number> size = (DynamicArray<Number>) poseChannel.getFieldValue("size");
DynamicArray<Number> quat = (DynamicArray<Number>) poseChannel.getFieldValue("quat");
if (fixUpAxis) {
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.setScale(size.get(0).floatValue(), size.get(2).floatValue(), size.get(1).floatValue());
} else {
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.setScale(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue());
}
Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());
localTransform.setScale(bone.getLocalScale());
localTransform.getTranslation().addLocal(poseTransform.getTranslation());
localTransform.getRotation().multLocal(poseTransform.getRotation());
localTransform.getScale().multLocal(poseTransform.getScale());
poseTransform.set(localTransform);
}
/**
* This method builds the bone. It recursively builds the bone's children.
*
* @param bones
* a list of bones where the newly created bone will be added
* @param boneOMAs
* the map between bone and its old memory address
* @param blenderContext
* the blender context
* @return newly created bone
*/
public Bone buildBone(List<Bone> bones, Map<Bone, Long> boneOMAs, BlenderContext blenderContext) {
Long boneOMA = boneStructure.getOldMemoryAddress();
bone = new Bone(boneName);
bones.add(bone);
boneOMAs.put(bone, boneOMA);
blenderContext.addLoadedFeatures(boneOMA, boneName, boneStructure, bone);
Matrix4f pose = this.restMatrix.clone();
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
Vector3f poseLocation = pose.toTranslationVector();
Quaternion rotation = pose.toRotationQuat();
Vector3f scale = objectHelper.getScale(pose);
bone.setBindTransforms(poseLocation, rotation, scale);
for (BoneContext child : children) {
bone.addChild(child.buildBone(bones, boneOMAs, blenderContext));
}
this.computePoseTransform();
return bone;
}
/**
* @return bone's pose transformation
*/
public Transform getPoseTransform() {
return poseTransform;
}
/**
* @return built bone (available after calling 'buildBone' method)
*/
public Bone getBone() {
return bone;
}
/**
* @return the old memory address of the bone
*/
public Long getBoneOma() {
return boneStructure.getOldMemoryAddress();
}
/**
* @return the length of the bone
*/
public float getLength() {
return length;
}
/**
* @return OMA of the bone's armature object
*/
public Long getArmatureObjectOMA() {
return armatureObjectOMA;
}
}

@ -13,116 +13,116 @@ import java.util.Arrays;
* @author Marcin Roguski (Kaelthas)
*/
public class CalculationBone extends Node {
private Bone bone;
/** The bone's tracks. Will be altered at the end of calculation process. */
private BoneTrack track;
/** The starting position of the bone. */
private Vector3f startTranslation;
/** The starting rotation of the bone. */
private Quaternion startRotation;
/** The starting scale of the bone. */
private Vector3f startScale;
private Vector3f[] translations;
private Quaternion[] rotations;
private Vector3f[] scales;
private Bone bone;
/** The bone's tracks. Will be altered at the end of calculation process. */
private BoneTrack track;
/** The starting position of the bone. */
private Vector3f startTranslation;
/** The starting rotation of the bone. */
private Quaternion startRotation;
/** The starting scale of the bone. */
private Vector3f startScale;
private Vector3f[] translations;
private Quaternion[] rotations;
private Vector3f[] scales;
public CalculationBone(Bone bone, int boneFramesCount) {
this.bone = bone;
this.startRotation = bone.getModelSpaceRotation().clone();
this.startTranslation = bone.getModelSpacePosition().clone();
this.startScale = bone.getModelSpaceScale().clone();
this.reset();
if(boneFramesCount > 0) {
this.translations = new Vector3f[boneFramesCount];
this.rotations = new Quaternion[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 CalculationBone(Bone bone, int boneFramesCount) {
this.bone = bone;
this.startRotation = bone.getModelSpaceRotation().clone();
this.startTranslation = bone.getModelSpacePosition().clone();
this.startScale = bone.getModelSpaceScale().clone();
this.reset();
if (boneFramesCount > 0) {
this.translations = new Vector3f[boneFramesCount];
this.rotations = new Quaternion[boneFramesCount];
this.scales = new Vector3f[boneFramesCount];
public int getBoneFramesCount() {
return this.translations==null ? 0 : this.translations.length;
}
/**
* 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());
}
}
Arrays.fill(this.translations, 0, boneFramesCount, this.startTranslation);
Arrays.fill(this.rotations, 0, boneFramesCount, this.startRotation);
Arrays.fill(this.scales, 0, boneFramesCount, this.startScale);
}
}
/**
* This method resets the calculation bone to the starting position.
*/
public void reset() {
this.setLocalTranslation(startTranslation);
this.setLocalRotation(startRotation);
this.setLocalScale(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();
}
@Override
public int attachChild(Spatial child) {
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 int getBoneFramesCount() {
return this.translations == null ? 0 : this.translations.length;
}
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;
}
/**
* 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());
}
}
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();
}
}
/**
* This method resets the calculation bone to the starting position.
*/
public void reset() {
this.setLocalTranslation(startTranslation);
this.setLocalRotation(startRotation);
this.setLocalScale(startScale);
}
@Override
public String toString() {
return bone.getName() + ": " + this.getLocalRotation() + " " + this.getLocalTranslation();
}
@Override
public int attachChild(Spatial child) {
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
*/
public class Ipo {
private static final Logger LOGGER = Logger.getLogger(Ipo.class.getName());
public static final int AC_LOC_X = 1;
public static final int AC_LOC_Y = 2;
public static final int AC_LOC_Z = 3;
public static final int OB_ROT_X = 7;
public static final int OB_ROT_Y = 8;
public static final int OB_ROT_Z = 9;
public static final int AC_SIZE_X = 13;
public static final int AC_SIZE_Y = 14;
public static final int AC_SIZE_Z = 15;
public static final int AC_QUAT_W = 25;
public static final int AC_QUAT_X = 26;
public static final int AC_QUAT_Y = 27;
public static final int AC_QUAT_Z = 28;
/** A list of bezier curves for this interpolation object. */
private BezierCurve[] bezierCurves;
/** Each ipo contains one bone track. */
private Track calculatedTrack;
/** This variable indicates if the Y asxis is the UP axis or not. */
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. */
protected final int blenderVersion;
/**
* Constructor. Stores the bezier curves.
*
* @param bezierCurves
* a table of bezier curves
* @param fixUpAxis
* indicates if the Y is the up axis or not
* @param blenderVersion
* the blender version that is currently used
*/
public Ipo(BezierCurve[] bezierCurves, boolean fixUpAxis, int blenderVersion) {
this.bezierCurves = bezierCurves;
this.fixUpAxis = fixUpAxis;
this.blenderVersion = blenderVersion;
}
/**
* This method calculates the ipo value for the first curve.
*
* @param frame
* the frame for which the value is calculated
* @return calculated ipo value
*/
public float calculateValue(int frame) {
return this.calculateValue(frame, 0);
}
/**
* 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
* amount of curves before calling this method.
*
* @param frame
* the frame for which the value is calculated
* @param curveIndex
* the index of the curve
* @return calculated ipo value
*/
public float calculateValue(int frame, int curveIndex) {
return bezierCurves[curveIndex].evaluate(frame, BezierCurve.Y_VALUE);
}
/**
* This method returns the curves amount.
*
* @return the curves amount
*/
public int getCurvesAmount() {
return bezierCurves.length;
}
/**
* This method returns the frame where last bezier triple center point of
* the specified bezier curve is located.
*
* @return the frame number of the last defined bezier triple point for the
* specified ipo
*/
public int getLastFrame() {
int result = 1;
for (int i = 0; i < bezierCurves.length; ++i) {
int tempResult = bezierCurves[i].getLastFrame();
if (tempResult > result) {
result = tempResult;
}
}
return result;
}
/**
* This method calculates the value of the curves as a bone track between
* the specified frames.
*
* @param targetIndex
* the index of the target for which the method calculates the
* tracks IMPORTANT! Aet to -1 (or any negative number) if you
* want to load spatial animation.
* @param localQuaternionRotation
* the local rotation of the object/bone that will be animated by
* the track
* @param startFrame
* the firs frame of tracks (inclusive)
* @param stopFrame
* the last frame of the tracks (inclusive)
* @param fps
* frame rate (frames per second)
* @param spatialTrack
* this flag indicates if the track belongs to a spatial or to a
* bone; the diference is important because it appears that bones
* in blender have the same type of coordinate system (Y as UP)
* as jme while other features have different one (Z is UP)
* @return bone track for the specified bone
*/
public Track calculateTrack(int targetIndex, Quaternion localQuaternionRotation, int startFrame, int stopFrame, int fps, boolean spatialTrack) {
if (calculatedTrack == null) {
// preparing data for track
int framesAmount = stopFrame - startFrame;
float timeBetweenFrames = 1.0f / fps;
float[] times = new float[framesAmount + 1];
Vector3f[] translations = new Vector3f[framesAmount + 1];
float[] translation = new float[3];
Quaternion[] rotations = new Quaternion[framesAmount + 1];
float[] quaternionRotation = new float[] { 0, 0, 0, 1 };
float[] objectRotation = new float[3];
Vector3f[] scales = new Vector3f[framesAmount + 1];
float[] scale = new float[] { 1.0f, 1.0f, 1.0f };
float degreeToRadiansFactor = 1;
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
}
// calculating track data
for (int frame = startFrame; frame <= stopFrame; ++frame) {
int index = frame - startFrame;
times[index] = index * timeBetweenFrames;//start + (frame - 1) * timeBetweenFrames;
for (int j = 0; j < bezierCurves.length; ++j) {
double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE);
switch (bezierCurves[j].getType()) {
// LOCATION
case AC_LOC_X:
translation[0] = (float) value;
break;
case AC_LOC_Y:
if (fixUpAxis) {
translation[2] = (float) -value;
} else {
translation[1] = (float) value;
}
break;
case AC_LOC_Z:
translation[fixUpAxis ? 1 : 2] = (float) value;
break;
// ROTATION (used with object animation)
// the value here is in degrees divided by 10 (so in
// example: 9 = PI/2)
case OB_ROT_X:
objectRotation[0] = (float) value * degreeToRadiansFactor;
break;
case OB_ROT_Y:
if (fixUpAxis) {
objectRotation[2] = (float) -value * degreeToRadiansFactor;
} else {
objectRotation[1] = (float) value * degreeToRadiansFactor;
}
break;
case OB_ROT_Z:
objectRotation[fixUpAxis ? 1 : 2] = (float) value * degreeToRadiansFactor;
break;
// SIZE
case AC_SIZE_X:
scale[0] = (float) value;
break;
case AC_SIZE_Y:
if (fixUpAxis) {
scale[2] = (float) value;
} else {
scale[1] = (float) value;
}
break;
case AC_SIZE_Z:
scale[fixUpAxis ? 1 : 2] = (float) value;
break;
// QUATERNION ROTATION (used with bone animation), dunno
// why but here we shouldn't check the
// spatialTrack flag value
case AC_QUAT_W:
quaternionRotation[3] = (float) value;
break;
case AC_QUAT_X:
quaternionRotation[0] = (float) value;
break;
case AC_QUAT_Y:
if (fixUpAxis) {
quaternionRotation[2] = -(float) value;
} else {
quaternionRotation[1] = (float) value;
}
break;
case AC_QUAT_Z:
if (fixUpAxis) {
quaternionRotation[1] = (float) value;
} else {
quaternionRotation[2] = (float) value;
}
break;
default:
LOGGER.warning("Unknown ipo curve type: " + bezierCurves[j].getType());
}
}
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]);
scales[index] = new Vector3f(scale[0], scale[1], scale[2]);
}
if (spatialTrack) {
calculatedTrack = new SpatialTrack(times, translations, rotations, scales);
} else {
calculatedTrack = new BoneTrack(targetIndex, times, translations, rotations, scales);
}
}
return calculatedTrack;
}
private static final Logger LOGGER = Logger.getLogger(Ipo.class.getName());
public static final int AC_LOC_X = 1;
public static final int AC_LOC_Y = 2;
public static final int AC_LOC_Z = 3;
public static final int OB_ROT_X = 7;
public static final int OB_ROT_Y = 8;
public static final int OB_ROT_Z = 9;
public static final int AC_SIZE_X = 13;
public static final int AC_SIZE_Y = 14;
public static final int AC_SIZE_Z = 15;
public static final int AC_QUAT_W = 25;
public static final int AC_QUAT_X = 26;
public static final int AC_QUAT_Y = 27;
public static final int AC_QUAT_Z = 28;
/** A list of bezier curves for this interpolation object. */
private BezierCurve[] bezierCurves;
/** Each ipo contains one bone track. */
private Track calculatedTrack;
/** This variable indicates if the Y asxis is the UP axis or not. */
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. */
protected final int blenderVersion;
/**
* Constructor. Stores the bezier curves.
*
* @param bezierCurves
* a table of bezier curves
* @param fixUpAxis
* indicates if the Y is the up axis or not
* @param blenderVersion
* the blender version that is currently used
*/
public Ipo(BezierCurve[] bezierCurves, boolean fixUpAxis, int blenderVersion) {
this.bezierCurves = bezierCurves;
this.fixUpAxis = fixUpAxis;
this.blenderVersion = blenderVersion;
}
/**
* This method calculates the ipo value for the first curve.
*
* @param frame
* the frame for which the value is calculated
* @return calculated ipo value
*/
public float calculateValue(int frame) {
return this.calculateValue(frame, 0);
}
/**
* 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
* amount of curves before calling this method.
*
* @param frame
* the frame for which the value is calculated
* @param curveIndex
* the index of the curve
* @return calculated ipo value
*/
public float calculateValue(int frame, int curveIndex) {
return bezierCurves[curveIndex].evaluate(frame, BezierCurve.Y_VALUE);
}
/**
* This method returns the curves amount.
*
* @return the curves amount
*/
public int getCurvesAmount() {
return bezierCurves.length;
}
/**
* This method returns the frame where last bezier triple center point of
* the specified bezier curve is located.
*
* @return the frame number of the last defined bezier triple point for the
* specified ipo
*/
public int getLastFrame() {
int result = 1;
for (int i = 0; i < bezierCurves.length; ++i) {
int tempResult = bezierCurves[i].getLastFrame();
if (tempResult > result) {
result = tempResult;
}
}
return result;
}
/**
* This method calculates the value of the curves as a bone track between
* the specified frames.
*
* @param targetIndex
* the index of the target for which the method calculates the
* tracks IMPORTANT! Aet to -1 (or any negative number) if you
* want to load spatial animation.
* @param localQuaternionRotation
* the local rotation of the object/bone that will be animated by
* the track
* @param startFrame
* the firs frame of tracks (inclusive)
* @param stopFrame
* the last frame of the tracks (inclusive)
* @param fps
* frame rate (frames per second)
* @param spatialTrack
* this flag indicates if the track belongs to a spatial or to a
* bone; the diference is important because it appears that bones
* in blender have the same type of coordinate system (Y as UP)
* as jme while other features have different one (Z is UP)
* @return bone track for the specified bone
*/
public Track calculateTrack(int targetIndex, Quaternion localQuaternionRotation, int startFrame, int stopFrame, int fps, boolean spatialTrack) {
if (calculatedTrack == null) {
// preparing data for track
int framesAmount = stopFrame - startFrame;
float timeBetweenFrames = 1.0f / fps;
float[] times = new float[framesAmount + 1];
Vector3f[] translations = new Vector3f[framesAmount + 1];
float[] translation = new float[3];
Quaternion[] rotations = new Quaternion[framesAmount + 1];
float[] quaternionRotation = new float[] { 0, 0, 0, 1 };
float[] objectRotation = new float[3];
Vector3f[] scales = new Vector3f[framesAmount + 1];
float[] scale = new float[] { 1.0f, 1.0f, 1.0f };
float degreeToRadiansFactor = 1;
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
}
// calculating track data
for (int frame = startFrame; frame <= stopFrame; ++frame) {
int index = frame - startFrame;
times[index] = index * timeBetweenFrames;// start + (frame - 1) * timeBetweenFrames;
for (int j = 0; j < bezierCurves.length; ++j) {
double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE);
switch (bezierCurves[j].getType()) {
// LOCATION
case AC_LOC_X:
translation[0] = (float) value;
break;
case AC_LOC_Y:
if (fixUpAxis) {
translation[2] = (float) -value;
} else {
translation[1] = (float) value;
}
break;
case AC_LOC_Z:
translation[fixUpAxis ? 1 : 2] = (float) value;
break;
// ROTATION (used with object animation)
// the value here is in degrees divided by 10 (so in
// example: 9 = PI/2)
case OB_ROT_X:
objectRotation[0] = (float) value * degreeToRadiansFactor;
break;
case OB_ROT_Y:
if (fixUpAxis) {
objectRotation[2] = (float) -value * degreeToRadiansFactor;
} else {
objectRotation[1] = (float) value * degreeToRadiansFactor;
}
break;
case OB_ROT_Z:
objectRotation[fixUpAxis ? 1 : 2] = (float) value * degreeToRadiansFactor;
break;
// SIZE
case AC_SIZE_X:
scale[0] = (float) value;
break;
case AC_SIZE_Y:
if (fixUpAxis) {
scale[2] = (float) value;
} else {
scale[1] = (float) value;
}
break;
case AC_SIZE_Z:
scale[fixUpAxis ? 1 : 2] = (float) value;
break;
// QUATERNION ROTATION (used with bone animation), dunno
// why but here we shouldn't check the
// spatialTrack flag value
case AC_QUAT_W:
quaternionRotation[3] = (float) value;
break;
case AC_QUAT_X:
quaternionRotation[0] = (float) value;
break;
case AC_QUAT_Y:
if (fixUpAxis) {
quaternionRotation[2] = -(float) value;
} else {
quaternionRotation[1] = (float) value;
}
break;
case AC_QUAT_Z:
if (fixUpAxis) {
quaternionRotation[1] = (float) value;
} else {
quaternionRotation[2] = (float) value;
}
break;
default:
LOGGER.warning("Unknown ipo curve type: " + bezierCurves[j].getType());
}
}
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]);
scales[index] = new Vector3f(scale[0], scale[1], scale[2]);
}
if (spatialTrack) {
calculatedTrack = new SpatialTrack(times, translations, rotations, scales);
} else {
calculatedTrack = new BoneTrack(targetIndex, times, translations, rotations, scales);
}
}
return calculatedTrack;
}
}

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

@ -17,59 +17,59 @@ import java.util.logging.Logger;
*/
public class CameraHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(CameraHelper.class.getName());
protected static final int DEFAULT_CAM_WIDTH = 640;
protected static final int DEFAULT_CAM_HEIGHT = 480;
private static final Logger LOGGER = Logger.getLogger(CameraHelper.class.getName());
protected static final int DEFAULT_CAM_WIDTH = 640;
protected static final int DEFAULT_CAM_HEIGHT = 480;
/**
* 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
* the version read from the blend file
* @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) {
super(blenderVersion, fixUpAxis);
}
/**
* This method converts the given structure to jme camera.
*
* @param structure
* camera structure
* @return jme camera object
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
/**
* This method converts the given structure to jme camera.
*
* @param structure
* camera structure
* @return jme camera object
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
public CameraNode toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
if (blenderVersion >= 250) {
if (blenderVersion >= 250) {
return this.toCamera250(structure, blenderContext.getSceneStructure());
} 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+.
*
* @param structure
* camera structure
* @param sceneStructure
* scene structure
* @return jme camera object
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
/**
* This method converts the given structure to jme camera. Should be used form blender 2.5+.
*
* @param structure
* camera structure
* @param sceneStructure
* scene structure
* @return jme camera object
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
private CameraNode toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException {
int width = DEFAULT_CAM_WIDTH;
int height = DEFAULT_CAM_HEIGHT;
if (sceneStructure != null) {
Structure renderData = (Structure)sceneStructure.getFieldValue("r");
width = ((Number)renderData.getFieldValue("xsch")).shortValue();
height = ((Number)renderData.getFieldValue("ysch")).shortValue();
Structure renderData = (Structure) sceneStructure.getFieldValue("r");
width = ((Number) renderData.getFieldValue("xsch")).shortValue();
height = ((Number) renderData.getFieldValue("ysch")).shortValue();
}
Camera camera = new Camera(width, height);
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);
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);
float aspect = width / (float)height;
float aspect = width / (float) height;
float fovY; // Vertical field of view in degrees
float clipsta = ((Number) structure.getFieldValue("clipsta")).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.
float sensor = 32.0f;
boolean sensorVertical = false;
Number sensorFit = (Number)structure.getFieldValue("sensor_fit");
Number sensorFit = (Number) structure.getFieldValue("sensor_fit");
if (sensorFit != null) {
// If sensor_fit is vert (2), then sensor_y is used
sensorVertical = sensorFit.byteValue() == 2;
@ -113,17 +113,17 @@ public class CameraHelper extends AbstractBlenderHelper {
camera.setFrustumPerspective(fovY, aspect, clipsta, clipend);
return new CameraNode(null, camera);
}
/**
* This method converts the given structure to jme camera. Should be used form blender 2.49.
*
* @param structure
* camera structure
* @return jme camera object
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
* This method converts the given structure to jme camera. Should be used form blender 2.49.
*
* @param structure
* camera structure
* @return jme camera object
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
private CameraNode toCamera249(Structure structure) throws BlenderFileException {
Camera camera = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT);
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);
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);
float aspect = 0;
float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue();
@ -145,8 +145,8 @@ public class CameraHelper extends AbstractBlenderHelper {
return new CameraNode(null, camera);
}
@Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.CAMERAS) != 0;
}
@Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.CAMERAS) != 0;
}
}

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

@ -19,109 +19,109 @@ import com.jme3.scene.plugins.blender.file.Structure;
* @author Marcin Roguski (Kaelthas)
*/
public abstract class Constraint {
private static final Logger LOGGER = Logger.getLogger(Constraint.class.getName());
/** The name of this constraint. */
protected final String name;
/** Indicates if the constraint is already baked or not. */
protected boolean baked;
protected Space ownerSpace;
protected final ConstraintDefinition constraintDefinition;
protected Long ownerOMA;
protected Long targetOMA;
protected Space targetSpace;
protected String subtargetName;
/** The ipo object defining influence. */
protected final Ipo ipo;
/** The blender context. */
protected final BlenderContext blenderContext;
protected final ConstraintHelper constraintHelper;
/**
* This constructor creates the constraint instance.
*
* @param constraintStructure
* the constraint's structure (bConstraint clss in blender 2.49).
* @param ownerOMA
* the old memory address of the constraint owner
* @param influenceIpo
* the ipo curve of the influence factor
* @param blenderContext
* the blender context
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
public Constraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
this.blenderContext = blenderContext;
this.name = constraintStructure.getFieldValue("name").toString();
Pointer pData = (Pointer) constraintStructure.getFieldValue("data");
if (pData.isNotNull()) {
Structure data = pData.fetchData(blenderContext.getInputStream()).get(0);
constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, blenderContext);
Pointer pTar = (Pointer)data.getFieldValue("tar");
if(pTar!= null && pTar.isNotNull()) {
this.targetOMA = pTar.getOldMemoryAddress();
this.targetSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("tarspace")).byteValue());
Object subtargetValue = data.getFieldValue("subtarget");
if(subtargetValue != null) {//not all constraint data have the subtarget field
subtargetName = subtargetValue.toString();
}
}
} else {
//Null constraint has no data, so create it here
constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, blenderContext);
}
this.ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue());
this.ipo = influenceIpo;
this.ownerOMA = ownerOMA;
this.constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
}
/**
* 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
* order is kept.
*/
public void bake() {
if(!this.validate()) {
LOGGER.warning("The constraint " + name + " is invalid and will not be applied.");
} else if(!baked) {
if(targetOMA != null) {
List<Constraint> targetConstraints = blenderContext.getConstraints(targetOMA);
if(targetConstraints != null && targetConstraints.size() > 0) {
LOGGER.log(Level.FINE, "Baking target constraints of constraint: {0}", name);
for(Constraint targetConstraint : targetConstraints) {
targetConstraint.bake();
}
}
}
LOGGER.log(Level.FINE, "Performing baking of constraint: {0}", name);
this.performBakingOperation();
baked = true;
}
}
/**
* Performs validation before baking. Checks factors that can prevent constraint from baking that could not be
* checked during constraint loading.
*/
protected abstract boolean validate();
/**
* This method should be overwridden and perform the baking opertion.
*/
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 -
* 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
* 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();
private static final Logger LOGGER = Logger.getLogger(Constraint.class.getName());
/** The name of this constraint. */
protected final String name;
/** Indicates if the constraint is already baked or not. */
protected boolean baked;
protected Space ownerSpace;
protected final ConstraintDefinition constraintDefinition;
protected Long ownerOMA;
protected Long targetOMA;
protected Space targetSpace;
protected String subtargetName;
/** The ipo object defining influence. */
protected final Ipo ipo;
/** The blender context. */
protected final BlenderContext blenderContext;
protected final ConstraintHelper constraintHelper;
/**
* This constructor creates the constraint instance.
*
* @param constraintStructure
* the constraint's structure (bConstraint clss in blender 2.49).
* @param ownerOMA
* the old memory address of the constraint owner
* @param influenceIpo
* the ipo curve of the influence factor
* @param blenderContext
* the blender context
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
public Constraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
this.blenderContext = blenderContext;
this.name = constraintStructure.getFieldValue("name").toString();
Pointer pData = (Pointer) constraintStructure.getFieldValue("data");
if (pData.isNotNull()) {
Structure data = pData.fetchData(blenderContext.getInputStream()).get(0);
constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, blenderContext);
Pointer pTar = (Pointer) data.getFieldValue("tar");
if (pTar != null && pTar.isNotNull()) {
this.targetOMA = pTar.getOldMemoryAddress();
this.targetSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("tarspace")).byteValue());
Object subtargetValue = data.getFieldValue("subtarget");
if (subtargetValue != null) {// not all constraint data have the subtarget field
subtargetName = subtargetValue.toString();
}
}
} else {
// Null constraint has no data, so create it here
constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, blenderContext);
}
this.ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue());
this.ipo = influenceIpo;
this.ownerOMA = ownerOMA;
this.constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
}
/**
* 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
* order is kept.
*/
public void bake() {
if (!this.validate()) {
LOGGER.warning("The constraint " + name + " is invalid and will not be applied.");
} else if (!baked) {
if (targetOMA != null) {
List<Constraint> targetConstraints = blenderContext.getConstraints(targetOMA);
if (targetConstraints != null && targetConstraints.size() > 0) {
LOGGER.log(Level.FINE, "Baking target constraints of constraint: {0}", name);
for (Constraint targetConstraint : targetConstraints) {
targetConstraint.bake();
}
}
}
LOGGER.log(Level.FINE, "Performing baking of constraint: {0}", name);
this.performBakingOperation();
baked = true;
}
}
/**
* Performs validation before baking. Checks factors that can prevent constraint from baking that could not be
* checked during constraint loading.
*/
protected abstract boolean validate();
/**
* This method should be overwridden and perform the baking opertion.
*/
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 -
* 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
* 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();
}

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

@ -15,23 +15,24 @@ import com.jme3.scene.plugins.blender.file.Structure;
*
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class SkeletonConstraint extends Constraint {
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);
}
/* package */class SkeletonConstraint extends Constraint {
private static final Logger LOGGER = Logger.getLogger(SkeletonConstraint.class.getName());
@Override
public void performBakingOperation() {
LOGGER.warning("Applying constraints to skeleton is not supported.");
}
public SkeletonConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
super(constraintStructure, ownerOMA, influenceIpo, blenderContext);
}
@Override
protected boolean validate() {
return true;
}
@Override
protected void prepareTracksForApplyingConstraints() { }
@Override
public void performBakingOperation() {
LOGGER.warning("Applying constraints to skeleton is not supported.");
}
@Override
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class SpatialConstraint extends BoneConstraint {
private static final Logger LOGGER = Logger.getLogger(SpatialConstraint.class.getName());
/** The owner of the constraint. */
private Spatial owner;
/** The target of the constraint. */
private Object target;
public SpatialConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext)
throws BlenderFileException {
super(constraintStructure, ownerOMA, influenceIpo, blenderContext);
}
@Override
public void performBakingOperation() {
this.owner = (Spatial) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
this.target = targetOMA != null ? blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE) : null;
this.prepareTracksForApplyingConstraints();
//apply static constraint
Transform ownerTransform = constraintHelper.getNodeObjectTransform(ownerSpace, ownerOMA, blenderContext);
Transform targetTransform = targetOMA != null ? constraintHelper.getNodeObjectTransform(targetSpace, targetOMA, blenderContext) : null;
constraintDefinition.bake(ownerTransform, targetTransform, null, null, this.ipo);
constraintHelper.applyTransform(owner, ownerSpace, ownerTransform);
//apply dynamic constraint
AnimData animData = blenderContext.getAnimData(ownerOMA);
if(animData != null) {
for(Animation animation : animData.anims) {
SpatialTrack ownerTrack = constraintHelper.getTrack(owner, animation);
AnimData targetAnimData = blenderContext.getAnimData(targetOMA);
SpatialTrack targetTrack = null;
if(targetAnimData != null) {
targetTrack = constraintHelper.getTrack((Spatial)target, targetAnimData.anims.get(0));
}
constraintDefinition.bake(ownerTransform, targetTransform, ownerTrack, targetTrack, this.ipo);
}
}
}
@Override
protected void prepareTracksForApplyingConstraints() {
Long[] spatialsOMAs = new Long[] { ownerOMA, targetOMA };
Space[] spaces = new Space[] { ownerSpace, targetSpace };
//creating animations for current objects if at least on of their parents have an animation
for (int i = 0; i < spatialsOMAs.length; ++i) {
Long oma = spatialsOMAs[i];
if(oma != null && oma > 0L) {
AnimData animData = blenderContext.getAnimData(oma);
if(animData == null) {
Spatial currentSpatial = (Spatial)blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE);
if(currentSpatial != null) {
if(currentSpatial.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) == Boolean.TRUE) {//look for it among bones
BoneContext currentBoneContext = blenderContext.getBoneByName(subtargetName);
Bone currentBone = currentBoneContext.getBone();
Bone parent = currentBone.getParent();
boolean foundAnimation = false;
while(parent != null && !foundAnimation) {
BoneContext boneContext = blenderContext.getBoneByName(parent.getName());
foundAnimation = this.hasAnimation(boneContext.getBoneOma());
animData = blenderContext.getAnimData(boneContext.getBoneOma());
parent = parent.getParent();
}
if(foundAnimation) {
this.applyAnimData(currentBoneContext, spaces[i], animData);
}
} else {
Spatial parent = currentSpatial.getParent();
while(parent != null && animData == null) {
Structure parentStructure = (Structure)blenderContext.getLoadedFeature(parent.getName(), LoadedFeatureDataType.LOADED_STRUCTURE);
if(parentStructure == null) {
parent = null;
} else {
Long parentOma = parentStructure.getOldMemoryAddress();
animData = blenderContext.getAnimData(parentOma);
parent = parent.getParent();
}
}
if(animData != null) {//create anim data for the current object
this.applyAnimData(currentSpatial, oma, spaces[i], animData.anims.get(0));
}
}
} 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!");
}
}
}
}
//creating animation for owner if it doesn't have one already and if the target has it
AnimData animData = blenderContext.getAnimData(ownerOMA);
if(animData == null) {
AnimData targetAnimData = blenderContext.getAnimData(targetOMA);
if(targetAnimData != null) {
this.applyAnimData(owner, ownerOMA, ownerSpace, targetAnimData.anims.get(0));
}
}
}
/**
* This method applies spatial transform on each frame of the given
* animations.
*
* @param spatial
* the spatial
* @param spatialOma
* the OMA of the given spatial
* @param space
* the space we compute the transform in
* @param referenceAnimation
* the object containing the animations
*/
private void applyAnimData(Spatial spatial, Long spatialOma, Space space, Animation referenceAnimation) {
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
Transform transform = constraintHelper.getNodeObjectTransform(space, spatialOma, blenderContext);
SpatialTrack parentTrack = (SpatialTrack) referenceAnimation.getTracks()[0];
HashMap<String, Animation> anims = new HashMap<String, Animation>(1);
Animation animation = new Animation(spatial.getName(), referenceAnimation.getLength());
anims.put(spatial.getName(), animation);
float[] times = parentTrack.getTimes();
Vector3f[] translations = new Vector3f[times.length];
Quaternion[] rotations = new Quaternion[times.length];
Vector3f[] scales = new Vector3f[times.length];
Arrays.fill(translations, transform.getTranslation());
Arrays.fill(rotations, transform.getRotation());
Arrays.fill(scales, transform.getScale());
animation.addTrack(new SpatialTrack(times, translations, rotations, scales));
AnimControl control = new AnimControl(null);
control.setAnimations(anims);
spatial.addControl(control);
blenderContext.setAnimData(spatialOma, new AnimData(null, new ArrayList<Animation>(anims.values())));
}
/* package */class SpatialConstraint extends BoneConstraint {
private static final Logger LOGGER = Logger.getLogger(SpatialConstraint.class.getName());
/** The owner of the constraint. */
private Spatial owner;
/** The target of the constraint. */
private Object target;
public SpatialConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
super(constraintStructure, ownerOMA, influenceIpo, blenderContext);
}
@Override
public void performBakingOperation() {
this.owner = (Spatial) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
this.target = targetOMA != null ? blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE) : null;
this.prepareTracksForApplyingConstraints();
// apply static constraint
Transform ownerTransform = constraintHelper.getNodeObjectTransform(ownerSpace, ownerOMA, blenderContext);
Transform targetTransform = targetOMA != null ? constraintHelper.getNodeObjectTransform(targetSpace, targetOMA, blenderContext) : null;
constraintDefinition.bake(ownerTransform, targetTransform, null, null, this.ipo);
constraintHelper.applyTransform(owner, ownerSpace, ownerTransform);
// apply dynamic constraint
AnimData animData = blenderContext.getAnimData(ownerOMA);
if (animData != null) {
for (Animation animation : animData.anims) {
SpatialTrack ownerTrack = constraintHelper.getTrack(owner, animation);
AnimData targetAnimData = blenderContext.getAnimData(targetOMA);
SpatialTrack targetTrack = null;
if (targetAnimData != null) {
targetTrack = constraintHelper.getTrack((Spatial) target, targetAnimData.anims.get(0));
}
constraintDefinition.bake(ownerTransform, targetTransform, ownerTrack, targetTrack, this.ipo);
}
}
}
@Override
protected void prepareTracksForApplyingConstraints() {
Long[] spatialsOMAs = new Long[] { ownerOMA, targetOMA };
Space[] spaces = new Space[] { ownerSpace, targetSpace };
// creating animations for current objects if at least on of their parents have an animation
for (int i = 0; i < spatialsOMAs.length; ++i) {
Long oma = spatialsOMAs[i];
if (oma != null && oma > 0L) {
AnimData animData = blenderContext.getAnimData(oma);
if (animData == null) {
Spatial currentSpatial = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE);
if (currentSpatial != null) {
if (currentSpatial.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) == Boolean.TRUE) {// look for it among bones
BoneContext currentBoneContext = blenderContext.getBoneByName(subtargetName);
Bone currentBone = currentBoneContext.getBone();
Bone parent = currentBone.getParent();
boolean foundAnimation = false;
while (parent != null && !foundAnimation) {
BoneContext boneContext = blenderContext.getBoneByName(parent.getName());
foundAnimation = this.hasAnimation(boneContext.getBoneOma());
animData = blenderContext.getAnimData(boneContext.getBoneOma());
parent = parent.getParent();
}
if (foundAnimation) {
this.applyAnimData(currentBoneContext, spaces[i], animData);
}
} else {
Spatial parent = currentSpatial.getParent();
while (parent != null && animData == null) {
Structure parentStructure = (Structure) blenderContext.getLoadedFeature(parent.getName(), LoadedFeatureDataType.LOADED_STRUCTURE);
if (parentStructure == null) {
parent = null;
} else {
Long parentOma = parentStructure.getOldMemoryAddress();
animData = blenderContext.getAnimData(parentOma);
parent = parent.getParent();
}
}
if (animData != null) {// create anim data for the current object
this.applyAnimData(currentSpatial, oma, spaces[i], animData.anims.get(0));
}
}
} 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!");
}
}
}
}
// creating animation for owner if it doesn't have one already and if the target has it
AnimData animData = blenderContext.getAnimData(ownerOMA);
if (animData == null) {
AnimData targetAnimData = blenderContext.getAnimData(targetOMA);
if (targetAnimData != null) {
this.applyAnimData(owner, ownerOMA, ownerSpace, targetAnimData.anims.get(0));
}
}
}
/**
* This method applies spatial transform on each frame of the given
* animations.
*
* @param spatial
* the spatial
* @param spatialOma
* the OMA of the given spatial
* @param space
* the space we compute the transform in
* @param referenceAnimation
* the object containing the animations
*/
private void applyAnimData(Spatial spatial, Long spatialOma, Space space, Animation referenceAnimation) {
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
Transform transform = constraintHelper.getNodeObjectTransform(space, spatialOma, blenderContext);
SpatialTrack parentTrack = (SpatialTrack) referenceAnimation.getTracks()[0];
HashMap<String, Animation> anims = new HashMap<String, Animation>(1);
Animation animation = new Animation(spatial.getName(), referenceAnimation.getLength());
anims.put(spatial.getName(), animation);
float[] times = parentTrack.getTimes();
Vector3f[] translations = new Vector3f[times.length];
Quaternion[] rotations = new Quaternion[times.length];
Vector3f[] scales = new Vector3f[times.length];
Arrays.fill(translations, transform.getTranslation());
Arrays.fill(rotations, transform.getRotation());
Arrays.fill(scales, transform.getScale());
animation.addTrack(new SpatialTrack(times, translations, rotations, scales));
AnimControl control = new AnimControl(null);
control.setAnimations(anims);
spatial.addControl(control);
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;
public abstract class ConstraintDefinition {
protected int flag;
public ConstraintDefinition(Structure constraintData, BlenderContext blenderContext) {
if(constraintData != null) {//Null constraint has no data
Number flag = (Number)constraintData.getFieldValue("flag");
if(flag != null) {
this.flag = flag.intValue();
}
}
}
public void bake(Transform ownerTransform, Transform targetTransform, Track ownerTrack, Track targetTrack, Ipo influenceIpo) {
TrackWrapper ownerWrapperTrack = ownerTrack != null ? new TrackWrapper(ownerTrack) : null;
TrackWrapper targetWrapperTrack = targetTrack != null ? new TrackWrapper(targetTrack) : null;
//uruchamiamy bake dla transformat zalenie od tego, ktre argumenty s nullami, a ktre - nie
this.bake(ownerTransform, targetTransform, influenceIpo.calculateValue(0));
if(ownerWrapperTrack != null) {
float[] ownerTimes = ownerWrapperTrack.getTimes();
Vector3f[] translations = ownerWrapperTrack.getTranslations();
Quaternion[] rotations = ownerWrapperTrack.getRotations();
Vector3f[] scales = ownerWrapperTrack.getScales();
float[] targetTimes = targetWrapperTrack == null ? null : targetWrapperTrack.getTimes();
Vector3f[] targetTranslations = targetWrapperTrack == null ? null : targetWrapperTrack.getTranslations();
Quaternion[] targetRotations = targetWrapperTrack == null ? null : targetWrapperTrack.getRotations();
Vector3f[] targetScales = targetWrapperTrack == null ? null : targetWrapperTrack.getScales();
Vector3f translation = new Vector3f(), scale = new Vector3f();
Quaternion rotation = new Quaternion();
Transform ownerTemp = new Transform(), targetTemp = new Transform();
for (int i = 0; i <ownerTimes.length; ++i) {
float t = ownerTimes[i];
ownerTemp.setTranslation(translations[i]);
ownerTemp.setRotation(rotations[i]);
ownerTemp.setScale(scales[i]);
if(targetWrapperTrack == null) {
this.bake(ownerTemp, targetTransform, influenceIpo.calculateValue(i));
} else {
//getting the values that are the interpolation of the target track for the time 't'
this.interpolate(targetTranslations, targetTimes, t, translation);
this.interpolate(targetRotations, targetTimes, t, rotation);
this.interpolate(targetScales, targetTimes, t, scale);
targetTemp.setTranslation(translation);
targetTemp.setRotation(rotation);
targetTemp.setScale(scale);
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
translations[i] = ownerTemp.getTranslation().clone();
rotations[i] = ownerTemp.getRotation().clone();
scales[i] = ownerTemp.getScale().clone();
}
ownerWrapperTrack.setKeyframes(ownerTimes, translations, rotations, scales);
}
}
protected abstract void bake(Transform ownerTransform, Transform targetTransform, float influence);
private void interpolate(Vector3f[] targetVectors, float[] targetTimes, float currentTime, Vector3f result) {
int index = 0;
for (int i = 1; i < targetTimes.length; ++i) {
if(targetTimes[i] < currentTime) {
++index;
} else {
break;
}
}
if(index >= targetTimes.length - 1) {
result.set(targetVectors[targetTimes.length - 1]);
} else {
float delta = targetTimes[index + 1] - targetTimes[index];
if(delta == 0.0f) {
result.set(targetVectors[index + 1]);
} else {
float scale = (currentTime - targetTimes[index])/(targetTimes[index + 1] - targetTimes[index]);
FastMath.interpolateLinear(scale, targetVectors[index], targetVectors[index + 1], result);
}
}
}
private void interpolate(Quaternion[] targetQuaternions, float[] targetTimes, float currentTime, Quaternion result) {
int index = 0;
for (int i = 1; i < targetTimes.length; ++i) {
if(targetTimes[i] < currentTime) {
++index;
} else {
break;
}
}
if(index >= targetTimes.length - 1) {
result.set(targetQuaternions[targetTimes.length - 1]);
} else {
float delta = targetTimes[index + 1] - targetTimes[index];
if(delta == 0.0f) {
result.set(targetQuaternions[index + 1]);
} else {
float scale = (currentTime - targetTimes[index])/(targetTimes[index + 1] - targetTimes[index]);
result.slerp(targetQuaternions[index], targetQuaternions[index + 1], scale);
}
}
}
/**
* This class holds either the bone track or spatial track. Is made to improve
* code readability.
*
* @author Marcin Roguski (Kaelthas)
*/
private static class TrackWrapper implements Track {
/** The spatial track. */
private SpatialTrack spatialTrack;
/** The bone track. */
private BoneTrack boneTrack;
/**
* Constructs the object using the given track. The track must be of one of the types:
* <li> BoneTrack
* <li> SpatialTrack
*
* @param track
* the animation track
*/
public TrackWrapper(Track track) {
if(track instanceof SpatialTrack) {
this.spatialTrack = (SpatialTrack)track;
} else if(track instanceof BoneTrack) {
this.boneTrack = (BoneTrack)track;
} else {
throw new IllegalStateException("Unknown track type!");
}
}
/**
* @return the array of rotations of this track
*/
public Quaternion[] getRotations() {
if (boneTrack != null) {
return boneTrack.getRotations();
}
return spatialTrack.getRotations();
}
/**
* @return the array of scales for this track
*/
public Vector3f[] getScales() {
if (boneTrack != null) {
return boneTrack.getScales();
}
return spatialTrack.getScales();
}
/**
* @return the arrays of time for this track
*/
public float[] getTimes() {
if (boneTrack != null) {
return boneTrack.getTimes();
}
return spatialTrack.getTimes();
}
/**
* @return the array of translations of this track
*/
public Vector3f[] getTranslations() {
if (boneTrack != null) {
return boneTrack.getTranslations();
}
return spatialTrack.getTranslations();
}
/**
* Set the translations, rotations and scales for this bone track
*
* @param times
* a float array with the time of each frame
* @param translations
* the translation of the bone for each frame
* @param rotations
* the rotation 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) {
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 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);
} else {
spatialTrack.setTime(time, weight, control, channel, vars);
}
}
public float getLength() {
return spatialTrack == null ? boneTrack.getLength() : spatialTrack
.getLength();
}
@Override
public TrackWrapper clone() {
if (boneTrack != null) {
return new TrackWrapper(boneTrack.clone());
}
return new TrackWrapper(spatialTrack.clone());
}
}
protected int flag;
public ConstraintDefinition(Structure constraintData, BlenderContext blenderContext) {
if (constraintData != null) {// Null constraint has no data
Number flag = (Number) constraintData.getFieldValue("flag");
if (flag != null) {
this.flag = flag.intValue();
}
}
}
public void bake(Transform ownerTransform, Transform targetTransform, Track ownerTrack, Track targetTrack, Ipo influenceIpo) {
TrackWrapper ownerWrapperTrack = ownerTrack != null ? new TrackWrapper(ownerTrack) : null;
TrackWrapper targetWrapperTrack = targetTrack != null ? new TrackWrapper(targetTrack) : null;
// uruchamiamy bake dla transformat zalenie od tego, ktre argumenty s nullami, a ktre - nie
this.bake(ownerTransform, targetTransform, influenceIpo.calculateValue(0));
if (ownerWrapperTrack != null) {
float[] ownerTimes = ownerWrapperTrack.getTimes();
Vector3f[] translations = ownerWrapperTrack.getTranslations();
Quaternion[] rotations = ownerWrapperTrack.getRotations();
Vector3f[] scales = ownerWrapperTrack.getScales();
float[] targetTimes = targetWrapperTrack == null ? null : targetWrapperTrack.getTimes();
Vector3f[] targetTranslations = targetWrapperTrack == null ? null : targetWrapperTrack.getTranslations();
Quaternion[] targetRotations = targetWrapperTrack == null ? null : targetWrapperTrack.getRotations();
Vector3f[] targetScales = targetWrapperTrack == null ? null : targetWrapperTrack.getScales();
Vector3f translation = new Vector3f(), scale = new Vector3f();
Quaternion rotation = new Quaternion();
Transform ownerTemp = new Transform(), targetTemp = new Transform();
for (int i = 0; i < ownerTimes.length; ++i) {
float t = ownerTimes[i];
ownerTemp.setTranslation(translations[i]);
ownerTemp.setRotation(rotations[i]);
ownerTemp.setScale(scales[i]);
if (targetWrapperTrack == null) {
this.bake(ownerTemp, targetTransform, influenceIpo.calculateValue(i));
} else {
// getting the values that are the interpolation of the target track for the time 't'
this.interpolate(targetTranslations, targetTimes, t, translation);
this.interpolate(targetRotations, targetTimes, t, rotation);
this.interpolate(targetScales, targetTimes, t, scale);
targetTemp.setTranslation(translation);
targetTemp.setRotation(rotation);
targetTemp.setScale(scale);
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
translations[i] = ownerTemp.getTranslation().clone();
rotations[i] = ownerTemp.getRotation().clone();
scales[i] = ownerTemp.getScale().clone();
}
ownerWrapperTrack.setKeyframes(ownerTimes, translations, rotations, scales);
}
}
protected abstract void bake(Transform ownerTransform, Transform targetTransform, float influence);
private void interpolate(Vector3f[] targetVectors, float[] targetTimes, float currentTime, Vector3f result) {
int index = 0;
for (int i = 1; i < targetTimes.length; ++i) {
if (targetTimes[i] < currentTime) {
++index;
} else {
break;
}
}
if (index >= targetTimes.length - 1) {
result.set(targetVectors[targetTimes.length - 1]);
} else {
float delta = targetTimes[index + 1] - targetTimes[index];
if (delta == 0.0f) {
result.set(targetVectors[index + 1]);
} else {
float scale = (currentTime - targetTimes[index]) / (targetTimes[index + 1] - targetTimes[index]);
FastMath.interpolateLinear(scale, targetVectors[index], targetVectors[index + 1], result);
}
}
}
private void interpolate(Quaternion[] targetQuaternions, float[] targetTimes, float currentTime, Quaternion result) {
int index = 0;
for (int i = 1; i < targetTimes.length; ++i) {
if (targetTimes[i] < currentTime) {
++index;
} else {
break;
}
}
if (index >= targetTimes.length - 1) {
result.set(targetQuaternions[targetTimes.length - 1]);
} else {
float delta = targetTimes[index + 1] - targetTimes[index];
if (delta == 0.0f) {
result.set(targetQuaternions[index + 1]);
} else {
float scale = (currentTime - targetTimes[index]) / (targetTimes[index + 1] - targetTimes[index]);
result.slerp(targetQuaternions[index], targetQuaternions[index + 1], scale);
}
}
}
/**
* This class holds either the bone track or spatial track. Is made to improve
* code readability.
*
* @author Marcin Roguski (Kaelthas)
*/
private static class TrackWrapper implements Track {
/** The spatial track. */
private SpatialTrack spatialTrack;
/** The bone track. */
private BoneTrack boneTrack;
/**
* Constructs the object using the given track. The track must be of one of the types: <li>BoneTrack <li>SpatialTrack
*
* @param track
* the animation track
*/
public TrackWrapper(Track track) {
if (track instanceof SpatialTrack) {
this.spatialTrack = (SpatialTrack) track;
} else if (track instanceof BoneTrack) {
this.boneTrack = (BoneTrack) track;
} else {
throw new IllegalStateException("Unknown track type!");
}
}
/**
* @return the array of rotations of this track
*/
public Quaternion[] getRotations() {
if (boneTrack != null) {
return boneTrack.getRotations();
}
return spatialTrack.getRotations();
}
/**
* @return the array of scales for this track
*/
public Vector3f[] getScales() {
if (boneTrack != null) {
return boneTrack.getScales();
}
return spatialTrack.getScales();
}
/**
* @return the arrays of time for this track
*/
public float[] getTimes() {
if (boneTrack != null) {
return boneTrack.getTimes();
}
return spatialTrack.getTimes();
}
/**
* @return the array of translations of this track
*/
public Vector3f[] getTranslations() {
if (boneTrack != null) {
return boneTrack.getTranslations();
}
return spatialTrack.getTranslations();
}
/**
* Set the translations, rotations and scales for this bone track
*
* @param times
* a float array with the time of each frame
* @param translations
* the translation of the bone for each frame
* @param rotations
* the rotation 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) {
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 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);
} else {
spatialTrack.setTime(time, weight, control, channel, vars);
}
}
public float getLength() {
return spatialTrack == null ? boneTrack.getLength() : spatialTrack.getLength();
}
@Override
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionAction extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName());
public ConstraintDefinitionAction(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
/* package */class ConstraintDefinitionAction extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName());
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Action' constraint
LOGGER.log(Level.WARNING, "'Action' constraint NOT implemented!");
}
public ConstraintDefinitionAction(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionCameraSolver extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName());
public ConstraintDefinitionCameraSolver(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
/* package */class ConstraintDefinitionCameraSolver extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName());
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Camera solver' constraint
LOGGER.log(Level.WARNING, "'Camera solver' constraint NOT implemented!");
}
public ConstraintDefinitionCameraSolver(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionChildOf extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionChildOf.class.getName());
public ConstraintDefinitionChildOf(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement ChildOf constraint
LOGGER.log(Level.WARNING, "ChildOf constraint NOT implemented!");
}
/* package */class ConstraintDefinitionChildOf extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionChildOf.class.getName());
public ConstraintDefinitionChildOf(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement ChildOf constraint
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionClampTo extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionClampTo.class.getName());
public ConstraintDefinitionClampTo(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
//TODO: implement when curves are implemented
LOGGER.log(Level.WARNING, "'Clamp to' not yet implemented!");
}
/* package */class ConstraintDefinitionClampTo extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionClampTo.class.getName());
public ConstraintDefinitionClampTo(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement when curves are 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+.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionDampTrack extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionDampTrack.class.getName());
public ConstraintDefinitionDampTrack(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO Auto-generated method stub
LOGGER.log(Level.WARNING, "'Damp Track' constraint NOT implemented!");
}
/* package */class ConstraintDefinitionDampTrack extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionDampTrack.class.getName());
public ConstraintDefinitionDampTrack(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO Auto-generated method stub
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionDistLimit extends ConstraintDefinition {
private static final int LIMITDIST_INSIDE = 0;
private static final int LIMITDIST_OUTSIDE = 1;
private static final int LIMITDIST_ONSURFACE = 2;
protected int mode;
protected float dist;
public ConstraintDefinitionDistLimit(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
mode = ((Number) constraintData.getFieldValue("mode")).intValue();
dist = ((Number) constraintData.getFieldValue("dist")).floatValue();
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
Vector3f v = ownerTransform.getTranslation().subtract(targetTransform.getTranslation());
float currentDistance = v.length();
switch (mode) {
case LIMITDIST_INSIDE:
if (currentDistance >= dist) {
v.normalizeLocal();
v.multLocal(dist + (currentDistance - dist) * (1.0f - influence));
ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation()));
}
break;
case LIMITDIST_ONSURFACE:
if (currentDistance > dist) {
v.normalizeLocal();
v.multLocal(dist + (currentDistance - dist) * (1.0f - influence));
ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation()));
} else if(currentDistance < dist) {
v.normalizeLocal().multLocal(dist * influence);
ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v));
}
break;
case LIMITDIST_OUTSIDE:
if (currentDistance <= dist) {
v = targetTransform.getTranslation().subtract(ownerTransform.getTranslation()).normalizeLocal().multLocal(dist * influence);
ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v));
}
break;
default:
throw new IllegalStateException("Unknown distance limit constraint mode: " + mode);
}
}
/* package */class ConstraintDefinitionDistLimit extends ConstraintDefinition {
private static final int LIMITDIST_INSIDE = 0;
private static final int LIMITDIST_OUTSIDE = 1;
private static final int LIMITDIST_ONSURFACE = 2;
protected int mode;
protected float dist;
public ConstraintDefinitionDistLimit(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
mode = ((Number) constraintData.getFieldValue("mode")).intValue();
dist = ((Number) constraintData.getFieldValue("dist")).floatValue();
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
Vector3f v = ownerTransform.getTranslation().subtract(targetTransform.getTranslation());
float currentDistance = v.length();
switch (mode) {
case LIMITDIST_INSIDE:
if (currentDistance >= dist) {
v.normalizeLocal();
v.multLocal(dist + (currentDistance - dist) * (1.0f - influence));
ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation()));
}
break;
case LIMITDIST_ONSURFACE:
if (currentDistance > dist) {
v.normalizeLocal();
v.multLocal(dist + (currentDistance - dist) * (1.0f - influence));
ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation()));
} else if (currentDistance < dist) {
v.normalizeLocal().multLocal(dist * influence);
ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v));
}
break;
case LIMITDIST_OUTSIDE:
if (currentDistance <= dist) {
v = targetTransform.getTranslation().subtract(ownerTransform.getTranslation()).normalizeLocal().multLocal(dist * influence);
ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v));
}
break;
default:
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;
public class ConstraintDefinitionFactory {
private static final Map<String, Class<? extends ConstraintDefinition>> CONSTRAINT_CLASSES = new HashMap<String, Class<? extends ConstraintDefinition>>();
static {
CONSTRAINT_CLASSES.put("bActionConstraint", ConstraintDefinitionAction.class);
CONSTRAINT_CLASSES.put("bChildOfConstraint", ConstraintDefinitionChildOf.class);
CONSTRAINT_CLASSES.put("bClampToConstraint", ConstraintDefinitionClampTo.class);
CONSTRAINT_CLASSES.put("bDistLimitConstraint", ConstraintDefinitionDistLimit.class);
CONSTRAINT_CLASSES.put("bFollowPathConstraint", ConstraintDefinitionFollowPath.class);
CONSTRAINT_CLASSES.put("bKinematicConstraint", ConstraintDefinitionInverseKinematics.class);
CONSTRAINT_CLASSES.put("bLockTrackConstraint", ConstraintDefinitionLockTrack.class);
CONSTRAINT_CLASSES.put("bLocateLikeConstraint", ConstraintDefinitionLocLike.class);
CONSTRAINT_CLASSES.put("bLocLimitConstraint", ConstraintDefinitionLocLimit.class);
CONSTRAINT_CLASSES.put("bMinMaxConstraint", ConstraintDefinitionMinMax.class);
CONSTRAINT_CLASSES.put("bNullConstraint", ConstraintDefinitionNull.class);
CONSTRAINT_CLASSES.put("bPythonConstraint", ConstraintDefinitionPython.class);
CONSTRAINT_CLASSES.put("bRigidBodyJointConstraint", ConstraintDefinitionRigidBodyJoint.class);
CONSTRAINT_CLASSES.put("bRotateLikeConstraint", ConstraintDefinitionRotLike.class);
CONSTRAINT_CLASSES.put("bShrinkWrapConstraint", ConstraintDefinitionShrinkWrap.class);
CONSTRAINT_CLASSES.put("bSizeLikeConstraint", ConstraintDefinitionSizeLike.class);
CONSTRAINT_CLASSES.put("bSizeLimitConstraint", ConstraintDefinitionSizeLimit.class);
CONSTRAINT_CLASSES.put("bStretchToConstraint", ConstraintDefinitionStretchTo.class);
CONSTRAINT_CLASSES.put("bTransformConstraint", ConstraintDefinitionTransform.class);
CONSTRAINT_CLASSES.put("bRotLimitConstraint", ConstraintDefinitionRotLimit.class);
//Blender 2.50+
CONSTRAINT_CLASSES.put("bSplineIKConstraint", ConstraintDefinitionSplineInverseKinematic.class);
CONSTRAINT_CLASSES.put("bDampTrackConstraint", ConstraintDefinitionDampTrack.class);
CONSTRAINT_CLASSES.put("bPivotConstraint", ConstraintDefinitionDampTrack.class);
//Blender 2.56+
CONSTRAINT_CLASSES.put("bTrackToConstraint", ConstraintDefinitionTrackTo.class);
CONSTRAINT_CLASSES.put("bSameVolumeConstraint", ConstraintDefinitionSameVolume.class);
CONSTRAINT_CLASSES.put("bTransLikeConstraint", ConstraintDefinitionTransLike.class);
//Blender 2.62+
CONSTRAINT_CLASSES.put("bCameraSolverConstraint", ConstraintDefinitionCameraSolver.class);
CONSTRAINT_CLASSES.put("bObjectSolverConstraint", ConstraintDefinitionObjectSolver.class);
CONSTRAINT_CLASSES.put("bFollowTrackConstraint", ConstraintDefinitionFollowTrack.class);
}
/**
* This method creates the constraint instance.
*
* @param constraintStructure
* the constraint's structure (bConstraint clss in blender 2.49). If the value is null the NullConstraint is created.
* @param blenderContext
* the blender context
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, BlenderContext blenderContext) throws BlenderFileException {
if(constraintStructure == null) {
return new ConstraintDefinitionNull(null, blenderContext);
}
String constraintClassName = constraintStructure.getType();
Class<? extends ConstraintDefinition> constraintDefinitionClass = CONSTRAINT_CLASSES.get(constraintClassName);
if(constraintDefinitionClass != null) {
try {
return (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, blenderContext);
} catch (IllegalArgumentException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (SecurityException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (InstantiationException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (IllegalAccessException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (InvocationTargetException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
}
} else {
throw new BlenderFileException("Unknown constraint type: " + constraintClassName);
}
}
private static final Map<String, Class<? extends ConstraintDefinition>> CONSTRAINT_CLASSES = new HashMap<String, Class<? extends ConstraintDefinition>>();
static {
CONSTRAINT_CLASSES.put("bActionConstraint", ConstraintDefinitionAction.class);
CONSTRAINT_CLASSES.put("bChildOfConstraint", ConstraintDefinitionChildOf.class);
CONSTRAINT_CLASSES.put("bClampToConstraint", ConstraintDefinitionClampTo.class);
CONSTRAINT_CLASSES.put("bDistLimitConstraint", ConstraintDefinitionDistLimit.class);
CONSTRAINT_CLASSES.put("bFollowPathConstraint", ConstraintDefinitionFollowPath.class);
CONSTRAINT_CLASSES.put("bKinematicConstraint", ConstraintDefinitionInverseKinematics.class);
CONSTRAINT_CLASSES.put("bLockTrackConstraint", ConstraintDefinitionLockTrack.class);
CONSTRAINT_CLASSES.put("bLocateLikeConstraint", ConstraintDefinitionLocLike.class);
CONSTRAINT_CLASSES.put("bLocLimitConstraint", ConstraintDefinitionLocLimit.class);
CONSTRAINT_CLASSES.put("bMinMaxConstraint", ConstraintDefinitionMinMax.class);
CONSTRAINT_CLASSES.put("bNullConstraint", ConstraintDefinitionNull.class);
CONSTRAINT_CLASSES.put("bPythonConstraint", ConstraintDefinitionPython.class);
CONSTRAINT_CLASSES.put("bRigidBodyJointConstraint", ConstraintDefinitionRigidBodyJoint.class);
CONSTRAINT_CLASSES.put("bRotateLikeConstraint", ConstraintDefinitionRotLike.class);
CONSTRAINT_CLASSES.put("bShrinkWrapConstraint", ConstraintDefinitionShrinkWrap.class);
CONSTRAINT_CLASSES.put("bSizeLikeConstraint", ConstraintDefinitionSizeLike.class);
CONSTRAINT_CLASSES.put("bSizeLimitConstraint", ConstraintDefinitionSizeLimit.class);
CONSTRAINT_CLASSES.put("bStretchToConstraint", ConstraintDefinitionStretchTo.class);
CONSTRAINT_CLASSES.put("bTransformConstraint", ConstraintDefinitionTransform.class);
CONSTRAINT_CLASSES.put("bRotLimitConstraint", ConstraintDefinitionRotLimit.class);
// Blender 2.50+
CONSTRAINT_CLASSES.put("bSplineIKConstraint", ConstraintDefinitionSplineInverseKinematic.class);
CONSTRAINT_CLASSES.put("bDampTrackConstraint", ConstraintDefinitionDampTrack.class);
CONSTRAINT_CLASSES.put("bPivotConstraint", ConstraintDefinitionDampTrack.class);
// Blender 2.56+
CONSTRAINT_CLASSES.put("bTrackToConstraint", ConstraintDefinitionTrackTo.class);
CONSTRAINT_CLASSES.put("bSameVolumeConstraint", ConstraintDefinitionSameVolume.class);
CONSTRAINT_CLASSES.put("bTransLikeConstraint", ConstraintDefinitionTransLike.class);
// Blender 2.62+
CONSTRAINT_CLASSES.put("bCameraSolverConstraint", ConstraintDefinitionCameraSolver.class);
CONSTRAINT_CLASSES.put("bObjectSolverConstraint", ConstraintDefinitionObjectSolver.class);
CONSTRAINT_CLASSES.put("bFollowTrackConstraint", ConstraintDefinitionFollowTrack.class);
}
/**
* This method creates the constraint instance.
*
* @param constraintStructure
* the constraint's structure (bConstraint clss in blender 2.49). If the value is null the NullConstraint is created.
* @param blenderContext
* the blender context
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, BlenderContext blenderContext) throws BlenderFileException {
if (constraintStructure == null) {
return new ConstraintDefinitionNull(null, blenderContext);
}
String constraintClassName = constraintStructure.getType();
Class<? extends ConstraintDefinition> constraintDefinitionClass = CONSTRAINT_CLASSES.get(constraintClassName);
if (constraintDefinitionClass != null) {
try {
return (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, blenderContext);
} catch (IllegalArgumentException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (SecurityException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (InstantiationException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (IllegalAccessException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (InvocationTargetException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
}
} else {
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionFollowPath extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionFollowPath.class.getName());
public ConstraintDefinitionFollowPath(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
//TODO: implement when curves are implemented
LOGGER.log(Level.WARNING, "'Follow path' not implemented! Curves not yet implemented!");
}
/* package */class ConstraintDefinitionFollowPath extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionFollowPath.class.getName());
public ConstraintDefinitionFollowPath(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement when curves are 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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionFollowTrack extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName());
public ConstraintDefinitionFollowTrack(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
/* package */class ConstraintDefinitionFollowTrack extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName());
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Follow track' constraint
LOGGER.log(Level.WARNING, "'Follow track' constraint NOT implemented!");
}
public ConstraintDefinitionFollowTrack(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionInverseKinematics extends ConstraintDefinition {
//private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionInverseKinematics.class.getName());
//private static final float IK_SOLVER_ERROR = 0.5f;
public ConstraintDefinitionInverseKinematics(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
/* package */class ConstraintDefinitionInverseKinematics extends ConstraintDefinition {
// private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionInverseKinematics.class.getName());
// private static final float IK_SOLVER_ERROR = 0.5f;
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// try {
// IK solver is only attached to bones
// Bone ownerBone = (Bone) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
// AnimData animData = blenderContext.getAnimData(ownerOMA);
// if(animData == null) {
//TODO: to nie moxe byx null, utworzyx dane bez ruchu, w zalexnoxci czy target six rusza
// }
//prepare a list of all parents of this bone
// CalculationBone[] bones = this.getBonesToCalculate(skeleton, boneAnimation);
// get the target point
// Object targetObject = this.getTarget(LoadedFeatureDataType.LOADED_FEATURE);
// Vector3f pt = null;// Point Target
// if (targetObject instanceof Bone) {
// pt = ((Bone) targetObject).getModelSpacePosition();
// } else if (targetObject instanceof Spatial) {
// pt = ((Spatial) targetObject).getWorldTranslation();
// } else if (targetObject instanceof Skeleton) {
// Structure armatureNodeStructure = (Structure) this.getTarget(LoadedFeatureDataType.LOADED_STRUCTURE);
// ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
// Transform transform = objectHelper.getTransformation(armatureNodeStructure, blenderContext);
// pt = transform.getTranslation();
// } else {
// throw new IllegalStateException(
// "Unknown target object type! Should be Node, Bone or Skeleton and there is: "
// + targetObject.getClass().getName());
// }
//fetching the owner's bone track
// BoneTrack ownerBoneTrack = null;
// int boneIndex = skeleton.getBoneIndex(ownerBone);
// for (int i = 0; i < boneAnimation.getTracks().length; ++i) {
// if (boneAnimation.getTracks()[i].getTargetBoneIndex() == boneIndex) {
// ownerBoneTrack = boneAnimation.getTracks()[i];
// break;
// }
// }
// int ownerBoneFramesCount = ownerBoneTrack==null ? 0 : ownerBoneTrack.getTimes().length;
//
// // preparing data
// int maxIterations = ((Number) data.getFieldValue("iterations")).intValue();
// CalculationBone[] bones = this.getBonesToCalculate(ownerBone, skeleton, boneAnimation);
// for (int i = 0; i < bones.length; ++i) {
// System.out.println(Arrays.toString(bones[i].track.getTranslations()));
// System.out.println(Arrays.toString(bones[i].track.getRotations()));
// System.out.println("===============================");
// }
// Quaternion rotation = new Quaternion();
// //all tracks should have the same amount of frames
// int framesCount = bones[0].getBoneFramesCount();
// assert framesCount >=1;
// for (int frame = 0; frame < framesCount; ++frame) {
// float error = IK_SOLVER_ERROR;
// int iteration = 0;
// while (error >= IK_SOLVER_ERROR && iteration <= maxIterations) {
// // rotating the bones
// for (int i = 0; i < bones.length - 1; ++i) {
// Vector3f pe = bones[i].getEndPoint();
// Vector3f pc = bones[i + 1].getWorldTranslation().clone();
//
// Vector3f peSUBpc = pe.subtract(pc).normalizeLocal();
// Vector3f ptSUBpc = pt.subtract(pc).normalizeLocal();
//
// float theta = FastMath.acos(peSUBpc.dot(ptSUBpc));
// Vector3f direction = peSUBpc.cross(ptSUBpc).normalizeLocal();
// bones[i].rotate(rotation.fromAngleAxis(theta, direction), frame);
// }
// error = pt.subtract(bones[0].getEndPoint()).length();
// ++iteration;
// }
// }
//
// for (CalculationBone bone : bones) {
// bone.applyCalculatedTracks();
// }
//
// System.out.println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");
// for (int i = 0; i < bones.length; ++i) {
// System.out.println(Arrays.toString(bones[i].track.getTranslations()));
// 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
* @param boneAnimation
* the bone animation data that stores the traces for the skeleton's bones
* @return a list of bones to imitate the bone's movement during IK solving
*/
private CalculationBone[] getBonesToCalculate(Skeleton skeleton, Animation boneAnimation) {
// Bone ownerBone = (Bone) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
// List<CalculationBone> bonesList = new ArrayList<CalculationBone>();
// do {
// bonesList.add(new CalculationBone(ownerBone, 1));
// int boneIndex = skeleton.getBoneIndex(ownerBone);
// for (int i = 0; i < boneAnimation.getTracks().length; ++i) {
// if (((BoneTrack[])boneAnimation.getTracks())[i].getTargetBoneIndex() == boneIndex) {
// bonesList.add(new CalculationBone(ownerBone, (BoneTrack)boneAnimation.getTracks()[i]));
// break;
// }
// }
// ownerBone = ownerBone.getParent();
// } while (ownerBone != null);
// //attaching children
// CalculationBone[] result = bonesList.toArray(new CalculationBone[bonesList.size()]);
// for (int i = result.length - 1; i > 0; --i) {
// result[i].attachChild(result[i - 1]);
// }
// return result;
return null;
}
public ConstraintDefinitionInverseKinematics(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// try {
// IK solver is only attached to bones
// Bone ownerBone = (Bone) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
// AnimData animData = blenderContext.getAnimData(ownerOMA);
// if(animData == null) {
// TODO: to nie moxe byx null, utworzyx dane bez ruchu, w zalexnoxci czy target six rusza
// }
// prepare a list of all parents of this bone
// CalculationBone[] bones = this.getBonesToCalculate(skeleton, boneAnimation);
// get the target point
// Object targetObject = this.getTarget(LoadedFeatureDataType.LOADED_FEATURE);
// Vector3f pt = null;// Point Target
// if (targetObject instanceof Bone) {
// pt = ((Bone) targetObject).getModelSpacePosition();
// } else if (targetObject instanceof Spatial) {
// pt = ((Spatial) targetObject).getWorldTranslation();
// } else if (targetObject instanceof Skeleton) {
// Structure armatureNodeStructure = (Structure) this.getTarget(LoadedFeatureDataType.LOADED_STRUCTURE);
// ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
// Transform transform = objectHelper.getTransformation(armatureNodeStructure, blenderContext);
// pt = transform.getTranslation();
// } else {
// throw new IllegalStateException(
// "Unknown target object type! Should be Node, Bone or Skeleton and there is: "
// + targetObject.getClass().getName());
// }
// fetching the owner's bone track
// BoneTrack ownerBoneTrack = null;
// int boneIndex = skeleton.getBoneIndex(ownerBone);
// for (int i = 0; i < boneAnimation.getTracks().length; ++i) {
// if (boneAnimation.getTracks()[i].getTargetBoneIndex() == boneIndex) {
// ownerBoneTrack = boneAnimation.getTracks()[i];
// break;
// }
// }
// int ownerBoneFramesCount = ownerBoneTrack==null ? 0 : ownerBoneTrack.getTimes().length;
//
// // preparing data
// int maxIterations = ((Number) data.getFieldValue("iterations")).intValue();
// CalculationBone[] bones = this.getBonesToCalculate(ownerBone, skeleton, boneAnimation);
// for (int i = 0; i < bones.length; ++i) {
// System.out.println(Arrays.toString(bones[i].track.getTranslations()));
// System.out.println(Arrays.toString(bones[i].track.getRotations()));
// System.out.println("===============================");
// }
// Quaternion rotation = new Quaternion();
// //all tracks should have the same amount of frames
// int framesCount = bones[0].getBoneFramesCount();
// assert framesCount >=1;
// for (int frame = 0; frame < framesCount; ++frame) {
// float error = IK_SOLVER_ERROR;
// int iteration = 0;
// while (error >= IK_SOLVER_ERROR && iteration <= maxIterations) {
// // rotating the bones
// for (int i = 0; i < bones.length - 1; ++i) {
// Vector3f pe = bones[i].getEndPoint();
// Vector3f pc = bones[i + 1].getWorldTranslation().clone();
//
// Vector3f peSUBpc = pe.subtract(pc).normalizeLocal();
// Vector3f ptSUBpc = pt.subtract(pc).normalizeLocal();
//
// float theta = FastMath.acos(peSUBpc.dot(ptSUBpc));
// Vector3f direction = peSUBpc.cross(ptSUBpc).normalizeLocal();
// bones[i].rotate(rotation.fromAngleAxis(theta, direction), frame);
// }
// error = pt.subtract(bones[0].getEndPoint()).length();
// ++iteration;
// }
// }
//
// for (CalculationBone bone : bones) {
// bone.applyCalculatedTracks();
// }
//
// System.out.println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");
// for (int i = 0; i < bones.length; ++i) {
// System.out.println(Arrays.toString(bones[i].track.getTranslations()));
// 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
* @param boneAnimation
* the bone animation data that stores the traces for the skeleton's bones
* @return a list of bones to imitate the bone's movement during IK solving
*/
private CalculationBone[] getBonesToCalculate(Skeleton skeleton, Animation boneAnimation) {
// Bone ownerBone = (Bone) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
// List<CalculationBone> bonesList = new ArrayList<CalculationBone>();
// do {
// bonesList.add(new CalculationBone(ownerBone, 1));
// int boneIndex = skeleton.getBoneIndex(ownerBone);
// for (int i = 0; i < boneAnimation.getTracks().length; ++i) {
// if (((BoneTrack[])boneAnimation.getTracks())[i].getTargetBoneIndex() == boneIndex) {
// bonesList.add(new CalculationBone(ownerBone, (BoneTrack)boneAnimation.getTracks()[i]));
// break;
// }
// }
// ownerBone = ownerBone.getParent();
// } while (ownerBone != null);
// //attaching children
// CalculationBone[] result = bonesList.toArray(new CalculationBone[bonesList.size()]);
// 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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionLocLike extends ConstraintDefinition {
private static final int LOCLIKE_X = 0x01;
private static final int LOCLIKE_Y = 0x02;
private static final int LOCLIKE_Z = 0x04;
//protected static final int LOCLIKE_TIP = 0x08;//this is deprecated in blender
/* package */class ConstraintDefinitionLocLike extends ConstraintDefinition {
private static final int LOCLIKE_X = 0x01;
private static final int LOCLIKE_Y = 0x02;
private static final int LOCLIKE_Z = 0x04;
// 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_Y_INVERT = 0x20;
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) {
super(constraintData, blenderContext);
if(blenderContext.getBlenderKey().isFixUpAxis()) {
//swapping Y and X limits flag in the bitwise flag
int y = flag & LOCLIKE_Y;
int invY = flag & LOCLIKE_Y_INVERT;
int z = flag & LOCLIKE_Z;
int invZ = flag & LOCLIKE_Z_INVERT;
flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET;//clear the other flags to swap them
flag |= y << 1;
flag |= invY << 1;
flag |= z >> 1;
flag |= invZ >> 1;
}
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
Vector3f ownerLocation = ownerTransform.getTranslation();
Vector3f targetLocation = targetTransform.getTranslation();
Vector3f startLocation = ownerTransform.getTranslation().clone();
Vector3f offset = Vector3f.ZERO;
if ((flag & LOCLIKE_OFFSET) != 0) {//we add the original location to the copied location
offset = startLocation;
}
super(constraintData, blenderContext);
if (blenderContext.getBlenderKey().isFixUpAxis()) {
// swapping Y and X limits flag in the bitwise flag
int y = flag & LOCLIKE_Y;
int invY = flag & LOCLIKE_Y_INVERT;
int z = flag & LOCLIKE_Z;
int invZ = flag & LOCLIKE_Z_INVERT;
flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET;// clear the other flags to swap them
flag |= y << 1;
flag |= invY << 1;
flag |= z >> 1;
flag |= invZ >> 1;
}
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
Vector3f ownerLocation = ownerTransform.getTranslation();
Vector3f targetLocation = targetTransform.getTranslation();
Vector3f startLocation = ownerTransform.getTranslation().clone();
Vector3f offset = Vector3f.ZERO;
if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original location to the copied location
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) {
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(influence < 1.0f) {
startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence);
ownerLocation.addLocal(startLocation);
}
}
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionLocLimit extends ConstraintDefinition {
private static final int LIMIT_XMIN = 0x01;
/* package */class ConstraintDefinitionLocLimit extends ConstraintDefinition {
private static final int LIMIT_XMIN = 0x01;
private static final int LIMIT_XMAX = 0x02;
private static final int LIMIT_YMIN = 0x04;
private static final int LIMIT_YMAX = 0x08;
private static final int LIMIT_ZMIN = 0x10;
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) {
super(constraintData, blenderContext);
if(blenderContext.getBlenderKey().isFixUpAxis()) {
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
int ymin = flag & LIMIT_YMIN;
int ymax = flag & LIMIT_YMAX;
int zmin = flag & LIMIT_ZMIN;
int zmax = flag & LIMIT_ZMAX;
flag &= LIMIT_XMIN | LIMIT_XMAX;//clear the other flags to swap them
flag |= ymin << 2;
flag |= ymax << 2;
flag |= zmin >> 2;
flag |= zmax >> 2;
} else {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).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();
}
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
Vector3f translation = ownerTransform.getTranslation();
if ((flag & LIMIT_XMIN) != 0 && translation.x < limits[0][0]) {
translation.x -= (translation.x - limits[0][0]) * influence;
}
if ((flag & LIMIT_XMAX) != 0 && translation.x > limits[0][1]) {
translation.x -= (translation.x - limits[0][1]) * influence;
}
if ((flag & LIMIT_YMIN) != 0 && translation.y < limits[1][0]) {
translation.y -= (translation.y - limits[1][0]) * influence;
}
if ((flag & LIMIT_YMAX) != 0 && translation.y > limits[1][1]) {
translation.y -= (translation.y - limits[1][1]) * influence;
}
if ((flag & LIMIT_ZMIN) != 0 && translation.z < limits[2][0]) {
translation.z -= (translation.z - limits[2][0]) * influence;
}
if ((flag & LIMIT_ZMAX) != 0 && translation.z > limits[2][1]) {
translation.z -= (translation.z - limits[2][1]) * influence;
}
}
super(constraintData, blenderContext);
if (blenderContext.getBlenderKey().isFixUpAxis()) {
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
int ymin = flag & LIMIT_YMIN;
int ymax = flag & LIMIT_YMAX;
int zmin = flag & LIMIT_ZMIN;
int zmax = flag & LIMIT_ZMAX;
flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap them
flag |= ymin << 2;
flag |= ymax << 2;
flag |= zmin >> 2;
flag |= zmax >> 2;
} else {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).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();
}
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
Vector3f translation = ownerTransform.getTranslation();
if ((flag & LIMIT_XMIN) != 0 && translation.x < limits[0][0]) {
translation.x -= (translation.x - limits[0][0]) * influence;
}
if ((flag & LIMIT_XMAX) != 0 && translation.x > limits[0][1]) {
translation.x -= (translation.x - limits[0][1]) * influence;
}
if ((flag & LIMIT_YMIN) != 0 && translation.y < limits[1][0]) {
translation.y -= (translation.y - limits[1][0]) * influence;
}
if ((flag & LIMIT_YMAX) != 0 && translation.y > limits[1][1]) {
translation.y -= (translation.y - limits[1][1]) * influence;
}
if ((flag & LIMIT_ZMIN) != 0 && translation.z < limits[2][0]) {
translation.z -= (translation.z - limits[2][0]) * influence;
}
if ((flag & LIMIT_ZMAX) != 0 && translation.z > limits[2][1]) {
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionLockTrack extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionLockTrack.class.getName());
public ConstraintDefinitionLockTrack(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Lock track' constraint
LOGGER.log(Level.WARNING, "'Lock track' constraint NOT implemented!");
}
/* package */class ConstraintDefinitionLockTrack extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionLockTrack.class.getName());
public ConstraintDefinitionLockTrack(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Lock track' constraint
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionMinMax extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionMinMax.class.getName());
public ConstraintDefinitionMinMax(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Min max' constraint
LOGGER.log(Level.WARNING, "'Min max' constraint NOT implemented!");
}
/* package */class ConstraintDefinitionMinMax extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionMinMax.class.getName());
public ConstraintDefinitionMinMax(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Min max' constraint
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionNull extends ConstraintDefinition {
/* package */class ConstraintDefinitionNull extends ConstraintDefinition {
public ConstraintDefinitionNull(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
//null constraint does nothing so no need to implement this one
}
public ConstraintDefinitionNull(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// 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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionObjectSolver extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName());
public ConstraintDefinitionObjectSolver(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
/* package */class ConstraintDefinitionObjectSolver extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName());
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Object solver' constraint
LOGGER.log(Level.WARNING, "'Object solver' constraint NOT implemented!");
}
public ConstraintDefinitionObjectSolver(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@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+.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionPivot extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionPivot.class.getName());
public ConstraintDefinitionPivot(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Pivot' constraint
LOGGER.log(Level.WARNING, "'Pivot' constraint NOT implemented!");
}
/* package */class ConstraintDefinitionPivot extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionPivot.class.getName());
public ConstraintDefinitionPivot(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Pivot' constraint
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionPython extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionPython.class.getName());
public ConstraintDefinitionPython(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Python' constraint
LOGGER.log(Level.WARNING, "'Python' constraint NOT implemented!");
}
/* package */class ConstraintDefinitionPython extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionPython.class.getName());
public ConstraintDefinitionPython(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Python' constraint
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionRigidBodyJoint extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionRigidBodyJoint.class.getName());
public ConstraintDefinitionRigidBodyJoint(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Rigid body joint' constraint
LOGGER.log(Level.WARNING, "'Rigid body joint' constraint NOT implemented!");
}
/* package */class ConstraintDefinitionRigidBodyJoint extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionRigidBodyJoint.class.getName());
public ConstraintDefinitionRigidBodyJoint(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Rigid body joint' constraint
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionRotLike extends ConstraintDefinition {
private static final int ROTLIKE_X = 0x01;
private static final int ROTLIKE_Y = 0x02;
private static final int ROTLIKE_Z = 0x04;
private static final int ROTLIKE_X_INVERT = 0x10;
private static final int ROTLIKE_Y_INVERT = 0x20;
private static final int ROTLIKE_Z_INVERT = 0x40;
private static final int ROTLIKE_OFFSET = 0x80;
private transient float[] ownerAngles = new float[3];
private transient float[] targetAngles = new float[3];
/* package */class ConstraintDefinitionRotLike extends ConstraintDefinition {
private static final int ROTLIKE_X = 0x01;
private static final int ROTLIKE_Y = 0x02;
private static final int ROTLIKE_Z = 0x04;
private static final int ROTLIKE_X_INVERT = 0x10;
private static final int ROTLIKE_Y_INVERT = 0x20;
private static final int ROTLIKE_Z_INVERT = 0x40;
private static final int ROTLIKE_OFFSET = 0x80;
private transient float[] ownerAngles = new float[3];
private transient float[] targetAngles = new float[3];
public ConstraintDefinitionRotLike(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
Quaternion ownerRotation = ownerTransform.getRotation();
ownerAngles = ownerRotation.toAngles(ownerAngles);
targetAngles = targetTransform.getRotation().toAngles(targetAngles);
Quaternion startRotation = ownerRotation.clone();
Quaternion offset = Quaternion.IDENTITY;
if ((flag & ROTLIKE_OFFSET) != 0) {//we add the original rotation to the copied rotation
offset = startRotation;
}
Quaternion ownerRotation = ownerTransform.getRotation();
ownerAngles = ownerRotation.toAngles(ownerAngles);
targetAngles = targetTransform.getRotation().toAngles(targetAngles);
Quaternion startRotation = ownerRotation.clone();
Quaternion offset = Quaternion.IDENTITY;
if ((flag & ROTLIKE_OFFSET) != 0) {// we add the original rotation to the copied rotation
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) {
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) {
if(influence < 1.0f) {
// startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence);
// ownerLocation.addLocal(startLocation);
//TODO
}
// startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence);
// ownerLocation.addLocal(startLocation);
// TODO
}
}
}

@ -11,75 +11,75 @@ import com.jme3.scene.plugins.blender.file.Structure;
* @author Marcin Roguski (Kaelthas)
*/
/* package */class ConstraintDefinitionRotLimit extends ConstraintDefinition {
private static final int LIMIT_XROT = 0x01;
private static final int LIMIT_YROT = 0x02;
private static final int LIMIT_ZROT = 0x04;
private static final int LIMIT_XROT = 0x01;
private static final int LIMIT_YROT = 0x02;
private static final int LIMIT_ZROT = 0x04;
private transient float[][] limits = new float[3][2];
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();
private transient float[][] limits = new float[3][2];
private transient float[] angles = new float[3];
// swapping Y and X limits flag in the bitwise flag
int limitY = flag & LIMIT_YROT;
int limitZ = flag & LIMIT_ZROT;
flag &= LIMIT_XROT;// clear the other flags to swap them
flag |= limitY << 1;
flag |= limitZ >> 1;
} else {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).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();
}
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();
// until blender 2.49 the rotations values were stored in degrees
if (blenderContext.getBlenderVersion() <= 249) {
for (int i = 0; i < limits.length; ++i) {
limits[i][0] *= FastMath.DEG_TO_RAD;
limits[i][1] *= FastMath.DEG_TO_RAD;
}
}
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
if ((flag & LIMIT_XROT) != 0) {
float difference = 0.0f;
if (angles[0] < limits[0][0]) {
difference = (angles[0] - limits[0][0]) * influence;
} else if (angles[0] > limits[0][1]) {
difference = (angles[0] - limits[0][1]) * influence;
}
angles[0] -= difference;
}
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]) {
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;
}
}
// swapping Y and X limits flag in the bitwise flag
int limitY = flag & LIMIT_YROT;
int limitZ = flag & LIMIT_ZROT;
flag &= LIMIT_XROT;// clear the other flags to swap them
flag |= limitY << 1;
flag |= limitZ >> 1;
} else {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).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
if (blenderContext.getBlenderVersion() <= 249) {
for (int i = 0; i < limits.length; ++i) {
limits[i][0] *= FastMath.DEG_TO_RAD;
limits[i][1] *= FastMath.DEG_TO_RAD;
}
}
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
if ((flag & LIMIT_XROT) != 0) {
float difference = 0.0f;
if (angles[0] < limits[0][0]) {
difference = (angles[0] - limits[0][0]) * influence;
} else if (angles[0] > limits[0][1]) {
difference = (angles[0] - limits[0][1]) * influence;
}
angles[0] -= difference;
}
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]) {
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)
*/
/*package*/ class ConstraintDefinitionSameVolume extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionSameVolume.class.getName());
public ConstraintDefinitionSameVolume(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Same volume' constraint
LOGGER.log(Level.WARNING, "'Same volume' constraint NOT implemented!");
}
/* package */class ConstraintDefinitionSameVolume extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionSameVolume.class.getName());
public ConstraintDefinitionSameVolume(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Same volume' constraint
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionShrinkWrap extends ConstraintDefinition {
public ConstraintDefinitionShrinkWrap(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
/* package */class ConstraintDefinitionShrinkWrap extends ConstraintDefinition {
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
//loading mesh points (blender ensures that the target is a mesh-object)
/*List<Vector3f> pts = new ArrayList<Vector3f>();
Node target = (Node) this.target.getObject();
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) {
pts.add(new Vector3f(floatBuffer.get(i), floatBuffer.get(i + 1), floatBuffer.get(i + 2)));
}
}
}
AnimData animData = blenderContext.getAnimData(this.owner.getOma());
if(animData != null) {
Object owner = this.owner.getObject();
for(Animation animation : animData.anims) {
BlenderTrack track = this.getTrack(owner, animData.skeleton, animation);
Vector3f[] translations = track.getTranslations();
Quaternion[] rotations = track.getRotations();
int maxFrames = translations.length;
for (int frame = 0; frame < maxFrames; ++frame) {
Vector3f currentTranslation = translations[frame];
//looking for minimum distanced point
Vector3f minDistancePoint = null;
float distance = Float.MAX_VALUE;
for(Vector3f p : pts) {
float temp = currentTranslation.distance(p);
if(temp < distance) {
distance = temp;
minDistancePoint = p;
}
}
translations[frame] = minDistancePoint.clone();
}
track.setKeyframes(track.getTimes(), translations, rotations, track.getScales());
}
}*/
//TODO: static constraint for spatials
}
public ConstraintDefinitionShrinkWrap(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// loading mesh points (blender ensures that the target is a mesh-object)
/*
* List<Vector3f> pts = new ArrayList<Vector3f>();
* Node target = (Node) this.target.getObject();
* 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) {
* pts.add(new Vector3f(floatBuffer.get(i), floatBuffer.get(i + 1), floatBuffer.get(i + 2)));
* }
* }
* }
* AnimData animData = blenderContext.getAnimData(this.owner.getOma());
* if(animData != null) {
* Object owner = this.owner.getObject();
* for(Animation animation : animData.anims) {
* BlenderTrack track = this.getTrack(owner, animData.skeleton, animation);
* Vector3f[] translations = track.getTranslations();
* Quaternion[] rotations = track.getRotations();
* int maxFrames = translations.length;
* for (int frame = 0; frame < maxFrames; ++frame) {
* Vector3f currentTranslation = translations[frame];
* //looking for minimum distanced point
* Vector3f minDistancePoint = null;
* float distance = Float.MAX_VALUE;
* for(Vector3f p : pts) {
* float temp = currentTranslation.distance(p);
* if(temp < distance) {
* distance = temp;
* minDistancePoint = p;
* }
* }
* translations[frame] = minDistancePoint.clone();
* }
* track.setKeyframes(track.getTimes(), translations, rotations, track.getScales());
* }
* }
*/
// 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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionSizeLike extends ConstraintDefinition {
private static final int SIZELIKE_X = 0x01;
private static final int SIZELIKE_Y = 0x02;
private static final int SIZELIKE_Z = 0x04;
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();
}
/* package */class ConstraintDefinitionSizeLike extends ConstraintDefinition {
private static final int SIZELIKE_X = 0x01;
private static final int SIZELIKE_Y = 0x02;
private static final int SIZELIKE_Z = 0x04;
private static final int LOCLIKE_OFFSET = 0x80;
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);
}
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) {
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionSizeLimit extends ConstraintDefinition {
private static final int LIMIT_XMIN = 0x01;
private static final int LIMIT_XMAX = 0x02;
private static final int LIMIT_YMIN = 0x04;
private static final int LIMIT_YMAX = 0x08;
private static final int LIMIT_ZMIN = 0x10;
private static final int LIMIT_ZMAX = 0x20;
protected transient float[][] limits = new float[3][2];
public ConstraintDefinitionSizeLimit(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
if(blenderContext.getBlenderKey().isFixUpAxis()) {
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
int ymin = flag & LIMIT_YMIN;
int ymax = flag & LIMIT_YMAX;
int zmin = flag & LIMIT_ZMIN;
int zmax = flag & LIMIT_ZMAX;
flag &= LIMIT_XMIN | LIMIT_XMAX;//clear the other flags to swap them
flag |= ymin << 2;
flag |= ymax << 2;
flag |= zmin >> 2;
flag |= zmax >> 2;
} else {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).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();
}
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
Vector3f scale = ownerTransform.getScale();
if ((flag & LIMIT_XMIN) != 0 && scale.x < limits[0][0]) {
scale.x -= (scale.x - limits[0][0]) * influence;
}
if ((flag & LIMIT_XMAX) != 0 && scale.x > limits[0][1]) {
scale.x -= (scale.x - limits[0][1]) * influence;
}
if ((flag & LIMIT_YMIN) != 0 && scale.y < limits[1][0]) {
scale.y -= (scale.y - limits[1][0]) * influence;
}
if ((flag & LIMIT_YMAX) != 0 && scale.y > limits[1][1]) {
scale.y -= (scale.y - limits[1][1]) * influence;
}
if ((flag & LIMIT_ZMIN) != 0 && scale.z < limits[2][0]) {
scale.z -= (scale.z - limits[2][0]) * influence;
}
if ((flag & LIMIT_ZMAX) != 0 && scale.z > limits[2][1]) {
scale.z -= (scale.z - limits[2][1]) * influence;
}
}
/* package */class ConstraintDefinitionSizeLimit extends ConstraintDefinition {
private static final int LIMIT_XMIN = 0x01;
private static final int LIMIT_XMAX = 0x02;
private static final int LIMIT_YMIN = 0x04;
private static final int LIMIT_YMAX = 0x08;
private static final int LIMIT_ZMIN = 0x10;
private static final int LIMIT_ZMAX = 0x20;
protected transient float[][] limits = new float[3][2];
public ConstraintDefinitionSizeLimit(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
if (blenderContext.getBlenderKey().isFixUpAxis()) {
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
int ymin = flag & LIMIT_YMIN;
int ymax = flag & LIMIT_YMAX;
int zmin = flag & LIMIT_ZMIN;
int zmax = flag & LIMIT_ZMAX;
flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap them
flag |= ymin << 2;
flag |= ymax << 2;
flag |= zmin >> 2;
flag |= zmax >> 2;
} else {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).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();
}
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
Vector3f scale = ownerTransform.getScale();
if ((flag & LIMIT_XMIN) != 0 && scale.x < limits[0][0]) {
scale.x -= (scale.x - limits[0][0]) * influence;
}
if ((flag & LIMIT_XMAX) != 0 && scale.x > limits[0][1]) {
scale.x -= (scale.x - limits[0][1]) * influence;
}
if ((flag & LIMIT_YMIN) != 0 && scale.y < limits[1][0]) {
scale.y -= (scale.y - limits[1][0]) * influence;
}
if ((flag & LIMIT_YMAX) != 0 && scale.y > limits[1][1]) {
scale.y -= (scale.y - limits[1][1]) * influence;
}
if ((flag & LIMIT_ZMIN) != 0 && scale.z < limits[2][0]) {
scale.z -= (scale.z - limits[2][0]) * influence;
}
if ((flag & LIMIT_ZMAX) != 0 && scale.z > limits[2][1]) {
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+.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionSplineInverseKinematic extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionSplineInverseKinematic.class.getName());
public ConstraintDefinitionSplineInverseKinematic(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO Auto-generated method stub
LOGGER.log(Level.WARNING, "'Splie IK' constraint NOT implemented!");
}
/* package */class ConstraintDefinitionSplineInverseKinematic extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionSplineInverseKinematic.class.getName());
public ConstraintDefinitionSplineInverseKinematic(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO Auto-generated method stub
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionStretchTo extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionStretchTo.class.getName());
public ConstraintDefinitionStretchTo(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
/* package */class ConstraintDefinitionStretchTo extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionStretchTo.class.getName());
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Stretch to' constraint
LOGGER.log(Level.WARNING, "'Stretch to' constraint NOT implemented!");
}
public ConstraintDefinitionStretchTo(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@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)
*/
/* 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) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Track to' constraint
LOGGER.log(Level.WARNING, "'Track to' constraint NOT implemented!");
}
public ConstraintDefinitionTrackTo(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Track to' constraint
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)
*/
/*package*/ class ConstraintDefinitionTransLike extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionTransLike.class.getName());
public ConstraintDefinitionTransLike(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Trans like' constraint
LOGGER.log(Level.WARNING, "'Trans like' constraint NOT implemented!");
}
/* package */class ConstraintDefinitionTransLike extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionTransLike.class.getName());
public ConstraintDefinitionTransLike(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Trans like' constraint
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class ConstraintDefinitionTransform extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName());
public ConstraintDefinitionTransform(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
/* package */class ConstraintDefinitionTransform extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName());
@Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// TODO: implement 'Transform' constraint
LOGGER.log(Level.WARNING, "'Transform' constraint NOT implemented!");
}
public ConstraintDefinitionTransform(Structure constraintData, BlenderContext blenderContext) {
super(constraintData, blenderContext);
}
@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 Y_VALUE = 1;
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.
*/
private int type;
private int type;
/** The dimension of the curve. */
private int dimension;
private int dimension;
/** A table of the bezier points. */
private float[][][] bezierPoints;
private float[][][] bezierPoints;
/** Array that stores a radius for each bezier triple. */
private float[] radiuses;
private float[] radiuses;
@SuppressWarnings("unchecked")
public BezierCurve(final int type, final List<Structure> bezTriples, final int dimension) {
@ -35,9 +35,9 @@ public class BezierCurve {
}
this.type = type;
this.dimension = dimension;
//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 third index specifies the coordinates of the specific point in a bezier triple
// 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 third index specifies the coordinates of the specific point in a bezier triple
bezierPoints = new float[bezTriples.size()][3][dimension];
radiuses = new float[bezTriples.size()];
int i = 0, j, k;
@ -48,18 +48,18 @@ public class BezierCurve {
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.
* @param frame
* the frame for which the value is being calculated
* the frame for which the value is being calculated
* @param valuePart
* 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
* Z_VALUE - the Z factor of the result
* 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
* Z_VALUE - the Z factor of the result
* @return the value of the curve
*/
public float evaluate(int frame, int valuePart) {
@ -74,7 +74,7 @@ public class BezierCurve {
}
if (frame < bezierPoints[0][1][0]) {
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];
}
}
@ -96,17 +96,17 @@ public class BezierCurve {
return type;
}
/**
* The method returns the radius for the required bezier triple.
*
* @param bezierTripleIndex
* index of the bezier triple
* @return radius of the required bezier triple
*/
public float getRadius(int bezierTripleIndex) {
return radiuses[bezierTripleIndex];
}
/**
* The method returns the radius for the required bezier triple.
*
* @param bezierTripleIndex
* index of the bezier triple
* @return radius of the required bezier triple
*/
public float getRadius(int bezierTripleIndex) {
return radiuses[bezierTripleIndex];
}
/**
* This method returns 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.
* @param tripleIndex
* index of the triple
* index of the triple
* @return text representation of the triple
*/
private String toStringBezTriple(int tripleIndex) {
if (this.dimension == 2) {
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] + ")]";
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] + ")]";
} else {
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] + ")]";
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] + ")]";
}
}
}

@ -43,13 +43,13 @@ public class BlenderFileException extends Exception {
* Constructor. Creates an exception with no description.
*/
public BlenderFileException() {
//this constructor has no message
// this constructor has no message
}
/**
* Constructor. Creates an exception containing the given message.
* @param message
* the message describing the problem that occured
* the message describing the problem that occured
*/
public BlenderFileException(String 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.
* @param throwable
* an exception/error that occured
* an exception/error that occured
*/
public BlenderFileException(Throwable throwable) {
super(throwable);
@ -67,9 +67,9 @@ public class BlenderFileException extends Exception {
/**
* Constructor. Creates an exception with both a message and stacktrace.
* @param message
* the message describing the problem that occured
* the message describing the problem that occured
* @param throwable
* an exception/error that occured
* an exception/error that occured
*/
public BlenderFileException(String message, Throwable throwable) {
super(message, throwable);

@ -46,35 +46,35 @@ import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
*/
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. */
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.
*/
private int pointerSize;
private int pointerSize;
/**
* 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. */
private String versionNumber;
private String versionNumber;
/** The buffer we store the read data to. */
protected byte[] cachedBuffer;
protected byte[] cachedBuffer;
/** The total size of the stored data. */
protected int size;
protected int size;
/** The current position of the read cursor. */
protected int position;
protected int position;
/**
* Constructor. The input stream is stored and used to read data.
* @param inputStream
* the stream we read data from
* the stream we read data from
* @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 {
//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 {
size = inputStream.available();
} catch (IOException e) {
@ -84,7 +84,7 @@ public class BlenderInputStream extends InputStream {
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;
if (inputStream instanceof BufferedInputStream) {
bufferedInputStream = (BufferedInputStream) inputStream;
@ -97,16 +97,16 @@ public class BlenderInputStream extends InputStream {
} catch (IOException e) {
throw new BlenderFileException("Problems occured while caching the file!", e);
} finally {
try {
inputStream.close();
} catch (IOException e) {
LOGGER.warning("Unable to close stream with blender file.");
}
try {
inputStream.close();
} catch (IOException e) {
LOGGER.warning("Unable to close stream with blender file.");
}
}
try {
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.position = 0;
this.readFileHeader();
@ -116,22 +116,22 @@ public class BlenderInputStream extends InputStream {
/**
* This method reads the whole stream into a buffer.
* @param inputStream
* the stream to read the file data from
* @throws IOException
* an exception is thrown when data read from the stream is invalid or there are problems with i/o
* operations
* the stream to read the file data from
* @throws IOException
* an exception is thrown when data read from the stream is invalid or there are problems with i/o
* operations
*/
private void readStreamToCache(InputStream inputStream) throws IOException {
int data = inputStream.read();
cachedBuffer = new byte[size];
size = 0;//this will count the actual size
size = 0;// this will count the actual size
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)];
System.arraycopy(cachedBuffer, 0, newBuffer, 0, cachedBuffer.length);
cachedBuffer = newBuffer;
}
cachedBuffer[size++] = (byte) data;
cachedBuffer[size++] = (byte) data;
data = inputStream.read();
}
}
@ -146,8 +146,7 @@ public class BlenderInputStream extends InputStream {
gis = new GZIPInputStream(new ByteArrayInputStream(cachedBuffer));
this.readStreamToCache(gis);
} catch (IOException e) {
throw new IllegalStateException("IO errors occured where they should NOT! "
+ "The data is already buffered at this point!", e);
throw new IllegalStateException("IO errors occured where they should NOT! " + "The data is already buffered at this point!", e);
} finally {
try {
if (gis != null) {
@ -162,9 +161,9 @@ public class BlenderInputStream extends InputStream {
/**
* This method loads the header from the given stream during instance creation.
* @param inputStream
* the stream we read the header from
* the stream we read the header from
* @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 {
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.
* @param bytes
* 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.
* @param position
* the position of the read cursor
* the position of the read cursor
*/
public void setPosition(int 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.
* @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) {
if (bytesAmount <= 0) {
@ -365,17 +364,17 @@ public class BlenderInputStream extends InputStream {
}
@Override
public void close() throws IOException {
//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
//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
public void close() throws IOException {
// 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
// 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
}
/**
* This method should be used to close the stream because some loaders may close the stream while reading resources from it.
*/
public void forceClose() {
cachedBuffer = null;
cachedBuffer = null;
}
}

@ -42,39 +42,37 @@ import java.util.Map;
*/
public class DnaBlockData {
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 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 STRC_ID = 'S' << 24 | 'T' << 16 | 'R' << 8 | 'C'; //STRC
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 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 STRC_ID = 'S' << 24 | 'T' << 16 | 'R' << 8 | 'C'; // STRC
/** Structures available inside the file. */
private final Structure[] structures;
private final Structure[] structures;
/** A map that helps finding a structure by type. */
private final Map<String, Structure> structuresMap;
/**
* Constructor. Loads the block from the given stream during instance creation.
* @param inputStream
* the stream we read the block from
* the stream we read the block from
* @param blenderContext
* the blender context
* the blender context
* @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 {
int identifier;
//reading 'SDNA' identifier
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16
| inputStream.readByte() << 8 | inputStream.readByte();
// reading 'SDNA' identifier
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
if (identifier != SDNA_ID) {
throw new BlenderFileException("Invalid identifier! '" + this.toString(SDNA_ID) + "' expected and found: " + this.toString(identifier));
}
//reading names
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16
| inputStream.readByte() << 8 | inputStream.readByte();
// reading names
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
if (identifier != NAME_ID) {
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();
}
//reding types
// reding types
inputStream.alignPosition(4);
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16
| inputStream.readByte() << 8 | inputStream.readByte();
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
if (identifier != TYPE_ID) {
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();
}
//reading lengths
// reading lengths
inputStream.alignPosition(4);
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16
| inputStream.readByte() << 8 | inputStream.readByte();
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
if (identifier != TLEN_ID) {
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) {
lengths[i] = inputStream.readShort();
}
//reading structures
// reading structures
inputStream.alignPosition(4);
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16
| inputStream.readByte() << 8 | inputStream.readByte();
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
if (identifier != STRC_ID) {
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.
* @param index
* the index of the structure
* the index of the structure
* @return the structure of the given 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.
* @param name
* the name of the structure
* the name of the structure
* @return the required structure or null if the given name is inapropriate
*/
public Structure getStructure(String name) {
@ -176,7 +171,7 @@ public class DnaBlockData {
/**
* This method indicates if the structure of the given name exists.
* @param name
* the name of the structure
* the name of the structure
* @return true if the structure exists and false otherwise
*/
public boolean hasStructure(String name) {
@ -186,7 +181,7 @@ public class DnaBlockData {
/**
* This method converts the given identifier code to string.
* @param code
* the code taht is to be converted
* the code taht is to be converted
* @return the string value of the identifier
*/
private String toString(int code) {

@ -37,12 +37,12 @@ import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
* An array that can be dynamically modified/
* @author Marcin Roguski
* @param <T>
* the type of stored data in the array
* the type of stored data in the array
*/
public class DynamicArray<T> implements Cloneable {
/** 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
* 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.
* @param tableSizes
* the sizes of the table
* the sizes of the table
* @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")
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.
* @param tableSizes
* the sizes of the table
* the sizes of the table
* @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 {
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
* consideration.
* @param position
* the position of the data
* the position of the data
* @return required data
*/
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
* table boundaries. Check the table's dimension first.
* @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
*/
public T get(int... position) {
@ -138,8 +138,8 @@ public class DynamicArray<T> implements Cloneable {
@Override
public String toString() {
StringBuilder result = new StringBuilder();
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'
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'
result.append(array[i]);
}
} else {

@ -11,38 +11,38 @@ import java.util.List;
* another structure.
* @author Marcin Roguski
*/
/*package*/
/* package */
class Field implements Cloneable {
private static final int NAME_LENGTH = 24;
private static final int TYPE_LENGTH = 16;
/** The blender context. */
public BlenderContext blenderContext;
public BlenderContext blenderContext;
/** The type of the field. */
public String type;
public String type;
/** The name of the field. */
public String name;
public String name;
/** The value of the field. Filled during data reading. */
public Object value;
public Object value;
/** 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.
*/
public int[] tableSizes;
public int[] tableSizes;
/** 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.
* @param name
* the name of the field
* the name of the field
* @param type
* the type of the field
* the type of the field
* @param blenderContext
* the blender context
* the blender context
* @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 {
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
* have a clead empty copy of the filed to fill with data.
* @param field
* the object that we copy
* the object that we copy
*/
private Field(Field field) {
type = field.type;
@ -75,9 +75,9 @@ class Field implements Cloneable {
/**
* This method fills the field wth data read from the input stream.
* @param blenderInputStream
* the stream we read data from
* the stream we read data from
* @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 {
int dataToRead = 1;
@ -107,9 +107,9 @@ class Field implements Cloneable {
}
break;
case CHARACTER:
//character is also stored as a number, because sometimes the new blender version uses
//other number type instead of character as a field type
//and characters are very often used as byte number stores instead of real chars
// character is also stored as a number, because sometimes the new blender version uses
// other number type instead of character as a field type
// and characters are very often used as byte number stores instead of real chars
if (dataToRead == 1) {
value = Byte.valueOf((byte) blenderInputStream.readByte());
} else {
@ -200,28 +200,28 @@ class Field implements Cloneable {
/**
* This method parses the field name to determine how the field should be used.
* @param nameBuilder
* the name of the field (given as StringBuilder)
* the name of the field (given as StringBuilder)
* @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 {
this.removeWhitespaces(nameBuilder);
//veryfying if the name is a pointer
// veryfying if the name is a pointer
int pointerIndex = nameBuilder.indexOf("*");
while (pointerIndex >= 0) {
++pointerLevel;
nameBuilder.deleteCharAt(pointerIndex);
pointerIndex = nameBuilder.indexOf("*");
}
//veryfying if the name is a function pointer
// veryfying if the name is a function pointer
if (nameBuilder.indexOf("(") >= 0) {
function = true;
this.removeCharacter(nameBuilder, '(');
this.removeCharacter(nameBuilder, ')');
} else {
//veryfying if the name is a table
// veryfying if the name is a table
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 {
tableStartIndex = nameBuilder.indexOf("[");
if (tableStartIndex > 0) {
@ -250,9 +250,9 @@ class Field implements Cloneable {
/**
* This method removes the required character from the text.
* @param text
* the text we remove characters from
* the text we remove characters from
* @param toRemove
* the character to be removed
* the character to be removed
*/
private void removeCharacter(StringBuilder text, char toRemove) {
for (int i = 0; i < text.length(); ++i) {
@ -266,7 +266,7 @@ class Field implements Cloneable {
/**
* This method removes all whitespaces from the text.
* @param text
* the text we remove whitespaces from
* the text we remove whitespaces from
*/
private void removeWhitespaces(StringBuilder text) {
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).
* @return the full name of the field
*/
public String getFullName() {
StringBuilder result = new StringBuilder();
StringBuilder result = new StringBuilder();
if (function) {
result.append('(');
}
@ -306,10 +306,10 @@ class Field implements Cloneable {
StringBuilder result = new StringBuilder();
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();
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
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
result.append(' ');
}
result.append(type);

@ -41,52 +41,51 @@ import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
*/
public class FileBlockHeader {
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_SR00 = 'S' << 24 | 'R' << 16; //SR00
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_OB00 = 'O' << 24 | 'B' << 16; //OB00
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_WO00 = 'W' << 24 | 'O' << 16; //WO00
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_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_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_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_TE00 = 'T' << 24 | 'E' << 16; // TE00
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_CA00 = 'C' << 24 | 'A' << 16; // CA00
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_MA00 = 'M' << 24 | 'A' << 16; // MA00
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_TX00 = 'T' << 24 | 'X' << 16; // TX00
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_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_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_ENDB = 'E' << 24 | 'N' << 16 | 'D' << 8 | 'B'; // ENDB
/** 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]. */
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
* size)].
*/
private long oldMemoryAddress;
private long oldMemoryAddress;
/** Index of the SDNA structure [4 bytes]. */
private int sdnaIndex;
private int sdnaIndex;
/** 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. */
private int blockPosition;
private int blockPosition;
/**
* Constructor. Loads the block header from the given stream during instance creation.
* @param inputStream
* the stream we read the block header from
* the stream we read the block header from
* @param blenderContext
* the blender context
* the blender context
* @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 {
inputStream.alignPosition(4);
code = inputStream.readByte() << 24 | inputStream.readByte() << 16
| inputStream.readByte() << 8 | inputStream.readByte();
code = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
size = inputStream.readInt();
oldMemoryAddress = inputStream.readPointer();
sdnaIndex = inputStream.readInt();
@ -103,7 +102,7 @@ public class FileBlockHeader {
/**
* This method returns the structure described by the header filled with appropriate data.
* @param blenderContext
* the blender context
* the blender context
* @return structure filled with data
* @throws BlenderFileException
*/
@ -186,7 +185,7 @@ public class FileBlockHeader {
/**
* This method transforms the coded bloch id into a string value.
* @param code
* the id of the block
* the id of the block
* @return the string value of the block id
*/
protected String codeToString(int code) {

@ -45,20 +45,20 @@ public class Pointer {
/** The blender context. */
private BlenderContext blenderContext;
/** The level of the pointer. */
private int pointerLevel;
private int pointerLevel;
/** The address in file it points to. */
private long oldMemoryAddress;
private long oldMemoryAddress;
/** This variable indicates if the field is a function pointer. */
public boolean function;
public boolean function;
/**
* Constructr. Stores the basic data about the pointer.
* @param pointerLevel
* the level of the pointer
* the level of the pointer
* @param function
* this variable indicates if the field is a function pointer
* this variable indicates if the field is a function pointer
* @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) {
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
* for this.
* @param inputStream
* the stream we read the pointer value from
* the stream we read the pointer value from
*/
public void fill(BlenderInputStream inputStream) {
oldMemoryAddress = inputStream.readPointer();
@ -79,10 +79,10 @@ public class Pointer {
/**
* This method fetches the data stored under the given address.
* @param inputStream
* the stream we read data from
* the stream we read data from
* @return the data read from the file
* @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 {
if (oldMemoryAddress == 0) {
@ -90,9 +90,8 @@ public class Pointer {
}
List<Structure> structures = null;
FileBlockHeader dataFileBlock = blenderContext.getFileBlock(oldMemoryAddress);
if(dataFileBlock == null) {
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.");
if (dataFileBlock == null) {
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.");
}
if (pointerLevel > 1) {
int pointersAmount = dataFileBlock.getSize() / inputStream.getPointerSize() * dataFileBlock.getCount();
@ -108,12 +107,12 @@ public class Pointer {
structures.addAll(p.fetchData(inputStream));
}
} 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
//of the material is important, that is why we need null's to indicate that some materials' slots are empty
if(structures == null) {
structures = new ArrayList<Structure>();
}
structures.add(null);
// 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
if (structures == null) {
structures = new ArrayList<Structure>();
}
structures.add(null);
}
}
} else {
@ -144,7 +143,7 @@ public class Pointer {
public boolean isNull() {
return oldMemoryAddress == 0;
}
/**
* 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

@ -49,20 +49,20 @@ public class Structure implements Cloneable {
/** The address of the block that fills the structure. */
private transient Long oldMemoryAddress;
/** The type of the structure. */
private String type;
private String 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.
* @param structure
* the structure to copy.
* the structure to copy.
* @param blenderContext
* the blender context of the structure
* the blender context of the structure
* @throws CloneNotSupportedException
* this exception should never be thrown
* this exception should never be thrown
*/
private Structure(Structure structure, BlenderContext blenderContext) throws CloneNotSupportedException {
type = structure.type;
@ -77,15 +77,15 @@ public class Structure implements Cloneable {
/**
* Constructor. Loads the structure from the given stream during instance creation.
* @param inputStream
* the stream we read the structure from
* the stream we read the structure from
* @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
* the names of types for the structure
* the names of types for the structure
* @param blenderContext
* the blender context
* the blender context
* @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 {
int nameIndex = inputStream.readShort();
@ -109,10 +109,10 @@ public class Structure implements Cloneable {
/**
* This method fills the structure with data.
* @param inputStream
* the stream we read data from, its read cursor should be placed at the start position of the data for the
* structure
* the stream we read data from, its read cursor should be placed at the start position of the data for the
* structure
* @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 {
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.
* @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
*/
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
* only (no substructures).
* @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
*/
public Object getFlatFieldValue(String fieldName) {
@ -153,7 +153,7 @@ public class Structure implements Cloneable {
return value;
} else if (value instanceof Structure) {
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;
}
}
@ -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
* held by this structure within the blend file.
* @param blenderContext
* the blender context
* the blender context
* @return a list of filled structures
* @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
* 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 {
if (!"ListBase".equals(this.type)) {
@ -209,17 +209,17 @@ public class Structure implements Cloneable {
/**
* This method returns the field name of the given index.
* @param fieldIndex
* the index of the field
* the index of the field
* @return the field name of the given index
*/
public String getFieldName(int fieldIndex) {
return fields[fieldIndex].name;
}
/**
* This method returns the full field name of the given index.
* @param fieldIndex
* the index of the field
* the index of the field
* @return the full field name of the given index
*/
public String getFieldFullName(int fieldIndex) {
@ -229,7 +229,7 @@ public class Structure implements Cloneable {
/**
* This method returns the field type of the given index.
* @param fieldIndex
* the index of the field
* the index of the field
* @return the field type of the given index
*/
public String getFieldType(int fieldIndex) {
@ -248,20 +248,20 @@ public class Structure implements Cloneable {
return oldMemoryAddress;
}
/**
* 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.
* @return the name of the structure read from the ID field or null
*/
public String getName() {
Object fieldValue = this.getFieldValue("ID");
if(fieldValue instanceof Structure) {
Structure id = (Structure)fieldValue;
return id == null ? null : id.getFieldValue("name").toString().substring(2);//blender adds 2-charactes as a name prefix
}
return null;
}
/**
* 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.
* @return the name of the structure read from the ID field or null
*/
public String getName() {
Object fieldValue = this.getFieldValue("ID");
if (fieldValue instanceof Structure) {
Structure id = (Structure) fieldValue;
return id == null ? null : id.getFieldValue("name").toString().substring(2);// blender adds 2-charactes as a name prefix
}
return null;
}
@Override
public String toString() {
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.
* @author Marcin Roguski
*/
/*package*/
/* package */
static enum DataType {
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
* is case sensitive!
* @param type
* the type name of the data
* the type name of the data
* @param blenderContext
* the blender context
* the blender context
* @return appropriate enum value to the given type name
* @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 {
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
* different blender versions.
* @param blenderVersion
* the version read from the blend file
* the version read from the blend file
* @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) {
super(blenderVersion, fixUpAxis);
}
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) {
return result;
}
Light light = null;
int type = ((Number) structure.getFieldValue("type")).intValue();
switch (type) {
case 0://Lamp
light = new PointLight();
case 0:// Lamp
light = new PointLight();
float distance = ((Number) structure.getFieldValue("dist")).floatValue();
((PointLight) light).setRadius(distance);
break;
case 1://Sun
case 1:// Sun
LOGGER.log(Level.WARNING, "'Sun' lamp is not supported in jMonkeyEngine.");
break;
case 2://Spot
light = new SpotLight();
//range
((SpotLight)light).setSpotRange(((Number) structure.getFieldValue("dist")).floatValue());
//outer angle
float outerAngle = ((Number) structure.getFieldValue("spotsize")).floatValue()*FastMath.DEG_TO_RAD * 0.5f;
((SpotLight)light).setSpotOuterAngle(outerAngle);
//inner angle
float spotblend = ((Number) structure.getFieldValue("spotblend")).floatValue();
case 2:// Spot
light = new SpotLight();
// range
((SpotLight) light).setSpotRange(((Number) structure.getFieldValue("dist")).floatValue());
// outer angle
float outerAngle = ((Number) structure.getFieldValue("spotsize")).floatValue() * FastMath.DEG_TO_RAD * 0.5f;
((SpotLight) light).setSpotOuterAngle(outerAngle);
// inner angle
float spotblend = ((Number) structure.getFieldValue("spotblend")).floatValue();
spotblend = FastMath.clamp(spotblend, 0, 1);
float innerAngle = outerAngle * (1 - spotblend);
((SpotLight)light).setSpotInnerAngle(innerAngle);
((SpotLight) light).setSpotInnerAngle(innerAngle);
break;
case 3://Hemi
case 3:// Hemi
LOGGER.log(Level.WARNING, "'Hemi' lamp is not supported in jMonkeyEngine.");
break;
case 4://Area
light = new DirectionalLight();
case 4:// Area
light = new DirectionalLight();
break;
default:
throw new BlenderFileException("Unknown light source type: " + type);
@ -115,9 +115,9 @@ public class LightHelper extends AbstractBlenderHelper {
}
return result;
}
@Override
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ interface IAlphaMask {
/**
* This method sets the size of the texture's image.
* @param width
* the width of the image
* @param height
* the height of the image
*/
void setImageSize(int width, int height);
/* package */interface IAlphaMask {
/**
* This method sets the size of the texture's image.
* @param width
* the width of the image
* @param height
* the height of the image
*/
void setImageSize(int width, int height);
/**
* This method returns the alpha value for the specified texture position.
* @param x
* the X coordinate of the texture position
* @param y
* the Y coordinate of the texture position
* @return the alpha value for the specified texture position
*/
byte getAlpha(float x, float y);
/**
* This method returns the alpha value for the specified texture position.
* @param x
* the X coordinate of the texture position
* @param y
* the Y coordinate of the texture position
* @return the alpha value for the specified texture position
*/
byte getAlpha(float x, float y);
}

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

@ -57,410 +57,411 @@ import java.util.logging.Level;
import java.util.logging.Logger;
public class MaterialHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(MaterialHelper.class.getName());
protected static final float DEFAULT_SHININESS = 20.0f;
public static final String TEXTURE_TYPE_COLOR = "ColorMap";
public static final String TEXTURE_TYPE_DIFFUSE = "DiffuseMap";
public static final String TEXTURE_TYPE_NORMAL = "NormalMap";
public static final String TEXTURE_TYPE_SPECULAR = "SpecularMap";
public static final String TEXTURE_TYPE_GLOW = "GlowMap";
public static final String TEXTURE_TYPE_ALPHA = "AlphaMap";
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_CONE = Integer.valueOf(2);
public static final Integer ALPHA_MASK_HYPERBOLE = Integer.valueOf(3);
protected final Map<Integer, IAlphaMask> alphaMasks = new HashMap<Integer, IAlphaMask>();
/**
* The type of the material's diffuse shader.
*/
public static enum DiffuseShader {
LAMBERT, ORENNAYAR, TOON, MINNAERT, FRESNEL
}
/**
* The type of the material's specular shader.
*/
public static enum SpecularShader {
COOKTORRENCE, PHONG, BLINN, TOON, WARDISO
}
/**
* 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 MaterialHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, false);
// setting alpha masks
alphaMasks.put(ALPHA_MASK_NONE, new IAlphaMask() {
public void setImageSize(int width, int height) {}
public byte getAlpha(float x, float y) {
return (byte) 255;
}
});
alphaMasks.put(ALPHA_MASK_CIRCLE, new IAlphaMask() {
private float r;
private float[] center;
public void setImageSize(int width, int height) {
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])));
return (byte) (d >= r ? 0 : 255);
}
});
alphaMasks.put(ALPHA_MASK_CONE, new IAlphaMask() {
private float r;
private float[] center;
public void setImageSize(int width, int height) {
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])));
return (byte) (d >= r ? 0 : -255.0f * d / r + 255.0f);
}
});
alphaMasks.put(ALPHA_MASK_HYPERBOLE, new IAlphaMask() {
private float r;
private float[] center;
public void setImageSize(int width, int height) {
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;
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
* structure with material data
* @param blenderContext
* the blender context
* @return jme material
* @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.");
MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
if (result != null) {
return result;
}
result = new MaterialContext(structure, blenderContext);
LOGGER.log(Level.FINE, "Material''s name: {0}", result.name);
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.
*
* @param material
* a material to be cloned without textures
* @param imageType
* 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 };
Map<String, Texture> textures = new HashMap<String, Texture>(textureParamNames.length);
for (String textureParamName : textureParamNames) {
MatParamTexture matParamTexture = material.getTextureParam(textureParamName);
if (matParamTexture != null) {
textures.put(textureParamName, matParamTexture.getTextureValue());
}
}
if (textures.isEmpty()) {
return material;
} else {
// clear all textures first so that wo de not waste resources cloning them
for (Entry<String, Texture> textureParamName : textures.entrySet()) {
String name = textureParamName.getValue().getName();
try {
int type = Integer.parseInt(name);
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);
}
}
Material result = material.clone();
// put the textures back in place
for (Entry<String, Texture> textureEntry : textures.entrySet()) {
material.setTexture(textureEntry.getKey(), textureEntry.getValue());
}
return result;
}
}
/**
* This method converts the given material into particles-usable material.
* The texture and glow color are being copied.
* The method assumes it receives the Lighting type of material.
* @param material
* the source material
* @param blenderContext
* 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");
// copying texture
MatParam diffuseMap = material.getParam("DiffuseMap");
if (diffuseMap != null) {
Texture texture = ((Texture) diffuseMap.getValue()).clone();
// applying alpha mask to the texture
Image image = texture.getImage();
ByteBuffer sourceBB = image.getData(0);
sourceBB.rewind();
int w = image.getWidth();
int h = image.getHeight();
ByteBuffer bb = BufferUtils.createByteBuffer(w * h * 4);
IAlphaMask iAlphaMask = alphaMasks.get(alphaMaskIndex);
iAlphaMask.setImageSize(w, h);
for (int x = 0; x < w; ++x) {
for (int y = 0; y < h; ++y) {
bb.put(sourceBB.get());
bb.put(sourceBB.get());
bb.put(sourceBB.get());
bb.put(iAlphaMask.getAlpha(x, y));
}
}
image = new Image(Format.RGBA8, w, h, bb);
texture.setImage(image);
result.setTextureParam("Texture", VarType.Texture2D, texture);
}
// copying glow color
MatParam glowColor = material.getParam("GlowColor");
if (glowColor != null) {
ColorRGBA color = (ColorRGBA) glowColor.getValue();
result.setParam("GlowColor", VarType.Vector3, color);
}
return result;
}
/**
* This method indicates if the material has any kind of texture.
*
* @param material
* 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) {
if (material.getTextureParam(TEXTURE_TYPE_ALPHA) != 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_GLOW) != null) {
return true;
}
if (material.getTextureParam(TEXTURE_TYPE_NORMAL) != null) {
return true;
}
if (material.getTextureParam(TEXTURE_TYPE_SPECULAR) != null) {
return true;
}
}
return false;
}
/**
* This method indicates if the material has a texture of a specified type.
*
* @param material
* the material
* @param textureType
* 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) {
return material.getTextureParam(textureType) != null;
}
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/
*
* @param structureWithMaterials
* the structure containing the mesh data
* @param blenderContext
* the blender context
* @return a list of vertices colors, each color belongs to a single vertex
* @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");
MaterialContext[] materials = null;
if (ppMaterials.isNotNull()) {
List<Structure> materialStructures = ppMaterials.fetchData(blenderContext.getInputStream());
if (materialStructures != null && materialStructures.size() > 0) {
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
materials = new MaterialContext[materialStructures.size()];
int i = 0;
for (Structure s : materialStructures) {
materials[i++] = s == null ? null : materialHelper.toMaterialContext(s, blenderContext);
}
}
}
return materials;
}
/**
* This method converts rgb values to hsv values.
*
* @param r
* red value of the color
* @param g
* green value of the color
* @param b
* blue value of the color
* @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;
float cmin = r;
cmax = g > cmax ? g : cmax;
cmin = g < cmin ? g : cmin;
cmax = b > cmax ? b : cmax;
cmin = b < cmin ? b : cmin;
hsv[2] = cmax; /* value */
if (cmax != 0.0) {
hsv[1] = (cmax - cmin) / cmax;
} else {
hsv[1] = 0.0f;
hsv[0] = 0.0f;
}
if (hsv[1] == 0.0) {
hsv[0] = -1.0f;
} else {
float cdelta = cmax - cmin;
float rc = (cmax - r) / cdelta;
float gc = (cmax - g) / cdelta;
float bc = (cmax - b) / cdelta;
if (r == cmax) {
hsv[0] = bc - gc;
} else if (g == cmax) {
hsv[0] = 2.0f + rc - bc;
} else {
hsv[0] = 4.0f + gc - rc;
}
hsv[0] *= 60.0f;
if (hsv[0] < 0.0f) {
hsv[0] += 360.0f;
}
}
hsv[0] /= 360.0f;
if (hsv[0] < 0.0f) {
hsv[0] = 0.0f;
}
}
/**
* This method converts rgb values to hsv values.
*
* @param h
* hue
* @param s
* saturation
* @param v
* value
* @param rgb
* rgb result vector (should have 3 elements)
*/
public void hsvToRgb(float h, float s, float v, float[] rgb) {
h *= 360.0f;
if (s == 0.0) {
rgb[0] = rgb[1] = rgb[2] = v;
} else {
if (h == 360) {
h = 0;
} else {
h /= 60;
}
int i = (int) Math.floor(h);
float f = h - i;
float p = v * (1.0f - s);
float q = v * (1.0f - s * f);
float t = v * (1.0f - s * (1.0f - f));
switch (i) {
case 0:
rgb[0] = v;
rgb[1] = t;
rgb[2] = p;
break;
case 1:
rgb[0] = q;
rgb[1] = v;
rgb[2] = p;
break;
case 2:
rgb[0] = p;
rgb[1] = v;
rgb[2] = t;
break;
case 3:
rgb[0] = p;
rgb[1] = q;
rgb[2] = v;
break;
case 4:
rgb[0] = t;
rgb[1] = p;
rgb[2] = v;
break;
case 5:
rgb[0] = v;
rgb[1] = p;
rgb[2] = q;
break;
}
}
}
@Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0;
}
private static final Logger LOGGER = Logger.getLogger(MaterialHelper.class.getName());
protected static final float DEFAULT_SHININESS = 20.0f;
public static final String TEXTURE_TYPE_COLOR = "ColorMap";
public static final String TEXTURE_TYPE_DIFFUSE = "DiffuseMap";
public static final String TEXTURE_TYPE_NORMAL = "NormalMap";
public static final String TEXTURE_TYPE_SPECULAR = "SpecularMap";
public static final String TEXTURE_TYPE_GLOW = "GlowMap";
public static final String TEXTURE_TYPE_ALPHA = "AlphaMap";
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_CONE = Integer.valueOf(2);
public static final Integer ALPHA_MASK_HYPERBOLE = Integer.valueOf(3);
protected final Map<Integer, IAlphaMask> alphaMasks = new HashMap<Integer, IAlphaMask>();
/**
* The type of the material's diffuse shader.
*/
public static enum DiffuseShader {
LAMBERT, ORENNAYAR, TOON, MINNAERT, FRESNEL
}
/**
* The type of the material's specular shader.
*/
public static enum SpecularShader {
COOKTORRENCE, PHONG, BLINN, TOON, WARDISO
}
/**
* 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 MaterialHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, false);
// setting alpha masks
alphaMasks.put(ALPHA_MASK_NONE, new IAlphaMask() {
public void setImageSize(int width, int height) {
}
public byte getAlpha(float x, float y) {
return (byte) 255;
}
});
alphaMasks.put(ALPHA_MASK_CIRCLE, new IAlphaMask() {
private float r;
private float[] center;
public void setImageSize(int width, int height) {
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])));
return (byte) (d >= r ? 0 : 255);
}
});
alphaMasks.put(ALPHA_MASK_CONE, new IAlphaMask() {
private float r;
private float[] center;
public void setImageSize(int width, int height) {
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])));
return (byte) (d >= r ? 0 : -255.0f * d / r + 255.0f);
}
});
alphaMasks.put(ALPHA_MASK_HYPERBOLE, new IAlphaMask() {
private float r;
private float[] center;
public void setImageSize(int width, int height) {
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;
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
* structure with material data
* @param blenderContext
* the blender context
* @return jme material
* @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.");
MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
if (result != null) {
return result;
}
result = new MaterialContext(structure, blenderContext);
LOGGER.log(Level.FINE, "Material''s name: {0}", result.name);
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.
*
* @param material
* a material to be cloned without textures
* @param imageType
* 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 };
Map<String, Texture> textures = new HashMap<String, Texture>(textureParamNames.length);
for (String textureParamName : textureParamNames) {
MatParamTexture matParamTexture = material.getTextureParam(textureParamName);
if (matParamTexture != null) {
textures.put(textureParamName, matParamTexture.getTextureValue());
}
}
if (textures.isEmpty()) {
return material;
} else {
// clear all textures first so that wo de not waste resources cloning them
for (Entry<String, Texture> textureParamName : textures.entrySet()) {
String name = textureParamName.getValue().getName();
try {
int type = Integer.parseInt(name);
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);
}
}
Material result = material.clone();
// put the textures back in place
for (Entry<String, Texture> textureEntry : textures.entrySet()) {
material.setTexture(textureEntry.getKey(), textureEntry.getValue());
}
return result;
}
}
/**
* This method converts the given material into particles-usable material.
* The texture and glow color are being copied.
* The method assumes it receives the Lighting type of material.
* @param material
* the source material
* @param blenderContext
* 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");
// copying texture
MatParam diffuseMap = material.getParam("DiffuseMap");
if (diffuseMap != null) {
Texture texture = ((Texture) diffuseMap.getValue()).clone();
// applying alpha mask to the texture
Image image = texture.getImage();
ByteBuffer sourceBB = image.getData(0);
sourceBB.rewind();
int w = image.getWidth();
int h = image.getHeight();
ByteBuffer bb = BufferUtils.createByteBuffer(w * h * 4);
IAlphaMask iAlphaMask = alphaMasks.get(alphaMaskIndex);
iAlphaMask.setImageSize(w, h);
for (int x = 0; x < w; ++x) {
for (int y = 0; y < h; ++y) {
bb.put(sourceBB.get());
bb.put(sourceBB.get());
bb.put(sourceBB.get());
bb.put(iAlphaMask.getAlpha(x, y));
}
}
image = new Image(Format.RGBA8, w, h, bb);
texture.setImage(image);
result.setTextureParam("Texture", VarType.Texture2D, texture);
}
// copying glow color
MatParam glowColor = material.getParam("GlowColor");
if (glowColor != null) {
ColorRGBA color = (ColorRGBA) glowColor.getValue();
result.setParam("GlowColor", VarType.Vector3, color);
}
return result;
}
/**
* This method indicates if the material has any kind of texture.
*
* @param material
* 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) {
if (material.getTextureParam(TEXTURE_TYPE_ALPHA) != 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_GLOW) != null) {
return true;
}
if (material.getTextureParam(TEXTURE_TYPE_NORMAL) != null) {
return true;
}
if (material.getTextureParam(TEXTURE_TYPE_SPECULAR) != null) {
return true;
}
}
return false;
}
/**
* This method indicates if the material has a texture of a specified type.
*
* @param material
* the material
* @param textureType
* 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) {
return material.getTextureParam(textureType) != null;
}
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/
*
* @param structureWithMaterials
* the structure containing the mesh data
* @param blenderContext
* the blender context
* @return a list of vertices colors, each color belongs to a single vertex
* @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");
MaterialContext[] materials = null;
if (ppMaterials.isNotNull()) {
List<Structure> materialStructures = ppMaterials.fetchData(blenderContext.getInputStream());
if (materialStructures != null && materialStructures.size() > 0) {
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
materials = new MaterialContext[materialStructures.size()];
int i = 0;
for (Structure s : materialStructures) {
materials[i++] = s == null ? null : materialHelper.toMaterialContext(s, blenderContext);
}
}
}
return materials;
}
/**
* This method converts rgb values to hsv values.
*
* @param r
* red value of the color
* @param g
* green value of the color
* @param b
* blue value of the color
* @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;
float cmin = r;
cmax = g > cmax ? g : cmax;
cmin = g < cmin ? g : cmin;
cmax = b > cmax ? b : cmax;
cmin = b < cmin ? b : cmin;
hsv[2] = cmax; /* value */
if (cmax != 0.0) {
hsv[1] = (cmax - cmin) / cmax;
} else {
hsv[1] = 0.0f;
hsv[0] = 0.0f;
}
if (hsv[1] == 0.0) {
hsv[0] = -1.0f;
} else {
float cdelta = cmax - cmin;
float rc = (cmax - r) / cdelta;
float gc = (cmax - g) / cdelta;
float bc = (cmax - b) / cdelta;
if (r == cmax) {
hsv[0] = bc - gc;
} else if (g == cmax) {
hsv[0] = 2.0f + rc - bc;
} else {
hsv[0] = 4.0f + gc - rc;
}
hsv[0] *= 60.0f;
if (hsv[0] < 0.0f) {
hsv[0] += 360.0f;
}
}
hsv[0] /= 360.0f;
if (hsv[0] < 0.0f) {
hsv[0] = 0.0f;
}
}
/**
* This method converts rgb values to hsv values.
*
* @param h
* hue
* @param s
* saturation
* @param v
* value
* @param rgb
* rgb result vector (should have 3 elements)
*/
public void hsvToRgb(float h, float s, float v, float[] rgb) {
h *= 360.0f;
if (s == 0.0) {
rgb[0] = rgb[1] = rgb[2] = v;
} else {
if (h == 360) {
h = 0;
} else {
h /= 60;
}
int i = (int) Math.floor(h);
float f = h - i;
float p = v * (1.0f - s);
float q = v * (1.0f - s * f);
float t = v * (1.0f - s * (1.0f - f));
switch (i) {
case 0:
rgb[0] = v;
rgb[1] = t;
rgb[2] = p;
break;
case 1:
rgb[0] = q;
rgb[1] = v;
rgb[2] = p;
break;
case 2:
rgb[0] = p;
rgb[1] = v;
rgb[2] = t;
break;
case 3:
rgb[0] = p;
rgb[1] = q;
rgb[2] = v;
break;
case 4:
rgb[0] = t;
rgb[1] = p;
rgb[2] = v;
break;
case 5:
rgb[0] = v;
rgb[1] = p;
rgb[2] = q;
break;
}
}
}
@Override
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.util.BufferUtils;
/*package*/ class MeshBuilder {
private static final Logger LOGGER = Logger.getLogger(MeshBuilder.class.getName());
/** An array of reference vertices. */
private Vector3f[][] verticesAndNormals;
/** An list of vertices colors. */
private List<byte[]> verticesColors;
/** A variable that indicates if the model uses generated textures. */
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). */
/*package*/class MeshBuilder {
private static final Logger LOGGER = Logger.getLogger(MeshBuilder.class.getName());
/** An array of reference vertices. */
private Vector3f[][] verticesAndNormals;
/** An list of vertices colors. */
private List<byte[]> verticesColors;
/** A variable that indicates if the model uses generated textures. */
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).
*/
private Map<Integer, Map<Integer, List<Integer>>> globalVertexReferenceMap;
/** 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). */
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). */
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). */
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). */
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. */
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).
* 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.
* @param verticesAndNormals the reference vertices and normals array
* @param usesGeneratedTextures a variable that indicates if the model uses generated textures or not
* @param verticesAndNormals
* 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) {
this.verticesAndNormals = verticesAndNormals;
this.verticesColors = verticesColors;
this.usesGeneratedTextures = usesGeneratedTextures;
globalVertexReferenceMap = new HashMap<Integer, Map<Integer, List<Integer>>>(verticesAndNormals.length);
}
/**
* This method adds a point to the mesh.
* @param coordinates the coordinates of the point
* @param normal the point's normal vector
* @param materialNumber 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
*/
public void appendEdge(int v1, int v2, boolean smooth) {
LOGGER.warning("Appending single line not yet supported!");//TODO
}
/**
* This method adds a face 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 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
* @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);
public MeshBuilder(Vector3f[][] verticesAndNormals, List<byte[]> verticesColors, boolean usesGeneratedTextures) {
this.verticesAndNormals = verticesAndNormals;
this.verticesColors = verticesColors;
this.usesGeneratedTextures = usesGeneratedTextures;
globalVertexReferenceMap = new HashMap<Integer, Map<Integer, List<Integer>>>(verticesAndNormals.length);
}
/**
* This method adds a point to the mesh.
* @param coordinates
* the coordinates of the point
* @param normal
* the point's normal vector
* @param materialNumber
* 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
*/
public void appendEdge(int v1, int v2, boolean smooth) {
LOGGER.warning("Appending single line not yet supported!");// TODO
}
/**
* This method adds a face 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 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
* @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) {
indexList = new ArrayList<Integer>();
indexMap.put(materialNumber, indexList);
@ -102,187 +120,187 @@ import com.jme3.util.BufferUtils;
}
List<byte[]> vertexColorsList = vertexColorsMap != null ? vertexColorsMap.get(materialNumber) : null;
int[] vertexColorIndex = new int[] { 0, 1, 2 };
if(vertexColorsList == null && vertexColorsMap != null) {
vertexColorsList = new ArrayList<byte[]>();
vertexColorsMap.put(materialNumber, vertexColorsList);
if (vertexColorsList == null && vertexColorsMap != null) {
vertexColorsList = new ArrayList<byte[]>();
vertexColorsMap.put(materialNumber, vertexColorsList);
}
List<Vector3f> normalList = normalMap.get(materialNumber);
if (normalList == null) {
normalList = new ArrayList<Vector3f>();
normalMap.put(materialNumber, normalList);
normalList = new ArrayList<Vector3f>();
normalMap.put(materialNumber, normalList);
}
Map<Integer, List<Integer>> vertexReferenceMap = globalVertexReferenceMap.get(materialNumber);
if(vertexReferenceMap == null) {
vertexReferenceMap = new HashMap<Integer, List<Integer>>();
globalVertexReferenceMap.put(materialNumber, vertexReferenceMap);
if (vertexReferenceMap == null) {
vertexReferenceMap = new HashMap<Integer, List<Integer>>();
globalVertexReferenceMap.put(materialNumber, vertexReferenceMap);
}
List<Vector2f> uvCoordinatesList = null;
if(uvs != null) {
uvCoordinatesList = uvCoordinates.get(Integer.valueOf(materialNumber));
if(uvCoordinatesList == null) {
uvCoordinatesList = new ArrayList<Vector2f>();
uvCoordinates.put(Integer.valueOf(materialNumber), uvCoordinatesList);
}
if (uvs != null) {
uvCoordinatesList = uvCoordinates.get(Integer.valueOf(materialNumber));
if (uvCoordinatesList == null) {
uvCoordinatesList = new ArrayList<Vector2f>();
uvCoordinates.put(Integer.valueOf(materialNumber), uvCoordinatesList);
}
}
faceIndex *= 4;
if(quad) {
vertexColorIndex[1] = 2;
vertexColorIndex[2] = 3;
if (quad) {
vertexColorIndex[1] = 2;
vertexColorIndex[2] = 3;
}
//creating faces
Integer[] index = new Integer[] {v1, v2, v3};
if(smooth && !usesGeneratedTextures) {
for (int i = 0; i < 3; ++i) {
if(!vertexReferenceMap.containsKey(index[i])) {
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
vertexList.add(verticesAndNormals[index[i]][0]);
if(verticesColors != null) {
vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i]));
}
normalList.add(verticesAndNormals[index[i]][1]);
if(uvCoordinatesList != null) {
uvsMap.put(vertexList.size(), uvs[i]);
uvCoordinatesList.add(uvs[i]);
}
index[i] = vertexList.size() - 1;
} else if(uvCoordinatesList != null) {
boolean vertexAlreadyUsed = false;
for(Integer vertexIndex : vertexReferenceMap.get(index[i])) {
if(uvs[i].equals(uvsMap.get(vertexIndex))) {
vertexAlreadyUsed = true;
index[i] = vertexIndex;
break;
}
}
if(!vertexAlreadyUsed) {
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
uvsMap.put(vertexList.size(), uvs[i]);
vertexList.add(verticesAndNormals[index[i]][0]);
if(verticesColors != null) {
vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i]));
}
normalList.add(verticesAndNormals[index[i]][1]);
uvCoordinatesList.add(uvs[i]);
index[i] = vertexList.size() - 1;
}
} else {
index[i] = vertexList.indexOf(verticesAndNormals[index[i]][0]);
}
indexList.add(index[i]);
}
// creating faces
Integer[] index = new Integer[] { v1, v2, v3 };
if (smooth && !usesGeneratedTextures) {
for (int i = 0; i < 3; ++i) {
if (!vertexReferenceMap.containsKey(index[i])) {
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
vertexList.add(verticesAndNormals[index[i]][0]);
if (verticesColors != null) {
vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i]));
}
normalList.add(verticesAndNormals[index[i]][1]);
if (uvCoordinatesList != null) {
uvsMap.put(vertexList.size(), uvs[i]);
uvCoordinatesList.add(uvs[i]);
}
index[i] = vertexList.size() - 1;
} else if (uvCoordinatesList != null) {
boolean vertexAlreadyUsed = false;
for (Integer vertexIndex : vertexReferenceMap.get(index[i])) {
if (uvs[i].equals(uvsMap.get(vertexIndex))) {
vertexAlreadyUsed = true;
index[i] = vertexIndex;
break;
}
}
if (!vertexAlreadyUsed) {
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
uvsMap.put(vertexList.size(), uvs[i]);
vertexList.add(verticesAndNormals[index[i]][0]);
if (verticesColors != null) {
vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i]));
}
normalList.add(verticesAndNormals[index[i]][1]);
uvCoordinatesList.add(uvs[i]);
index[i] = vertexList.size() - 1;
}
} else {
index[i] = vertexList.indexOf(verticesAndNormals[index[i]][0]);
}
indexList.add(index[i]);
}
} else {
Vector3f n = smooth ? null : FastMath.computeNormal(verticesAndNormals[v1][0], verticesAndNormals[v2][0], verticesAndNormals[v3][0]);
for (int i = 0; i < 3; ++i) {
indexList.add(vertexList.size());
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
if(uvCoordinatesList != null) {
uvCoordinatesList.add(uvs[i]);
uvsMap.put(vertexList.size(), uvs[i]);
}
vertexList.add(verticesAndNormals[index[i]][0]);
if(verticesColors != null) {
vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i]));
}
normalList.add(smooth ? verticesAndNormals[index[i]][1] : n);
}
Vector3f n = smooth ? null : FastMath.computeNormal(verticesAndNormals[v1][0], verticesAndNormals[v2][0], verticesAndNormals[v3][0]);
for (int i = 0; i < 3; ++i) {
indexList.add(vertexList.size());
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
if (uvCoordinatesList != null) {
uvCoordinatesList.add(uvs[i]);
uvsMap.put(vertexList.size(), uvs[i]);
}
vertexList.add(verticesAndNormals[index[i]][0]);
if (verticesColors != null) {
vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i]));
}
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
*/
public Map<Integer, Map<Integer, List<Integer>>> getVertexReferenceMap() {
return globalVertexReferenceMap;
}
/**
* @param materialNumber
* the material index
* @return result vertices array
*/
public Vector3f[] getVertices(int materialNumber) {
return vertexMap.get(materialNumber).toArray(new Vector3f[vertexMap.get(materialNumber).size()]);
}
/**
* @param materialNumber
* the material index
* @return the amount of result vertices
*/
public int getVerticesAmount(int materialNumber) {
return vertexMap.get(materialNumber).size();
}
/**
* @param materialNumber
* the material index
* @return normals result array
*/
public Vector3f[] getNormals(int materialNumber) {
return normalMap.get(materialNumber).toArray(new Vector3f[normalMap.get(materialNumber).size()]);
}
/**
* @param materialNumber
* the material index
* @return the vertices colors buffer or null if no vertex colors is set
*/
public ByteBuffer getVertexColorsBuffer(int materialNumber) {
ByteBuffer result = null;
if (verticesColors != null && vertexColorsMap.get(materialNumber) != null) {
List<byte[]> data = vertexColorsMap.get(materialNumber);
result = BufferUtils.createByteBuffer(4 * data.size());
for (byte[] v : data) {
if (v != null) {
result.put(v[0]).put(v[1]).put(v[2]).put(v[3]);
} else {
result.put((byte)0).put((byte)0).put((byte)0).put((byte)0);
}
}
result.flip();
}
return result;
}
/**
* @return a map between material number and the mesh part vertices indices
*/
public Map<Integer, List<Integer>> getMeshesMap() {
return indexMap;
}
/**
* @return the amount of meshes the source mesh was split into (depends on the applied materials count)
*/
public int getMeshesPartAmount() {
return indexMap.size();
}
/**
* @param materialNumber
* the material number that is appied to the mesh
* @return UV coordinates of vertices that belong to the required mesh part
*/
public List<Vector2f> getUVCoordinates(int materialNumber) {
return uvCoordinates.get(materialNumber);
}
/**
* @return indicates if the mesh has UV coordinates
*/
public boolean hasUVCoordinates() {
return uvCoordinates.size() > 0;
}
/**
* @return <b>true</b> if the mesh has no vertices and <b>false</b> otherwise
*/
public boolean isEmpty() {
return vertexMap.size() == 0;
}
}
/**
* @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() {
return globalVertexReferenceMap;
}
/**
* @param materialNumber
* the material index
* @return result vertices array
*/
public Vector3f[] getVertices(int materialNumber) {
return vertexMap.get(materialNumber).toArray(new Vector3f[vertexMap.get(materialNumber).size()]);
}
/**
* @param materialNumber
* the material index
* @return the amount of result vertices
*/
public int getVerticesAmount(int materialNumber) {
return vertexMap.get(materialNumber).size();
}
/**
* @param materialNumber
* the material index
* @return normals result array
*/
public Vector3f[] getNormals(int materialNumber) {
return normalMap.get(materialNumber).toArray(new Vector3f[normalMap.get(materialNumber).size()]);
}
/**
* @param materialNumber
* the material index
* @return the vertices colors buffer or null if no vertex colors is set
*/
public ByteBuffer getVertexColorsBuffer(int materialNumber) {
ByteBuffer result = null;
if (verticesColors != null && vertexColorsMap.get(materialNumber) != null) {
List<byte[]> data = vertexColorsMap.get(materialNumber);
result = BufferUtils.createByteBuffer(4 * data.size());
for (byte[] v : data) {
if (v != null) {
result.put(v[0]).put(v[1]).put(v[2]).put(v[3]);
} else {
result.put((byte) 0).put((byte) 0).put((byte) 0).put((byte) 0);
}
}
result.flip();
}
return result;
}
/**
* @return a map between material number and the mesh part vertices indices
*/
public Map<Integer, List<Integer>> getMeshesMap() {
return indexMap;
}
/**
* @return the amount of meshes the source mesh was split into (depends on the applied materials count)
*/
public int getMeshesPartAmount() {
return indexMap.size();
}
/**
* @param materialNumber
* the material number that is appied to the mesh
* @return UV coordinates of vertices that belong to the required mesh part
*/
public List<Vector2f> getUVCoordinates(int materialNumber) {
return uvCoordinates.get(materialNumber);
}
/**
* @return indicates if the mesh has UV coordinates
*/
public boolean hasUVCoordinates() {
return uvCoordinates.size() > 0;
}
/**
* @return <b>true</b> if the mesh has no vertices and <b>false</b> otherwise
*/
public boolean isEmpty() {
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
* 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)
*/
public class MeshContext {
/** A map between material index and the geometry. */
private Map<Integer, Geometry> geometries = new HashMap<Integer, Geometry>();
/** The vertex reference map. */
private Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap;
/** The UV-coordinates for each of the geometries. */
private Map<Geometry, VertexBuffer> uvCoordinates = new HashMap<Geometry, VertexBuffer>();
/** Bind buffer for vertices is stored here and applied when required. */
private Map<Integer, VertexBuffer> bindPoseBuffer = new HashMap<Integer, VertexBuffer>();
/** Bind buffer for normals is stored here and applied when required. */
private Map<Integer, VertexBuffer> bindNormalBuffer = new HashMap<Integer, VertexBuffer>();
/** A map between material index and the geometry. */
private Map<Integer, Geometry> geometries = new HashMap<Integer, Geometry>();
/** The vertex reference map. */
private Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap;
/** The UV-coordinates for each of the geometries. */
private Map<Geometry, VertexBuffer> uvCoordinates = new HashMap<Geometry, VertexBuffer>();
/** Bind buffer for vertices is stored here and applied when required. */
private Map<Integer, VertexBuffer> bindPoseBuffer = new HashMap<Integer, VertexBuffer>();
/** Bind buffer for normals is stored here and applied when required. */
private Map<Integer, VertexBuffer> bindNormalBuffer = new HashMap<Integer, VertexBuffer>();
/**
* Adds a geometry for the specified material index.
* @param materialIndex the material index
* @param geometry the 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);
}
/**
* Adds a geometry for the specified material index.
* @param materialIndex
* the material index
* @param geometry
* the geometry
*/
public void putGeometry(Integer materialIndex, Geometry geometry) {
geometries.put(materialIndex, geometry);
}
/**
* This method sets the vertex reference map.
*
* @param vertexReferenceMap
* the vertex reference map
*/
public void setVertexReferenceMap(Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap) {
this.vertexReferenceMap = vertexReferenceMap;
}
/**
* @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();
}
/**
* This method adds the mesh's UV-coordinates.
*
* @param geometry
* the mesh that has the UV-coordinates
* @param vertexBuffer
* the mesh's UV-coordinates
*/
public void addUVCoordinates(Geometry geometry, VertexBuffer vertexBuffer) {
uvCoordinates.put(geometry, vertexBuffer);
}
/**
* 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 mesh's UV-coordinates.
*
* @param geometry
* the mesh
* @return the mesh's UV-coordinates
*/
public VertexBuffer getUVCoordinates(Geometry geometry) {
return uvCoordinates.get(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 bind buffer for vertices.
*
* @param materialIndex
* the index of the mesh's material
* @param bindNormalBuffer
* the bind buffer for vertices
*/
public void setBindNormalBuffer(int materialIndex,
VertexBuffer bindNormalBuffer) {
this.bindNormalBuffer.put(materialIndex, bindNormalBuffer);
}
/**
* This method sets the vertex reference map.
*
* @param vertexReferenceMap
* the vertex reference map
*/
public void setVertexReferenceMap(Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap) {
this.vertexReferenceMap = vertexReferenceMap;
}
/**
* @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 adds the mesh's UV-coordinates.
*
* @param geometry
* the mesh that has the UV-coordinates
* @param vertexBuffer
* the mesh's UV-coordinates
*/
public void addUVCoordinates(Geometry geometry, VertexBuffer vertexBuffer) {
uvCoordinates.put(geometry, vertexBuffer);
}
/**
* 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);
}
/**
* This method returns the mesh's UV-coordinates.
*
* @param geometry
* the mesh
* @return the mesh's UV-coordinates
*/
public VertexBuffer getUVCoordinates(Geometry geometry) {
return uvCoordinates.get(geometry);
}
/**
* @param materialIndex
* the index of the mesh's material
* @return the bind buffer for normals
*/
public VertexBuffer getBindPoseBuffer(int materialIndex) {
return bindPoseBuffer.get(materialIndex);
}
/**
* This method sets the bind buffer for vertices.
*
* @param materialIndex
* the index of the mesh's material
* @param bindNormalBuffer
* 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)
*/
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
* versions.
@ -72,10 +72,10 @@ public class MeshHelper extends AbstractBlenderHelper {
* @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
* a variable that indicates if the Y asxis is the UP axis or not
*/
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) {
materials = materialHelper.getMaterials(structure, blenderContext);
}
// reading vertices and their colors
Vector3f[][] verticesAndNormals = this.getVerticesAndNormals(structure, blenderContext);
List<byte[]> verticesColors = this.getVerticesColors(structure, blenderContext);
MeshBuilder meshBuilder = new MeshBuilder(verticesAndNormals, verticesColors, this.areGeneratedTexturesPresent(materials));
if(this.isBMeshCompatible(structure)) {
this.readBMesh(meshBuilder, structure, blenderContext);
if (this.isBMeshCompatible(structure)) {
this.readBMesh(meshBuilder, structure, blenderContext);
} else {
this.readTraditionalFaces(meshBuilder, structure, blenderContext);
this.readTraditionalFaces(meshBuilder, structure, blenderContext);
}
if(meshBuilder.isEmpty()) {
geometries = new ArrayList<Geometry>(0);
blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries);
if (meshBuilder.isEmpty()) {
geometries = new ArrayList<Geometry>(0);
blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries);
blenderContext.setMeshContext(structure.getOldMemoryAddress(), meshContext);
return geometries;
}
meshContext.setVertexReferenceMap(meshBuilder.getVertexReferenceMap());
// reading vertices groups (from the parent)
@ -142,32 +142,32 @@ public class MeshHelper extends AbstractBlenderHelper {
// creating the result meshes
geometries = new ArrayList<Geometry>(meshBuilder.getMeshesPartAmount());
//reading custom properties
// reading custom properties
Properties properties = this.loadProperties(structure, blenderContext);
// generating meshes
for (Entry<Integer, List<Integer>> meshEntry : meshBuilder.getMeshesMap().entrySet()) {
int materialIndex = meshEntry.getKey();
//key is the material index (or -1 if the material has no texture)
//value is a list of vertex indices
int materialIndex = meshEntry.getKey();
// key is the material index (or -1 if the material has no texture)
// value is a list of vertex indices
Mesh mesh = new Mesh();
// creating vertices indices for this mesh
List<Integer> indexList = meshEntry.getValue();
if(meshBuilder.getVerticesAmount(materialIndex) <= Short.MAX_VALUE) {
short[] indices = new short[indexList.size()];
if (meshBuilder.getVerticesAmount(materialIndex) <= Short.MAX_VALUE) {
short[] indices = new short[indexList.size()];
for (int i = 0; i < indexList.size(); ++i) {
indices[i] = indexList.get(i).shortValue();
}
mesh.setBuffer(Type.Index, 1, indices);
} else {
int[] indices = new int[indexList.size()];
int[] indices = new int[indexList.size()];
for (int i = 0; i < indexList.size(); ++i) {
indices[i] = indexList.get(i).intValue();
}
mesh.setBuffer(Type.Index, 1, indices);
}
VertexBuffer verticesBuffer = new VertexBuffer(Type.Position);
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)
VertexBuffer normalsBind = new VertexBuffer(Type.BindPoseNormal);
normalsBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.createFloatBuffer(meshBuilder.getNormals(materialIndex)));
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
if (verticesColors != null) {
@ -193,227 +193,225 @@ public class MeshHelper extends AbstractBlenderHelper {
// setting faces' normals
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
Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh);
if (properties != null && properties.getValue() != null) {
this.applyProperties(geometry, properties);
this.applyProperties(geometry, properties);
}
geometries.add(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.setMeshContext(structure.getOldMemoryAddress(), meshContext);
//apply materials only when all geometries are in place
if(materials != null) {
for(Geometry geometry : geometries) {
int materialNumber = meshContext.getMaterialIndex(geometry);
if(materials[materialNumber] != null) {
List<Vector2f> uvCoordinates = meshBuilder.getUVCoordinates(materialNumber);
MaterialContext materialContext = materials[materialNumber];
materialContext.applyMaterial(geometry, structure.getOldMemoryAddress(), uvCoordinates, blenderContext);
// apply materials only when all geometries are in place
if (materials != null) {
for (Geometry geometry : geometries) {
int materialNumber = meshContext.getMaterialIndex(geometry);
if (materials[materialNumber] != null) {
List<Vector2f> uvCoordinates = meshBuilder.getUVCoordinates(materialNumber);
MaterialContext materialContext = materials[materialNumber];
materialContext.applyMaterial(geometry, structure.getOldMemoryAddress(), uvCoordinates, blenderContext);
} else {
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, " +
"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.");
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, " + "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 {
//add UV coordinates if they are defined even if the material is not applied to the model
VertexBuffer uvCoordsBuffer = null;
if(meshBuilder.hasUVCoordinates()) {
List<Vector2f> uvs = meshBuilder.getUVCoordinates(0);
uvCoordsBuffer = new VertexBuffer(Type.TexCoord);
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()])));
}
for(Geometry geometry : geometries) {
geometry.setMaterial(blenderContext.getDefaultMaterial());
if(uvCoordsBuffer != null) {
geometry.getMesh().setBuffer(uvCoordsBuffer);
}
}
// add UV coordinates if they are defined even if the material is not applied to the model
VertexBuffer uvCoordsBuffer = null;
if (meshBuilder.hasUVCoordinates()) {
List<Vector2f> uvs = meshBuilder.getUVCoordinates(0);
uvCoordsBuffer = new VertexBuffer(Type.TexCoord);
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()])));
}
for (Geometry geometry : geometries) {
geometry.setMaterial(blenderContext.getDefaultMaterial());
if (uvCoordsBuffer != null) {
geometry.getMesh().setBuffer(uvCoordsBuffer);
}
}
}
return geometries;
}
/**
* Tells if the given mesh structure supports BMesh.
*
* @param meshStructure
* the mesh structure
* @return <b>true</b> if BMesh is supported and <b>false</b> otherwise
*/
/**
* Tells if the given mesh structure supports BMesh.
*
* @param meshStructure
* the mesh structure
* @return <b>true</b> if BMesh is supported and <b>false</b> otherwise
*/
private boolean isBMeshCompatible(Structure meshStructure) {
Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop");
Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly");
return pMLoop != null && pMPoly != null && pMLoop.isNotNull() && pMPoly.isNotNull();
Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop");
Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly");
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) {
if(materials != null) {
for(MaterialContext material : materials) {
if(material != null && material.hasGeneratedTextures()) {
return true;
}
}
}
return false;
if (materials != null) {
for (MaterialContext material : materials) {
if (material != null && material.hasGeneratedTextures()) {
return true;
}
}
}
return false;
}
/**
* 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[]>();
mCol = pMCol.fetchData(blenderContext.getInputStream());
for (Structure color : mCol) {
byte r = ((Number)color.getFieldValue("r")).byteValue();
byte g = ((Number)color.getFieldValue("g")).byteValue();
byte b = ((Number)color.getFieldValue("b")).byteValue();
byte a = ((Number)color.getFieldValue("a")).byteValue();
verticesColors.add(new byte[]{b, g, r, a});
byte r = ((Number) color.getFieldValue("r")).byteValue();
byte g = ((Number) color.getFieldValue("g")).byteValue();
byte b = ((Number) color.getFieldValue("b")).byteValue();
byte a = ((Number) color.getFieldValue("a")).byteValue();
verticesColors.add(new byte[] { b, g, r, a });
}
}
return verticesColors;
@ -464,21 +462,21 @@ public class MeshHelper extends AbstractBlenderHelper {
Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert");
List<Structure> mVerts = pMVert.fetchData(blenderContext.getInputStream());
if(this.fixUpAxis) {
for (int i = 0; i < count; ++i) {
if (this.fixUpAxis) {
for (int i = 0; i < count; ++i) {
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());
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 {
for (int i = 0; i < count; ++i) {
for (int i = 0; i < count; ++i) {
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());
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;

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

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

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

@ -14,65 +14,65 @@ import com.jme3.scene.plugins.blender.file.Structure;
* @author Marcin Roguski (Kaelthas)
*/
public abstract class Modifier {
public static final String ARRAY_MODIFIER_DATA = "ArrayModifierData";
public static final String ARMATURE_MODIFIER_DATA = "ArmatureModifierData";
public static final String PARTICLE_MODIFIER_DATA = "ParticleSystemModifierData";
public static final String MIRROR_MODIFIER_DATA = "MirrorModifierData";
public static final String SUBSURF_MODIFIER_DATA = "SubsurfModifierData";
public static final String OBJECT_ANIMATION_MODIFIER_DATA = "ObjectAnimationModifierData";
public static final String ARRAY_MODIFIER_DATA = "ArrayModifierData";
public static final String ARMATURE_MODIFIER_DATA = "ArmatureModifierData";
public static final String PARTICLE_MODIFIER_DATA = "ParticleSystemModifierData";
public static final String MIRROR_MODIFIER_DATA = "MirrorModifierData";
public static final String SUBSURF_MODIFIER_DATA = "SubsurfModifierData";
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>). */
protected boolean invalid;
/**
* 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
* sense to add the modifier to the list of object's modifiers.
*/
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 variable indicates if the modifier is invalid (<b>true</b>) or not (<b>false</b>). */
protected boolean invalid;
/**
* 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
* sense to add the modifier to the list of object's modifiers.
*/
protected boolean modifying = true;
/**
* This method returns blender's type of modifier.
*
* @return blender's type of modifier
*/
public abstract String getType();
/**
* Determines if the modifier can be applied multiple times over one mesh.
* At this moment only armature and object animation modifiers cannot be
* applied multiple times.
*
* @param modifierType
* the type name of the modifier
* @return <b>true</b> if the modifier can be applied many times and
* <b>false</b> otherwise
*/
public static boolean canBeAppliedMultipleTimes(String modifierType) {
return !(ARMATURE_MODIFIER_DATA.equals(modifierType) || OBJECT_ANIMATION_MODIFIER_DATA.equals(modifierType));
}
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;
}
/**
* 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.
*
* @return blender's type of modifier
*/
public abstract String getType();
/**
* Determines if the modifier can be applied multiple times over one mesh.
* At this moment only armature and object animation modifiers cannot be
* applied multiple times.
*
* @param modifierType
* the type name of the modifier
* @return <b>true</b> if the modifier can be applied many times and
* <b>false</b> otherwise
*/
public static boolean canBeAppliedMultipleTimes(String modifierType) {
return !(ARMATURE_MODIFIER_DATA.equals(modifierType) || OBJECT_ANIMATION_MODIFIER_DATA.equals(modifierType));
}
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 {
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.
* 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 ModifierHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis);
}
/**
* 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 ModifierHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis);
}
/**
* This method reads the given object's modifiers.
*
* @param objectStructure
* the object structure
* @param blenderContext
* the blender context
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
public Collection<Modifier> readModifiers(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
Set<String> alreadyReadModifiers = new HashSet<String>();
Collection<Modifier> result = new ArrayList<Modifier>();
Structure modifiersListBase = (Structure) objectStructure.getFieldValue("modifiers");
List<Structure> modifiers = modifiersListBase.evaluateListBase(blenderContext);
for (Structure modifierStructure : modifiers) {
String modifierType = modifierStructure.getType();
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() });
} else {
Modifier modifier = null;
if (Modifier.ARRAY_MODIFIER_DATA.equals(modifierStructure.getType())) {
modifier = new ArrayModifier(modifierStructure, blenderContext);
} else if (Modifier.MIRROR_MODIFIER_DATA.equals(modifierStructure.getType())) {
modifier = new MirrorModifier(modifierStructure, blenderContext);
} else if (Modifier.ARMATURE_MODIFIER_DATA.equals(modifierStructure.getType())) {
modifier = new ArmatureModifier(objectStructure, modifierStructure, blenderContext);
} else if (Modifier.PARTICLE_MODIFIER_DATA.equals(modifierStructure.getType())) {
modifier = new ParticlesModifier(modifierStructure, blenderContext);
}
/**
* This method reads the given object's modifiers.
*
* @param objectStructure
* the object structure
* @param blenderContext
* the blender context
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
public Collection<Modifier> readModifiers(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
Set<String> alreadyReadModifiers = new HashSet<String>();
Collection<Modifier> result = new ArrayList<Modifier>();
Structure modifiersListBase = (Structure) objectStructure.getFieldValue("modifiers");
List<Structure> modifiers = modifiersListBase.evaluateListBase(blenderContext);
for (Structure modifierStructure : modifiers) {
String modifierType = modifierStructure.getType();
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() });
} else {
Modifier modifier = null;
if (Modifier.ARRAY_MODIFIER_DATA.equals(modifierStructure.getType())) {
modifier = new ArrayModifier(modifierStructure, blenderContext);
} else if (Modifier.MIRROR_MODIFIER_DATA.equals(modifierStructure.getType())) {
modifier = new MirrorModifier(modifierStructure, blenderContext);
} else if (Modifier.ARMATURE_MODIFIER_DATA.equals(modifierStructure.getType())) {
modifier = new ArmatureModifier(objectStructure, modifierStructure, blenderContext);
} else if (Modifier.PARTICLE_MODIFIER_DATA.equals(modifierStructure.getType())) {
modifier = new ParticlesModifier(modifierStructure, blenderContext);
}
if (modifier != null) {
if(modifier.isModifying()) {
result.add(modifier);
blenderContext.addModifier(objectStructure.getOldMemoryAddress(), modifier);
alreadyReadModifiers.add(modifierType);
} else {
LOGGER.log(Level.WARNING, "The modifier {0} will cause no changes in the model. It will be ignored!", modifierStructure.getName());
}
} else {
LOGGER.log(Level.WARNING, "Unsupported modifier type: {0}", modifierStructure.getType());
}
}
}
if (modifier != null) {
if (modifier.isModifying()) {
result.add(modifier);
blenderContext.addModifier(objectStructure.getOldMemoryAddress(), modifier);
alreadyReadModifiers.add(modifierType);
} else {
LOGGER.log(Level.WARNING, "The modifier {0} will cause no changes in the model. It will be ignored!", modifierStructure.getName());
}
} else {
LOGGER.log(Level.WARNING, "Unsupported modifier type: {0}", modifierStructure.getType());
}
}
}
// at the end read object's animation modifier (object animation is
// either described by action or by ipo of the object)
Modifier modifier;
if (blenderVersion <= 249) {
modifier = this.readAnimationModifier249(objectStructure, blenderContext);
} else {
modifier = this.readAnimationModifier250(objectStructure, blenderContext);
}
if (modifier != null) {
result.add(modifier);
}
return result;
}
// at the end read object's animation modifier (object animation is
// either described by action or by ipo of the object)
Modifier modifier;
if (blenderVersion <= 249) {
modifier = this.readAnimationModifier249(objectStructure, blenderContext);
} else {
modifier = this.readAnimationModifier250(objectStructure, blenderContext);
}
if (modifier != null) {
result.add(modifier);
}
return result;
}
@Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return true;
}
@Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return true;
}
/**
* This method reads the object's animation modifier for blender version
* 2.49 and lower.
*
* @param objectStructure
* the object's structure
* @param blenderContext
* the blender context
* @return loaded modifier
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
private Modifier readAnimationModifier249(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
Modifier result = null;
Pointer pIpo = (Pointer) objectStructure.getFieldValue("ipo");
if (pIpo.isNotNull()) {
IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);
Structure ipoStructure = pIpo.fetchData(blenderContext.getInputStream()).get(0);
Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext);
if(ipo != null) {
result = new ObjectAnimationModifier(ipo, objectStructure.getName(), objectStructure.getOldMemoryAddress(), blenderContext);
blenderContext.addModifier(objectStructure.getOldMemoryAddress(), result);
}
}
return result;
}
/**
* This method reads the object's animation modifier for blender version
* 2.49 and lower.
*
* @param objectStructure
* the object's structure
* @param blenderContext
* the blender context
* @return loaded modifier
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
private Modifier readAnimationModifier249(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
Modifier result = null;
Pointer pIpo = (Pointer) objectStructure.getFieldValue("ipo");
if (pIpo.isNotNull()) {
IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);
Structure ipoStructure = pIpo.fetchData(blenderContext.getInputStream()).get(0);
Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext);
if (ipo != null) {
result = new ObjectAnimationModifier(ipo, objectStructure.getName(), objectStructure.getOldMemoryAddress(), blenderContext);
blenderContext.addModifier(objectStructure.getOldMemoryAddress(), result);
}
}
return result;
}
/**
* This method reads the object's animation modifier for blender version
* 2.50 and higher.
*
* @param objectStructure
* the object's structure
* @param blenderContext
* the blender context
* @return loaded modifier
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
private Modifier readAnimationModifier250(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
Modifier result = null;
Pointer pAnimData = (Pointer) objectStructure.getFieldValue("adt");
if (pAnimData.isNotNull()) {
Structure animData = pAnimData.fetchData(blenderContext.getInputStream()).get(0);
Pointer pAction = (Pointer) animData.getFieldValue("action");
if (pAction.isNotNull()) {
Structure actionStructure = pAction.fetchData(blenderContext.getInputStream()).get(0);
IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);
Ipo ipo = ipoHelper.fromAction(actionStructure, blenderContext);
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);
blenderContext.addModifier(objectStructure.getOldMemoryAddress(), result);
}
}
}
return result;
}
/**
* This method reads the object's animation modifier for blender version
* 2.50 and higher.
*
* @param objectStructure
* the object's structure
* @param blenderContext
* the blender context
* @return loaded modifier
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
private Modifier readAnimationModifier250(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
Modifier result = null;
Pointer pAnimData = (Pointer) objectStructure.getFieldValue("adt");
if (pAnimData.isNotNull()) {
Structure animData = pAnimData.fetchData(blenderContext.getInputStream()).get(0);
Pointer pAction = (Pointer) animData.getFieldValue("action");
if (pAction.isNotNull()) {
Structure actionStructure = pAction.fetchData(blenderContext.getInputStream()).get(0);
IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class);
Ipo ipo = ipoHelper.fromAction(actionStructure, blenderContext);
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);
blenderContext.addModifier(objectStructure.getOldMemoryAddress(), result);
}
}
}
return result;
}
}

@ -22,73 +22,73 @@ import com.jme3.scene.plugins.ogre.AnimData;
* @author Marcin Roguski (Kaelthas)
*/
/* 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. */
private AnimData animData;
/** Loaded animation data. */
private AnimData animData;
/**
* This constructor reads animation of the object itself (without bones) and
* stores it as an ArmatureModifierData modifier. The animation is returned
* 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
* animation should be working. The stored modifier is an anim data and
* additional data is given object's OMA.
*
* @param ipo
* the object's interpolation curves
* @param objectAnimationName
* the name of object's animation
* @param objectOMA
* the OMA of the object
* @param blenderContext
* the blender context
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
public ObjectAnimationModifier(Ipo ipo, String objectAnimationName, Long objectOMA, BlenderContext blenderContext) throws BlenderFileException {
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);
/**
* This constructor reads animation of the object itself (without bones) and
* stores it as an ArmatureModifierData modifier. The animation is returned
* 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
* animation should be working. The stored modifier is an anim data and
* additional data is given object's OMA.
*
* @param ipo
* the object's interpolation curves
* @param objectAnimationName
* the name of object's animation
* @param objectOMA
* the OMA of the object
* @param blenderContext
* the blender context
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
public ObjectAnimationModifier(Ipo ipo, String objectAnimationName, Long objectOMA, BlenderContext blenderContext) throws BlenderFileException {
int fps = blenderContext.getBlenderKey().getFps();
Animation animation = new Animation(objectAnimationName, ipo.getLastFrame() / (float)fps);
animation.setTracks(new SpatialTrack[] { track });
ArrayList<Animation> animations = new ArrayList<Animation>(1);
animations.add(animation);
Spatial object = (Spatial) blenderContext.getLoadedFeature(objectOMA, LoadedFeatureDataType.LOADED_FEATURE);
// calculating track
SpatialTrack track = (SpatialTrack) ipo.calculateTrack(-1, object.getLocalRotation(), 0, ipo.getLastFrame(), fps, true);
animData = new AnimData(null, animations);
blenderContext.setAnimData(objectOMA, animData);
}
Animation animation = new Animation(objectAnimationName, ipo.getLastFrame() / (float) fps);
animation.setTracks(new SpatialTrack[] { track });
ArrayList<Animation> animations = new ArrayList<Animation>(1);
animations.add(animation);
@Override
public Node apply(Node node, BlenderContext blenderContext) {
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);
}
animData = new AnimData(null, animations);
blenderContext.setAnimData(objectOMA, animData);
}
AnimControl control = new AnimControl(null);
control.setAnimations(anims);
node.addControl(control);
}
}
return node;
}
@Override
public Node apply(Node node, BlenderContext blenderContext) {
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);
}
@Override
public String getType() {
return Modifier.OBJECT_ANIMATION_MODIFIER_DATA;
}
AnimControl control = new AnimControl(null);
control.setAnimations(anims);
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)
*/
/* package */class ParticlesModifier extends Modifier {
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);
}
}
}
private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName());
@Override
public Node apply(Node node, BlenderContext blenderContext) {
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();
/** Loaded particles emitter. */
private ParticleEmitter particleEmitter;
// veryfying the alpha function for particles' texture
Integer alphaFunction = MaterialHelper.ALPHA_MASK_HYPERBOLE;
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));
/**
* 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);
}
}
}
// 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);
}
@Override
public Node apply(Node node, BlenderContext blenderContext) {
if (invalid) {
LOGGER.log(Level.WARNING, "Particles modifier is invalid! Cannot be applied to: {0}", node.getName());
return node;
}
node.attachChild(emitter);
return node;
}
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
ParticleEmitter emitter = particleEmitter.clone();
@Override
public String getType() {
return Modifier.PARTICLE_MODIFIER_DATA;
}
// veryfying the alpha function for particles' texture
Integer alphaFunction = MaterialHelper.ALPHA_MASK_HYPERBOLE;
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)
*/
public class ObjectHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(ObjectHelper.class.getName());
protected static final int OBJECT_TYPE_EMPTY = 0;
protected static final int OBJECT_TYPE_MESH = 1;
protected static final int OBJECT_TYPE_CURVE = 2;
protected static final int OBJECT_TYPE_SURF = 3;
protected static final int OBJECT_TYPE_TEXT = 4;
protected static final int OBJECT_TYPE_METABALL = 5;
protected static final int OBJECT_TYPE_LAMP = 10;
protected static final int OBJECT_TYPE_CAMERA = 11;
protected static final int OBJECT_TYPE_WAVE = 21;
protected static final int OBJECT_TYPE_LATTICE = 22;
protected static final int OBJECT_TYPE_ARMATURE = 25;
/**
* 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 ObjectHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis);
}
/**
* This method reads the given structure and createn an object that represents the data.
* @param objectStructure
* the object's structure
* @param blenderContext
* the blender context
* @return blener's object representation
* @throws BlenderFileException
* an exception is thrown when the given data is inapropriate
*/
public Object toObject(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
if(loadedResult != null) {
return loadedResult;
}
blenderContext.pushParent(objectStructure);
//get object data
int type = ((Number)objectStructure.getFieldValue("type")).intValue();
String name = objectStructure.getName();
LOGGER.log(Level.FINE, "Loading obejct: {0}", name);
int restrictflag = ((Number)objectStructure.getFieldValue("restrictflag")).intValue();
boolean visible = (restrictflag & 0x01) != 0;
Node result = null;
Pointer pParent = (Pointer)objectStructure.getFieldValue("parent");
Object parent = blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
if(parent == null && pParent.isNotNull()) {
Structure parentStructure = pParent.fetchData(blenderContext.getInputStream()).get(0);
parent = this.toObject(parentStructure, blenderContext);
}
Transform t = this.getTransformation(objectStructure, blenderContext);
try {
switch(type) {
case OBJECT_TYPE_EMPTY:
LOGGER.log(Level.FINE, "Importing empty.");
Node empty = new Node(name);
empty.setLocalTransform(t);
if(parent instanceof Node) {
((Node) parent).attachChild(empty);
}
result = empty;
break;
case OBJECT_TYPE_MESH:
LOGGER.log(Level.FINE, "Importing mesh.");
Node node = new Node(name);
node.setCullHint(visible ? CullHint.Always : CullHint.Inherit);
//reading mesh
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
Pointer pMesh = (Pointer)objectStructure.getFieldValue("data");
List<Structure> meshesArray = pMesh.fetchData(blenderContext.getInputStream());
List<Geometry> geometries = meshHelper.toMesh(meshesArray.get(0), blenderContext);
if (geometries != null){
for(Geometry geometry : geometries) {
private static final Logger LOGGER = Logger.getLogger(ObjectHelper.class.getName());
protected static final int OBJECT_TYPE_EMPTY = 0;
protected static final int OBJECT_TYPE_MESH = 1;
protected static final int OBJECT_TYPE_CURVE = 2;
protected static final int OBJECT_TYPE_SURF = 3;
protected static final int OBJECT_TYPE_TEXT = 4;
protected static final int OBJECT_TYPE_METABALL = 5;
protected static final int OBJECT_TYPE_LAMP = 10;
protected static final int OBJECT_TYPE_CAMERA = 11;
protected static final int OBJECT_TYPE_WAVE = 21;
protected static final int OBJECT_TYPE_LATTICE = 22;
protected static final int OBJECT_TYPE_ARMATURE = 25;
/**
* 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 ObjectHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis);
}
/**
* This method reads the given structure and createn an object that represents the data.
* @param objectStructure
* the object's structure
* @param blenderContext
* the blender context
* @return blener's object representation
* @throws BlenderFileException
* an exception is thrown when the given data is inapropriate
*/
public Object toObject(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
if (loadedResult != null) {
return loadedResult;
}
blenderContext.pushParent(objectStructure);
// get object data
int type = ((Number) objectStructure.getFieldValue("type")).intValue();
String name = objectStructure.getName();
LOGGER.log(Level.FINE, "Loading obejct: {0}", name);
int restrictflag = ((Number) objectStructure.getFieldValue("restrictflag")).intValue();
boolean visible = (restrictflag & 0x01) != 0;
Node result = null;
Pointer pParent = (Pointer) objectStructure.getFieldValue("parent");
Object parent = blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
if (parent == null && pParent.isNotNull()) {
Structure parentStructure = pParent.fetchData(blenderContext.getInputStream()).get(0);
parent = this.toObject(parentStructure, blenderContext);
}
Transform t = this.getTransformation(objectStructure, blenderContext);
try {
switch (type) {
case OBJECT_TYPE_EMPTY:
LOGGER.log(Level.FINE, "Importing empty.");
Node empty = new Node(name);
empty.setLocalTransform(t);
if (parent instanceof Node) {
((Node) parent).attachChild(empty);
}
result = empty;
break;
case OBJECT_TYPE_MESH:
LOGGER.log(Level.FINE, "Importing mesh.");
Node node = new Node(name);
node.setCullHint(visible ? CullHint.Always : CullHint.Inherit);
// reading mesh
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
Pointer pMesh = (Pointer) objectStructure.getFieldValue("data");
List<Structure> meshesArray = pMesh.fetchData(blenderContext.getInputStream());
List<Geometry> geometries = meshHelper.toMesh(meshesArray.get(0), blenderContext);
if (geometries != null) {
for (Geometry geometry : geometries) {
node.attachChild(geometry);
}
}
node.setLocalTransform(t);
//setting the parent
if(parent instanceof Node) {
((Node)parent).attachChild(node);
}
result = node;
break;
case OBJECT_TYPE_SURF:
case OBJECT_TYPE_CURVE:
LOGGER.log(Level.FINE, "Importing curve/nurb.");
Pointer pCurve = (Pointer)objectStructure.getFieldValue("data");
if(pCurve.isNotNull()) {
CurvesHelper curvesHelper = blenderContext.getHelper(CurvesHelper.class);
Structure curveData = pCurve.fetchData(blenderContext.getInputStream()).get(0);
List<Geometry> curves = curvesHelper.toCurve(curveData, blenderContext);
result = new Node(name);
for(Geometry curve : curves) {
((Node)result).attachChild(curve);
}
((Node)result).setLocalTransform(t);
}
break;
case OBJECT_TYPE_LAMP:
LOGGER.log(Level.FINE, "Importing lamp.");
Pointer pLamp = (Pointer)objectStructure.getFieldValue("data");
if(pLamp.isNotNull()) {
LightHelper lightHelper = blenderContext.getHelper(LightHelper.class);
List<Structure> lampsArray = pLamp.fetchData(blenderContext.getInputStream());
LightNode light = lightHelper.toLight(lampsArray.get(0), blenderContext);
if(light!=null) {
light.setName(name);
light.setLocalTransform(t);
}
result = light;
}
break;
case OBJECT_TYPE_CAMERA:
Pointer pCamera = (Pointer)objectStructure.getFieldValue("data");
if(pCamera.isNotNull()) {
CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class);
List<Structure> camerasArray = pCamera.fetchData(blenderContext.getInputStream());
CameraNode camera = cameraHelper.toCamera(camerasArray.get(0), blenderContext);
camera.setName(name);
camera.setLocalTransform(t);
result = camera;
}
break;
case OBJECT_TYPE_ARMATURE:
//need to create an empty node to properly create parent-children relationships between nodes
Node armature = new Node(name);
armature.setLocalTransform(t);
armature.setUserData(ArmatureHelper.ARMETURE_NODE_MARKER, Boolean.TRUE);
if(parent instanceof Node) {
((Node)parent).attachChild(armature);
}
result = armature;
break;
default:
LOGGER.log(Level.WARNING, "Unknown object type: {0}", type);
}
} finally {
blenderContext.popParent();
}
if(result != null) {
result.updateModelBound();//I prefer do compute bounding box here than read it from the file
blenderContext.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result);
//applying modifiers
LOGGER.log(Level.FINE, "Reading and applying object's modifiers.");
ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class);
Collection<Modifier> modifiers = modifierHelper.readModifiers(objectStructure, blenderContext);
for(Modifier modifier : modifiers) {
modifier.apply(result, blenderContext);
}
//loading constraints connected with this object
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
constraintHelper.loadConstraints(objectStructure, blenderContext);
//reading custom properties
if(blenderContext.getBlenderKey().isLoadObjectProperties()) {
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
if(result instanceof Spatial && properties != null && properties.getValue() != null) {
this.applyProperties((Spatial) result, properties);
}
}
}
return result;
}
/**
* This method calculates local transformation for the object. Parentage is taken under consideration.
* @param objectStructure
* the object's structure
* @return objects transformation relative to its parent
*/
@SuppressWarnings("unchecked")
public Transform getTransformation(Structure objectStructure, BlenderContext blenderContext) {
//these are transformations in global space
DynamicArray<Number> loc = (DynamicArray<Number>)objectStructure.getFieldValue("loc");
DynamicArray<Number> size = (DynamicArray<Number>)objectStructure.getFieldValue("size");
DynamicArray<Number> rot = (DynamicArray<Number>)objectStructure.getFieldValue("rot");
//load parent inverse matrix
Pointer pParent = (Pointer) objectStructure.getFieldValue("parent");
Matrix4f parentInv = pParent.isNull() ? Matrix4f.IDENTITY : this.getMatrix(objectStructure, "parentinv");
//create the global matrix (without the scale)
Matrix4f globalMatrix = new Matrix4f();
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()));
//compute local matrix
Matrix4f localMatrix = parentInv.mult(globalMatrix);
Vector3f translation = localMatrix.toTranslationVector();
Quaternion rotation = localMatrix.toRotationQuat();
Vector3f scale = this.getScale(parentInv).multLocal(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue());
if(fixUpAxis) {
float y = translation.y;
translation.y = translation.z;
translation.z = -y;
y = rotation.getY();
float z = rotation.getZ();
rotation.set(rotation.getX(), z, -y, rotation.getW());
y=scale.y;
scale.y = scale.z;
scale.z = y;
}
//create the result
Transform t = new Transform(translation, rotation);
t.setScale(scale);
return t;
}
/**
* 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.
* @param structure
* the structure with matrix data
* @param matrixName
* the name of the matrix
* @return the required matrix
*/
public Matrix4f getMatrix(Structure structure, String matrixName) {
return this.getMatrix(structure, matrixName, false);
}
/**
* This method returns the matrix of a given name for the given structure.
* It takes up axis into consideration.
* @param structure
* the structure with matrix data
* @param matrixName
* the name of the matrix
* @return the required matrix
*/
@SuppressWarnings("unchecked")
public Matrix4f getMatrix(Structure structure, String matrixName, boolean applyFixUpAxis) {
Matrix4f result = new Matrix4f();
DynamicArray<Number> obmat = (DynamicArray<Number>)structure.getFieldValue(matrixName);
int rowAndColumnSize = Math.abs((int)Math.sqrt(obmat.getTotalSize()));//the matrix must be square
for(int i = 0; i < rowAndColumnSize; ++i) {
for(int j = 0; j < rowAndColumnSize; ++j) {
result.set(i, j, obmat.get(j, i).floatValue());
}
}
if(applyFixUpAxis && fixUpAxis) {
Vector3f translation = result.toTranslationVector();
node.setLocalTransform(t);
// setting the parent
if (parent instanceof Node) {
((Node) parent).attachChild(node);
}
result = node;
break;
case OBJECT_TYPE_SURF:
case OBJECT_TYPE_CURVE:
LOGGER.log(Level.FINE, "Importing curve/nurb.");
Pointer pCurve = (Pointer) objectStructure.getFieldValue("data");
if (pCurve.isNotNull()) {
CurvesHelper curvesHelper = blenderContext.getHelper(CurvesHelper.class);
Structure curveData = pCurve.fetchData(blenderContext.getInputStream()).get(0);
List<Geometry> curves = curvesHelper.toCurve(curveData, blenderContext);
result = new Node(name);
for (Geometry curve : curves) {
((Node) result).attachChild(curve);
}
((Node) result).setLocalTransform(t);
}
break;
case OBJECT_TYPE_LAMP:
LOGGER.log(Level.FINE, "Importing lamp.");
Pointer pLamp = (Pointer) objectStructure.getFieldValue("data");
if (pLamp.isNotNull()) {
LightHelper lightHelper = blenderContext.getHelper(LightHelper.class);
List<Structure> lampsArray = pLamp.fetchData(blenderContext.getInputStream());
LightNode light = lightHelper.toLight(lampsArray.get(0), blenderContext);
if (light != null) {
light.setName(name);
light.setLocalTransform(t);
}
result = light;
}
break;
case OBJECT_TYPE_CAMERA:
Pointer pCamera = (Pointer) objectStructure.getFieldValue("data");
if (pCamera.isNotNull()) {
CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class);
List<Structure> camerasArray = pCamera.fetchData(blenderContext.getInputStream());
CameraNode camera = cameraHelper.toCamera(camerasArray.get(0), blenderContext);
camera.setName(name);
camera.setLocalTransform(t);
result = camera;
}
break;
case OBJECT_TYPE_ARMATURE:
// need to create an empty node to properly create parent-children relationships between nodes
Node armature = new Node(name);
armature.setLocalTransform(t);
armature.setUserData(ArmatureHelper.ARMETURE_NODE_MARKER, Boolean.TRUE);
if (parent instanceof Node) {
((Node) parent).attachChild(armature);
}
result = armature;
break;
default:
LOGGER.log(Level.WARNING, "Unknown object type: {0}", type);
}
} finally {
blenderContext.popParent();
}
if (result != null) {
result.updateModelBound();// I prefer do compute bounding box here than read it from the file
blenderContext.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result);
// applying modifiers
LOGGER.log(Level.FINE, "Reading and applying object's modifiers.");
ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class);
Collection<Modifier> modifiers = modifierHelper.readModifiers(objectStructure, blenderContext);
for (Modifier modifier : modifiers) {
modifier.apply(result, blenderContext);
}
// loading constraints connected with this object
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
constraintHelper.loadConstraints(objectStructure, blenderContext);
// reading custom properties
if (blenderContext.getBlenderKey().isLoadObjectProperties()) {
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
if (result instanceof Spatial && properties != null && properties.getValue() != null) {
this.applyProperties((Spatial) result, properties);
}
}
}
return result;
}
/**
* This method calculates local transformation for the object. Parentage is taken under consideration.
* @param objectStructure
* the object's structure
* @return objects transformation relative to its parent
*/
@SuppressWarnings("unchecked")
public Transform getTransformation(Structure objectStructure, BlenderContext blenderContext) {
// these are transformations in global space
DynamicArray<Number> loc = (DynamicArray<Number>) objectStructure.getFieldValue("loc");
DynamicArray<Number> size = (DynamicArray<Number>) objectStructure.getFieldValue("size");
DynamicArray<Number> rot = (DynamicArray<Number>) objectStructure.getFieldValue("rot");
// load parent inverse matrix
Pointer pParent = (Pointer) objectStructure.getFieldValue("parent");
Matrix4f parentInv = pParent.isNull() ? Matrix4f.IDENTITY : this.getMatrix(objectStructure, "parentinv");
// create the global matrix (without the scale)
Matrix4f globalMatrix = new Matrix4f();
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()));
// compute local matrix
Matrix4f localMatrix = parentInv.mult(globalMatrix);
Vector3f translation = localMatrix.toTranslationVector();
Quaternion rotation = localMatrix.toRotationQuat();
Vector3f scale = this.getScale(parentInv).multLocal(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue());
if (fixUpAxis) {
float y = translation.y;
translation.y = translation.z;
translation.z = -y;
y = rotation.getY();
float z = rotation.getZ();
rotation.set(rotation.getX(), z, -y, rotation.getW());
y = scale.y;
scale.y = scale.z;
scale.z = y;
}
// create the result
Transform t = new Transform(translation, rotation);
t.setScale(scale);
return t;
}
/**
* 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.
* @param structure
* the structure with matrix data
* @param matrixName
* the name of the matrix
* @return the required matrix
*/
public Matrix4f getMatrix(Structure structure, String matrixName) {
return this.getMatrix(structure, matrixName, false);
}
/**
* This method returns the matrix of a given name for the given structure.
* It takes up axis into consideration.
* @param structure
* the structure with matrix data
* @param matrixName
* the name of the matrix
* @return the required matrix
*/
@SuppressWarnings("unchecked")
public Matrix4f getMatrix(Structure structure, String matrixName, boolean applyFixUpAxis) {
Matrix4f result = new Matrix4f();
DynamicArray<Number> obmat = (DynamicArray<Number>) structure.getFieldValue(matrixName);
int rowAndColumnSize = Math.abs((int) Math.sqrt(obmat.getTotalSize()));// the matrix must be square
for (int i = 0; i < rowAndColumnSize; ++i) {
for (int j = 0; j < rowAndColumnSize; ++j) {
result.set(i, j, obmat.get(j, i).floatValue());
}
}
if (applyFixUpAxis && fixUpAxis) {
Vector3f translation = result.toTranslationVector();
Quaternion rotation = result.toRotationQuat();
Vector3f scale = this.getScale(result);
float y = translation.y;
translation.y = translation.z;
translation.z = -y;
y = rotation.getY();
float z = rotation.getZ();
rotation.set(rotation.getX(), z, -y, rotation.getW());
y=scale.y;
scale.y = scale.z;
scale.z = y;
result.loadIdentity();
result.setTranslation(translation);
result.setRotationQuaternion(rotation);
result.setScale(scale);
float y = translation.y;
translation.y = translation.z;
translation.z = -y;
y = rotation.getY();
float z = rotation.getZ();
rotation.set(rotation.getX(), z, -y, rotation.getW());
y = scale.y;
scale.y = scale.z;
scale.z = y;
result.loadIdentity();
result.setTranslation(translation);
result.setRotationQuaternion(rotation);
result.setScale(scale);
}
return result;
}
/**
* This method returns the scale from the given matrix.
*
* @param matrix
* the transformation matrix
* @return the scale from the given matrix
*/
public Vector3f getScale(Matrix4f matrix) {
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 scaleZ = (float) Math.sqrt(matrix.m02 * matrix.m02 + matrix.m12 * matrix.m12 + matrix.m22 * matrix.m22);
return new Vector3f(scaleX, scaleY, scaleZ);
}
@Override
public void clearState() {
fixUpAxis = false;
}
@Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
int lay = ((Number) structure.getFieldValue("lay")).intValue();
return (lay & blenderContext.getBlenderKey().getLayersToLoad()) != 0
&& (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0;
}
return result;
}
/**
* This method returns the scale from the given matrix.
*
* @param matrix
* the transformation matrix
* @return the scale from the given matrix
*/
public Vector3f getScale(Matrix4f matrix) {
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 scaleZ = (float) Math.sqrt(matrix.m02 * matrix.m02 + matrix.m12 * matrix.m12 + matrix.m22 * matrix.m22);
return new Vector3f(scaleX, scaleY, scaleZ);
}
@Override
public void clearState() {
fixUpAxis = false;
}
@Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
int lay = ((Number) structure.getFieldValue("lay")).intValue();
return (lay & blenderContext.getBlenderKey().getLayersToLoad()) != 0 && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0;
}
}

@ -17,364 +17,364 @@ import java.util.Map;
* @author Marcin Roguski (Kaelthas)
*/
public class Properties implements Cloneable {
// property type
public static final int IDP_STRING = 0;
public static final int IDP_INT = 1;
public static final int IDP_FLOAT = 2;
public static final int IDP_ARRAY = 5;
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_DOUBLE = 8;
// the following are valid for blender 2.5x+
public static final int IDP_IDPARRAY = 9;
public static final int IDP_NUMTYPES = 10;
// property type
public static final int IDP_STRING = 0;
public static final int IDP_INT = 1;
public static final int IDP_FLOAT = 2;
public static final int IDP_ARRAY = 5;
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_DOUBLE = 8;
// the following are valid for blender 2.5x+
public static final int IDP_IDPARRAY = 9;
public static final int IDP_NUMTYPES = 10;
protected static final String RNA_PROPERTY_NAME = "_RNA_UI";
/** 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 RNA_PROPERTY_NAME = "_RNA_UI";
/** Default name of the property (used if the name is not specified in blender file). */
protected static final String DEFAULT_NAME = "Unnamed property";
/** The name of the property. */
private String name;
/** The type of the property. */
private int type;
/** The subtype of the property. Defines the type of array's elements. */
private int subType;
/** The value of the property. */
private Object value;
/** The description of the property. */
private String description;
/** The name of the property. */
private String name;
/** The type of the property. */
private int type;
/** The subtype of the property. Defines the type of array's elements. */
private int subType;
/** The value of the property. */
private Object value;
/** The description of the property. */
private String description;
/**
* This method loads the property from the belnder file.
* @param idPropertyStructure
* the ID structure constining the property
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when the belnder file is somehow invalid
*/
public void load(Structure idPropertyStructure, BlenderContext blenderContext) throws BlenderFileException {
name = idPropertyStructure.getFieldValue("name").toString();
if (name == null || name.length() == 0) {
name = DEFAULT_NAME;
}
subType = ((Number) idPropertyStructure.getFieldValue("subtype")).intValue();
type = ((Number) idPropertyStructure.getFieldValue("type")).intValue();
/**
* This method loads the property from the belnder file.
* @param idPropertyStructure
* the ID structure constining the property
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when the belnder file is somehow invalid
*/
public void load(Structure idPropertyStructure, BlenderContext blenderContext) throws BlenderFileException {
name = idPropertyStructure.getFieldValue("name").toString();
if (name == null || name.length() == 0) {
name = DEFAULT_NAME;
}
subType = ((Number) idPropertyStructure.getFieldValue("subtype")).intValue();
type = ((Number) idPropertyStructure.getFieldValue("type")).intValue();
// reading the data
Structure data = (Structure) idPropertyStructure.getFieldValue("data");
int len = ((Number) idPropertyStructure.getFieldValue("len")).intValue();
switch (type) {
case IDP_STRING: {
Pointer pointer = (Pointer) data.getFieldValue("pointer");
BlenderInputStream bis = blenderContext.getInputStream();
FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress());
bis.setPosition(dataFileBlock.getBlockPosition());
value = bis.readString();
break;
}
case IDP_INT:
int intValue = ((Number) data.getFieldValue("val")).intValue();
value = Integer.valueOf(intValue);
break;
case IDP_FLOAT:
int floatValue = ((Number) data.getFieldValue("val")).intValue();
value = Float.valueOf(Float.intBitsToFloat(floatValue));
break;
case IDP_ARRAY: {
Pointer pointer = (Pointer) data.getFieldValue("pointer");
BlenderInputStream bis = blenderContext.getInputStream();
FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress());
bis.setPosition(dataFileBlock.getBlockPosition());
int elementAmount = dataFileBlock.getSize();
switch (subType) {
case IDP_INT:
elementAmount /= 4;
int[] intList = new int[elementAmount];
for (int i = 0; i < elementAmount; ++i) {
intList[i] = bis.readInt();
}
value = intList;
break;
case IDP_FLOAT:
elementAmount /= 4;
float[] floatList = new float[elementAmount];
for (int i = 0; i < elementAmount; ++i) {
floatList[i] = bis.readFloat();
}
value = floatList;
break;
case IDP_DOUBLE:
elementAmount /= 8;
double[] doubleList = new double[elementAmount];
for (int i = 0; i < elementAmount; ++i) {
doubleList[i] = bis.readDouble();
}
value = doubleList;
break;
default:
throw new IllegalStateException("Invalid array subtype: " + subType);
}
}
case IDP_GROUP:
Structure group = (Structure) data.getFieldValue("group");
List<Structure> dataList = group.evaluateListBase(blenderContext);
List<Properties> subProperties = new ArrayList<Properties>(len);
for (Structure d : dataList) {
Properties properties = new Properties();
properties.load(d, blenderContext);
subProperties.add(properties);
}
value = subProperties;
break;
case IDP_DOUBLE:
int doublePart1 = ((Number) data.getFieldValue("val")).intValue();
int doublePart2 = ((Number) data.getFieldValue("val2")).intValue();
long doubleVal = (long) doublePart2 << 32 | doublePart1;
value = Double.valueOf(Double.longBitsToDouble(doubleVal));
break;
case IDP_IDPARRAY: {
Pointer pointer = (Pointer) data.getFieldValue("pointer");
List<Structure> arrays = pointer.fetchData(blenderContext.getInputStream());
List<Object> result = new ArrayList<Object>(arrays.size());
Properties temp = new Properties();
for (Structure array : arrays) {
temp.load(array, blenderContext);
result.add(temp.value);
}
this.value = result;
break;
}
case IDP_NUMTYPES:
throw new UnsupportedOperationException();
// case IDP_ID://not yet implemented in blender
// return null;
default:
throw new IllegalStateException("Unknown custom property type: " + type);
}
this.completeLoading();
}
// reading the data
Structure data = (Structure) idPropertyStructure.getFieldValue("data");
int len = ((Number) idPropertyStructure.getFieldValue("len")).intValue();
switch (type) {
case IDP_STRING: {
Pointer pointer = (Pointer) data.getFieldValue("pointer");
BlenderInputStream bis = blenderContext.getInputStream();
FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress());
bis.setPosition(dataFileBlock.getBlockPosition());
value = bis.readString();
break;
}
case IDP_INT:
int intValue = ((Number) data.getFieldValue("val")).intValue();
value = Integer.valueOf(intValue);
break;
case IDP_FLOAT:
int floatValue = ((Number) data.getFieldValue("val")).intValue();
value = Float.valueOf(Float.intBitsToFloat(floatValue));
break;
case IDP_ARRAY: {
Pointer pointer = (Pointer) data.getFieldValue("pointer");
BlenderInputStream bis = blenderContext.getInputStream();
FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress());
bis.setPosition(dataFileBlock.getBlockPosition());
int elementAmount = dataFileBlock.getSize();
switch (subType) {
case IDP_INT:
elementAmount /= 4;
int[] intList = new int[elementAmount];
for (int i = 0; i < elementAmount; ++i) {
intList[i] = bis.readInt();
}
value = intList;
break;
case IDP_FLOAT:
elementAmount /= 4;
float[] floatList = new float[elementAmount];
for (int i = 0; i < elementAmount; ++i) {
floatList[i] = bis.readFloat();
}
value = floatList;
break;
case IDP_DOUBLE:
elementAmount /= 8;
double[] doubleList = new double[elementAmount];
for (int i = 0; i < elementAmount; ++i) {
doubleList[i] = bis.readDouble();
}
value = doubleList;
break;
default:
throw new IllegalStateException("Invalid array subtype: " + subType);
}
}
case IDP_GROUP:
Structure group = (Structure) data.getFieldValue("group");
List<Structure> dataList = group.evaluateListBase(blenderContext);
List<Properties> subProperties = new ArrayList<Properties>(len);
for (Structure d : dataList) {
Properties properties = new Properties();
properties.load(d, blenderContext);
subProperties.add(properties);
}
value = subProperties;
break;
case IDP_DOUBLE:
int doublePart1 = ((Number) data.getFieldValue("val")).intValue();
int doublePart2 = ((Number) data.getFieldValue("val2")).intValue();
long doubleVal = (long) doublePart2 << 32 | doublePart1;
value = Double.valueOf(Double.longBitsToDouble(doubleVal));
break;
case IDP_IDPARRAY: {
Pointer pointer = (Pointer) data.getFieldValue("pointer");
List<Structure> arrays = pointer.fetchData(blenderContext.getInputStream());
List<Object> result = new ArrayList<Object>(arrays.size());
Properties temp = new Properties();
for (Structure array : arrays) {
temp.load(array, blenderContext);
result.add(temp.value);
}
this.value = result;
break;
}
case IDP_NUMTYPES:
throw new UnsupportedOperationException();
// case IDP_ID://not yet implemented in blender
// return null;
default:
throw new IllegalStateException("Unknown custom property type: " + type);
}
this.completeLoading();
}
/**
* This method returns the name of the property.
* @return the name of the property
*/
public String getName() {
return name;
}
/**
* This method returns the name of the property.
* @return the name of the property
*/
public String getName() {
return name;
}
/**
* This method returns the description of the property.
* @return the description of the property
*/
public String getDescription() {
return description;
}
/**
* This method returns the description of the property.
* @return the description of the property
*/
public String getDescription() {
return description;
}
/**
* This method returns the type of the property.
* @return the type of the property
*/
public int getType() {
return type;
}
/**
* This method returns the type of the property.
* @return the type of the property
*/
public int getType() {
return type;
}
/**
* This method returns the value of the property.
* The type of the value depends on the type of the property.
* @return the value of the property
*/
public Object getValue() {
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;
}
/**
* This method returns the value of the property.
* The type of the value depends on the type of the property.
* @return the value of the property
*/
public Object getValue() {
return value;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
this.append(sb, new StringBuilder());
return sb.toString();
}
/**
* @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 appends the data of the property to the given string buffer.
* @param sb
* string buffer
* @param indent
* indent buffer
*/
@SuppressWarnings("unchecked")
private void append(StringBuilder sb, StringBuilder indent) {
sb.append(indent).append("name: ").append(name).append("\n\r");
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");
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);
}
/**
* 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;
}
/**
* This method should be called after the properties loading.
* 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
public String toString() {
StringBuilder sb = new StringBuilder();
this.append(sb, new StringBuilder());
return sb.toString();
}
// loading the descriptions
Map<String, String> descriptions = new HashMap<String, String>(groupProperties.size());
List<Properties> propertiesRNA = (List<Properties>) rnaUI.value;
for (Properties properties : propertiesRNA) {
String name = properties.name;
String description = null;
List<Properties> rnaData = (List<Properties>) properties.value;
for (Properties rna : rnaData) {
if ("description".equalsIgnoreCase(rna.name)) {
description = (String) rna.value;
break;
}
}
descriptions.put(name, description);
}
/**
* This method appends the data of the property to the given string buffer.
* @param sb
* string buffer
* @param indent
* indent buffer
*/
@SuppressWarnings("unchecked")
private void append(StringBuilder sb, StringBuilder indent) {
sb.append(indent).append("name: ").append(name).append("\n\r");
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");
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) {
properties.description = descriptions.get(properties.name);
}
}
}
}
/**
* This method should be called after the properties loading.
* 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
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (description == null ? 0 : description.hashCode());
result = prime * result + (name == null ? 0 : name.hashCode());
result = prime * result + subType;
result = prime * result + type;
result = prime * result + (value == null ? 0 : value.hashCode());
return result;
}
// loading the descriptions
Map<String, String> descriptions = new HashMap<String, String>(groupProperties.size());
List<Properties> propertiesRNA = (List<Properties>) rnaUI.value;
for (Properties properties : propertiesRNA) {
String name = properties.name;
String description = null;
List<Properties> rnaData = (List<Properties>) properties.value;
for (Properties rna : rnaData) {
if ("description".equalsIgnoreCase(rna.name)) {
description = (String) rna.value;
break;
}
}
descriptions.put(name, description);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (this.getClass() != obj.getClass()) {
return false;
}
Properties other = (Properties) obj;
if (description == null) {
if (other.description != null) {
return false;
}
} else if (!description.equals(other.description)) {
return false;
}
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;
}
// applying the descriptions
for (Properties properties : groupProperties) {
properties.description = descriptions.get(properties.name);
}
}
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (description == null ? 0 : description.hashCode());
result = prime * result + (name == null ? 0 : name.hashCode());
result = prime * result + subType;
result = prime * result + type;
result = prime * result + (value == null ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (this.getClass() != obj.getClass()) {
return false;
}
Properties other = (Properties) obj;
if (description == null) {
if (other.description != null) {
return false;
}
} else if (!description.equals(other.description)) {
return false;
}
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;
public class ParticlesHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(ParticlesHelper.class.getName());
// part->type
public static final int PART_EMITTER = 0;
public static final int PART_REACTOR = 1;
public static final int PART_HAIR = 2;
public static final int PART_FLUID = 3;
// part->flag
public static final int PART_REACT_STA_END =1;
public static final int PART_REACT_MULTIPLE =2;
public static final int PART_LOOP =4;
//public static final int PART_LOOP_INSTANT =8;
public static final int PART_HAIR_GEOMETRY =16;
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_TRAND =128;
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_DIE_ON_COL =1<<12;
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_SIZEMASS =1<<16;
public static final int PART_ABS_LENGTH =1<<15;
public static final int PART_ABS_TIME =1<<17;
public static final int PART_GLOB_TIME =1<<18;
public static final int PART_BOIDS_2D =1<<19;
public static final int PART_BRANCHING =1<<20;
public static final int PART_ANIM_BRANCHING =1<<21;
public static final int PART_SELF_EFFECT =1<<22;
public static final int PART_SYMM_BRANCHING =1<<24;
public static final int PART_HAIR_BSPLINE =1024;
public static final int PART_GRID_INVERT =1<<26;
public static final int PART_CHILD_EFFECT =1<<27;
public static final int PART_CHILD_SEAMS =1<<28;
public static final int PART_CHILD_RENDER =1<<29;
public static final int PART_CHILD_GUIDE =1<<30;
// part->from
public static final int PART_FROM_VERT =0;
public static final int PART_FROM_FACE =1;
public static final int PART_FROM_VOLUME =2;
public static final int PART_FROM_PARTICLE =3;
public static final int PART_FROM_CHILD =4;
// part->phystype
public static final int PART_PHYS_NO = 0;
public static final int PART_PHYS_NEWTON= 1;
public static final int PART_PHYS_KEYED = 2;
public static final int PART_PHYS_BOIDS = 3;
// part->draw_as
public static final int PART_DRAW_NOT = 0;
public static final int PART_DRAW_DOT = 1;
public static final int PART_DRAW_CIRC = 2;
public static final int PART_DRAW_CROSS = 3;
public static final int PART_DRAW_AXIS = 4;
public static final int PART_DRAW_LINE = 5;
public static final int PART_DRAW_PATH = 6;
public static final int PART_DRAW_OB = 7;
public static final int PART_DRAW_GR = 8;
public static final int PART_DRAW_BB = 9;
/**
* 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 ParticlesHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis);
}
@SuppressWarnings("unchecked")
public ParticleEmitter toParticleEmitter(Structure particleSystem, BlenderContext blenderContext) throws BlenderFileException {
ParticleEmitter result = null;
Pointer pParticleSettings = (Pointer) particleSystem.getFieldValue("part");
if(pParticleSettings.isNotNull()) {
Structure particleSettings = pParticleSettings.fetchData(blenderContext.getInputStream()).get(0);
int totPart = ((Number) particleSettings.getFieldValue("totpart")).intValue();
//draw type will be stored temporarily in the name (it is used during modifier applying operation)
int drawAs = ((Number)particleSettings.getFieldValue("draw_as")).intValue();
char nameSuffix;//P - point, L - line, N - None, B - Bilboard
switch(drawAs) {
case PART_DRAW_NOT:
nameSuffix = 'N';
totPart = 0;//no need to generate particles in this case
break;
case PART_DRAW_BB:
nameSuffix = 'B';
break;
case PART_DRAW_OB:
case PART_DRAW_GR:
nameSuffix = 'P';
LOGGER.warning("Neither object nor group particles supported yet! Using point representation instead!");//TODO: support groups and aobjects
break;
case PART_DRAW_LINE:
nameSuffix = 'L';
LOGGER.warning("Lines not yet supported! Using point representation instead!");//TODO: support lines
default://all others are rendered as points in blender
nameSuffix = 'P';
}
result = new ParticleEmitter(particleSettings.getName()+nameSuffix, Type.Triangle, totPart);
if(nameSuffix=='N') {
return result;//no need to set anything else
}
//setting the emitters shape (the shapes meshes will be set later during modifier applying operation)
int from = ((Number)particleSettings.getFieldValue("from")).intValue();
switch(from) {
case PART_FROM_VERT:
result.setShape(new EmitterMeshVertexShape());
break;
case PART_FROM_FACE:
result.setShape(new EmitterMeshFaceShape());
break;
case PART_FROM_VOLUME:
result.setShape(new EmitterMeshConvexHullShape());
break;
default:
LOGGER.warning("Default shape used! Unknown emitter shape value ('from' parameter: " + from + ')');
}
//reading acceleration
DynamicArray<Number> acc = (DynamicArray<Number>) particleSettings.getFieldValue("acc");
result.setGravity(-acc.get(0).floatValue(), -acc.get(1).floatValue(), -acc.get(2).floatValue());
//setting the colors
result.setEndColor(new ColorRGBA(1f, 1f, 1f, 1f));
result.setStartColor(new ColorRGBA(1f, 1f, 1f, 1f));
//reading size
float sizeFactor = nameSuffix=='B' ? 1.0f : 0.3f;
float size = ((Number)particleSettings.getFieldValue("size")).floatValue() * sizeFactor;
result.setStartSize(size);
result.setEndSize(size);
//reading lifetime
int fps = blenderContext.getBlenderKey().getFps();
float lifetime = ((Number)particleSettings.getFieldValue("lifetime")).floatValue() / fps;
float randlife = ((Number)particleSettings.getFieldValue("randlife")).floatValue() / fps;
result.setLowLife(lifetime * (1.0f - randlife));
result.setHighLife(lifetime);
//preparing influencer
ParticleInfluencer influencer;
int phystype = ((Number)particleSettings.getFieldValue("phystype")).intValue();
switch(phystype) {
case PART_PHYS_NEWTON:
influencer = new NewtonianParticleInfluencer();
((NewtonianParticleInfluencer)influencer).setNormalVelocity(((Number)particleSettings.getFieldValue("normfac")).floatValue());
((NewtonianParticleInfluencer)influencer).setVelocityVariation(((Number)particleSettings.getFieldValue("randfac")).floatValue());
((NewtonianParticleInfluencer)influencer).setSurfaceTangentFactor(((Number)particleSettings.getFieldValue("tanfac")).floatValue());
((NewtonianParticleInfluencer)influencer).setSurfaceTangentRotation(((Number)particleSettings.getFieldValue("tanphase")).floatValue());
break;
case PART_PHYS_BOIDS:
case PART_PHYS_KEYED://TODO: support other influencers
LOGGER.warning("Boids and Keyed particles physic not yet supported! Empty influencer used!");
case PART_PHYS_NO:
default:
influencer = new EmptyParticleInfluencer();
}
result.setParticleInfluencer(influencer);
}
return result;
}
@Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return true;
}
private static final Logger LOGGER = Logger.getLogger(ParticlesHelper.class.getName());
// part->type
public static final int PART_EMITTER = 0;
public static final int PART_REACTOR = 1;
public static final int PART_HAIR = 2;
public static final int PART_FLUID = 3;
// part->flag
public static final int PART_REACT_STA_END = 1;
public static final int PART_REACT_MULTIPLE = 2;
public static final int PART_LOOP = 4;
// public static final int PART_LOOP_INSTANT =8;
public static final int PART_HAIR_GEOMETRY = 16;
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_TRAND = 128;
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_DIE_ON_COL = 1 << 12;
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_SIZEMASS = 1 << 16;
public static final int PART_ABS_LENGTH = 1 << 15;
public static final int PART_ABS_TIME = 1 << 17;
public static final int PART_GLOB_TIME = 1 << 18;
public static final int PART_BOIDS_2D = 1 << 19;
public static final int PART_BRANCHING = 1 << 20;
public static final int PART_ANIM_BRANCHING = 1 << 21;
public static final int PART_SELF_EFFECT = 1 << 22;
public static final int PART_SYMM_BRANCHING = 1 << 24;
public static final int PART_HAIR_BSPLINE = 1024;
public static final int PART_GRID_INVERT = 1 << 26;
public static final int PART_CHILD_EFFECT = 1 << 27;
public static final int PART_CHILD_SEAMS = 1 << 28;
public static final int PART_CHILD_RENDER = 1 << 29;
public static final int PART_CHILD_GUIDE = 1 << 30;
// part->from
public static final int PART_FROM_VERT = 0;
public static final int PART_FROM_FACE = 1;
public static final int PART_FROM_VOLUME = 2;
public static final int PART_FROM_PARTICLE = 3;
public static final int PART_FROM_CHILD = 4;
// part->phystype
public static final int PART_PHYS_NO = 0;
public static final int PART_PHYS_NEWTON = 1;
public static final int PART_PHYS_KEYED = 2;
public static final int PART_PHYS_BOIDS = 3;
// part->draw_as
public static final int PART_DRAW_NOT = 0;
public static final int PART_DRAW_DOT = 1;
public static final int PART_DRAW_CIRC = 2;
public static final int PART_DRAW_CROSS = 3;
public static final int PART_DRAW_AXIS = 4;
public static final int PART_DRAW_LINE = 5;
public static final int PART_DRAW_PATH = 6;
public static final int PART_DRAW_OB = 7;
public static final int PART_DRAW_GR = 8;
public static final int PART_DRAW_BB = 9;
/**
* 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 ParticlesHelper(String blenderVersion, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis);
}
@SuppressWarnings("unchecked")
public ParticleEmitter toParticleEmitter(Structure particleSystem, BlenderContext blenderContext) throws BlenderFileException {
ParticleEmitter result = null;
Pointer pParticleSettings = (Pointer) particleSystem.getFieldValue("part");
if (pParticleSettings.isNotNull()) {
Structure particleSettings = pParticleSettings.fetchData(blenderContext.getInputStream()).get(0);
int totPart = ((Number) particleSettings.getFieldValue("totpart")).intValue();
// draw type will be stored temporarily in the name (it is used during modifier applying operation)
int drawAs = ((Number) particleSettings.getFieldValue("draw_as")).intValue();
char nameSuffix;// P - point, L - line, N - None, B - Bilboard
switch (drawAs) {
case PART_DRAW_NOT:
nameSuffix = 'N';
totPart = 0;// no need to generate particles in this case
break;
case PART_DRAW_BB:
nameSuffix = 'B';
break;
case PART_DRAW_OB:
case PART_DRAW_GR:
nameSuffix = 'P';
LOGGER.warning("Neither object nor group particles supported yet! Using point representation instead!");// TODO: support groups and aobjects
break;
case PART_DRAW_LINE:
nameSuffix = 'L';
LOGGER.warning("Lines not yet supported! Using point representation instead!");// TODO: support lines
default:// all others are rendered as points in blender
nameSuffix = 'P';
}
result = new ParticleEmitter(particleSettings.getName() + nameSuffix, Type.Triangle, totPart);
if (nameSuffix == 'N') {
return result;// no need to set anything else
}
// setting the emitters shape (the shapes meshes will be set later during modifier applying operation)
int from = ((Number) particleSettings.getFieldValue("from")).intValue();
switch (from) {
case PART_FROM_VERT:
result.setShape(new EmitterMeshVertexShape());
break;
case PART_FROM_FACE:
result.setShape(new EmitterMeshFaceShape());
break;
case PART_FROM_VOLUME:
result.setShape(new EmitterMeshConvexHullShape());
break;
default:
LOGGER.warning("Default shape used! Unknown emitter shape value ('from' parameter: " + from + ')');
}
// reading acceleration
DynamicArray<Number> acc = (DynamicArray<Number>) particleSettings.getFieldValue("acc");
result.setGravity(-acc.get(0).floatValue(), -acc.get(1).floatValue(), -acc.get(2).floatValue());
// setting the colors
result.setEndColor(new ColorRGBA(1f, 1f, 1f, 1f));
result.setStartColor(new ColorRGBA(1f, 1f, 1f, 1f));
// reading size
float sizeFactor = nameSuffix == 'B' ? 1.0f : 0.3f;
float size = ((Number) particleSettings.getFieldValue("size")).floatValue() * sizeFactor;
result.setStartSize(size);
result.setEndSize(size);
// reading lifetime
int fps = blenderContext.getBlenderKey().getFps();
float lifetime = ((Number) particleSettings.getFieldValue("lifetime")).floatValue() / fps;
float randlife = ((Number) particleSettings.getFieldValue("randlife")).floatValue() / fps;
result.setLowLife(lifetime * (1.0f - randlife));
result.setHighLife(lifetime);
// preparing influencer
ParticleInfluencer influencer;
int phystype = ((Number) particleSettings.getFieldValue("phystype")).intValue();
switch (phystype) {
case PART_PHYS_NEWTON:
influencer = new NewtonianParticleInfluencer();
((NewtonianParticleInfluencer) influencer).setNormalVelocity(((Number) particleSettings.getFieldValue("normfac")).floatValue());
((NewtonianParticleInfluencer) influencer).setVelocityVariation(((Number) particleSettings.getFieldValue("randfac")).floatValue());
((NewtonianParticleInfluencer) influencer).setSurfaceTangentFactor(((Number) particleSettings.getFieldValue("tanfac")).floatValue());
((NewtonianParticleInfluencer) influencer).setSurfaceTangentRotation(((Number) particleSettings.getFieldValue("tanphase")).floatValue());
break;
case PART_PHYS_BOIDS:
case PART_PHYS_KEYED:// TODO: support other influencers
LOGGER.warning("Boids and Keyed particles physic not yet supported! Empty influencer used!");
case PART_PHYS_NO:
default:
influencer = new EmptyParticleInfluencer();
}
result.setParticleInfluencer(influencer);
}
return result;
}
@Override
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
return true;
}
}

@ -48,310 +48,310 @@ import java.util.logging.Logger;
* @author Marcin Roguski (Kaelthas)
*/
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
public static final int IPO_LINEAR = 0;
public static final int IPO_EASE = 1;
public static final int IPO_BSPLINE = 2;
public static final int IPO_CARDINAL = 3;
public static final int IPO_CONSTANT = 4;
// interpolation types
public static final int IPO_LINEAR = 0;
public static final int IPO_EASE = 1;
public static final int IPO_BSPLINE = 2;
public static final int IPO_CARDINAL = 3;
public static final int IPO_CONSTANT = 4;
private int cursorsAmount, ipoType;
private ColorBandData[] data;
private int cursorsAmount, ipoType;
private ColorBandData[] data;
/**
* Constructor. Loads the data from the given structure.
* @param tex
* @param blenderContext
*/
@SuppressWarnings("unchecked")
public ColorBand(Structure tex, BlenderContext blenderContext) {
int flag = ((Number) tex.getFieldValue("flag")).intValue();
if ((flag & GeneratedTexture.TEX_COLORBAND) != 0) {
Pointer pColorband = (Pointer) tex.getFieldValue("coba");
try {
Structure colorbandStructure = pColorband.fetchData(blenderContext.getInputStream()).get(0);
this.cursorsAmount = ((Number) colorbandStructure.getFieldValue("tot")).intValue();
this.ipoType = ((Number) colorbandStructure.getFieldValue("ipotype")).intValue();
this.data = new ColorBandData[this.cursorsAmount];
DynamicArray<Structure> data = (DynamicArray<Structure>) colorbandStructure.getFieldValue("data");
for (int i = 0; i < this.cursorsAmount; ++i) {
this.data[i] = new ColorBandData(data.get(i));
}
} catch (BlenderFileException e) {
LOGGER.log(Level.WARNING, "Cannot fetch the colorband structure. The reason: {0}", e.getLocalizedMessage());
}
}
}
/**
* Constructor. Loads the data from the given structure.
* @param tex
* @param blenderContext
*/
@SuppressWarnings("unchecked")
public ColorBand(Structure tex, BlenderContext blenderContext) {
int flag = ((Number) tex.getFieldValue("flag")).intValue();
if ((flag & GeneratedTexture.TEX_COLORBAND) != 0) {
Pointer pColorband = (Pointer) tex.getFieldValue("coba");
try {
Structure colorbandStructure = pColorband.fetchData(blenderContext.getInputStream()).get(0);
this.cursorsAmount = ((Number) colorbandStructure.getFieldValue("tot")).intValue();
this.ipoType = ((Number) colorbandStructure.getFieldValue("ipotype")).intValue();
this.data = new ColorBandData[this.cursorsAmount];
DynamicArray<Structure> data = (DynamicArray<Structure>) colorbandStructure.getFieldValue("data");
for (int i = 0; i < this.cursorsAmount; ++i) {
this.data[i] = new ColorBandData(data.get(i));
}
} catch (BlenderFileException e) {
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
* 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 computes the values of the colorband.
*
* @return an array of 1001 elements and each element is float[4] object
* containing rgba values
*/
public float[][] computeValues() {
float[][] result = null;
if (data != null) {
result = new float[1001][4];// 1001 - amount of possible cursor
// positions; 4 = [r, g, b, a]
/**
* This method computes the values of the colorband.
*
* @return an array of 1001 elements and each element is float[4] object
* containing rgba values
*/
public float[][] computeValues() {
float[][] result = null;
if (data != null) {
result = new float[1001][4];// 1001 - amount of possible cursor
// positions; 4 = [r, g, b, a]
if (data.length == 1) {// special case; use only one color for all
// types of colorband interpolation
for (int i = 0; i < result.length; ++i) {
result[i][0] = data[0].r;
result[i][1] = data[0].g;
result[i][2] = data[0].b;
result[i][3] = data[0].a;
}
} 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.length == 1) {// special case; use only one color for all
// types of colorband interpolation
for (int i = 0; i < result.length; ++i) {
result[i][0] = data[0].r;
result[i][1] = data[0].g;
result[i][2] = data[0].b;
result[i][3] = data[0].a;
}
} 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) {
cbDataMap.put(Integer.valueOf(-1), data[0]);
} else {
ColorBandData cbData = data[0].clone();
cbData.pos = 0;
cbDataMap.put(Integer.valueOf(-1), cbData);
cbDataMap.put(Integer.valueOf(-2), cbData);
}
if (data[0].pos == 0) {
cbDataMap.put(Integer.valueOf(-1), data[0]);
} else {
ColorBandData cbData = data[0].clone();
cbData.pos = 0;
cbDataMap.put(Integer.valueOf(-1), cbData);
cbDataMap.put(Integer.valueOf(-2), cbData);
}
if (data[data.length - 1].pos == 1000) {
cbDataMap.put(Integer.valueOf(data.length), data[data.length - 1]);
} 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);
}
if (data[data.length - 1].pos == 1000) {
cbDataMap.put(Integer.valueOf(data.length), data[data.length - 1]);
} 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];
float f;
float[] ipoFactors = new float[4];
float f;
ColorBandData data0 = this.getColorbandData(currentCursor - 2, cbDataMap);
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);
ColorBandData data1 = this.getColorbandData(currentCursor - 1, cbDataMap);
ColorBandData data2 = this.getColorbandData(currentCursor, cbDataMap);
ColorBandData data3 = this.getColorbandData(currentCursor + 1, cbDataMap);
for (int i = 0; i < result.length; ++i) {
if (data2.pos != data1.pos) {
f = (i - data2.pos) / (float) (data1.pos - data2.pos);
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 (data2.pos != data1.pos) {
f = (i - data2.pos) / (float) (data1.pos - data2.pos);
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);
if (nextData.pos == i) {
++currentCursor;
data0 = cbDataMap.get(currentCursor - 2);
data1 = cbDataMap.get(currentCursor - 1);
data2 = cbDataMap.get(currentCursor);
data3 = cbDataMap.get(currentCursor + 1);
}
}
break;
case ColorBand.IPO_EASE:
float d,
a,
b,
d2;
for (int i = 0; i < result.length; ++i) {
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) {
++currentCursor;
data0 = cbDataMap.get(currentCursor - 2);
data1 = cbDataMap.get(currentCursor - 1);
data2 = cbDataMap.get(currentCursor);
data3 = cbDataMap.get(currentCursor + 1);
}
}
break;
case ColorBand.IPO_EASE:
float d,
a,
b,
d2;
for (int i = 0; i < result.length; ++i) {
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;
}
result[i][0] = b * currentData.r + a * nextData.r;
result[i][1] = b * currentData.g + a * nextData.g;
result[i][2] = b * currentData.b + a * nextData.b;
result[i][3] = b * currentData.a + a * nextData.a;
if (nextData.pos == i) {
currentData = data[currentCursor++];
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;
}
result[i][0] = b * currentData.r + a * nextData.r;
result[i][1] = b * currentData.g + a * nextData.g;
result[i][2] = b * currentData.b + a * nextData.b;
result[i][3] = b * currentData.a + a * nextData.a;
if (nextData.pos == i) {
currentData = data[currentCursor++];
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;
}
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
* interpolation.
*
* @param d
* distance factor for the current intensity
* @param ipoFactors
* table to store the results (size of the table must be at least
* 4)
*/
private void getIpoData(float d, float[] ipoFactors) {
float d2 = d * d;
float d3 = d2 * d;
if (ipoType == ColorBand.IPO_BSPLINE) {
ipoFactors[0] = -0.71f * d3 + 1.42f * d2 - 0.71f * d;
ipoFactors[1] = 1.29f * d3 - 2.29f * d2 + 1.0f;
ipoFactors[2] = -1.29f * d3 + 1.58f * d2 + 0.71f * d;
ipoFactors[3] = 0.71f * d3 - 0.71f * d2;
} else if (ipoType == ColorBand.IPO_CARDINAL) {
ipoFactors[0] = -0.16666666f * d3 + 0.5f * d2 - 0.5f * d + 0.16666666f;
ipoFactors[1] = 0.5f * d3 - d2 + 0.6666666f;
ipoFactors[2] = -0.5f * d3 + 0.5f * d2 + 0.5f * d + 0.16666666f;
ipoFactors[3] = 0.16666666f * d3;
} else {
throw new IllegalStateException("Cannot get interpolation data for other colorband types than B-spline and Cardinal!");
}
}
/**
* This method returns the data for either B-spline of Cardinal
* interpolation.
*
* @param d
* distance factor for the current intensity
* @param ipoFactors
* table to store the results (size of the table must be at least
* 4)
*/
private void getIpoData(float d, float[] ipoFactors) {
float d2 = d * d;
float d3 = d2 * d;
if (ipoType == ColorBand.IPO_BSPLINE) {
ipoFactors[0] = -0.71f * d3 + 1.42f * d2 - 0.71f * d;
ipoFactors[1] = 1.29f * d3 - 2.29f * d2 + 1.0f;
ipoFactors[2] = -1.29f * d3 + 1.58f * d2 + 0.71f * d;
ipoFactors[3] = 0.71f * d3 - 0.71f * d2;
} else if (ipoType == ColorBand.IPO_CARDINAL) {
ipoFactors[0] = -0.16666666f * d3 + 0.5f * d2 - 0.5f * d + 0.16666666f;
ipoFactors[1] = 0.5f * d3 - d2 + 0.6666666f;
ipoFactors[2] = -0.5f * d3 + 0.5f * d2 + 0.5f * d + 0.16666666f;
ipoFactors[3] = 0.16666666f * d3;
} else {
throw new IllegalStateException("Cannot get interpolation data for other colorband types than B-spline and Cardinal!");
}
}
/**
* Class to store the single colorband cursor data.
*
* @author Marcin Roguski (Kaelthas)
*/
private static class ColorBandData implements Cloneable {
public final float r, g, b, a;
public int pos;
/**
* Class to store the single colorband cursor data.
*
* @author Marcin Roguski (Kaelthas)
*/
private static class ColorBandData implements Cloneable {
public final float r, g, b, a;
public int pos;
public ColorBandData() {
r = g = b = 0;
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;
}
public ColorBandData() {
r = g = b = 0;
a = 1;
}
/**
* 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);
}
/**
* 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.
*
* @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
public ColorBandData clone() {
try {
return (ColorBandData) super.clone();
} catch (CloneNotSupportedException e) {
return new ColorBandData(this);
}
}
@Override
public ColorBandData clone() {
try {
return (ColorBandData) super.clone();
} catch (CloneNotSupportedException e) {
return new ColorBandData(this);
}
}
@Override
public String toString() {
return "P: " + this.pos + " [" + this.r + ", " + this.g + ", " + this.b + ", " + this.a + "]";
}
}
@Override
public String toString() {
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)
*/
/* package */class DDSTexelData {
/** The colors of the texes. */
private TexturePixel[][] colors;
/** The indexes of the texels. */
private long[] indexes;
/** The alphas of the texels (might be null). */
private float[][] alphas;
/** The indexels of texels alpha values (might be null). */
private long[] alphaIndexes;
/** The counter of texel x column. */
private int xCounter;
/** The counter of texel y row. */
private int yCounter;
/** The width of the image in pixels. */
private int widthInPixels;
/** The height of the image in pixels. */
private int heightInPixels;
/** The total texel count. */
private int xTexelCount;
/** The colors of the texes. */
private TexturePixel[][] colors;
/** The indexes of the texels. */
private long[] indexes;
/** The alphas of the texels (might be null). */
private float[][] alphas;
/** The indexels of texels alpha values (might be null). */
private long[] alphaIndexes;
/** The counter of texel x column. */
private int xCounter;
/** The counter of texel y row. */
private int yCounter;
/** The width of the image in pixels. */
private int widthInPixels;
/** The height of the image in pixels. */
private int heightInPixels;
/** The total texel count. */
private int xTexelCount;
/**
* Constructor. Allocates memory for data structures.
*
* @param compressedSize
* the size of compressed image (or its mipmap)
* @param widthToHeightRatio
* width/height ratio for the image
* @param format
* the format of the image
*/
public DDSTexelData(int compressedSize, float widthToHeightRatio, Format format) {
int texelsCount = compressedSize * 8 / format.getBitsPerPixel() / 16;
this.colors = new TexturePixel[texelsCount][];
this.indexes = new long[texelsCount];
this.widthInPixels = (int) (0.5f * (float) Math.sqrt(this.getSizeInBytes() / widthToHeightRatio));
this.heightInPixels = (int) (this.widthInPixels / widthToHeightRatio);
this.xTexelCount = widthInPixels >> 2;
this.yCounter = (heightInPixels >> 2) - 1;// xCounter is 0 for now
if (format == Format.DXT3 || format == Format.DXT5) {
this.alphas = new float[texelsCount][];
this.alphaIndexes = new long[texelsCount];
}
}
/**
* Constructor. Allocates memory for data structures.
*
* @param compressedSize
* the size of compressed image (or its mipmap)
* @param widthToHeightRatio
* width/height ratio for the image
* @param format
* the format of the image
*/
public DDSTexelData(int compressedSize, float widthToHeightRatio, Format format) {
int texelsCount = compressedSize * 8 / format.getBitsPerPixel() / 16;
this.colors = new TexturePixel[texelsCount][];
this.indexes = new long[texelsCount];
this.widthInPixels = (int) (0.5f * (float) Math.sqrt(this.getSizeInBytes() / widthToHeightRatio));
this.heightInPixels = (int) (this.widthInPixels / widthToHeightRatio);
this.xTexelCount = widthInPixels >> 2;
this.yCounter = (heightInPixels >> 2) - 1;// xCounter is 0 for now
if (format == Format.DXT3 || format == Format.DXT5) {
this.alphas = new float[texelsCount][];
this.alphaIndexes = new long[texelsCount];
}
}
/**
* This method adds a color and indexes for a texel.
*
* @param colors
* the colors of the texel
* @param indexes
* the indexes of the texel
*/
public void add(TexturePixel[] colors, int indexes) {
this.add(colors, indexes, null, 0);
}
/**
* This method adds a color and indexes for a texel.
*
* @param colors
* the colors of the texel
* @param indexes
* the indexes of the texel
*/
public void add(TexturePixel[] colors, int indexes) {
this.add(colors, indexes, null, 0);
}
/**
* This method adds a color, color indexes and alha values (with their
* indexes) for a texel.
*
* @param colors
* the colors of the texel
* @param indexes
* the indexes of the texel
* @param alphas
* the alpha values
* @param alphaIndexes
* the indexes of the given alpha values
*/
public void add(TexturePixel[] colors, int indexes, float[] alphas, long alphaIndexes) {
int index = yCounter * xTexelCount + xCounter;
this.colors[index] = colors;
this.indexes[index] = indexes;
if (alphas != null) {
this.alphas[index] = alphas;
this.alphaIndexes[index] = alphaIndexes;
}
++this.xCounter;
if (this.xCounter >= this.xTexelCount) {
this.xCounter = 0;
--this.yCounter;
}
}
/**
* This method adds a color, color indexes and alha values (with their
* indexes) for a texel.
*
* @param colors
* the colors of the texel
* @param indexes
* the indexes of the texel
* @param alphas
* the alpha values
* @param alphaIndexes
* the indexes of the given alpha values
*/
public void add(TexturePixel[] colors, int indexes, float[] alphas, long alphaIndexes) {
int index = yCounter * xTexelCount + xCounter;
this.colors[index] = colors;
this.indexes[index] = indexes;
if (alphas != null) {
this.alphas[index] = alphas;
this.alphaIndexes[index] = alphaIndexes;
}
++this.xCounter;
if (this.xCounter >= this.xTexelCount) {
this.xCounter = 0;
--this.yCounter;
}
}
/**
* This method returns the values of the pixel located on the given
* coordinates on the result image.
*
* @param x
* the x coordinate of the pixel
* @param y
* the y coordinate of the pixel
* @param result
* the table where the result is stored
* @return <b>true</b> if the pixel was correctly read and <b>false</b> if
* the position was outside the image sizes
*/
public boolean getRGBA8(int x, int y, byte[] result) {
int xTexetlIndex = x % widthInPixels / 4;
int yTexelIndex = y % heightInPixels / 4;
/**
* This method returns the values of the pixel located on the given
* coordinates on the result image.
*
* @param x
* the x coordinate of the pixel
* @param y
* the y coordinate of the pixel
* @param result
* the table where the result is stored
* @return <b>true</b> if the pixel was correctly read and <b>false</b> if
* the position was outside the image sizes
*/
public boolean getRGBA8(int x, int y, byte[] result) {
int xTexetlIndex = x % widthInPixels / 4;
int yTexelIndex = y % heightInPixels / 4;
int texelIndex = yTexelIndex * xTexelCount + xTexetlIndex;
if (texelIndex < colors.length) {
TexturePixel[] colors = this.colors[texelIndex];
int texelIndex = yTexelIndex * xTexelCount + xTexetlIndex;
if (texelIndex < colors.length) {
TexturePixel[] colors = this.colors[texelIndex];
// coordinates of the pixel in the selected texel
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)
// coordinates of the pixel in the selected texel
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)
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 pixelIndexInTexel = (y * 4 + x) * (int) FastMath.log(colors.length, 2);
int alphaIndexInTexel = alphas != null ? (y * 4 + x) * (int) FastMath.log(alphas.length, 2) : 0;
// getting the pixel
int indexMask = colors.length - 1;
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;
result[0] = (byte) (colors[colorIndex].red * 255.0f);
result[1] = (byte) (colors[colorIndex].green * 255.0f);
result[2] = (byte) (colors[colorIndex].blue * 255.0f);
result[3] = (byte) (alpha * 255.0f);
return true;
}
return false;
}
// getting the pixel
int indexMask = colors.length - 1;
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;
result[0] = (byte) (colors[colorIndex].red * 255.0f);
result[1] = (byte) (colors[colorIndex].green * 255.0f);
result[2] = (byte) (colors[colorIndex].blue * 255.0f);
result[3] = (byte) (alpha * 255.0f);
return true;
}
return false;
}
/**
* @return the size of the decompressed texel (in bytes)
*/
public int getSizeInBytes() {
// indexes.length == count of texels
return indexes.length * 16 * 4;
}
/**
* @return the size of the decompressed texel (in bytes)
*/
public int getSizeInBytes() {
// indexes.length == count of texels
return indexes.length * 16 * 4;
}
/**
* @return image (mipmap) width
*/
public int getPixelWidth() {
return widthInPixels;
}
/**
* @return image (mipmap) width
*/
public int getPixelWidth() {
return widthInPixels;
}
/**
* @return image (mipmap) height
*/
public int getPixelHeight() {
return heightInPixels;
}
/**
* @return image (mipmap) height
*/
public int getPixelHeight() {
return heightInPixels;
}
}

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

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

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

@ -52,442 +52,428 @@ import java.util.logging.Logger;
* @author Marcin Roguski (Kaelthas)
*/
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 {
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),
// still stored in vertex->accum, 1 D
TEXCO_PARTICLE_OR_STRAND(8192),
TEXCO_STRESS(16384),
TEXCO_SPEED(32768);
public static enum UVCoordinatesType {
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),
// 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) {
this.blenderValue = blenderValue;
}
private UVCoordinatesType(int blenderValue) {
this.blenderValue = blenderValue;
}
public static UVCoordinatesType valueOf(int blenderValue) {
for (UVCoordinatesType coordinatesType : UVCoordinatesType.values()) {
if (coordinatesType.blenderValue == blenderValue) {
return coordinatesType;
}
}
return null;
}
}
public static UVCoordinatesType valueOf(int blenderValue) {
for (UVCoordinatesType coordinatesType : UVCoordinatesType.values()) {
if (coordinatesType.blenderValue == blenderValue) {
return coordinatesType;
}
}
return null;
}
}
/**
* Generates a UV coordinates for 2D texture.
*
* @param mesh
* the mesh we generate UV's for
* @param texco
* UV coordinates type
* @param projection
* projection type
* @param geometries
* the geometris the given mesh belongs to (required to compute
* bounding box)
* @return UV coordinates for the given mesh
*/
public static List<Vector2f> generateUVCoordinatesFor2DTexture(Mesh mesh, UVCoordinatesType texco, UVProjectionType projection, List<Geometry> geometries) {
List<Vector2f> result = new ArrayList<Vector2f>();
BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries);
float[] inputData = null;// positions, normals, reflection vectors, etc.
/**
* Generates a UV coordinates for 2D texture.
*
* @param mesh
* the mesh we generate UV's for
* @param texco
* UV coordinates type
* @param projection
* projection type
* @param geometries
* the geometris the given mesh belongs to (required to compute
* bounding box)
* @return UV coordinates for the given mesh
*/
public static List<Vector2f> generateUVCoordinatesFor2DTexture(Mesh mesh, UVCoordinatesType texco, UVProjectionType projection, List<Geometry> geometries) {
List<Vector2f> result = new ArrayList<Vector2f>();
BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries);
float[] inputData = null;// positions, normals, reflection vectors, etc.
switch (texco) {
case TEXCO_ORCO:
inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position));
break;
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) };
for (int i = 0; i < mesh.getVertexCount(); ++i) {
result.add(data[i % 3]);
}
break;
case TEXCO_NORM:
inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal));
break;
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:
LOGGER.warning("Texture coordinates type not currently supported: " + texco);
break;
default:
throw new IllegalStateException("Unknown texture coordinates value: " + texco);
}
switch (texco) {
case TEXCO_ORCO:
inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position));
break;
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) };
for (int i = 0; i < mesh.getVertexCount(); ++i) {
result.add(data[i % 3]);
}
break;
case TEXCO_NORM:
inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal));
break;
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:
LOGGER.warning("Texture coordinates type not currently supported: " + texco);
break;
default:
throw new IllegalStateException("Unknown texture coordinates value: " + texco);
}
if (inputData != null) {// make projection calculations
switch (projection) {
case PROJECTION_FLAT:
inputData = UVProjectionGenerator.flatProjection(inputData, bb);
break;
case PROJECTION_CUBE:
inputData = UVProjectionGenerator.cubeProjection(inputData, bb);
break;
case PROJECTION_TUBE:
BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometries);
inputData = UVProjectionGenerator.tubeProjection(inputData, bt);
break;
case PROJECTION_SPHERE:
BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometries);
inputData = UVProjectionGenerator.sphereProjection(inputData, bs);
break;
default:
throw new IllegalStateException("Unknown projection type: " + projection);
}
for (int i = 0; i < inputData.length; i += 2) {
result.add(new Vector2f(inputData[i], inputData[i + 1]));
}
}
return result;
}
if (inputData != null) {// make projection calculations
switch (projection) {
case PROJECTION_FLAT:
inputData = UVProjectionGenerator.flatProjection(inputData, bb);
break;
case PROJECTION_CUBE:
inputData = UVProjectionGenerator.cubeProjection(inputData, bb);
break;
case PROJECTION_TUBE:
BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometries);
inputData = UVProjectionGenerator.tubeProjection(inputData, bt);
break;
case PROJECTION_SPHERE:
BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometries);
inputData = UVProjectionGenerator.sphereProjection(inputData, bs);
break;
default:
throw new IllegalStateException("Unknown projection type: " + projection);
}
for (int i = 0; i < inputData.length; i += 2) {
result.add(new Vector2f(inputData[i], inputData[i + 1]));
}
}
return result;
}
/**
* Generates a UV coordinates for 3D texture.
*
* @param mesh
* the mesh we generate UV's for
* @param texco
* UV coordinates type
* @param coordinatesSwappingIndexes
* coordinates swapping indexes
* @param geometries
* the geometris the given mesh belongs to (required to compute
* bounding box)
* @return UV coordinates for the given mesh
*/
public static List<Vector3f> generateUVCoordinatesFor3DTexture(Mesh mesh, UVCoordinatesType texco, int[] coordinatesSwappingIndexes, List<Geometry> geometries) {
List<Vector3f> result = new ArrayList<Vector3f>();
BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries);
float[] inputData = null;// positions, normals, reflection vectors, etc.
/**
* Generates a UV coordinates for 3D texture.
*
* @param mesh
* the mesh we generate UV's for
* @param texco
* UV coordinates type
* @param coordinatesSwappingIndexes
* coordinates swapping indexes
* @param geometries
* the geometris the given mesh belongs to (required to compute
* bounding box)
* @return UV coordinates for the given mesh
*/
public static List<Vector3f> generateUVCoordinatesFor3DTexture(Mesh mesh, UVCoordinatesType texco, int[] coordinatesSwappingIndexes, List<Geometry> geometries) {
List<Vector3f> result = new ArrayList<Vector3f>();
BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries);
float[] inputData = null;// positions, normals, reflection vectors, etc.
switch (texco) {
case TEXCO_ORCO:
inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position));
break;
case TEXCO_UV:
Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) };
for (int i = 0; i < mesh.getVertexCount(); ++i) {
Vector2f uv = data[i % 3];
result.add(new Vector3f(uv.x, uv.y, 0));
}
break;
case TEXCO_NORM:
inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal));
break;
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:
LOGGER.warning("Texture coordinates type not currently supported: " + texco);
break;
default:
throw new IllegalStateException("Unknown texture coordinates value: " + texco);
}
switch (texco) {
case TEXCO_ORCO:
inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position));
break;
case TEXCO_UV:
Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) };
for (int i = 0; i < mesh.getVertexCount(); ++i) {
Vector2f uv = data[i % 3];
result.add(new Vector3f(uv.x, uv.y, 0));
}
break;
case TEXCO_NORM:
inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal));
break;
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:
LOGGER.warning("Texture coordinates type not currently supported: " + texco);
break;
default:
throw new IllegalStateException("Unknown texture coordinates value: " + texco);
}
if (inputData != null) {// make calculations
Vector3f min = bb.getMin(null);
float[] uvCoordsResults = new float[4];// used for coordinates swapping
float[] ext = new float[] { bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2 };
for (int i = 0; i < ext.length; ++i) {
if (ext[i] == 0) {
ext[i] = 1;
}
}
// now transform the coordinates so that they are in the range of
// <0; 1>
for (int i = 0; i < inputData.length; i += 3) {
uvCoordsResults[1] = (inputData[i] - min.x) / ext[0];
uvCoordsResults[2] = (inputData[i + 1] - min.y) / ext[1];
uvCoordsResults[3] = (inputData[i + 2] - min.z) / ext[2];
result.add(new Vector3f(uvCoordsResults[coordinatesSwappingIndexes[0]], uvCoordsResults[coordinatesSwappingIndexes[1]], uvCoordsResults[coordinatesSwappingIndexes[2]]));
}
}
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);
}
}
if (inputData != null) {// make calculations
Vector3f min = bb.getMin(null);
float[] uvCoordsResults = new float[4];// used for coordinates swapping
float[] ext = new float[] { bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2 };
for (int i = 0; i < ext.length; ++i) {
if (ext[i] == 0) {
ext[i] = 1;
}
}
// now transform the coordinates so that they are in the range of
// <0; 1>
for (int i = 0; i < inputData.length; i += 3) {
uvCoordsResults[1] = (inputData[i] - min.x) / ext[0];
uvCoordsResults[2] = (inputData[i + 1] - min.y) / ext[1];
uvCoordsResults[3] = (inputData[i + 2] - min.z) / ext[2];
result.add(new Vector3f(uvCoordsResults[coordinatesSwappingIndexes[0]], uvCoordsResults[coordinatesSwappingIndexes[1]], uvCoordsResults[coordinatesSwappingIndexes[2]]));
}
}
return result;
}
/**
* This method returns the bounding box of the given geometries.
*
* @param geometries
* the list of geometries
* @return bounding box of the given geometries
*/
public static BoundingBox getBoundingBox(List<Geometry> geometries) {
BoundingBox result = null;
for (Geometry geometry : geometries) {
BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometry.getMesh());
if (result == null) {
result = bb;
} else {
result.merge(bb);
}
}
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 mesh.
*
* @param mesh
* the mesh
* @return bounding box of the given mesh
*/
/* package */static BoundingBox getBoundingBox(Mesh mesh) {
mesh.updateBound();
BoundingVolume bv = mesh.getBound();
if (bv instanceof BoundingBox) {
return (BoundingBox) bv;
} else if (bv instanceof BoundingSphere) {
BoundingSphere bs = (BoundingSphere) bv;
float r = bs.getRadius();
return new BoundingBox(bs.getCenter(), r, r, r);
} else {
throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName());
}
}
/**
* This method returns the bounding box of the given geometries.
*
* @param geometries
* the list of geometries
* @return bounding box of the given geometries
*/
public static BoundingBox getBoundingBox(List<Geometry> geometries) {
BoundingBox result = null;
for (Geometry geometry : geometries) {
BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometry.getMesh());
if (result == null) {
result = bb;
} else {
result.merge(bb);
}
}
return result;
}
/**
* This method returns the bounding sphere of the given geometries.
*
* @param geometries
* the list of geometries
* @return bounding sphere of the given geometries
*/
/* package */static BoundingSphere getBoundingSphere(List<Geometry> geometries) {
BoundingSphere result = null;
for (Geometry geometry : geometries) {
BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometry.getMesh());
if (result == null) {
result = bs;
} else {
result.merge(bs);
}
}
return result;
}
/**
* This method returns the bounding box of the given mesh.
*
* @param mesh
* the mesh
* @return bounding box of the given mesh
*/
/* package */static BoundingBox getBoundingBox(Mesh mesh) {
mesh.updateBound();
BoundingVolume bv = mesh.getBound();
if (bv instanceof BoundingBox) {
return (BoundingBox) bv;
} else if (bv instanceof BoundingSphere) {
BoundingSphere bs = (BoundingSphere) bv;
float r = bs.getRadius();
return new BoundingBox(bs.getCenter(), r, r, r);
} else {
throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName());
}
}
/**
* This method returns the bounding sphere of the given mesh.
*
* @param mesh
* the mesh
* @return bounding sphere of the given mesh
*/
/* package */static BoundingSphere getBoundingSphere(Mesh mesh) {
mesh.updateBound();
BoundingVolume bv = mesh.getBound();
if (bv instanceof BoundingBox) {
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());
}
}
/**
* This method returns the bounding sphere of the given geometries.
*
* @param geometries
* the list of geometries
* @return bounding sphere of the given geometries
*/
/* package */static BoundingSphere getBoundingSphere(List<Geometry> geometries) {
BoundingSphere result = null;
for (Geometry geometry : geometries) {
BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometry.getMesh());
if (result == null) {
result = bs;
} else {
result.merge(bs);
}
}
return result;
}
/**
* This method returns the bounding tube of the given mesh.
*
* @param mesh
* the mesh
* @return bounding tube of the given mesh
*/
/* package */static BoundingTube getBoundingTube(Mesh mesh) {
Vector3f center = new Vector3f();
float maxx = -Float.MAX_VALUE, minx = Float.MAX_VALUE;
float maxy = -Float.MAX_VALUE, miny = Float.MAX_VALUE;
float maxz = -Float.MAX_VALUE, minz = Float.MAX_VALUE;
/**
* This method returns the bounding sphere of the given mesh.
*
* @param mesh
* the mesh
* @return bounding sphere of the given mesh
*/
/* package */static BoundingSphere getBoundingSphere(Mesh mesh) {
mesh.updateBound();
BoundingVolume bv = mesh.getBound();
if (bv instanceof BoundingBox) {
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();
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);
/**
* This method returns the bounding tube of the given mesh.
*
* @param mesh
* the mesh
* @return bounding tube of the given mesh
*/
/* package */static BoundingTube getBoundingTube(Mesh mesh) {
Vector3f center = new Vector3f();
float maxx = -Float.MAX_VALUE, minx = Float.MAX_VALUE;
float maxy = -Float.MAX_VALUE, miny = Float.MAX_VALUE;
float maxz = -Float.MAX_VALUE, minz = Float.MAX_VALUE;
float radius = Math.max(maxx - minx, maxy - miny) * 0.5f;
return new BoundingTube(radius, maxz - minz, center);
}
FloatBuffer positions = mesh.getFloatBuffer(VertexBuffer.Type.Position);
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);
/**
* This method returns the bounding tube of the given geometries.
*
* @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;
}
float radius = Math.max(maxx - minx, maxy - miny) * 0.5f;
return new BoundingTube(radius, maxz - minz, center);
}
/**
* A very simple bounding tube. Id holds only the basic data bout the
* bounding tube and does not provide full functionality of a
* BoundingVolume. Should be replaced with a bounding tube that extends the
* BoundingVolume if it is ever created.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */static class BoundingTube {
private float radius;
private float height;
private Vector3f center;
/**
* This method returns the bounding tube of the given geometries.
*
* @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;
}
/**
* Constructor creates the tube with the given params.
*
* @param radius
* the radius of the tube
* @param height
* the height of the tube
* @param center
* the center of the tube
*/
public BoundingTube(float radius, float height, Vector3f center) {
this.radius = radius;
this.height = height;
this.center = center;
}
/**
* A very simple bounding tube. Id holds only the basic data bout the
* bounding tube and does not provide full functionality of a
* BoundingVolume. Should be replaced with a bounding tube that extends the
* BoundingVolume if it is ever created.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */static class BoundingTube {
private float radius;
private float height;
private Vector3f center;
/**
* This method merges two bounding tubes.
*
* @param boundingTube
* bounding tube to be merged woth the current one
* @return new instance of bounding tube representing the tubes' merge
*/
public BoundingTube merge(BoundingTube boundingTube) {
// get tubes (tube1.radius >= tube2.radius)
BoundingTube tube1, tube2;
if (this.radius >= boundingTube.radius) {
tube1 = this;
tube2 = boundingTube;
} else {
tube1 = boundingTube;
tube2 = this;
}
float r1 = tube1.radius;
float r2 = tube2.radius;
/**
* Constructor creates the tube with the given params.
*
* @param radius
* the radius of the tube
* @param height
* the height of the tube
* @param center
* the center of the tube
*/
public BoundingTube(float radius, float height, Vector3f center) {
this.radius = radius;
this.height = height;
this.center = center;
}
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);
float height = maxZ - minZ;
Vector3f distance = tube2.center.subtract(tube1.center);
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);
}
/**
* This method merges two bounding tubes.
*
* @param boundingTube
* bounding tube to be merged woth the current one
* @return new instance of bounding tube representing the tubes' merge
*/
public BoundingTube merge(BoundingTube boundingTube) {
// get tubes (tube1.radius >= tube2.radius)
BoundingTube tube1, tube2;
if (this.radius >= boundingTube.radius) {
tube1 = this;
tube2 = boundingTube;
} else {
tube1 = boundingTube;
tube2 = this;
}
float r1 = tube1.radius;
float r2 = tube2.radius;
/**
* @return the radius of the tube
*/
public float getRadius() {
return 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);
float height = maxZ - minZ;
Vector3f distance = tube2.center.subtract(tube1.center);
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
*/
public float getHeight() {
return height;
}
/**
* @return the radius of the tube
*/
public float getRadius() {
return radius;
}
/**
* @return the center of the tube
*/
public Vector3f getCenter() {
return center;
}
}
/**
* @return the height of the tube
*/
public float getHeight() {
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)
*/
/* package */class UVProjectionGenerator {
/**
* 2D texture mapping (projection)
* @author Marcin Roguski (Kaelthas)
*/
public static enum UVProjectionType {
PROJECTION_FLAT(0),
PROJECTION_CUBE(1),
PROJECTION_TUBE(2),
PROJECTION_SPHERE(3);
public final int blenderValue;
private UVProjectionType(int blenderValue) {
this.blenderValue = blenderValue;
}
public static UVProjectionType valueOf(int blenderValue) {
for(UVProjectionType projectionType : UVProjectionType.values()) {
if(projectionType.blenderValue == blenderValue) {
return projectionType;
}
}
return null;
}
}
/**
* Flat projection for 2D textures.
*
* @param mesh
* mesh that is to be projected
* @param bb
* the bounding box for projecting
* @return UV coordinates after the projection
*/
public static float[] flatProjection(float[] positions, BoundingBox bb) {
Vector3f min = bb.getMin(null);
float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getZExtent() * 2.0f };
float[] uvCoordinates = new float[positions.length / 3 * 2];
for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) {
uvCoordinates[j] = (positions[i] - min.x) / ext[0];
// skip the Y-coordinate
uvCoordinates[j + 1] = (positions[i + 2] - min.z) / ext[1];
}
return uvCoordinates;
}
/**
* Cube projection for 2D textures.
*
* @param positions
* points to be projected
* @param bb
* the bounding box for projecting
* @return UV coordinates after the projection
*/
public static float[] cubeProjection(float[] positions, BoundingBox bb) {
Triangle triangle = new Triangle();
Vector3f x = new Vector3f(1, 0, 0);
Vector3f y = new Vector3f(0, 1, 0);
Vector3f z = new Vector3f(0, 0, 1);
Vector3f min = bb.getMin(null);
float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getYExtent() * 2.0f, bb.getZExtent() * 2.0f };
float[] uvCoordinates = new float[positions.length/3*2];
float borderAngle = (float) Math.sqrt(2.0f) / 2.0f;
for (int i = 0, pointIndex = 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]);
triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]);
Vector3f n = triangle.getNormal();
float dotNX = Math.abs(n.dot(x));
float dorNY = Math.abs(n.dot(y));
float dotNZ = Math.abs(n.dot(z));
if (dotNX > borderAngle) {
if (dotNZ < borderAngle) {// discard X-coordinate
uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1];
uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2];
uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1];
uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2];
uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1];
uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2];
} else {// discard Z-coordinate
uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1];
uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0];
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];
}
} else {
if (dorNY > borderAngle) {// discard Y-coordinate
uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2];
uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2];
uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2];
} else {// discard Z-coordinate
uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1];
uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0];
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;
}
/**
* Tube projection for 2D textures.
*
* @param positions
* points to be projected
* @param bt
* the bounding tube for projecting
* @return UV coordinates after the projection
*/
public static float[] tubeProjection(float[] positions, BoundingTube bt) {
float[] uvCoordinates = new float[positions.length / 3 * 2];
Vector3f v = new Vector3f();
float cx = bt.getCenter().x, cz = bt.getCenter().z;
Vector3f uBase = new Vector3f(0, 0, -1);
float vBase = bt.getCenter().y - bt.getHeight() * 0.5f;
for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) {
// calculating U
v.set(positions[i]-cx, 0, positions[i + 2]-cz);
v.normalizeLocal();
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
angle = FastMath.TWO_PI - angle;
}
uvCoordinates[j] = angle / FastMath.TWO_PI;
// 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) {
triangle.set(0, positions[i], positions[i + 1], positions[i + 2]);
triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]);
triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]);
float sgn1 = Math.signum(triangle.get1().x-cx);
float sgn2 = Math.signum(triangle.get2().x-cx);
float sgn3 = Math.signum(triangle.get3().x-cx);
float xSideFactor = sgn1 + sgn2 + sgn3;
float ySideFactor = Math.signum(triangle.get1().z-cz)+
Math.signum(triangle.get2().z-cz)+
Math.signum(triangle.get3().z-cz);
if((xSideFactor>-3 || xSideFactor<3) && ySideFactor<0) {//the triangle is on the splitting plane
if(sgn1==1.0f) {
uvCoordinates[i/3*2] += 1.0f;
}
if(sgn2==1.0f) {
uvCoordinates[(i/3+1)*2] += 1.0f;
}
if(sgn3==1.0f) {
uvCoordinates[(i/3+2)*2] += 1.0f;
}
}
}
return uvCoordinates;
}
/**
* Sphere projection for 2D textures.
*
* @param positions
* points to be projected
* @param bb
* the bounding box for projecting
* @return UV coordinates after the projection
*/
public static float[] sphereProjection(float[] positions, BoundingSphere bs) {//TODO: rotate it to be vertical
float[] uvCoordinates = new float[positions.length / 3 * 2];
Vector3f v = new Vector3f();
float cx = bs.getCenter().x, cy = bs.getCenter().y, cz = bs.getCenter().z;
Vector3f uBase = new Vector3f(0, -1, 0);
Vector3f vBase = new Vector3f(0, 0, -1);
for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) {
// calculating U
v.set(positions[i]-cx, positions[i + 1] - cy, 0);
v.normalizeLocal();
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
angle = FastMath.TWO_PI - angle;
}
uvCoordinates[j] = angle / FastMath.TWO_PI;
// calculating V
v.set(positions[i]-cx, positions[i + 1]-cy, positions[i + 2]-cz);
v.normalizeLocal();
angle = v.angleBetween(vBase);// result between [0; PI]
uvCoordinates[j+1] = angle / FastMath.PI;
}
//looking for splitted triangles
Triangle triangle = new Triangle();
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]);
triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]);
float sgn1 = Math.signum(triangle.get1().x-cx);
float sgn2 = Math.signum(triangle.get2().x-cx);
float sgn3 = Math.signum(triangle.get3().x-cx);
float xSideFactor = sgn1 + sgn2 + sgn3;
float ySideFactor = Math.signum(triangle.get1().y-cy)+
Math.signum(triangle.get2().y-cy)+
Math.signum(triangle.get3().y-cy);
if((xSideFactor>-3 || xSideFactor<3) && ySideFactor<0) {//the triangle is on the splitting plane
if(sgn1==1.0f) {
uvCoordinates[i/3*2] += 1.0f;
}
if(sgn2==1.0f) {
uvCoordinates[(i/3+1)*2] += 1.0f;
}
if(sgn3==1.0f) {
uvCoordinates[(i/3+2)*2] += 1.0f;
}
}
}
return uvCoordinates;
}
/**
* 2D texture mapping (projection)
* @author Marcin Roguski (Kaelthas)
*/
public static enum UVProjectionType {
PROJECTION_FLAT(0), PROJECTION_CUBE(1), PROJECTION_TUBE(2), PROJECTION_SPHERE(3);
public final int blenderValue;
private UVProjectionType(int blenderValue) {
this.blenderValue = blenderValue;
}
public static UVProjectionType valueOf(int blenderValue) {
for (UVProjectionType projectionType : UVProjectionType.values()) {
if (projectionType.blenderValue == blenderValue) {
return projectionType;
}
}
return null;
}
}
/**
* Flat projection for 2D textures.
*
* @param mesh
* mesh that is to be projected
* @param bb
* the bounding box for projecting
* @return UV coordinates after the projection
*/
public static float[] flatProjection(float[] positions, BoundingBox bb) {
Vector3f min = bb.getMin(null);
float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getZExtent() * 2.0f };
float[] uvCoordinates = new float[positions.length / 3 * 2];
for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) {
uvCoordinates[j] = (positions[i] - min.x) / ext[0];
// skip the Y-coordinate
uvCoordinates[j + 1] = (positions[i + 2] - min.z) / ext[1];
}
return uvCoordinates;
}
/**
* Cube projection for 2D textures.
*
* @param positions
* points to be projected
* @param bb
* the bounding box for projecting
* @return UV coordinates after the projection
*/
public static float[] cubeProjection(float[] positions, BoundingBox bb) {
Triangle triangle = new Triangle();
Vector3f x = new Vector3f(1, 0, 0);
Vector3f y = new Vector3f(0, 1, 0);
Vector3f z = new Vector3f(0, 0, 1);
Vector3f min = bb.getMin(null);
float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getYExtent() * 2.0f, bb.getZExtent() * 2.0f };
float[] uvCoordinates = new float[positions.length / 3 * 2];
float borderAngle = (float) Math.sqrt(2.0f) / 2.0f;
for (int i = 0, pointIndex = 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]);
triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]);
Vector3f n = triangle.getNormal();
float dotNX = Math.abs(n.dot(x));
float dorNY = Math.abs(n.dot(y));
float dotNZ = Math.abs(n.dot(z));
if (dotNX > borderAngle) {
if (dotNZ < borderAngle) {// discard X-coordinate
uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1];
uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2];
uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1];
uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2];
uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1];
uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2];
} else {// discard Z-coordinate
uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1];
uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0];
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];
}
} else {
if (dorNY > borderAngle) {// discard Y-coordinate
uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2];
uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2];
uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2];
} else {// discard Z-coordinate
uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0];
uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1];
uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0];
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;
}
/**
* Tube projection for 2D textures.
*
* @param positions
* points to be projected
* @param bt
* the bounding tube for projecting
* @return UV coordinates after the projection
*/
public static float[] tubeProjection(float[] positions, BoundingTube bt) {
float[] uvCoordinates = new float[positions.length / 3 * 2];
Vector3f v = new Vector3f();
float cx = bt.getCenter().x, cz = bt.getCenter().z;
Vector3f uBase = new Vector3f(0, 0, -1);
float vBase = bt.getCenter().y - bt.getHeight() * 0.5f;
for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) {
// calculating U
v.set(positions[i] - cx, 0, positions[i + 2] - cz);
v.normalizeLocal();
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
angle = FastMath.TWO_PI - angle;
}
uvCoordinates[j] = angle / FastMath.TWO_PI;
// 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) {
triangle.set(0, positions[i], positions[i + 1], positions[i + 2]);
triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]);
triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]);
float sgn1 = Math.signum(triangle.get1().x - cx);
float sgn2 = Math.signum(triangle.get2().x - cx);
float sgn3 = Math.signum(triangle.get3().x - cx);
float xSideFactor = sgn1 + sgn2 + sgn3;
float ySideFactor = Math.signum(triangle.get1().z - cz) + Math.signum(triangle.get2().z - cz) + Math.signum(triangle.get3().z - cz);
if ((xSideFactor > -3 || xSideFactor < 3) && ySideFactor < 0) {// the triangle is on the splitting plane
if (sgn1 == 1.0f) {
uvCoordinates[i / 3 * 2] += 1.0f;
}
if (sgn2 == 1.0f) {
uvCoordinates[(i / 3 + 1) * 2] += 1.0f;
}
if (sgn3 == 1.0f) {
uvCoordinates[(i / 3 + 2) * 2] += 1.0f;
}
}
}
return uvCoordinates;
}
/**
* Sphere projection for 2D textures.
*
* @param positions
* points to be projected
* @param bb
* the bounding box for projecting
* @return UV coordinates after the projection
*/
public static float[] sphereProjection(float[] positions, BoundingSphere bs) {// TODO: rotate it to be vertical
float[] uvCoordinates = new float[positions.length / 3 * 2];
Vector3f v = new Vector3f();
float cx = bs.getCenter().x, cy = bs.getCenter().y, cz = bs.getCenter().z;
Vector3f uBase = new Vector3f(0, -1, 0);
Vector3f vBase = new Vector3f(0, 0, -1);
for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) {
// calculating U
v.set(positions[i] - cx, positions[i + 1] - cy, 0);
v.normalizeLocal();
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
angle = FastMath.TWO_PI - angle;
}
uvCoordinates[j] = angle / FastMath.TWO_PI;
// calculating V
v.set(positions[i] - cx, positions[i + 1] - cy, positions[i + 2] - cz);
v.normalizeLocal();
angle = v.angleBetween(vBase);// result between [0; PI]
uvCoordinates[j + 1] = angle / FastMath.PI;
}
// looking for splitted triangles
Triangle triangle = new Triangle();
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]);
triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]);
float sgn1 = Math.signum(triangle.get1().x - cx);
float sgn2 = Math.signum(triangle.get2().x - cx);
float sgn3 = Math.signum(triangle.get3().x - cx);
float xSideFactor = sgn1 + sgn2 + sgn3;
float ySideFactor = Math.signum(triangle.get1().y - cy) + Math.signum(triangle.get2().y - cy) + Math.signum(triangle.get3().y - cy);
if ((xSideFactor > -3 || xSideFactor < 3) && ySideFactor < 0) {// the triangle is on the splitting plane
if (sgn1 == 1.0f) {
uvCoordinates[i / 3 * 2] += 1.0f;
}
if (sgn2 == 1.0f) {
uvCoordinates[(i / 3 + 1) * 2] += 1.0f;
}
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)
*/
/* package */abstract class AbstractTextureBlender implements TextureBlender {
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;
}
private static final Logger LOGGER = Logger.getLogger(AbstractTextureBlender.class.getName());
/**
* The method that performs the ramp blending.
*
* @param type
* the blend type
* @param materialRGB
* 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);
protected int flag;
protected boolean negateTexture;
protected int blendType;
protected float[] materialColor;
protected float[] color;
protected float blendFactor;
switch (type) {
case MTEX_BLEND_HUE: {// FIXME: not working well for image textures (works fine for generated textures)
float[] colorTransformResult = new float[3];
materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult);
if (colorTransformResult[0] != 0.0f) {
float colH = colorTransformResult[0];
materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult);
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];
}
break;
}
case MTEX_BLEND_SAT: {
float[] colorTransformResult = new float[3];
materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult);
float h = colorTransformResult[0];
float s = colorTransformResult[1];
float v = colorTransformResult[2];
if (s != 0.0f) {
materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult);
materialHelper.hsvToRgb(h, oneMinusFactor * s + fac * colorTransformResult[1], v, materialRGB);
}
break;
}
case MTEX_BLEND_VAL: {
float[] rgbToHsv = new float[3];
float[] colToHsv = new float[3];
materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv);
materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv);
materialHelper.hsvToRgb(rgbToHsv[0], rgbToHsv[1], oneMinusFactor * rgbToHsv[2] + fac * colToHsv[2], materialRGB);
break;
}
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) {
materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv);
materialHelper.hsvToRgb(colToHsv[0], colToHsv[1], rgbToHsv[2], rgbToHsv);
materialRGB[0] = oneMinusFactor * materialRGB[0] + fac * rgbToHsv[0];
materialRGB[1] = oneMinusFactor * materialRGB[1] + fac * rgbToHsv[1];
materialRGB[2] = oneMinusFactor * materialRGB[2] + fac * rgbToHsv[2];
}
break;
}
default:
throw new IllegalStateException("Unknown ramp type: " + type);
}
}
public void copyBlendingData(TextureBlender textureBlender) {
if(textureBlender instanceof AbstractTextureBlender) {
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);
}
}
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;
}
/**
* The method that performs the ramp blending.
*
* @param type
* the blend type
* @param materialRGB
* 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) {
case MTEX_BLEND_HUE: {// FIXME: not working well for image textures (works fine for generated textures)
float[] colorTransformResult = new float[3];
materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult);
if (colorTransformResult[0] != 0.0f) {
float colH = colorTransformResult[0];
materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult);
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];
}
break;
}
case MTEX_BLEND_SAT: {
float[] colorTransformResult = new float[3];
materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult);
float h = colorTransformResult[0];
float s = colorTransformResult[1];
float v = colorTransformResult[2];
if (s != 0.0f) {
materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult);
materialHelper.hsvToRgb(h, oneMinusFactor * s + fac * colorTransformResult[1], v, materialRGB);
}
break;
}
case MTEX_BLEND_VAL: {
float[] rgbToHsv = new float[3];
float[] colToHsv = new float[3];
materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv);
materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv);
materialHelper.hsvToRgb(rgbToHsv[0], rgbToHsv[1], oneMinusFactor * rgbToHsv[2] + fac * colToHsv[2], materialRGB);
break;
}
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) {
materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv);
materialHelper.hsvToRgb(colToHsv[0], colToHsv[1], rgbToHsv[2], rgbToHsv);
materialRGB[0] = oneMinusFactor * materialRGB[0] + fac * rgbToHsv[0];
materialRGB[1] = oneMinusFactor * materialRGB[1] + fac * rgbToHsv[1];
materialRGB[2] = oneMinusFactor * materialRGB[2] + fac * rgbToHsv[2];
}
break;
}
default:
throw new IllegalStateException("Unknown ramp type: " + type);
}
}
public void copyBlendingData(TextureBlender textureBlender) {
if (textureBlender instanceof AbstractTextureBlender) {
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)
*/
public interface TextureBlender {
// types of blending
int MTEX_BLEND = 0;
int MTEX_MUL = 1;
int MTEX_ADD = 2;
int MTEX_SUB = 3;
int MTEX_DIV = 4;
int MTEX_DARK = 5;
int MTEX_DIFF = 6;
int MTEX_LIGHT = 7;
int MTEX_SCREEN = 8;
int MTEX_OVERLAY = 9;
int MTEX_BLEND_HUE = 10;
int MTEX_BLEND_SAT = 11;
int MTEX_BLEND_VAL = 12;
int MTEX_BLEND_COLOR = 13;
int MTEX_NUM_BLENDTYPES = 14;
// types of blending
int MTEX_BLEND = 0;
int MTEX_MUL = 1;
int MTEX_ADD = 2;
int MTEX_SUB = 3;
int MTEX_DIV = 4;
int MTEX_DARK = 5;
int MTEX_DIFF = 6;
int MTEX_LIGHT = 7;
int MTEX_SCREEN = 8;
int MTEX_OVERLAY = 9;
int MTEX_BLEND_HUE = 10;
int MTEX_BLEND_SAT = 11;
int MTEX_BLEND_VAL = 12;
int MTEX_BLEND_COLOR = 13;
int MTEX_NUM_BLENDTYPES = 14;
/**
* 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
* created. The input texture is NOT.
*
* @param image
* the image we use in blending
* @param baseImage
* the texture that is underneath the current texture (its pixels
* will be used instead of material color)
* @param blenderContext
* the blender context
* @return new image that was created after the blending
*/
Image blend(Image image, Image baseImage, BlenderContext blenderContext);
/**
* 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
* created. The input texture is NOT.
*
* @param image
* the image we use in blending
* @param baseImage
* the texture that is underneath the current texture (its pixels
* will be used instead of material color)
* @param blenderContext
* the blender context
* @return new image that was created after the blending
*/
Image blend(Image image, Image baseImage, BlenderContext blenderContext);
/**
* Copies blending data. Used for blending type format changing.
*
* @param textureBlender
* the blend data that should be copied
*/
void copyBlendingData(TextureBlender textureBlender);
/**
* Copies blending data. Used for blending type format changing.
*
* @param textureBlender
* the blend data that should be copied
*/
void copyBlendingData(TextureBlender textureBlender);
}

@ -43,202 +43,184 @@ import java.nio.ByteBuffer;
import java.util.ArrayList;
/**
* 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
* 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
* @author Marcin Roguski (Kaelthas)
*/
public class TextureBlenderAWT extends AbstractTextureBlender {
public TextureBlenderAWT(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float 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 TextureBlenderAWT(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) {
super(flag, negateTexture, blendType, materialColor, color, blendFactor);
}
/**
* This method blends the single pixel depending on the blending type.
*
* @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;
public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
this.prepareImagesForBlending(image, baseImage);
switch (blendType) {
case MTEX_BLEND:
result[0] = blendFactor * pixelColor[0] + oneMinusFactor * materialColor[0];
result[1] = blendFactor * pixelColor[1] + oneMinusFactor * materialColor[1];
result[2] = blendFactor * pixelColor[2] + oneMinusFactor * materialColor[2];
break;
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);
}
}
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;
}
/**
* This method blends the single pixel depending on the blending type.
*
* @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) {
case MTEX_BLEND:
result[0] = blendFactor * pixelColor[0] + oneMinusFactor * materialColor[0];
result[1] = blendFactor * pixelColor[1] + oneMinusFactor * materialColor[1];
result[2] = blendFactor * pixelColor[2] + oneMinusFactor * materialColor[2];
break;
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;
/**
* The class that is responsible for blending the following texture types:
* <li> DXT1
* <li> DXT1A
* <li> DXT3
* <li> DXT5
* The class that is responsible for blending the following texture types: <li>DXT1 <li>DXT1A <li>DXT3 <li>DXT5
* @author Marcin Roguski (Kaelthas)
*/
public class TextureBlenderDDS extends TextureBlenderAWT {
public TextureBlenderDDS(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float 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]);
}
public TextureBlenderDDS(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) {
super(flag, negateTexture, blendType, materialColor, color, blendFactor);
}
// 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);
}
@Override
public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
this.prepareImagesForBlending(image, baseImage);
// 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;
}
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
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)
*/
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.
*
* @param format
* the texture format
* @return texture blending class
*/
public static TextureBlender createTextureBlender(Format format, int flag, boolean negate, int blendType, float[] materialColor, float[] color, float colfac) {
switch (format) {
case Luminance8:
case Luminance8Alpha8:
case Luminance16:
case Luminance16Alpha16:
case Luminance16F:
case Luminance16FAlpha16F:
case Luminance32F:
return new TextureBlenderLuminance(flag, negate, blendType, materialColor, color, colfac);
case RGBA8:
case ABGR8:
case BGR8:
case RGB8:
case RGB10:
case RGB111110F:
case RGB16:
case RGB16F:
case RGB16F_to_RGB111110F:
case RGB16F_to_RGB9E5:
case RGB32F:
case RGB565:
case RGB5A1:
case RGB9E5:
case RGBA16:
case RGBA16F:
case RGBA32F:
return new TextureBlenderAWT(flag, negate, blendType, materialColor, color, colfac);
case DXT1:
case DXT1A:
case DXT3:
case DXT5:
return new TextureBlenderDDS(flag, negate, blendType, materialColor, color, colfac);
case Alpha16:
case Alpha8:
case ARGB4444:
case Depth:
case Depth16:
case Depth24:
case Depth32:
case Depth32F:
case Intensity16:
case Intensity8:
case LATC:
case LTC:
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() {
public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
return image;
}
/**
* This method creates the blending class.
*
* @param format
* the texture format
* @return texture blending class
*/
public static TextureBlender createTextureBlender(Format format, int flag, boolean negate, int blendType, float[] materialColor, float[] color, float colfac) {
switch (format) {
case Luminance8:
case Luminance8Alpha8:
case Luminance16:
case Luminance16Alpha16:
case Luminance16F:
case Luminance16FAlpha16F:
case Luminance32F:
return new TextureBlenderLuminance(flag, negate, blendType, materialColor, color, colfac);
case RGBA8:
case ABGR8:
case BGR8:
case RGB8:
case RGB10:
case RGB111110F:
case RGB16:
case RGB16F:
case RGB16F_to_RGB111110F:
case RGB16F_to_RGB9E5:
case RGB32F:
case RGB565:
case RGB5A1:
case RGB9E5:
case RGBA16:
case RGBA16F:
case RGBA32F:
return new TextureBlenderAWT(flag, negate, blendType, materialColor, color, colfac);
case DXT1:
case DXT1A:
case DXT3:
case DXT5:
return new TextureBlenderDDS(flag, negate, blendType, materialColor, color, colfac);
case Alpha16:
case Alpha8:
case ARGB4444:
case Depth:
case Depth16:
case Depth24:
case Depth32:
case Depth32F:
case Intensity16:
case Intensity8:
case LATC:
case LTC:
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() {
public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
return image;
}
public void copyBlendingData(TextureBlender textureBlender) {
}
};
default:
throw new IllegalStateException("Unknown image format type: " + format);
}
}
public void copyBlendingData(TextureBlender textureBlender) {
}
};
default:
throw new IllegalStateException("Unknown image format type: " + format);
}
}
/**
* This method changes the image format in the texture blender.
*
* @param format
* the new image format
* @param textureBlender
* the texture blender that will be altered
* @return altered texture blender
*/
public static TextureBlender alterTextureType(Format format, TextureBlender textureBlender) {
TextureBlender result = TextureBlenderFactory.createTextureBlender(format, 0, false, 0, null, null, 0);
result.copyBlendingData(textureBlender);
return result;
}
/**
* This method changes the image format in the texture blender.
*
* @param format
* the new image format
* @param textureBlender
* the texture blender that will be altered
* @return altered texture blender
*/
public static TextureBlender alterTextureType(Format format, TextureBlender textureBlender) {
TextureBlender result = TextureBlenderFactory.createTextureBlender(format, 0, false, 0, null, null, 0);
result.copyBlendingData(textureBlender);
return result;
}
}

@ -14,236 +14,228 @@ import java.util.logging.Level;
import java.util.logging.Logger;
/**
* 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:
* 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:
* @author Marcin Roguski (Kaelthas)
*/
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) {
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);
public TextureBlenderLuminance(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) {
super(flag, negateTexture, blendType, materialColor, color, blendFactor);
}
float[] resultPixel = new float[4];
float[] tinAndAlpha = new float[2];
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;
}
public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
this.prepareImagesForBlending(image, baseImage);
/**
* This method return texture intensity and alpha value.
*
* @param data
* the texture data
* @param imageFormat
* the image format
* @param neg
* 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);
}
}
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();
}
/**
* This method blends the texture with an appropriate color.
*
* @param result
* the result color (variable 'in' in blender source code)
* @param materialColor
* the texture color (variable 'out' in blender source coude)
* @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;
int width = image.getWidth();
int height = image.getHeight();
int depth = image.getDepth();
if (depth == 0) {
depth = 1;
}
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(depth);
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);
}
}
float[] resultPixel = new float[4];
float[] tinAndAlpha = new float[2];
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;
}
/**
* This method return texture intensity and alpha value.
*
* @param data
* the texture data
* @param imageFormat
* the image format
* @param neg
* 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);
}
}
/**
* This method blends the texture with an appropriate color.
*
* @param result
* the result color (variable 'in' in blender source code)
* @param materialColor
* the texture color (variable 'out' in blender source coude)
* @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) {
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.
* @author Marcin Roguski (Kaelthas)
*/
/*package*/ class NoiseGenerator extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(NoiseGenerator.class.getName());
/* package */class NoiseGenerator extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(NoiseGenerator.class.getName());
// tex->stype
protected static final int TEX_PLASTIC = 0;
protected static final int TEX_WALLIN = 1;
protected static final int TEX_WALLOUT = 2;
protected static final int TEX_PLASTIC = 0;
protected static final int TEX_WALLIN = 1;
protected static final int TEX_WALLOUT = 2;
// musgrave stype
protected static final int TEX_MFRACTAL = 0;
protected static final int TEX_RIDGEDMF = 1;
protected static final int TEX_HYBRIDMF = 2;
protected static final int TEX_FBM = 3;
protected static final int TEX_HTERRAIN = 4;
protected static final int TEX_MFRACTAL = 0;
protected static final int TEX_RIDGEDMF = 1;
protected static final int TEX_HYBRIDMF = 2;
protected static final int TEX_FBM = 3;
protected static final int TEX_HTERRAIN = 4;
// keyblock->type
protected static final int KEY_LINEAR = 0;
protected static final int KEY_CARDINAL = 1;
protected static final int KEY_BSPLINE = 2;
protected static final int KEY_LINEAR = 0;
protected static final int KEY_CARDINAL = 1;
protected static final int KEY_BSPLINE = 2;
// CONSTANTS (read from file)
protected static float[] hashpntf;
protected static short[] hash;
protected static float[] hashvectf;
protected static short[] p;
protected static float[][] g;
protected static float[] hashpntf;
protected static short[] hash;
protected static float[] hashvectf;
protected static short[] p;
protected static float[][] g;
/**
* Constructor. Stores the blender version number and loads the constants needed for computations.
* @param blenderVersion
* the number of blender version
* the number of blender version
*/
public NoiseGenerator(String blenderVersion) {
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 {
noiseFunctions.put(Integer.valueOf(0), new NoiseFunction() {
// originalBlenderNoise
// originalBlenderNoise
public float execute(float x, float y, float z) {
return NoiseFunctions.originalBlenderNoise(x, y, z);
}
@ -130,7 +130,7 @@ import java.util.logging.Logger;
}
});
noiseFunctions.put(Integer.valueOf(1), new NoiseFunction() {
// orgPerlinNoise
// orgPerlinNoise
public float execute(float x, float y, float 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() {
// newPerlin
// newPerlin
public float execute(float x, float y, float 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() {
// voronoi_F1
// voronoi_F1
public float execute(float x, float y, float z) {
float[] da = new float[4], pa = new float[12];
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() {
// voronoi_F2
// voronoi_F2
public float execute(float x, float y, float z) {
float[] da = new float[4], pa = new float[12];
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() {
// voronoi_F3
// voronoi_F3
public float execute(float x, float y, float z) {
float[] da = new float[4], pa = new float[12];
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() {
// voronoi_F4
// voronoi_F4
public float execute(float x, float y, float z) {
float[] da = new float[4], pa = new float[12];
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() {
// voronoi_F1F2
// voronoi_F1F2
public float execute(float x, float y, float z) {
float[] da = new float[4], pa = new float[12];
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() {
// voronoi_Cr
// voronoi_Cr
public float execute(float x, float y, float z) {
float t = 10 * noiseFunctions.get(Integer.valueOf(7)).execute(x, y, z);// voronoi_F1F2
return t > 1.0f ? 1.0f : t;
@ -232,7 +232,7 @@ import java.util.logging.Logger;
}
});
noiseFunctions.put(Integer.valueOf(14), new NoiseFunction() {
// cellNoise
// cellNoise
public float execute(float x, float y, float z) {
int xi = (int) Math.floor(x);
int yi = (int) Math.floor(y);
@ -252,25 +252,25 @@ import java.util.logging.Logger;
static {
distanceFunctions.put(Integer.valueOf(0), new DistanceFunction() {
// real distance
// real distance
public float execute(float x, float y, float z, float e) {
return (float) Math.sqrt(x * x + y * y + z * z);
}
});
distanceFunctions.put(Integer.valueOf(1), new DistanceFunction() {
// distance squared
// distance squared
public float execute(float x, float y, float z, float e) {
return x * x + y * y + z * z;
}
});
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) {
return FastMath.abs(x) + FastMath.abs(y) + FastMath.abs(z);
}
});
distanceFunctions.put(Integer.valueOf(3), new DistanceFunction() {
// Chebychev
// Chebychev
public float execute(float x, float y, float z, float e) {
x = FastMath.abs(x);
y = FastMath.abs(y);
@ -280,14 +280,14 @@ import java.util.logging.Logger;
}
});
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) {
float d = (float) (Math.sqrt(FastMath.abs(x)) + Math.sqrt(FastMath.abs(y)) + Math.sqrt(FastMath.abs(z)));
return d * d;
}
});
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) {
x *= x;
y *= y;
@ -296,13 +296,13 @@ import java.util.logging.Logger;
}
});
distanceFunctions.put(Integer.valueOf(6), new DistanceFunction() {
// Minkovsky, general case
// Minkovsky, general case
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);
}
});
}
protected static Map<Integer, MusgraveFunction> musgraveFunctions = new HashMap<Integer, NoiseGenerator.MusgraveFunction>();
static {
musgraveFunctions.put(Integer.valueOf(TEX_MFRACTAL), new MusgraveFunction() {
@ -460,64 +460,64 @@ import java.util.logging.Logger;
}
});
}
public static class NoiseFunctions {
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));
if (abstractNoiseFunc == null) {
abstractNoiseFunc = noiseFunctions.get(0);
noiseBasis = 0;
}
if (noiseBasis == 0) {
++x;
++y;
++z;
}
if (noiseSize != 0.0) {
noiseSize = 1.0f / noiseSize;
x *= noiseSize;
y *= noiseSize;
z *= noiseSize;
}
float result = abstractNoiseFunc.execute(x, y, z);
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) {
NoiseFunction abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noiseBasis));
if (abstractNoiseFunc == null) {
abstractNoiseFunc = noiseFunctions.get(0);
noiseBasis = 0;
}
if (noiseBasis == 0) {
++x;
++y;
++z;
}
if (noiseSize != 0.0) {
noiseSize = 1.0f / noiseSize;
x *= noiseSize;
y *= noiseSize;
z *= noiseSize;
}
float sum = 0, t, amp = 1, fscale = 1;
for (int i = 0; i <= noiseDepth; ++i, amp *= 0.5, fscale *= 2) {
t = abstractNoiseFunc.execute(fscale * x, fscale * y, fscale * z);
if (isHard) {
t = FastMath.abs(2.0f * t - 1.0f);
}
sum += t * amp;
}
sum *= (float) (1 << noiseDepth) / (float) ((1 << noiseDepth + 1) - 1);
return sum;
}
/**
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));
if (abstractNoiseFunc == null) {
abstractNoiseFunc = noiseFunctions.get(0);
noiseBasis = 0;
}
if (noiseBasis == 0) {
++x;
++y;
++z;
}
if (noiseSize != 0.0) {
noiseSize = 1.0f / noiseSize;
x *= noiseSize;
y *= noiseSize;
z *= noiseSize;
}
float result = abstractNoiseFunc.execute(x, y, z);
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) {
NoiseFunction abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noiseBasis));
if (abstractNoiseFunc == null) {
abstractNoiseFunc = noiseFunctions.get(0);
noiseBasis = 0;
}
if (noiseBasis == 0) {
++x;
++y;
++z;
}
if (noiseSize != 0.0) {
noiseSize = 1.0f / noiseSize;
x *= noiseSize;
y *= noiseSize;
z *= noiseSize;
}
float sum = 0, t, amp = 1, fscale = 1;
for (int i = 0; i <= noiseDepth; ++i, amp *= 0.5, fscale *= 2) {
t = abstractNoiseFunc.execute(fscale * x, fscale * y, fscale * z);
if (isHard) {
t = FastMath.abs(2.0f * t - 1.0f);
}
sum += t * amp;
}
sum *= (float) (1 << noiseDepth) / (float) ((1 << noiseDepth + 1) - 1);
return sum;
}
/**
* 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) {
@ -531,7 +531,7 @@ import java.util.logging.Logger;
int xi = (int) FastMath.floor(x);
int yi = (int) FastMath.floor(y);
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 j = yi - 1; j <= yi + 1; ++j) {
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
public static float newPerlin(float x, float y, float z) {
int A, AA, AB, B, BA, BB;
@ -598,7 +598,7 @@ import java.util.logging.Logger;
x -= floorX;
y -= floorY;
z -= floorZ;
//computing fading curves
// computing fading curves
floorX = NoiseMath.npfade(x);
floorY = NoiseMath.npfade(y);
floorZ = NoiseMath.npfade(z);
@ -608,14 +608,8 @@ import java.util.logging.Logger;
B = hash[intX + 1] + intY;
BA = hash[B] + intZ;
BB = hash[B + 1] + intZ;
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.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))));
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.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) {
@ -624,13 +618,13 @@ import java.util.logging.Logger;
int bx1 = bx0 + 1 & 0xFF;
float rx0 = t - (int) t;
float rx1 = rx0 - 1.0f;
t = y + 10000.0f;
int by0 = (int) t & 0xFF;
int by1 = by0 + 1 & 0xFF;
float ry0 = t - (int) t;
float ry1 = ry0 - 1.0f;
t = z + 10000.0f;
int bz0 = (int) t & 0xFF;
int bz1 = bz0 + 1 & 0xFF;
@ -685,7 +679,7 @@ import java.util.logging.Logger;
int ix = (int) Math.floor(x);
int iy = (int) Math.floor(y);
int iz = (int) Math.floor(z);
float ox = x - ix;
float oy = y - iy;
float oz = z - iz;
@ -707,24 +701,23 @@ import java.util.logging.Logger;
cn4 = 1.0f - 3.0f * cn4 - 2.0f * cn4 * jx;
cn5 = 1.0f - 3.0f * cn5 - 2.0f * cn5 * jy;
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,
cn4 * cn2 * cn3, cn4 * cn2 * cn6, cn4 * cn5 * cn3, cn4 * 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, };
int b00 = hash[hash[ix & 0xFF] + (iy & 0xFF)];
int b01 = hash[hash[ix & 0xFF] + (iy + 1 & 0xFF)];
int b10 = hash[hash[ix + 1 & 0xFF] + (iy & 0xFF)];
int b11 = hash[hash[ix + 1 & 0xFF] + (iy + 1 & 0xFF)];
int[] b1 = new int[] {b00, b00, b01, b01, b10, b10, b11, b11};
int[] b2 = new int[] {iz & 0xFF, iz + 1 & 0xFF};
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[] zFactor = new float[] {oz, jz, oz, jz, oz, jz, oz, jz};
for(int i=0;i<8;++i) {
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]);
int[] b1 = new int[] { b00, b00, b01, b01, b10, b10, b11, b11 };
int[] b2 = new int[] { iz & 0xFF, iz + 1 & 0xFF };
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[] zFactor = new float[] { oz, jz, oz, jz, oz, jz, oz, jz };
for (int i = 0; i < 8; ++i) {
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]);
}
if (n < 0.0f) {
@ -746,11 +739,11 @@ import java.util.logging.Logger;
/**
* This method calculates the unsigned value of the noise.
* @param x
* the x texture coordinate
* the x texture coordinate
* @param y
* the y texture coordinate
* the y texture coordinate
* @param z
* the z texture coordinate
* the z texture coordinate
* @return value of the noise
*/
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.
* @param x
* the x texture coordinate
* the x texture coordinate
* @param y
* the y texture coordinate
* the y texture coordinate
* @param z
* the z texture coordinate
* the z texture coordinate
* @return value of the noise
*/
float executeSigned(float x, float y, float z);
}
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);
}
public static float npfade(float t) {
public static float npfade(float t) {
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;
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);
}
public static float surve(float t) {
public static float surve(float 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];
}
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[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];
}
}
@Override
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.
* @param x
* the x coordinate
* the x coordinate
* @param y
* the y coordinate
* the y coordinate
* @param z
* the z coordinate
* the z coordinate
* @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
*/
float execute(float x, float y, float z, float e);

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

@ -42,90 +42,90 @@ import com.jme3.texture.Image.Format;
* @author Marcin Roguski (Kaelthas)
*/
public final class TextureGeneratorBlend extends TextureGenerator {
private static final IntensityFunction INTENSITY_FUNCTION[] = new IntensityFunction[7];
static {
INTENSITY_FUNCTION[0] = new IntensityFunction() {//Linear: stype = 0 (TEX_LIN)
public float getIntensity(float x, float y, float z) {
return (1.0f + x) * 0.5f;
}
};
INTENSITY_FUNCTION[1] = new IntensityFunction() {//Quad: stype = 1 (TEX_QUAD)
public float getIntensity(float x, float y, float z) {
float result = (1.0f + x) * 0.5f;
return result * result;
}
};
INTENSITY_FUNCTION[2] = new IntensityFunction() {//Ease: stype = 2 (TEX_EASE)
public float getIntensity(float x, float y, float z) {
float result = (1.0f + x) * 0.5f;
if (result <= 0.0f) {
return 0.0f;
} else if (result >= 1.0f) {
return 1.0f;
} else {
return result * result *(3.0f - 2.0f * result);
}
}
};
INTENSITY_FUNCTION[3] = new IntensityFunction() {//Diagonal: stype = 3 (TEX_DIAG)
public float getIntensity(float x, float y, float z) {
return (2.0f + x + y) * 0.25f;
}
};
INTENSITY_FUNCTION[4] = new IntensityFunction() {//Sphere: stype = 4 (TEX_SPHERE)
public float getIntensity(float x, float y, float z) {
float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z);
return result < 0.0f ? 0.0f : result;
}
};
INTENSITY_FUNCTION[5] = new IntensityFunction() {//Halo: stype = 5 (TEX_HALO)
public float getIntensity(float x, float y, float z) {
float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z);
return result <= 0.0f ? 0.0f : result * result;
}
};
INTENSITY_FUNCTION[6] = new IntensityFunction() {//Radial: stype = 6 (TEX_RAD)
public float getIntensity(float x, float y, float z) {
return (float) Math.atan2(y, x) * FastMath.INV_TWO_PI + 0.5f;
}
};
INTENSITY_FUNCTION[0] = new IntensityFunction() {// Linear: stype = 0 (TEX_LIN)
public float getIntensity(float x, float y, float z) {
return (1.0f + x) * 0.5f;
}
};
INTENSITY_FUNCTION[1] = new IntensityFunction() {// Quad: stype = 1 (TEX_QUAD)
public float getIntensity(float x, float y, float z) {
float result = (1.0f + x) * 0.5f;
return result * result;
}
};
INTENSITY_FUNCTION[2] = new IntensityFunction() {// Ease: stype = 2 (TEX_EASE)
public float getIntensity(float x, float y, float z) {
float result = (1.0f + x) * 0.5f;
if (result <= 0.0f) {
return 0.0f;
} else if (result >= 1.0f) {
return 1.0f;
} else {
return result * result * (3.0f - 2.0f * result);
}
}
};
INTENSITY_FUNCTION[3] = new IntensityFunction() {// Diagonal: stype = 3 (TEX_DIAG)
public float getIntensity(float x, float y, float z) {
return (2.0f + x + y) * 0.25f;
}
};
INTENSITY_FUNCTION[4] = new IntensityFunction() {// Sphere: stype = 4 (TEX_SPHERE)
public float getIntensity(float x, float y, float z) {
float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z);
return result < 0.0f ? 0.0f : result;
}
};
INTENSITY_FUNCTION[5] = new IntensityFunction() {// Halo: stype = 5 (TEX_HALO)
public float getIntensity(float x, float y, float z) {
float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z);
return result <= 0.0f ? 0.0f : result * result;
}
};
INTENSITY_FUNCTION[6] = new IntensityFunction() {// Radial: stype = 6 (TEX_RAD)
public float getIntensity(float x, float y, float z) {
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 {
float getIntensity(float x, float y, float z);
}
@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 {
float getIntensity(float x, float y, float z);
}
}

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

@ -43,46 +43,46 @@ import com.jme3.texture.Image.Format;
* @author Marcin Roguski (Kaelthas)
*/
public class TextureGeneratorDistnoise extends TextureGenerator {
protected float noisesize;
protected float distAmount;
protected int noisebasis;
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];
protected float noisesize;
protected float distAmount;
protected int noisebasis;
protected int noisebasis2;
this.applyBrightnessAndContrast(bacd, pixel);
} else {
this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness);
}
}
/**
* 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 {
this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness);
}
}
/**
/**
* "Variable Lacunarity Noise" A distorted variety of Perlin noise. This method is used to calculate distorted noise
* texture.
* @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 ry = abstractNoiseFunc1.execute(x, y, z) * 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;
public class TextureGeneratorFactory {
private NoiseGenerator noiseGenerator;
public TextureGeneratorFactory(String blenderVersion) {
noiseGenerator = new NoiseGenerator(blenderVersion);
}
public TextureGenerator createTextureGenerator(int generatedTexture) {
switch(generatedTexture) {
case TextureHelper.TEX_BLEND:
return new TextureGeneratorBlend(noiseGenerator);
case TextureHelper.TEX_CLOUDS:
return new TextureGeneratorClouds(noiseGenerator);
case TextureHelper.TEX_DISTNOISE:
return new TextureGeneratorDistnoise(noiseGenerator);
case TextureHelper.TEX_MAGIC:
return new TextureGeneratorMagic(noiseGenerator);
case TextureHelper.TEX_MARBLE:
return new TextureGeneratorMarble(noiseGenerator);
case TextureHelper.TEX_MUSGRAVE:
return new TextureGeneratorMusgrave(noiseGenerator);
case TextureHelper.TEX_NOISE:
return new TextureGeneratorNoise(noiseGenerator);
case TextureHelper.TEX_STUCCI:
return new TextureGeneratorStucci(noiseGenerator);
case TextureHelper.TEX_VORONOI:
return new TextureGeneratorVoronoi(noiseGenerator);
case TextureHelper.TEX_WOOD:
return new TextureGeneratorWood(noiseGenerator);
default:
throw new IllegalStateException("Unknown generated texture type: " + generatedTexture);
}
}
private NoiseGenerator noiseGenerator;
public TextureGeneratorFactory(String blenderVersion) {
noiseGenerator = new NoiseGenerator(blenderVersion);
}
public TextureGenerator createTextureGenerator(int generatedTexture) {
switch (generatedTexture) {
case TextureHelper.TEX_BLEND:
return new TextureGeneratorBlend(noiseGenerator);
case TextureHelper.TEX_CLOUDS:
return new TextureGeneratorClouds(noiseGenerator);
case TextureHelper.TEX_DISTNOISE:
return new TextureGeneratorDistnoise(noiseGenerator);
case TextureHelper.TEX_MAGIC:
return new TextureGeneratorMagic(noiseGenerator);
case TextureHelper.TEX_MARBLE:
return new TextureGeneratorMarble(noiseGenerator);
case TextureHelper.TEX_MUSGRAVE:
return new TextureGeneratorMusgrave(noiseGenerator);
case TextureHelper.TEX_NOISE:
return new TextureGeneratorNoise(noiseGenerator);
case TextureHelper.TEX_STUCCI:
return new TextureGeneratorStucci(noiseGenerator);
case TextureHelper.TEX_VORONOI:
return new TextureGeneratorVoronoi(noiseGenerator);
case TextureHelper.TEX_WOOD:
return new TextureGeneratorWood(noiseGenerator);
default:
throw new IllegalStateException("Unknown generated texture type: " + generatedTexture);
}
}
}

@ -42,119 +42,119 @@ import com.jme3.texture.Image.Format;
* @author Marcin Roguski (Kaelthas)
*/
public class TextureGeneratorMagic extends TextureGenerator {
private static NoiseDepthFunction[] noiseDepthFunctions = new NoiseDepthFunction[10];
static {
noiseDepthFunctions[0] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[1] = -(float) Math.cos(xyz[0] - xyz[1] + xyz[2]) * turbulence;
}
};
noiseDepthFunctions[1] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[0] = (float) Math.cos(xyz[0] - xyz[1] - xyz[2]) * turbulence;
}
};
noiseDepthFunctions[2] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[2] = (float) Math.sin(-xyz[0] - xyz[1] - xyz[2]) * turbulence;
}
};
noiseDepthFunctions[3] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[0] = -(float) Math.cos(-xyz[0] + xyz[1] - xyz[2]) * turbulence;
}
};
noiseDepthFunctions[4] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[1] = -(float) Math.sin(-xyz[0] + xyz[1] + xyz[2]) * turbulence;
}
};
noiseDepthFunctions[5] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[1] = -(float) Math.cos(-xyz[0] + xyz[1] + xyz[2]) * turbulence;
}
};
noiseDepthFunctions[6] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[0] = (float) Math.cos(xyz[0] + xyz[1] + xyz[2]) * turbulence;
}
};
noiseDepthFunctions[7] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[2] = (float) Math.sin(xyz[0] + xyz[1] - xyz[2]) * turbulence;
}
};
noiseDepthFunctions[8] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[0] = -(float) Math.cos(-xyz[0] - xyz[1] + xyz[2]) * turbulence;
}
};
noiseDepthFunctions[9] = new NoiseDepthFunction() {
public void compute(float[] xyz, float 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);
private static NoiseDepthFunction[] noiseDepthFunctions = new NoiseDepthFunction[10];
static {
noiseDepthFunctions[0] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[1] = -(float) Math.cos(xyz[0] - xyz[1] + xyz[2]) * turbulence;
}
};
noiseDepthFunctions[1] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[0] = (float) Math.cos(xyz[0] - xyz[1] - xyz[2]) * turbulence;
}
};
noiseDepthFunctions[2] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[2] = (float) Math.sin(-xyz[0] - xyz[1] - xyz[2]) * turbulence;
}
};
noiseDepthFunctions[3] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[0] = -(float) Math.cos(-xyz[0] + xyz[1] - xyz[2]) * turbulence;
}
};
noiseDepthFunctions[4] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[1] = -(float) Math.sin(-xyz[0] + xyz[1] + xyz[2]) * turbulence;
}
};
noiseDepthFunctions[5] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[1] = -(float) Math.cos(-xyz[0] + xyz[1] + xyz[2]) * turbulence;
}
};
noiseDepthFunctions[6] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[0] = (float) Math.cos(xyz[0] + xyz[1] + xyz[2]) * turbulence;
}
};
noiseDepthFunctions[7] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[2] = (float) Math.sin(xyz[0] + xyz[1] - xyz[2]) * turbulence;
}
};
noiseDepthFunctions[8] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[0] = -(float) Math.cos(-xyz[0] - xyz[1] + xyz[2]) * turbulence;
}
};
noiseDepthFunctions[9] = new NoiseDepthFunction() {
public void compute(float[] xyz, float turbulence) {
xyz[1] = -(float) Math.sin(xyz[0] - xyz[1] + xyz[2]) * turbulence;
}
};
}
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);
}
}
protected int noisedepth;
protected float turbul;
protected float[] xyz = new float[3];
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);
}
/**
* 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) {
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)
*/
public class TextureGeneratorMarble extends TextureGeneratorWood {
// tex->stype
protected static final int TEX_SOFT = 0;
protected static final int TEX_SHARP = 1;
// tex->stype
protected static final int TEX_SOFT = 0;
protected static final int TEX_SHARP = 1;
protected static final int TEX_SHARPER = 2;
protected MarbleData marbleData;
/**
* Constructor stores the given noise generator.
* @param noiseGenerator
* the noise generator
*/
public TextureGeneratorMarble(NoiseGenerator noiseGenerator) {
super(noiseGenerator);
}
@Override
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);
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);
pixel.alpha = colorBand[colorbandIndex][3];
} else {
this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness);
}
}
protected MarbleData marbleData;
/**
* Constructor stores the given noise generator.
* @param noiseGenerator
* the noise generator
*/
public TextureGeneratorMarble(NoiseGenerator noiseGenerator) {
super(noiseGenerator);
}
@Override
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);
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);
pixel.alpha = colorBand[colorbandIndex][3];
} else {
this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness);
}
}
public float marbleInt(MarbleData marbleData, float x, float y, float z) {
int waveform;
int waveform;
if (marbleData.waveform > TEX_TRI || marbleData.waveform < TEX_SIN) {
waveform = 0;
waveform = 0;
} else {
waveform = marbleData.waveform;
waveform = marbleData.waveform;
}
float n = 5.0f * (x + y + z);
@ -99,18 +99,18 @@ public class TextureGeneratorMarble extends TextureGeneratorWood {
}
return mi;
}
private static class MarbleData {
public final float noisesize;
public final int noisebasis;
public final int noisedepth;
public final int stype;
public final float turbul;
public final int waveform;
public final boolean isHard;
public final float noisesize;
public final int noisebasis;
public final int noisedepth;
public final int stype;
public final float turbul;
public final int waveform;
public final boolean isHard;
public MarbleData(Structure tex) {
noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue();
stype = ((Number) tex.getFieldValue("stype")).intValue();
@ -118,6 +118,6 @@ public class TextureGeneratorMarble extends TextureGeneratorWood {
int noisetype = ((Number) tex.getFieldValue("noisetype")).intValue();
waveform = ((Number) tex.getFieldValue("noisebasis2")).intValue();
isHard = noisetype != TEX_NOISESOFT;
}
}
}
}

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

Loading…
Cancel
Save