Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Joint Group Indices don't correspond to joint indices #41

Open
mad-guru opened this issue Jan 20, 2024 · 11 comments
Open

Joint Group Indices don't correspond to joint indices #41

mad-guru opened this issue Jan 20, 2024 · 11 comments

Comments

@mad-guru
Copy link

Hi,

I'm working with the dna calibration tools (thank you for these amazing additions to Metahumans).

Question:

I can get joint names using dnaReader.getJointName(num)
face control names using dnaReader.getGUIControlName(num)

I can get Group Indices connected to a control using dnaReader.getJointGroupJointIndices(index)

The joint indices returned by that command don't correspond to the index numbers from dnaReader.getJointName(num).

How can I get the joints names that go with the GroupJointIndices?

Thank you

@n0phx
Copy link

n0phx commented Jan 29, 2024

Hello!

I can get Group Indices connected to a control using dnaReader.getJointGroupJointIndices(index)

what you describe here as control is not actually the control index. The above used API, dnaReader.getJointGroupJointIndices(index) is accepting the joint group index, not the control index as its only argument.

Other than that, the joint indices that are returned by the dnaReader.getJointGroupJointIndices(jointGroupIndex) API can be used to get the joint names. This will indeed have the effect that you expect, which is to return you the joint names belonging to a particular joint group. (Bear in mind that joint groups are NOT controls, joints are separated into joint groups - see the whitepaper about RigLogic for the details about how this separation happens).

for jointGroupIndex in range(dnaReader.getJointGroupCount()):
    jointIndices = dnaReader.getJointGroupJointIndices(jointGroupIndex):
    for jointIndex in jointIndices:
        jointName = dnaReader.getJointName(jointIndex)

This above shown snippet will gather joint names within each joint group.

Now, finding which exact joints are affected by which exact controls is a more complicated process :)

@mad-guru
Copy link
Author

Thanks so much for the reply. I did know how to do that part, but could use some help with the following.

face_riglogic
I am developing Maya tools around DNA Calibration tools to create Custom Metahumans on meshes that would not work in Mesh to Metahuman. I’m trying to wrap my head around Behavior layer data, looking at the DNA file as JSON, reading the white paper on rig logic and documentation.

I have learned a lot but am still unclear on the exact nature of
input indices (contains expression index and other data?),
output indices (same question as input indices)
How to find specific data in it? Do I look at JointRowCount and JointColumnCount to help determine where the value for a particular expression or control resides etc?

I have some workarounds to get most of the data I need but do need to figure out jointgroup values at the very least so that I can save the resulting updates to control triggered joint positions.

