예제 #1
0
        public virtual void FixedUpdate()
        {
            if (!limitsSet && PFUtils.canCheckTech())
            {
                limitsSet = true;

                float minSize = PFUtils.getTechMinValue("PROCFAIRINGS_MINDIAMETER", 0.25f);
                float maxSize = PFUtils.getTechMaxValue("PROCFAIRINGS_MAXDIAMETER", 30);

                PFUtils.setFieldRange(Fields["manualMaxSize"], minSize, maxSize * 2);

                ((UI_FloatEdit)Fields["manualMaxSize"].uiControlEditor).incrementLarge = diameterStepLarge;
                ((UI_FloatEdit)Fields["manualMaxSize"].uiControlEditor).incrementSmall = diameterStepSmall;

                ((UI_FloatEdit)Fields["manualCylStart"].uiControlEditor).incrementLarge = heightStepLarge;
                ((UI_FloatEdit)Fields["manualCylStart"].uiControlEditor).incrementSmall = heightStepSmall;
                ((UI_FloatEdit)Fields["manualCylEnd"].uiControlEditor).incrementLarge   = heightStepLarge;
                ((UI_FloatEdit)Fields["manualCylEnd"].uiControlEditor).incrementSmall   = heightStepSmall;
            }

            if (!part.packed && topBasePart != null)
            {
                var adapter = part.GetComponent <ProceduralFairingAdapter>();

                if (adapter)
                {
                    topBasePart = adapter.getTopPart();

                    if (topBasePart == null)
                    {
                        removeJoints();
                    }
                }
            }
        }
예제 #2
0
        public virtual void updateShape()
        {
            changed = false;

            var node = part.findAttachNode("bottom");

            if (node != null)
            {
                node.size = Mathf.RoundToInt(baseSize / diameterStepLarge);
            }

            node = part.findAttachNode("top");
            if (node != null)
            {
                node.size = Mathf.RoundToInt(baseSize / diameterStepLarge);
            }

            node = part.findAttachNode(topNodeName);
            if (node != null)
            {
                node.position = new Vector3(0, height, 0);
                node.size     = Mathf.RoundToInt(topSize / diameterStepLarge);
                if (!justLoaded)
                {
                    PFUtils.updateAttachedPartPos(node, part);
                }
            }
            else
            {
                Debug.LogError("[ProceduralAdapterBase] No '" + topNodeName + "' node in part", this);
            }
        }
예제 #3
0
        public void scaleNode(AttachNode node, float scale, bool setSize)
        {
            if (node == null)
            {
                return;
            }

            node.position = node.originalPosition * scale;

            if (!justLoaded)
            {
                PFUtils.updateAttachedPartPos(node, part);
            }

            if (setSize)
            {
                node.size = Mathf.RoundToInt(scale / diameterStepLarge);
            }

            if (node.attachedPart != null)
            {
                var baseEventDatum = new BaseEventDetails(0);

                baseEventDatum.Set <Vector3>("location", node.position);
                baseEventDatum.Set <Vector3>("orientation", node.orientation);
                baseEventDatum.Set <Vector3>("secondaryAxis", node.secondaryAxis);
                baseEventDatum.Set <AttachNode>("node", node);

                node.attachedPart.SendEvent("OnPartAttachNodePositionChanged", baseEventDatum);
            }
        }
예제 #4
0
        public override void resizePart(float scale)
        {
            base.resizePart(scale);

            var node = part.findAttachNode("bottom");

            foreach (var n in part.findAttachNodes("bottom"))
            {
                n.position.y = node.position.y;
                if (!justLoaded)
                {
                    PFUtils.updateAttachedPartPos(n, part);
                }
            }

            var nnt = part.GetComponent <KzNodeNumberTweaker>();

            if (nnt)
            {
                float mr = size * 0.5f;
                if (nnt.radius > mr)
                {
                    nnt.radius = mr;
                }
                ((UI_FloatEdit)nnt.Fields["radius"].uiControlEditor).maxValue = mr;
            }
        }
예제 #5
0
        void OnToggleAutoshapeUI()
        {
            Fields["manualMaxSize"].guiActiveEditor  = !autoShape;
            Fields["manualCylStart"].guiActiveEditor = !autoShape;
            Fields["manualCylEnd"].guiActiveEditor   = !autoShape;

            PFUtils.refreshPartWindow();
        }
예제 #6
0
        public override void resizePart(float scale)
        {
            float sth = calcSideThickness();

            float br = size * 0.5f - sth;

            scale = br * 2;

            base.resizePart(scale);

            var topNode    = part.FindAttachNode("top");
            var bottomNode = part.FindAttachNode("bottom");

            float y = (topNode.position.y + bottomNode.position.y) * 0.5f;

            int sideNodeSize = Mathf.RoundToInt(scale / diameterStepLarge) - 1;

            if (sideNodeSize < 0)
            {
                sideNodeSize = 0;
            }

            var nodes = part.FindAttachNodes("connect");

            for (int i = 0; i < nodes.Length; i++)
            {
                var n = nodes [i];

                n.position.y = y;
                n.size       = sideNodeSize;

                if (!justLoaded)
                {
                    PFUtils.updateAttachedPartPos(n, part);
                }
            }

            var nnt = part.GetComponent <KzNodeNumberTweaker>();

            if (nnt)
            {
                nnt.radius = size * 0.5f;
            }

            var fbase = part.GetComponent <ProceduralFairingBase>();

            if (fbase)
            {
                fbase.baseSize        = br * 2;
                fbase.sideThickness   = sth;
                fbase.needShapeUpdate = true;
            }
        }
예제 #7
0
 public void scaleNode(AttachNode node, float scale, bool setSize)
 {
     if (node == null)
     {
         return;
     }
     node.position = node.originalPosition * scale;
     if (!justLoaded)
     {
         PFUtils.updateAttachedPartPos(node, part);
     }
     if (setSize)
     {
         node.size = Mathf.RoundToInt(scale / diameterStepLarge);
     }
 }
예제 #8
0
        public virtual void resizePart(float scale, bool pushAttachments)
        {
            if (part.FindAttachNode("bottom") is AttachNode node &&
                part.FindAttachNodes("bottom") is AttachNode[] nodes)
            {
                foreach (AttachNode n in nodes)
                {
                    Vector3 newPos = new Vector3(n.position.x, node.position.y, n.position.z);
                    PFUtils.UpdateNode(part, n, newPos, node.size, pushAttachments);
                }
            }

            if (part.GetComponent <KzNodeNumberTweaker>() is KzNodeNumberTweaker nnt)
            {
                nnt.SetRadius(Math.Min(nnt.radius, size / 2), pushAttachments);
                (nnt.Fields[nameof(nnt.radius)].uiControlEditor as UI_FloatEdit).maxValue = size / 2;
            }
        }
        void updateNodePositions()
        {
            float d = Mathf.Sin(Mathf.PI / numNodes) * radius * 2;

            int size = Mathf.RoundToInt(d / (radiusStepLarge * 2));

            for (int i = 1; i <= numNodes; ++i)
            {
                var node = findNode(i);

                if (node == null)
                {
                    continue;
                }

                float a = Mathf.PI * 2 * (i - 1) / numNodes;

                node.position.x = Mathf.Cos(a) * radius;
                node.position.z = Mathf.Sin(a) * radius;

                if (shouldResizeNodes)
                {
                    node.size = size;
                }

                if (!justLoaded)
                {
                    PFUtils.updateAttachedPartPos(node, part);
                }
            }

            for (int i = numNodes + 1; i <= maxNumber; ++i)
            {
                var node = findNode(i);

                if (node == null)
                {
                    continue;
                }

                node.position.x = 10000;
            }
        }
