Feature: added automatic action mapping instead of explicit mapping in
BlenderKey.
This commit is contained in:
parent
03f8df05b6
commit
51215a352e
@ -33,11 +33,7 @@ package com.jme3.asset;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
|
||||||
import com.jme3.animation.Animation;
|
import com.jme3.animation.Animation;
|
||||||
@ -66,70 +62,68 @@ import com.jme3.texture.Texture;
|
|||||||
*/
|
*/
|
||||||
public class BlenderKey extends ModelKey {
|
public class BlenderKey extends ModelKey {
|
||||||
|
|
||||||
protected static final int DEFAULT_FPS = 25;
|
protected static final int DEFAULT_FPS = 25;
|
||||||
/**
|
/**
|
||||||
* FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time
|
* FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time
|
||||||
* between the frames.
|
* between the frames.
|
||||||
*/
|
*/
|
||||||
protected int fps = DEFAULT_FPS;
|
protected int fps = DEFAULT_FPS;
|
||||||
/**
|
/**
|
||||||
* This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded.
|
* This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded.
|
||||||
*/
|
*/
|
||||||
protected int featuresToLoad = FeaturesToLoad.ALL;
|
protected int featuresToLoad = FeaturesToLoad.ALL;
|
||||||
/** This variable determines if assets that are not linked to the objects should be loaded. */
|
/** This variable determines if assets that are not linked to the objects should be loaded. */
|
||||||
protected boolean loadUnlinkedAssets;
|
protected boolean loadUnlinkedAssets;
|
||||||
/** The root path for all the assets. */
|
/** The root path for all the assets. */
|
||||||
protected String assetRootPath;
|
protected String assetRootPath;
|
||||||
/** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */
|
/** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */
|
||||||
protected boolean fixUpAxis = true;
|
protected boolean fixUpAxis = true;
|
||||||
/** Generated textures resolution (PPU - Pixels Per Unit). */
|
/** Generated textures resolution (PPU - Pixels Per Unit). */
|
||||||
protected int generatedTexturePPU = 128;
|
protected int generatedTexturePPU = 128;
|
||||||
/**
|
/**
|
||||||
* The name of world settings that the importer will use. If not set or specified name does not occur in the file
|
* The name of world settings that the importer will use. If not set or specified name does not occur in the file
|
||||||
* then the first world settings in the file will be used.
|
* then the first world settings in the file will be used.
|
||||||
*/
|
*/
|
||||||
protected String usedWorld;
|
protected String usedWorld;
|
||||||
/**
|
/**
|
||||||
* User's default material that is set fo objects that have no material definition in blender. The default value is
|
* User's default material that is set fo objects that have no material definition in blender. The default value is
|
||||||
* null. If the value is null the importer will use its own default material (gray color - like in blender).
|
* null. If the value is null the importer will use its own default material (gray color - like in blender).
|
||||||
*/
|
*/
|
||||||
protected Material defaultMaterial;
|
protected Material defaultMaterial;
|
||||||
/** Face cull mode. By default it is disabled. */
|
/** Face cull mode. By default it is disabled. */
|
||||||
protected FaceCullMode faceCullMode = FaceCullMode.Back;
|
protected FaceCullMode faceCullMode = FaceCullMode.Back;
|
||||||
/**
|
/**
|
||||||
* Variable describes which layers will be loaded. N-th bit set means N-th layer will be loaded.
|
* Variable describes which layers will be loaded. N-th bit set means N-th layer will be loaded.
|
||||||
* If set to -1 then the current layer will be loaded.
|
* If set to -1 then the current layer will be loaded.
|
||||||
*/
|
*/
|
||||||
protected int layersToLoad = -1;
|
protected int layersToLoad = -1;
|
||||||
/** A variable that toggles the object custom properties loading. */
|
/** A variable that toggles the object custom properties loading. */
|
||||||
protected boolean loadObjectProperties = true;
|
protected boolean loadObjectProperties = true;
|
||||||
/**
|
/**
|
||||||
* Maximum texture size. Might be dependant on the graphic card.
|
* Maximum texture size. Might be dependant on the graphic card.
|
||||||
* This value is taken from <b>org.lwjgl.opengl.GL11.GL_MAX_TEXTURE_SIZE</b>.
|
* This value is taken from <b>org.lwjgl.opengl.GL11.GL_MAX_TEXTURE_SIZE</b>.
|
||||||
*/
|
*/
|
||||||
protected int maxTextureSize = 8192;
|
protected int maxTextureSize = 8192;
|
||||||
/** Allows to toggle generated textures loading. Disabled by default because it very often takes too much memory and needs to be used wisely. */
|
/** Allows to toggle generated textures loading. Disabled by default because it very often takes too much memory and needs to be used wisely. */
|
||||||
protected boolean loadGeneratedTextures;
|
protected boolean loadGeneratedTextures;
|
||||||
/** Tells if the mipmaps will be generated by jme or not. By default generation is dependant on the blender settings. */
|
/** Tells if the mipmaps will be generated by jme or not. By default generation is dependant on the blender settings. */
|
||||||
protected MipmapGenerationMethod mipmapGenerationMethod = MipmapGenerationMethod.GENERATE_WHEN_NEEDED;
|
protected MipmapGenerationMethod mipmapGenerationMethod = MipmapGenerationMethod.GENERATE_WHEN_NEEDED;
|
||||||
/**
|
/**
|
||||||
* If the sky has only generated textures applied then they will have the following size (both width and height). If 2d textures are used then the generated
|
* If the sky has only generated textures applied then they will have the following size (both width and height). If 2d textures are used then the generated
|
||||||
* textures will get their proper size.
|
* textures will get their proper size.
|
||||||
*/
|
*/
|
||||||
protected int skyGeneratedTextureSize = 1000;
|
protected int skyGeneratedTextureSize = 1000;
|
||||||
/** The radius of a shape that will be used while creating the generated texture for the sky. The higher it is the larger part of the texture will be seen. */
|
/** The radius of a shape that will be used while creating the generated texture for the sky. The higher it is the larger part of the texture will be seen. */
|
||||||
protected float skyGeneratedTextureRadius = 1;
|
protected float skyGeneratedTextureRadius = 1;
|
||||||
/** The shape against which the generated texture for the sky will be created. */
|
/** The shape against which the generated texture for the sky will be created. */
|
||||||
protected SkyGeneratedTextureShape skyGeneratedTextureShape = SkyGeneratedTextureShape.SPHERE;
|
protected SkyGeneratedTextureShape skyGeneratedTextureShape = SkyGeneratedTextureShape.SPHERE;
|
||||||
/**
|
/**
|
||||||
* This field tells if the importer should optimise the use of textures or not. If set to true, then textures of the same mapping type will be merged together
|
* This field tells if the importer should optimise the use of textures or not. If set to true, then textures of the same mapping type will be merged together
|
||||||
* and textures that in the final result will never be visible - will be discarded.
|
* and textures that in the final result will never be visible - will be discarded.
|
||||||
*/
|
*/
|
||||||
protected boolean optimiseTextures;
|
protected boolean optimiseTextures;
|
||||||
/** A map between node name and its animation names. */
|
/** The method of matching animations to skeletons. The default value is: AT_LEAST_ONE_NAME_MATCH. */
|
||||||
protected Map<String, List<String>> nodeAnimationMap = new HashMap<String, List<String>>();
|
protected AnimationMatchMethod animationMatchMethod = AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH;
|
||||||
/** A map between node name and its skeleton animation names. */
|
|
||||||
protected Map<String, List<String>> skeletonAnimationMap = new HashMap<String, List<String>>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor used by serialization mechanisms.
|
* Constructor used by serialization mechanisms.
|
||||||
@ -446,6 +440,23 @@ public class BlenderKey extends ModelKey {
|
|||||||
return optimiseTextures;
|
return optimiseTextures;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the way the animations will be matched with skeletons.
|
||||||
|
*
|
||||||
|
* @param animationMatchMethod
|
||||||
|
* the way the animations will be matched with skeletons
|
||||||
|
*/
|
||||||
|
public void setAnimationMatchMethod(AnimationMatchMethod animationMatchMethod) {
|
||||||
|
this.animationMatchMethod = animationMatchMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the way the animations will be matched with skeletons
|
||||||
|
*/
|
||||||
|
public AnimationMatchMethod getAnimationMatchMethod() {
|
||||||
|
return animationMatchMethod;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This mehtod sets the name of the WORLD data block taht should be used during file loading. By default the name is
|
* This mehtod sets the name of the WORLD data block taht should be used during file loading. By default the name is
|
||||||
* not set. If no name is set or the given name does not occur in the file - the first WORLD data block will be used
|
* not set. If no name is set or the given name does not occur in the file - the first WORLD data block will be used
|
||||||
@ -482,58 +493,6 @@ public class BlenderKey extends ModelKey {
|
|||||||
return defaultMaterial;
|
return defaultMaterial;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds spatial animation name for specified node.
|
|
||||||
* @param nodeName
|
|
||||||
* the name of the node
|
|
||||||
* @param animationName
|
|
||||||
* the spatial animation name
|
|
||||||
*/
|
|
||||||
public void addNodeAnimation(String nodeName, String animationName) {
|
|
||||||
List<String> animations = nodeAnimationMap.get(nodeName);
|
|
||||||
if (animations == null) {
|
|
||||||
animations = new ArrayList<String>();
|
|
||||||
nodeAnimationMap.put(nodeName, animations);
|
|
||||||
}
|
|
||||||
animations.add(animationName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all spatial animation names for the given node.
|
|
||||||
* @param nodeName
|
|
||||||
* the name of the node
|
|
||||||
* @return all spatial animations names or null if none are defined
|
|
||||||
*/
|
|
||||||
public List<String> getNodeAnimationNames(String nodeName) {
|
|
||||||
return nodeAnimationMap.get(nodeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds bone animation name for specified node.
|
|
||||||
* @param nodeName
|
|
||||||
* the name of the node
|
|
||||||
* @param animationName
|
|
||||||
* the bone animation name
|
|
||||||
*/
|
|
||||||
public void addSkeletonAnimation(String nodeName, String animationName) {
|
|
||||||
List<String> animations = skeletonAnimationMap.get(nodeName);
|
|
||||||
if (animations == null) {
|
|
||||||
animations = new ArrayList<String>();
|
|
||||||
skeletonAnimationMap.put(nodeName, animations);
|
|
||||||
}
|
|
||||||
animations.add(animationName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all bone animation names for the given node.
|
|
||||||
* @param nodeName
|
|
||||||
* the name of the node
|
|
||||||
* @return all bone animations names or null if none are defined
|
|
||||||
*/
|
|
||||||
public List<String> getSkeletonAnimationNames(String nodeName) {
|
|
||||||
return skeletonAnimationMap.get(nodeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(JmeExporter e) throws IOException {
|
public void write(JmeExporter e) throws IOException {
|
||||||
super.write(e);
|
super.write(e);
|
||||||
@ -553,30 +512,7 @@ public class BlenderKey extends ModelKey {
|
|||||||
oc.write(skyGeneratedTextureRadius, "sky-generated-texture-radius", 1f);
|
oc.write(skyGeneratedTextureRadius, "sky-generated-texture-radius", 1f);
|
||||||
oc.write(skyGeneratedTextureShape, "sky-generated-texture-shape", SkyGeneratedTextureShape.SPHERE);
|
oc.write(skyGeneratedTextureShape, "sky-generated-texture-shape", SkyGeneratedTextureShape.SPHERE);
|
||||||
oc.write(optimiseTextures, "optimise-textures", false);
|
oc.write(optimiseTextures, "optimise-textures", false);
|
||||||
|
oc.write(animationMatchMethod, "animation-match-method", AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH);
|
||||||
if (nodeAnimationMap == null) {
|
|
||||||
oc.write(0, "node-anims-map-size", 0);
|
|
||||||
} else {
|
|
||||||
oc.write(nodeAnimationMap.size(), "node-anims-map-size", 0);
|
|
||||||
int counter = 0;
|
|
||||||
for (Entry<String, List<String>> entry : nodeAnimationMap.entrySet()) {
|
|
||||||
oc.write(entry.getKey(), "node-anim-" + counter, null);
|
|
||||||
oc.write(entry.getValue().toArray(new String[entry.getValue().size()]), "node-anims-" + counter, null);
|
|
||||||
++counter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skeletonAnimationMap == null) {
|
|
||||||
oc.write(0, "skeleton-anims-map-size", 0);
|
|
||||||
} else {
|
|
||||||
oc.write(skeletonAnimationMap.size(), "skeleton-anims-map-size", 0);
|
|
||||||
int counter = 0;
|
|
||||||
for (Entry<String, List<String>> entry : skeletonAnimationMap.entrySet()) {
|
|
||||||
oc.write(entry.getKey(), "skeleton-anim-" + counter, null);
|
|
||||||
oc.write(entry.getValue().toArray(new String[entry.getValue().size()]), "skeleton-anims-" + counter, null);
|
|
||||||
++counter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -598,32 +534,14 @@ public class BlenderKey extends ModelKey {
|
|||||||
skyGeneratedTextureRadius = ic.readFloat("sky-generated-texture-radius", 1f);
|
skyGeneratedTextureRadius = ic.readFloat("sky-generated-texture-radius", 1f);
|
||||||
skyGeneratedTextureShape = ic.readEnum("sky-generated-texture-shape", SkyGeneratedTextureShape.class, SkyGeneratedTextureShape.SPHERE);
|
skyGeneratedTextureShape = ic.readEnum("sky-generated-texture-shape", SkyGeneratedTextureShape.class, SkyGeneratedTextureShape.SPHERE);
|
||||||
optimiseTextures = ic.readBoolean("optimise-textures", false);
|
optimiseTextures = ic.readBoolean("optimise-textures", false);
|
||||||
|
animationMatchMethod = ic.readEnum("animation-match-method", AnimationMatchMethod.class, AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH);
|
||||||
int animsSize = ic.readInt("node-anims-map-size", 0);
|
|
||||||
nodeAnimationMap = new HashMap<String, List<String>>(animsSize);
|
|
||||||
if (animsSize > 0) {
|
|
||||||
for (int i = 0; i < animsSize; ++i) {
|
|
||||||
String nodeName = ic.readString("node-anim-" + i, null);
|
|
||||||
String[] anims = ic.readStringArray("node-anims-" + i, null);
|
|
||||||
nodeAnimationMap.put(nodeName, new ArrayList<String>(Arrays.asList(anims)));// must create ArrayList because 'asList' method returns unmodifiable list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
animsSize = ic.readInt("skeleton-anims-map-size", 0);
|
|
||||||
skeletonAnimationMap = new HashMap<String, List<String>>(animsSize);
|
|
||||||
if (animsSize > 0) {
|
|
||||||
for (int i = 0; i < animsSize; ++i) {
|
|
||||||
String nodeName = ic.readString("skeleton-anim-" + i, null);
|
|
||||||
String[] anims = ic.readStringArray("skeleton-anims-" + i, null);
|
|
||||||
skeletonAnimationMap.put(nodeName, new ArrayList<String>(Arrays.asList(anims)));// must create ArrayList because 'asList' method returns unmodifiable list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int prime = 31;
|
final int prime = 31;
|
||||||
int result = super.hashCode();
|
int result = super.hashCode();
|
||||||
|
result = prime * result + (animationMatchMethod == null ? 0 : animationMatchMethod.hashCode());
|
||||||
result = prime * result + (assetRootPath == null ? 0 : assetRootPath.hashCode());
|
result = prime * result + (assetRootPath == null ? 0 : assetRootPath.hashCode());
|
||||||
result = prime * result + (defaultMaterial == null ? 0 : defaultMaterial.hashCode());
|
result = prime * result + (defaultMaterial == null ? 0 : defaultMaterial.hashCode());
|
||||||
result = prime * result + (faceCullMode == null ? 0 : faceCullMode.hashCode());
|
result = prime * result + (faceCullMode == null ? 0 : faceCullMode.hashCode());
|
||||||
@ -637,9 +555,7 @@ public class BlenderKey extends ModelKey {
|
|||||||
result = prime * result + (loadUnlinkedAssets ? 1231 : 1237);
|
result = prime * result + (loadUnlinkedAssets ? 1231 : 1237);
|
||||||
result = prime * result + maxTextureSize;
|
result = prime * result + maxTextureSize;
|
||||||
result = prime * result + (mipmapGenerationMethod == null ? 0 : mipmapGenerationMethod.hashCode());
|
result = prime * result + (mipmapGenerationMethod == null ? 0 : mipmapGenerationMethod.hashCode());
|
||||||
result = prime * result + (nodeAnimationMap == null ? 0 : nodeAnimationMap.hashCode());
|
|
||||||
result = prime * result + (optimiseTextures ? 1231 : 1237);
|
result = prime * result + (optimiseTextures ? 1231 : 1237);
|
||||||
result = prime * result + (skeletonAnimationMap == null ? 0 : skeletonAnimationMap.hashCode());
|
|
||||||
result = prime * result + Float.floatToIntBits(skyGeneratedTextureRadius);
|
result = prime * result + Float.floatToIntBits(skyGeneratedTextureRadius);
|
||||||
result = prime * result + (skyGeneratedTextureShape == null ? 0 : skyGeneratedTextureShape.hashCode());
|
result = prime * result + (skyGeneratedTextureShape == null ? 0 : skyGeneratedTextureShape.hashCode());
|
||||||
result = prime * result + skyGeneratedTextureSize;
|
result = prime * result + skyGeneratedTextureSize;
|
||||||
@ -649,13 +565,13 @@ public class BlenderKey extends ModelKey {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (this == obj) {
|
if (obj instanceof BlenderKey) {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!(obj instanceof BlenderKey)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
BlenderKey other = (BlenderKey) obj;
|
BlenderKey other = (BlenderKey) obj;
|
||||||
|
if (animationMatchMethod != other.animationMatchMethod) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (assetRootPath == null) {
|
if (assetRootPath == null) {
|
||||||
if (other.assetRootPath != null) {
|
if (other.assetRootPath != null) {
|
||||||
return false;
|
return false;
|
||||||
@ -703,23 +619,9 @@ public class BlenderKey extends ModelKey {
|
|||||||
if (mipmapGenerationMethod != other.mipmapGenerationMethod) {
|
if (mipmapGenerationMethod != other.mipmapGenerationMethod) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (nodeAnimationMap == null) {
|
|
||||||
if (other.nodeAnimationMap != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!nodeAnimationMap.equals(other.nodeAnimationMap)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (optimiseTextures != other.optimiseTextures) {
|
if (optimiseTextures != other.optimiseTextures) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (skeletonAnimationMap == null) {
|
|
||||||
if (other.skeletonAnimationMap != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!skeletonAnimationMap.equals(other.skeletonAnimationMap)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (Float.floatToIntBits(skyGeneratedTextureRadius) != Float.floatToIntBits(other.skyGeneratedTextureRadius)) {
|
if (Float.floatToIntBits(skyGeneratedTextureRadius) != Float.floatToIntBits(other.skyGeneratedTextureRadius)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -773,6 +675,29 @@ public class BlenderKey extends ModelKey {
|
|||||||
CUBE, SPHERE;
|
CUBE, SPHERE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This enum describes which animations should be attached to which armature.
|
||||||
|
* Blender does not store the mapping between action and armature. That is why the importer
|
||||||
|
* will try to match those by comparing bone name of the armature with the channel names
|
||||||
|
* int the actions.
|
||||||
|
*
|
||||||
|
* @author Marcin Roguski (Kaelthas)
|
||||||
|
*/
|
||||||
|
public static enum AnimationMatchMethod {
|
||||||
|
/**
|
||||||
|
* Animation is matched with skeleton when at leas one bone name matches the name of the action channel.
|
||||||
|
* All the bones that do not have their corresponding channel in the animation will not get the proper tracks for
|
||||||
|
* this particulat animation.
|
||||||
|
* Also the channel will not be used for the animation if it does not find the proper bone name.
|
||||||
|
*/
|
||||||
|
AT_LEAST_ONE_NAME_MATCH,
|
||||||
|
/**
|
||||||
|
* Animation is matched when all action names are covered by the target names (bone names or the name of the
|
||||||
|
* animated spatial.
|
||||||
|
*/
|
||||||
|
ALL_NAMES_MATCH;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class holds the loading results according to the given loading flag.
|
* This class holds the loading results according to the given loading flag.
|
||||||
* @author Marcin Roguski (Kaelthas)
|
* @author Marcin Roguski (Kaelthas)
|
||||||
|
@ -47,6 +47,7 @@ import com.jme3.asset.BlenderKey;
|
|||||||
import com.jme3.material.Material;
|
import com.jme3.material.Material;
|
||||||
import com.jme3.math.ColorRGBA;
|
import com.jme3.math.ColorRGBA;
|
||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.scene.plugins.blender.animations.BlenderAction;
|
||||||
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
||||||
import com.jme3.scene.plugins.blender.constraints.Constraint;
|
import com.jme3.scene.plugins.blender.constraints.Constraint;
|
||||||
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
|
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
|
||||||
@ -76,7 +77,7 @@ public class BlenderContext {
|
|||||||
/** The asset manager. */
|
/** The asset manager. */
|
||||||
private AssetManager assetManager;
|
private AssetManager assetManager;
|
||||||
/** The blocks read from the file. */
|
/** The blocks read from the file. */
|
||||||
protected List<FileBlockHeader> blocks;
|
protected List<FileBlockHeader> blocks;
|
||||||
/**
|
/**
|
||||||
* A map containing the file block headers. The key is the old memory address.
|
* A map containing the file block headers. The key is the old memory address.
|
||||||
*/
|
*/
|
||||||
@ -114,6 +115,8 @@ public class BlenderContext {
|
|||||||
private Map<String, AbstractBlenderHelper> helpers = new HashMap<String, AbstractBlenderHelper>();
|
private Map<String, AbstractBlenderHelper> helpers = new HashMap<String, AbstractBlenderHelper>();
|
||||||
/** Markers used by loading classes to store some custom data. This is made to avoid putting this data into user properties. */
|
/** Markers used by loading classes to store some custom data. This is made to avoid putting this data into user properties. */
|
||||||
private Map<String, Map<Object, Object>> markers = new HashMap<String, Map<Object, Object>>();
|
private Map<String, Map<Object, Object>> markers = new HashMap<String, Map<Object, Object>>();
|
||||||
|
/** A map of blender actions. The key is the action name and the value is the action itself. */
|
||||||
|
private Map<String, BlenderAction> actions = new HashMap<String, BlenderAction>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method sets the blender file version.
|
* This method sets the blender file version.
|
||||||
@ -405,7 +408,7 @@ public class BlenderContext {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method adds the animation for the specified OMA of its owner.
|
* This method adds the animation for the specified OMA of its owner.
|
||||||
*
|
*
|
||||||
@ -416,13 +419,13 @@ public class BlenderContext {
|
|||||||
*/
|
*/
|
||||||
public void addAnimation(Long ownerOMA, Animation animation) {
|
public void addAnimation(Long ownerOMA, Animation animation) {
|
||||||
List<Animation> animList = animations.get(ownerOMA);
|
List<Animation> animList = animations.get(ownerOMA);
|
||||||
if(animList == null) {
|
if (animList == null) {
|
||||||
animList = new ArrayList<Animation>();
|
animList = new ArrayList<Animation>();
|
||||||
animations.put(ownerOMA, animList);
|
animations.put(ownerOMA, animList);
|
||||||
}
|
}
|
||||||
animList.add(animation);
|
animList.add(animation);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns the animation data for the specified owner.
|
* This method returns the animation data for the specified owner.
|
||||||
*
|
*
|
||||||
@ -534,14 +537,15 @@ public class BlenderContext {
|
|||||||
/**
|
/**
|
||||||
* Returns bone by given name.
|
* Returns bone by given name.
|
||||||
*
|
*
|
||||||
* @param skeletonOMA the OMA of the skeleton where the bone will be searched
|
* @param skeletonOMA
|
||||||
|
* the OMA of the skeleton where the bone will be searched
|
||||||
* @param name
|
* @param name
|
||||||
* the name of the bone
|
* the name of the bone
|
||||||
* @return found bone or null if none bone of a given name exists
|
* @return found bone or null if none bone of a given name exists
|
||||||
*/
|
*/
|
||||||
public BoneContext getBoneByName(Long skeletonOMA, String name) {
|
public BoneContext getBoneByName(Long skeletonOMA, String name) {
|
||||||
for (Entry<Long, BoneContext> entry : boneContexts.entrySet()) {
|
for (Entry<Long, BoneContext> entry : boneContexts.entrySet()) {
|
||||||
if(entry.getValue().getArmatureObjectOMA().equals(skeletonOMA)) {
|
if (entry.getValue().getArmatureObjectOMA().equals(skeletonOMA)) {
|
||||||
Bone bone = entry.getValue().getBone();
|
Bone bone = entry.getValue().getBone();
|
||||||
if (bone != null && name.equals(bone.getName())) {
|
if (bone != null && name.equals(bone.getName())) {
|
||||||
return entry.getValue();
|
return entry.getValue();
|
||||||
@ -619,6 +623,22 @@ public class BlenderContext {
|
|||||||
return markersMap == null ? null : markersMap.get(feature);
|
return markersMap == null ? null : markersMap.get(feature);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds blender action to the context.
|
||||||
|
* @param action
|
||||||
|
* the action loaded from the blend file
|
||||||
|
*/
|
||||||
|
public void addAction(BlenderAction action) {
|
||||||
|
actions.put(action.getName(), action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a map of blender actions; the key is the action name and the value is action itself
|
||||||
|
*/
|
||||||
|
public Map<String, BlenderAction> getActions() {
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This enum defines what loaded data type user wants to retreive. It can be
|
* This enum defines what loaded data type user wants to retreive. It can be
|
||||||
* either filled structure or already converted data.
|
* either filled structure or already converted data.
|
||||||
|
@ -2,9 +2,10 @@ package com.jme3.scene.plugins.blender.animations;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@ -14,11 +15,11 @@ import com.jme3.animation.BoneTrack;
|
|||||||
import com.jme3.animation.Skeleton;
|
import com.jme3.animation.Skeleton;
|
||||||
import com.jme3.animation.SkeletonControl;
|
import com.jme3.animation.SkeletonControl;
|
||||||
import com.jme3.animation.SpatialTrack;
|
import com.jme3.animation.SpatialTrack;
|
||||||
import com.jme3.math.Quaternion;
|
import com.jme3.asset.BlenderKey.AnimationMatchMethod;
|
||||||
import com.jme3.math.Vector3f;
|
|
||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
|
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||||
|
import com.jme3.scene.plugins.blender.animations.Ipo.ConstIpo;
|
||||||
import com.jme3.scene.plugins.blender.curves.BezierCurve;
|
import com.jme3.scene.plugins.blender.curves.BezierCurve;
|
||||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||||
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
|
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
|
||||||
@ -32,10 +33,7 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
|
|||||||
* @author Marcin Roguski (Kaelthas)
|
* @author Marcin Roguski (Kaelthas)
|
||||||
*/
|
*/
|
||||||
public class AnimationHelper extends AbstractBlenderHelper {
|
public class AnimationHelper extends AbstractBlenderHelper {
|
||||||
private static final Logger LOGGER = Logger.getLogger(AnimationHelper.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(AnimationHelper.class.getName());
|
||||||
|
|
||||||
/** A map of blender actions. */
|
|
||||||
private Map<String, BlenderAction> actions = new HashMap<String, BlenderAction>();
|
|
||||||
|
|
||||||
public AnimationHelper(String blenderVersion, BlenderContext blenderContext) {
|
public AnimationHelper(String blenderVersion, BlenderContext blenderContext) {
|
||||||
super(blenderVersion, blenderContext);
|
super(blenderVersion, blenderContext);
|
||||||
@ -54,7 +52,7 @@ public class AnimationHelper extends AbstractBlenderHelper {
|
|||||||
for (FileBlockHeader header : actionHeaders) {
|
for (FileBlockHeader header : actionHeaders) {
|
||||||
Structure actionStructure = header.getStructure(blenderContext);
|
Structure actionStructure = header.getStructure(blenderContext);
|
||||||
LOGGER.log(Level.INFO, "Found animation: {0}.", actionStructure.getName());
|
LOGGER.log(Level.INFO, "Found animation: {0}.", actionStructure.getName());
|
||||||
actions.put(actionStructure.getName(), this.getTracks(actionStructure, blenderContext));
|
blenderContext.addAction(this.getTracks(actionStructure, blenderContext));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,24 +61,20 @@ public class AnimationHelper extends AbstractBlenderHelper {
|
|||||||
* The method applies animations to the given node. The names of the animations should be the same as actions names in the blender file.
|
* The method applies animations to the given node. The names of the animations should be the same as actions names in the blender file.
|
||||||
* @param node
|
* @param node
|
||||||
* the node to whom the animations will be applied
|
* the node to whom the animations will be applied
|
||||||
* @param animationNames
|
* @param animationMatchMethod
|
||||||
* the names of the animations to be applied
|
* the way animation should be matched with node
|
||||||
*/
|
*/
|
||||||
public void applyAnimations(Node node, List<String> animationNames) {
|
public void applyAnimations(Node node, AnimationMatchMethod animationMatchMethod) {
|
||||||
if (animationNames != null && animationNames.size() > 0) {
|
List<BlenderAction> actions = this.getActions(node, animationMatchMethod);
|
||||||
|
if (actions.size() > 0) {
|
||||||
List<Animation> animations = new ArrayList<Animation>();
|
List<Animation> animations = new ArrayList<Animation>();
|
||||||
for (String animationName : animationNames) {
|
for (BlenderAction action : actions) {
|
||||||
BlenderAction action = actions.get(animationName);
|
SpatialTrack[] tracks = action.toTracks(node);
|
||||||
if (action != null) {
|
if (tracks != null && tracks.length > 0) {
|
||||||
SpatialTrack[] tracks = action.toTracks(node);
|
Animation spatialAnimation = new Animation(action.getName(), action.getAnimationTime());
|
||||||
if (tracks != null && tracks.length > 0) {
|
spatialAnimation.setTracks(tracks);
|
||||||
Animation spatialAnimation = new Animation(animationName, action.getAnimationTime());
|
animations.add(spatialAnimation);
|
||||||
spatialAnimation.setTracks(tracks);
|
blenderContext.addAnimation((Long) node.getUserData(ObjectHelper.OMA_MARKER), spatialAnimation);
|
||||||
animations.add(spatialAnimation);
|
|
||||||
blenderContext.addAnimation((Long) node.getUserData(ObjectHelper.OMA_MARKER), spatialAnimation);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LOGGER.log(Level.WARNING, "Cannot find animation named: {0}.", animationName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,28 +97,24 @@ public class AnimationHelper extends AbstractBlenderHelper {
|
|||||||
* the node where the animations will be applied
|
* the node where the animations will be applied
|
||||||
* @param skeleton
|
* @param skeleton
|
||||||
* the skeleton of the node
|
* the skeleton of the node
|
||||||
* @param animationNames
|
* @param animationMatchMethod
|
||||||
* the names of the skeleton animations
|
* the way animation should be matched with skeleton
|
||||||
*/
|
*/
|
||||||
public void applyAnimations(Node node, Skeleton skeleton, List<String> animationNames) {
|
public void applyAnimations(Node node, Skeleton skeleton, AnimationMatchMethod animationMatchMethod) {
|
||||||
node.addControl(new SkeletonControl(skeleton));
|
node.addControl(new SkeletonControl(skeleton));
|
||||||
blenderContext.setNodeForSkeleton(skeleton, node);
|
blenderContext.setNodeForSkeleton(skeleton, node);
|
||||||
|
List<BlenderAction> actions = this.getActions(skeleton, animationMatchMethod);
|
||||||
|
|
||||||
if (animationNames != null && animationNames.size() > 0) {
|
if (actions.size() > 0) {
|
||||||
List<Animation> animations = new ArrayList<Animation>();
|
List<Animation> animations = new ArrayList<Animation>();
|
||||||
for (String animationName : animationNames) {
|
for (BlenderAction action : actions) {
|
||||||
BlenderAction action = actions.get(animationName);
|
BoneTrack[] tracks = action.toTracks(skeleton);
|
||||||
if (action != null) {
|
if (tracks != null && tracks.length > 0) {
|
||||||
BoneTrack[] tracks = action.toTracks(skeleton);
|
Animation boneAnimation = new Animation(action.getName(), action.getAnimationTime());
|
||||||
if (tracks != null && tracks.length > 0) {
|
boneAnimation.setTracks(tracks);
|
||||||
Animation boneAnimation = new Animation(animationName, action.getAnimationTime());
|
animations.add(boneAnimation);
|
||||||
boneAnimation.setTracks(tracks);
|
Long animatedNodeOMA = ((Number) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, node)).longValue();
|
||||||
animations.add(boneAnimation);
|
blenderContext.addAnimation(animatedNodeOMA, boneAnimation);
|
||||||
Long animatedNodeOMA = ((Number)blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, node)).longValue();
|
|
||||||
blenderContext.addAnimation(animatedNodeOMA, boneAnimation);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LOGGER.log(Level.WARNING, "Cannot find animation named: {0}.", animationName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (animations.size() > 0) {
|
if (animations.size() > 0) {
|
||||||
@ -136,10 +126,10 @@ public class AnimationHelper extends AbstractBlenderHelper {
|
|||||||
}
|
}
|
||||||
control.setAnimations(anims);
|
control.setAnimations(anims);
|
||||||
node.addControl(control);
|
node.addControl(control);
|
||||||
|
|
||||||
//make sure that SkeletonControl is added AFTER the AnimControl
|
// make sure that SkeletonControl is added AFTER the AnimControl
|
||||||
SkeletonControl skeletonControl = node.getControl(SkeletonControl.class);
|
SkeletonControl skeletonControl = node.getControl(SkeletonControl.class);
|
||||||
if(skeletonControl != null) {
|
if (skeletonControl != null) {
|
||||||
node.removeControl(SkeletonControl.class);
|
node.removeControl(SkeletonControl.class);
|
||||||
node.addControl(skeletonControl);
|
node.addControl(skeletonControl);
|
||||||
}
|
}
|
||||||
@ -230,7 +220,7 @@ public class AnimationHelper extends AbstractBlenderHelper {
|
|||||||
LOGGER.log(Level.FINE, "Getting tracks!");
|
LOGGER.log(Level.FINE, "Getting tracks!");
|
||||||
Structure groups = (Structure) actionStructure.getFieldValue("groups");
|
Structure groups = (Structure) actionStructure.getFieldValue("groups");
|
||||||
List<Structure> actionGroups = groups.evaluateListBase();// bActionGroup
|
List<Structure> actionGroups = groups.evaluateListBase();// bActionGroup
|
||||||
BlenderAction blenderAction = new BlenderAction(blenderContext.getBlenderKey().getFps());
|
BlenderAction blenderAction = new BlenderAction(actionStructure.getName(), blenderContext.getBlenderKey().getFps());
|
||||||
int lastFrame = 1;
|
int lastFrame = 1;
|
||||||
for (Structure actionGroup : actionGroups) {
|
for (Structure actionGroup : actionGroups) {
|
||||||
String name = actionGroup.getFieldValue("name").toString();
|
String name = actionGroup.getFieldValue("name").toString();
|
||||||
@ -269,7 +259,7 @@ public class AnimationHelper extends AbstractBlenderHelper {
|
|||||||
LOGGER.log(Level.FINE, "Getting tracks!");
|
LOGGER.log(Level.FINE, "Getting tracks!");
|
||||||
Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase");
|
Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase");
|
||||||
List<Structure> actionChannels = chanbase.evaluateListBase();// bActionChannel
|
List<Structure> actionChannels = chanbase.evaluateListBase();// bActionChannel
|
||||||
BlenderAction blenderAction = new BlenderAction(blenderContext.getBlenderKey().getFps());
|
BlenderAction blenderAction = new BlenderAction(actionStructure.getName(), blenderContext.getBlenderKey().getFps());
|
||||||
int lastFrame = 1;
|
int lastFrame = 1;
|
||||||
for (Structure bActionChannel : actionChannels) {
|
for (Structure bActionChannel : actionChannels) {
|
||||||
String animatedFeatureName = bActionChannel.getFieldValue("name").toString();
|
String animatedFeatureName = bActionChannel.getFieldValue("name").toString();
|
||||||
@ -277,7 +267,7 @@ public class AnimationHelper extends AbstractBlenderHelper {
|
|||||||
if (!p.isNull()) {
|
if (!p.isNull()) {
|
||||||
Structure ipoStructure = p.fetchData().get(0);
|
Structure ipoStructure = p.fetchData().get(0);
|
||||||
Ipo ipo = this.fromIpoStructure(ipoStructure, blenderContext);
|
Ipo ipo = this.fromIpoStructure(ipoStructure, blenderContext);
|
||||||
if(ipo != null) {//this can happen when ipo with no curves appear in blender file
|
if (ipo != null) {// this can happen when ipo with no curves appear in blender file
|
||||||
lastFrame = Math.max(lastFrame, ipo.getLastFrame());
|
lastFrame = Math.max(lastFrame, ipo.getLastFrame());
|
||||||
blenderAction.featuresTracks.put(animatedFeatureName, ipo);
|
blenderAction.featuresTracks.put(animatedFeatureName, ipo);
|
||||||
}
|
}
|
||||||
@ -321,104 +311,77 @@ public class AnimationHelper extends AbstractBlenderHelper {
|
|||||||
if (rnaPath.endsWith("rotation") || rnaPath.endsWith("rotation_euler")) {
|
if (rnaPath.endsWith("rotation") || rnaPath.endsWith("rotation_euler")) {
|
||||||
return Ipo.OB_ROT_X + arrayIndex;
|
return Ipo.OB_ROT_X + arrayIndex;
|
||||||
}
|
}
|
||||||
LOGGER.warning("Unknown curve rna path: " + rnaPath);
|
LOGGER.log(Level.WARNING, "Unknown curve rna path: {0}", rnaPath);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract representation of animation. The data stored here is mainly a raw action data loaded from blender.
|
* The method returns the actions for the given skeleton. The actions represent armature animation in blender.
|
||||||
* It can later be transformed into bone or spatial animation and applied to the specified node.
|
* @param skeleton
|
||||||
*
|
* the skeleton we fetch the actions for
|
||||||
* @author Marcin Roguski (Kaelthas)
|
* @param animationMatchMethod
|
||||||
|
* the method of animation matching
|
||||||
|
* @return a list of animations for the specified skeleton
|
||||||
*/
|
*/
|
||||||
private static class BlenderAction {
|
private List<BlenderAction> getActions(Skeleton skeleton, AnimationMatchMethod animationMatchMethod) {
|
||||||
/** Animation speed - frames per second. */
|
List<BlenderAction> result = new ArrayList<BlenderAction>();
|
||||||
private int fps;
|
|
||||||
/** The last frame of the animation (the last ipo curve node position is used as a last frame). */
|
|
||||||
private int stopFrame;
|
|
||||||
/**
|
|
||||||
* Tracks of the features. In case of bone animation the keys are the names of the bones. In case of spatial animation - the node's name
|
|
||||||
* is used. A single ipo contains all tracks for location, rotation and scales.
|
|
||||||
*/
|
|
||||||
private Map<String, Ipo> featuresTracks = new HashMap<String, Ipo>();
|
|
||||||
|
|
||||||
public BlenderAction(int fps) {
|
// first get a set of bone names
|
||||||
this.fps = fps;
|
Set<String> boneNames = new HashSet<String>();
|
||||||
}
|
for (int i = 0; i < skeleton.getBoneCount(); ++i) {
|
||||||
|
String boneName = skeleton.getBone(i).getName();
|
||||||
/**
|
if (boneName != null && boneName.length() > 0) {
|
||||||
* Converts the action into JME spatial animation tracks.
|
boneNames.add(skeleton.getBone(i).getName());
|
||||||
* @param node
|
|
||||||
* the node that will be animated
|
|
||||||
* @return the spatial tracks for the node
|
|
||||||
*/
|
|
||||||
public SpatialTrack[] toTracks(Node node) {
|
|
||||||
List<SpatialTrack> tracks = new ArrayList<SpatialTrack>(featuresTracks.size());
|
|
||||||
for (Entry<String, Ipo> entry : featuresTracks.entrySet()) {
|
|
||||||
tracks.add((SpatialTrack) entry.getValue().calculateTrack(0, node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale(), 1, stopFrame, fps, true));
|
|
||||||
}
|
}
|
||||||
return tracks.toArray(new SpatialTrack[tracks.size()]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// finding matches
|
||||||
* Converts the action into JME bone animation tracks.
|
Set<String> matchingNames = new HashSet<String>();
|
||||||
* @param skeleton
|
for (Entry<String, BlenderAction> actionEntry : blenderContext.getActions().entrySet()) {
|
||||||
* the skeleton that will be animated
|
// compute how many action tracks match the skeleton bones' names
|
||||||
* @return the bone tracks for the node
|
for (String boneName : boneNames) {
|
||||||
*/
|
if (actionEntry.getValue().hasTrackName(boneName)) {
|
||||||
public BoneTrack[] toTracks(Skeleton skeleton) {
|
matchingNames.add(boneName);
|
||||||
List<BoneTrack> tracks = new ArrayList<BoneTrack>(featuresTracks.size());
|
}
|
||||||
for (Entry<String, Ipo> entry : featuresTracks.entrySet()) {
|
|
||||||
int boneIndex = skeleton.getBoneIndex(entry.getKey());
|
|
||||||
tracks.add((BoneTrack) entry.getValue().calculateTrack(boneIndex, Vector3f.ZERO, Quaternion.IDENTITY, Vector3f.UNIT_XYZ, 1, stopFrame, fps, false));
|
|
||||||
}
|
}
|
||||||
return tracks.toArray(new BoneTrack[tracks.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
BlenderAction action = null;
|
||||||
* @return the time of animations (in seconds)
|
if (animationMatchMethod == AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH && matchingNames.size() > 0) {
|
||||||
*/
|
action = actionEntry.getValue();
|
||||||
public float getAnimationTime() {
|
} else if (matchingNames.size() == actionEntry.getValue().getTracksCount()) {
|
||||||
return (stopFrame - 1) / (float) fps;
|
action = actionEntry.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action != null) {
|
||||||
|
// remove the tracks that do not match the bone names if the matching method is different from ALL_NAMES_MATCH
|
||||||
|
if (animationMatchMethod != AnimationMatchMethod.ALL_NAMES_MATCH) {
|
||||||
|
action = action.clone();
|
||||||
|
action.removeTracksThatAreNotInTheCollection(matchingNames);
|
||||||
|
}
|
||||||
|
result.add(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
matchingNames.clear();
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ipo constant curve. This is a curve with only one value and no specified
|
* The method returns the actions for the given node. The actions represent object animation in blender.
|
||||||
* type. This type of ipo cannot be used to calculate tracks. It should only
|
* @param node
|
||||||
* be used to calculate single value for a given frame.
|
* the node we fetch the actions for
|
||||||
*
|
* @param animationMatchMethod
|
||||||
* @author Marcin Roguski (Kaelthas)
|
* the method of animation matching
|
||||||
|
* @return a list of animations for the specified node
|
||||||
*/
|
*/
|
||||||
private class ConstIpo extends Ipo {
|
private List<BlenderAction> getActions(Node node, AnimationMatchMethod animationMatchMethod) {
|
||||||
|
List<BlenderAction> result = new ArrayList<BlenderAction>();
|
||||||
|
|
||||||
/** The constant value of this ipo. */
|
for (Entry<String, BlenderAction> actionEntry : blenderContext.getActions().entrySet()) {
|
||||||
private float constValue;
|
if (actionEntry.getValue().hasTrackName(node.getName())) {
|
||||||
|
result.add(actionEntry.getValue());
|
||||||
/**
|
}
|
||||||
* 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 BoneTrack calculateTrack(int boneIndex, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean boneTrack) {
|
|
||||||
throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!");
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,134 @@
|
|||||||
|
package com.jme3.scene.plugins.blender.animations;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import com.jme3.animation.BoneTrack;
|
||||||
|
import com.jme3.animation.Skeleton;
|
||||||
|
import com.jme3.animation.SpatialTrack;
|
||||||
|
import com.jme3.math.Quaternion;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Node;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract representation of animation. The data stored here is mainly a
|
||||||
|
* raw action data loaded from blender. It can later be transformed into
|
||||||
|
* bone or spatial animation and applied to the specified node.
|
||||||
|
*
|
||||||
|
* @author Marcin Roguski (Kaelthas)
|
||||||
|
*/
|
||||||
|
public class BlenderAction implements Cloneable {
|
||||||
|
/** The action name. */
|
||||||
|
/* package */final String name;
|
||||||
|
/** Animation speed - frames per second. */
|
||||||
|
/* package */int fps;
|
||||||
|
/**
|
||||||
|
* The last frame of the animation (the last ipo curve node position is
|
||||||
|
* used as a last frame).
|
||||||
|
*/
|
||||||
|
/* package */int stopFrame;
|
||||||
|
/**
|
||||||
|
* Tracks of the features. In case of bone animation the keys are the
|
||||||
|
* names of the bones. In case of spatial animation - the node's name is
|
||||||
|
* used. A single ipo contains all tracks for location, rotation and
|
||||||
|
* scales.
|
||||||
|
*/
|
||||||
|
/* package */Map<String, Ipo> featuresTracks = new HashMap<String, Ipo>();
|
||||||
|
|
||||||
|
public BlenderAction(String name, int fps) {
|
||||||
|
this.name = name;
|
||||||
|
this.fps = fps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeTracksThatAreNotInTheCollection(Collection<String> trackNames) {
|
||||||
|
Map<String, Ipo> newTracks = new HashMap<String, Ipo>();
|
||||||
|
for (String trackName : trackNames) {
|
||||||
|
if (featuresTracks.containsKey(trackName)) {
|
||||||
|
newTracks.put(trackName, featuresTracks.get(trackName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
featuresTracks = newTracks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlenderAction clone() {
|
||||||
|
BlenderAction result = new BlenderAction(name, fps);
|
||||||
|
result.stopFrame = stopFrame;
|
||||||
|
result.featuresTracks = new HashMap<String, Ipo>(featuresTracks);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the action into JME spatial animation tracks.
|
||||||
|
*
|
||||||
|
* @param node
|
||||||
|
* the node that will be animated
|
||||||
|
* @return the spatial tracks for the node
|
||||||
|
*/
|
||||||
|
public SpatialTrack[] toTracks(Node node) {
|
||||||
|
List<SpatialTrack> tracks = new ArrayList<SpatialTrack>(featuresTracks.size());
|
||||||
|
for (Entry<String, Ipo> entry : featuresTracks.entrySet()) {
|
||||||
|
tracks.add((SpatialTrack) entry.getValue().calculateTrack(0, node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale(), 1, stopFrame, fps, true));
|
||||||
|
}
|
||||||
|
return tracks.toArray(new SpatialTrack[tracks.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the action into JME bone animation tracks.
|
||||||
|
*
|
||||||
|
* @param skeleton
|
||||||
|
* the skeleton that will be animated
|
||||||
|
* @return the bone tracks for the node
|
||||||
|
*/
|
||||||
|
public BoneTrack[] toTracks(Skeleton skeleton) {
|
||||||
|
List<BoneTrack> tracks = new ArrayList<BoneTrack>(featuresTracks.size());
|
||||||
|
for (Entry<String, Ipo> entry : featuresTracks.entrySet()) {
|
||||||
|
int boneIndex = skeleton.getBoneIndex(entry.getKey());
|
||||||
|
tracks.add((BoneTrack) entry.getValue().calculateTrack(boneIndex, Vector3f.ZERO, Quaternion.IDENTITY, Vector3f.UNIT_XYZ, 1, stopFrame, fps, false));
|
||||||
|
}
|
||||||
|
return tracks.toArray(new BoneTrack[tracks.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the name of the action
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the time of animations (in seconds)
|
||||||
|
*/
|
||||||
|
public float getAnimationTime() {
|
||||||
|
return (stopFrame - 1) / (float) fps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the current action has a track of a given name.
|
||||||
|
* CAUTION! The names are case sensitive.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* the name of the track
|
||||||
|
* @return <B>true</b> if the track of a given name exists for the
|
||||||
|
* action and <b>false</b> otherwise
|
||||||
|
*/
|
||||||
|
public boolean hasTrackName(String name) {
|
||||||
|
return featuresTracks.containsKey(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the amount of tracks in current action
|
||||||
|
*/
|
||||||
|
public int getTracksCount() {
|
||||||
|
return featuresTracks.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "BlenderTrack [name = " + name + "; tracks = [" + featuresTracks.keySet() + "]]";
|
||||||
|
}
|
||||||
|
}
|
@ -170,7 +170,7 @@ public class Ipo {
|
|||||||
for (int j = 0; j < bezierCurves.length; ++j) {
|
for (int j = 0; j < bezierCurves.length; ++j) {
|
||||||
double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE);
|
double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE);
|
||||||
switch (bezierCurves[j].getType()) {
|
switch (bezierCurves[j].getType()) {
|
||||||
// LOCATION
|
// LOCATION
|
||||||
case AC_LOC_X:
|
case AC_LOC_X:
|
||||||
translation[0] = (float) value;
|
translation[0] = (float) value;
|
||||||
break;
|
break;
|
||||||
@ -186,18 +186,18 @@ public class Ipo {
|
|||||||
|
|
||||||
// EULER ROTATION
|
// EULER ROTATION
|
||||||
case OB_ROT_X:
|
case OB_ROT_X:
|
||||||
eulerRotationUsed = true;
|
eulerRotationUsed = true;
|
||||||
eulerRotation[0] = (float) value * degreeToRadiansFactor;
|
eulerRotation[0] = (float) value * degreeToRadiansFactor;
|
||||||
break;
|
break;
|
||||||
case OB_ROT_Y:
|
case OB_ROT_Y:
|
||||||
eulerRotationUsed = true;
|
eulerRotationUsed = true;
|
||||||
if (swapAxes && value != 0) {
|
if (swapAxes && value != 0) {
|
||||||
value = -value;
|
value = -value;
|
||||||
}
|
}
|
||||||
eulerRotation[yIndex] = (float) value * degreeToRadiansFactor;
|
eulerRotation[yIndex] = (float) value * degreeToRadiansFactor;
|
||||||
break;
|
break;
|
||||||
case OB_ROT_Z:
|
case OB_ROT_Z:
|
||||||
eulerRotationUsed = true;
|
eulerRotationUsed = true;
|
||||||
eulerRotation[zIndex] = (float) value * degreeToRadiansFactor;
|
eulerRotation[zIndex] = (float) value * degreeToRadiansFactor;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -214,15 +214,15 @@ public class Ipo {
|
|||||||
|
|
||||||
// QUATERNION ROTATION (used with bone animation)
|
// QUATERNION ROTATION (used with bone animation)
|
||||||
case AC_QUAT_W:
|
case AC_QUAT_W:
|
||||||
queternionRotationUsed = true;
|
queternionRotationUsed = true;
|
||||||
quaternionRotation[3] = (float) value;
|
quaternionRotation[3] = (float) value;
|
||||||
break;
|
break;
|
||||||
case AC_QUAT_X:
|
case AC_QUAT_X:
|
||||||
queternionRotationUsed = true;
|
queternionRotationUsed = true;
|
||||||
quaternionRotation[0] = (float) value;
|
quaternionRotation[0] = (float) value;
|
||||||
break;
|
break;
|
||||||
case AC_QUAT_Y:
|
case AC_QUAT_Y:
|
||||||
queternionRotationUsed = true;
|
queternionRotationUsed = true;
|
||||||
if (swapAxes && value != 0) {
|
if (swapAxes && value != 0) {
|
||||||
value = -value;
|
value = -value;
|
||||||
}
|
}
|
||||||
@ -236,12 +236,12 @@ public class Ipo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
translations[index] = localRotation.multLocal(new Vector3f(translation[0], translation[1], translation[2]));
|
translations[index] = localRotation.multLocal(new Vector3f(translation[0], translation[1], translation[2]));
|
||||||
if(queternionRotationUsed) {
|
if (queternionRotationUsed) {
|
||||||
rotations[index] = new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]);
|
rotations[index] = new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]);
|
||||||
} else {
|
} else {
|
||||||
rotations[index] = new Quaternion().fromAngles(eulerRotation);
|
rotations[index] = new Quaternion().fromAngles(eulerRotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
scales[index] = new Vector3f(scale[0], scale[1], scale[2]);
|
scales[index] = new Vector3f(scale[0], scale[1], scale[2]);
|
||||||
}
|
}
|
||||||
if (spatialTrack) {
|
if (spatialTrack) {
|
||||||
@ -249,12 +249,51 @@ public class Ipo {
|
|||||||
} else {
|
} else {
|
||||||
calculatedTrack = new BoneTrack(targetIndex, times, translations, rotations, scales);
|
calculatedTrack = new BoneTrack(targetIndex, times, translations, rotations, scales);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(queternionRotationUsed && eulerRotationUsed) {
|
if (queternionRotationUsed && eulerRotationUsed) {
|
||||||
LOGGER.warning("Animation uses both euler and quaternion tracks for rotations. Quaternion rotation is applied. Make sure that this is what you wanted!");
|
LOGGER.warning("Animation uses both euler and quaternion tracks for rotations. Quaternion rotation is applied. Make sure that this is what you wanted!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculatedTrack;
|
return calculatedTrack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 (Kaelthas)
|
||||||
|
*/
|
||||||
|
/* package */static 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 BoneTrack calculateTrack(int boneIndex, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean boneTrack) {
|
||||||
|
throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,396 +1,396 @@
|
|||||||
package com.jme3.scene.plugins.blender.modifiers;
|
package com.jme3.scene.plugins.blender.modifiers;
|
||||||
|
|
||||||
import java.nio.Buffer;
|
import java.nio.Buffer;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.FloatBuffer;
|
import java.nio.FloatBuffer;
|
||||||
import java.nio.IntBuffer;
|
import java.nio.IntBuffer;
|
||||||
import java.nio.ShortBuffer;
|
import java.nio.ShortBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import com.jme3.animation.Bone;
|
import com.jme3.animation.Bone;
|
||||||
import com.jme3.animation.Skeleton;
|
import com.jme3.animation.Skeleton;
|
||||||
import com.jme3.math.Matrix4f;
|
import com.jme3.math.Matrix4f;
|
||||||
import com.jme3.math.Vector3f;
|
import com.jme3.math.Vector3f;
|
||||||
import com.jme3.scene.Geometry;
|
import com.jme3.scene.Geometry;
|
||||||
import com.jme3.scene.Mesh;
|
import com.jme3.scene.Mesh;
|
||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
import com.jme3.scene.Spatial;
|
import com.jme3.scene.Spatial;
|
||||||
import com.jme3.scene.VertexBuffer;
|
import com.jme3.scene.VertexBuffer;
|
||||||
import com.jme3.scene.VertexBuffer.Format;
|
import com.jme3.scene.VertexBuffer.Format;
|
||||||
import com.jme3.scene.VertexBuffer.Type;
|
import com.jme3.scene.VertexBuffer.Type;
|
||||||
import com.jme3.scene.VertexBuffer.Usage;
|
import com.jme3.scene.VertexBuffer.Usage;
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
|
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
|
||||||
import com.jme3.scene.plugins.blender.animations.AnimationHelper;
|
import com.jme3.scene.plugins.blender.animations.AnimationHelper;
|
||||||
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
||||||
import com.jme3.scene.plugins.blender.animations.BoneEnvelope;
|
import com.jme3.scene.plugins.blender.animations.BoneEnvelope;
|
||||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
|
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
|
||||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||||
import com.jme3.scene.plugins.blender.file.Structure;
|
import com.jme3.scene.plugins.blender.file.Structure;
|
||||||
import com.jme3.scene.plugins.blender.meshes.MeshContext;
|
import com.jme3.scene.plugins.blender.meshes.MeshContext;
|
||||||
import com.jme3.scene.plugins.blender.meshes.MeshContext.VertexGroup;
|
import com.jme3.scene.plugins.blender.meshes.MeshContext.VertexGroup;
|
||||||
import com.jme3.util.BufferUtils;
|
import com.jme3.util.BufferUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This modifier allows to add bone animation to the object.
|
* This modifier allows to add bone animation to the object.
|
||||||
*
|
*
|
||||||
* @author Marcin Roguski (Kaelthas)
|
* @author Marcin Roguski (Kaelthas)
|
||||||
*/
|
*/
|
||||||
/* package */class ArmatureModifier extends Modifier {
|
/* package */class ArmatureModifier extends Modifier {
|
||||||
private static final Logger LOGGER = Logger.getLogger(ArmatureModifier.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(ArmatureModifier.class.getName());
|
||||||
private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; // JME
|
private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; // JME
|
||||||
|
|
||||||
private static final int FLAG_VERTEX_GROUPS = 0x01;
|
private static final int FLAG_VERTEX_GROUPS = 0x01;
|
||||||
private static final int FLAG_BONE_ENVELOPES = 0x02;
|
private static final int FLAG_BONE_ENVELOPES = 0x02;
|
||||||
|
|
||||||
private Structure armatureObject;
|
private Structure armatureObject;
|
||||||
private Skeleton skeleton;
|
private Skeleton skeleton;
|
||||||
private Structure meshStructure;
|
private Structure meshStructure;
|
||||||
/** The wold transform matrix of the armature object. */
|
/** The wold transform matrix of the armature object. */
|
||||||
private Matrix4f objectWorldMatrix;
|
private Matrix4f objectWorldMatrix;
|
||||||
/** Old memory address of the mesh that will have the skeleton applied. */
|
/** Old memory address of the mesh that will have the skeleton applied. */
|
||||||
private Long meshOMA;
|
private Long meshOMA;
|
||||||
/** The variable tells if the vertex groups of the mesh should be used to assign verts to bones. */
|
/** The variable tells if the vertex groups of the mesh should be used to assign verts to bones. */
|
||||||
private boolean useVertexGroups;
|
private boolean useVertexGroups;
|
||||||
/** The variable tells if the bones' envelopes should be used to assign verts to bones. */
|
/** The variable tells if the bones' envelopes should be used to assign verts to bones. */
|
||||||
private boolean useBoneEnvelopes;
|
private boolean useBoneEnvelopes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This constructor reads animation data from the object structore. The
|
* This constructor reads animation data from the object structore. The
|
||||||
* stored data is the AnimData and additional data is armature's OMA.
|
* stored data is the AnimData and additional data is armature's OMA.
|
||||||
*
|
*
|
||||||
* @param objectStructure
|
* @param objectStructure
|
||||||
* the structure of the object
|
* the structure of the object
|
||||||
* @param modifierStructure
|
* @param modifierStructure
|
||||||
* the structure of the modifier
|
* the structure of the modifier
|
||||||
* @param blenderContext
|
* @param blenderContext
|
||||||
* the blender context
|
* the blender context
|
||||||
* @throws BlenderFileException
|
* @throws BlenderFileException
|
||||||
* this exception is thrown when the blender file is somehow
|
* this exception is thrown when the blender file is somehow
|
||||||
* corrupted
|
* corrupted
|
||||||
*/
|
*/
|
||||||
public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
|
public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
|
||||||
Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData().get(0);
|
Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData().get(0);
|
||||||
if (this.validate(modifierStructure, blenderContext)) {
|
if (this.validate(modifierStructure, blenderContext)) {
|
||||||
Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");
|
Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");
|
||||||
if (pArmatureObject.isNotNull()) {
|
if (pArmatureObject.isNotNull()) {
|
||||||
int deformflag = ((Number) modifierStructure.getFieldValue("deformflag")).intValue();
|
int deformflag = ((Number) modifierStructure.getFieldValue("deformflag")).intValue();
|
||||||
useVertexGroups = (deformflag & FLAG_VERTEX_GROUPS) != 0;
|
useVertexGroups = (deformflag & FLAG_VERTEX_GROUPS) != 0;
|
||||||
useBoneEnvelopes = (deformflag & FLAG_BONE_ENVELOPES) != 0;
|
useBoneEnvelopes = (deformflag & FLAG_BONE_ENVELOPES) != 0;
|
||||||
modifying = useBoneEnvelopes || useVertexGroups;
|
modifying = useBoneEnvelopes || useVertexGroups;
|
||||||
if (modifying) {// if neither option is used the modifier will not modify anything anyway
|
if (modifying) {// if neither option is used the modifier will not modify anything anyway
|
||||||
armatureObject = pArmatureObject.fetchData().get(0);
|
armatureObject = pArmatureObject.fetchData().get(0);
|
||||||
|
|
||||||
// load skeleton
|
// load skeleton
|
||||||
Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData().get(0);
|
Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData().get(0);
|
||||||
List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase();
|
List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase();
|
||||||
List<Bone> bonesList = new ArrayList<Bone>();
|
List<Bone> bonesList = new ArrayList<Bone>();
|
||||||
for (int i = 0; i < bonebase.size(); ++i) {
|
for (int i = 0; i < bonebase.size(); ++i) {
|
||||||
this.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectStructure.getOldMemoryAddress(), blenderContext);
|
this.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectStructure.getOldMemoryAddress(), blenderContext);
|
||||||
}
|
}
|
||||||
bonesList.add(0, new Bone(""));
|
bonesList.add(0, new Bone(""));
|
||||||
Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]);
|
Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]);
|
||||||
skeleton = new Skeleton(bones);
|
skeleton = new Skeleton(bones);
|
||||||
blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton);
|
blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton);
|
||||||
this.meshStructure = meshStructure;
|
this.meshStructure = meshStructure;
|
||||||
|
|
||||||
// read mesh indexes
|
// read mesh indexes
|
||||||
meshOMA = meshStructure.getOldMemoryAddress();
|
meshOMA = meshStructure.getOldMemoryAddress();
|
||||||
|
|
||||||
if (useBoneEnvelopes) {
|
if (useBoneEnvelopes) {
|
||||||
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
|
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
|
||||||
Spatial object = (Spatial) blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
|
Spatial object = (Spatial) blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
|
||||||
objectWorldMatrix = constraintHelper.toMatrix(object.getWorldTransform(), new Matrix4f());
|
objectWorldMatrix = constraintHelper.toMatrix(object.getWorldTransform(), new Matrix4f());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
modifying = false;
|
modifying = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method builds the object's bones structure.
|
* This method builds the object's bones structure.
|
||||||
*
|
*
|
||||||
* @param armatureObjectOMA
|
* @param armatureObjectOMA
|
||||||
* the OMa of the armature node
|
* the OMa of the armature node
|
||||||
* @param boneStructure
|
* @param boneStructure
|
||||||
* the structure containing the bones' data
|
* the structure containing the bones' data
|
||||||
* @param parent
|
* @param parent
|
||||||
* the parent bone
|
* the parent bone
|
||||||
* @param result
|
* @param result
|
||||||
* the list where the newly created bone will be added
|
* the list where the newly created bone will be added
|
||||||
* @param spatialOMA
|
* @param spatialOMA
|
||||||
* the OMA of the spatial that will own the skeleton
|
* the OMA of the spatial that will own the skeleton
|
||||||
* @param blenderContext
|
* @param blenderContext
|
||||||
* the blender context
|
* the blender context
|
||||||
* @throws BlenderFileException
|
* @throws BlenderFileException
|
||||||
* an exception is thrown when there is problem with the blender
|
* an exception is thrown when there is problem with the blender
|
||||||
* file
|
* file
|
||||||
*/
|
*/
|
||||||
private void buildBones(Long armatureObjectOMA, Structure boneStructure, Bone parent, List<Bone> result, Long spatialOMA, BlenderContext blenderContext) throws BlenderFileException {
|
private void buildBones(Long armatureObjectOMA, Structure boneStructure, Bone parent, List<Bone> result, Long spatialOMA, BlenderContext blenderContext) throws BlenderFileException {
|
||||||
BoneContext bc = new BoneContext(armatureObjectOMA, boneStructure, blenderContext);
|
BoneContext bc = new BoneContext(armatureObjectOMA, boneStructure, blenderContext);
|
||||||
bc.buildBone(result, spatialOMA, blenderContext);
|
bc.buildBone(result, spatialOMA, blenderContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void apply(Node node, BlenderContext blenderContext) {
|
public void apply(Node node, BlenderContext blenderContext) {
|
||||||
if (invalid) {
|
if (invalid) {
|
||||||
LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName());
|
LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName());
|
||||||
}// if invalid, animData will be null
|
}// if invalid, animData will be null
|
||||||
if (skeleton != null) {
|
if (skeleton != null) {
|
||||||
// setting weights for bones
|
// setting weights for bones
|
||||||
List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE);
|
List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE);
|
||||||
MeshContext meshContext = blenderContext.getMeshContext(meshOMA);
|
MeshContext meshContext = blenderContext.getMeshContext(meshOMA);
|
||||||
for (Geometry geom : geomList) {
|
for (Geometry geom : geomList) {
|
||||||
int materialIndex = meshContext.getMaterialIndex(geom);
|
int materialIndex = meshContext.getMaterialIndex(geom);
|
||||||
Mesh mesh = geom.getMesh();
|
Mesh mesh = geom.getMesh();
|
||||||
|
|
||||||
MeshWeightsData buffers = this.readVerticesWeightsData(meshContext, skeleton, materialIndex, mesh, blenderContext);
|
MeshWeightsData buffers = this.readVerticesWeightsData(meshContext, skeleton, materialIndex, mesh, blenderContext);
|
||||||
if (buffers != null) {
|
if (buffers != null) {
|
||||||
mesh.setMaxNumWeights(buffers.maximumWeightsPerVertex);
|
mesh.setMaxNumWeights(buffers.maximumWeightsPerVertex);
|
||||||
mesh.setBuffer(buffers.verticesWeights);
|
mesh.setBuffer(buffers.verticesWeights);
|
||||||
mesh.setBuffer(buffers.verticesWeightsIndices);
|
mesh.setBuffer(buffers.verticesWeightsIndices);
|
||||||
|
|
||||||
LOGGER.fine("Generating bind pose and normal buffers.");
|
LOGGER.fine("Generating bind pose and normal buffers.");
|
||||||
mesh.generateBindPose(true);
|
mesh.generateBindPose(true);
|
||||||
|
|
||||||
// change the usage type of vertex and normal buffers from
|
// change the usage type of vertex and normal buffers from
|
||||||
// Static to Stream
|
// Static to Stream
|
||||||
mesh.getBuffer(Type.Position).setUsage(Usage.Stream);
|
mesh.getBuffer(Type.Position).setUsage(Usage.Stream);
|
||||||
mesh.getBuffer(Type.Normal).setUsage(Usage.Stream);
|
mesh.getBuffer(Type.Normal).setUsage(Usage.Stream);
|
||||||
|
|
||||||
// creating empty buffers for HW skinning
|
// creating empty buffers for HW skinning
|
||||||
// the buffers will be setup if ever used.
|
// the buffers will be setup if ever used.
|
||||||
VertexBuffer verticesWeightsHW = new VertexBuffer(Type.HWBoneWeight);
|
VertexBuffer verticesWeightsHW = new VertexBuffer(Type.HWBoneWeight);
|
||||||
VertexBuffer verticesWeightsIndicesHW = new VertexBuffer(Type.HWBoneIndex);
|
VertexBuffer verticesWeightsIndicesHW = new VertexBuffer(Type.HWBoneIndex);
|
||||||
mesh.setBuffer(verticesWeightsHW);
|
mesh.setBuffer(verticesWeightsHW);
|
||||||
mesh.setBuffer(verticesWeightsIndicesHW);
|
mesh.setBuffer(verticesWeightsIndicesHW);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
|
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
|
||||||
animationHelper.applyAnimations(node, skeleton, blenderContext.getBlenderKey().getSkeletonAnimationNames(node.getName()));
|
animationHelper.applyAnimations(node, skeleton, blenderContext.getBlenderKey().getAnimationMatchMethod());
|
||||||
node.updateModelBound();
|
node.updateModelBound();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the vertices data and prepares appropriate buffers to be added to the mesh. There is a bone index buffer and weitghts buffer.
|
* Reads the vertices data and prepares appropriate buffers to be added to the mesh. There is a bone index buffer and weitghts buffer.
|
||||||
*
|
*
|
||||||
* @param meshContext
|
* @param meshContext
|
||||||
* the mesh context
|
* the mesh context
|
||||||
* @param skeleton
|
* @param skeleton
|
||||||
* the current skeleton
|
* the current skeleton
|
||||||
* @param materialIndex
|
* @param materialIndex
|
||||||
* the material index
|
* the material index
|
||||||
* @param mesh
|
* @param mesh
|
||||||
* the mesh we create the buffers for
|
* the mesh we create the buffers for
|
||||||
* @param blenderContext
|
* @param blenderContext
|
||||||
* the blender context
|
* the blender context
|
||||||
* @return an instance that aggregates all needed data for the mesh
|
* @return an instance that aggregates all needed data for the mesh
|
||||||
*/
|
*/
|
||||||
private MeshWeightsData readVerticesWeightsData(MeshContext meshContext, Skeleton skeleton, int materialIndex, Mesh mesh, BlenderContext blenderContext) {
|
private MeshWeightsData readVerticesWeightsData(MeshContext meshContext, Skeleton skeleton, int materialIndex, Mesh mesh, BlenderContext blenderContext) {
|
||||||
int vertexListSize = meshContext.getVertexCount(materialIndex);
|
int vertexListSize = meshContext.getVertexCount(materialIndex);
|
||||||
Map<Integer, List<Integer>> vertexReferenceMap = meshContext.getVertexReferenceMap(materialIndex);
|
Map<Integer, List<Integer>> vertexReferenceMap = meshContext.getVertexReferenceMap(materialIndex);
|
||||||
|
|
||||||
Map<String, VertexGroup> vertexGroups = new HashMap<String, VertexGroup>();
|
Map<String, VertexGroup> vertexGroups = new HashMap<String, VertexGroup>();
|
||||||
Buffer indexes = mesh.getBuffer(Type.Index).getData();
|
Buffer indexes = mesh.getBuffer(Type.Index).getData();
|
||||||
FloatBuffer positions = mesh.getFloatBuffer(Type.Position);
|
FloatBuffer positions = mesh.getFloatBuffer(Type.Position);
|
||||||
|
|
||||||
int maximumWeightsPerVertex = 0;
|
int maximumWeightsPerVertex = 0;
|
||||||
if (useVertexGroups) {
|
if (useVertexGroups) {
|
||||||
LOGGER.fine("Attaching verts to bones using vertex groups.");
|
LOGGER.fine("Attaching verts to bones using vertex groups.");
|
||||||
for (int boneIndex = 1; boneIndex < skeleton.getBoneCount(); ++boneIndex) {// bone with index 0 is a root bone
|
for (int boneIndex = 1; boneIndex < skeleton.getBoneCount(); ++boneIndex) {// bone with index 0 is a root bone
|
||||||
Bone bone = skeleton.getBone(boneIndex);
|
Bone bone = skeleton.getBone(boneIndex);
|
||||||
VertexGroup vertexGroup = meshContext.getGroup(bone.getName());
|
VertexGroup vertexGroup = meshContext.getGroup(bone.getName());
|
||||||
if (vertexGroup != null) {
|
if (vertexGroup != null) {
|
||||||
vertexGroup.setBoneIndex(boneIndex);
|
vertexGroup.setBoneIndex(boneIndex);
|
||||||
vertexGroups.put(bone.getName(), vertexGroup);
|
vertexGroups.put(bone.getName(), vertexGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useBoneEnvelopes) {
|
if (useBoneEnvelopes) {
|
||||||
LOGGER.fine("Attaching verts to bones using bone envelopes.");
|
LOGGER.fine("Attaching verts to bones using bone envelopes.");
|
||||||
Vector3f pos = new Vector3f();
|
Vector3f pos = new Vector3f();
|
||||||
|
|
||||||
for (int boneIndex = 1; boneIndex < skeleton.getBoneCount(); ++boneIndex) {// bone with index 0 is a root bone
|
for (int boneIndex = 1; boneIndex < skeleton.getBoneCount(); ++boneIndex) {// bone with index 0 is a root bone
|
||||||
Bone bone = skeleton.getBone(boneIndex);
|
Bone bone = skeleton.getBone(boneIndex);
|
||||||
BoneContext boneContext = blenderContext.getBoneContext(bone);
|
BoneContext boneContext = blenderContext.getBoneContext(bone);
|
||||||
BoneEnvelope boneEnvelope = boneContext.getBoneEnvelope();
|
BoneEnvelope boneEnvelope = boneContext.getBoneEnvelope();
|
||||||
if (boneEnvelope != null) {
|
if (boneEnvelope != null) {
|
||||||
VertexGroup vertexGroup = vertexGroups.get(bone.getName());
|
VertexGroup vertexGroup = vertexGroups.get(bone.getName());
|
||||||
if (vertexGroup == null) {
|
if (vertexGroup == null) {
|
||||||
vertexGroup = new VertexGroup();
|
vertexGroup = new VertexGroup();
|
||||||
vertexGroups.put(bone.getName(), vertexGroup);
|
vertexGroups.put(bone.getName(), vertexGroup);
|
||||||
}
|
}
|
||||||
vertexGroup.setBoneIndex(boneIndex);
|
vertexGroup.setBoneIndex(boneIndex);
|
||||||
|
|
||||||
for (Entry<Integer, List<Integer>> entry : vertexReferenceMap.entrySet()) {
|
for (Entry<Integer, List<Integer>> entry : vertexReferenceMap.entrySet()) {
|
||||||
List<Integer> vertexIndices = entry.getValue();
|
List<Integer> vertexIndices = entry.getValue();
|
||||||
for (int j = 0; j < indexes.limit(); ++j) {
|
for (int j = 0; j < indexes.limit(); ++j) {
|
||||||
int index = indexes instanceof ShortBuffer ? ((ShortBuffer) indexes).get(j) : ((IntBuffer) indexes).get(j);
|
int index = indexes instanceof ShortBuffer ? ((ShortBuffer) indexes).get(j) : ((IntBuffer) indexes).get(j);
|
||||||
if (vertexIndices.contains(index)) {// current geometry has the index assigned to the current mesh
|
if (vertexIndices.contains(index)) {// current geometry has the index assigned to the current mesh
|
||||||
int ii = index * 3;
|
int ii = index * 3;
|
||||||
pos.set(positions.get(ii), positions.get(ii + 1), positions.get(ii + 2));
|
pos.set(positions.get(ii), positions.get(ii + 1), positions.get(ii + 2));
|
||||||
// move the vertex to the global space position
|
// move the vertex to the global space position
|
||||||
objectWorldMatrix.mult(pos, pos);// TODO: optimize: check every vertex once and apply its references
|
objectWorldMatrix.mult(pos, pos);// TODO: optimize: check every vertex once and apply its references
|
||||||
if (boneEnvelope.isInEnvelope(pos)) {
|
if (boneEnvelope.isInEnvelope(pos)) {
|
||||||
vertexGroup.addVertex(index, boneEnvelope.getWeight());
|
vertexGroup.addVertex(index, boneEnvelope.getWeight());
|
||||||
} else if (boneIndex == 5) {
|
} else if (boneIndex == 5) {
|
||||||
System.out.println("Si nie zaapa: " + pos);
|
System.out.println("Si nie zaapa: " + pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<Integer, WeightsAndBoneIndexes> weights = new HashMap<Integer, WeightsAndBoneIndexes>();// [vertex_index; [bone_index; weight]]
|
Map<Integer, WeightsAndBoneIndexes> weights = new HashMap<Integer, WeightsAndBoneIndexes>();// [vertex_index; [bone_index; weight]]
|
||||||
if (vertexGroups.size() > 0) {
|
if (vertexGroups.size() > 0) {
|
||||||
LOGGER.fine("Gathering vertex groups information to prepare the buffers for the mesh.");
|
LOGGER.fine("Gathering vertex groups information to prepare the buffers for the mesh.");
|
||||||
for (VertexGroup vertexGroup : vertexGroups.values()) {
|
for (VertexGroup vertexGroup : vertexGroups.values()) {
|
||||||
for (Entry<Integer, Float> entry : vertexGroup.entrySet()) {
|
for (Entry<Integer, Float> entry : vertexGroup.entrySet()) {
|
||||||
WeightsAndBoneIndexes vertexWeights = weights.get(entry.getKey());
|
WeightsAndBoneIndexes vertexWeights = weights.get(entry.getKey());
|
||||||
if (vertexWeights == null) {
|
if (vertexWeights == null) {
|
||||||
vertexWeights = new WeightsAndBoneIndexes();
|
vertexWeights = new WeightsAndBoneIndexes();
|
||||||
weights.put(entry.getKey(), vertexWeights);
|
weights.put(entry.getKey(), vertexWeights);
|
||||||
}
|
}
|
||||||
vertexWeights.put(vertexGroup.getBoneIndex(), entry.getValue());
|
vertexWeights.put(vertexGroup.getBoneIndex(), entry.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.log(Level.FINE, "Equalizing the amount of weights per vertex to {0} if any of them has more or less.", MAXIMUM_WEIGHTS_PER_VERTEX);
|
LOGGER.log(Level.FINE, "Equalizing the amount of weights per vertex to {0} if any of them has more or less.", MAXIMUM_WEIGHTS_PER_VERTEX);
|
||||||
for (Entry<Integer, WeightsAndBoneIndexes> entry : weights.entrySet()) {
|
for (Entry<Integer, WeightsAndBoneIndexes> entry : weights.entrySet()) {
|
||||||
maximumWeightsPerVertex = Math.max(maximumWeightsPerVertex, entry.getValue().size());
|
maximumWeightsPerVertex = Math.max(maximumWeightsPerVertex, entry.getValue().size());
|
||||||
entry.getValue().normalize(MAXIMUM_WEIGHTS_PER_VERTEX);
|
entry.getValue().normalize(MAXIMUM_WEIGHTS_PER_VERTEX);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maximumWeightsPerVertex > MAXIMUM_WEIGHTS_PER_VERTEX) {
|
if (maximumWeightsPerVertex > MAXIMUM_WEIGHTS_PER_VERTEX) {
|
||||||
LOGGER.log(Level.WARNING, "{0} has vertices with more than 4 weights assigned. The model may not behave as it should.", meshStructure.getName());
|
LOGGER.log(Level.WARNING, "{0} has vertices with more than 4 weights assigned. The model may not behave as it should.", meshStructure.getName());
|
||||||
maximumWeightsPerVertex = MAXIMUM_WEIGHTS_PER_VERTEX;// normalization already made at most 'MAXIMUM_WEIGHTS_PER_VERTEX' weights per vertex
|
maximumWeightsPerVertex = MAXIMUM_WEIGHTS_PER_VERTEX;// normalization already made at most 'MAXIMUM_WEIGHTS_PER_VERTEX' weights per vertex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(maximumWeightsPerVertex == 0) {
|
if(maximumWeightsPerVertex == 0) {
|
||||||
LOGGER.fine("No vertex group data nor bone envelopes found to attach vertices to bones!");
|
LOGGER.fine("No vertex group data nor bone envelopes found to attach vertices to bones!");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.fine("Preparing buffers for the mesh.");
|
LOGGER.fine("Preparing buffers for the mesh.");
|
||||||
FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
|
FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
|
||||||
ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
|
ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
|
||||||
for (int i = 0; i < indexes.limit(); ++i) {
|
for (int i = 0; i < indexes.limit(); ++i) {
|
||||||
int index = indexes instanceof ShortBuffer ? ((ShortBuffer) indexes).get(i) : ((IntBuffer) indexes).get(i);
|
int index = indexes instanceof ShortBuffer ? ((ShortBuffer) indexes).get(i) : ((IntBuffer) indexes).get(i);
|
||||||
WeightsAndBoneIndexes weightsAndBoneIndexes = weights.get(index);
|
WeightsAndBoneIndexes weightsAndBoneIndexes = weights.get(index);
|
||||||
if (weightsAndBoneIndexes != null) {
|
if (weightsAndBoneIndexes != null) {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (Entry<Integer, Float> entry : weightsAndBoneIndexes.entrySet()) {
|
for (Entry<Integer, Float> entry : weightsAndBoneIndexes.entrySet()) {
|
||||||
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getValue());
|
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getValue());
|
||||||
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getKey().byteValue());
|
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getKey().byteValue());
|
||||||
++count;
|
++count;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if no bone is assigned to this vertex then attach it to the 0-indexed root bone
|
// if no bone is assigned to this vertex then attach it to the 0-indexed root bone
|
||||||
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
|
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
|
||||||
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
|
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
|
VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
|
||||||
verticesWeights.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.Float, weightsFloatData);
|
verticesWeights.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.Float, weightsFloatData);
|
||||||
|
|
||||||
VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);
|
VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);
|
||||||
verticesWeightsIndices.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.UnsignedByte, indicesData);
|
verticesWeightsIndices.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.UnsignedByte, indicesData);
|
||||||
|
|
||||||
return new MeshWeightsData(maximumWeightsPerVertex, verticesWeights, verticesWeightsIndices);
|
return new MeshWeightsData(maximumWeightsPerVertex, verticesWeights, verticesWeightsIndices);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that gathers the data for mesh bone buffers.
|
* A class that gathers the data for mesh bone buffers.
|
||||||
* Added to increase code readability.
|
* Added to increase code readability.
|
||||||
*
|
*
|
||||||
* @author Marcin Roguski (Kaelthas)
|
* @author Marcin Roguski (Kaelthas)
|
||||||
*/
|
*/
|
||||||
private static class MeshWeightsData {
|
private static class MeshWeightsData {
|
||||||
public final int maximumWeightsPerVertex;
|
public final int maximumWeightsPerVertex;
|
||||||
public final VertexBuffer verticesWeights;
|
public final VertexBuffer verticesWeights;
|
||||||
public final VertexBuffer verticesWeightsIndices;
|
public final VertexBuffer verticesWeightsIndices;
|
||||||
|
|
||||||
public MeshWeightsData(int maximumWeightsPerVertex, VertexBuffer verticesWeights, VertexBuffer verticesWeightsIndices) {
|
public MeshWeightsData(int maximumWeightsPerVertex, VertexBuffer verticesWeights, VertexBuffer verticesWeightsIndices) {
|
||||||
this.maximumWeightsPerVertex = maximumWeightsPerVertex;
|
this.maximumWeightsPerVertex = maximumWeightsPerVertex;
|
||||||
this.verticesWeights = verticesWeights;
|
this.verticesWeights = verticesWeights;
|
||||||
this.verticesWeightsIndices = verticesWeightsIndices;
|
this.verticesWeightsIndices = verticesWeightsIndices;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map between the bone index and the bone's weight.
|
* A map between the bone index and the bone's weight.
|
||||||
*
|
*
|
||||||
* @author Marcin Roguski (Kaelthas)
|
* @author Marcin Roguski (Kaelthas)
|
||||||
*/
|
*/
|
||||||
private static class WeightsAndBoneIndexes extends HashMap<Integer, Float> {
|
private static class WeightsAndBoneIndexes extends HashMap<Integer, Float> {
|
||||||
private static final long serialVersionUID = 2754299007299077459L;
|
private static final long serialVersionUID = 2754299007299077459L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The method normalizes the weights and bone indexes data.
|
* The method normalizes the weights and bone indexes data.
|
||||||
* First it truncates the amount to MAXIMUM_WEIGHTS_PER_VERTEX because this is how many weights JME can handle.
|
* First it truncates the amount to MAXIMUM_WEIGHTS_PER_VERTEX because this is how many weights JME can handle.
|
||||||
* Next it normalizes the weights so that the sum of all verts is 1.
|
* Next it normalizes the weights so that the sum of all verts is 1.
|
||||||
* @param maximumSize
|
* @param maximumSize
|
||||||
* the maximum size that the data will be truncated to (usually: MAXIMUM_WEIGHTS_PER_VERTEX)
|
* the maximum size that the data will be truncated to (usually: MAXIMUM_WEIGHTS_PER_VERTEX)
|
||||||
*/
|
*/
|
||||||
public void normalize(int maximumSize) {
|
public void normalize(int maximumSize) {
|
||||||
if (this.size() > maximumSize) {// select only the most significant weights
|
if (this.size() > maximumSize) {// select only the most significant weights
|
||||||
float lowestWeight = Float.MAX_VALUE;
|
float lowestWeight = Float.MAX_VALUE;
|
||||||
int lowestWeightIndex = -1;
|
int lowestWeightIndex = -1;
|
||||||
HashMap<Integer, Float> msw = new HashMap<Integer, Float>(maximumSize);// msw = Most Significant Weight
|
HashMap<Integer, Float> msw = new HashMap<Integer, Float>(maximumSize);// msw = Most Significant Weight
|
||||||
for (Entry<Integer, Float> entry : this.entrySet()) {
|
for (Entry<Integer, Float> entry : this.entrySet()) {
|
||||||
if (msw.size() < maximumSize) {
|
if (msw.size() < maximumSize) {
|
||||||
msw.put(entry.getKey(), entry.getValue());
|
msw.put(entry.getKey(), entry.getValue());
|
||||||
if (entry.getValue() < lowestWeight) {
|
if (entry.getValue() < lowestWeight) {
|
||||||
lowestWeight = entry.getValue();
|
lowestWeight = entry.getValue();
|
||||||
lowestWeightIndex = entry.getKey();
|
lowestWeightIndex = entry.getKey();
|
||||||
}
|
}
|
||||||
} else if (entry.getValue() > lowestWeight) {
|
} else if (entry.getValue() > lowestWeight) {
|
||||||
msw.remove(lowestWeightIndex);
|
msw.remove(lowestWeightIndex);
|
||||||
msw.put(lowestWeightIndex, lowestWeight);
|
msw.put(lowestWeightIndex, lowestWeight);
|
||||||
|
|
||||||
// search again for the lowest weight
|
// search again for the lowest weight
|
||||||
lowestWeight = Float.MAX_VALUE;
|
lowestWeight = Float.MAX_VALUE;
|
||||||
for (Entry<Integer, Float> e : msw.entrySet()) {
|
for (Entry<Integer, Float> e : msw.entrySet()) {
|
||||||
if (e.getValue() < lowestWeight) {
|
if (e.getValue() < lowestWeight) {
|
||||||
lowestWeight = e.getValue();
|
lowestWeight = e.getValue();
|
||||||
lowestWeightIndex = e.getKey();
|
lowestWeightIndex = e.getKey();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// replace current weights with the given ones
|
// replace current weights with the given ones
|
||||||
this.clear();
|
this.clear();
|
||||||
this.putAll(msw);
|
this.putAll(msw);
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalizing the weights so that the sum of the values is equal to '1'
|
// normalizing the weights so that the sum of the values is equal to '1'
|
||||||
float sum = 0;
|
float sum = 0;
|
||||||
for (Entry<Integer, Float> entry : this.entrySet()) {
|
for (Entry<Integer, Float> entry : this.entrySet()) {
|
||||||
sum += entry.getValue();
|
sum += entry.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sum != 0 && sum != 1) {
|
if (sum != 0 && sum != 1) {
|
||||||
for (Entry<Integer, Float> entry : this.entrySet()) {
|
for (Entry<Integer, Float> entry : this.entrySet()) {
|
||||||
entry.setValue(entry.getValue() / sum);
|
entry.setValue(entry.getValue() / sum);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,8 +190,8 @@ public class ObjectHelper extends AbstractBlenderHelper {
|
|||||||
LightHelper lightHelper = blenderContext.getHelper(LightHelper.class);
|
LightHelper lightHelper = blenderContext.getHelper(LightHelper.class);
|
||||||
List<Structure> lampsArray = pLamp.fetchData();
|
List<Structure> lampsArray = pLamp.fetchData();
|
||||||
result = lightHelper.toLight(lampsArray.get(0), blenderContext);
|
result = lightHelper.toLight(lampsArray.get(0), blenderContext);
|
||||||
if(result == null) {
|
if (result == null) {
|
||||||
//probably some light type is not supported, just create a node so that we can maintain child-parent relationship for nodes
|
// probably some light type is not supported, just create a node so that we can maintain child-parent relationship for nodes
|
||||||
result = new Node(name);
|
result = new Node(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,7 +218,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
|
|||||||
if (objectType == ObjectType.ARMATURE) {
|
if (objectType == ObjectType.ARMATURE) {
|
||||||
blenderContext.addMarker(ARMATURE_NODE_MARKER, result, Boolean.TRUE);
|
blenderContext.addMarker(ARMATURE_NODE_MARKER, result, Boolean.TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.setLocalTransform(t);
|
result.setLocalTransform(t);
|
||||||
result.setCullHint(visible ? CullHint.Always : CullHint.Inherit);
|
result.setCullHint(visible ? CullHint.Always : CullHint.Inherit);
|
||||||
if (parent instanceof Node) {
|
if (parent instanceof Node) {
|
||||||
@ -245,7 +245,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
|
|||||||
|
|
||||||
LOGGER.fine("Applying animations to the object if such are defined.");
|
LOGGER.fine("Applying animations to the object if such are defined.");
|
||||||
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
|
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
|
||||||
animationHelper.applyAnimations(result, blenderContext.getBlenderKey().getNodeAnimationNames(name));
|
animationHelper.applyAnimations(result, blenderContext.getBlenderKey().getAnimationMatchMethod());
|
||||||
|
|
||||||
LOGGER.fine("Loading constraints connected with this object.");
|
LOGGER.fine("Loading constraints connected with this object.");
|
||||||
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
|
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user