• Hi Guest!

    We are extremely excited to announce the release of our first Beta1.1 and the first release of our Public AddonKit!
    To participate in the Beta, a subscription to the Entertainer or Creator Tier is required. For access to the Public AddonKit you must be a Creator tier member. Once subscribed, download instructions can be found here.

    Click here for information and guides regarding the VaM2 beta. Join our Discord server for more announcements and community discussion about VaM2.
  • Hi Guest!

    VaM2 Resource Categories have now been added to the Hub! For information on posting VaM2 resources and details about VaM2 related changes to our Community Forums, please see our official announcement here.
Auto Aligner

Plugins + Scripts Auto Aligner

Download [<1 MB]
C#:
// AutoAligner.cs
using UnityEngine;
using System.Collections.Generic;
using System.Linq;

namespace Stopper {
    public class AutoAligner : MVRScript {
        JSONStorableStringChooser personChooser;
        UIDynamicPopup personPopup;

        JSONStorableStringChooser boneChooser;
        UIDynamicPopup bonePopup;

        Atom selectedPerson;
        Transform targetBone;
        UIDynamicTextField bonePathLabel;

        Dictionary<string, string> labeledBonePaths = new Dictionary<string, string>();

        JSONStorableFloat oscSpeedJSON;
        JSONStorableFloat oscAmplitudeJSON;

        JSONStorableFloat moveXJSON;
        JSONStorableFloat moveYJSON;
        JSONStorableFloat moveZJSON;

        JSONStorableFloat rotXJSON;
        JSONStorableFloat rotYJSON;
        JSONStorableFloat rotZJSON;

        JSONStorableBool alignPositionJSON;
        JSONStorableBool alignRotationJSON;

        JSONStorableBool oscMoveXJSON;
        JSONStorableBool oscMoveYJSON;
        JSONStorableBool oscMoveZJSON;

        JSONStorableBool oscRotXJSON;
        JSONStorableBool oscRotYJSON;
        JSONStorableBool oscRotZJSON;

        float oscTime;

        public override void Init() {
            List<string> personChoices = SuperController.singleton.GetAtoms()
                .Where(a => a.type == "Person")
                .Select(a => a.uid)
                .ToList();

            personChooser = new JSONStorableStringChooser("person", personChoices, null, "Target Person", (uid) => {
                selectedPerson = SuperController.singleton.GetAtomByUid(uid);
                UpdateBoneList();
            });
            RegisterStringChooser(personChooser);
            personPopup = CreateFilterablePopup(personChooser);

            boneChooser = new JSONStorableStringChooser("bone", new List<string>(), null, "Target Bone", (path) => {
                if (labeledBonePaths.ContainsKey(path)) {
                    targetBone = FindBoneByPath(selectedPerson?.transform, labeledBonePaths[path]);
                } else {
                    targetBone = FindBoneByPath(selectedPerson?.transform, path);
                }
                if (bonePathLabel != null) bonePathLabel.text = labeledBonePaths.ContainsKey(path) ? labeledBonePaths[path] : path;
            });
            RegisterStringChooser(boneChooser);
            bonePopup = CreateFilterablePopup(boneChooser);

            bonePathLabel = CreateTextField(new JSONStorableString("bonePathLabel", ""), false);
            bonePathLabel.height = 30f;

            alignPositionJSON = new JSONStorableBool("Align Position", true);
            RegisterBool(alignPositionJSON);
            CreateToggle(alignPositionJSON);

            alignRotationJSON = new JSONStorableBool("Align Rotation", true);
            RegisterBool(alignRotationJSON);
            CreateToggle(alignRotationJSON);

            oscMoveXJSON = new JSONStorableBool("Oscillate Position X", false);
            RegisterBool(oscMoveXJSON);
            CreateToggle(oscMoveXJSON);

            oscMoveYJSON = new JSONStorableBool("Oscillate Position Y", false);
            RegisterBool(oscMoveYJSON);
            CreateToggle(oscMoveYJSON);

            oscMoveZJSON = new JSONStorableBool("Oscillate Position Z", false);
            RegisterBool(oscMoveZJSON);
            CreateToggle(oscMoveZJSON);

            oscRotXJSON = new JSONStorableBool("Oscillate Rotation X", false);
            RegisterBool(oscRotXJSON);
            CreateToggle(oscRotXJSON);

            oscRotYJSON = new JSONStorableBool("Oscillate Rotation Y", false);
            RegisterBool(oscRotYJSON);
            CreateToggle(oscRotYJSON);

            oscRotZJSON = new JSONStorableBool("Oscillate Rotation Z", false);
            RegisterBool(oscRotZJSON);
            CreateToggle(oscRotZJSON);

            oscSpeedJSON = new JSONStorableFloat("Oscillation Speed", 1f, 0.1f, 30f);
            RegisterFloat(oscSpeedJSON);
            CreateSlider(oscSpeedJSON);

            oscAmplitudeJSON = new JSONStorableFloat("Oscillation Amplitude", 0.1f, 0f, 1f);
            RegisterFloat(oscAmplitudeJSON);
            CreateSlider(oscAmplitudeJSON);

            moveXJSON = new JSONStorableFloat("Position Offset X", 0f, -1f, 1f);
            RegisterFloat(moveXJSON);
            CreateSlider(moveXJSON);

            moveYJSON = new JSONStorableFloat("Position Offset Y", 0f, -1f, 1f);
            RegisterFloat(moveYJSON);
            CreateSlider(moveYJSON);

            moveZJSON = new JSONStorableFloat("Position Offset Z", 0f, -1f, 1f);
            RegisterFloat(moveZJSON);
            CreateSlider(moveZJSON);

            rotXJSON = new JSONStorableFloat("Rotation Offset X", 0f, -180f, 180f);
            RegisterFloat(rotXJSON);
            CreateSlider(rotXJSON);

            rotYJSON = new JSONStorableFloat("Rotation Offset Y", 0f, -180f, 180f);
            RegisterFloat(rotYJSON);
            CreateSlider(rotYJSON);

            rotZJSON = new JSONStorableFloat("Rotation Offset Z", 0f, -180f, 180f);
            RegisterFloat(rotZJSON);
            CreateSlider(rotZJSON);
        }