예제 #10
0
        public static void UpdateNode(Part part, AttachNode node, Vector3 newPosition, int size, bool pushAttachments, float attachDiameter = 0)
        {
            if (node is AttachNode)
            {
                Vector3 oldPosWorld = part.transform.TransformPoint(node.position);
                node.position = newPosition;
                node.size     = size;

                if (pushAttachments)
                {
                    PFUtils.UpdateAttachedPartPos(node, part, oldPosWorld);
                }

                if (node.attachedPart is Part)
                {
                    PFUtils.InformAttachedPartNodePositionChanged(node);
                    PFUtils.InformAttachNodeSizeChanged(node, attachDiameter > 0 ? attachDiameter : Mathf.Max(node.size, 0.01f));
                }
            }
        }
예제 #11
0
        public virtual void FixedUpdate()
        {
            if (!limitsSet && PFUtils.canCheckTech())
            {
                limitsSet = true;
                float minSize = PFUtils.getTechMinValue(minSizeName, 0.25f);
                float maxSize = PFUtils.getTechMaxValue(maxSizeName, 30);

                PFUtils.setFieldRange(Fields["size"], minSize, maxSize);

                ((UI_FloatEdit)Fields["size"].uiControlEditor).incrementLarge = diameterStepLarge;
                ((UI_FloatEdit)Fields["size"].uiControlEditor).incrementSmall = diameterStepSmall;
            }

            if (size != oldSize)
            {
                resizePart(size);
            }
            justLoaded = false;
        }
예제 #12
0
 public void FixedUpdate()
 {
     if (HighLogic.LoadedSceneIsEditor)
     {
         int nsym = part.symmetryCounterparts.Count;
         if (nsym == 0)
         {
             massDisplay = PFUtils.formatMass(part.mass);
             costDisplay = PFUtils.formatCost(part.partInfo.cost + GetModuleCost());
         }
         else if (nsym == 1)
         {
             massDisplay = PFUtils.formatMass(part.mass * 2) + " (both)";
             costDisplay = PFUtils.formatCost((part.partInfo.cost + GetModuleCost()) * 2) + " (both)";
         }
         else
         {
             massDisplay = PFUtils.formatMass(part.mass * (nsym + 1)) + " (all " + (nsym + 1) + ")";
             costDisplay = PFUtils.formatCost((part.partInfo.cost + GetModuleCost()) * (nsym + 1)) + " (all " + (nsym + 1) + ")";
         }
     }
 }
예제 #13
0
        public virtual void resizePart(float scale)
        {
            oldSize = size;

            part.mass = totalMass = ((specificMass.x * scale + specificMass.y) * scale + specificMass.z) * scale + specificMass.w;

            massDisplay = PFUtils.formatMass(totalMass);
            costDisplay = PFUtils.formatCost(part.partInfo.cost + GetModuleCost(part.partInfo.cost, ModifierStagingSituation.CURRENT) + part.partInfo.cost);

            part.breakingForce  = specificBreakingForce * Mathf.Pow(scale, 2);
            part.breakingTorque = specificBreakingTorque * Mathf.Pow(scale, 2);

            var model = part.FindModelTransform("model");

            if (model != null)
            {
                model.localScale = Vector3.one * scale;
            }
            else
            {
                Debug.LogError("[PF]: No 'model' transform found in part!", this);
            }

            part.rescaleFactor = scale;

            scaleNode(part.FindAttachNode("top"), scale, true);
            scaleNode(part.FindAttachNode("bottom"), scale, true);

            var nodes = part.FindAttachNodes("interstage");

            if (nodes != null)
            {
                for (int i = 0; i < nodes.Length; i++)
                {
                    scaleNode(nodes [i], scale, true);
                }
            }
        }
예제 #14
0
        public static IEnumerator <YieldInstruction> updateDragCubeCoroutine(Part part, float areaScale)
        {
            while (true)
            {
                if (part == null || part.Equals(null))
                {
                    yield break;
                }

                if (HighLogic.LoadedSceneIsFlight)
                {
                    if (part.vessel == null || part.vessel.Equals(null))
                    {
                        yield break;
                    }

                    if (!FlightGlobals.ready || part.packed || !part.vessel.loaded)
                    {
                        yield return(new WaitForFixedUpdate());

                        continue;
                    }

                    break;
                }

                if (HighLogic.LoadedSceneIsEditor)
                {
                    yield return(new WaitForFixedUpdate());

                    break;
                }

                yield break;
            }

            PFUtils.updateDragCube(part, areaScale);
        }
예제 #15
0
        public virtual void resizePart(float scale)
        {
            oldSize = size;

            part.mass           = ((specificMass.x * scale + specificMass.y) * scale + specificMass.z) * scale + specificMass.w;
            massDisplay         = PFUtils.formatMass(part.mass);
            costDisplay         = PFUtils.formatCost(GetModuleCost() + part.partInfo.cost);
            part.breakingForce  = specificBreakingForce * Mathf.Pow(scale, 2);
            part.breakingTorque = specificBreakingTorque * Mathf.Pow(scale, 2);

            var model = part.FindModelTransform("model");

            if (model != null)
            {
                model.localScale = Vector3.one * scale;
            }
            else
            {
                Debug.LogError("[KzPartResizer] No 'model' transform in the part", this);
            }

            scaleNode(part.findAttachNode("top"), scale, true);
            scaleNode(part.findAttachNode("bottom"), scale, true);
        }
예제 #16
0
        public virtual void updateShape()
        {
            changed = false;

            float topheight     = 0;
            float topnodeheight = 0;

            var node = part.FindAttachNode("bottom");

            if (node != null)
            {
                node.size = Mathf.RoundToInt(baseSize / diameterStepLarge);
            }

            node = part.FindAttachNode("top");

            if (node != null)
            {
                node.size = Mathf.RoundToInt(baseSize / diameterStepLarge);

                topheight = node.position.y;
            }

            node = part.FindAttachNode(topNodeName);

            if (node != null)
            {
                node.position = new Vector3(0, height, 0);

                node.size = Mathf.RoundToInt(topSize / diameterStepLarge);

                if (!justLoaded)
                {
                    PFUtils.updateAttachedPartPos(node, part);
                }

                topnodeheight = height;
            }
            else
            {
                Debug.LogError("[PF]: No '" + topNodeName + "' node in part!", this);
            }

            var internodes = part.FindAttachNodes("interstage");

            if (internodes != null)
            {
                var inc = (topnodeheight - topheight) / (internodes.Length / 2 + 1);

                for (int i = 0, j = 0; i < internodes.Length; i = i + 2)
                {
                    var height = topheight + (j + 1) * inc;

                    j++;

                    node = internodes [i];

                    node.position.y = height;
                    node.size       = node.size = Mathf.RoundToInt(topSize / diameterStepLarge) - 1;

                    if (!justLoaded)
                    {
                        PFUtils.updateAttachedPartPos(node, part);
                    }

                    node = internodes [i + 1];

                    node.position.y = height;
                    node.size       = node.size = Mathf.RoundToInt(topSize / diameterStepLarge) - 1;

                    if (!justLoaded)
                    {
                        PFUtils.updateAttachedPartPos(node, part);
                    }
                }
            }
        }
