using System; using UnityEngine; using UnityEngine.Experimental.Rendering; using System.Collections; using System.Collections.Generic; using System.Text; using SimpleJSON; using System.Linq; using MVR; using MeshVR; using MVR.FileManagementSecure; using Request = MeshVR.AssetLoader.AssetBundleFromFileRequest; using GPUTools.Cloth.Scripts; using Unity.Collections; using AssetBundles; using Leap.Unity; using System.Diagnostics; using GPUTools.Hair.Scripts.Runtime.Render; using Battlehub.RTSaveLoad.UnityEngineNS.AINS; using GPUTools.Physics.Scripts.Types.Dynamic; namespace Stopper { [DefaultExecutionOrder(1000)] public class CUAClothing : MVRScript { Transform originalParent; Atom atom; bool connected = false; bool pointerCtrlAttach = false; UIDynamicButton pointerCtrlAttachButton; private int attachCollisionLayer = 1; Atom cuaAtom; string cuaAtomUid; CUAManager cuaManager; JSONStorableFloat atomScale; DAZBone parentBone; DAZCharacterSelector selector; PhysicsMaterialControl physics; DAZCharacterRun characterRun; DAZClothingItem dazClothingItem; DAZDynamic dazDynamic; DAZMesh dazMesh; DAZSkinWrap dazSkinWrap; DAZSkinV2 skin; ClothSettings clothSettings; DAZHairGroup dazHair; HairSimControl hairSimControl; DAZBone head; int numStrands; ComputeBuffer verticesBuffer; ComputeBuffer particlesBuffer; ComputeBuffer normalsBuffer; List triangleIndexes = new List(); int minVertex, maxVertex; int[] triangles; Vector2[] uv; AsyncGPUReadbackRequest verticesRequest; AsyncGPUReadbackRequest normalsRequest; AsyncGPUReadbackRequest particlesRequest; JSONStorableUrl jAssetUrl; JSONStorableString jAssetName; JSONStorableStringChooser jAssetChooser; JSONStorableStringChooser cuaAssetChooser; JSONStorableStringChooser jConnectionTypeChooser; JSONStorableStringChooser jHairParticleTypeChooser; UIDynamicPopup assetChooserPopup; JSONStorableFloat jVertexIndex; JSONStorableFloat jScale; JSONStorableFloat jXOffset; JSONStorableFloat jYOffset; JSONStorableFloat jZOffset; JSONStorableFloat jXRotation; JSONStorableFloat jYRotation; JSONStorableFloat jZRotation; JSONStorableBool jAllowColorize; JSONStorableColor jDiffColor; JSONStorableColor jSpecColor; JSONStorableBool jZeroOnReattach; protected UIDynamicColorPicker diffColorPicker; protected UIDynamicColorPicker specColorPicker; Vector3 previousRotation; UIDynamicSlider vertexSlider; JSONStorableStringChooser parentBoneChooser; UIDynamicPopup parentBoneChooserPopup; UIDynamicPopup hairParticleTypePopup; List vertexAttachComponents = new List(); List offsetSliders = new List(); public class ConnectionType { public static ConnectionType Skin = new ConnectionType(); public static ConnectionType Clothing = new ConnectionType(); public static ConnectionType Bone = new ConnectionType(); public static ConnectionType Hair = new ConnectionType(); } ConnectionType connectionType = ConnectionType.Skin; public class HairParticleType { public static HairParticleType Physics = new HairParticleType(); public static HairParticleType Render = new HairParticleType(); } HairParticleType hairParticleType = HairParticleType.Physics; public override void Init() { WalkAndGetParentItems(); cuaAtomUid = $"{containingAtom.name}#{dazDynamic.name}#{this.name}"; // If we're loading from scene, the atom might already be present cuaAtom = SuperController.singleton.GetAtomByUid(cuaAtomUid); physics = containingAtom.GetStorableByID("PhysicsModel") as PhysicsMaterialControl; if (physics == null) { SuperController.LogError("Unable to find PhysicsModel. Is this a Person atom?"); return; } atomScale = containingAtom.GetStorableByID("rescaleObject").GetFloatJSONParam("scale"); atomScale.setCallbackFunction += UpdateScale; selector = containingAtom.GetStorableByID("geometry") as DAZCharacterSelector; if (selector == null) { SuperController.LogError("Unable to find character selector. Is this a Person atom?"); return; } characterRun = selector.GetComponent(); head = containingAtom.GetStorableByID("head") as DAZBone; var selectFileButton = CreateButton("Select File", false); var clearButton = CreateButton("Clear CUA", true); jAssetUrl = new JSONStorableUrl("assetUrl", string.Empty, new JSONStorableString.SetStringCallback(this.SyncAssetUrl), "assetbundle|scene", "Custom\\Assets"); jAssetUrl.allowFullComputerBrowse = true; jAssetUrl.allowBrowseAboveSuggestedPath = true; jAssetUrl.beginBrowseWithObjectCallback = new JSONStorableUrl.BeginBrowseWithObjectCallback(this.BeginBrowse); jAssetUrl.suggestedPathGroup = "UnityAsset"; jAssetUrl.fileBrowseButton = selectFileButton.button; jAssetUrl.clearButton = clearButton.button; RegisterUrl(this.jAssetUrl); jAssetName = new JSONStorableString("assetName", null); RegisterString(jAssetName); jAssetName.setCallbackFunction = (name) => { if (cuaAssetChooser != null) { cuaAssetChooser.val = name; } }; jAssetChooser = new JSONStorableStringChooser("Asset", new List(), null, "Asset", (string asset) => { jAssetName.val = asset; }); jConnectionTypeChooser = new JSONStorableStringChooser( "ConnectionType", GetConnectionTypes(), "None", "Connection Type", (string type) => { if (type == "Skin") { connectionType = ConnectionType.Skin; skin = characterRun.skin; StartCoroutine(ConnectSkin()); UIVertexAttachMode(); } else if (type == "Clothing") { connectionType = ConnectionType.Clothing; UIVertexAttachMode(); StartCoroutine(ConnectClothing()); } else if (type == "Bone") { connectionType = ConnectionType.Bone; connected = true; UIBoneAttachMode(); } else if (type == "Hair") { connectionType = ConnectionType.Hair; UIVertexAttachMode(true); StartCoroutine(ConnectHair()); } else { UINoMode(); Disconnect(); } }); RegisterStringChooser(jConnectionTypeChooser); parentBoneChooser = new JSONStorableStringChooser( "Parent Bone", GetBones(), null, "Parent Bone", (string boneName) => { Disconnect(); parentBone = physics.GetComponentsInChildren(true).First((bone) => bone.name == boneName); connected = true; } ); RegisterStringChooser(parentBoneChooser); jHairParticleTypeChooser = new JSONStorableStringChooser( "HairParticleType", new List() { "Physics", "Render" }, "Physics", "Hair Particle Type", (string type) => { if (type == "Physics") { hairParticleType = HairParticleType.Physics; } else if (type == "Render") { hairParticleType = HairParticleType.Render; } StartCoroutine(ConnectHair()); }); RegisterStringChooser(jHairParticleTypeChooser); jVertexIndex = new JSONStorableFloat("Vertex Index", 0, 0, 0, false, true); jVertexIndex.storeType = JSONStorableParam.StoreType.Full; jVertexIndex.setCallbackFunction = (val) => { if (val > jVertexIndex.max || val < 0) { return; } minVertex = (int)jVertexIndex.max; maxVertex = 0; if (triangles != null) { triangleIndexes.Clear(); for (int i = 0; i < triangles.Length; i += 3) { for (int j = 0; j < 3; j++) { if (triangles[i + j] == (int)val) { if (!triangleIndexes.Contains(i)) { triangleIndexes.Add(i); } for (int k = 0; k < 3; k++) { minVertex = Math.Min(minVertex, triangles[i + k]); maxVertex = Math.Max(maxVertex, triangles[i + k]); } } } } if (minVertex >= maxVertex) { minVertex = 0; maxVertex = (int)jVertexIndex.max; } } else { minVertex = 0; maxVertex = (int)jVertexIndex.max; } }; RegisterFloat(jVertexIndex); jZeroOnReattach = new JSONStorableBool("zeroLocalOnReattach", false); attachCollisionLayer |= 1; // Base attachCollisionLayer |= 1 << 12; // Tongue attachCollisionLayer |= 1 << 14; // PhysicsMesh1 attachCollisionLayer |= 1 << 15; // PhysicsMesh2 attachCollisionLayer |= 1 << 26; // Character attachCollisionLayer |= 1 << 29; // CharacterExtremities // attachCollisionLayer |= 1<<28; // Hair jXOffset = new JSONStorableFloat("xOffset", 0, -0.1f, 0.1f, false); jXOffset.setCallbackFunction = (val) => SyncLocalPosition(); RegisterFloat(jXOffset); jYOffset = new JSONStorableFloat("yOffset", 0, -0.1f, 0.1f, false); jYOffset.setCallbackFunction = (val) => SyncLocalPosition(); RegisterFloat(jYOffset); jZOffset = new JSONStorableFloat("zOffset", 0, -0.1f, 0.1f, false); jZOffset.setCallbackFunction = (val) => SyncLocalPosition(); RegisterFloat(jZOffset); jXRotation = new JSONStorableFloat("xRotation", 0, 0, 360, true); jXRotation.setCallbackFunction = (val) => SyncLocalPosition(); RegisterFloat(jXRotation); jYRotation = new JSONStorableFloat("yRotation", 0, 0, 360, true); jYRotation.setCallbackFunction = (val) => SyncLocalPosition(); RegisterFloat(jYRotation); jZRotation = new JSONStorableFloat("zRotation", 0, 0, 360, true); jZRotation.setCallbackFunction = (val) => SyncLocalPosition(); RegisterFloat(jZRotation); jScale = new JSONStorableFloat("Scale", 1, 0.1f, 2, false); jScale.setCallbackFunction = UpdateScale; RegisterFloat(jScale); jAllowColorize = new JSONStorableBool("Colorize", false); RegisterBool(jAllowColorize); HSVColor whiteHSVC = HSVColorPicker.RGBToHSV(1f, 1f, 1f); jDiffColor = new JSONStorableColor("Diffuse Color", whiteHSVC, (JSONStorableColor color) => { cuaManager?.diffuse.SetColor(color.colorPicker.currentColor); }); RegisterColor(jDiffColor); jSpecColor = new JSONStorableColor("Specular Color", whiteHSVC, (JSONStorableColor color) => { cuaManager?.specular.SetColor(color.colorPicker.currentColor); }); RegisterColor(jSpecColor); } public override void InitUI() { base.InitUI(); if (UITransform == null) { // InitUI gets called twice: once in Awake when the MVRScript component is created, and again after MVRPluginManager assigns UITransform return; } // We do this here, as we can't disable UI components in Init(), because // InitUI() runs after it and re-activates all of them... var selectCuaButton = CreateButton("Select CUA", true); selectCuaButton.button.onClick.AddListener(() => { if (cuaAtom != null) SuperController.singleton.SelectController(cuaAtom.mainController); }); assetChooserPopup = CreateFilterablePopup(jAssetChooser, false); assetChooserPopup.gameObject.SetActive(false); CreateScrollablePopup(jConnectionTypeChooser, false); parentBoneChooserPopup = CreateFilterablePopup(parentBoneChooser, false); parentBoneChooserPopup.popup.onOpenPopupHandlers += () => { parentBoneChooser.choices = GetBones(); }; hairParticleTypePopup = CreateScrollablePopup(jHairParticleTypeChooser, false); vertexAttachComponents.Add(hairParticleTypePopup); vertexSlider = CreateSlider(jVertexIndex, false); vertexSlider.rangeAdjustEnabled = false; vertexSlider.slider.wholeNumbers = true; vertexSlider.valueFormat = "F0"; vertexAttachComponents.Add(vertexSlider); var zeroOnReattachToggle = CreateToggle(jZeroOnReattach, false); zeroOnReattachToggle.label = "Zero Offset On Reattach"; vertexAttachComponents.Add(zeroOnReattachToggle); var attachToClosest = CreateButton("Re-attach To Closest", false); attachToClosest.button.onClick.AddListener(() => AttachToClosest()); vertexAttachComponents.Add(attachToClosest); pointerCtrlAttachButton = CreateButton("Point & Ctrl To Attach", false); pointerCtrlAttachButton.button.onClick.AddListener(() => TogglePointerCtrlAttach()); vertexAttachComponents.Add(pointerCtrlAttachButton); offsetSliders.Add(CreateF3Slider(jXOffset, false)); offsetSliders.Add(CreateF3Slider(jYOffset, false)); offsetSliders.Add(CreateF3Slider(jZOffset, false)); var rotationWarning = CreateTextField(new JSONStorableString("rotationWarning", "Note: modifying xRotation is unstable between 90-270 degrees."), false); rotationWarning.height = 10; offsetSliders.Add(rotationWarning); offsetSliders.Add(CreateSlider(jXRotation, false)); offsetSliders.Add(CreateSlider(jYRotation, false)); offsetSliders.Add(CreateSlider(jZRotation, false)); CreateSlider(jScale, true); UIDynamicColorPicker diffusePicker = null; UIDynamicColorPicker specularPicker = null; var allowColorizeButton = CreateToggle(jAllowColorize, true); allowColorizeButton.toggle.onValueChanged.AddListener(delegate (bool state) { if (cuaManager != null) { cuaManager.diffuse.enabled = state; cuaManager.specular.enabled = state; } diffusePicker?.gameObject.SetActive(state); specularPicker?.gameObject.SetActive(jAllowColorize.val); }); diffusePicker = CreateColorPicker(jDiffColor, true); diffusePicker.gameObject.SetActive(jAllowColorize.val); specularPicker = CreateColorPicker(jSpecColor, true); specularPicker.gameObject.SetActive(jAllowColorize.val); var pluginInfo = CreateTextField(new JSONStorableString("pluginInfo", "This is Stopper's CUAClothing plugin modified by qdaro. Changes:\n\nAdded automatic asset material vamification so it receives shadows and reflects current VaM environment map.\n\nAdded colorizing of materials' diffuse & specular colors.\n\nAdded Scale slider with custom handling for CUAs with joints in them.\n\nAdded \"Select CUA\" button.\n\nAdded \"Point & Ctrl to attach\" toggle button (hover over target point with cursor and press Ctrl to attach to it, don't forget to confirm).\n\nFixed CUA position de-sync when parenting to a bone.\n\nFixed CUA position offsets & scale ignoring person atom scale.\n\nChanged x/y/zOffset precisions to 0.001 so that we have usable offset values.\n\nChanged \"Vertex Index\" slider format to integer so that we have usable increment buttons.\n\nDisabling clothing item removes CUA from scene to not pollute people's scenes with hundreds of atoms when trying out clothes.\n\nAnd some other small UI tweaks and fixes."), true); pluginInfo.height = 400; jConnectionTypeChooser.setCallbackFunction.Invoke(jConnectionTypeChooser.val); } public UIDynamicSlider CreateF3Slider(JSONStorableFloat js, bool constrained = true) { var slider = CreateSlider(js, constrained); slider.valueFormat = "F3"; return slider; } public void TogglePointerCtrlAttach() { TogglePointerCtrlAttach(!pointerCtrlAttach); } public void TogglePointerCtrlAttach(bool state) { pointerCtrlAttach = state; pointerCtrlAttachButton.buttonText.text = pointerCtrlAttach ? "Confirm" : "Point & Ctrl To Attach"; pointerCtrlAttachButton.buttonColor = pointerCtrlAttach ? new Color(0.60f, 1f, 0.70f) : new Color(0.8f, 0.8f, 0.8f); } private Vector3? GetHoveredPoint() { RaycastHit hit; Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out hit, 100f, attachCollisionLayer)) { return hit.point; } return null; } private void UpdateScale(float val) { UpdateScale(); } private void UpdateScale() { if (cuaManager != null) { var scale = jScale.val * atomScale.val; cuaManager.Scale(new Vector3(scale, scale, scale)); } SyncLocalPosition(); } public void Start() { } private void UINoMode() { foreach (var uiDynamic in vertexAttachComponents) { uiDynamic.gameObject.SetActive(false); } foreach (var uiDynamic in offsetSliders) { uiDynamic.gameObject.SetActive(false); } parentBoneChooserPopup.gameObject.SetActive(false); } private void UIVertexAttachMode(bool hair = false) { UINoMode(); foreach (var uiDynamic in vertexAttachComponents) { uiDynamic.gameObject.SetActive(true); } foreach (var uiDynamic in offsetSliders) { uiDynamic.gameObject.SetActive(true); } hairParticleTypePopup.gameObject.SetActive(hair); } private void UIBoneAttachMode() { UINoMode(); foreach (var uiDynamic in offsetSliders) { uiDynamic.gameObject.SetActive(true); } parentBoneChooserPopup.gameObject.SetActive(true); } private void WalkAndGetParentItems() { GameObject parent = this.gameObject; while (containingAtom == null || parent != containingAtom.gameObject) { if (parent.transform.parent == null) { return; } if (dazClothingItem == null) { dazClothingItem = parent.GetComponent(); } if (dazHair == null) { dazHair = parent.GetComponent(); } if (dazMesh == null) { dazMesh = parent.GetComponent(); } if (dazDynamic == null) { dazDynamic = parent.GetComponent(); } if (dazSkinWrap == null) { dazSkinWrap = parent.GetComponent(); } containingAtom = parent.GetComponent(); parent = parent.transform.parent.gameObject; } } private List GetConnectionTypes() { List connectionTypes = new List() { "None", "Skin", "Bone" }; if (dazClothingItem != null) { connectionTypes.Add("Clothing"); } if (dazHair != null) { connectionTypes.Add("Hair"); } return connectionTypes; } private List GetBones() { List boneNames = new List(); foreach (var bone in physics.GetComponentsInChildren(true)) { boneNames.Add(bone.name); } return boneNames; } protected void BeginBrowse(JSONStorableUrl jsurl) { List shortCutsForDirectory = FileManagerSecure.GetShortCutsForDirectory("Custom/Assets", true, true, true, true); jsurl.shortCuts = shortCutsForDirectory; } private IEnumerator CreateCUA(string url) { if (cuaAtom == null) { GameObject cuaPrefab = SuperController.singleton.GetCachedPrefab("a_mis", "CustomUnityAsset"); if (cuaPrefab == null) { AssetBundleLoadAssetOperation request = AssetBundleManager.LoadAssetAsync("a_mis", "CustomUnityAsset", typeof(GameObject)); if (request == null) { SuperController.LogError("Unable to load CUA prefab"); yield break; } yield return base.StartCoroutine(request); cuaPrefab = request.GetAsset(); if (cuaPrefab != null) { SuperController.singleton.RegisterPrefab("a_mis", "CustomUnityAsset", cuaPrefab); } else { SuperController.LogError("Unable to load CUA prefab"); yield break; } } var cua = Instantiate(cuaPrefab); cuaAtom = cua.GetComponent(); atom = containingAtom; SuperController.singleton.AddAtom(cuaAtom, cuaAtomUid, false, false, false, false); } if (cuaManager == null) { cuaManager = cuaAtom.gameObject.AddComponent(); cuaManager.diffuse.enabled = jAllowColorize.val; cuaManager.diffuse.SetColor(jDiffColor.colorPicker.currentColor); cuaManager.specular.enabled = jAllowColorize.val; cuaManager.specular.SetColor(jSpecColor.colorPicker.currentColor); cuaManager.Scale(new Vector3(jScale.val, jScale.val, jScale.val)); } cuaAtom.hidden = true; SyncLocalPosition(); CustomUnityAssetLoader cuaLoader = cuaAtom.reParentObject.GetComponentInChildren(); var urlJson = cuaLoader.GetUrlJSONParam("assetUrl"); urlJson.val = url; cuaAssetChooser = cuaLoader.GetStringChooserJSONParam("assetName"); yield return new WaitUntil(() => cuaAssetChooser.choices.Count > 1); assetChooserPopup.gameObject.SetActive(true); jAssetChooser.choices = new List(cuaAssetChooser.choices); if (jAssetName.val != null) { jAssetChooser.val = jAssetName.val; jAssetName.setCallbackFunction(jAssetName.val); } } protected void SyncAssetUrl(string url) { StartCoroutine(CreateCUA(url)); } private List GetAtomUids() { List atomUids = new List() { "None" }; atomUids.AddRange(SuperController.singleton.GetAtoms().Where(a => a.type == "Person").Select(a => a.uid)); return atomUids; } private List GetItemUids() { List itemUids = new List() { "None" }; if (atom != null) { if (connectionType == ConnectionType.Clothing) { itemUids.AddRange(atom.GetComponentsInChildren().Select(c => c.uid).ToList()); } else if (connectionType == ConnectionType.Skin) { itemUids.Add("Skin"); } } return itemUids; } private void Disconnect() { connected = false; verticesBuffer = null; normalsBuffer = null; triangles = null; uv = null; } private IEnumerator ConnectSkin() { Disconnect(); yield return SwapSkin(); connected = true; yield break; } private IEnumerator SwapSkin() { skin = characterRun.skin; if (!skin.isActiveAndEnabled) { yield return new WaitUntil(() => skin.isActiveAndEnabled); } yield return null; SyncBuffers(); var dazMesh = skin.dazMesh; triangles = dazMesh.UVTriangles; uv = dazMesh.UV; jVertexIndex.max = verticesBuffer.count - 1; jVertexIndex.setCallbackFunction(jVertexIndex.val); } private IEnumerator ConnectClothing() { Disconnect(); if (!dazClothingItem.enabled && dazClothingItem.ready) yield return new WaitUntil(() => dazClothingItem.enabled && dazClothingItem.ready); dazSkinWrap = dazClothingItem.GetComponentInChildren(); if (dazSkinWrap == null || !dazSkinWrap.enabled) { yield return new WaitUntil(() => { dazSkinWrap = dazClothingItem.GetComponentInChildren(); return dazSkinWrap != null && dazSkinWrap.enabled; }); } yield return null; clothSettings = dazClothingItem.GetComponentInChildren(); SyncBuffers(); var dazMesh = dazSkinWrap.dazMesh; triangles = dazMesh.UVTriangles; uv = dazMesh.UV; jVertexIndex.max = verticesBuffer.count - 1; jVertexIndex.setCallbackFunction(jVertexIndex.val); connected = true; yield break; } private IEnumerator ConnectHair() { Disconnect(); if (!dazHair.enabled) yield return new WaitUntil(() => dazHair.enabled); hairSimControl = dazHair.GetComponentInChildren(); if (hairSimControl == null || !hairSimControl.enabled) { yield return new WaitUntil(() => { hairSimControl = dazHair.GetComponentInChildren(); return hairSimControl != null && hairSimControl.enabled; }); } yield return null; SyncBuffers(); jVertexIndex.max = particlesBuffer.count - 2; jVertexIndex.setCallbackFunction(jVertexIndex.val); connected = true; yield break; } private void AttachToClosest() { if (cuaAtom != null) AttachToClosest(cuaAtom.mainController.transform.position); } private void AttachToClosest(Vector3 position) { if (!connected) { return; } float closestDistance = float.MaxValue; int closestVertex = 0; if (connectionType == ConnectionType.Hair) { if (hairParticleType == HairParticleType.Physics) { GPParticle[] particles = new GPParticle[particlesBuffer.count]; particlesBuffer.GetData(particles); for (int i = 0; i < particles.Length; i++) { float sqrDistance = (position - particles[i].Position).sqrMagnitude; if (sqrDistance < closestDistance) { closestVertex = i; closestDistance = sqrDistance; } } } else if (hairParticleType == HairParticleType.Render) { TessRenderParticle[] particles = new TessRenderParticle[particlesBuffer.count]; particlesBuffer.GetData(particles); for (int i = 0; i < particles.Length; i++) { float sqrDistance = (position - particles[i].Position).sqrMagnitude; if (sqrDistance < closestDistance) { closestVertex = i; closestDistance = sqrDistance; } } } } else { Vector3[] vertices = new Vector3[verticesBuffer.count]; verticesBuffer.GetData(vertices); for (int i = 0; i < vertices.Length; i++) { float sqrDistance = (position - vertices[i]).sqrMagnitude; if (sqrDistance < closestDistance) { closestVertex = i; closestDistance = sqrDistance; } } } jVertexIndex.val = closestVertex; if (jZeroOnReattach.val) { jXRotation.valNoCallback = 0; jYRotation.valNoCallback = 0; jZRotation.valNoCallback = 0; jXOffset.valNoCallback = 0; jYOffset.valNoCallback = 0; jZOffset.valNoCallback = 0; SyncLocalPosition(); } } private void SyncBuffers() { if (connectionType == ConnectionType.Clothing) { if (clothSettings != null && clothSettings.enabled) { verticesBuffer = clothSettings.Runtime.ClothOnlyVertices.ComputeBuffer; } else { verticesBuffer = dazSkinWrap.PreCalculatedVerticesBuffer.ComputeBuffer; // Need to call dispatch to copy verts into the pre-compute buffer // Perf hit should be minor dazSkinWrap.Dispatch(); } normalsBuffer = dazSkinWrap.NormalsBuffer.ComputeBuffer; } else if (connectionType == ConnectionType.Skin) { if (skin != characterRun.skin) { StartCoroutine(SwapSkin()); } if (skin.delayDisplayOneFrame) { verticesBuffer = skin.delayedVertsBuffer; } else if (skin.useSmoothing) { verticesBuffer = skin.smoothedVertsBuffer; } else { verticesBuffer = skin.rawVertsBuffer; } normalsBuffer = skin.NormalsBuffer.ComputeBuffer; } else if (connectionType == ConnectionType.Hair) { if (hairParticleType == HairParticleType.Physics) { particlesBuffer = hairSimControl.hairSettings.RuntimeData.Particles.ComputeBuffer; } else if (hairParticleType == HairParticleType.Render) { particlesBuffer = hairSimControl.hairSettings.RuntimeData.TessRenderParticles.ComputeBuffer; } numStrands = hairSimControl.hairSettings.StandsSettings.Provider.GetStandsNum(); } } private void SyncLocalPosition() { if (cuaAtom != null) { cuaAtom.mainController.transform.localPosition = new Vector3(jXOffset.val, jYOffset.val, jZOffset.val) * atomScale.val; cuaAtom.mainController.transform.localEulerAngles = new Vector3(jXRotation.val, jYRotation.val, jZRotation.val); } } private bool isConnected() { if (!connected) return false; if (cuaAtom == null || !cuaAtom.enabled) return false; if (connectionType == ConnectionType.Clothing) { if (dazSkinWrap == null) return false; if (!dazSkinWrap.enabled) return false; if (jVertexIndex.val > jVertexIndex.max || jVertexIndex.val < 0) return false; } else if (connectionType == ConnectionType.Skin) { if (skin == null) return false; if (!skin.enabled) return false; if (jVertexIndex.val > jVertexIndex.max || jVertexIndex.val < 0) return false; } else if (connectionType == ConnectionType.Hair) { if (hairSimControl == null) return false; if (!hairSimControl.enabled) return false; } return true; } UnityEngine.Pose? prevBonePose; public void LateUpdate() { if (!isConnected()) { return; } var rotation = cuaAtom.mainController.transform.localEulerAngles; if (rotation != previousRotation) { // jXRotation.valNoCallback = rotation.x; // jYRotation.valNoCallback = rotation.y; // jZRotation.valNoCallback = rotation.z; } previousRotation = rotation; // jXOffset.valNoCallback = cuaAtom.mainController.transform.localPosition.x; // jYOffset.valNoCallback = cuaAtom.mainController.transform.localPosition.y; // jZOffset.valNoCallback = cuaAtom.mainController.transform.localPosition.z; if (connectionType != ConnectionType.Bone) { SyncBuffers(); } if (connectionType == ConnectionType.Bone) { if (parentBone != null) { // cuaAtom.reParentObject.position = parentBone.transform.position; // cuaAtom.reParentObject.rotation = parentBone.transform.rotation; var thisPose = new UnityEngine.Pose(parentBone.transform.position, parentBone.transform.rotation); // Fixes de-sync between bone and cua position/rotation. // Dunno why. VamCuaSuperglue does it this way, and it seems to work. if (prevBonePose != null) { cuaAtom.reParentObject.position = ((UnityEngine.Pose)prevBonePose).position; cuaAtom.reParentObject.rotation = ((UnityEngine.Pose)prevBonePose).rotation; } prevBonePose = thisPose; } } else if (connectionType == ConnectionType.Hair) { int index = (int)jVertexIndex.val; if (particlesBuffer != null) { if (index >= particlesBuffer.count) { return; } if (hairParticleType == HairParticleType.Physics) { particlesRequest = AsyncGPUReadback.Request(particlesBuffer, GPParticle.Size() * 2, index * GPParticle.Size()); } else if (hairParticleType == HairParticleType.Render) { particlesRequest = AsyncGPUReadback.Request(particlesBuffer, TessRenderParticle.Size() * 2, index * TessRenderParticle.Size()); } } else { return; } if (!particlesRequest.done) { particlesRequest.WaitForCompletion(); } if (particlesRequest.hasError) { return; } var particlePosition = Vector3.zero; var nextParticlePosition = Vector3.zero; if (hairParticleType == HairParticleType.Physics) { var particles = particlesRequest.GetData(); particlePosition = particles[0].Position; nextParticlePosition = particles[1].Position; } else if (hairParticleType == HairParticleType.Render) { var particles = particlesRequest.GetData(); particlePosition = particles[0].Position; nextParticlePosition = particles[1].Position; } if (particlePosition.ContainsNaN() || nextParticlePosition.ContainsNaN()) { Log("nan"); return; } Vector3 normal; if ((index + 1) % numStrands == 0) { //next particle is in another strand normal = Vector3.down; } else { normal = (nextParticlePosition - particlePosition).normalized; } Vector3 tangent = (head.transform.position - particlePosition).normalized; cuaAtom.reParentObject.position = particlePosition; cuaAtom.reParentObject.rotation = Quaternion.LookRotation(normal, tangent); } else { int vertex = (int)jVertexIndex.val; if (verticesBuffer != null && normalsBuffer != null) { if (vertex >= verticesBuffer.count) { return; } verticesRequest = AsyncGPUReadback.Request(verticesBuffer, (maxVertex - minVertex + 1) * 12, minVertex * 12); normalsRequest = AsyncGPUReadback.Request(normalsBuffer, 12, vertex * 12); } else { return; } if (!verticesRequest.done) { verticesRequest.WaitForCompletion(); } if (!normalsRequest.done) { normalsRequest.WaitForCompletion(); } if (verticesRequest.hasError || normalsRequest.hasError) { return; } var vertices = verticesRequest.GetData(); var normals = normalsRequest.GetData(); Vector3 position = vertices[vertex - minVertex]; Vector3 normal = normals[0]; if (position.ContainsNaN() || normal.ContainsNaN()) { return; } Vector3 tangent = GetTangent(vertex, triangleIndexes, minVertex, triangles, vertices, normals, uv); cuaAtom.reParentObject.position = position; cuaAtom.reParentObject.rotation = Quaternion.LookRotation(normal, tangent); } // Press Ctrl to attach to hovered point if (pointerCtrlAttach && (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))) { var hoveredPoint = GetHoveredPoint(); if (hoveredPoint != null) AttachToClosest((Vector3)hoveredPoint); } } public static void Log(string str) { if (UnityEngine.Time.frameCount % 10 == 0) { SuperController.LogMessage(str); } } public JSONStorableFloat AddSlider(string name, float defaultVal, float min, float max, JSONStorableFloat.SetFloatCallback onChange = null, bool rangeAdjust = false, bool rightSide = false) { JSONStorableFloat storableFloat = new JSONStorableFloat(name, defaultVal, min, max, true, true); storableFloat.storeType = JSONStorableParam.StoreType.Full; UIDynamicSlider uiSlider = CreateSlider(storableFloat, rightSide); uiSlider.rangeAdjustEnabled = rangeAdjust; uiSlider.valueFormat = "F4"; RegisterFloat(storableFloat); if (onChange != null) { storableFloat.setCallbackFunction = onChange; } return storableFloat; } // Originally Copied from https://discussions.unity.com/t/calculating-tangents-vector4/179 public Vector3 GetTangent(int vertex, List triangleIndexes, int vertexOffset, int[] triangles, NativeArray vertices, NativeArray normals, Vector2[] uv) { Vector3 tan = new Vector3(); foreach (int a in triangleIndexes) { int i1 = triangles[a + 0]; int i2 = triangles[a + 1]; int i3 = triangles[a + 2]; Vector3 v1 = vertices[i1 - vertexOffset]; Vector3 v2 = vertices[i2 - vertexOffset]; Vector3 v3 = vertices[i3 - vertexOffset]; Vector2 w1 = uv[i1]; Vector2 w2 = uv[i2]; Vector2 w3 = uv[i3]; float x1 = v2.x - v1.x; float x2 = v3.x - v1.x; float y1 = v2.y - v1.y; float y2 = v3.y - v1.y; float z1 = v2.z - v1.z; float z2 = v3.z - v1.z; float s1 = w2.x - w1.x; float s2 = w3.x - w1.x; float t1 = w2.y - w1.y; float t2 = w3.y - w1.y; float r = 1.0f / (s1 * t2 - s2 * t1); Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r); if (i1 == vertex) tan += sdir; if (i2 == vertex) tan += sdir; if (i3 == vertex) tan += sdir; ; } Vector3 n = normals[0]; Vector3 t = tan; Vector3 tmp = (t - n * Vector3.Dot(n, t)).normalized; return new Vector3(tmp.x, tmp.y, tmp.z); } private void OnEnable() { SyncAssetUrl(jAssetUrl.val); // if (cuaAtom != null) { // cuaAtom.gameObject.SetActive(true); // } } private void OnDisable() { OnDestroy(); // if (cuaAtom != null) { // cuaAtom.gameObject.SetActive(false); // } } private void OnDestroy() { atomScale.setCallbackFunction -= UpdateScale; if (cuaAtom != null) { SuperController.singleton.RemoveAtom(cuaAtom); } } } // Handles vamifying, coloring, and scaling CUAs with special handling to fix joint positions. // Add to CUA gameObject. public class CUAManager : MonoBehaviour { CustomUnityAssetLoader cuaLoader; JSONStorableStringChooser cuaAssetChooser; Coroutine waitingCo = null; // Waiting for an asset coroutine. public void Awake() { try { vamPropShader = Shader.Find(VAM_SHADER_NAME); cuaLoader = gameObject.GetComponentInChildren(); if (cuaLoader == null) { var message = "CUAManager: No CustomUnityAssetLoader found inside atom. Wrong atom?"; SuperController.LogError(message); throw new Exception(message); } cuaAssetChooser = cuaLoader.GetStringChooserJSONParam("assetName"); cuaLoader.RegisterAssetClearedCallback(AssetCleared); // We use wait here because old asset might not be removed from hierarchy, and wait // iterator forces 1 frame delay to work around it. cuaLoader.RegisterAssetLoadedCallback(WaitForAssetLoaded); if (cuaLoader.isAssetLoaded) { AssetLoaded(); } else if (cuaAssetChooser.val != "" && cuaAssetChooser.val.ToLower() != "none") { WaitForAssetLoaded(); } SerializeJoints(); } catch (System.Exception e) { SuperController.LogError($"CUAManager.Awake() exception: {e}"); } } void AssetCleared() { // don't need to reset materials because the asset would have been destroyed joints.Clear(); renderers.Clear(); diffuse.Clear(); specular.Clear(); } void WaitForAssetLoaded() { AssetCleared(); if (waitingCo == null) { waitingCo = StartCoroutine(_WaitForAssetLoaded()); } } IEnumerator _WaitForAssetLoaded() { // We force 1 frame delay to ensure old asset was removed from hierarchy, // since Destroy() clears objects only at the end of the frame. yield return null; while (cuaLoader?.transform != null && cuaLoader.transform.childCount < 1) { yield return new WaitForSeconds(0.5f); } waitingCo = null; AssetLoaded(); } void CancelWaitForAssetLoaded() { if (waitingCo != null) { StopCoroutine(waitingCo); waitingCo = null; } } void AssetLoaded() { try { AssetCleared(); if (waitingCo != null) return; SerializeJoints(); ConvertMaterials(); } catch (System.Exception e) { SuperController.LogError($"CUAManager.AssetLoaded() exception: {e}"); } } void OnDestroy() { CancelWaitForAssetLoaded(); RestoreMaterials(); AssetCleared(); #if VAM_GT_1_20 if (cuaLoader != null) { cuaLoader.DeregisterAssetClearedCallback(AssetCleared); cuaLoader.DeregisterAssetLoadedCallback(WaitForAssetLoaded); } #endif } // Scaling List joints = new List(); struct ManagedJoint { public Joint joint; public Vector3 anchor; public Vector3 connectedAnchor; public ManagedJoint(Joint joint) { this.joint = joint; anchor = joint.anchor; connectedAnchor = joint.connectedAnchor; } } public void Scale(Vector3 scale) { cuaLoader.gameObject.transform.localScale = scale; // Update joints by resetting them to their original values foreach (var joint in joints) { if (joint.joint == null) continue; joint.joint.anchor = joint.anchor; joint.joint.connectedAnchor = joint.connectedAnchor; } } void SerializeJoints() { joints.Clear(); foreach (var joint in cuaLoader.gameObject.GetComponentsInChildren()) { joint.autoConfigureConnectedAnchor = false; joints.Add(new ManagedJoint(joint)); } } // Vamifying & coloring, adapted from UnityAssetVamifier by NoStage readonly List UNITY_SHADER_NAMES = new List(new string[] { "Standard", "Standard (Specular setup)", "Standard (Roughness setup)" }); readonly List INVALID_SHADER_KEYWORDS = new List(new string[] { "_ALPHAPREMULTIPLY_ON", "_ALPHATEST_ON", "_ALPHABLEND_ON", "_EMISSION" }); readonly string VAM_SHADER_NAME = "Custom/Subsurface/GlossNMCull"; Shader vamPropShader; Dictionary renderers = new Dictionary(); public ColorManager diffuse = new ColorManager("_Color"); public ColorManager specular = new ColorManager("_SpecColor"); struct ManagedRenderer { public Renderer renderer; public List initMaterials; public List newMaterials; public ManagedRenderer(Renderer renderer, List initMaterials, List newMaterials) { this.renderer = renderer; this.initMaterials = initMaterials; this.newMaterials = newMaterials; } public void Reset() { if (renderer != null) { renderer.materials = initMaterials.ToArray(); } } } struct Colorable { public Material material; public string prop; public Color initColor; public Colorable(Material material, string prop) { this.material = material; this.prop = prop; initColor = material.GetColor(prop); } public void Set(Color color) { material.SetColor(prop, color); } public void Reset() { material.SetColor(prop, initColor); } } public class ColorManager { string prop; Color currentColor = Color.white; bool _enabled = false; List colorables = new List(); public bool enabled { get { return _enabled; } set { _enabled = value; if (_enabled) SetColor(currentColor); else Reset(); } } public ColorManager(string prop) { this.prop = prop; } public void AddMaterial(Material material) { var colorable = new Colorable(material, prop); if (_enabled) colorable.Set(currentColor); colorables.Add(colorable); } public void SetColor(Color color) { currentColor = color; if (!_enabled) return; foreach (var colorable in colorables) colorable.Set(color); } public void Reset() { foreach (var colorable in colorables) colorable.Reset(); } public void Clear() { colorables.Clear(); } } // Determine if the material should be converted or not bool IsValidMaterial(Material mat) { if (!UNITY_SHADER_NAMES.Contains(mat.shader.name)) return false; foreach (string keyword in INVALID_SHADER_KEYWORDS) { if (mat.IsKeywordEnabled(keyword)) return false; } return true; } void ConvertMaterials() { var allRenderers = cuaLoader.GetComponentsInChildren(); var materialsCount = 0; var convertedCount = 0; // Collect materials to assign to each renderer (needs to be assigned as array...) foreach (var renderer in allRenderers) { var initMaterials = new List(); var newMaterials = new List(); materialsCount += renderer.materials.Length; foreach (var initMaterial in renderer.materials) { initMaterials.Add(initMaterial); if (!IsValidMaterial(initMaterial)) { newMaterials.Add(initMaterial); continue; } convertedCount++; var vamMaterial = new Material(vamPropShader); Vector2 mainTexScale = initMaterial.GetTextureScale("_MainTex"); Vector2 mainTexOffset = initMaterial.GetTextureOffset("_MainTex"); if (initMaterial.HasProperty("_Color")) { vamMaterial.SetColor("_Color", initMaterial.GetColor("_Color")); } if (initMaterial.HasProperty("_SpecColor")) { vamMaterial.SetColor("_SpecColor", initMaterial.GetColor("_SpecColor")); } if (initMaterial.HasProperty("_Glossiness")) { // Just a guesstimation since it'd be too annoying to set up some visual test for this vamMaterial.SetFloat("_Shininess", 10f * initMaterial.GetFloat("_Glossiness")); } if (initMaterial.HasProperty("_SubdermisColor")) { vamMaterial.SetColor("_SubdermisColor", initMaterial.GetColor("_SubdermisColor")); } // Use tiling from main texture since supported unity shaders don't allow tiling per texture type if (initMaterial.HasProperty("_MainTex")) { vamMaterial.SetTexture("_MainTex", initMaterial.GetTexture("_MainTex")); vamMaterial.SetTextureScale("_MainTex", mainTexScale); vamMaterial.SetTextureOffset("_MainTex", mainTexOffset); } if (initMaterial.HasProperty("_BumpMap")) { vamMaterial.SetTexture("_BumpMap", initMaterial.GetTexture("_BumpMap")); vamMaterial.SetTextureScale("_BumpMap", mainTexScale); vamMaterial.SetTextureOffset("_BumpMap", mainTexOffset); } if (initMaterial.HasProperty("_SpecGlossMap")) { vamMaterial.SetTexture("_SpecTex", initMaterial.GetTexture("_SpecGlossMap")); vamMaterial.SetTextureScale("_SpecTex", mainTexScale); vamMaterial.SetTextureOffset("_SpecTex", mainTexOffset); } if (initMaterial.HasProperty("_MetallicGlossMap")) { vamMaterial.SetTexture("_GlossTex", initMaterial.GetTexture("_MetallicGlossMap")); vamMaterial.SetTextureScale("_GlossTex", mainTexScale); vamMaterial.SetTextureOffset("_GlossTex", mainTexOffset); } diffuse.AddMaterial(vamMaterial); specular.AddMaterial(vamMaterial); newMaterials.Add(vamMaterial); } renderer.materials = newMaterials.ToArray(); renderers.Add(renderer, new ManagedRenderer(renderer, initMaterials, newMaterials)); } } void RestoreMaterials() { foreach (var renderer in renderers) renderer.Value.Reset(); } } static class Util { public static void Log(string message) { SuperController.LogMessage(message); } public static void LogError(Exception e) { var _sb = new StringBuilder(); _sb .Append(e.ToString()) .AppendFormat("\n{0}", new System.Diagnostics.StackTrace()); SuperController.LogError(_sb.ToString()); } public static void LogTransform(string message, Transform t) { StringBuilder b = new StringBuilder(); b.Append(message).Append("\n"); LogTransformInternal(t, 0, b); Log(b.ToString()); } public static void LogTransform(Transform t) { StringBuilder b = new StringBuilder(); LogTransformInternal(t, 0, b); Log(b.ToString()); } private static void LogTransformInternal(Transform t, int indent, StringBuilder b) { b.Append(' ', indent * 4).Append(t.name).Append(" (active: ").Append(t.gameObject.activeSelf).Append(")\n"); Component[] comps = t.GetComponents(); if (comps.Length > 0) { b.Append(' ', indent * 4 + 2).Append("Components:\n"); for (int i = 0; i < comps.Length; ++i) { Component c = comps[i]; b.Append(' ', indent * 4 + 4).Append(c.GetType().FullName).Append("\n"); } } if (t.childCount > 0) { b.Append(' ', indent * 4 + 2).Append("Children:\n"); for (int i = 0; i < t.childCount; ++i) { Transform c = t.GetChild(i); LogTransformInternal(c, indent + 1, b); } } } public static bool IsInPackage(MVRScript self) { string id = self.name.Substring(0, self.name.IndexOf('_')); string filename = self.manager.GetJSON()["plugins"][id].Value; return filename.IndexOf(":/") >= 0; } } }