www.jpct.net
Bones  Skeletal and Pose Animations for jPCT/jPCTAE => Bones => Topic started by: AGP on June 16, 2016, 06:44:26 am

I've exported into a JSONserialized format of my creation all the weights of all the joints in each vertex. How, now, do I fill the 2dimensional arrays that are weights and jointIndices? Thanks in advance.

MAX_JOINTS_PER_VERTEX = 4
jointIndices is a [mesh size][MAX_JOINTS_PER_VERTEX] short array, describing which joints in skeleton is affecting each vertex.
weights is a [mesh size][MAX_JOINTS_PER_VERTEX] float array, describing how much each vertex is affected by corresponding joint in skeleton. if weight is zero, no calculation is done and corresponding jointIndex is irrelevant.

I have filled arrays with the Bone and BoneReference classes below. How would you fill your arrays given this data? The following was my first, unsuccessful attempt (I don't understand how the data is arranged in your arrays, and I assumed that mesh_size is maxTriangles).
float[][] weights = new float[maxTriangles][Skeleton.MAX_JOINTS_PER_VERTEX];//Skeleton.MAX_JOINTS_PER_VERTEX IS 4
short[][] jointIndices = new short[maxTriangles][Skeleton.MAX_JOINTS_PER_VERTEX];
for (int y = 0; y < weights[0].length; y++) {
for (int x = 0; x < weights.length; x++) {
weights[x][y] = boneRefs.get(x).vertexWeight;
jointIndices[x][y] = boneRefs.get(x).vertexIndexReference;
}
}
class Bone {
protected String name;
protected SimpleVector position;
protected Matrix transform;
protected int index, parentIndex;
public Bone(String name, SimpleVector position, Matrix transform, int index, int parentIndex) {
this.name = name;
this.position = position;
this.transform = transform;
this.index = index;
this.parentIndex = parentIndex;
}
}
class BoneReference {
public short vertexIndexReference;
public short boneIndexReference;
public float vertexWeight;
public BoneReference(short vertexIndexReference, short boneIndexReference, float vertexWeight) {
this.vertexIndexReference = vertexIndexReference;
this.boneIndexReference = boneIndexReference;
this.vertexWeight = vertexWeight;
}
}

weights[x][y] = boneRefs.get(x).vertexWeight;
jointIndices[x][y] = boneRefs.get(x).vertexIndexReference;
if I understand your stucture correct, these should be just the opposite.
jointIndices[x][y]
says vertex x is influenced by joint y, NOT joint x influences vertex y. similar for weights.
looks like you shoud loop over boneRefs and fill in jointIndices and weights accordingly. not the other way around

OK, but if I do
for (int i = 0; i < boneRefs.size(); i++)
then, what's y in
jointIndices[i][y] = boneRefs.get(i).vertexIndexReference
? I can't seem to visualize the 2d array as you designed it.

you cant do that in a single iteration IMHO.*
first iterate over boneRefs to collect vertexbone relations
map = Map<Integer, List<Integer>>();
for (b: boneRefs) {
map.get(b.vertexIndexReference).add(b.boneIndexReference);
}
then loop over your map to place into jointIndices array. sth like this
for (e : map.entrySet) {
count = 0;
for (index : e.value) {
jointIndices[e.key][count++] = index;
}
}
hope this helps
r a f t
(*) actually you can but will be ugly

Same confusion: between
HashMap<Integer, java.util.List<Integer>> map = new HashMap<Integer, java.util.List<Integer>>();
for (BoneReference b: boneRefs)
and
map.get(b.vertexIndexReference).add((int)b.boneIndexReference);
//then loop over your map to place into jointIndices array. sth like this
for (Map.Entry<Integer, java.util.List<Integer>> e : map.entrySet()) {
int count = 0;
for (Integer index : e.getValue())//index : e.value
jointIndices[e.getKey()][count++] = index.shortValue();
}
how do I fill the HashMap?

it's written above AGP, in the first iteration
map = Map<Integer, List<Integer>>();
for (b: boneRefs) {
map.get(b.vertexIndexReference).add(b.boneIndexReference);
}
but of course you should first create the list if it's null

boneRefs is neither null nor size 0. But map.get(b.vertexIndexReference).add((int)b.boneIndexReference) produces a NullPointerExecption (because get(...) returns a null value).

but of course you should first create the list if it's null
map = Map<Integer, List<Integer>>();
for (b: boneRefs) {
list = map.get(b.vertexIndexReference);
if (list == null) {
list = new ArrayList<Integer>();
map.put(b.vertexIndexReference, list);
}
list.add(b.boneIndexReference);
}

Ah, there you go. Thanks for holding my hand. I've no experience with hash maps. It's working now. Next step, animation!

Hey, guys.
I'm working with AGP and I got stuck.
About the weights and jointsIndices, I'm not sure if I got it right. Here is how I'm building the skin data:
float[][] weights = new float[vertices.size()][Skeleton.MAX_JOINTS_PER_VERTEX];
short[][] jointIndices = new short[bones.size()][Skeleton.MAX_JOINTS_PER_VERTEX];
int[] weightCounters = new int[vertices.size()];
int[] jointIndicesCounters = new int[bones.size()];
for (BoneReference aBoneRef: boneRefs) {
int weightCounter = weightCounters[aBoneRef.vertexIndexReference];
if (weightCounter < Skeleton.MAX_JOINTS_PER_VERTEX) {
weights[aBoneRef.vertexIndexReference][weightCounter] = aBoneRef.vertexWeight;
weightCounters[aBoneRef.vertexIndexReference]++;
}
int jointIndicesCounter = jointIndicesCounters[aBoneRef.boneIndexReference];
if (jointIndicesCounter < Skeleton.MAX_JOINTS_PER_VERTEX) {
jointIndices[aBoneRef.boneIndexReference][jointIndicesCounter] = aBoneRef.vertexIndexReference;
jointIndicesCounters[aBoneRef.boneIndexReference]++;
}
}
Everything goes well untill I try to applySkeletonPose. I get index out of bounds.
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 249
at raft.jpct.bones.Animated3D.applySkeletonPose(Animated3D.java:523)
at Importer.loop(Importer.java:344)
at Importer.<init>(Importer.java:56)
at Importer.main(Importer.java:422)
I'm using the SkeletonDebugger to check the bones and they're look fine.
Do you have any idea on how to make it work?
Thanks!

it's hard to guess what your code exactly does but I've noticed a logical error. both weights' and jointIndices' first dimension is vertex index. looks like you use bone index as jointIndices' first dimension.
see the message above:
http://www.jpct.net/forum2/index.php/topic,4706.msg32344.html#msg32344

Joint number 9 is the left upper arm. The following code produces the following image. It looks a lot like it's a weight accuracy problem, now, doesn't it? The UVs themselves mostly work but are somewhat screwy too. I don't suppose that you would have any insight on this problem.
else if (state.getKeyCode() == KeyEvent.VK_X) {
Animated3D animated = ((Animated3D)toLoad);
Skeleton skeleton = animated.getSkeleton();
SkeletonPose pose = animated.getSkeletonPose();
SkeletonPose pose2 = pose.clone();
pose2.setToBindPose();
int jointCount = skeleton.getNumberOfJoints();
pose2.getLocal(9).rotateX(1f);
pose2.updateTransforms();
animated.setSkeletonPose(pose2);
this.skeletonDebugger.update(pose2);
animated.applySkeletonPose();
animated.applyAnimation();
}
(https://dl.dropboxusercontent.com/u/93826015/NewBones.jpg)

I'm not sure if this is a weight accuracy problem. any progress on this one?

Kind of, sort of. I'm going to see this through, but at the moment it's still broken. I think I'll have news on Thursday.

OK, in all the tests the model appears to both export and import right (then again, there's that pesky UV issue). The next step was always to write an exporter for Blender, so instead we're going to be writing an IMPORTER first. Hopefully I'll have more tomorrow.

News: UVs in Blender are proving tougher than expected. But the bones are moving better (still not perfect) than in Bones. I will post screenshots later tonight.

Voilá. UVs and all. Alas, I have no clue about the UV and weight problems (problem?).
UPDATE: Updated the image to include the model in Bones, for comparison's sake.
(https://dl.dropboxusercontent.com/u/93826015/NewBones1&2.jpg)

I'm even exporting animations, now. Trouble is Max's quaternions aren't making any sense to me, even when I call invert() (in the MaxScript). I've also included the transformation matrix, as retrieved for each vertex with bone.transform (MaxScript). Either I fix the quaternions or I extract the data from the transform matrix. The following method is used to build a jpct transformation matrix. Given that, how could I go about using the matrix?
private Matrix getTransformationMatrix(JSONArray transformData) {
Matrix transformMatrix = new Matrix();
for (int j = 0; j < 4; j++) {
JSONArray row = (JSONArray) transformData.get(j);
transformMatrix.setRow(j,
((Number)row.get(0)).floatValue(),
((Number)row.get(1)).floatValue(),
((Number)row.get(2)).floatValue(), 1f);
}
return transformMatrix;
}

I have no idea how Max stores transformation infomation so I have no idea how to pull information out of that matrix.
Try Googling or Max documents.

Again, it's not about max. Sorry I even mentioned the max script. The question was: how do I turn a transformation matrix per vertex per frame into a SkinClip. I'll post how I did it with the quaternions in a little bit, but the matrices are easier to convert (I've already done it or I wouldn't even have a model).

check javadoc of SkinClip and JointChannel.
think of JointChannel as a series of points in time describing Joint's transformation. times between points are interpolated.
you need to pull translations (SimpleVector) and rotations (Quaternion) data out of a series of that transformation Matrices.

The Matrix method is slightly better than the Quaternion one. Both methods produce a blob of animated polygons (but the Matrix blob has feet and a head somewhere in the middle). Animations are easily identifiable.
Matrix transformationMatrix = getTransformationMatrix(transformData);
times[i] = (float)i/(float)framesCount;
// translations[i] = new SimpleVector(((Number)translation.get(0)).floatValue(), ((Number)translation.get(1)).floatValue(), ((Number)translation.get(2)).floatValue());
// rotations[i] = new Quaternion(((Number)rotation.get(0)).floatValue(), ((Number)rotation.get(1)).floatValue(), ((Number)rotation.get(2)).floatValue(), ((Number)rotation.get(3)).floatValue());
translations[i] = SimpleVector.ORIGIN;//transformationMatrix.getTranslation();
rotations[i] = new Quaternion(transformationMatrix);
(https://dl.dropboxusercontent.com/u/93826015/TheBlob.jpg)

I managed to export and reimport all the animation ciips in 3ds max (other than that UV issue, the imported model is identical to the original). But in order for them to work on Max, I'm exporting them in worldspace. The result works in max but still doesn't in jpct. Do you transform the transformation matrix at all from Ogre or Collada? If so, what do you do?

No, I don't do any transformation.

I expect that the answer will be the same, but just to clarify: I meant the transform matrices not of the object itself but of the animation description (per joint per frame, naturally).

No, I don't do any transformation.

Got animation working! Still has a weight problem. I'll post a video soon.

Hi, raft.
Should the vertices' weights be normalized?
I didn't find any reference about it in the docs or in this forum.
Thanks

Should the vertices' weights be normalized?
you mean like sum of weights should be 1 per vertex? no there is no such requirement

you mean like sum of weights should be 1 per vertex? no there is no such requirement
great, thanks again :)

But actually, it's normalized out of 3ds max. So the better question is: can it be normalized or should I multiply them by some value? What is the range of the weights? Thanks in advance.

programatically there is no restriction. if the value is too low its affect will be hardly noticable, if it's too big you will get some weird affects

I made a printout of the SkinDatas of the same model exported to BONES and exported to my JSONserialized format in an effort to solve my weight problem (and, with a little luck, kill the UV issue as well with the same proverbial stone). The following image shows that there's nearly six times more weights and jointIndices in the BONES version. SkinData doesn't hold animation information, does it? And if it's not animation, what could explain this? Thanks in advance.
(https://dl.dropboxusercontent.com/u/93826015/JSONvsBONES.png)

SkinData doesn't hold animation information, does it?
no it doesnt.
not sure what's going on there. as a reminder Bones does not have exporter/importer code. it just uses JME's importer

hey, raft. in some models, some vertices have more than 4 joints associated to them. do you have any input on how to handle these cases?

And is it possible that other engines, like Unity, have some kind of logic to redistribute the weights for models that use more than four? Yet another question: is it trivial to increase the number of joints per vertices, or would that be a massive undertaking?

hey, raft. in some models, some vertices have more than 4 joints associated to them. do you have any input on how to handle these cases?
need to check loader (jME, ardor3d) code but most possibly they are simply ignored. I cant see any good reason to support more than 4 joints. to me 4 joints are almost more than good enough

And is it possible that other engines, like Unity, have some kind of logic to redistribute the weights for models that use more than four? Yet another question: is it trivial to increase the number of joints per vertices, or would that be a massive undertaking?
assuming exporter supports it, it should be modified in loader code (jME, ardor3d). but again I dont see any good benefit for that

We have for the same model, two very different vertex counts (triple the number of triangles in jpct versus the unique vertex count which is typically less than the number of triangles in any given model), while retaining the same number of triangles, between jpct and 3ds max. But when we create our SkinData, we use only the number of unique vertices. Now we're thinking that we should be using the number of vertices in jpct. Did that make sense to you and do you agree?

what e
We have for the same model, two very different vertex counts (triple the number of triangles in jpct versus the unique vertex count which is typically less than the number of triangles in any given model), while retaining the same number of triangles, between jpct and 3ds max. But when we create our SkinData, we use only the number of unique vertices. Now we're thinking that we should be using the number of vertices in jpct. Did that make sense to you and do you agree?
what exactly is your pipeline?
if you are creating your skeletal data from scratch in code, and loading your model from 3ds format or similar, yes, you possibly need to use number of vertices instead of unique vertex count.

No, the bones are also defined in the file. What do you mean by our pipeline?

I'm not sure I understand what you are trying to do.
No, the bones are also defined in the file.
then why are you creating a SkinData "manually"?

I saved the data from 3ds max's skin and I'm applying it to SkinData. Is there an automatic way to calculate SkinData? Am I being thick?

we are talking about Bones' SkinData class, right?
when you import any skeletal animation into Bones, SkinData is automatically generated for you. you can access it via Animated3D.getSkinData()

I'd still need the very first one, for the model itself, right? We're still talking about my JSONserialized format, you see.

I'd still need the very first one, for the model itself, right?
sorry?
We're still talking about my JSONserialized format, you see.
in that case you shouldnt care about vertex count or unique vertex count in jPCT. your exporter from 3dsMax and importer to jPCT should agree on that.

Our skeleton order, on a regular Biped skeleton, goes: index 0 for the skeleton itself, 1 for "footsteps", 2 for pelvis, from which it then moves to the head before moving to the clavicles and arms. Is there any particular order (since what is basically a tree has to be converted into an ordered array) needed?

as you intend to write the importer part too, it doesnt actually matter.
but Bones' internal logic requires joints in a skeleton are ordered such that a joint's parent joint always comes before the joint itself. this should be handled either at exporter or importer.

Yeah, we did that on both ends for performance. Still, we get joint issues. I'll post more on Tuesday. Thanks for the quick response.