예제 #17
0
        public override void FixedUpdate()
        {
            base.FixedUpdate();

            if (!limitsSet && PFUtils.canCheckTech())
            {
                limitsSet = true;

                float minSize = PFUtils.getTechMinValue("PROCFAIRINGS_MINDIAMETER", 0.25f);
                float maxSize = PFUtils.getTechMaxValue("PROCFAIRINGS_MAXDIAMETER", 30);

                PFUtils.setFieldRange(Fields["baseSize"], minSize, maxSize);
                PFUtils.setFieldRange(Fields["topSize"], minSize, maxSize);

                ((UI_FloatEdit)Fields["baseSize"].uiControlEditor).incrementLarge = diameterStepLarge;
                ((UI_FloatEdit)Fields["baseSize"].uiControlEditor).incrementSmall = diameterStepSmall;
                ((UI_FloatEdit)Fields["topSize"].uiControlEditor).incrementLarge  = diameterStepLarge;
                ((UI_FloatEdit)Fields["topSize"].uiControlEditor).incrementSmall  = diameterStepSmall;

                ((UI_FloatEdit)Fields["height"].uiControlEditor).incrementLarge      = heightStepLarge;
                ((UI_FloatEdit)Fields["height"].uiControlEditor).incrementSmall      = heightStepSmall;
                ((UI_FloatEdit)Fields["extraHeight"].uiControlEditor).incrementLarge = heightStepLarge;
                ((UI_FloatEdit)Fields["extraHeight"].uiControlEditor).incrementSmall = heightStepSmall;
            }

            if (isShipModified)
            {
                isShipModified = false;

                //  Remove the engine fairing (if there is any) from topmost node.

                if (!engineFairingRemoved)
                {
                    var node = part.FindAttachNode(topNodeName);

                    if (node != null && node.attachedPart != null)
                    {
                        var tp = node.attachedPart;

                        if (HighLogic.LoadedSceneIsEditor || !tp.packed)
                        {
                            var comps = tp.GetComponents <ModuleJettison>();

                            for (int i = 0; i < comps.Length; i++)
                            {
                                var mj = comps [i];

                                var jt = tp.FindModelTransform(mj.jettisonName);

                                if (jt == null)
                                {
                                    jt = mj.jettisonTransform;
                                }

                                if (jt != null)
                                {
                                    jt.gameObject.SetActive(false);
                                }

                                mj.jettisonName      = null;
                                mj.jettisonTransform = null;
                            }

                            if (!HighLogic.LoadedSceneIsEditor)
                            {
                                engineFairingRemoved = true;
                            }
                        }
                    }
                }

                if (!HighLogic.LoadedSceneIsEditor)
                {
                    if (isTopNodePartPresent)
                    {
                        var tp = getTopPart();

                        if (tp == null)
                        {
                            isTopNodePartPresent = false;

                            Events["UIToggleTopNodeDecouple"].guiActive = false;
                        }
                        else
                        {
                            if (topNodeDecouplesWhenFairingsGone && !CheckForFairingPresent())
                            {
                                PartModule item = part.Modules["ModuleDecouple"];

                                if (item == null)
                                {
                                    Debug.LogError("[PF]: Cannot decouple from top part!", this);
                                }
                                else
                                {
                                    RemoveTopPartJoints();

                                    ((ModuleDecouple)item).Decouple();

                                    part.stackIcon.RemoveIcon();

                                    StageManager.Instance.SortIcons(true);

                                    isFairingPresent     = false;
                                    isTopNodePartPresent = false;

                                    Events["UIToggleTopNodeDecouple"].guiActive = false;
                                }
                            }
                        }
                    }

                    if (isStaged)
                    {
                        isStaged = false;

                        if (part != null)
                        {
                            if (stageNum == part.inverseStage)
                            {
                                part.stackIcon.RemoveIcon();

                                StageManager.Instance.SortIcons(true);

                                Events["UIToggleTopNodeDecouple"].guiActive = false;
                            }
                        }
                    }
                }
            }
        }
예제 #18
0
        public override void updateShape()
        {
            base.updateShape();

            float sth = calcSideThickness();

            float br    = baseSize * 0.5f - sth;
            float scale = br * 2;

            part.mass = totalMass = ((specificMass.x * scale + specificMass.y) * scale + specificMass.z) * scale + specificMass.w;

            massDisplay = PFUtils.formatMass(totalMass);
            costDisplay = PFUtils.formatCost(part.partInfo.cost + GetModuleCost(part.partInfo.cost, ModifierStagingSituation.CURRENT));

            part.breakingForce  = specificBreakingForce * Mathf.Pow(br, 2);
            part.breakingTorque = specificBreakingTorque * Mathf.Pow(br, 2);

            var model = part.FindModelTransform("model");

            if (model != null)
            {
                model.localScale = Vector3.one * scale;
            }
            else
            {
                Debug.LogError("[PF]: No 'model' transform found in part!", this);
            }

            part.rescaleFactor = scale;

            var node = part.FindAttachNode("top");

            node.position = node.originalPosition * scale;

            if (!justLoaded)
            {
                PFUtils.updateAttachedPartPos(node, part);
            }

            var topNode    = part.FindAttachNode("top");
            var bottomNode = part.FindAttachNode("bottom");

            float y = (topNode.position.y + bottomNode.position.y) * 0.5f;

            int sideNodeSize = Mathf.RoundToInt(scale / diameterStepLarge) - 1;

            if (sideNodeSize < 0)
            {
                sideNodeSize = 0;
            }

            var nodes = part.FindAttachNodes("connect");

            if (nodes != null)
            {
                for (int i = 0; i < nodes.Length; i++)
                {
                    var n = nodes [i];

                    n.position.y = y;
                    n.size       = sideNodeSize;

                    if (!justLoaded)
                    {
                        PFUtils.updateAttachedPartPos(n, part);
                    }
                }
            }

            var topnode2   = part.FindAttachNode(topNodeName);
            var internodes = part.FindAttachNodes("interstage");

            if (internodes != null && topnode2 != null)
            {
                var topheight      = topNode.position.y;
                var topnode2height = topnode2.position.y;

                var inc = (topnode2height - topheight) / (internodes.Length / 2 + 1);

                for (int i = 0, j = 0; i < internodes.Length; i = i + 2)
                {
                    var baseHeight = topheight + (j + 1) * inc;

                    j++;

                    node = internodes [i];

                    node.position.y = baseHeight;
                    node.size       = topNode.size;

                    if (!justLoaded)
                    {
                        PFUtils.updateAttachedPartPos(node, part);
                    }

                    node = internodes [i + 1];

                    node.position.y = baseHeight;
                    node.size       = sideNodeSize;

                    if (!justLoaded)
                    {
                        PFUtils.updateAttachedPartPos(node, part);
                    }
                }
            }

            var nnt = part.GetComponent <KzNodeNumberTweaker>();

            if (nnt)
            {
                nnt.radius = baseSize * 0.5f;
            }

            var fbase = part.GetComponent <ProceduralFairingBase>();

            if (fbase)
            {
                fbase.baseSize        = br * 2;
                fbase.sideThickness   = sth;
                fbase.needShapeUpdate = true;
            }

            StartCoroutine(PFUtils.updateDragCubeCoroutine(part, dragAreaScale));
        }
