Ejemplo n.º 1
0
        public void OnInitStorables(VAMLaunch plugin)
        {
            _minPosition = new JSONStorableFloat("patternSourceMinPosition", 10.0f, 0.0f, 99.0f);
            plugin.RegisterFloat(_minPosition);
            _maxPosition = new JSONStorableFloat("patternSourceMaxPosition", 80.0f, 0.0f, 99.0f);
            plugin.RegisterFloat(_maxPosition);

            _targetAnimationAtomChooser = new JSONStorableStringChooser("patternSourceTargetAnimationAtom",
                                                                        GetTargetAnimationAtomChoices(), "", "Target Animation Pattern",
                                                                        (name) =>
            {
                _animationAtomController = null;
                _targetAnimationPattern  = null;

                if (string.IsNullOrEmpty(name))
                {
                    return;
                }

                var atom = SuperController.singleton.GetAtomByUid(name);
                if (atom && atom.animationPatterns.Length > 0)
                {
                    _animationAtomController = atom.freeControllers[0];
                    _targetAnimationPattern  = atom.animationPatterns[0];
                    _patternTime             = _targetAnimationPattern.GetFloatJSONParam("currentTime");
                    _patternSpeed            = _targetAnimationPattern.GetFloatJSONParam("speed");
                }
            });
            plugin.RegisterStringChooser(_targetAnimationAtomChooser);

            _samplePlaneChooser = new JSONStorableStringChooser("patternSourceSamplePlane", _samplePlaneChoices, "",
                                                                "Sample Plane", (name) =>
            {
                for (int i = 0; i < _samplePlaneChoices.Count; i++)
                {
                    if (_samplePlaneChoices[i] == name)
                    {
                        _samplePlaneIndex = i;
                        break;
                    }
                }
            });
            plugin.RegisterStringChooser(_samplePlaneChooser);
            if (string.IsNullOrEmpty(_samplePlaneChooser.val))
            {
                _samplePlaneChooser.SetVal("Y");
            }

            _includeMidPoints = new JSONStorableBool("patternSourceIncludeMidPoints", false);
            plugin.RegisterBool(_includeMidPoints);
            _invertPosition = new JSONStorableBool("patternSourceInvertPosition", false);
            plugin.RegisterBool(_invertPosition);
            _useLocalSpace = new JSONStorableBool("patternSourceUseLocalSpace", false);
            plugin.RegisterBool(_useLocalSpace);
        }
Ejemplo n.º 2
0
    public override void Init()
    {
        try
        {
            _pattern = containingAtom.GetComponentInChildren <AnimationPattern>();
            if (_pattern == null)
            {
                throw new Exception("The Director plugin can only be applied on AnimationPattern.");
            }
            _possessor                  = SuperController.singleton.centerCameraTarget.transform.GetComponent <Possessor>();
            _currentTimeJSON            = _pattern.GetFloatJSONParam("currentTime");
            _speedJSON                  = _pattern.GetFloatJSONParam("speed");
            _camExposureJSON            = GameObject.FindObjectOfType <SkyshopLightController>()?.GetFloatJSONParam("camExposure");
            _navigationHologridRenderer = SuperController.singleton.navigationHologrid.gameObject.GetComponent <MeshRenderer>();

            InitControls();
            UpdateActivation();
        }
        catch (Exception e)
        {
            SuperController.LogError("Failed to initialize plugin: " + e);
        }
    }
