private static void SetTransformInfo(RopeData.TransformInfo transformInfo, GameObject node)
    {
        if(transformInfo.tfParent != null)
        {
            node.transform.position = transformInfo.tfParent.TransformPoint(transformInfo.v3LocalPosition);
            node.transform.rotation = transformInfo.tfParent.rotation * transformInfo.qLocalOrientation;
        }
        else
        {
            node.transform.position = transformInfo.v3LocalPosition;
            node.transform.rotation = transformInfo.qLocalOrientation;
        }

        node.transform.localScale = transformInfo.v3LocalScale;
    }
    public static void StorePersistentData(UltimateRope rope)
    {
        RopeData ropeData = new RopeData(rope);

        // Attributes

        foreach(FieldInfo fieldInfo in rope.GetType().GetFields())
        {
            if(Attribute.IsDefined(fieldInfo, typeof(RopePersistAttribute)))
            {
                ropeData.m_hashFieldName2Value.Add(fieldInfo.Name, fieldInfo.GetValue(rope));
            }
        }

        // Physics

        if(rope.Deleted)
        {
            ropeData.m_bDeleted = true;
        }
        else
        {
            ropeData.m_aaJointsBroken    = new bool[rope.RopeNodes.Count][];
            ropeData.m_aaJointsProcessed = new bool[rope.RopeNodes.Count][];

            ropeData.m_transformInfoRope = ComputeTransformInfo(rope, rope.gameObject, rope.transform.parent != null ? rope.transform.parent.gameObject : null);

            if(rope.RopeStart != null)
            {
                ropeData.m_transformInfoStart = ComputeTransformInfo(rope, rope.RopeStart, rope.RopeStart.transform.parent != null ? rope.RopeStart.transform.parent.gameObject : null);
            }

            int nLinearLinkIndex = 0;

            for(int nNode = 0; nNode < rope.RopeNodes.Count; nNode++)
            {
                if(rope.RopeNodes[nNode].goNode != null)
                {
                    ropeData.m_transformInfoSegments[nNode] = ComputeTransformInfo(rope, rope.RopeNodes[nNode].goNode, rope.RopeNodes[nNode].goNode.transform.parent != null ? rope.RopeNodes[nNode].goNode.transform.parent.gameObject : null);
                }

                foreach(GameObject link in rope.RopeNodes[nNode].segmentLinks)
                {
                    ropeData.m_aLinkTransformInfo[nLinearLinkIndex] = ComputeTransformInfo(rope, link, rope.RopeType == UltimateRope.ERopeType.ImportBones ? rope.ImportedBones[nLinearLinkIndex].tfNonBoneParent.gameObject : rope.RopeNodes[nNode].goNode.transform.gameObject);
                    nLinearLinkIndex++;
                }

                ropeData.m_aaJointsBroken[nNode]    = new bool[rope.RopeNodes[nNode].linkJoints.Length];
                ropeData.m_aaJointsProcessed[nNode] = new bool[rope.RopeNodes[nNode].linkJointBreaksProcessed.Length];

                for(int nJoint = 0; nJoint < rope.RopeNodes[nNode].linkJoints.Length; nJoint++)
                {
                    ropeData.m_aaJointsBroken[nNode][nJoint] = rope.RopeNodes[nNode].linkJoints[nJoint] == null;
                }

                for(int nJoint = 0; nJoint < rope.RopeNodes[nNode].linkJoints.Length; nJoint++)
                {
                    ropeData.m_aaJointsProcessed[nNode][nJoint] = rope.RopeNodes[nNode].linkJointBreaksProcessed[nJoint];
                }
            }

            ropeData.m_bDeleted = false;
        }

        s_hashInstanceID2RopeData.Add(rope.GetInstanceID(), ropeData);
    }