예제 #19
0
        public override void FixedUpdate()
        {
            base.FixedUpdate();

            if (!limitsSet && PFUtils.canCheckTech())
            {
                limitsSet = true;
                float minSize = PFUtils.getTechMinValue("PROCFAIRINGS_MINDIAMETER", 0.25f);
                float maxSize = PFUtils.getTechMaxValue("PROCFAIRINGS_MAXDIAMETER", 30);

                PFUtils.setFieldRange(Fields["baseSize"], minSize, maxSize);
                PFUtils.setFieldRange(Fields["topSize"], minSize, maxSize);

                ((UI_FloatEdit)Fields["baseSize"].uiControlEditor).incrementLarge = diameterStepLarge;
                ((UI_FloatEdit)Fields["baseSize"].uiControlEditor).incrementSmall = diameterStepSmall;
                ((UI_FloatEdit)Fields["topSize"].uiControlEditor).incrementLarge  = diameterStepLarge;
                ((UI_FloatEdit)Fields["topSize"].uiControlEditor).incrementSmall  = diameterStepSmall;

                ((UI_FloatEdit)Fields["height"].uiControlEditor).incrementLarge      = heightStepLarge;
                ((UI_FloatEdit)Fields["height"].uiControlEditor).incrementSmall      = heightStepSmall;
                ((UI_FloatEdit)Fields["extraHeight"].uiControlEditor).incrementLarge = heightStepLarge;
                ((UI_FloatEdit)Fields["extraHeight"].uiControlEditor).incrementSmall = heightStepSmall;
            }

            if (!engineFairingRemoved)
            {
                var node = part.findAttachNode(topNodeName);
                if (node != null && node.attachedPart != null)
                {
                    var tp = node.attachedPart;

                    if (HighLogic.LoadedSceneIsEditor || !tp.packed)
                    {
                        foreach (var mj in tp.GetComponents <ModuleJettison>())
                        {
                            // print("[ProceduralFairingAdapter] removing engine fairings "+mj);
                            var jt = tp.FindModelTransform(mj.jettisonName);
                            if (jt == null)
                            {
                                jt = mj.jettisonTransform;
                            }
                            if (jt != null)
                            {
                                // print("[ProceduralFairingAdapter] disabling engine fairing "+jt);
                                jt.gameObject.SetActive(false);
                            }

                            mj.jettisonName      = null;
                            mj.jettisonTransform = null;

                            // tp.RemoveModule(mj);
                        }

                        if (!HighLogic.LoadedSceneIsEditor)
                        {
                            engineFairingRemoved = true;
                        }
                    }
                }
            }

            if (!HighLogic.LoadedSceneIsEditor)
            {
                var node = part.findAttachNode(topNodeName);
                if (node != null && node.attachedPart != null)
                {
                    var tp = node.attachedPart;

                    foreach (var n in part.findAttachNodes("connect"))
                    {
                        if (n.attachedPart != null)
                        {
                            return;
                        }
                    }

                    if (part.parent == tp)
                    {
                        part.decouple(0);
                    }
                    else if (tp.parent == part)
                    {
                        tp.decouple(0);
                    }
                    else
                    {
                        Debug.LogError("[ProceduralFairingAdapter] Can't decouple from top part", this);
                    }
                }
            }
        }
예제 #20
0
        public override void updateShape()
        {
            base.updateShape();

            float sth   = calcSideThickness();
            float br    = baseSize * 0.5f - sth;
            float scale = br * 2;

            part.mass           = ((specificMass.x * scale + specificMass.y) * scale + specificMass.z) * scale + specificMass.w;
            massDisplay         = PFUtils.formatMass(part.mass);
            costDisplay         = PFUtils.formatCost(part.partInfo.cost + GetModuleCost());
            part.breakingForce  = specificBreakingForce * Mathf.Pow(br, 2);
            part.breakingTorque = specificBreakingTorque * Mathf.Pow(br, 2);

            var model = part.FindModelTransform("model");

            if (model != null)
            {
                model.localScale = Vector3.one * scale;
            }
            else
            {
                Debug.LogError("[ProceduralFairingAdapter] No 'model' transform in the part", this);
            }

            var node = part.findAttachNode("top");

            node.position = node.originalPosition * scale;
            if (!justLoaded)
            {
                PFUtils.updateAttachedPartPos(node, part);
            }

            var topNode    = part.findAttachNode("top");
            var bottomNode = part.findAttachNode("bottom");

            float y            = (topNode.position.y + bottomNode.position.y) * 0.5f;
            int   sideNodeSize = Mathf.RoundToInt(scale / diameterStepLarge) - 1;

            if (sideNodeSize < 0)
            {
                sideNodeSize = 0;
            }

            foreach (var n in part.findAttachNodes("connect"))
            {
                n.position.y = y;
                n.size       = sideNodeSize;
                if (!justLoaded)
                {
                    PFUtils.updateAttachedPartPos(n, part);
                }
            }

            var nnt = part.GetComponent <KzNodeNumberTweaker>();

            if (nnt)
            {
                nnt.radius = baseSize * 0.5f;
            }

            var fbase = part.GetComponent <ProceduralFairingBase>();

            if (fbase)
            {
                fbase.baseSize      = br * 2;
                fbase.sideThickness = sth;
                fbase.updateDelay   = 0;
            }
        }
예제 #21
0
        public override void OnStart(StartState state)
        {
            limitsSet = false;

            if (!HighLogic.LoadedSceneIsEditor && !HighLogic.LoadedSceneIsFlight)
            {
                return;
            }

            PFUtils.hideDragStuff(part);

            GameEvents.onEditorShipModified.Add(new EventData <ShipConstruct> .OnEvent(onEditorVesselModified));

            if (HighLogic.LoadedSceneIsEditor)
            {
                if (line)
                {
                    line.transform.Rotate(0, 90, 0);
                }

                DestroyAllLineRenderers();

                destroyOutline();

                for (int i = 0; i < outlineSlices; ++i)
                {
                    var r = makeLineRenderer("fairing outline", outlineColor, outlineWidth);

                    outline.Add(r);

                    r.transform.Rotate(0, i * 360f / outlineSlices, 0);
                }

                ShowHideInterstageNodes();

                recalcShape();

                updateDelay = 0.1f;

                needShapeUpdate = true;
            }
            else
            {
                topBasePart = null;

                var adapter = part.GetComponent <ProceduralFairingAdapter>();

                if (adapter)
                {
                    topBasePart = adapter.getTopPart();
                }
                else
                {
                    var scan = scanPayload();

                    if (scan.targets.Count > 0)
                    {
                        topBasePart = scan.targets [0];
                    }
                }
            }

            SetUIChangedCallBacks();

            OnToggleAutoshapeUI();
        }