Ejemplo n.º 3
0
        public void OnSimulatorUpdate(float prevPos, float newPos, float deltaTime)
        {
            if (_targetAnimationPattern == null)
            {
                if (!string.IsNullOrEmpty(_targetAnimationAtomChooser.val))
                {
                    _targetAnimationAtomChooser.SetVal("");
                }

                return;
            }

            if (_pluginFreeController.selected && SuperController.singleton.editModeToggle.isOn)
            {
                _lineDrawer0.SetLinePoints(_pluginFreeController.transform.position,
                                           _animationAtomController.transform.position);
                _lineDrawer0.Draw();
            }

            var currentTime = _targetAnimationPattern.GetFloatJSONParam("currentTime");

            float totalTime = _targetAnimationPattern.GetTotalTime();

            float normOldTime = currentTime.val / totalTime;

            float normalizedLaunchPos = Mathf.InverseLerp(_minPosition.val, _maxPosition.val, newPos);

            float newTime;

            if (_moveUpwards)
            {
                newTime = Mathf.Lerp(0.0f, totalTime * 0.5f, normalizedLaunchPos);
            }
            else
            {
                newTime = Mathf.Lerp(totalTime, totalTime * 0.5f, normalizedLaunchPos);
            }

            newTime = (newTime + (totalTime * _animationOffset.val)) % totalTime;

            float normNewTime = newTime / totalTime;

            if (Mathf.Abs(normNewTime - normOldTime) > 0.05f)
            {
                currentTime.SetVal(newTime);
            }
            _targetAnimationPattern.SetFloatParamValue("speed", totalTime / (_dirChangeDuration * 2.0f));
        }
Ejemplo n.º 4
0
        public ThrustController(DollmasterPlugin dm) : base(dm)
        {
            thrustEnabled = new JSONStorableBool("thrustEnabled", true);
            dm.RegisterBool(thrustEnabled);
            UIDynamicToggle moduleEnableToggle = dm.CreateToggle(thrustEnabled);

            moduleEnableToggle.label           = "Enable Thrusting";
            moduleEnableToggle.backgroundColor = Color.green;

            thrustAtomChooser = new JSONStorableStringChooser("thrustTarget", GetAnimationPatternNames(), "", "Thrust Control", (string name) =>
            {
                RestoreOriginalSlider();

                ap = null;

                UISetupState(SuperController.singleton.GetAtomByUid(name));
            });
            thrustAtomChooser.storeType = JSONStorableParam.StoreType.Full;
            dm.RegisterStringChooser(thrustAtomChooser);
            UIDynamicPopup popup = dm.CreatePopup(thrustAtomChooser);

            popup.popup.onOpenPopupHandlers += () =>
            {
                thrustAtomChooser.choices = GetAnimationPatternNames();
            };

            slider = dm.ui.CreateSlider("Thrust Speed", 300, 120);
            slider.transform.Translate(0, 0.15f, 0, Space.Self);

            slider.slider.onValueChanged.AddListener((float v) =>
            {
                if (ap == null)
                {
                    return;
                }

                JSONStorableFloat speedStore = ap.GetFloatJSONParam("speed");
                if (speedStore.slider != slider.slider)
                {
                    AttachCustomSlider();
                }
            });

            Image img = slider.GetComponentInChildren <Image>();

            img.color = new Color(0.4f, 0.2f, 0.245f, 1.0f);
            slider.labelText.color = new Color(1, 1, 1);

            slider.gameObject.SetActive(false);

            createButton = dm.ui.CreateButton("Generate Animation Pattern or...", 400, 120);
            createButton.transform.Translate(0, 0.15f, 0, Space.Self);
            createButton.buttonColor = new Color(0.4f, 0.2f, 0.245f, 1.0f);
            createButton.textColor   = new Color(1, 1, 1);

            createButton.button.onClick.AddListener(GenerateThrustAtoms);

            selectButton = dm.ui.CreateButton("Select Animation Pattern To Control", 400, 120);
            selectButton.transform.Translate(0.52f, 0.15f, 0, Space.Self);
            selectButton.buttonColor = new Color(0.4f, 0.2f, 0.245f, 1.0f);
            selectButton.textColor   = new Color(1, 1, 1);

            selectButton.button.onClick.AddListener(() =>
            {
                SuperController.singleton.SelectModeAtom((atom) =>
                {
                    if (atom == null)
                    {
                        return;
                    }

                    if (atom.GetStorableByID("AnimationPattern") == null)
                    {
                        SuperController.LogError("Select an Animation Pattern");
                        return;
                    }

                    AnimationPattern ap = atom.GetStorableByID("AnimationPattern") as AnimationPattern;
                    ap.SyncStepNames();
                    ap.autoSyncStepNamesJSON.SetVal(false);

                    thrustAtomChooser.SetVal(atom.uid);
                });
            });

            dm.CreateSpacer();

            GenerateThrustAtoms();
        }
