Example #1
0
        private void UpdatePartJoint(Part p)
        {
            if (!KJRJointUtils.JointAdjustmentValid(p) || p.rb == null || p.attachJoint == null)
            {
                return;
            }

            if (p.attachMethod == AttachNodeMethod.LOCKED_JOINT)
            {
                if (KJRJointUtils.settings.debug)
                {
                    Debug.Log("KJR: Already processed part before: " + p.partInfo.name + " (" + p.flightID + ") -> " +
                              p.parent.partInfo.name + " (" + p.parent.flightID + ")");
                }

                return;
            }
            List <ConfigurableJoint> jointList;

            if (p.Modules.Contains <CModuleStrut>())
            {
                CModuleStrut s = p.Modules.GetModule <CModuleStrut>();

                if (!(s.jointTarget == null || s.jointRoot == null))
                {
                    jointList = KJRJointUtils.GetJointListFromAttachJoint(s.strutJoint);

                    if (jointList != null)
                    {
                        for (int i = 0; i < jointList.Count; i++)
                        {
                            ConfigurableJoint j = jointList[i];

                            if (j == null)
                            {
                                continue;
                            }

                            JointDrive strutDrive = j.angularXDrive;
                            strutDrive.positionSpring = KJRJointUtils.settings.decouplerAndClampJointStrength;
                            strutDrive.maximumForce   = KJRJointUtils.settings.decouplerAndClampJointStrength;
                            j.xDrive = j.yDrive = j.zDrive = j.angularXDrive = j.angularYZDrive = strutDrive;

                            j.xMotion        = j.yMotion = j.zMotion = ConfigurableJointMotion.Locked;
                            j.angularXMotion = j.angularYMotion = j.angularZMotion = ConfigurableJointMotion.Locked;

                            //float scalingFactor = (s.jointTarget.mass + s.jointTarget.GetResourceMass() + s.jointRoot.mass + s.jointRoot.GetResourceMass()) * 0.01f;

                            j.breakForce  = KJRJointUtils.settings.decouplerAndClampJointStrength;
                            j.breakTorque = KJRJointUtils.settings.decouplerAndClampJointStrength;
                        }

                        p.attachMethod = AttachNodeMethod.LOCKED_JOINT;
                    }
                }
            }


            jointList = KJRJointUtils.GetJointListFromAttachJoint(p.attachJoint);
            if (jointList == null)
            {
                return;
            }

            StringBuilder debugString = new StringBuilder();

            bool addAdditionalJointToParent = KJRJointUtils.settings.multiPartAttachNodeReinforcement;

            //addAdditionalJointToParent &= !(p.Modules.Contains("LaunchClamp") || (p.parent.Modules.Contains("ModuleDecouple") || p.parent.Modules.Contains("ModuleAnchoredDecoupler")));
            addAdditionalJointToParent &= !p.Modules.Contains <CModuleStrut>();

            float partMass = p.mass + p.GetResourceMass();

            for (int i = 0; i < jointList.Count; i++)
            {
                ConfigurableJoint j = jointList[i];
                if (j == null)
                {
                    continue;
                }

                String    jointType     = j.GetType().Name;
                Rigidbody connectedBody = j.connectedBody;

                Part  connectedPart = connectedBody.GetComponent <Part>() ?? p.parent;
                float parentMass    = connectedPart.mass + connectedPart.GetResourceMass();

                if (partMass < KJRJointUtils.settings.massForAdjustment || parentMass < KJRJointUtils.settings.massForAdjustment)
                {
                    if (KJRJointUtils.settings.debug)
                    {
                        Debug.Log("KJR: Part mass too low, skipping: " + p.partInfo.name + " (" + p.flightID + ")");
                    }

                    continue;
                }

                // Check attachment nodes for better orientation data
                AttachNode attach   = p.FindAttachNodeByPart(p.parent);
                AttachNode p_attach = p.parent.FindAttachNodeByPart(p);
                AttachNode node     = attach ?? p_attach;

                if (node == null)
                {
                    // Check if it's a pair of coupled docking ports
                    var dock1 = p.Modules.GetModule <ModuleDockingNode>();
                    var dock2 = p.parent.Modules.GetModule <ModuleDockingNode>();

                    //Debug.Log(dock1 + " " + (dock1 ? ""+dock1.dockedPartUId : "?") + " " + dock2 + " " + (dock2 ? ""+dock2.dockedPartUId : "?"));

                    if (dock1 && dock2 && (dock1.dockedPartUId == p.parent.flightID || dock2.dockedPartUId == p.flightID))
                    {
                        attach   = p.FindAttachNode(dock1.referenceAttachNode);
                        p_attach = p.parent.FindAttachNode(dock2.referenceAttachNode);
                        node     = attach ?? p_attach;
                    }
                }

                // If still no node and apparently surface attached, use the normal one if it's there
                if (node == null && p.attachMode == AttachModes.SRF_ATTACH)
                {
                    node = attach = p.srfAttachNode;
                }

                if (KJRJointUtils.settings.debug)
                {
                    debugString.AppendLine("Original joint from " + p.partInfo.title + " to " + p.parent.partInfo.title);
                    debugString.AppendLine("  " + p.partInfo.name + " (" + p.flightID + ") -> " + p.parent.partInfo.name + " (" + p.parent.flightID + ")");
                    debugString.AppendLine("");
                    debugString.AppendLine(p.partInfo.title + " Inertia Tensor: " + p.rb.inertiaTensor + " " + p.parent.partInfo.name + " Inertia Tensor: " + connectedBody.inertiaTensor);
                    debugString.AppendLine("");


                    debugString.AppendLine("Std. Joint Parameters");
                    debugString.AppendLine("Connected Body: " + p.attachJoint.Joint.connectedBody);
                    debugString.AppendLine("Attach mode: " + p.attachMode + " (was " + jointType + ")");
                    if (attach != null)
                    {
                        debugString.AppendLine("Attach node: " + attach.id + " - " + attach.nodeType + " " + attach.size);
                    }
                    if (p_attach != null)
                    {
                        debugString.AppendLine("Parent node: " + p_attach.id + " - " + p_attach.nodeType + " " + p_attach.size);
                    }
                    debugString.AppendLine("Anchor: " + p.attachJoint.Joint.anchor);
                    debugString.AppendLine("Axis: " + p.attachJoint.Joint.axis);
                    debugString.AppendLine("Sec Axis: " + p.attachJoint.Joint.secondaryAxis);
                    debugString.AppendLine("Break Force: " + p.attachJoint.Joint.breakForce);
                    debugString.AppendLine("Break Torque: " + p.attachJoint.Joint.breakTorque);
                    debugString.AppendLine("");

                    debugString.AppendLine("Joint Motion Locked: " + Convert.ToString(p.attachJoint.Joint.xMotion == ConfigurableJointMotion.Locked));

                    debugString.AppendLine("X Drive");
                    debugString.AppendLine("Position Spring: " + p.attachJoint.Joint.xDrive.positionSpring);
                    debugString.AppendLine("Position Damper: " + p.attachJoint.Joint.xDrive.positionDamper);
                    debugString.AppendLine("Max Force: " + p.attachJoint.Joint.xDrive.maximumForce);
                    debugString.AppendLine("");

                    debugString.AppendLine("Y Drive");
                    debugString.AppendLine("Position Spring: " + p.attachJoint.Joint.yDrive.positionSpring);
                    debugString.AppendLine("Position Damper: " + p.attachJoint.Joint.yDrive.positionDamper);
                    debugString.AppendLine("Max Force: " + p.attachJoint.Joint.yDrive.maximumForce);
                    debugString.AppendLine("");

                    debugString.AppendLine("Z Drive");
                    debugString.AppendLine("Position Spring: " + p.attachJoint.Joint.zDrive.positionSpring);
                    debugString.AppendLine("Position Damper: " + p.attachJoint.Joint.zDrive.positionDamper);
                    debugString.AppendLine("Max Force: " + p.attachJoint.Joint.zDrive.maximumForce);
                    debugString.AppendLine("");

                    debugString.AppendLine("Angular X Drive");
                    debugString.AppendLine("Position Spring: " + p.attachJoint.Joint.angularXDrive.positionSpring);
                    debugString.AppendLine("Position Damper: " + p.attachJoint.Joint.angularXDrive.positionDamper);
                    debugString.AppendLine("Max Force: " + p.attachJoint.Joint.angularXDrive.maximumForce);
                    debugString.AppendLine("");

                    debugString.AppendLine("Angular YZ Drive");
                    debugString.AppendLine("Position Spring: " + p.attachJoint.Joint.angularYZDrive.positionSpring);
                    debugString.AppendLine("Position Damper: " + p.attachJoint.Joint.angularYZDrive.positionDamper);
                    debugString.AppendLine("Max Force: " + p.attachJoint.Joint.angularYZDrive.maximumForce);
                    debugString.AppendLine("");


                    //Debug.Log(debugString.ToString());
                }


                float   breakForce      = Math.Min(p.breakingForce, connectedPart.breakingForce) * KJRJointUtils.settings.breakForceMultiplier;
                float   breakTorque     = Math.Min(p.breakingTorque, connectedPart.breakingTorque) * KJRJointUtils.settings.breakTorqueMultiplier;
                Vector3 anchor          = j.anchor;
                Vector3 connectedAnchor = j.connectedAnchor;
                Vector3 axis            = j.axis;

                float radius          = 0;
                float area            = 0;
                float momentOfInertia = 0;

                if (node != null)
                {
                    // Part that owns the node. For surface attachment,
                    // this can only be parent if docking flips hierarchy.
                    Part main = (node == attach) ? p : p.parent;

                    // Orientation and position of the node in owner's local coords
                    Vector3 ndir = node.orientation.normalized;
                    Vector3 npos = node.position + node.offset;

                    // And in the current part's local coords
                    Vector3 dir = axis = p.transform.InverseTransformDirection(main.transform.TransformDirection(ndir));

                    if (node.nodeType == AttachNode.NodeType.Surface)
                    {
                        // Guessed main axis; for parts with stack nodes should be the axis of the stack
                        Vector3 up = KJRJointUtils.GuessUpVector(main).normalized;

                        // if guessed up direction is same as node direction, it's basically stack
                        // for instance, consider a radially-attached docking port
                        if (Mathf.Abs(Vector3.Dot(up, ndir)) > 0.9f)
                        {
                            radius = Mathf.Min(KJRJointUtils.CalculateRadius(main, ndir), KJRJointUtils.CalculateRadius(connectedPart, ndir));
                            if (radius <= 0.001)
                            {
                                radius = node.size * 1.25f;
                            }
                            area            = Mathf.PI * radius * radius;           //Area of cylinder
                            momentOfInertia = area * radius * radius / 4;           //Moment of Inertia of cylinder
                        }
                        else
                        {
                            // x along surface, y along ndir normal to surface, z along surface & main axis (up)
                            var size1 = KJRJointUtils.CalculateExtents(main, ndir, up);

                            var size2 = KJRJointUtils.CalculateExtents(connectedPart, ndir, up);

                            // use average of the sides, since we don't know which one is used for attaching
                            float width1 = (size1.x + size1.z) / 2;
                            float width2 = (size2.x + size2.z) / 2;
                            if (size1.y * width1 > size2.y * width2)
                            {
                                area   = size1.y * width1;
                                radius = Mathf.Max(size1.y, width1);
                            }
                            else
                            {
                                area   = size2.y * width2;
                                radius = Mathf.Max(size2.y, width2);
                            }

                            momentOfInertia = area * radius / 12;          //Moment of Inertia of a rectangle bending along the longer length
                        }
                    }
                    else
                    {
                        radius = Mathf.Min(KJRJointUtils.CalculateRadius(p, dir), KJRJointUtils.CalculateRadius(connectedPart, dir));
                        if (radius <= 0.001)
                        {
                            radius = node.size * 1.25f;
                        }
                        area            = Mathf.PI * radius * radius;           //Area of cylinder
                        momentOfInertia = area * radius * radius / 4;           //Moment of Inertia of cylinder
                    }
                }
                //Assume part is attached along its "up" cross section; use a cylinder to approximate properties
                else if (p.attachMode == AttachModes.STACK)
                {
                    radius = Mathf.Min(KJRJointUtils.CalculateRadius(p, Vector3.up), KJRJointUtils.CalculateRadius(connectedPart, Vector3.up));
                    if (radius <= 0.001)
                    {
                        radius = node.size * 1.25f;
                    }
                    area            = Mathf.PI * radius * radius;           //Area of cylinder
                    momentOfInertia = area * radius * radius / 4;           //Moment of Inertia of cylinder
                }
                else if (p.attachMode == AttachModes.SRF_ATTACH)
                {
                    // x,z sides, y along main axis
                    Vector3 up1   = KJRJointUtils.GuessUpVector(p);
                    var     size1 = KJRJointUtils.CalculateExtents(p, up1);

                    Vector3 up2   = KJRJointUtils.GuessUpVector(connectedPart);
                    var     size2 = KJRJointUtils.CalculateExtents(connectedPart, up2);

                    // use average of the sides, since we don't know which one is used for attaching
                    float width1 = (size1.x + size1.z) / 2;
                    float width2 = (size2.x + size2.z) / 2;
                    if (size1.y * width1 > size2.y * width2)
                    {
                        area   = size1.y * width1;
                        radius = Mathf.Max(size1.y, width1);
                    }
                    else
                    {
                        area   = size2.y * width2;
                        radius = Mathf.Max(size2.y, width2);
                    }
                    momentOfInertia = area * radius / 12;          //Moment of Inertia of a rectangle bending along the longer length
                }

                if (KJRJointUtils.settings.useVolumeNotArea)       //If using volume, raise al stiffness-affecting parameters to the 1.5 power
                {
                    area            = Mathf.Pow(area, 1.5f);
                    momentOfInertia = Mathf.Pow(momentOfInertia, 1.5f);
                }


                breakForce  = Mathf.Max(KJRJointUtils.settings.breakStrengthPerArea * area, breakForce);
                breakTorque = Mathf.Max(KJRJointUtils.settings.breakTorquePerMOI * momentOfInertia, breakTorque);

                JointDrive angDrive = j.angularXDrive;
                angDrive.positionSpring = Mathf.Max(momentOfInertia * KJRJointUtils.settings.angularDriveSpring, angDrive.positionSpring);
                angDrive.positionDamper = Mathf.Max(momentOfInertia * KJRJointUtils.settings.angularDriveDamper * 0.1f, angDrive.positionDamper);
                angDrive.maximumForce   = breakTorque;

                /*float moi_avg = p.rb.inertiaTensor.magnitude;
                 *
                 * moi_avg += (p.transform.localToWorldMatrix.MultiplyPoint(p.CoMOffset) - p.parent.transform.position).sqrMagnitude * p.rb.mass;
                 *
                 * if (moi_avg * 2f / drive.positionDamper < 0.08f)
                 * {
                 *  drive.positionDamper = moi_avg / (0.04f);
                 *
                 *  drive.positionSpring = drive.positionDamper * drive.positionDamper / moi_avg;
                 * }*/
                j.angularXDrive = j.angularYZDrive = j.slerpDrive = angDrive;

                JointDrive linDrive = j.xDrive;
                linDrive.maximumForce = breakForce;
                j.xDrive = j.yDrive = j.zDrive = linDrive;

                SoftJointLimit lim = new SoftJointLimit();
                lim.limit      = 0;
                lim.bounciness = 0;

                SoftJointLimitSpring limSpring = new SoftJointLimitSpring();

                limSpring.spring = 0;
                limSpring.damper = 0;

                j.linearLimit       = j.angularYLimit = j.angularZLimit = j.lowAngularXLimit = j.highAngularXLimit = lim;
                j.linearLimitSpring = j.angularYZLimitSpring = j.angularXLimitSpring = limSpring;

                j.targetAngularVelocity = Vector3.zero;
                j.targetVelocity        = Vector3.zero;
                j.targetRotation        = Quaternion.identity;
                j.targetPosition        = Vector3.zero;

                j.breakForce  = breakForce;
                j.breakTorque = breakTorque;
                p.attachJoint.SetBreakingForces(j.breakForce, j.breakTorque);

                p.attachMethod = AttachNodeMethod.LOCKED_JOINT;

                if (addAdditionalJointToParent && p.parent.parent != null)
                {
                    addAdditionalJointToParent = false;
                    if (!KJRJointUtils.JointAdjustmentValid(p.parent) || !KJRJointUtils.JointAdjustmentValid(p.parent.parent))
                    {
                        continue;
                    }

                    /*if (ValidDecoupler(p) || ValidDecoupler(p.parent))
                     *  continue;*/
                    Part newConnectedPart = p.parent.parent;

                    bool massRatioBelowThreshold = false;
                    int  numPartsFurther         = 0;

                    float       partMaxMass          = KJRJointUtils.MaximumPossiblePartMass(p);
                    List <Part> partsCrossed         = new List <Part>();
                    List <Part> possiblePartsCrossed = new List <Part>();

                    partsCrossed.Add(p);
                    partsCrossed.Add(p.parent);
                    partsCrossed.Add(newConnectedPart);

                    Rigidbody connectedRb = newConnectedPart.rb;

                    do
                    {
                        float massRat1, massRat2;
                        massRat1 = partMaxMass / newConnectedPart.mass;
                        if (massRat1 < 1)
                        {
                            massRat1 = 1 / massRat1;
                        }

                        massRat2 = p.mass / KJRJointUtils.MaximumPossiblePartMass(newConnectedPart);
                        if (massRat2 < 1)
                        {
                            massRat2 = 1 / massRat2;
                        }

                        if (massRat1 > KJRJointUtils.settings.stiffeningExtensionMassRatioThreshold || massRat2 > KJRJointUtils.settings.stiffeningExtensionMassRatioThreshold)
                        {
                            if (newConnectedPart.parent != null)
                            {
                                if (!KJRJointUtils.JointAdjustmentValid(newConnectedPart.parent))
                                {
                                    break;
                                }

                                newConnectedPart = newConnectedPart.parent;
                                if (newConnectedPart.rb == null)
                                {
                                    possiblePartsCrossed.Add(newConnectedPart);
                                }
                                else
                                {
                                    connectedRb = newConnectedPart.rb;
                                    partsCrossed.AddRange(possiblePartsCrossed);
                                    partsCrossed.Add(newConnectedPart);
                                    possiblePartsCrossed.Clear();
                                }
                            }
                            else
                            {
                                break;
                            }
                            numPartsFurther++;
                        }
                        else
                        {
                            massRatioBelowThreshold = true;
                        }
                    } while (!massRatioBelowThreshold);// && numPartsFurther < 5);

                    if (connectedRb != null && !multiJointManager.CheckMultiJointBetweenParts(p, newConnectedPart))
                    {
                        ConfigurableJoint newJoint = p.gameObject.AddComponent <ConfigurableJoint>();

                        newJoint.connectedBody   = connectedRb;
                        newJoint.axis            = Vector3.right;
                        newJoint.secondaryAxis   = Vector3.forward;
                        newJoint.anchor          = Vector3.zero;
                        newJoint.connectedAnchor = p.transform.worldToLocalMatrix.MultiplyPoint(newConnectedPart.transform.position);

                        //if(massRatioBelowThreshold)
                        //{

                        newJoint.angularXDrive = newJoint.angularYZDrive = newJoint.slerpDrive = j.angularXDrive;

                        newJoint.xDrive = j.xDrive;
                        newJoint.yDrive = j.yDrive;
                        newJoint.zDrive = j.zDrive;

                        newJoint.linearLimit = newJoint.angularYLimit = newJoint.angularZLimit = newJoint.lowAngularXLimit = newJoint.highAngularXLimit = lim;

                        /*newJoint.targetAngularVelocity = Vector3.zero;
                         * newJoint.targetVelocity = Vector3.zero;
                         * newJoint.targetRotation = Quaternion.identity;
                         * newJoint.targetPosition = Vector3.zero;*/
                        /*}
                         * else
                         * {
                         *  newJoint.xMotion = newJoint.yMotion = newJoint.zMotion = ConfigurableJointMotion.Locked;
                         *  newJoint.angularXMotion = newJoint.angularYMotion = newJoint.angularZMotion = ConfigurableJointMotion.Locked;
                         * }*/

                        newJoint.breakForce  = breakForce;
                        newJoint.breakTorque = breakTorque;

                        //jointList.Add(newJoint);
                        for (int k = 0; k < partsCrossed.Count; k++)
                        {
                            multiJointManager.RegisterMultiJoint(partsCrossed[k], newJoint);
                        }
                    }

                    /*if(p.symmetryCounterparts != null && p.symmetryCounterparts.Count > 0)
                     * {
                     *  Part linkPart = null;
                     *  Vector3 center = p.transform.position;
                     *  float cross = float.NegativeInfinity;
                     *  for(int k = 0; k < p.symmetryCounterparts.Count; k++)
                     *  {
                     *      center += p.symmetryCounterparts[k].transform.position;
                     *  }
                     *  center /= (p.symmetryCounterparts.Count + 1);
                     *
                     *  for(int k = 0; k < p.symmetryCounterparts.Count; k++)
                     *  {
                     *      Part counterPart = p.symmetryCounterparts[k];
                     *      if (counterPart.parent == p.parent && counterPart.rb != null)
                     *      {
                     *          float tmpCross = Vector3.Dot(Vector3.Cross(center - p.transform.position, counterPart.transform.position - p.transform.position), p.transform.up);
                     *          if(tmpCross > cross)
                     *          {
                     *              cross = tmpCross;
                     *              linkPart = counterPart;
                     *          }
                     *      }
                     *  }
                     *  if (linkPart)
                     *  {
                     *      Rigidbody rigidBody = linkPart.rb;
                     *      if (!linkPart.rb)
                     *          continue;
                     *      ConfigurableJoint newJoint;
                     *
                     *      newJoint = p.gameObject.AddComponent<ConfigurableJoint>();
                     *
                     *      newJoint.connectedBody = rigidBody;
                     *      newJoint.anchor = Vector3.zero;
                     *      newJoint.axis = Vector3.right;
                     *      newJoint.secondaryAxis = Vector3.forward;
                     *      newJoint.breakForce = KJRJointUtils.decouplerAndClampJointStrength;
                     *      newJoint.breakTorque = KJRJointUtils.decouplerAndClampJointStrength;
                     *
                     *      newJoint.xMotion = newJoint.yMotion = newJoint.zMotion = ConfigurableJointMotion.Locked;
                     *      newJoint.angularXMotion = newJoint.angularYMotion = newJoint.angularZMotion = ConfigurableJointMotion.Locked;
                     *
                     *      multiJointManager.RegisterMultiJoint(p, newJoint);
                     *      multiJointManager.RegisterMultiJoint(linkPart, newJoint);
                     *  }
                     * }*/
                }

                if (KJRJointUtils.settings.debug)
                {
                    debugString.AppendLine("Updated joint from " + p.partInfo.title + " to " + p.parent.partInfo.title);
                    debugString.AppendLine("  " + p.partInfo.name + " (" + p.flightID + ") -> " + p.parent.partInfo.name + " (" + p.parent.flightID + ")");
                    debugString.AppendLine("");
                    debugString.AppendLine(p.partInfo.title + " Inertia Tensor: " + p.rb.inertiaTensor + " " + p.parent.partInfo.name + " Inertia Tensor: " + connectedBody.inertiaTensor);
                    debugString.AppendLine("");


                    debugString.AppendLine("Std. Joint Parameters");
                    debugString.AppendLine("Connected Body: " + p.attachJoint.Joint.connectedBody);
                    debugString.AppendLine("Attach mode: " + p.attachMode + " (was " + jointType + ")");
                    if (attach != null)
                    {
                        debugString.AppendLine("Attach node: " + attach.id + " - " + attach.nodeType + " " + attach.size);
                    }
                    if (p_attach != null)
                    {
                        debugString.AppendLine("Parent node: " + p_attach.id + " - " + p_attach.nodeType + " " + p_attach.size);
                    }
                    debugString.AppendLine("Anchor: " + p.attachJoint.Joint.anchor);
                    debugString.AppendLine("Axis: " + p.attachJoint.Joint.axis);
                    debugString.AppendLine("Sec Axis: " + p.attachJoint.Joint.secondaryAxis);
                    debugString.AppendLine("Break Force: " + p.attachJoint.Joint.breakForce);
                    debugString.AppendLine("Break Torque: " + p.attachJoint.Joint.breakTorque);
                    debugString.AppendLine("");

                    debugString.AppendLine("Joint Motion Locked: " + Convert.ToString(p.attachJoint.Joint.xMotion == ConfigurableJointMotion.Locked));

                    debugString.AppendLine("Angular Drive");
                    debugString.AppendLine("Position Spring: " + angDrive.positionSpring);
                    debugString.AppendLine("Position Damper: " + angDrive.positionDamper);
                    debugString.AppendLine("Max Force: " + angDrive.maximumForce);
                    debugString.AppendLine("");

                    debugString.AppendLine("Cross Section Properties");
                    debugString.AppendLine("Radius: " + radius);
                    debugString.AppendLine("Area: " + area);
                    debugString.AppendLine("Moment of Inertia: " + momentOfInertia);
                }
            }
            if (KJRJointUtils.settings.debug)
            {
                Debug.Log(debugString.ToString());
            }
        }
        // attachJoint's are always joints from a part to its parent
        private void ReinforceAttachJoints(Part p)
        {
            if (p.rb == null || p.attachJoint == null || !KJRJointUtils.IsJointAdjustmentAllowed(p))
            {
                return;
            }

            if ((p.attachMethod == AttachNodeMethod.LOCKED_JOINT) &&
                KJRJointUtils.debug)
            {
                Debug.Log("KJR: Already processed part before: " + p.partInfo.name + " (" + p.flightID + ") -> " +
                          p.parent.partInfo.name + " (" + p.parent.flightID + ")");
            }

            List <ConfigurableJoint> jointList;

            if (p.Modules.Contains <CModuleStrut>())
            {
                CModuleStrut s = p.Modules.GetModule <CModuleStrut>();

                if ((s.jointTarget != null) && (s.jointRoot != null))
                {
                    jointList = s.strutJoint.joints;

                    if (jointList != null)
                    {
                        for (int i = 0; i < jointList.Count; i++)
                        {
                            ConfigurableJoint j = jointList[i];

                            if (j == null)
                            {
                                continue;
                            }

                            JointDrive strutDrive = j.angularXDrive;
                            strutDrive.positionSpring = KJRJointUtils.decouplerAndClampJointStrength;
                            strutDrive.maximumForce   = KJRJointUtils.decouplerAndClampJointStrength;
                            j.xDrive = j.yDrive = j.zDrive = j.angularXDrive = j.angularYZDrive = strutDrive;

                            j.xMotion        = j.yMotion = j.zMotion = ConfigurableJointMotion.Locked;
                            j.angularXMotion = j.angularYMotion = j.angularZMotion = ConfigurableJointMotion.Locked;

                            //float scalingFactor = (s.jointTarget.mass + s.jointTarget.GetResourceMass() + s.jointRoot.mass + s.jointRoot.GetResourceMass()) * 0.01f;

                            j.breakForce  = KJRJointUtils.decouplerAndClampJointStrength;
                            j.breakTorque = KJRJointUtils.decouplerAndClampJointStrength;
                        }

                        p.attachMethod = AttachNodeMethod.LOCKED_JOINT;
                    }
                }
            }

            jointList = p.attachJoint.joints;

            if (jointList == null)
            {
                return;
            }

            StringBuilder debugString = new StringBuilder();

            bool addAdditionalJointToParent = KJRJointUtils.multiPartAttachNodeReinforcement;

            //addAdditionalJointToParent &= !(p.Modules.Contains("LaunchClamp") || (p.parent.Modules.Contains("ModuleDecouple") || p.parent.Modules.Contains("ModuleAnchoredDecoupler")));
            addAdditionalJointToParent &= !p.Modules.Contains <CModuleStrut>();

            if (!KJRJointUtils.IsJointUnlockable(p))            // exclude those actions from joints that can be dynamically unlocked
            {
                float partMass = p.mass + p.GetResourceMass();
                for (int i = 0; i < jointList.Count; i++)
                {
                    ConfigurableJoint j = jointList[i];
                    if (j == null)
                    {
                        continue;
                    }

                    String    jointType     = j.GetType().Name;
                    Rigidbody connectedBody = j.connectedBody;

                    Part  connectedPart = connectedBody.GetComponent <Part>() ?? p.parent;
                    float parentMass    = connectedPart.mass + connectedPart.GetResourceMass();

                    if (partMass < KJRJointUtils.massForAdjustment || parentMass < KJRJointUtils.massForAdjustment)
                    {
                        if (KJRJointUtils.debug)
                        {
                            Debug.Log("KJR: Part mass too low, skipping: " + p.partInfo.name + " (" + p.flightID + ")");
                        }

                        continue;
                    }

                    // Check attachment nodes for better orientation data
                    AttachNode attach   = p.FindAttachNodeByPart(p.parent);
                    AttachNode p_attach = p.parent.FindAttachNodeByPart(p);
                    AttachNode node     = attach ?? p_attach;

                    if (node == null)
                    {
                        // Check if it's a pair of coupled docking ports
                        var dock1 = p.Modules.GetModule <ModuleDockingNode>();
                        var dock2 = p.parent.Modules.GetModule <ModuleDockingNode>();

                        //Debug.Log(dock1 + " " + (dock1 ? ""+dock1.dockedPartUId : "?") + " " + dock2 + " " + (dock2 ? ""+dock2.dockedPartUId : "?"));

                        if (dock1 && dock2 && (dock1.dockedPartUId == p.parent.flightID || dock2.dockedPartUId == p.flightID))
                        {
                            attach   = p.FindAttachNode(dock1.referenceAttachNode);
                            p_attach = p.parent.FindAttachNode(dock2.referenceAttachNode);
                            node     = attach ?? p_attach;
                        }
                    }

                    // If still no node and apparently surface attached, use the normal one if it's there
                    if (node == null && p.attachMode == AttachModes.SRF_ATTACH)
                    {
                        node = attach = p.srfAttachNode;
                    }

                    if (KJRJointUtils.debug)
                    {
                        debugString.AppendLine("Original joint from " + p.partInfo.title + " to " + p.parent.partInfo.title);
                        debugString.AppendLine("  " + p.partInfo.name + " (" + p.flightID + ") -> " + p.parent.partInfo.name + " (" + p.parent.flightID + ")");
                        debugString.AppendLine("");
                        debugString.AppendLine(p.partInfo.title + " Inertia Tensor: " + p.rb.inertiaTensor + " " + p.parent.partInfo.name + " Inertia Tensor: " + connectedBody.inertiaTensor);
                        debugString.AppendLine("");


                        debugString.AppendLine("Std. Joint Parameters");
                        debugString.AppendLine("Connected Body: " + p.attachJoint.Joint.connectedBody);
                        debugString.AppendLine("Attach mode: " + p.attachMode + " (was " + jointType + ")");
                        if (attach != null)
                        {
                            debugString.AppendLine("Attach node: " + attach.id + " - " + attach.nodeType + " " + attach.size);
                        }
                        if (p_attach != null)
                        {
                            debugString.AppendLine("Parent node: " + p_attach.id + " - " + p_attach.nodeType + " " + p_attach.size);
                        }
                        debugString.AppendLine("Anchor: " + p.attachJoint.Joint.anchor);
                        debugString.AppendLine("Axis: " + p.attachJoint.Joint.axis);
                        debugString.AppendLine("Sec Axis: " + p.attachJoint.Joint.secondaryAxis);
                        debugString.AppendLine("Break Force: " + p.attachJoint.Joint.breakForce);
                        debugString.AppendLine("Break Torque: " + p.attachJoint.Joint.breakTorque);
                        debugString.AppendLine("");

                        debugString.AppendLine("Joint Motion Locked: " + Convert.ToString(p.attachJoint.Joint.xMotion == ConfigurableJointMotion.Locked));

                        debugString.AppendLine("X Drive");
                        debugString.AppendLine("Position Spring: " + p.attachJoint.Joint.xDrive.positionSpring);
                        debugString.AppendLine("Position Damper: " + p.attachJoint.Joint.xDrive.positionDamper);
                        debugString.AppendLine("Max Force: " + p.attachJoint.Joint.xDrive.maximumForce);
                        debugString.AppendLine("");

                        debugString.AppendLine("Y Drive");
                        debugString.AppendLine("Position Spring: " + p.attachJoint.Joint.yDrive.positionSpring);
                        debugString.AppendLine("Position Damper: " + p.attachJoint.Joint.yDrive.positionDamper);
                        debugString.AppendLine("Max Force: " + p.attachJoint.Joint.yDrive.maximumForce);
                        debugString.AppendLine("");

                        debugString.AppendLine("Z Drive");
                        debugString.AppendLine("Position Spring: " + p.attachJoint.Joint.zDrive.positionSpring);
                        debugString.AppendLine("Position Damper: " + p.attachJoint.Joint.zDrive.positionDamper);
                        debugString.AppendLine("Max Force: " + p.attachJoint.Joint.zDrive.maximumForce);
                        debugString.AppendLine("");

                        debugString.AppendLine("Angular X Drive");
                        debugString.AppendLine("Position Spring: " + p.attachJoint.Joint.angularXDrive.positionSpring);
                        debugString.AppendLine("Position Damper: " + p.attachJoint.Joint.angularXDrive.positionDamper);
                        debugString.AppendLine("Max Force: " + p.attachJoint.Joint.angularXDrive.maximumForce);
                        debugString.AppendLine("");

                        debugString.AppendLine("Angular YZ Drive");
                        debugString.AppendLine("Position Spring: " + p.attachJoint.Joint.angularYZDrive.positionSpring);
                        debugString.AppendLine("Position Damper: " + p.attachJoint.Joint.angularYZDrive.positionDamper);
                        debugString.AppendLine("Max Force: " + p.attachJoint.Joint.angularYZDrive.maximumForce);
                        debugString.AppendLine("");


                        //Debug.Log(debugString.ToString());
                    }


                    float   breakForce      = Math.Min(p.breakingForce, connectedPart.breakingForce) * KJRJointUtils.breakForceMultiplier;
                    float   breakTorque     = Math.Min(p.breakingTorque, connectedPart.breakingTorque) * KJRJointUtils.breakTorqueMultiplier;
                    Vector3 anchor          = j.anchor;
                    Vector3 connectedAnchor = j.connectedAnchor;
                    Vector3 axis            = j.axis;

                    float radius          = 0;
                    float area            = 0;
                    float momentOfInertia = 0;

                    if (node != null)
                    {
                        // Part that owns the node. For surface attachment,
                        // this can only be parent if docking flips hierarchy.
                        Part main = (node == attach) ? p : p.parent;

                        // Orientation and position of the node in owner's local coords
                        Vector3 ndir = node.orientation.normalized;
                        Vector3 npos = node.position + node.offset;

                        // And in the current part's local coords
                        Vector3 dir = axis = p.transform.InverseTransformDirection(main.transform.TransformDirection(ndir));

                        if (node.nodeType == AttachNode.NodeType.Surface)
                        {
                            // Guessed main axis; for parts with stack nodes should be the axis of the stack
                            Vector3 up = KJRJointUtils.GuessUpVector(main).normalized;

                            // if guessed up direction is same as node direction, it's basically stack
                            // for instance, consider a radially-attached docking port
                            if (Mathf.Abs(Vector3.Dot(up, ndir)) > 0.9f)
                            {
                                radius = Mathf.Min(KJRJointUtils.CalculateRadius(main, ndir), KJRJointUtils.CalculateRadius(connectedPart, ndir));
                                if (radius <= 0.001)
                                {
                                    radius = node.size * 1.25f;
                                }
                                area            = Mathf.PI * radius * radius;                                              //Area of cylinder
                                momentOfInertia = area * radius * radius / 4;                                              //Moment of Inertia of cylinder
                            }
                            else
                            {
                                // x along surface, y along ndir normal to surface, z along surface & main axis (up)
                                var size1 = KJRJointUtils.CalculateExtents(main, ndir, up);

                                var size2 = KJRJointUtils.CalculateExtents(connectedPart, ndir, up);

                                // use average of the sides, since we don't know which one is used for attaching
                                float width1 = (size1.x + size1.z) / 2;
                                float width2 = (size2.x + size2.z) / 2;
                                if (size1.y * width1 > size2.y * width2)
                                {
                                    area   = size1.y * width1;
                                    radius = Mathf.Max(size1.y, width1);
                                }
                                else
                                {
                                    area   = size2.y * width2;
                                    radius = Mathf.Max(size2.y, width2);
                                }

                                momentOfInertia = area * radius / 12;                                             //Moment of Inertia of a rectangle bending along the longer length
                            }
                        }
                        else
                        {
                            radius = Mathf.Min(KJRJointUtils.CalculateRadius(p, dir), KJRJointUtils.CalculateRadius(connectedPart, dir));
                            if (radius <= 0.001)
                            {
                                radius = node.size * 1.25f;
                            }
                            area            = Mathf.PI * radius * radius;                                          //Area of cylinder
                            momentOfInertia = area * radius * radius / 4;                                          //Moment of Inertia of cylinder
                        }
                    }
                    //Assume part is attached along its "up" cross section; use a cylinder to approximate properties
                    else if (p.attachMode == AttachModes.STACK)
                    {
                        radius = Mathf.Min(KJRJointUtils.CalculateRadius(p, Vector3.up), KJRJointUtils.CalculateRadius(connectedPart, Vector3.up));
                        if (radius <= 0.001)
                        {
                            radius = 1.25f;                             // FEHLER, komisch, wieso setzen wir dann nicht alles < 1.25f auf 1.25f? -> zudem hatten wir hier sowieso einen Bug, das ist also sowieso zu hinterfragen
                        }
                        area            = Mathf.PI * radius * radius;   //Area of cylinder
                        momentOfInertia = area * radius * radius / 4;   //Moment of Inertia of cylinder
                    }
                    else if (p.attachMode == AttachModes.SRF_ATTACH)
                    {
                        // x,z sides, y along main axis
                        Vector3 up1   = KJRJointUtils.GuessUpVector(p);
                        var     size1 = KJRJointUtils.CalculateExtents(p, up1);

                        Vector3 up2   = KJRJointUtils.GuessUpVector(connectedPart);
                        var     size2 = KJRJointUtils.CalculateExtents(connectedPart, up2);

                        // use average of the sides, since we don't know which one is used for attaching
                        float width1 = (size1.x + size1.z) / 2;
                        float width2 = (size2.x + size2.z) / 2;
                        if (size1.y * width1 > size2.y * width2)
                        {
                            area   = size1.y * width1;
                            radius = Mathf.Max(size1.y, width1);
                        }
                        else
                        {
                            area   = size2.y * width2;
                            radius = Mathf.Max(size2.y, width2);
                        }
                        momentOfInertia = area * radius / 12;                                     //Moment of Inertia of a rectangle bending along the longer length
                    }

                    if (KJRJointUtils.useVolumeNotArea)                                                 //If using volume, raise al stiffness-affecting parameters to the 1.5 power
                    {
                        area            = Mathf.Pow(area, 1.5f);
                        momentOfInertia = Mathf.Pow(momentOfInertia, 1.5f);
                    }


                    breakForce  = Mathf.Max(KJRJointUtils.breakStrengthPerArea * area, breakForce);
                    breakTorque = Mathf.Max(KJRJointUtils.breakTorquePerMOI * momentOfInertia, breakTorque);

                    JointDrive angDrive = j.angularXDrive;
                    angDrive.positionSpring = Mathf.Max(momentOfInertia * KJRJointUtils.angularDriveSpring, angDrive.positionSpring);
                    angDrive.positionDamper = Mathf.Max(momentOfInertia * KJRJointUtils.angularDriveDamper * 0.1f, angDrive.positionDamper);
                    angDrive.maximumForce   = breakTorque;

                    /*float moi_avg = p.rb.inertiaTensor.magnitude;
                     *
                     * moi_avg += (p.transform.localToWorldMatrix.MultiplyPoint(p.CoMOffset) - p.parent.transform.position).sqrMagnitude * p.rb.mass;
                     *
                     * if(moi_avg * 2f / drive.positionDamper < 0.08f)
                     * {
                     *      drive.positionDamper = moi_avg / (0.04f);
                     *
                     *      drive.positionSpring = drive.positionDamper * drive.positionDamper / moi_avg;
                     * }*/
                    j.angularXDrive = j.angularYZDrive = j.slerpDrive = angDrive;

                    JointDrive linDrive = j.xDrive;
                    linDrive.maximumForce = breakForce;
                    j.xDrive = j.yDrive = j.zDrive = linDrive;

                    j.linearLimit = j.angularYLimit = j.angularZLimit = j.lowAngularXLimit = j.highAngularXLimit
                                                                                                 = new SoftJointLimit {
                        limit = 0, bounciness = 0
                    };
                    j.linearLimitSpring = j.angularYZLimitSpring = j.angularXLimitSpring
                                                                       = new SoftJointLimitSpring {
                        spring = 0, damper = 0
                    };

                    j.targetAngularVelocity = Vector3.zero;
                    j.targetVelocity        = Vector3.zero;
                    j.targetRotation        = Quaternion.identity;
                    j.targetPosition        = Vector3.zero;

                    j.breakForce  = breakForce;
                    j.breakTorque = breakTorque;
                    p.attachJoint.SetBreakingForces(j.breakForce, j.breakTorque);

                    p.attachMethod = AttachNodeMethod.LOCKED_JOINT;

                    if (KJRJointUtils.debug)
                    {
                        debugString.AppendLine("Updated joint from " + p.partInfo.title + " to " + p.parent.partInfo.title);
                        debugString.AppendLine("  " + p.partInfo.name + " (" + p.flightID + ") -> " + p.parent.partInfo.name + " (" + p.parent.flightID + ")");
                        debugString.AppendLine("");
                        debugString.AppendLine(p.partInfo.title + " Inertia Tensor: " + p.rb.inertiaTensor + " " + p.parent.partInfo.name + " Inertia Tensor: " + connectedBody.inertiaTensor);
                        debugString.AppendLine("");


                        debugString.AppendLine("Std. Joint Parameters");
                        debugString.AppendLine("Connected Body: " + p.attachJoint.Joint.connectedBody);
                        debugString.AppendLine("Attach mode: " + p.attachMode + " (was " + jointType + ")");
                        if (attach != null)
                        {
                            debugString.AppendLine("Attach node: " + attach.id + " - " + attach.nodeType + " " + attach.size);
                        }
                        if (p_attach != null)
                        {
                            debugString.AppendLine("Parent node: " + p_attach.id + " - " + p_attach.nodeType + " " + p_attach.size);
                        }
                        debugString.AppendLine("Anchor: " + p.attachJoint.Joint.anchor);
                        debugString.AppendLine("Axis: " + p.attachJoint.Joint.axis);
                        debugString.AppendLine("Sec Axis: " + p.attachJoint.Joint.secondaryAxis);
                        debugString.AppendLine("Break Force: " + p.attachJoint.Joint.breakForce);
                        debugString.AppendLine("Break Torque: " + p.attachJoint.Joint.breakTorque);
                        debugString.AppendLine("");

                        debugString.AppendLine("Joint Motion Locked: " + Convert.ToString(p.attachJoint.Joint.xMotion == ConfigurableJointMotion.Locked));

                        debugString.AppendLine("Angular Drive");
                        debugString.AppendLine("Position Spring: " + angDrive.positionSpring);
                        debugString.AppendLine("Position Damper: " + angDrive.positionDamper);
                        debugString.AppendLine("Max Force: " + angDrive.maximumForce);
                        debugString.AppendLine("");

                        debugString.AppendLine("Cross Section Properties");
                        debugString.AppendLine("Radius: " + radius);
                        debugString.AppendLine("Area: " + area);
                        debugString.AppendLine("Moment of Inertia: " + momentOfInertia);
                    }
                }
            }

#if IncludeAnalyzer
            addAdditionalJointToParent &= WindowManager.Instance.BuildAdditionalJointToParent;
#endif

            if (addAdditionalJointToParent && p.parent.parent != null &&
                KJRJointUtils.IsJointAdjustmentAllowed(p.parent))          // verify that parent is not an excluded part -> we will skip this in our calculation, that's why we need to check it now
            {
                ConfigurableJoint j = p.attachJoint.Joint;                 // second steps uses the first/main joint as reference

                Part newConnectedPart = p.parent.parent;

                bool massRatioBelowThreshold = false;
                int  numPartsFurther         = 0;

                float       partMaxMass          = KJRJointUtils.MaximumPossiblePartMass(p);
                List <Part> partsCrossed         = new List <Part>();
                List <Part> possiblePartsCrossed = new List <Part>();

                partsCrossed.Add(p.parent);

                Part connectedRbPart = newConnectedPart;

                // search the first part with an acceptable mass/mass ration to this part (joints work better then)
                do
                {
                    float massRat1 = (partMaxMass < newConnectedPart.mass) ? (newConnectedPart.mass / partMaxMass) : (partMaxMass / newConnectedPart.mass);

                    if (massRat1 <= KJRJointUtils.stiffeningExtensionMassRatioThreshold)
                    {
                        massRatioBelowThreshold = true;
                    }
                    else
                    {
                        float maxMass  = KJRJointUtils.MaximumPossiblePartMass(newConnectedPart);
                        float massRat2 = (p.mass < maxMass) ? (maxMass / p.mass) : (p.mass / maxMass);

                        if (massRat2 <= KJRJointUtils.stiffeningExtensionMassRatioThreshold)
                        {
                            massRatioBelowThreshold = true;
                        }
                        else
                        {
                            if ((newConnectedPart.parent == null) ||
                                !KJRJointUtils.IsJointAdjustmentAllowed(newConnectedPart))
                            {
                                break;
                            }

                            newConnectedPart = newConnectedPart.parent;

                            if (newConnectedPart.rb == null)
                            {
                                possiblePartsCrossed.Add(newConnectedPart);
                            }
                            else
                            {
                                connectedRbPart = newConnectedPart;
                                partsCrossed.AddRange(possiblePartsCrossed);
                                partsCrossed.Add(newConnectedPart);
                                possiblePartsCrossed.Clear();
                            }

                            numPartsFurther++;
                        }
                    }
                } while(!massRatioBelowThreshold);                // && numPartsFurther < 5);

                if (newConnectedPart.rb != null && !multiJointManager.CheckDirectJointBetweenParts(p, newConnectedPart))
                {
                    ConfigurableJoint newJoint;

                    if ((p.mass >= newConnectedPart.mass) || (p.rb == null))
                    {
                        newJoint = p.gameObject.AddComponent <ConfigurableJoint>();
                        newJoint.connectedBody = newConnectedPart.rb;

                        newJoint.anchor = Vector3.zero;

                        if (!KJRJointUtils.useOldJointCreation)
                        {
                            newJoint.autoConfigureConnectedAnchor = false;
                            newJoint.connectedAnchor = Quaternion.Inverse(newConnectedPart.orgRot) * (p.orgPos - newConnectedPart.orgPos);

                            Quaternion must = newConnectedPart.transform.rotation * (Quaternion.Inverse(newConnectedPart.orgRot) * p.orgRot);
                            newJoint.SetTargetRotationLocal(Quaternion.Inverse(p.transform.rotation) * must, Quaternion.identity);
                            // FEHLER, direkter machen
                        }
                    }
                    else
                    {
                        newJoint = newConnectedPart.gameObject.AddComponent <ConfigurableJoint>();
                        newJoint.connectedBody = p.rb;

                        newJoint.anchor = Vector3.zero;

                        if (!KJRJointUtils.useOldJointCreation)
                        {
                            newJoint.autoConfigureConnectedAnchor = false;
                            newJoint.connectedAnchor = Quaternion.Inverse(p.orgRot) * (newConnectedPart.orgPos - p.orgPos);

                            Quaternion must = p.transform.rotation * (Quaternion.Inverse(p.orgRot) * newConnectedPart.orgRot);
                            newJoint.SetTargetRotationLocal(Quaternion.Inverse(newConnectedPart.transform.rotation) * must, Quaternion.identity);
                            // FEHLER, direkter machen
                        }
                    }

                    //			newJoint.linearLimit = newJoint.angularYLimit = newJoint.angularZLimit = newJoint.lowAngularXLimit = newJoint.highAngularXLimit
                    //				= new SoftJointLimit { limit = 0, bounciness = 0 }; // FEHLER, hab das mal rausgenommen -> evtl. später doch wieder Limited arbeiten??

                    newJoint.xMotion        = newJoint.yMotion = newJoint.zMotion = ConfigurableJointMotion.Free;
                    newJoint.angularYMotion = newJoint.angularZMotion = newJoint.angularXMotion = ConfigurableJointMotion.Free;

                    newJoint.xDrive        = j.xDrive; newJoint.yDrive = j.yDrive; newJoint.zDrive = j.zDrive;
                    newJoint.angularXDrive = newJoint.angularYZDrive = newJoint.slerpDrive = j.angularXDrive;

                    newJoint.breakForce  = j.breakForce;
                    newJoint.breakTorque = j.breakTorque;

                    // register joint
                    multiJointManager.RegisterMultiJoint(p, newJoint, true, KJRMultiJointManager.Reason.AdditionalJointToParent);
                    multiJointManager.RegisterMultiJoint(newConnectedPart, newJoint, true, KJRMultiJointManager.Reason.AdditionalJointToParent);

                    foreach (Part part in partsCrossed)
                    {
                        multiJointManager.RegisterMultiJoint(part, newJoint, false, KJRMultiJointManager.Reason.AdditionalJointToParent);
                    }
                }
            }

            if (KJRJointUtils.debug)
            {
                Debug.Log(debugString.ToString());
            }
        }