예제 #22
0
        public void recalcShape()
        {
            var scan = scanPayload();

            //  Check for reversed bases (inline fairings).

            float topY   = 0;
            float topRad = 0;

            AttachNode topSideNode = null;

            bool isInline = false;

            var adapter = part.GetComponent <ProceduralFairingAdapter>();

            if (adapter)
            {
                isInline = true;

                topY = adapter.height + adapter.extraHeight;

                if (topY < scan.ofs)
                {
                    topY = scan.ofs;
                }

                topRad = adapter.topRadius;
            }
            else if (scan.targets.Count > 0)
            {
                isInline = true;

                var topBase = scan.targets [0].GetComponent <ProceduralFairingBase>();

                topY = scan.w2l.MultiplyPoint3x4(topBase.part.transform.position).y;

                if (topY < scan.ofs)
                {
                    topY = scan.ofs;
                }

                topSideNode = HasNodeComponent <ProceduralFairingSide>(topBase.part.FindAttachNodes("connect"));

                topRad = topBase.baseSize * 0.5f;
            }

            //  No payload case.

            if (scan.profile.Count <= 0)
            {
                scan.profile.Add(extraRadius);
            }

            //  Fill profile outline (for debugging).

            if (line)
            {
                line.positionCount = scan.profile.Count * 2 + 2;

                float prevRad = 0;

                int hi = 0;

                for (int i = 0; i < scan.profile.Count; i++)
                {
                    var r = scan.profile [i];

                    line.SetPosition(hi * 2, new Vector3(prevRad, hi * verticalStep + scan.ofs, 0));
                    line.SetPosition(hi * 2 + 1, new Vector3(r, hi * verticalStep + scan.ofs, 0));

                    hi++; prevRad = r;
                }

                line.SetPosition(hi * 2, new Vector3(prevRad, hi * verticalStep + scan.ofs, 0));
                line.SetPosition(hi * 2 + 1, new Vector3(0, hi * verticalStep + scan.ofs, 0));
            }

            //  Check for attached side parts.

            var attached = part.FindAttachNodes("connect");

            //  Get the number of available fairing attachment nodes from NodeNumberTweaker.

            var nnt = part.GetComponent <KzNodeNumberTweaker>();

            int numSideParts = nnt.numNodes;

            var sideNode = HasNodeComponent <ProceduralFairingSide>(attached);

            var baseConeShape = new Vector4(0, 0, 0, 0);
            var noseConeShape = new Vector4(0, 0, 0, 0);
            var mappingScale  = new Vector2(1024, 1024);
            var stripMapping  = new Vector2(992, 1024);
            var horMapping    = new Vector4(0, 480, 512, 992);
            var vertMapping   = new Vector4(0, 160, 704, 1024);

            float baseCurveStartX = 0;
            float baseCurveStartY = 0;
            float baseCurveEndX   = 0;
            float baseCurveEndY   = 0;

            int baseConeSegments = 1;

            float noseCurveStartX = 0;
            float noseCurveStartY = 0;
            float noseCurveEndX   = 0;
            float noseCurveEndY   = 0;

            int   noseConeSegments = 1;
            float noseHeightRatio  = 1;
            float minBaseConeAngle = 20;

            float density = 0;

            if (sideNode != null)
            {
                var sf = sideNode.attachedPart.GetComponent <ProceduralFairingSide>();

                mappingScale     = sf.mappingScale;
                stripMapping     = sf.stripMapping;
                horMapping       = sf.horMapping;
                vertMapping      = sf.vertMapping;
                noseHeightRatio  = sf.noseHeightRatio;
                minBaseConeAngle = sf.minBaseConeAngle;
                baseConeShape    = sf.baseConeShape;
                baseCurveStartX  = sf.baseCurveStartX;
                baseCurveStartY  = sf.baseCurveStartY;
                baseCurveEndX    = sf.baseCurveEndX;
                baseCurveEndY    = sf.baseCurveEndY;
                baseConeSegments = (int)sf.baseConeSegments;
                noseConeShape    = sf.noseConeShape;
                noseCurveStartX  = sf.noseCurveStartX;
                noseCurveStartY  = sf.noseCurveStartY;
                noseCurveEndX    = sf.noseCurveEndX;
                noseCurveEndY    = sf.noseCurveEndY;
                noseConeSegments = (int)sf.noseConeSegments;
                density          = sf.density;
            }

            //   Compute the fairing shape.

            float baseRad        = baseSize * 0.5f;
            float minBaseConeTan = Mathf.Tan(minBaseConeAngle * Mathf.Deg2Rad);

            float cylStart = 0;
            float maxRad;

            int profTop = scan.profile.Count;

            if (isInline)
            {
                profTop = Mathf.CeilToInt((topY - scan.ofs) / verticalStep);

                if (profTop > scan.profile.Count)
                {
                    profTop = scan.profile.Count;
                }

                maxRad = 0;

                for (int i = 0; i < profTop; ++i)
                {
                    maxRad = Mathf.Max(maxRad, scan.profile [i]);
                }

                maxRad = Mathf.Max(maxRad, topRad);
            }
            else
            {
                maxRad = PFUtils.GetMaxValueFromList(scan.profile);
            }

            if (maxRad > baseRad)
            {
                //  Try to fit the base cone as high as possible.

                cylStart = scan.ofs;

                for (int i = 1; i < scan.profile.Count; ++i)
                {
                    float y  = i * verticalStep + scan.ofs;
                    float r0 = baseRad;
                    float k  = (maxRad - r0) / y;

                    if (k < minBaseConeTan)
                    {
                        break;
                    }

                    bool ok = true;

                    float r = r0 + k * scan.ofs;

                    for (int j = 0; j < i; ++j, r += k * verticalStep)
                    {
                        if (scan.profile [j] > r)
                        {
                            ok = false;

                            break;
                        }
                    }

                    if (!ok)
                    {
                        break;
                    }

                    cylStart = y;
                }
            }
            else
            {
                //  No base cone, just a cylinder and a nose.

                maxRad = baseRad;
            }

            float cylEnd = scan.profile.Count * verticalStep + scan.ofs;

            if (isInline)
            {
                float r0 = topRad;

                if (profTop > 0 && profTop < scan.profile.Count)
                {
                    r0 = Mathf.Max(r0, scan.profile [profTop - 1]);

                    if (profTop - 2 >= 0)
                    {
                        r0 = Mathf.Max(r0, scan.profile [profTop - 2]);
                    }
                }

                if (maxRad > r0)
                {
                    if (cylEnd > topY)
                    {
                        cylEnd = topY - verticalStep;
                    }

                    //  Try to fit the top cone as low as possible.

                    for (int i = profTop - 1; i >= 0; --i)
                    {
                        float y = i * verticalStep + scan.ofs;
                        float k = (maxRad - r0) / (y - topY);

                        bool ok = true;

                        float r = maxRad + k * verticalStep;

                        for (int j = i; j < profTop; ++j, r += k * verticalStep)
                        {
                            if (r < r0)
                            {
                                r = r0;
                            }

                            if (scan.profile [j] > r)
                            {
                                ok = false;

                                break;
                            }
                        }

                        if (!ok)
                        {
                            break;
                        }

                        cylEnd = y;
                    }
                }
                else
                {
                    cylEnd = topY;
                }
            }
            else
            {
                //  Try to fit the nose cone as low as possible.

                for (int i = scan.profile.Count - 1; i >= 0; --i)
                {
                    float s = verticalStep / noseHeightRatio;

                    bool ok = true;

                    float r = maxRad - s;

                    for (int j = i; j < scan.profile.Count; ++j, r -= s)
                    {
                        if (scan.profile [j] > r)
                        {
                            ok = false;

                            break;
                        }
                    }

                    if (!ok)
                    {
                        break;
                    }

                    float y = i * verticalStep + scan.ofs;

                    cylEnd = y;
                }
            }

            if (autoShape)
            {
                manualMaxSize  = maxRad * 2;
                manualCylStart = cylStart;
                manualCylEnd   = cylEnd;
            }
            else
            {
                maxRad   = manualMaxSize * 0.5f;
                cylStart = manualCylStart;
                cylEnd   = manualCylEnd;
            }

            if (cylStart > cylEnd)
            {
                cylStart = cylEnd;
            }

            //  Build the fairing shape line.

            Vector3 [] shape;

            if (isInline)
            {
                shape = buildInlineFairingShape(baseRad, maxRad, topRad, cylStart, cylEnd, topY, baseConeShape, baseConeSegments, vertMapping, mappingScale.y);
            }
            else
            {
                shape = buildFairingShape(baseRad, maxRad, cylStart, cylEnd, noseHeightRatio, baseConeShape, noseConeShape, baseConeSegments, noseConeSegments, vertMapping, mappingScale.y);
            }

            if (sideNode == null && topSideNode == null)
            {
                //  No side parts - fill fairing outlines.

                for (int j = 0; j < outline.Count; j++)
                {
                    var lr = outline [j];

                    lr.positionCount = shape.Length;

                    for (int i = 0; i < shape.Length; ++i)
                    {
                        lr.SetPosition(i, new Vector3(shape [i].x, shape [i].y));
                    }
                }
            }
            else
            {
                for (int j = 0; j < outline.Count; j++)
                {
                    var lr = outline [j];

                    lr.positionCount = 0;
                }
            }

            //  Rebuild the side parts.

            int numSegs = circleSegments / numSideParts;

            if (numSegs < 2)
            {
                numSegs = 2;
            }

            for (int i = 0; i < attached.Length; i++)
            {
                var sn = attached [i];
                var sp = sn.attachedPart;

                if (!sp)
                {
                    continue;
                }

                var sf = sp.GetComponent <ProceduralFairingSide>();

                if (!sf)
                {
                    continue;
                }

                if (sf.shapeLock)
                {
                    continue;
                }

                var mf = sp.FindModelComponent <MeshFilter>("model");

                if (!mf)
                {
                    Debug.LogError("[PF]: No model in side fairing!", sp);

                    continue;
                }

                var nodePos = sn.position;

                mf.transform.position = part.transform.position;
                mf.transform.rotation = part.transform.rotation;

                float ra = Mathf.Atan2(-nodePos.z, nodePos.x) * Mathf.Rad2Deg;

                mf.transform.Rotate(0, ra, 0);

                if (sf.meshPos == mf.transform.localPosition &&
                    sf.meshRot == mf.transform.localRotation &&
                    sf.numSegs == numSegs &&
                    sf.numSideParts == numSideParts &&
                    sf.baseRad.Equals(baseRad) &&
                    sf.maxRad.Equals(maxRad) &&
                    sf.cylStart.Equals(cylStart) &&
                    sf.cylEnd.Equals(cylEnd) &&
                    sf.topRad.Equals(topRad) &&
                    sf.inlineHeight.Equals(topY) &&
                    sf.sideThickness.Equals(sideThickness) &&
                    !sf.baseCurveStartX.Equals(baseCurveStartX) &&
                    !sf.baseCurveStartY.Equals(baseCurveStartY) &&
                    !sf.baseCurveEndX.Equals(baseCurveEndX) &&
                    !sf.baseCurveEndY.Equals(baseCurveEndY) &&
                    !sf.baseConeSegments.Equals(baseConeSegments) &&
                    !sf.noseCurveStartX.Equals(noseCurveStartX) &&
                    !sf.noseCurveStartY.Equals(noseCurveStartY) &&
                    !sf.noseCurveEndX.Equals(noseCurveEndX) &&
                    !sf.noseCurveEndY.Equals(noseCurveEndY) &&
                    !sf.noseConeSegments.Equals(noseConeSegments) &&
                    !sf.noseHeightRatio.Equals(noseHeightRatio) &&
                    !sf.density.Equals(density))
                {
                    continue;
                }

                sf.meshPos          = mf.transform.localPosition;
                sf.meshRot          = mf.transform.localRotation;
                sf.numSegs          = numSegs;
                sf.numSideParts     = numSideParts;
                sf.baseRad          = baseRad;
                sf.maxRad           = maxRad;
                sf.cylStart         = cylStart;
                sf.cylEnd           = cylEnd;
                sf.topRad           = topRad;
                sf.inlineHeight     = topY;
                sf.sideThickness    = sideThickness;
                sf.baseCurveStartX  = baseCurveStartX;
                sf.baseCurveStartY  = baseCurveStartY;
                sf.baseCurveEndX    = baseCurveEndX;
                sf.baseCurveEndY    = baseCurveEndY;
                sf.baseConeSegments = baseConeSegments;
                sf.noseCurveStartX  = noseCurveStartX;
                sf.noseCurveStartY  = noseCurveStartY;
                sf.noseCurveEndX    = noseCurveEndX;
                sf.noseCurveEndY    = noseCurveEndY;
                sf.noseConeSegments = noseConeSegments;
                sf.noseHeightRatio  = noseHeightRatio;
                sf.density          = density;

                sf.rebuildMesh();
            }

            var shielding = part.GetComponent <KzFairingBaseShielding>();

            if (shielding)
            {
                shielding.reset();
            }
        }