Ejemplo n.º 5
0
        public override void Init()
        {
            try
            {
                if (containingAtom == null)
                {
                    return;
                }


                PATH_WHEN_LOADED = SuperController.singleton.currentLoadDir;
                SuperController.singleton.currentSaveDir = PATH_WHEN_LOADED;

                uiVisible = new JSONStorableBool("uiVisible", false, (bool on) =>
                {
                    ui.canvas.enabled    = on;
                    keyUI.canvas.enabled = on;
                });
                RegisterBool(uiVisible);


                #region Animation Pattern Init
                animationPattern = containingAtom.GetStorableByID("AnimationPattern") as AnimationPattern;
                if (animationPattern == null)
                {
                    SuperController.LogError("You must add this plugin to an AnimationPattern");
                    return;
                }
                animationPattern.Pause();
                animationPattern.containingAtom.GetStorableByID("scale").SetFloatParamValue("scale", 0);
                #endregion


                CreatePeopleChooser((atom) =>
                {
                    if (atom == null)
                    {
                        return;
                    }
                    person = atom;
                });

                saveStore = new JSONStorableString("keyframes", "");
                RegisterString(saveStore);

                #region World UI
                float UISCALE = 0.001f;
                ui = new UI(this, UISCALE);
                ui.canvas.transform.SetParent(animationPattern.containingAtom.mainController.transform, false);

                UIDynamicToggle  onToggle   = ui.CreateToggle("On", 120, 60);
                JSONStorableBool onStorable = animationPattern.GetBoolJSONParam("on");
                onStorable.toggleAlt             = onToggle.toggle;
                onToggle.transform.localPosition = new Vector3(-575 + 35, 160, 0);

                UIDynamicToggle  loopToggle   = ui.CreateToggle("Loop", 140, 60);
                JSONStorableBool loopStorable = animationPattern.GetBoolJSONParam("loop");
                loopStorable.toggleAlt             = loopToggle.toggle;
                loopToggle.transform.localPosition = new Vector3(-575 + 180, 160, 0);

                UIDynamicButton doneEditingButton = ui.CreateButton("Done Editing", 200, 60);
                doneEditingButton.transform.localPosition = new Vector3(-575 + 75, -300, 0);
                doneEditingButton.buttonColor             = new Color(0.45f, 0.8f, 0.3f);
                doneEditingButton.textColor = new Color(1, 1, 1);
                doneEditingButton.button.onClick.AddListener(() =>
                {
                    uiVisible.SetVal(false);
                    ClearEditingKey();
                    if (SuperController.singleton.GetSelectedAtom() == animationPattern.containingAtom)
                    {
                        SuperController.singleton.ClearSelection();
                    }
                });

                timeSlider = ui.CreateSlider("Time", 500, 110);
                timeSlider.transform.localPosition = new Vector3(-350, 55, 0);
                timeSlider.slider.maxValue         = animationPattern.GetTotalTime();

                time           = animationPattern.GetFloatJSONParam("currentTime");
                time.sliderAlt = timeSlider.slider;

                UIDynamicButton playButton = ui.CreateButton("▶", 50, 60);
                playButton.transform.localPosition = new Vector3(-575, -30, 0);
                playButton.button.onClick.AddListener(() =>
                {
                    ClearEditingKey();
                    if (animationPattern.GetCurrentTimeCounter() == animationPattern.GetTotalTime())
                    {
                        animationPattern.ResetAndPlay();
                    }
                    else
                    {
                        animationPattern.Play();
                    }
                });

                UIDynamicButton pauseButton = ui.CreateButton("||", 50, 60);
                pauseButton.transform.localPosition = new Vector3(-525, -30, 0);
                pauseButton.button.onClick.AddListener(() =>
                {
                    ClearEditingKey();
                    animationPattern.Pause();
                });

                UIDynamicButton rewindButton = ui.CreateButton("«", 50, 60);
                rewindButton.transform.localPosition = new Vector3(-475, -30, 0);
                rewindButton.button.onClick.AddListener(() =>
                {
                    ClearEditingKey();
                    animationPattern.ResetAnimation();
                });

                UIDynamicButton addKeyButton = ui.CreateButton("+Key", 100, 60);
                addKeyButton.textColor               = new Color(0.984f, 0.917f, 0.972f);
                addKeyButton.buttonColor             = new Color(0.474f, 0.023f, 0.4f);
                addKeyButton.transform.localPosition = new Vector3(-550, -200, 0);
                addKeyButton.button.onClick.AddListener(AddKey);

                UIDynamicButton insertKeyButton = ui.CreateButton("Insert Key", 140, 60);
                insertKeyButton.textColor               = new Color(0.984f, 0.917f, 0.972f);
                insertKeyButton.buttonColor             = new Color(0.474f, 0.023f, 0.4f);
                insertKeyButton.transform.localPosition = new Vector3(-420, -200, 0);
                insertKeyButton.button.onClick.AddListener(InsertKey);

                UIDynamicButton nextKeyButton = ui.CreateButton("Next Key", 140, 60);
                nextKeyButton.transform.localPosition = new Vector3(-390, -100, 0);
                nextKeyButton.button.onClick.AddListener(() =>
                {
                    if (keyframes.Count <= 0)
                    {
                        return;
                    }

                    int index     = keyframes.IndexOf(GetNearestKeyToTime(time.val));
                    int nextIndex = index + 1;
                    if (nextIndex >= keyframes.Count)
                    {
                        nextIndex = 0;
                    }

                    SuperController.singleton.SelectController(keyframes[nextIndex].step.containingAtom.mainController);
                });

                UIDynamicButton previousKeyButton = ui.CreateButton("Prev Key", 140, 60);
                previousKeyButton.transform.localPosition = new Vector3(-530, -100, 0);
                previousKeyButton.button.onClick.AddListener(() =>
                {
                    if (keyframes.Count <= 0)
                    {
                        return;
                    }

                    int index     = keyframes.IndexOf(GetNearestKeyToTime(time.val));
                    int prevIndex = index - 1;

                    if (prevIndex < 0)
                    {
                        prevIndex = keyframes.Count - 1;
                    }

                    SuperController.singleton.SelectController(keyframes[prevIndex].step.containingAtom.mainController);
                });



                #endregion

                #region Keyframe UI
                keyUI          = new UI(this, UISCALE * 0.8f);
                undoPoseButton = keyUI.CreateButton("Undo", 300, 90);
                undoPoseButton.buttonText.fontSize     = 40;
                undoPoseButton.transform.localPosition = new Vector3(0, 120, 0);
                undoPoseButton.textColor   = new Color(1, 1, 1);
                undoPoseButton.buttonColor = new Color(0.650f, 0.027f, 0.027f);
                undoPoseButton.button.onClick.AddListener(() =>
                {
                    if (editingKeyframe == null)
                    {
                        return;
                    }

                    editingKeyframe.pose = lastPose;
                    SetToPose(lastPose, true);
                });

                setDurationSlider = keyUI.CreateSlider("Duration To Key", 500, 110);
                setDurationSlider.defaultButtonEnabled    = true;
                setDurationSlider.quickButtonsEnabled     = true;
                setDurationSlider.rangeAdjustEnabled      = true;
                setDurationSlider.transform.localPosition = new Vector3(0, -270, 0);

                tweenSelection = keyUI.CreatePopup("Tween", 500, 110);
                tweenSelection.transform.localPosition = new Vector3(0, -350, 0);

                UIDynamicButton copyPoseButton = keyUI.CreateButton("Copy Pose", 200, 60);
                copyPoseButton.transform.localPosition = new Vector3(-150, -170, 0);
                copyPoseButton.button.onClick.AddListener(() =>
                {
                    if (editingKeyframe == null)
                    {
                        return;
                    }

                    copiedPose = editingKeyframe.pose;
                });

                UIDynamicButton pastePoseButton = keyUI.CreateButton("Paste Pose", 200, 60);
                pastePoseButton.transform.localPosition = new Vector3(50, -170, 0);
                pastePoseButton.button.onClick.AddListener(() =>
                {
                    if (editingKeyframe == null)
                    {
                        return;
                    }

                    if (copiedPose == null)
                    {
                        return;
                    }

                    editingKeyframe.pose = copiedPose;
                    SetToPose(editingKeyframe.pose, true);
                });

                UIDynamicButton copyControllerButton = keyUI.CreateButton("Copy Control", 200, 60);
                copyControllerButton.transform.localPosition = new Vector3(-150, -120, 0);
                copyControllerButton.button.onClick.AddListener(() =>
                {
                    if (editingKeyframe == null)
                    {
                        return;
                    }

                    FreeControllerV3 selectedController = SuperController.singleton.GetSelectedController();
                    if (selectedController == null)
                    {
                        return;
                    }

                    copiedPosition = selectedController.transform.position;
                    copiedRotation = selectedController.transform.rotation;
                });

                UIDynamicButton pasteControllerButton = keyUI.CreateButton("Paste Control", 200, 60);
                pasteControllerButton.transform.localPosition = new Vector3(50, -120, 0);
                pasteControllerButton.button.onClick.AddListener(() =>
                {
                    if (editingKeyframe == null)
                    {
                        return;
                    }

                    FreeControllerV3 selectedController = SuperController.singleton.GetSelectedController();
                    if (selectedController == null)
                    {
                        return;
                    }

                    selectedController.transform.position = copiedPosition;
                    selectedController.transform.rotation = copiedRotation;
                });

                #endregion

                #region Plugin (Debug) UI
                #region Experimental Save and Load Pose
                UIDynamicButton loadPoseButton = CreateButton("Load Pose");
                loadPoseButton.transform.localPosition = new Vector3(-520, -150, 0);
                loadPoseButton.button.onClick.AddListener(() =>
                {
                    if (person == null)
                    {
                        return;
                    }

                    Vector3 position    = person.mainController.transform.position;
                    Quaternion rotation = person.mainController.transform.rotation;

                    person.LoadPhysicalPresetDialog();

                    person.mainController.transform.SetPositionAndRotation(position, rotation);
                });

                UIDynamicButton savePoseButton = CreateButton("Save Pose");
                savePoseButton.transform.localPosition = new Vector3(-360, -150, 0);
                savePoseButton.button.onClick.AddListener(() =>
                {
                    if (person == null)
                    {
                        return;
                    }

                    person.SavePresetDialog(true, false);
                });
                #endregion
                #endregion

                timeSlider.slider.onValueChanged.AddListener((value) =>
                {
                });

                UIDynamicToggle snapToggle = ui.CreateToggle("Snap To Keyframe", 340, 60);
                snapToggle.transform.localPosition = new Vector3(-270, -30);

                EventTrigger       timeSliderET = timeSlider.slider.gameObject.AddComponent <EventTrigger>();
                EventTrigger.Entry entry        = new EventTrigger.Entry();
                entry.eventID = EventTriggerType.Drag;
                entry.callback.AddListener((data) =>
                {
                    if (keyframes.Count == 0)
                    {
                        return;
                    }

                    if (snapToggle.toggle.isOn)
                    {
                        Keyframe nearestToTime = null;
                        keyframes.ForEach((key) =>
                        {
                            float timeDelta = Mathf.Abs(key.step.timeStep - time.val);
                            if (timeDelta <= 0.01f && nearestToTime == null)
                            {
                                nearestToTime = key;
                            }
                        });

                        if (nearestToTime != null)
                        {
                            SuperController.singleton.SelectController(nearestToTime.step.containingAtom.mainController);
                        }
                        else
                        {
                            SuperController.singleton.ClearSelection();
                            ClearEditingKey();
                        }
                    }
                    else
                    {
                        ClearEditingKey();
                    }
                });
                timeSliderET.triggers.Add(entry);

                Transform thickTimelineTransform = new GameObject().transform;
                thickTimelineTransform.SetParent(animationPattern.containingAtom.mainController.transform, false);
                thickTimeline = thickTimelineTransform.gameObject.AddComponent <LineRenderer>();
                thickTimeline.positionCount     = 0;
                thickTimeline.startWidth        = 0.02f;
                thickTimeline.endWidth          = 0.02f;
                thickTimeline.material          = animationPattern.rootLineDrawerMaterial;
                thickTimeline.material.color    = new Color(0.15f, 0.66f, 0.0f);
                thickTimeline.useWorldSpace     = false;
                thickTimeline.numCornerVertices = 6;
                thickTimeline.alignment         = LineAlignment.View;

                updateKeyframeLoop = StartCoroutine(UpdateEditingKeyframeLoop());

                lookAtCamera = new JSONStorableBool("UILookAtCamera", true, (bool on) =>
                {
                    ui.lookAtCamera    = on;
                    keyUI.lookAtCamera = on;
                });
                RegisterBool(lookAtCamera);
                CreateToggle(lookAtCamera);

                person.freeControllers.ToList().ForEach((fc) =>
                {
                    JSONStorableBool animateControl = new JSONStorableBool(fc.name, true);
                    RegisterBool(animateControl);
                    CreateToggle(animateControl, true);
                });

                CreateButton("Reverse Animation").button.onClick.AddListener(() =>
                {
                    List <AnimationStep> reversedSteps = animationPattern.steps.ToList();
                    reversedSteps.Reverse();

                    animationPattern.steps = reversedSteps.ToArray();
                    animationPattern.RecalculateTimeSteps();

                    keyframes.Reverse();
                });

                maxKeyframesPerRow = new JSONStorableFloat("max keyframes per row", 6, (float value) =>
                {
                }, 2, 20, true);
                CreateSlider(maxKeyframesPerRow);
                RegisterFloat(maxKeyframesPerRow);
            }
            catch (Exception e)
            {
                SuperController.LogError("Exception caught: " + e);
            }
        }