        void UpdateBoneList() {
            if (selectedPerson == null) return;

            labeledBonePaths.Clear();

            var allTransforms = selectedPerson.GetComponentsInChildren<Transform>(true);
            foreach (var t in allTransforms) {
                string path = GetRelativePath(t, selectedPerson.transform);

                if (path.EndsWith("mouthPhysicsMeshPredictionPoint")) {
                    labeledBonePaths["Mouth"] = path;
                } else if (path.EndsWith("LabiaTrigger")) {
                    labeledBonePaths["Genitals"] = path;
                } else if (path.EndsWith("_JointAl/Debug")) {
                    labeledBonePaths["Anus"] = path;
                }
            }

            boneChooser.choices = labeledBonePaths.Keys.ToList();
            if (boneChooser.choices.Count > 0) {
                boneChooser.val = boneChooser.choices[0];
                if (bonePathLabel != null) bonePathLabel.text = labeledBonePaths[boneChooser.choices[0]];
            }
        }

        Transform FindBoneByPath(Transform root, string path) {
            return root != null ? root.Find(path) : null;
        }

        string GetRelativePath(Transform t, Transform root) {
            List<string> names = new List<string>();
            while (t != null && t != root) {
                names.Insert(0, t.name);
                t = t.parent;
            }
            return string.Join("/", names.ToArray());
        }

        void LateUpdate() {
            if (selectedPerson == null || targetBone == null || boneChooser.val == null || !labeledBonePaths.ContainsKey(boneChooser.val)) return;

            var checkTransform = FindBoneByPath(selectedPerson.transform, labeledBonePaths[boneChooser.val]);
            if (checkTransform == null) return;

            oscTime += Time.deltaTime * oscSpeedJSON.val;
            float oscOffset = Mathf.Sin(oscTime) * oscAmplitudeJSON.val;

            Vector3 offset = Vector3.zero;
            if (oscMoveXJSON.val) offset += targetBone.right * oscOffset;
            if (oscMoveYJSON.val) offset += targetBone.up * oscOffset;
            if (oscMoveZJSON.val) offset += targetBone.forward * oscOffset;

            offset += targetBone.right * moveXJSON.val;
            offset += targetBone.up * moveYJSON.val;
            offset += targetBone.forward * moveZJSON.val;

            Vector3 newPosition = targetBone.position + offset;
            Quaternion additionalRotation = Quaternion.identity;

            if (oscRotXJSON.val) additionalRotation *= Quaternion.Euler(oscOffset, 0, 0);
            if (oscRotYJSON.val) additionalRotation *= Quaternion.Euler(0, oscOffset, 0);
            if (oscRotZJSON.val) additionalRotation *= Quaternion.Euler(0, 0, oscOffset);

            Quaternion newRotation = targetBone.rotation * additionalRotation * Quaternion.Euler(
                rotXJSON.val,
                rotYJSON.val,
                rotZJSON.val);

            if (alignPositionJSON.val) containingAtom.mainController.transform.position = newPosition;
            if (alignRotationJSON.val) containingAtom.mainController.transform.rotation = newRotation;
        }
    }
}



The code was written by ChatGPT.

Below is the list of referenced plugins

AlignHelper​



CUA Clothing​




I referenced the alignment function from AlignHelper.
I don’t have much knowledge of code, so I’m not sure how similar it is, but after dozens of attempts, ChatGPT managed to understand how it works and succeeded.
Next, I needed the position of the bone set as the receiver—this was based on the functionality of CUA Clothing.

I listed all the bones of the character and used the Collider Editor by the creator 'Acid Bubbles' to determine the most suitable bone as the target.
That part alone took around 8 hours.
ChatGPT kept giving error codes or repeated the same non-working solutions.

I didn’t realize how stressful it would be to instruct it to understand the logic behind fetching the receiver list. As for the distance and speed control, I just asked ChatGPT and it created them.

Since it's a simple logic, I guess it was easy for it to implement
This one was newly created using two plugins released under a CC BY license.

I would like to express my gratitude for the original creators' hard work and dedication, and I clearly state that I hold no copyright. Since the original authors released their work under a CC BY license, I am releasing this under a CC BY-SA license in the spirit of continuing their values.

This plugin doesn’t always function perfectly.
If a collider interaction occurs while the asset’s physics option is turned off, the character may get pushed away. In practice, enabling the physics option is almost essential.
Because of this, if the character makes a large motion or changes position, the object can slip out of the mouth, for example. Putting it back in requires the inconvenience of turning the collider and physics options off and on again.
React to this content...

Share this resource

More resources from bqbq

Latest updates

  1. The target list is now displayed correctly.

    Previously, only one 'person' was searchable in the filter, and even after deletion, it would...
Back
Top Bottom