예제 #23
0
        public override void FixedUpdate()
        {
            base.FixedUpdate();

            if (!limitsSet && PFUtils.canCheckTech())
            {
                limitsSet = true;

                float minSize = PFUtils.getTechMinValue("PROCFAIRINGS_MINDIAMETER", 0.25f);
                float maxSize = PFUtils.getTechMaxValue("PROCFAIRINGS_MAXDIAMETER", 30);

                PFUtils.setFieldRange(Fields["baseSize"], minSize, maxSize);
                PFUtils.setFieldRange(Fields["topSize"], minSize, maxSize);

                ((UI_FloatEdit)Fields["baseSize"].uiControlEditor).incrementLarge = diameterStepLarge;
                ((UI_FloatEdit)Fields["baseSize"].uiControlEditor).incrementSmall = diameterStepSmall;
                ((UI_FloatEdit)Fields["topSize"].uiControlEditor).incrementLarge  = diameterStepLarge;
                ((UI_FloatEdit)Fields["topSize"].uiControlEditor).incrementSmall  = diameterStepSmall;

                ((UI_FloatEdit)Fields["height"].uiControlEditor).incrementLarge      = heightStepLarge;
                ((UI_FloatEdit)Fields["height"].uiControlEditor).incrementSmall      = heightStepSmall;
                ((UI_FloatEdit)Fields["extraHeight"].uiControlEditor).incrementLarge = heightStepLarge;
                ((UI_FloatEdit)Fields["extraHeight"].uiControlEditor).incrementSmall = heightStepSmall;
            }

            if (isShipModified)
            {
                isShipModified = false;

                //  We used to remove the engine fairing (if there is any) from topmost node, but lately that's been causing NREs.
                //  Since KSP gives us this option nativley, let's just use KSP to do that if we want.

                if (!HighLogic.LoadedSceneIsEditor)
                {
                    if (isTopNodePartPresent)
                    {
                        var tp = getTopPart();

                        if (tp == null)
                        {
                            isTopNodePartPresent = false;

                            Events["UIToggleTopNodeDecouple"].guiActive = false;
                        }
                        else
                        {
                            if (topNodeDecouplesWhenFairingsGone && !CheckForFairingPresent())
                            {
                                PartModule item = part.Modules["ModuleDecouple"];

                                if (item == null)
                                {
                                    Debug.LogError("[PF]: Cannot decouple from top part!", this);
                                }
                                else
                                {
                                    RemoveTopPartJoints();

                                    ((ModuleDecouple)item).Decouple();

                                    part.stackIcon.RemoveIcon();

                                    StageManager.Instance.SortIcons(true);

                                    isFairingPresent     = false;
                                    isTopNodePartPresent = false;

                                    Events["UIToggleTopNodeDecouple"].guiActive = false;
                                }
                            }
                        }
                    }

                    if (isStaged)
                    {
                        isStaged = false;

                        if (part != null)
                        {
                            if (stageNum == part.inverseStage)
                            {
                                part.stackIcon.RemoveIcon();

                                StageManager.Instance.SortIcons(true);

                                Events["UIToggleTopNodeDecouple"].guiActive = false;
                            }
                        }
                    }
                }
            }
        }