Ejemplo n.º 6
0
        public ThrustController(DollmasterPlugin dm) : base(dm)
        {
            thrustEnabled = new JSONStorableBool("thrustEnabled", true);
            dm.RegisterBool(thrustEnabled);
            UIDynamicToggle moduleEnableToggle = dm.CreateToggle(thrustEnabled);

            moduleEnableToggle.label           = "Enable Thrusting";
            moduleEnableToggle.backgroundColor = Color.green;

            thrustAtomChooser = new JSONStorableStringChooser("thrustTarget", GetAnimationPatternNames(), "", "Thrust Control", (string name) =>
            {
                RestoreOriginalSlider();

                ap = null;

                UISetupState(SuperController.singleton.GetAtomByUid(name));
            });
            thrustAtomChooser.storeType = JSONStorableParam.StoreType.Full;
            dm.RegisterStringChooser(thrustAtomChooser);
            UIDynamicPopup popup = dm.CreatePopup(thrustAtomChooser);

            popup.popup.onOpenPopupHandlers += () =>
            {
                thrustAtomChooser.choices = GetAnimationPatternNames();
            };

            slider = dm.ui.CreateSlider("Thrust Speed", 300, 120);
            slider.transform.Translate(0, 0.15f, 0, Space.Self);

            slider.slider.onValueChanged.AddListener((float v) =>
            {
                if (ap == null)
                {
                    return;
                }

                JSONStorableFloat speedStore = ap.GetFloatJSONParam("speed");
                if (speedStore.slider != slider.slider)
                {
                    AttachCustomSlider();
                }
            });

            Image img = slider.GetComponentInChildren <Image>();

            img.color = new Color(0.4f, 0.2f, 0.245f, 1.0f);
            slider.labelText.color = new Color(1, 1, 1);

            slider.gameObject.SetActive(false);

            createButton = dm.ui.CreateButton("Generate Animation Pattern or...", 400, 120);
            createButton.transform.Translate(0, 0.15f, 0, Space.Self);
            createButton.buttonColor = new Color(0.4f, 0.2f, 0.245f, 1.0f);
            createButton.textColor   = new Color(1, 1, 1);

            createButton.button.onClick.AddListener(() =>
            {
                dm.StartCoroutine(CreateAtom("AnimationPattern", "Thrust AP", (apAtom) =>
                {
                    AnimationPattern ap = apAtom.GetStorableByID("AnimationPattern") as AnimationPattern;

                    ap.autoSyncStepNamesJSON.SetVal(false);

                    thrustAtomChooser.SetVal(apAtom.name);

                    if (ap.steps.Length >= 2)
                    {
                        return;
                    }

                    FreeControllerV3 hipControl = atom.GetStorableByID("hipControl") as FreeControllerV3;
                    apAtom.mainController.transform.SetPositionAndRotation(hipControl.transform.position, hipControl.transform.rotation);
                    apAtom.SelectAtomParent(atom);

                    //ap.animatedTransform = hipControl.transform;

                    MoveProducer mp = apAtom.GetStorableByID("AnimatedObject") as MoveProducer;
                    mp.SetReceiverByName(atom.name + ":hipControl");

                    AnimationStep stepA = ap.CreateStepAtPosition(0);
                    stepA.containingAtom.ClearParentAtom();
                    stepA.containingAtom.mainController.transform.position = apAtom.mainController.transform.position;
                    stepA.containingAtom.mainController.transform.rotation = apAtom.mainController.transform.rotation;
                    //stepA.containingAtom.SetParentAtom(apAtom.name);

                    AnimationStep stepB = ap.CreateStepAtPosition(1);
                    stepB.containingAtom.ClearParentAtom();
                    FreeControllerV3 abdomen2Control = atom.GetStorableByID("abdomen2Control") as FreeControllerV3;

                    stepB.containingAtom.mainController.transform.position = abdomen2Control.transform.position;
                    stepB.containingAtom.mainController.transform.rotation = apAtom.mainController.transform.rotation;

                    apAtom.mainController.transform.Translate(0, 0, -0.2f, Space.Self);

                    stepA.containingAtom.mainController.currentPositionState = FreeControllerV3.PositionState.ParentLink;
                    stepA.containingAtom.mainController.currentRotationState = FreeControllerV3.RotationState.ParentLink;
                    Rigidbody rba = SuperController.singleton.RigidbodyNameToRigidbody(apAtom.name + ":control");
                    stepA.containingAtom.mainController.SelectLinkToRigidbody(rba, FreeControllerV3.SelectLinkState.PositionAndRotation);

                    stepB.containingAtom.mainController.currentPositionState = FreeControllerV3.PositionState.ParentLink;
                    stepB.containingAtom.mainController.currentRotationState = FreeControllerV3.RotationState.ParentLink;
                    Rigidbody rbb = SuperController.singleton.RigidbodyNameToRigidbody(apAtom.name + ":control");
                    stepB.containingAtom.mainController.SelectLinkToRigidbody(rbb, FreeControllerV3.SelectLinkState.PositionAndRotation);

                    UISetupState(apAtom);

                    ap.SyncStepNames();
                }, true));
            });

            selectButton = dm.ui.CreateButton("Select Animation Pattern To Control", 400, 120);
            selectButton.transform.Translate(0.52f, 0.15f, 0, Space.Self);
            selectButton.buttonColor = new Color(0.4f, 0.2f, 0.245f, 1.0f);
            selectButton.textColor   = new Color(1, 1, 1);

            selectButton.button.onClick.AddListener(() =>
            {
                SuperController.singleton.SelectModeAtom((atom) =>
                {
                    if (atom == null)
                    {
                        return;
                    }

                    if (atom.GetStorableByID("AnimationPattern") == null)
                    {
                        SuperController.LogError("Select an Animation Pattern");
                        return;
                    }

                    AnimationPattern ap = atom.GetStorableByID("AnimationPattern") as AnimationPattern;
                    ap.SyncStepNames();
                    ap.autoSyncStepNamesJSON.SetVal(false);

                    thrustAtomChooser.SetVal(atom.uid);
                });
            });

            dm.CreateSpacer();
        }