jointgroup values (I think based on y=kx or is it y=kx+b?
my understanding is that:
y is the resulting stored value
k is the delta (triggered transform - neutral transform) * x which is the control value, in the case of stored values, always 1.
b would be adding the neutral value again for the actual scene transform value I think

Any help in clarifying this data would be appreciated.

@n0phx
Copy link

n0phx commented Feb 1, 2024

input indices (contains expression index and other data?),

this is correct, it contains only expression indices, where expressions are:

  1. the raw controls that you can set through RigLogic
  2. and corrective (PSD) expressions (which are automatically turned on by RigLogic (the user cannot set these)

output indices (same question as input indices)

output indices are joint attribute indices. Each joint has 9 attributes (translation.x, translation.y, translation.z, rotation.x, rotation.y, rotation.z, scale.x, scale.y, scale.z), so given any output index, when divided by 9, will result in the joint index (and this is how you can find / match which joint is affected by which row from the joint group values matrix). The joint group values matrix is in row major order, with the rows being joint deltas per each joint attribute and the columns being the expressions that turn them on. So:

  • dna.getJointGroupOutputIndices(jointGroupIndex)[3] will give you the joint attribute index affected by the fourth row from the joint group matrix.
  • dna.getRawControlName(dna.getJointGroupInputIndices(jointGroupIndex)[5]) would give the raw control name for the sixth column from the matrix.
  • There are len(dna.getJointGroupOutputIndices(jointGroupIndex)) rows and len(dna.getJointGroupInputIndices(jointGroupIndex)) columns in the matrix.

Using this information you should be able to pinpoint which exact raw controls are affecting which exact joint attributes.

jointgroup values (I think based on y=kx or is it y=kx+b?

the equation is indeex y=k*x+n, but n = 0 always for the joint behavior data, so we remain with k*x only. Neutral values are not considered in this equation, since rotations cannot be naively added onto the delta values.

@xiancg
Copy link

xiancg commented Feb 2, 2024

Hi @n0phx !

Thanks for the replies you've been providing in the GitHub issues.

May I ask how is it possible that this affirmation is correct?

"so given any output index, when divided by 9, will result in the joint index"

Since output indices are consecutive integer numbers in sets of 6 (since scale values are ignored), how is it possible that any given set of consecutive integers divided by 9 results in the same number (the joint index)? By aproximation of the possible resulting float results to an integer value?

Thanks!

@n0phx
Copy link

n0phx commented Feb 2, 2024

Since output indices are consecutive integer numbers in sets of 6 (since scale values are ignored),

this is not a correct assumption, you may only assume that output indices will appear in ascending order, but it is a sparse storage, so rows will be frequently skipped entirely where all deltas would be zeros in the matrix (this is part of the pruning strategy described in the paper).
When I mentioned division, I implied integer division, not floating point, so the automatic rounding will give you correct results:
e.g.:

output index 17
17 / 9 = 1 (so this is joint index 1)
17 % 9 = 8 (so the attribute index within the joint is 8 which means it's the scale.z value) (attributes will always follow the order [translation.x, translation.y, translation.z, rotation.x, rotation.y, rotation.z, scale.x, scale.y, scale.z]

output index 112
112 / 9 = 12 (so this is joint 12)
112 % 9 = 4 (so this is the rotation.x attribute of the joint)

and scale values (while might not be frequent) but they certainly appear somewhere in the behavior data, they are not entirely absent, just not that frequently used.

@mad-guru
Copy link
Author

mad-guru commented Feb 8, 2024

Thanks so much for all of this clarification n0phx. I read it as soon as you posted, but needed a few days to really explore and try things out. I ended up rewriting code with this more detailed understanding, which makes for a much faster and more efficient tool.

I think I have some values updated correctly, by taking the entire joint group values and updating a few indices with new values.

setJointGroupValues(jointGroupIndex, values, count)

For saving out I am using something like this to keep previous dna data with setfrom and add an updated values list:

        outputPath = dna_path.replace('.dna', '_updated.dna')
    
        # Create a writer DNA from input DNA
        output_stream = dna.FileStream(outputPath, dna.FileStream.AccessMode_Write, dna.FileStream.OpenMode_Binary)
        writer = dna.BinaryStreamWriter(output_stream)
        writer.setFrom(dnaReader)
        writer.setJointGroupValues(joint_group, values)        
        writer.write()
        print(f'Finished writing out {dna_path}')

With this way, I wasn't sure how I might use count in setJointGroupValues. Would that be if I created a partial list of new values such as:

[0.003, 0.2012, 0.02342]

and somehow insert just that at the right spot, telling it how many values in the count?

Also, since it is for a specific group, if I am updating several groups, I need to save to dna file each time, which can slow down the process. Any advice for efficient use here?

Thank you.

@n0phx
Copy link

n0phx commented Feb 8, 2024

Glad this helps, sadly the API documentation / general usage guidelines for these lower level portions of the library are lacking in depth.

setJointGroupValues(jointGroupIndex, values, count)

The count parameter is only present / visible in the C++ API, it is not available in the Python API. With the C++ API, you give a pointer and a size (which is the count parameter), telling the DNA library how many floating point values are in the matrix.
When you use the Python API, this information is implicitly encoded in the list that you pass to the API, so the length of the given list will be used to infer the count. You may only update the whole matrix, sadly it is not possible to do a partial update of the values (at least with the current API, this might change in the future though)

Also, since it is for a specific group, if I am updating several groups, I need to save to dna file each time, which can slow down the process. Any advice for efficient use here?

you don't need to save the DNA for each single joint group update, you can do a bulk update, call writer.setJointGroupValues in a loop for each joint group, and save the DNA only one time at the end when all the updates are done.

@mad-guru
Copy link
Author

mad-guru commented Feb 13, 2024

Thanks again for the clarifications n0phx. I've been working on organizing the dna file data and generating the correct transform deltas and the corresponding indices to place them into the joint values list. I've been able to write out data, but it is not correct. The basic idea was to subtract neutral transform values from control expression triggered transforms to get the delta values that would go into the joint group values. The joint group indices to place those values is figured out by cycling through output indices to find the correct control expression and input indices to find the correct joints moving row by row to figure out the correct index value. See code below.

    # first step get transform data
    # get neutral value
    joint_index = self.joint_name_list.index(joint_name)
    joint_neutral_translation = self.dnaReader.getNeutralJointTranslation(joint_index)
    joint_neutral_rotation = self.dnaReader.getNeutralJointRotation(joint_index)
    joint_neutral_transform = [joint_neutral_translation[0], joint_neutral_translation[1], joint_neutral_translation[2], joint_neutral_rotation[0], joint_neutral_rotation[1], joint_neutral_rotation[2]]#, 1, 1, 1]
    
    # triggered value
    joint_triggered_translation = cmds.getAttr(joint_name + '.translate')[0]
    joint_triggered_rotation = cmds.getAttr(joint_name + '.rotate')[0]
    joint_triggered_scale = cmds.getAttr(joint_name + '.scale')[0]
    joint_triggered_transform = [joint_triggered_translation[0], joint_triggered_translation[1], joint_triggered_translation[2], joint_triggered_rotation[0], joint_triggered_rotation[1], joint_triggered_rotation[2]]#, joint_triggered_scale[0], joint_triggered_scale[1], joint_triggered_scale[2]]

step 2 get the delta. I have scale commented out as I am not sure if this is used.
delta_transform = [triggered_transform[0] - neutral_transform[0], triggered_transform[1] - neutral_transform[1], triggered_transform[2] - neutral_transform[2], triggered_transform[3] - neutral_transform[3], triggered_transform[4] - neutral_transform[4], triggered_transform[5] - neutral_transform[5]]#, triggered_transform[6] - neutral_transform[6], triggered_transform[7] - neutral_transform[7], triggered_transform[8] - neutral_transform[8]]

I'm not sure if the above is the correct way to get the delta.

step 3 get the value list index for each of these transforms. This way with a list of transform deltas and value indices, I should be able to write out the data correclty.

def return_joint_value_indices(self, joint_group, control_name, joint_name):
    joint_output_indices = self.dnaReader.getJointGroupOutputIndices(joint_group) # joint
    joint_input_indices = self.dnaReader.getJointGroupInputIndices(joint_group) # raw  ctrl

    # within group get values related to the ctrl expression
    # from output indices /9 get joint
    update_value_list = []
    joint_index = 0
    for num in range(len(joint_output_indices)):
        output_index = joint_output_indices[num]

        # from input indices get ctrl expression
        for num in range(len(joint_input_indices)):
            input_index = joint_input_indices[num]
            
            if self.dnaReader.getRawControlName(input_index) == control_name and int(output_index/9) == self.joint_name_list.index(joint_name):
                update_value_list.append(joint_index)
                
            joint_index += 1

unfortunately this does not yield the correct results. I'm not quite sure how to fix this. Any thoughts?

@n0phx
Copy link

n0phx commented Feb 21, 2024

def return_joint_value_indices(self, joint_group, control_name, joint_name):
    joint_output_indices = self.dnaReader.getJointGroupOutputIndices(joint_group) # joint
    joint_input_indices = self.dnaReader.getJointGroupInputIndices(joint_group) # raw  ctrl

    # within group get values related to the ctrl expression
    # from output indices /9 get joint
    update_value_list = []

    row_count = len(joint_output_indices)
    col_count = len(joint_input_indices)

    for row in range(row_count):
        output_index = joint_output_indices[row]
        joint_index = output_index // 9

        # from input indices get ctrl expression
        for col in range(col_count):
            input_index = joint_input_indices[col]

            if self.dnaReader.getRawControlName(input_index) == control_name and joint_index == self.joint_name_list.index(joint_name):
                update_value_list.append(row * col_count + col)

this would collect the indices of the exact delta values from the joint group values matrix that are related to the given control and joint names (I think this is what you intended to implement with the above code).. the rest seems ok from a quick glance

@mad-guru
Copy link
Author

thank you! This was a huge help. The information you provided in this entire thread has really helped increase the understanding of how to work with the dna data in addition to the deep dive video. Much appreciated.

@n0phx
Copy link

n0phx commented Mar 23, 2024

I'm glad it helped!

@EpicGames EpicGames deleted a comment from kalebzaki4 Oct 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants