Question Hair part reversed

mspeck01

Well-known member
Messages
183
Reactions
663
Points
93
I'm trying to make an accurate look and most of the hair I like have the part on the wrong side.
Is there an easy setting to reverse the hair so the part is on the other side?
Sorry if this is a stupid question. I searched but couldn't find anything on this.
 
Sorry to say there is no way to do that with an existing style. It's been on the wanted list for many people for a very long time.
 
Upvote 0
The question gave me a rough idea for a Plugin:
  • create Plugin for person Atom, just add "flip hair" button
  • Button has to find all hair objects attached to this person (assumes it is a VAM style hair and not CUA based)
  • iterate over hair objects
  • invert local x axis scaling from 1 to -1
That would switch left to right and right to left. In the Unity Editor with an example palm tree it worked.

10000€ please and I'll do it :ROFLMAO:
 
Upvote 0
I'm still waiting for an easy way to convert clothing from female to male and vice versa. But that's a whole other kettle of fish...
 
Upvote 0
I have tried and failed to make the Plugin idea reality.
EDIT:
Months later ... Use localScale = new Vector3() instead of Set()! That does 'something' with the hair.

Unfortunately Unity / VAM ignores all attempts to alter the local scale.
It's always 1.0f. I don't know the reason, but it could be related to this:

https://answers.unity.com/questions/1220751/localscale-not-working-1.html
Unity intentionally doesn't let you change the value of a property you are animating.
What is happening here is you're changing the local scale value of your object, but since you're in play mode and the object is animated, it will be overwritten back to the animated value.

Here is a code snippet in case somebody wants to try:
C#:
Atom _person;    
public override void Init()
{
    try {
        _person = SuperController.singleton.GetSelectedAtom();
        if (_person.category != "People")
        {
            SuperController.LogError("SallyHairInvert must be on a Person atom.");
            return;
        }

        List<DAZHairGroup> DAZHairGroups = new List<DAZHairGroup>();
        foreach (DAZHairGroup hairGroup in _person.GetComponentsInChildren<DAZHairGroup>())
        {
            SuperController.LogMessage("SallyHairInvert DAZHairGroup: " + hairGroup.name + "  scale.x: " + hairGroup.transform.localScale.x);
            Vector3 scale = hairGroup.transform.localScale;
            //scale.x *= -1f;
            scale = new Vector3(2f, 1f, 1f);
            hairGroup.transform.localScale.Set(scale.x, scale.y, scale.z);
            SuperController.LogMessage("inverted scale.x: " + hairGroup.transform.localScale.x);
        }

        JSONStorableBool jbool = new JSONStorableBool("INVERT HAIR", false);
        UIDynamicToggle dToggleInvertHair = CreateToggle(jbool, false);
        //jbool.setCallbackFunction += ToggleInvertHair;
    }
    catch (Exception e) {SuperController.LogError("Exception caught: " + e);}
}
 
Last edited:
Upvote 0
I have tried and failed to make the Plugin idea reality.
EDIT:
Months later ... Use localScale = new Vector3() instead of Set()! That does 'something' with the hair.

Unfortunately Unity / VAM ignores all attempts to alter the local scale.
It's always 1.0f. I don't know the reason, but it could be related to this:

https://answers.unity.com/questions/1220751/localscale-not-working-1.html


Here is a code snippet in case somebody wants to try:
C#:
Atom _person;   
public override void Init()
{
    try {
        _person = SuperController.singleton.GetSelectedAtom();
        if (_person.category != "People")
        {
            SuperController.LogError("SallyHairInvert must be on a Person atom.");
            return;
        }

        List<DAZHairGroup> DAZHairGroups = new List<DAZHairGroup>();
        foreach (DAZHairGroup hairGroup in _person.GetComponentsInChildren<DAZHairGroup>())
        {
            SuperController.LogMessage("SallyHairInvert DAZHairGroup: " + hairGroup.name + "  scale.x: " + hairGroup.transform.localScale.x);
            Vector3 scale = hairGroup.transform.localScale;
            //scale.x *= -1f;
            scale = new Vector3(2f, 1f, 1f);
            hairGroup.transform.localScale.Set(scale.x, scale.y, scale.z);
            SuperController.LogMessage("inverted scale.x: " + hairGroup.transform.localScale.x);
        }

        JSONStorableBool jbool = new JSONStorableBool("INVERT HAIR", false);
        UIDynamicToggle dToggleInvertHair = CreateToggle(jbool, false);
        //jbool.setCallbackFunction += ToggleInvertHair;
    }
    catch (Exception e) {SuperController.LogError("Exception caught: " + e);}
}
did you guys ever get anywhere with this?

It seems like a BASIC feature that Meshed should have added to the main feature set.
There are so many hairstyles that might just want flipped
 
Upvote 0
To anyone searching and finding this thread in the future:

The key is to recreate the '.vab' file of the hairstyle, but with all the X values of the vertices inverted. Unfortunately '.vab' file is stored in binary, so it can't be done with a text editor (also you'd have to edit ten of thousands of vertices).

Looking at VaM decompiled code, The vertices are stored in the class RuntimeHairGeometryCreator in RuntimeHairGeometryCreator.cs, but the class that creates the '.vab' with those values is DAZDynamic in DAZDynamic.cs . Look for the string ".vab" for the piece of code that saves binary file.

I coded something quick to flip one specific hairstyle, it looked like this:


C#:
        static void Main(string[] args)
        {
            string inPath = @"path\to\original.vab";
            string outPath = @"path\to\changed.vab";

            using (FileStream inStream = new FileStream(inPath, FileMode.Open, FileAccess.Read))
            using (FileStream outStream = new FileStream(outPath, FileMode.OpenOrCreate, FileAccess.Write))
            using (BinaryReader binReader = new BinaryReader(inStream))
            using (BinaryWriter writer = new BinaryWriter(outStream))
            {
                string someString;
                bool someBoolean;
                int someInt32;
                Single someSingle;

                someString = binReader.ReadString();
                writer.Write(someString);
                Debug.Assert(someString == "DynamicStore");

                someString = binReader.ReadString();
                writer.Write(someString);
                Debug.Assert(someString == "1.0");

                someBoolean = binReader.ReadBoolean();
                writer.Write(someBoolean);
                Debug.Assert(someBoolean);

                someString = binReader.ReadString();
                writer.Write(someString);
                Debug.Assert(someString == "RuntimeHairGeometryCreator");

                someString = binReader.ReadString();
                writer.Write(someString);
                Debug.Assert(someString == "1.1");

                string scalpProviderName = binReader.ReadString();
                writer.Write(scalpProviderName);
                bool flag1 = true;

                Int32 segments = binReader.ReadInt32();
                writer.Write(segments);

                Single segmentLength = binReader.ReadSingle();
                writer.Write(segmentLength);

                // strandmask
                {
                    someString = binReader.ReadString();
                    writer.Write(someString);

                    int length = binReader.ReadInt32();
                    writer.Write(length);

                    for (int index = 0; index < length; ++index)
                    {
                        someBoolean = binReader.ReadBoolean();
                        writer.Write(someBoolean);
                    }
                }

                int num1 = binReader.ReadInt32(); // vertexCount
                writer.Write(num1);

                for (int vertexIndex = 0; vertexIndex < num1; ++vertexIndex)
                {
                    Int32 scalpIndex = binReader.ReadInt32();
                    writer.Write(scalpIndex);

                    int num = binReader.ReadInt32();
                    writer.Write(num);

                    for (int index2 = 0; index2 < num; ++index2)
                    {
                        Single x = binReader.ReadSingle();
                        writer.Write(-x); // MAGIC HAPPENS HERE.

                        Single y = binReader.ReadSingle();
                        writer.Write(y);

                        Single z = binReader.ReadSingle();
                        writer.Write(z);
                    }
                }


                int length1 = binReader.ReadInt32();
                writer.Write(length1);

                for (int index = 0; index < length1; ++index)
                {
                    someInt32 = binReader.ReadInt32();
                    writer.Write(someInt32);
                }

                int num3 = binReader.ReadInt32();
                writer.Write(num3);

                for (int index = 0; index < num3; ++index)
                {
                    Single x = binReader.ReadSingle();
                    writer.Write(-x); // ALSO MAGIC.

                    Single y = binReader.ReadSingle();
                    writer.Write(y);

                    Single z = binReader.ReadSingle();
                    writer.Write(z);
                }

                if (flag1)
                {
                    int num4 = binReader.ReadInt32();
                    writer.Write(num4);

                    for (int index = 0; index < num4; ++index)
                    {
                        someSingle = binReader.ReadSingle();
                        writer.Write(someSingle);
                    }
                }
                    
                int length2 = binReader.ReadInt32();
                writer.Write(length2);

                for (int index = 0; index < length2; ++index)
                {
                    someInt32 = binReader.ReadInt32();
                    writer.Write(someInt32);
                }

                int num5 = binReader.ReadInt32();
                writer.Write(num5);

                for (int index1 = 0; index1 < num5; ++index1)
                {
                    int num6 = binReader.ReadInt32();
                    writer.Write(num6);

                    for (int index2 = 0; index2 < num6; ++index2)
                    {
                        Single x = binReader.ReadSingle();
                        writer.Write(-x); // Not sure about MAGIC here, fingers crossed!

                        Single y = binReader.ReadSingle();
                        writer.Write(y);

                        Single z = binReader.ReadSingle();
                        writer.Write(z);

                        Single w = binReader.ReadSingle();
                        writer.Write(w);
                    }
                }

                while (binReader.BaseStream.Position != binReader.BaseStream.Length) // This can't be right? Urgh.
                {
                    byte someByte = binReader.ReadByte();
                    writer.Write(someByte);
                }
            }

        }
 
Upvote 0
I tried it with a morph ages ago. I made a morph that inverted the characters x scale to flip hair and clothing to the other side. I admit, there may be some minor deformation, but other than that it works perfectly.

1721680406344.png
 
Upvote 0
Back
Top Bottom