예제 #24
0
        public void rebuildMesh()
        {
            var mf = part.FindModelComponent <MeshFilter>("model");

            if (!mf)
            {
                Debug.LogError("[PF]: No model for side fairing!", part);

                return;
            }

            Mesh m = mf.mesh;

            if (!m)
            {
                Debug.LogError("[PF]: No mesh in side fairing model!", part);

                return;
            }

            mf.transform.localPosition = meshPos;
            mf.transform.localRotation = meshRot;

            updateNodeSize();

            //  Build the fairing shape line.

            float tip = maxRad * noseHeightRatio;

            Vector3 [] shape;

            baseConeShape = new Vector4(baseCurveStartX, baseCurveStartY, baseCurveEndX, baseCurveEndY);
            noseConeShape = new Vector4(noseCurveStartX, noseCurveStartY, noseCurveEndX, noseCurveEndY);

            if (inlineHeight <= 0)
            {
                shape = ProceduralFairingBase.buildFairingShape(baseRad, maxRad, cylStart, cylEnd, noseHeightRatio, baseConeShape, noseConeShape, (int)baseConeSegments, (int)noseConeSegments, vertMapping, mappingScale.y);
            }
            else
            {
                shape = ProceduralFairingBase.buildInlineFairingShape(baseRad, maxRad, topRad, cylStart, cylEnd, inlineHeight, baseConeShape, (int)baseConeSegments, vertMapping, mappingScale.y);
            }

            //  Set up parameters.

            var dirs = new Vector3 [numSegs + 1];

            for (int i = 0; i <= numSegs; ++i)
            {
                float a = Mathf.PI * 2 * (i - numSegs * 0.5f) / (numSideParts * numSegs);

                dirs [i] = new Vector3(Mathf.Cos(a), 0, Mathf.Sin(a));
            }

            float segOMappingScale = (horMapping.y - horMapping.x) / (mappingScale.x * numSegs);
            float segIMappingScale = (horMapping.w - horMapping.z) / (mappingScale.x * numSegs);
            float segOMappingOfs   = horMapping.x / mappingScale.x;
            float segIMappingOfs   = horMapping.z / mappingScale.x;

            if (numSideParts > 2)
            {
                segOMappingOfs   += segOMappingScale * numSegs * (0.5f - 1f / numSideParts);
                segOMappingScale *= 2f / numSideParts;

                segIMappingOfs   += segIMappingScale * numSegs * (0.5f - 1f / numSideParts);
                segIMappingScale *= 2f / numSideParts;
            }

            float stripU0 = stripMapping.x / mappingScale.x;
            float stripU1 = stripMapping.y / mappingScale.x;

            float ringSegLen    = baseRad * Mathf.PI * 2 / (numSegs * numSideParts);
            float topRingSegLen = topRad * Mathf.PI * 2 / (numSegs * numSideParts);

            float collWidth = maxRad * Mathf.PI * 2 / (numSideParts * 3);

            int numMainVerts = (numSegs + 1) * (shape.Length - 1) + 1;
            int numMainFaces = numSegs * ((shape.Length - 2) * 2 + 1);

            int numSideVerts = shape.Length * 2;
            int numSideFaces = (shape.Length - 1) * 2;

            int numRingVerts = (numSegs + 1) * 2;
            int numRingFaces = numSegs * 2;

            if (inlineHeight > 0)
            {
                numMainVerts = (numSegs + 1) * shape.Length;
                numMainFaces = numSegs * (shape.Length - 1) * 2;
            }

            int totalVerts = numMainVerts * 2 + numSideVerts * 2 + numRingVerts;
            int totalFaces = numMainFaces * 2 + numSideFaces * 2 + numRingFaces;

            if (inlineHeight > 0)
            {
                totalVerts += numRingVerts;
                totalFaces += numRingFaces;
            }

            var p = shape [shape.Length - 1];

            float topY = p.y, topV = p.z;

            float collCenter = (cylStart + cylEnd) / 2;
            float collHeight = cylEnd - cylStart;

            if (collHeight <= 0)
            {
                collHeight = Mathf.Min(topY - cylEnd, cylStart) / 2;
            }

            //  Compute the area.

            double area = 0;

            for (int i = 1; i < shape.Length; ++i)
            {
                area += (shape [i - 1].x + shape [i].x) * (shape [i].y - shape [i - 1].y) * Mathf.PI / numSideParts;
            }

            //  Set the parameters based on volume.

            float volume = (float)(area * sideThickness);

            part.mass           = totalMass = volume * density;
            part.breakingForce  = part.mass * specificBreakingForce;
            part.breakingTorque = part.mass * specificBreakingTorque;

            var offset = new Vector3(maxRad * 0.7f, topY * 0.5f, 0);

            part.CoMOffset = part.transform.InverseTransformPoint(mf.transform.TransformPoint(offset));

            //  Remove any old colliders.

            var colls = part.FindModelComponents <Collider>();

            for (int i = 0; i < colls.Count; i++)
            {
                var c = colls [i];

                UnityEngine.Object.Destroy(c.gameObject);
            }

            //  Add the new colliders.

            for (int i = -1; i <= 1; ++i)
            {
                var obj = new GameObject("collider");

                obj.transform.parent        = mf.transform;
                obj.transform.localPosition = Vector3.zero;
                obj.transform.localRotation = Quaternion.AngleAxis(90f * i / numSideParts, Vector3.up);

                var coll = obj.AddComponent <BoxCollider>();

                coll.center = new Vector3(maxRad + sideThickness * 0.5f, collCenter, 0);
                coll.size   = new Vector3(sideThickness, collHeight, collWidth);
            }
            {
                //  Nose collider.

                float r = maxRad * 0.2f;

                var obj = new GameObject("nose_collider");

                obj.transform.parent        = mf.transform;
                obj.transform.localPosition = new Vector3(r, cylEnd + tip - r * 1.2f, 0);
                obj.transform.localRotation = Quaternion.identity;

                if (inlineHeight > 0)
                {
                    r = sideThickness * 0.5f;

                    obj.transform.localPosition = new Vector3(maxRad + r, collCenter, 0);
                }

                var coll = obj.AddComponent <SphereCollider>();

                coll.center = Vector3.zero;
                coll.radius = r;
            }

            //  Build the fairing mesh.

            m.Clear();

            var verts = new Vector3 [totalVerts];
            var uv    = new Vector2 [totalVerts];
            var norm  = new Vector3 [totalVerts];
            var tang  = new Vector4 [totalVerts];

            if (inlineHeight <= 0)
            {
                //  Tip vertex.

                verts [numMainVerts - 1].Set(0, topY + sideThickness, 0);       //  Outside.
                verts [numMainVerts * 2 - 1].Set(0, topY, 0);                   //  Inside.

                uv [numMainVerts - 1].Set(segOMappingScale * 0.5f * numSegs + segOMappingOfs, topV);
                uv [numMainVerts * 2 - 1].Set(segIMappingScale * 0.5f * numSegs + segIMappingOfs, topV);

                norm [numMainVerts - 1]     = Vector3.up;
                norm [numMainVerts * 2 - 1] = -Vector3.up;

                tang [numMainVerts - 1]     = Vector3.zero;
                tang [numMainVerts * 2 - 1] = Vector3.zero;
            }

            //  Main vertices.

            float noseV0     = vertMapping.z / mappingScale.y;
            float noseV1     = vertMapping.w / mappingScale.y;
            float noseVScale = 1f / (noseV1 - noseV0);
            float oCenter    = (horMapping.x + horMapping.y) / (mappingScale.x * 2);
            float iCenter    = (horMapping.z + horMapping.w) / (mappingScale.x * 2);

            int vi = 0;

            for (int i = 0; i < shape.Length - (inlineHeight <= 0 ? 1 : 0); ++i)
            {
                p = shape [i];

                Vector2 n;

                if (i == 0)
                {
                    n = shape [1] - shape [0];
                }
                else if (i == shape.Length - 1)
                {
                    n = shape [i] - shape [i - 1];
                }
                else
                {
                    n = shape [i + 1] - shape [i - 1];
                }

                n.Set(n.y, -n.x);

                n.Normalize();

                for (int j = 0; j <= numSegs; ++j, ++vi)
                {
                    var d = dirs [j];

                    var dp = d * p.x + Vector3.up * p.y;
                    var dn = d * n.x + Vector3.up * n.y;

                    if (i == 0 || i == shape.Length - 1)
                    {
                        verts [vi] = dp + d * sideThickness;
                    }
                    else
                    {
                        verts [vi] = dp + dn * sideThickness;
                    }

                    verts[vi + numMainVerts] = dp;

                    float v  = (p.z - noseV0) * noseVScale;
                    float uo = j * segOMappingScale + segOMappingOfs;
                    float ui = (numSegs - j) * segIMappingScale + segIMappingOfs;

                    if (v > 0 && v < 1)
                    {
                        float us = 1 - v;

                        uo = (uo - oCenter) * us + oCenter;
                        ui = (ui - iCenter) * us + iCenter;
                    }

                    uv [vi].Set(uo, p.z);

                    uv [vi + numMainVerts].Set(ui, p.z);

                    norm [vi] = dn;
                    norm [vi + numMainVerts] = -dn;

                    tang [vi].Set(-d.z, 0, d.x, 0);
                    tang [vi + numMainVerts].Set(d.z, 0, -d.x, 0);
                }
            }

            //  Side strip vertices.

            float stripScale = Mathf.Abs(stripMapping.y - stripMapping.x) / (sideThickness * mappingScale.y);

            vi = numMainVerts * 2;

            float o = 0;

            for (int i = 0; i < shape.Length; ++i, vi += 2)
            {
                int si = i * (numSegs + 1);

                var d = dirs [0];

                verts [vi] = verts [si];

                uv [vi].Set(stripU0, o);
                norm [vi].Set(d.z, 0, -d.x);

                verts [vi + 1] = verts [si + numMainVerts];
                uv [vi + 1].Set(stripU1, o);
                norm [vi + 1] = norm[vi];
                tang [vi]     = tang [vi + 1] = (verts [vi + 1] - verts [vi]).normalized;

                if (i + 1 < shape.Length)
                {
                    o += ((Vector2)shape [i + 1] - (Vector2)shape [i]).magnitude * stripScale;
                }
            }

            vi += numSideVerts - 2;

            for (int i = shape.Length - 1; i >= 0; --i, vi -= 2)
            {
                int si = i * (numSegs + 1) + numSegs;

                if (i == shape.Length - 1 && inlineHeight <= 0)
                {
                    si = numMainVerts - 1;
                }

                var d = dirs [numSegs];

                verts [vi] = verts [si];
                uv [vi].Set(stripU0, o);
                norm [vi].Set(-d.z, 0, d.x);

                verts [vi + 1] = verts [si + numMainVerts];
                uv [vi + 1].Set(stripU1, o);
                norm [vi + 1] = norm [vi];
                tang [vi]     = tang [vi + 1] = (verts [vi + 1] - verts [vi]).normalized;

                if (i > 0)
                {
                    o += ((Vector2)shape [i] - (Vector2)shape [i - 1]).magnitude * stripScale;
                }
            }

            //  Ring vertices.

            vi = numMainVerts * 2 + numSideVerts * 2;

            o = 0;

            for (int j = numSegs; j >= 0; --j, vi += 2, o += ringSegLen * stripScale)
            {
                verts [vi] = verts [j];
                uv [vi].Set(stripU0, o);
                norm [vi] = -Vector3.up;

                verts [vi + 1] = verts [j + numMainVerts];
                uv [vi + 1].Set(stripU1, o);
                norm [vi + 1] = -Vector3.up;
                tang [vi]     = tang [vi + 1] = (verts [vi + 1] - verts [vi]).normalized;
            }

            if (inlineHeight > 0)
            {
                //  Top ring vertices.

                o = 0;

                int si = (shape.Length - 1) * (numSegs + 1);

                for (int j = 0; j <= numSegs; ++j, vi += 2, o += topRingSegLen * stripScale)
                {
                    verts [vi] = verts [si + j];
                    uv [vi].Set(stripU0, o);
                    norm [vi] = Vector3.up;

                    verts [vi + 1] = verts [si + j + numMainVerts];
                    uv [vi + 1].Set(stripU1, o);
                    norm [vi + 1] = Vector3.up;
                    tang [vi]     = tang [vi + 1] = (verts [vi + 1] - verts [vi]).normalized;
                }
            }

            //  Set vertex data to mesh.

            for (int i = 0; i < totalVerts; ++i)
            {
                tang [i].w = 1;
            }

            m.vertices = verts;
            m.uv       = uv;
            m.normals  = norm;
            m.tangents = tang;

            m.uv2      = null;
            m.colors32 = null;

            var tri = new int [totalFaces * 3];

            //  Main faces.

            vi = 0;

            int ti1 = 0, ti2 = numMainFaces * 3;

            for (int i = 0; i < shape.Length - (inlineHeight <= 0 ? 2 : 1); ++i, ++vi)
            {
                p = shape [i];

                for (int j = 0; j < numSegs; ++j, ++vi)
                {
                    tri [ti1++] = vi;
                    tri [ti1++] = vi + 1 + numSegs + 1;
                    tri [ti1++] = vi + 1;

                    tri [ti1++] = vi;
                    tri [ti1++] = vi + numSegs + 1;
                    tri [ti1++] = vi + 1 + numSegs + 1;

                    tri [ti2++] = numMainVerts + vi;
                    tri [ti2++] = numMainVerts + vi + 1;
                    tri [ti2++] = numMainVerts + vi + 1 + numSegs + 1;

                    tri [ti2++] = numMainVerts + vi;
                    tri [ti2++] = numMainVerts + vi + 1 + numSegs + 1;
                    tri [ti2++] = numMainVerts + vi + numSegs + 1;
                }
            }

            if (inlineHeight <= 0)
            {
                //  Main tip faces.

                for (int j = 0; j < numSegs; ++j, ++vi)
                {
                    tri [ti1++] = vi;
                    tri [ti1++] = numMainVerts - 1;
                    tri [ti1++] = vi + 1;

                    tri [ti2++] = numMainVerts + vi;
                    tri [ti2++] = numMainVerts + vi + 1;
                    tri [ti2++] = numMainVerts + numMainVerts - 1;
                }
            }

            //  Side strip faces.

            vi  = numMainVerts * 2;
            ti1 = numMainFaces * 2 * 3;
            ti2 = ti1 + numSideFaces * 3;

            for (int i = 0; i < shape.Length - 1; ++i, vi += 2)
            {
                tri [ti1++] = vi;
                tri [ti1++] = vi + 1;
                tri [ti1++] = vi + 3;

                tri [ti1++] = vi;
                tri [ti1++] = vi + 3;
                tri [ti1++] = vi + 2;

                tri [ti2++] = numSideVerts + vi;
                tri [ti2++] = numSideVerts + vi + 3;
                tri [ti2++] = numSideVerts + vi + 1;

                tri [ti2++] = numSideVerts + vi;
                tri [ti2++] = numSideVerts + vi + 2;
                tri [ti2++] = numSideVerts + vi + 3;
            }

            //  Ring faces.

            vi  = numMainVerts * 2 + numSideVerts * 2;
            ti1 = (numMainFaces + numSideFaces) * 2 * 3;

            for (int j = 0; j < numSegs; ++j, vi += 2)
            {
                tri [ti1++] = vi;
                tri [ti1++] = vi + 1;
                tri [ti1++] = vi + 3;

                tri [ti1++] = vi;
                tri [ti1++] = vi + 3;
                tri [ti1++] = vi + 2;
            }

            if (inlineHeight > 0)
            {
                //  Top ring faces.

                vi += 2;

                for (int j = 0; j < numSegs; ++j, vi += 2)
                {
                    tri [ti1++] = vi;
                    tri [ti1++] = vi + 1;
                    tri [ti1++] = vi + 3;

                    tri [ti1++] = vi;
                    tri [ti1++] = vi + 3;
                    tri [ti1++] = vi + 2;
                }
            }

            m.triangles = tri;

            StartCoroutine(PFUtils.updateDragCubeCoroutine(part, 1));
        }