Merge branch 'master' into fix/10_show_select_springbone

This commit is contained in:
ousttrue
2024-12-23 13:39:18 +09:00
committed by GitHub
76 changed files with 2188 additions and 919 deletions

View File

@@ -14,23 +14,16 @@ defaults:
shell: bash
jobs:
checkout:
create-unitypackage:
runs-on: [self-hosted, Windows, X64, Unity]
timeout-minutes: 10
timeout-minutes: 60
steps:
- id: checkout
uses: actions/checkout@v4
with:
submodules: recursive
lfs: true
detect-unity-version:
needs: checkout
runs-on: [self-hosted, Windows, X64, Unity]
timeout-minutes: 10
outputs:
unity-editor-executable: ${{ steps.detect-unity-version.outputs.unity-editor-executable }}
steps:
- name: Detect Unity Version
id: detect-unity-version
run: |
@@ -58,18 +51,13 @@ jobs:
echo "${UNITY_EDITOR_EXECUTABLE} is installed."
echo "unity-editor-executable=${UNITY_EDITOR_EXECUTABLE}" >> "${GITHUB_OUTPUT}"
run-edit-mode-tests:
needs: detect-unity-version
runs-on: [self-hosted, Windows, X64, Unity]
timeout-minutes: 10
steps:
- name: Run EditMode Tests
id: run-edit-mode-tests
run: |
echo "Run EditMode Tests..."
# RunEditModeTests の実行の結果、終了コードが 0 でない場合でもテストの結果を表示したいので set +e して一時的に回避する
set +e
"${{ needs.detect-unity-version.outputs.unity-editor-executable }}" \
"${{ steps.detect-unity-version.outputs.unity-editor-executable }}" \
-batchmode \
-silent-crashes \
-projectPath "${{ env.UNITY_PROJECT_PATH }}" \
@@ -91,7 +79,7 @@ jobs:
echo "Test failed."
exit 1
fi
- name: Upload test results
if: ${{ always() }}
uses: actions/upload-artifact@v4
@@ -99,15 +87,10 @@ jobs:
name: run-edit-mode-tests.xml
path: ${{ env.UNITY_PROJECT_PATH }}/run-edit-mode-tests.xml
create-unitypackage:
needs: [detect-unity-version, run-edit-mode-tests]
runs-on: [self-hosted, Windows, X64, Unity]
timeout-minutes: 20
steps:
- name: Create UnityPackage
id: create-unitypackage
run: |
"${{ needs.detect-unity-version.outputs.unity-editor-executable }}" \
"${{ steps.detect-unity-version.outputs.unity-editor-executable }}" \
-batchmode \
-silent-crashes \
-projectPath "${{ env.UNITY_PROJECT_PATH }}" \

View File

@@ -6,18 +6,9 @@ namespace UniGLTF
{
public static class GltfImportMenu
{
public const string MENU_NAME = "Import glTF... (*.gltf|*.glb|*.zip)";
public static void ImportGltfFileToGameObject()
{
var path = EditorUtility.OpenFilePanel(MENU_NAME + ": open glb", "",
#if UNITY_EDITOR_OSX
// https://github.com/vrm-c/UniVRM/issues/1837
"glb"
#else
"gltf,glb,zip"
#endif
);
if (string.IsNullOrEmpty(path))
if(!UniGltfEditorDialog.TryOpenFilePanel("", out var path))
{
return;
}

View File

@@ -23,7 +23,7 @@ namespace UniGLTF
private static void ExportGameObjectToGltf() => GltfExportWindow.ExportGameObjectToGltfFile();
[MenuItem(UserGltfMenuPrefix + "/" + GltfImportMenu.MENU_NAME, priority = 2)]
[MenuItem(UserGltfMenuPrefix + "/" + UniGltfEditorDialog.IMPORT_MENU_NAME, priority = 2)]
private static void ImportGltfFile() => GltfImportMenu.ImportGltfFileToGameObject();

View File

@@ -156,8 +156,7 @@ namespace UniGLTF
if (GUILayout.Button("Export", GUILayout.MinWidth(100)))
{
var path = SaveFileDialog.GetPath(SaveTitle, SaveName, SaveExtensions);
if (!string.IsNullOrEmpty(path))
if (UniGltfEditorDialog.TrySaveFilePanel(SaveTitle, SaveName, SaveExtensions, out var path))
{
ExportPath(path);
// close

View File

@@ -1,55 +0,0 @@
using System.IO;
using UnityEditor;
using UnityEngine;
namespace UniGLTF
{
public static class SaveFileDialog
{
static string m_lastExportDir;
static string extensionString(string[] extensions)
{
#if UNITY_EDITOR_OSX
// in OSX multi extension cause exception.
// https://github.com/vrm-c/UniVRM/issues/1837
return extensions.Length > 0 ? extensions[0] : "";
#else
return string.Join(",", extensions);
#endif
}
public static string GetPath(string title, string name, params string[] extensions)
{
string directory = m_lastExportDir;
if (string.IsNullOrEmpty(directory))
{
directory = Directory.GetParent(Application.dataPath).ToString();
}
var path = EditorUtility.SaveFilePanel(title, directory, name, extensionString(extensions));
if (!string.IsNullOrEmpty(path))
{
m_lastExportDir = Path.GetDirectoryName(path).Replace("\\", "/");
}
return path;
}
public static string GetDir(string title, string dir = null)
{
string directory = string.IsNullOrEmpty(dir) ? m_lastExportDir : dir;
if (string.IsNullOrEmpty(directory))
{
directory = Directory.GetParent(Application.dataPath).ToString();
}
var path = EditorUtility.SaveFolderPanel(title, directory, null);
if (!string.IsNullOrEmpty(path))
{
m_lastExportDir = Path.GetDirectoryName(path).Replace("\\", "/");
}
return path;
}
}
}

View File

@@ -0,0 +1,78 @@
using System.IO;
using UnityEditor;
using UnityEngine;
namespace UniGLTF
{
public static class UniGltfEditorDialog
{
public const string IMPORT_MENU_NAME = "Import glTF... (*.gltf|*.glb|*.zip)";
static string extensionString(string[] extensions)
{
if (Application.platform == RuntimePlatform.WindowsEditor)
{
return string.Join(",", extensions);
}
else
{
// in OSX multi extension cause exception.
// https://github.com/vrm-c/UniVRM/issues/1837
// in Linux
// https://github.com/vrm-c/UniVRM/issues/2515
return extensions.Length > 0 ? extensions[0] : "";
}
}
public static bool TryOpenFilePanel(string directory, out string path)
{
path = EditorUtility.OpenFilePanel(IMPORT_MENU_NAME, directory,
// https://github.com/vrm-c/UniVRM/issues/1837
Application.platform == RuntimePlatform.WindowsEditor ? "gltf,glb,zip" : "glb"
);
if (string.IsNullOrEmpty(path))
{
return false;
}
return true;
}
static string s_lastExportDir;
public static bool TrySaveFilePanel(string title, string name, string[] extensions, out string path)
{
string directory = s_lastExportDir;
if (string.IsNullOrEmpty(directory))
{
directory = Directory.GetParent(Application.dataPath).ToString();
}
path = EditorUtility.SaveFilePanel(title, directory, name, extensionString(extensions));
if (string.IsNullOrEmpty(path))
{
return false;
}
s_lastExportDir = Path.GetDirectoryName(path).Replace("\\", "/");
return true;
}
public static bool TryGetDir(string title, string dir, out string path)
{
string directory = string.IsNullOrEmpty(dir) ? s_lastExportDir : dir;
if (string.IsNullOrEmpty(directory))
{
directory = Directory.GetParent(Application.dataPath).ToString();
}
path = EditorUtility.SaveFolderPanel(title, directory, null);
if (string.IsNullOrEmpty(path))
{
return false;
}
s_lastExportDir = Path.GetDirectoryName(path).Replace("\\", "/");
return true;
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 35dc28badc82c5e4a8aa813ddd3369fe
guid: 20581dc53a52e174cbaec605148d3e78
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -172,7 +172,14 @@ namespace UniGLTF.SpringBoneJobs.InputPorts
for (int i = offset; i < end; ++i)
{
// mark velocity zero
#if UNITY_2022_2_OR_NEWER
currentTails.GetSubArray(offset, Logics.Length).AsSpan().Fill(new Vector3(float.NaN, float.NaN, float.NaN));
#else
var subArray = currentTails.GetSubArray(offset, Logics.Length);
var value = new Vector3(float.NaN, float.NaN, float.NaN);
for (int a = 0; a < subArray.Length; ++a)
subArray[a] = value;
#endif
}
}
}

View File

@@ -75,6 +75,17 @@ namespace UniGLTF
[Serializable]
public class glTFPrimitives
{
public enum Mode
{
POINTS = 0,
LINES = 1,
LINE_LOOP = 2,
LINE_STRIP = 3,
TRIANGLES = 4,
TRIANGLE_STRIP = 5,
TRIANGLE_FAN = 6,
}
[JsonSchema(EnumValues = new object[] { 0, 1, 2, 3, 4, 5, 6 })]
public int mode;

View File

@@ -43,22 +43,18 @@ namespace UniGLTF
public glTFBufferView Extend(IntPtr p, int bytesLength, int stride, glBufferTarget target)
{
var tmp = m_bytes;
// alignment
var padding = m_used % stride == 0 ? 0 : stride - m_used % stride;
var requiredLength = m_used + padding + bytesLength;
if (m_bytes == null || m_used + padding + bytesLength > m_bytes.Length)
if (m_bytes == null || requiredLength > m_bytes.Length)
{
// recreate buffer
m_bytes = new Byte[m_used + padding + bytesLength];
var newLength = Math.Max(requiredLength, m_bytes?.Length * 2 ?? 256);
var newBuffer = new Byte[newLength];
if (m_used > 0)
{
Buffer.BlockCopy(tmp, 0, m_bytes, 0, m_used);
}
}
if (m_used + padding + bytesLength > m_bytes.Length)
{
throw new ArgumentOutOfRangeException();
Buffer.BlockCopy(m_bytes, 0, newBuffer, 0, m_used);
m_bytes = newBuffer;
}
Marshal.Copy(p, m_bytes, m_used + padding, bytesLength);
@@ -70,7 +66,7 @@ namespace UniGLTF
byteStride = stride,
target = target,
};
m_used = m_used + padding + bytesLength;
m_used = requiredLength;
return result;
}

View File

@@ -14,20 +14,22 @@ namespace UniGLTF
private readonly MaterialDescriptor m_defaultMaterialParams;
private readonly List<MaterialLoadInfo> m_materials = new List<MaterialLoadInfo>();
/// <summary>
/// gltfPritmitive.material が無い場合のデフォルトマテリアル
/// https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#default-material
///
/// </summary>
private Material m_defaultMaterial;
public IReadOnlyList<MaterialLoadInfo> Materials => m_materials;
public MaterialFactory(IReadOnlyDictionary<SubAssetKey, Material> externalMaterialMap, MaterialDescriptor defaultMaterialParams)
{
m_externalMap = externalMaterialMap;
m_defaultMaterialParams = defaultMaterialParams;
m_defaultMaterialParams = new MaterialDescriptor(
m_defaultMaterialKey.Name,
defaultMaterialParams.Shader,
defaultMaterialParams.RenderQueue,
defaultMaterialParams.TextureSlots,
defaultMaterialParams.FloatValues,
defaultMaterialParams.Colors,
defaultMaterialParams.Vectors,
defaultMaterialParams.Actions,
defaultMaterialParams.AsyncActions
);
}
public struct MaterialLoadInfo
@@ -56,11 +58,6 @@ namespace UniGLTF
UnityObjectDestroyer.DestroyRuntimeOrEditor(x.Asset);
}
}
if (m_defaultMaterial != null)
{
UnityObjectDestroyer.DestroyRuntimeOrEditor(m_defaultMaterial);
}
}
/// <summary>
@@ -84,12 +81,6 @@ namespace UniGLTF
m_materials.Remove(x);
}
}
if (m_defaultMaterial != null)
{
take(m_defaultMaterialKey, m_defaultMaterial);
m_defaultMaterial = null;
}
}
public Material GetMaterial(int index)
@@ -99,22 +90,16 @@ namespace UniGLTF
return m_materials[index].Asset;
}
public async Task<Material> GetDefaultMaterialAsync(IAwaitCaller awaitCaller)
/// <summary>
/// gltfPritmitive.material が無い場合のデフォルトマテリアル
/// https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#default-material
///
/// </summary>
public Task<Material> GetDefaultMaterialAsync(IAwaitCaller awaitCaller)
{
if (m_externalMap.ContainsKey(m_defaultMaterialKey))
{
m_defaultMaterial = m_externalMap[m_defaultMaterialKey];
return m_externalMap[m_defaultMaterialKey];
}
if (m_defaultMaterial == null)
{
m_defaultMaterial = await LoadAsync(m_defaultMaterialParams, (_, _) => null, awaitCaller);
}
return m_defaultMaterial;
return LoadAsync(m_defaultMaterialParams, (_, _) => null, awaitCaller);
}
public async Task<Material> LoadAsync(MaterialDescriptor matDesc, GetTextureAsyncFunc getTexture, IAwaitCaller awaitCaller)
{
if (m_externalMap.TryGetValue(matDesc.SubAssetKey, out Material material))

View File

@@ -114,52 +114,7 @@ namespace UniGLTF
attributes.JOINTS_0 = jointsAccessorIndex;
}
var gltfMesh = new glTFMesh(mesh.name);
var indices = new List<uint>();
for (int j = 0; j < mesh.subMeshCount; ++j)
{
indices.Clear();
var triangles = mesh.GetIndices(j);
if (triangles.Length == 0)
{
// https://github.com/vrm-c/UniVRM/issues/664
continue;
}
for (int i = 0; i < triangles.Length; i += 3)
{
var i0 = triangles[i];
var i1 = triangles[i + 1];
var i2 = triangles[i + 2];
// flip triangle
indices.Add((uint)i2);
indices.Add((uint)i1);
indices.Add((uint)i0);
}
var indicesAccessorIndex = data.ExtendBufferAndGetAccessorIndex(indices.ToArray(), glBufferTarget.ELEMENT_ARRAY_BUFFER);
if (indicesAccessorIndex < 0)
{
// https://github.com/vrm-c/UniVRM/issues/664
throw new Exception();
}
if (j >= materials.Length)
{
Debug.LogWarningFormat("{0}.materials is not enough", unityMesh.Mesh.name);
break;
}
gltfMesh.primitives.Add(new glTFPrimitives
{
attributes = attributes,
indices = indicesAccessorIndex,
mode = 4, // triangles ?
material = unityMaterials.IndexOf(materials[j])
});
}
var gltfMesh = CreateGLTFMesh(attributes, data, unityMesh, unityMaterials);
var blendShapeIndexMap = new Dictionary<int, int>();
{
@@ -201,6 +156,119 @@ namespace UniGLTF
return (gltfMesh, blendShapeIndexMap);
}
private static glTFMesh CreateGLTFMesh(glTFAttributes attributes, ExportingGltfData data, MeshExportInfo unityMesh, List<Material> unityMaterials)
{
var mesh = unityMesh.Mesh;
var materials = unityMesh.Materials;
var gltfMesh = new glTFMesh(mesh.name);
var indices = new List<uint>();
for (int j = 0; j < mesh.subMeshCount; ++j)
{
indices.Clear();
if (j >= materials.Length)
{
Debug.LogWarningFormat("{0}.materials is not enough", mesh.name);
continue;
}
var subMesh = mesh.GetSubMesh(j);
var topologyType = subMesh.topology;
var materialIndex = unityMaterials.IndexOf(materials[j]);
var submeshIndices = mesh.GetIndices(j);
if (submeshIndices.Length == 0)
{
// https://github.com/vrm-c/UniVRM/issues/664
break;
}
else if (submeshIndices.Length < 3)
{
Debug.LogWarningFormat("Invalid primitive of type {0} found", topologyType);
continue;
}
// Add indices considering the topology type
switch (topologyType)
{
case MeshTopology.Triangles:
if (submeshIndices.Length % 3 != 0)
Debug.LogWarningFormat("triangle indices is not multiple of 3");
GetTriangleIndices(indices, submeshIndices);
break;
case MeshTopology.Quads:
if (submeshIndices.Length % 4 != 0)
Debug.LogWarningFormat("quad indices is not multiple of 4");
GetQuadIndices(indices, submeshIndices);
break;
default:
case MeshTopology.Lines:
case MeshTopology.LineStrip:
case MeshTopology.Points:
Debug.LogWarningFormat("Mesh {0} has unsupported topology type {1}.", mesh.name, topologyType);
continue;
}
var primitive = CreatePrimitives(attributes, data, indices, materialIndex);
gltfMesh.primitives.Add(primitive);
}
return gltfMesh;
}
private static glTFPrimitives CreatePrimitives(glTFAttributes attributes, ExportingGltfData data, List<uint> indices, int materialIndex)
{
var indicesAccessorIndex = data.ExtendBufferAndGetAccessorIndex(indices.ToArray(), glBufferTarget.ELEMENT_ARRAY_BUFFER);
if (indicesAccessorIndex < 0)
{
// https://github.com/vrm-c/UniVRM/issues/664
throw new Exception();
}
var primitive = new glTFPrimitives
{
attributes = attributes,
indices = indicesAccessorIndex,
mode = (int)glTFPrimitives.Mode.TRIANGLES, // triangles ?
material = materialIndex
};
return primitive;
}
private static void GetQuadIndices(List<uint> indices, int[] quadIndices)
{
for (int i = 0; i < quadIndices.Length - 3; i += 4)
{
var i0 = quadIndices[i];
var i1 = quadIndices[i + 1];
var i2 = quadIndices[i + 2];
var i3 = quadIndices[i + 3];
// flip triangles
indices.Add((uint)i2);
indices.Add((uint)i1);
indices.Add((uint)i0);
indices.Add((uint)i3);
indices.Add((uint)i2);
indices.Add((uint)i0);
}
}
private static void GetTriangleIndices(List<uint> indices, int[] triangleIndices)
{
for (int i = 0; i < triangleIndices.Length - 2; i += 3)
{
var i0 = triangleIndices[i];
var i1 = triangleIndices[i + 1];
var i2 = triangleIndices[i + 2];
// flip triangle
indices.Add((uint)i2);
indices.Add((uint)i1);
indices.Add((uint)i0);
}
}
static bool UseSparse(
bool usePosition, Vector3 position,
bool useNormal, Vector3 normal,

View File

@@ -69,8 +69,8 @@ namespace UniGLTF
public NativeArray<T> CreateNativeArray<T>(ArraySegment<T> data) where T : struct
{
var array = CreateNativeArray<T>(data.Count);
// TODO: remove ToArray
array.CopyFrom(data.ToArray());
for (int i = 0; i < data.Count; i++)
array[i] = data.Array[data.Offset + i];
return array;
}

View File

@@ -1,8 +1,7 @@
fileFormatVersion: 2
guid: 976e99d37c093ce4c9b249c81c2cbdd5
timeCreated: 1520401720
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0

View File

@@ -12,6 +12,10 @@ namespace VRM
[LangMsg(Languages.en, "Model's normalization (bake to remove roation and scaling from the hierarchy)")]
NORMALIZE,
[LangMsg(Languages.ja, "正規化するときににブレンドシェイプによる変形をベイク処理します。")]
[LangMsg(Languages.en, "Bake the blendshape deformations as the base model when normalizing.")]
FREEZE_BLENDSHAPE,
[LangMsg(Languages.ja, "エクスポート時に新しいJsonSerializerを使う")]
[LangMsg(Languages.en, "The new version of JsonSerializer for model export")]
USE_GENERATED_SERIALIZER,

View File

@@ -23,7 +23,7 @@ namespace VRM
/// FreezeBlendShape
/// </summary>
[Tooltip("when freeze mesh, blendShpae base use current weight")]
public bool FreezeMeshUseCurrentBlendShapeWeight = false;
public bool FreezeMeshUseCurrentBlendShapeWeight = true;
/// <summary>
/// BlendShapeのシリアライズにSparseAccessorを使う

View File

@@ -48,6 +48,7 @@ namespace VRM
private void OnEnable()
{
m_checkbox_list.Add(new CheckBoxProp(serializedObject.FindProperty(nameof(VRMExportSettings.PoseFreeze)), VRMExportOptions.NORMALIZE));
m_checkbox_list.Add(new CheckBoxProp(serializedObject.FindProperty(nameof(VRMExportSettings.FreezeMeshUseCurrentBlendShapeWeight)), VRMExportOptions.FREEZE_BLENDSHAPE));
m_checkbox_list.Add(new CheckBoxProp(serializedObject.FindProperty(nameof(VRMExportSettings.UseSparseAccessor)), VRMExportOptions.BLENDSHAPE_USE_SPARSE));
m_checkbox_list.Add(new CheckBoxProp(serializedObject.FindProperty(nameof(VRMExportSettings.OnlyBlendshapePosition)), VRMExportOptions.BLENDSHAPE_EXCLUDE_NORMAL_AND_TANGENT));
m_checkbox_list.Add(new CheckBoxProp(serializedObject.FindProperty(nameof(VRMExportSettings.ReduceBlendshape)), VRMExportOptions.BLENDSHAPE_ONLY_CLIP_USE));

8
Assets/VRM/Icons.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e811516644fdd954f866e2f48a257a16
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -0,0 +1,114 @@
fileFormatVersion: 2
guid: 7656e3d0da852c54e979d2887196ef75
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,13 +1,11 @@
fileFormatVersion: 2
guid: 329dca3bf78fcdd42b2df941673db76f
timeCreated: 1519195979
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
icon: {fileID: 2800000, guid: 7656e3d0da852c54e979d2887196ef75, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,13 +1,11 @@
fileFormatVersion: 2
guid: 37562b39ff933b245ac2f35d87edbcd6
timeCreated: 1517402750
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
icon: {fileID: 2800000, guid: 7656e3d0da852c54e979d2887196ef75, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,13 +1,11 @@
fileFormatVersion: 2
guid: 5b678c1df50cfb547990db24a32856da
timeCreated: 1517467747
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
icon: {fileID: 2800000, guid: 7656e3d0da852c54e979d2887196ef75, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,13 +1,11 @@
fileFormatVersion: 2
guid: dedba1309bdf12b42af2362f52eea134
timeCreated: 1519218333
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
icon: {fileID: 2800000, guid: 7656e3d0da852c54e979d2887196ef75, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,12 +1,11 @@
fileFormatVersion: 2
guid: 690ea0146224b8b4694a1925dddeb352
timeCreated: 1522391118
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
icon: {fileID: 2800000, guid: 7656e3d0da852c54e979d2887196ef75, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,12 +1,11 @@
fileFormatVersion: 2
guid: 63b589176a34b344b9ccbee2b7e7114a
timeCreated: 1522391129
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
icon: {fileID: 2800000, guid: 7656e3d0da852c54e979d2887196ef75, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,13 +1,11 @@
fileFormatVersion: 2
guid: 00ea06e1753e16f4ca870c39c067c86b
timeCreated: 1517224588
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
icon: {fileID: 2800000, guid: 7656e3d0da852c54e979d2887196ef75, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,6 +1,7 @@
using UnityEditor;
using UnityEngine;
namespace UniVRM10
{
[CustomEditor(typeof(VRM10SpringBoneCollider))]
@@ -9,32 +10,41 @@ namespace UniVRM10
VRM10SpringBoneCollider _target;
Vrm10Instance _vrm;
SerializedProperty _script;
SerializedProperty _colliderType;
SerializedProperty _offset;
SerializedProperty _tail;
static VRM10SpringBoneCollider s_selected;
private void OnEnable()
{
_target = (VRM10SpringBoneCollider)target;
if(_target!=null)
if (_target != null)
{
_vrm = _target.GetComponentInParent<Vrm10Instance>();
}
_script = serializedObject.FindProperty("m_Script");
_colliderType = serializedObject.FindProperty(nameof(_target.ColliderType));
_offset = serializedObject.FindProperty(nameof(_target.Offset));
_tail = serializedObject.FindProperty(nameof(_target.Tail));
}
public override void OnInspectorGUI()
{
// if (VRM10Window.Active == target)
// {
// GUI.backgroundColor = Color.cyan;
// Repaint();
// }
var property = serializedObject.FindProperty("m_Script");
var component = (VRM10SpringBoneCollider)target;
EditorGUILayout.PropertyField(property, new GUIContent("Script (" + component.GetIdentificationName() + ")"));
switch (component.ColliderType)
using (new EditorGUI.DisabledScope(true))
{
EditorGUILayout.PropertyField(_script);
}
switch (_target.ColliderType)
{
case VRM10SpringBoneColliderTypes.Plane:
// radius: x
// tail: x
// normal: o
DrawPropertiesExcluding(serializedObject, "m_Script", nameof(component.Tail), nameof(component.Radius));
DrawPropertiesExcluding(serializedObject, "m_Script", nameof(_target.Tail), nameof(_target.Radius));
break;
case VRM10SpringBoneColliderTypes.Sphere:
@@ -42,7 +52,7 @@ namespace UniVRM10
// radius: o
// tail: x
// normal: x
DrawPropertiesExcluding(serializedObject, nameof(component.Tail), nameof(component.Normal), "m_Script");
DrawPropertiesExcluding(serializedObject, nameof(_target.Tail), nameof(_target.Normal), "m_Script");
break;
case VRM10SpringBoneColliderTypes.Capsule:
@@ -50,21 +60,74 @@ namespace UniVRM10
// radius: o
// tail: o
// normal: x
DrawPropertiesExcluding(serializedObject, nameof(component.Normal), "m_Script");
DrawPropertiesExcluding(serializedObject, nameof(_target.Normal), "m_Script");
break;
}
EditorGUILayout.BeginHorizontal();
{
if (GUILayout.Button("Drag handle"))
{
s_selected = _target;
}
if (_target.transform.childCount > 0)
{
if (GUILayout.Button("Fit head-tail capsule"))
{
_colliderType.enumValueFlag = (int)VRM10SpringBoneColliderTypes.Capsule;
_offset.vector3Value = Vector3.zero;
var tail = _target.transform.GetChild(0);
_tail.vector3Value = _target.transform.worldToLocalMatrix.MultiplyPoint(
tail.position);
}
}
}
EditorGUILayout.EndHorizontal();
if (serializedObject.ApplyModifiedProperties())
{
if (Application.isPlaying)
{
// UniGLTF.UniGLTFLogger.Log("invaliate");
if(_vrm!=null)
if (_vrm != null)
{
_vrm.Runtime.SpringBone.ReconstructSpringBone();
}
}
}
}
public void OnSceneGUI()
{
HandleUtility.Repaint();
if (s_selected == _target)
{
var c = _target;
Handles.matrix = c.transform.localToWorldMatrix;
if (_target.ColliderType == VRM10SpringBoneColliderTypes.Capsule
|| _target.ColliderType == VRM10SpringBoneColliderTypes.CapsuleInside)
{
EditorGUI.BeginChangeCheck();
var newTargetPosition = Handles.PositionHandle(c.Tail, Quaternion.identity);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(c, "collider");
c.Tail = newTargetPosition;
}
}
else
{
EditorGUI.BeginChangeCheck();
var newTargetPosition = Handles.PositionHandle(c.Offset, Quaternion.identity);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(c, "collider");
c.Offset = newTargetPosition;
}
}
}
}
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Linq;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace UniVRM10
{
[CustomEditor(typeof(VRM10SpringBoneColliderGroup))]
class VRM10SpringBoneColliderGroupEditor : Editor
{
ListView m_colliders;
static VRM10SpringBoneCollider s_collider;
public override VisualElement CreateInspectorGUI()
{
var root = new VisualElement();
// root.TrackSerializedObjectValue(serializedObject, OnValueChanged);
root.Bind(serializedObject);
{
var s = new PropertyField { bindingPath = "m_Script" };
s.SetEnabled(false);
root.Add(s);
}
root.Add(new PropertyField { bindingPath = nameof(VRM10SpringBoneColliderGroup.Name) });
{
m_colliders = new ListView
{
bindingPath = nameof(VRM10SpringBoneColliderGroup.Colliders)
};
m_colliders.selectionChanged += (e) =>
{
var item = (SerializedProperty)e.FirstOrDefault();
if (item == null)
{
s_collider = null;
}
else
{
s_collider = (VRM10SpringBoneCollider)item.objectReferenceValue;
}
};
m_colliders.showAddRemoveFooter = true;
m_colliders.showFoldoutHeader = true;
m_colliders.headerTitle = "Colliders";
root.Add(m_colliders);
}
return root;
}
public void OnSceneGUI()
{
HandleUtility.Repaint();
if (m_colliders == null)
{
return;
}
if (s_collider is VRM10SpringBoneCollider c)
{
EditorGUI.BeginChangeCheck();
Handles.matrix = c.transform.localToWorldMatrix;
var newTargetPosition = Handles.PositionHandle(c.Offset, Quaternion.identity);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(c, "collider");
c.Offset = newTargetPosition;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0ccae4c83e5d3a6438402cdd307f4ad2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,63 @@
using UniGLTF;
using UnityEngine;
using System.Linq;
using UnityEditor.AssetImporters;
namespace UniVRM10
{
[ScriptedImporter(1, "vrma")]
public class VrmaScriptedImporter : ScriptedImporter
{
/// <summary>
/// Vrm-1.0 の Asset にアイコンを付与する
/// </summary>
static Texture2D _AssetIcon = null;
static Texture2D AssetIcon
{
get
{
if (_AssetIcon == null)
{
// try package
_AssetIcon = UnityEditor.AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.vrmc.vrm/Icons/vrm-48x48.png");
}
if (_AssetIcon == null)
{
// try assets
_AssetIcon = UnityEditor.AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/VRM10/Icons/vrm-48x48.png");
}
return _AssetIcon;
}
}
public override void OnImportAsset(AssetImportContext context)
{
// 2 回目以降の Asset Import において、 Importer の設定で Extract した UnityEngine.Object が入る
var extractedObjects = GetExternalObjectMap()
.Where(x => x.Value != null)
.ToDictionary(kv => new SubAssetKey(kv.Value.GetType(), kv.Key.name), kv => kv.Value);
using (var data = new AutoGltfFileParser(assetPath).Parse())
using (var loader = new VrmAnimationImporter(data, extractedObjects))
{
var loaded = loader.Load();
loaded.TransferOwnership((k, o) =>
{
context.AddObjectToAsset(k.Name, o);
});
var root = loaded.Root;
GameObject.DestroyImmediate(loaded);
// var vrma = root.GetComponent<Vrm10AnimationInstance>();
// context.AddObjectToAsset("__boxman_mesh__", vrma.BoxMan.sharedMesh);
// context.AddObjectToAsset("__boxman_mesh__material__", vrma.BoxMan.sharedMaterial);
context.AddObjectToAsset(root.name, root, AssetIcon);
context.SetMainObject(root);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 19427481f19aca9429da3548d28da6f5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -190,8 +190,7 @@ namespace UniVRM10
if (GUILayout.Button("Create new VRM10Object and default Expressions. select target folder"))
{
var saveName = GetSaveName(instance);
var dir = SaveFileDialog.GetDir(SaveTitle, System.IO.Path.GetDirectoryName(saveName));
if (!string.IsNullOrEmpty(dir))
if (UniGltfEditorDialog.TryGetDir(SaveTitle, System.IO.Path.GetDirectoryName(saveName), out var dir))
{
var expressions = new Dictionary<ExpressionPreset, VRM10Expression>();
foreach (ExpressionPreset expression in CachedEnum.GetValues<ExpressionPreset>())

View File

@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
icon: {fileID: 2800000, guid: ad4861e134018c948ac79793d290f48b, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
icon: {fileID: 2800000, guid: ad4861e134018c948ac79793d290f48b, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
icon: {fileID: 2800000, guid: ad4861e134018c948ac79793d290f48b, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -231,29 +231,6 @@ namespace UniVRM10
}
}
// UVアクセスするテクスチャーのScaleOffsetプロパティの一覧
static Dictionary<string, string[]> UVPropMap = new Dictionary<string, string[]>
{
{"Standard", new string[]{
"_MainTex_ST",
}},
{"VRM10/MToon10", new string[]{
"_MainTex_ST",
}},
};
static string[] DefaultProps = { "_MainTex_ST" };
public static String[] GetUVProps(string shaderName)
{
if (UVPropMap.TryGetValue(shaderName, out string[] props))
{
return props;
}
else
{
return DefaultProps;
}
}
HashSet<MaterialTarget> m_used = new HashSet<MaterialTarget>();
public void Apply()
{
@@ -302,10 +279,8 @@ namespace UniVRM10
//
// Standard and MToon use _MainTex_ST as uv0 scale/offset
//
foreach (var prop in GetUVProps(item.Material.shader.name))
{
item.Material.SetVector(prop, kv.Value);
}
item.Material.mainTextureScale = new Vector2(kv.Value.x, kv.Value.y);
item.Material.mainTextureOffset = new Vector2(kv.Value.z, kv.Value.w);
}
}
m_materialUVMap.Clear();

View File

@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
icon: {fileID: 2800000, guid: ad4861e134018c948ac79793d290f48b, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
icon: {fileID: 2800000, guid: ad4861e134018c948ac79793d290f48b, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
icon: {fileID: 2800000, guid: ad4861e134018c948ac79793d290f48b, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
icon: {fileID: 2800000, guid: ad4861e134018c948ac79793d290f48b, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -54,21 +54,23 @@ namespace UniVRM10
public void DrawGizmos()
{
var backup = Gizmos.matrix;
Gizmos.matrix = Matrix4x4.identity;
VRM10SpringBoneJoint lastJoint = Joints[0];
for (int i = 1; i < Joints.Count; ++i)
if (Joints.Count > 0)
{
var joint = Joints[i];
Gizmos.color = JointColor(lastJoint);
if (joint != null && lastJoint != null)
var backup = Gizmos.matrix;
Gizmos.matrix = Matrix4x4.identity;
VRM10SpringBoneJoint lastJoint = Joints[0];
for (int i = 1; i < Joints.Count; ++i)
{
Gizmos.DrawLine(lastJoint.transform.position, joint.transform.position);
var joint = Joints[i];
Gizmos.color = JointColor(lastJoint);
if (joint != null && lastJoint != null)
{
Gizmos.DrawLine(lastJoint.transform.position, joint.transform.position);
}
lastJoint = joint;
}
lastJoint = joint;
Gizmos.matrix = backup;
}
Gizmos.matrix = backup;
}
}

View File

@@ -93,7 +93,7 @@ namespace UniVRM10
m_fastSpringBoneService.BufferCombiner.Unregister(m_fastSpringBoneBuffer);
}
m_fastSpringBoneBuffer = await FastSpringBoneBufferFactory.ConstructSpringBoneAsync(new ImmediateCaller(), m_instance, m_fastSpringBoneBuffer);
m_fastSpringBoneBuffer = await FastSpringBoneBufferFactory.ConstructSpringBoneAsync(awaitCaller, m_instance, m_fastSpringBoneBuffer);
// 登録
m_fastSpringBoneService.BufferCombiner.Register(m_fastSpringBoneBuffer);

View File

@@ -94,7 +94,7 @@ namespace UniVRM10
m_bufferCombiner.Unregister(m_fastSpringBoneBuffer);
}
m_fastSpringBoneBuffer = await FastSpringBoneBufferFactory.ConstructSpringBoneAsync(new ImmediateCaller(), m_instance, m_fastSpringBoneBuffer);
m_fastSpringBoneBuffer = await FastSpringBoneBufferFactory.ConstructSpringBoneAsync(awaitCaller, m_instance, m_fastSpringBoneBuffer);
// 登録
m_bufferCombiner.Register(m_fastSpringBoneBuffer);

View File

@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
icon: {fileID: 2800000, guid: ad4861e134018c948ac79793d290f48b, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -7,12 +7,15 @@ using UniGLTF.Extensions.VRMC_vrm_animation;
using UniHumanoid;
using UniJSON;
using UnityEngine;
using VrmLib;
namespace UniVRM10
{
public class VrmAnimationImporter : UniGLTF.ImporterContext
{
VRMC_vrm_animation m_vrma;
ExpressionInfo[] m_expressions;
Material m_defaultMaterial;
public VrmAnimationImporter(GltfData data,
IReadOnlyDictionary<SubAssetKey, UnityEngine.Object> externalObjectMap = null,
@@ -230,8 +233,8 @@ namespace UniVRM10
{
// Expression は AnimationClip を分ける。
// glTFData から関連 Animation を取り除いて、取っておく。
var expressions = IterateExpressions().ToArray();
foreach (var channelIndex in expressions.Select(x => x.ChannelIndex).OrderByDescending(x => x))
m_expressions = IterateExpressions().ToArray();
foreach (var channelIndex in m_expressions.Select(x => x.ChannelIndex).OrderByDescending(x => x))
{
var nodeIndex = Data.GLTF.animations[0].channels[channelIndex].target.node;
Data.GLTF.nodes.RemoveAt(nodeIndex);
@@ -243,11 +246,16 @@ namespace UniVRM10
Data.GLTF.scenes[0].nodes = Data.GLTF.scenes[0].nodes.Take(1).ToArray();
// 可視化メッシュ用マテリアル。base.LoadAsync を呼ぶ前に生成する。
var defaultMaterial = await MaterialFactory.GetDefaultMaterialAsync(awaitCaller);
m_defaultMaterial = await MaterialFactory.GetDefaultMaterialAsync(awaitCaller);
// Humanoid Animation が Gltf アニメーションとしてロードされる
var instance = await base.LoadAsync(awaitCaller, measureTime);
return instance;
}
protected override Task OnLoadHierarchy(IAwaitCaller awaitCaller, Func<string, IDisposable> MeasureTime)
{
// setup humanoid
var humanMap = GetHumanMap();
if (humanMap.Count > 0)
@@ -256,22 +264,23 @@ namespace UniVRM10
//
// avatar
//
var avatar = description.CreateAvatar(instance.Root.transform);
var avatar = description.CreateAvatar(Root.transform);
avatar.name = "Avatar";
// AvatarDescription = description;
var animator = instance.gameObject.AddComponent<Animator>();
var animator = Root.AddComponent<Animator>();
animator.avatar = avatar;
}
if (expressions.Length > 0)
if (m_expressions.Length > 0)
{
var animation = instance.GetComponentOrThrow<Animation>();
var animation = Root.GetComponentOrThrow<Animation>();
var clip = animation.clip;
// m_expressionClip.name = "__expression__";
// Expression の float カーブを追加する
// VrmAnimationInstance の "preset_xx" field に連動する
var gltfAnimation = Data.GLTF.animations[0];
foreach (var expression in expressions)
foreach (var expression in m_expressions)
{
var channel = expression.Channel;
var sampler = gltfAnimation.samplers[channel.sampler];
@@ -284,11 +293,22 @@ namespace UniVRM10
}
}
// VRMA-animation solver
var animationInstance = instance.gameObject.AddComponent<Vrm10AnimationInstance>();
animationInstance.Initialize(expressions.Select(x => x.Key), defaultMaterial);
var animationInstance = Root.AddComponent<Vrm10AnimationInstance>();
return instance;
animationInstance.Initialize(m_expressions.Select(x => x.Key), m_defaultMaterial);
return Task.CompletedTask;
}
public override void TransferOwnership(TakeResponsibilityForDestroyObjectFunc take)
{
var animationInstance = Root.GetComponent<Vrm10AnimationInstance>();
take(SubAssetKey.Create(animationInstance.BoxMan.sharedMesh), animationInstance.BoxMan.sharedMesh);
var animator = Root.GetComponent<Animator>();
take(SubAssetKey.Create(animator.avatar), animator.avatar);
base.TransferOwnership(take);
}
}
}
}

View File

@@ -138,7 +138,7 @@ namespace UniVRM10.Cloth.Viewer
var warp = childchild.gameObject.AddComponent<ClothWarpRoot>();
// Name = name,
// CollisionMask = mask,
warp.BaseSettings.radius = 0.02f;
warp.BaseSettings.Radius = 0.02f;
// Connection = type
transforms.Add(warp);
break;
@@ -179,7 +179,7 @@ namespace UniVRM10.Cloth.Viewer
if (warp != null)
{
// CollisionMask = mask,
warp.BaseSettings.radius = 0.02f;
warp.BaseSettings.Radius = 0.02f;
// Connection = type
transforms.Add(warp);
}

View File

@@ -402,7 +402,7 @@ MonoBehaviour:
onValueChanged:
m_PersistentCalls:
m_Calls: []
m_IsOn: 0
m_IsOn: 1
--- !u!1 &153452228
GameObject:
m_ObjectHideFlags: 0
@@ -686,6 +686,92 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 168425994}
m_CullTransparentMesh: 0
--- !u!1 &172483632
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 172483633}
- component: {fileID: 172483634}
m_Layer: 5
m_Name: AddClothToHips
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &172483633
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 172483632}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1472853923}
- {fileID: 1724807119}
m_Father: {fileID: 339774397}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 162, y: 20}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &172483634
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 172483632}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9085046f02f69544eb97fd06b6048fe2, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Highlighted
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 1472853924}
toggleTransition: 1
graphic: {fileID: 1236232219}
m_Group: {fileID: 0}
onValueChanged:
m_PersistentCalls:
m_Calls: []
m_IsOn: 1
--- !u!1 &175751362
GameObject:
m_ObjectHideFlags: 0
@@ -1407,6 +1493,7 @@ RectTransform:
- {fileID: 1767706907}
- {fileID: 947409974}
- {fileID: 135168672}
- {fileID: 172483633}
- {fileID: 2144476967}
- {fileID: 1194499280}
- {fileID: 153452229}
@@ -5027,6 +5114,81 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1215781541}
m_CullTransparentMesh: 0
--- !u!1 &1236232217
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1236232218}
- component: {fileID: 1236232220}
- component: {fileID: 1236232219}
m_Layer: 5
m_Name: Checkmark
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1236232218
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1236232217}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1472853923}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 20, y: 20}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1236232219
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1236232217}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10901, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!222 &1236232220
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1236232217}
m_CullTransparentMesh: 0
--- !u!1 &1242458542
GameObject:
m_ObjectHideFlags: 0
@@ -6228,6 +6390,82 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_IsOn: 0
--- !u!1 &1472853922
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1472853923}
- component: {fileID: 1472853925}
- component: {fileID: 1472853924}
m_Layer: 5
m_Name: Background
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1472853923
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1472853922}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1236232218}
m_Father: {fileID: 172483633}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 10, y: -10}
m_SizeDelta: {x: 20, y: 20}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1472853924
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1472853922}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!222 &1472853925
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1472853922}
m_CullTransparentMesh: 0
--- !u!1 &1476033060
GameObject:
m_ObjectHideFlags: 0
@@ -6774,6 +7012,85 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1684483641}
m_CullTransparentMesh: 0
--- !u!1 &1724807118
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1724807119}
- component: {fileID: 1724807121}
- component: {fileID: 1724807120}
m_Layer: 5
m_Name: Label
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1724807119
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1724807118}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 172483633}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 9, y: -0.5}
m_SizeDelta: {x: -28, y: -3}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1724807120
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1724807118}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 10
m_MaxSize: 40
m_Alignment: 0
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: Add cloth to Hips
--- !u!222 &1724807121
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1724807118}
m_CullTransparentMesh: 0
--- !u!1 &1761414315
GameObject:
m_ObjectHideFlags: 0
@@ -7224,6 +7541,7 @@ MonoBehaviour:
m_openModel: {fileID: 2009818433}
m_showBoxMan: {fileID: 1767706908}
m_useJob: {fileID: 135168673}
m_addClothToHips: {fileID: 172483634}
m_reconstructSprngBone: {fileID: 2144476968}
m_resetSpringBone: {fileID: 1194499281}
m_pauseSpringBone: {fileID: 153452230}

View File

@@ -21,6 +21,7 @@ namespace UniVRM10.Cloth.Viewer
[Header("Cloth")]
[SerializeField] Toggle m_useJob = default;
[SerializeField] Toggle m_addClothToHips = default;
[SerializeField] Button m_reconstructSprngBone = default;
[SerializeField] Button m_resetSpringBone = default;
[SerializeField] Toggle m_pauseSpringBone = default;
@@ -61,6 +62,7 @@ namespace UniVRM10.Cloth.Viewer
m_showBoxMan = map.Get<Toggle>("ShowBoxMan");
m_useJob = map.Get<Toggle>("UseJob");
m_addClothToHips = map.Get<Toggle>("AddClothToHips");
m_reconstructSprngBone = map.Get<Button>("ReconstcutSpringBone");
m_resetSpringBone = map.Get<Button>("ResetSpringBone");
m_pauseSpringBone = map.Get<Toggle>("PauseSpringBone");
@@ -402,18 +404,20 @@ namespace UniVRM10.Cloth.Viewer
else
{
ClothWarpRuntimeProvider.FromVrm10(vrm,
go => go.AddComponent<ClothWarpRoot>(),
o => GameObject.DestroyImmediate(o));
go => go.AddComponent<ClothWarpRoot>());
}
if (animator.GetBoneTransform(HumanBodyBones.Hips) is var hips)
if (m_addClothToHips.isOn)
{
var cloth = hips.GetComponent<ClothGrid>();
if (cloth == null)
if (animator.GetBoneTransform(HumanBodyBones.Hips) is var hips)
{
cloth = hips.gameObject.AddComponent<ClothGrid>();
cloth.Reset();
cloth.LoopIsClosed = true;
var cloth = hips.GetComponent<ClothGrid>();
if (cloth == null)
{
cloth = hips.gameObject.AddComponent<ClothGrid>();
cloth.Reset();
cloth.LoopIsClosed = true;
}
}
}
}

View File

@@ -4,7 +4,9 @@
"references": [
"GUID:308b348fb80d89d42a9620951b0f60db",
"GUID:e47c917724578cc43b5506c17a27e9a0",
"GUID:3e5d614bc16b50d41bd94c8d7444ca46"
"GUID:3e5d614bc16b50d41bd94c8d7444ca46",
"GUID:8d76e605759c3f64a957d63ef96ada7c",
"GUID:5f875fdc81c40184c8333b9d63c6ddd5"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@@ -1,20 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using UniVRM10;
using UniGLTF;
using System.Linq;
namespace UniVRM10.ClothWarp.Components
{
[CustomEditor(typeof(ClothWarpRoot))]
class WarpRootEditor : Editor
class ClothWarpRootEditor : Editor
{
private ClothWarpRoot m_target;
private Vrm10Instance m_vrm;
private MultiColumnTreeView m_treeview;
VisualElement m_body;
void OnEnable()
{
@@ -27,37 +28,20 @@ namespace UniVRM10.ClothWarp.Components
m_vrm = m_target.GetComponentInParent<Vrm10Instance>();
}
// public override void OnInspectorGUI()
// {
// var n = EditorUtility.GetDirtyCount(m_target.GetInstanceID());
// base.OnInspectorGUI();
// if (n != EditorUtility.GetDirtyCount(m_target.GetInstanceID()))
// {
// if (m_vrm != null)
// {
// if (Application.isPlaying)
// {
// m_vrm.Runtime.SpringBone.SetJointLevel(m_target.transform, m_target.BaseSettings);
// foreach (var p in m_target.Particles)
// {
// m_vrm.Runtime.SpringBone.SetJointLevel(p.Transform, p.GetSettings(m_target.BaseSettings));
// }
// }
// }
// }
// }
void BindColumn<T>(string title, int width, Func<T> makeVisualELmeent, Func<int, bool> enableFunc, string subpath) where T : BindableElement
void BindColumn<T>(MultiColumnTreeView tree, string title,
int width, Func<T> makeVisualELment,
Func<int, bool> enableFunc, string subpath) where T : BindableElement
{
m_treeview.columns.Add(new Column
{
title = title,
width = width,
makeCell = makeVisualELmeent,
bindCell = (v, i) =>
makeCell = makeVisualELment,
bindCell = (v, index) =>
{
if (v is T prop)
{
var i = tree.GetIdForIndex(index);
var sb = new System.Text.StringBuilder();
sb.Append("m_particles.Array.data[");
sb.Append(i);
@@ -85,8 +69,24 @@ namespace UniVRM10.ClothWarp.Components
s.SetEnabled(false);
root.Add(s);
}
root.Add(new PropertyField { bindingPath = nameof(ClothWarpRoot.BaseSettings) });
root.Add(new PropertyField { bindingPath = nameof(ClothWarpRoot.Center) });
root.Add(new IMGUIContainer(() =>
{
foreach (var v in m_target.Validations)
{
v.DrawGUI();
}
}));
m_body = new VisualElement();
root.Add(m_body);
m_body.style.display = m_target.Validations.All(x => x.ErrorLevel < ErrorLevels.Warning)
? DisplayStyle.Flex
: DisplayStyle.None
;
m_body.Add(new PropertyField { bindingPath = nameof(ClothWarpRoot.BaseSettings) });
m_body.Add(new PropertyField { bindingPath = nameof(ClothWarpRoot.Center) });
// root.Add(new PropertyField { bindingPath = "m_particles" });
{
@@ -96,35 +96,43 @@ namespace UniVRM10.ClothWarp.Components
};
m_treeview = new MultiColumnTreeView();
BindColumn("Transform", 120, () => new ObjectField(), (_) => false, "Transform");
BindColumn("Mode", 40, () => new EnumField(), (_) => true, "Mode");
BindColumn("stiffnessForce", 40, () => new FloatField(), isCustom, "Settings.stiffnessForce");
BindColumn("gravityPower", 40, () => new FloatField(), isCustom, "Settings.gravityPower");
BindColumn("gravityDir", 120, () => new Vector3Field(), isCustom, "Settings.gravityDir");
BindColumn("dragForce", 40, () => new FloatField(), isCustom, "Settings.dragForce");
BindColumn("radius", 40, () => new FloatField(), isCustom, "Settings.radius");
BindColumn(m_treeview, "Transform", 120, () => new ObjectField(), (_) => false, "Transform");
BindColumn(m_treeview, "Mode", 40, () => new EnumField(), (_) => true, "Mode");
BindColumn(m_treeview, "Stiffness", 40, () => new FloatField(), isCustom, "Settings.Stiffness");
BindColumn(m_treeview, "Gravity", 120, () => new Vector3Field(), isCustom, "Settings.Gravity");
BindColumn(m_treeview, "Deceleration", 40, () => new FloatField(), isCustom, "Settings.Deceleration");
BindColumn(m_treeview, "Radius", 40, () => new FloatField(), isCustom, "Settings.Radius");
m_treeview.autoExpand = true;
m_treeview.SetRootItems(m_target.m_rootitems);
root.Add(m_treeview);
m_body.Add(m_treeview);
}
root.Add(new PropertyField { bindingPath = nameof(ClothWarpRoot.ColliderGroups) });
m_body.Add(new PropertyField { bindingPath = nameof(ClothWarpRoot.ColliderGroups) });
return root;
}
private void OnValueChanged(SerializedObject so)
{
Debug.Log("Name changed: " + so.targetObject.name);
// var nameProperty = so.FindProperty("m_Name");
if (m_vrm != null)
{
if (Application.isPlaying)
{
m_vrm.Runtime.SpringBone.SetJointLevel(m_target.transform, m_target.BaseSettings.ToBlittableJointMutable());
foreach (var p in m_target.Particles)
{
m_vrm.Runtime.SpringBone.SetJointLevel(p.Transform, p.Settings.ToBlittableJointMutable());
}
}
}
// if (nameProperty.stringValue.Contains(" "))
// _textField.style.backgroundColor = Color.red;
// else
// _textField.style.backgroundColor = StyleKeyword.Null;
m_treeview.RefreshItems();
// m_treeview.SetRootItems(m_target.m_rootitems);
m_body.style.display = m_target.Validations.All(x => x.ErrorLevel < ErrorLevels.Warning)
? DisplayStyle.Flex
: DisplayStyle.None
;
Repaint();
}
@@ -147,7 +155,7 @@ namespace UniVRM10.ClothWarp.Components
p = m_target.GetParticleFromTransform(p.Transform);
var t = p.Transform;
Handles.color = Color.green;
Handles.SphereHandleCap(t.GetInstanceID(), t.position, t.rotation, p.Settings.radius * 2, EventType.Repaint);
Handles.SphereHandleCap(t.GetInstanceID(), t.position, t.rotation, p.Settings.Radius * 2, EventType.Repaint);
}
}
}

View File

@@ -1,7 +1,6 @@
using System.Linq;
using UnityEngine.UIElements;
using UnityEditor;
using UnityEngine;
using UniVRM10;
using UnityEditor.UIElements;
namespace UniVRM10.ClothWarp.Components
@@ -9,60 +8,109 @@ namespace UniVRM10.ClothWarp.Components
[CustomEditor(typeof(ClothWarpRuntimeProvider))]
public class RotateParticleRuntimeProviderEditor : Editor
{
const string FROM_VRM10_MENU = "Replace VRM10 Springs to ClothWarp Warps";
ClothWarpRuntimeProvider _target;
Vrm10Instance _vrm;
[MenuItem(FROM_VRM10_MENU, true)]
public static bool IsFromVrm10()
void OnEnable()
{
var go = Selection.activeGameObject;
if (go == null)
_target = (ClothWarpRuntimeProvider)target;
if (_target != null)
{
return false;
_vrm = _target.GetComponent<Vrm10Instance>();
}
return go.GetComponent<Vrm10Instance>() != null;
}
public override void OnInspectorGUI()
public override VisualElement CreateInspectorGUI()
{
var provider = target as ClothWarpRuntimeProvider;
if (provider == null)
var root = new VisualElement();
root.Bind(serializedObject);
{
return;
var s = new PropertyField { bindingPath = "m_Script" };
s.SetEnabled(false);
root.Add(s);
}
var instance = provider.GetComponent<Vrm10Instance>();
using (new EditorGUI.DisabledScope(instance == null))
root.Add(new PropertyField { bindingPath = nameof(_target.UseJob) });
root.Add(new PropertyField { bindingPath = nameof(_target.Warps) });
root.Add(new PropertyField { bindingPath = nameof(_target.Cloths) });
{
if (GUILayout.Button("Replace VRM10 Springs to ClothWarp Warps"))
var setup = new Foldout { text = "Setup" };
var from_vrm10 = new Button { text = "Load VRM10 Springs to ClothWarp Warps" };
setup.Add(from_vrm10);
from_vrm10.RegisterCallback<ClickEvent>(e =>
{
Undo.IncrementCurrentGroup();
Undo.SetCurrentGroupName(FROM_VRM10_MENU);
Undo.SetCurrentGroupName("Load Vrm-1.0 Springs to ClothWarp Warps");
var undo = Undo.GetCurrentGroup();
Undo.RegisterCompleteObjectUndo(instance, "RegisterCompleteObjectUndo");
ClothWarpRuntimeProvider.FromVrm10(instance, Undo.AddComponent<ClothWarpRoot>, Undo.DestroyObjectImmediate);
Undo.RegisterFullObjectHierarchyUndo(instance.gameObject, "RegisterFullObjectHierarchyUndo");
// attach ClothWarp from VRM10Instance.Springs
ClothWarpRuntimeProvider.FromVrm10(_vrm, Undo.AddComponent<ClothWarpRoot>);
Undo.RegisterFullObjectHierarchyUndo(_vrm.gameObject, "RegisterFullObjectHierarchyUndo");
Undo.RegisterCompleteObjectUndo(provider, "RegisterCompleteObjectUndo");
provider.Reset();
// update ClothWarpRuntimeProvider
Undo.RegisterCompleteObjectUndo(_target, "RegisterCompleteObjectUndo");
_target.Reset();
Undo.CollapseUndoOperations(undo);
}
}
});
using (new EditorGUI.DisabledScope(instance == null || !Application.isPlaying))
{
if (GUILayout.Button("RestoreInitialTransform"))
var clear_vrm10_springs = new Button { text = "Clear Vrm-1.0 springs" };
setup.Add(clear_vrm10_springs);
clear_vrm10_springs.RegisterCallback<ClickEvent>(e =>
{
instance.Runtime.SpringBone.RestoreInitialTransform();
}
Undo.IncrementCurrentGroup();
Undo.SetCurrentGroupName("Clear VRM10 Srpings");
var undo = Undo.GetCurrentGroup();
Undo.RegisterCompleteObjectUndo(_vrm, "RegisterCompleteObjectUndo");
foreach (var spring in _vrm.SpringBone.Springs)
{
if (spring != null)
{
foreach (var joint in spring.Joints)
{
if (joint != null)
{
Undo.DestroyObjectImmediate(joint);
}
}
}
spring.Joints.Clear();
}
_vrm.SpringBone.Springs.Clear();
Undo.RegisterFullObjectHierarchyUndo(_vrm.gameObject, "RegisterFullObjectHierarchyUndo");
Undo.CollapseUndoOperations(undo);
});
var reload = new Button { text = "Reload" };
setup.Add(reload);
reload.RegisterCallback<ClickEvent>(e =>
{
_target.Reset();
});
root.Add(setup);
}
if (GUILayout.Button("Reset"))
{
provider.Reset();
// runtime: reset button
var runtime = new Foldout { text = "Runtime" };
root.Add(runtime);
var button = new Button
{
text = "RestoreInitialTransform",
};
runtime.Add(button);
button.RegisterCallback<ClickEvent>((e) =>
{
_vrm.Runtime.SpringBone.RestoreInitialTransform();
});
}
base.OnInspectorGUI();
return root;
}
}
}

View File

@@ -23,7 +23,7 @@ Base 設定を変更する場合は変える方を列挙設定できる。
Custom と Disable を選択できる。
- ゆれものの根元にアタッチする
- [ ] 子孫に HumanoidBone がある場合にアタッチ不可
- [x] 子孫に HumanoidBone がある場合にアタッチ不可
- [ ] 枝分かれ
- [ ] WarpRoot らからデフォルト以外の Warp を選び出す
- [ ] Center
@@ -126,4 +126,4 @@ WarpRoot2 o=o=o
### Optimize
- [ ] 衝突グループ(現状総当たり)
- [x] 衝突グループ

View File

@@ -10,8 +10,8 @@ namespace UniVRM10.ClothWarp
class ClothRectList
{
readonly List<Transform> _particles;
public List<(SpringConstraint, ClothRect)> List = new();
public readonly ClothGrid[] ClothGrids;
public List<(int, SpringConstraint, ClothRect)> List = new();
public readonly bool[] ClothUsedParticles;
public ClothRectList(List<Transform> particles, Vrm10Instance vrm)
@@ -19,15 +19,16 @@ namespace UniVRM10.ClothWarp
_particles = particles;
ClothUsedParticles = new bool[_particles.Count];
var cloths = vrm.GetComponentsInChildren<ClothGrid>();
foreach (var cloth in cloths)
ClothGrids = vrm.GetComponentsInChildren<ClothGrid>();
for (int i = 0; i < ClothGrids.Length; ++i)
{
AddCloth(cloth, vrm);
AddCloth(i, vrm);
}
}
void AddCloth(ClothGrid cloth, Vrm10Instance vrm)
void AddCloth(int clothGridIndex, Vrm10Instance vrm)
{
var cloth = ClothGrids[clothGridIndex];
for (int i = 1; i < cloth.Warps.Count; ++i)
{
var s0 = cloth.Warps[i - 1];
@@ -53,6 +54,7 @@ namespace UniVRM10.ClothWarp
(c, d) = (d, c);
}
List.Add((
clothGridIndex,
new SpringConstraint(
_particles.IndexOf(a),
_particles.IndexOf(b),
@@ -91,6 +93,7 @@ namespace UniVRM10.ClothWarp
(c, d) = (d, c);
}
List.Add((
clothGridIndex,
new SpringConstraint(
_particles.IndexOf(a),
_particles.IndexOf(b),

View File

@@ -5,7 +5,8 @@
"GUID:3e5d614bc16b50d41bd94c8d7444ca46",
"GUID:e47c917724578cc43b5506c17a27e9a0",
"GUID:8d76e605759c3f64a957d63ef96ada7c",
"GUID:1cd941934d098654fa21a13f28346412"
"GUID:1cd941934d098654fa21a13f28346412",
"GUID:5f875fdc81c40184c8333b9d63c6ddd5"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@@ -11,6 +11,9 @@ using UnityEngine;
namespace UniVRM10.ClothWarp
{
/// <summary>
/// プロトタイプ。非 job
/// </summary>
public class ClothWarpRuntime : IVrm10SpringBoneRuntime
{
Vrm10Instance _vrm;
@@ -58,7 +61,7 @@ namespace UniVRM10.ClothWarp
return Color.gray;
}
public async Task InitializeAsync(Vrm10Instance vrm, IAwaitCaller awaitCaller)
public Task InitializeAsync(Vrm10Instance vrm, IAwaitCaller awaitCaller)
{
_building = true;
_vrm = vrm;
@@ -102,7 +105,7 @@ namespace UniVRM10.ClothWarp
_clothRectCollisions = new();
for (int i = 0; i < _clothRects.List.Count; ++i)
{
var (s, r) = _clothRects.List[i];
var (grid, s, r) = _clothRects.List[i];
_clothRectCollisions.Add(new());
var c = _clothRectCollisions.Last();
c.InitializeColliderSide(_newPos, _colliderGroups, r);
@@ -112,6 +115,8 @@ namespace UniVRM10.ClothWarp
_initialized = true;
_building = false;
return Task.CompletedTask;
}
/// <summary>
@@ -167,7 +172,7 @@ namespace UniVRM10.ClothWarp
// verlet 積分
var time = new FrameTime(deltaTime);
_list.BeginFrame(Env, time, _restPositions);
foreach (var (spring, collision) in _clothRects.List)
foreach (var (gridIndex, spring, collision) in _clothRects.List)
{
// cloth constraint
spring.Resolve(time, _clothFactor, _list._particles);
@@ -189,7 +194,7 @@ namespace UniVRM10.ClothWarp
for (int j = 0; j < _clothRects.List.Count; ++j)
{
var (spring, rect) = _clothRects.List[j];
var (gridIndex, spring, rect) = _clothRects.List[j];
var collision = _clothRectCollisions[j];
// using var prof = new ProfileSample("Collision: Cloth");
// 頂点 abcd は同じ CollisionMask

View File

@@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UniGLTF.SpringBoneJobs.Blittables;
using UniGLTF;
using UnityEngine;
using UnityEngine.UIElements;
using UniVRM10;
namespace UniVRM10.ClothWarp.Components
@@ -17,18 +16,6 @@ namespace UniVRM10.ClothWarp.Components
/// </summary>
public class ClothWarpRoot : MonoBehaviour
{
public static BlittableJointMutable DefaultSetting()
{
return new BlittableJointMutable
{
stiffnessForce = 1.0f,
gravityPower = 0,
gravityDir = new Vector3(0, -1.0f, 0),
dragForce = 0.4f,
radius = 0.02f,
};
}
public enum ParticleMode
{
/// <summary>
@@ -53,28 +40,28 @@ namespace UniVRM10.ClothWarp.Components
{
public Transform Transform;
public ParticleMode Mode;
public BlittableJointMutable Settings;
public Jobs.ParticleSettings Settings;
public Particle(Transform t, ParticleMode mode, BlittableJointMutable settings)
public Particle(Transform t, ParticleMode mode, Jobs.ParticleSettings settings)
{
Transform = t;
Mode = mode;
Settings = settings;
}
public Particle(Transform t, BlittableJointMutable settings)
public Particle(Transform t, Jobs.ParticleSettings settings)
: this(t, ParticleMode.Custom, settings)
{
}
public Particle(Transform t)
: this(t, ParticleMode.Base, DefaultSetting())
: this(t, ParticleMode.Base, Jobs.ParticleSettings.Default)
{
}
}
[SerializeField]
public BlittableJointMutable BaseSettings = DefaultSetting();
public Jobs.ParticleSettings BaseSettings = Jobs.ParticleSettings.Default;
/// <summary>
/// null のときは world root ではなく model root で処理
@@ -98,9 +85,59 @@ namespace UniVRM10.ClothWarp.Components
// 逆引き
Dictionary<Transform, int> m_map = new();
public readonly List<Validation> Validations = new();
bool HasHumanoidBonesInChildren(Animator animator, out Transform t)
{
foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones)))
{
if (bone == HumanBodyBones.LastBone)
{
continue;
}
var b = animator.GetBoneTransform(bone);
if (b != null)
{
for (var parent = b.parent; parent != null; parent = parent.parent)
{
if (parent == transform)
{
t = transform;
return true;
}
}
}
}
t = default;
return false;
}
void OnValidate()
{
m_particles = GetComponentsInChildren<Transform>().Skip(1).Select(x => new Particle(x)).ToList();
Validations.Clear();
if (GetComponentInParent<Animator>() is var animator)
{
if (HasHumanoidBonesInChildren(animator, out var t))
{
Validations.Add(Validation.Error(
"アタッチできません。子孫にHumanoidBoneがあります",
ValidationContext.Create(t)
));
}
}
var backup = m_particles.ToDictionary(x => x.Transform, x => x);
m_particles = GetComponentsInChildren<Transform>().Skip(1).Select(x =>
{
foreach (var particle in m_particles)
{
if (particle.Transform == x)
{
return particle;
}
}
return new Particle(x);
}).ToList();
m_map.Clear();
for (int i = 0; i < m_particles.Count; ++i)
{
@@ -164,7 +201,7 @@ namespace UniVRM10.ClothWarp.Components
}
}
public void SetSettings(Transform t, BlittableJointMutable settings)
public void SetSettings(Transform t, Jobs.ParticleSettings settings)
{
if (t == null) return;
for (int i = 0; i < m_particles.Count; ++i)
@@ -182,7 +219,7 @@ namespace UniVRM10.ClothWarp.Components
public void OnDrawGizmosSelected()
{
Gizmos.DrawSphere(transform.position, BaseSettings.radius);
Gizmos.DrawSphere(transform.position, BaseSettings.Radius);
foreach (var p in Particles)
{
@@ -190,7 +227,7 @@ namespace UniVRM10.ClothWarp.Components
{
continue;
}
Gizmos.DrawWireSphere(p.Transform.position, p.Settings.radius);
Gizmos.DrawWireSphere(p.Transform.position, p.Settings.Radius);
if (TryGetClosestParent(p.Transform, out var parent))
{

View File

@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UniVRM10;
namespace UniVRM10.ClothWarp.Components
{
@@ -17,7 +17,7 @@ namespace UniVRM10.ClothWarp.Components
public List<ClothGrid> Cloths = new();
[SerializeField]
public bool UseJob;
public bool UseJob = true;
IVrm10SpringBoneRuntime m_runtime;
public IVrm10SpringBoneRuntime CreateSpringBoneRuntime()
@@ -45,8 +45,7 @@ namespace UniVRM10.ClothWarp.Components
}
public static void FromVrm10(Vrm10Instance instance,
Func<GameObject, ClothWarpRoot> addWarp,
Action<UnityEngine.Object> deleteObject)
Func<GameObject, ClothWarpRoot> addWarp)
{
foreach (var spring in instance.SpringBone.Springs)
{
@@ -64,29 +63,30 @@ namespace UniVRM10.ClothWarp.Components
var warp = root_joint.GetComponent<ClothWarpRoot>();
if (warp == null)
{
// var warp = Undo.AddComponent<Warp>(root_joint);
warp = addWarp(root_joint);
var joints = spring.Joints.Where(x => x != null).ToArray();
for (int i = 0; i < joints.Length; ++i)
{
var joint = joints[i];
var settings = new UniGLTF.SpringBoneJobs.Blittables.BlittableJointMutable
// mod ?
var stiffness = Mathf.Min(0.08f, joint.m_stiffnessForce * 0.1f);
var settings = new Jobs.ParticleSettings
{
dragForce = joint.m_dragForce,
gravityDir = joint.m_gravityDir,
gravityPower = joint.m_gravityPower,
// mod
stiffnessForce = joint.m_stiffnessForce * 6,
Deceleration = joint.m_dragForce,
Gravity = joint.m_gravityDir * joint.m_gravityPower,
Stiffness = stiffness,
};
if (i == 0)
{
settings.radius = joints[0].m_jointRadius;
settings.Radius = joints[0].m_jointRadius;
warp.BaseSettings = settings;
}
else
{
// breaking change from vrm-1.0
settings.radius = joints[i - 1].m_jointRadius;
settings.Radius = joints[i - 1].m_jointRadius;
var useInheritSettings = warp.BaseSettings.Equals(settings);
if (useInheritSettings)
{
@@ -97,14 +97,10 @@ namespace UniVRM10.ClothWarp.Components
warp.SetSettings(joint.transform, settings);
}
}
// Undo.DestroyObjectImmediate(joint);
deleteObject(joint);
}
spring.Joints.Clear();
warp.ColliderGroups = spring.ColliderGroups.ToList();
}
}
instance.SpringBone.Springs.Clear();
}
}
}

View File

@@ -7,11 +7,13 @@ using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Jobs;
using UniVRM10;
namespace UniVRM10.ClothWarp.Jobs
{
/// <summary>
/// Job 版
/// </summary>
public class ClothWarpJobRuntime : IVrm10SpringBoneRuntime
{
Vrm10Instance _vrm;
@@ -19,12 +21,25 @@ namespace UniVRM10.ClothWarp.Jobs
bool _building = false;
//
// collider
// colliderTransform
//
List<Transform> _colliderTransforms;
TransformAccessArray _colliderTransformAccessArray;
NativeArray<Matrix4x4> _currentColliders;
NativeArray<BlittableCollider> _colliders;
//
// collider
//
List<VRM10SpringBoneCollider> _colliders;
NativeArray<BlittableCollider> _colliderInfo;
//
// colliderGroup
//
List<VRM10SpringBoneColliderGroup> _colliderGroups;
NativeArray<int> _colliderRef;
NativeArray<ArrayRange> _colliderGroup;
NativeArray<int> _colliderGroupRef;
//
// particle
@@ -38,10 +53,10 @@ namespace UniVRM10.ClothWarp.Jobs
NativeArray<Vector3> _nextPositions;
NativeArray<Quaternion> _nextRotations;
NativeArray<Vector3> _strandCollision;
NativeArray<int> _clothCollisionCount;
NativeArray<Vector3> _clothCollisionDelta;
NativeArray<Vector3> _forces;
NativeArray<Vector3> _warpCollision;
NativeArray<int> _rectCollisionCount;
NativeArray<Vector3> _rectCollisionDelta;
NativeArray<Vector3> _impulsiveForces;
//
// warp
@@ -49,16 +64,26 @@ namespace UniVRM10.ClothWarp.Jobs
NativeArray<WarpInfo> _warps;
//
// cloth
// cloth rect
//
NativeArray<bool> _clothUsedParticles;
NativeArray<(SpringConstraint, SphereTriangle.ClothRect)> _clothRects;
NativeArray<(int ClothGridIndex, SpringConstraint SpringConstraint, SphereTriangle.ClothRect Rect)> _clothRects;
NativeArray<(Vector3, Vector3, Vector3, Vector3)> _clothRectResults;
//
// cloth grid
//
NativeArray<ClothInfo> _cloths;
public void Dispose()
{
if (_colliderTransformAccessArray.isCreated) _colliderTransformAccessArray.Dispose();
if (_currentColliders.IsCreated) _currentColliders.Dispose();
if (_colliderRef.IsCreated) _colliderRef.Dispose();
if (_colliderGroup.IsCreated) _colliderGroup.Dispose();
if (_colliderGroupRef.IsCreated) _colliderGroupRef.Dispose();
if (_warps.IsCreated) _warps.Dispose();
if (_transformAccessArray.isCreated) _transformAccessArray.Dispose();
if (_inputData.IsCreated) _inputData.Dispose();
@@ -67,15 +92,17 @@ namespace UniVRM10.ClothWarp.Jobs
if (_prevPositions.IsCreated) _prevPositions.Dispose();
if (_nextPositions.IsCreated) _nextPositions.Dispose();
if (_nextRotations.IsCreated) _nextRotations.Dispose();
if (_strandCollision.IsCreated) _strandCollision.Dispose();
if (_clothCollisionCount.IsCreated) _clothCollisionCount.Dispose();
if (_clothCollisionDelta.IsCreated) _clothCollisionDelta.Dispose();
if (_forces.IsCreated) _forces.Dispose();
if (_warpCollision.IsCreated) _warpCollision.Dispose();
if (_rectCollisionCount.IsCreated) _rectCollisionCount.Dispose();
if (_rectCollisionDelta.IsCreated) _rectCollisionDelta.Dispose();
if (_impulsiveForces.IsCreated) _impulsiveForces.Dispose();
if (_warps.IsCreated) _warps.Dispose();
if (_cloths.IsCreated) _cloths.Dispose();
if (_clothUsedParticles.IsCreated) _clothUsedParticles.Dispose();
if (_clothRects.IsCreated) _clothRects.Dispose();
if (_clothRectResults.IsCreated) _clothRectResults.Dispose();
}
(int index, bool isNew) GetTransformIndex(Transform t,
@@ -97,6 +124,33 @@ namespace UniVRM10.ClothWarp.Jobs
return (i, true);
}
Transform GetParent(Transform t, Transform root)
{
for (var parent = t.parent; parent != null; parent = parent.parent)
{
if (_transforms.Contains(parent))
{
return parent;
}
if (parent == root)
{
break;
}
}
throw new Exception();
}
int GetOrAddColliderTransform(Transform t)
{
var index = _colliderTransforms.IndexOf(t);
if (index == -1)
{
index = _colliderTransforms.Count;
_colliderTransforms.Add(t);
}
return index;
}
public async Task InitializeAsync(Vrm10Instance vrm, IAwaitCaller awaitCaller)
{
_vrm = vrm;
@@ -111,30 +165,67 @@ namespace UniVRM10.ClothWarp.Jobs
//
// colliders
//
_colliders = new();
_colliderGroups = new();
_colliderTransforms = new();
List<BlittableCollider> colliders = new();
foreach (var collider in vrm.GetComponentsInChildren<VRM10SpringBoneCollider>())
List<BlittableCollider> colliderInfo = new();
List<int> colliderRef = new();
List<ArrayRange> colliderGroups = new();
foreach (var colliderGroup in vrm.GetComponentsInChildren<VRM10SpringBoneColliderGroup>())
{
colliders.Add(new BlittableCollider
if (colliderGroup == null)
{
offset = collider.Offset,
radius = collider.Radius,
tailOrNormal = collider.TailOrNormal,
colliderType = TranslateColliderType(collider.ColliderType)
continue;
}
var startColliderRef = colliderRef.Count;
foreach (var collider in colliderGroup.Colliders)
{
if (collider == null)
{
continue;
}
if (_colliders.Contains(collider))
{
continue;
}
var colliderTransformIndex = GetOrAddColliderTransform(collider.transform);
colliderRef.Add(colliderInfo.Count);
colliderInfo.Add(new BlittableCollider
{
offset = collider.Offset,
radius = collider.Radius,
tailOrNormal = collider.TailOrNormal,
colliderType = TranslateColliderType(collider.ColliderType),
transformIndex = colliderTransformIndex,
});
_colliders.Add(collider);
}
_colliderGroups.Add(colliderGroup);
colliderGroups.Add(new ArrayRange
{
Start = startColliderRef,
End = colliderRef.Count,
});
_colliderTransforms.Add(collider.transform);
}
_colliderTransformAccessArray = new(_colliderTransforms.ToArray(), 128);
_colliders = new(colliders.ToArray(), Allocator.Persistent);
_currentColliders = new(_colliderTransforms.Count, Allocator.Persistent);
_colliderInfo = new(colliderInfo.ToArray(), Allocator.Persistent);
_colliderRef = new(colliderRef.ToArray(), Allocator.Persistent);
_colliderGroup = new(colliderGroups.ToArray(), Allocator.Persistent);
//
// warps
// warps => particles
//
_transforms = new();
List<TransformInfo> info = new();
List<Vector3> positions = new();
List<WarpInfo> warps = new();
List<int> colliderGroupRef = new();
var warpSrcs = vrm.GetComponentsInChildren<Components.ClothWarpRoot>();
for (int warpIndex = 0; warpIndex < warpSrcs.Length; ++warpIndex)
{
@@ -145,14 +236,16 @@ namespace UniVRM10.ClothWarp.Jobs
{
GetTransformIndex(warp.Center, new TransformInfo
{
TransformType = TransformType.Center
TransformType = TransformType.Center,
WarpIndex = warpIndex,
}, info, positions);
start += 1;
}
var warpRootParentTransformIndex = GetTransformIndex(warp.transform.parent, new TransformInfo
{
TransformType = TransformType.WarpRootParent
TransformType = TransformType.WarpRootParent,
WarpIndex = warpIndex,
}, info, positions);
Debug.Assert(warpRootParentTransformIndex.index != -1);
if (warpRootParentTransformIndex.isNew)
@@ -166,31 +259,81 @@ namespace UniVRM10.ClothWarp.Jobs
ParentIndex = warpRootParentTransformIndex.index,
InitLocalPosition = vrm.DefaultTransformStates[warp.transform].LocalPosition,
InitLocalRotation = vrm.DefaultTransformStates[warp.transform].LocalRotation,
Settings = warp.BaseSettings,
WarpIndex = warpIndex,
}, info, positions);
Debug.Assert(warpRootTransformIndex.index != -1);
Debug.Assert(warpRootTransformIndex.isNew);
var parentIndex = warpRootTransformIndex.index;
foreach (var particle in warp.Particles)
var colliderGroupRefStart = colliderGroupRef.Count;
if (warpRootTransformIndex.isNew)
{
if (particle.Transform != null && particle.Mode != Components.ClothWarpRoot.ParticleMode.Disabled)
// var parentIndex = warpRootTransformIndex.index;
Func<int, int> GetFirstSiblingIndex = (parent) =>
{
var outputParticleTransformIndex = GetTransformIndex((Transform)particle.Transform, new TransformInfo
for (int i = 0; i < info.Count; ++i)
{
TransformType = TransformType.Particle,
ParentIndex = parentIndex,
InitLocalPosition = vrm.DefaultTransformStates[(Transform)particle.Transform].LocalPosition,
InitLocalRotation = vrm.DefaultTransformStates[(Transform)particle.Transform].LocalRotation,
Settings = particle.Settings,
}, info, positions);
parentIndex = outputParticleTransformIndex.index;
if (info[i].ParentIndex == parent)
{
return i;
}
}
throw new Exception();
};
HashSet<int> parentIndexSet = new();
foreach (var particle in warp.Particles)
{
if (particle.Transform != null && particle.Mode != Components.ClothWarpRoot.ParticleMode.Disabled)
{
var parentIndex = _transforms.IndexOf(GetParent(particle.Transform, warp.transform));
BranchInfo? branch = default;
if (parentIndexSet.Contains(parentIndex))
{
branch = new BranchInfo
{
FirstSiblingIndex = GetFirstSiblingIndex(parentIndex),
};
}
else
{
parentIndexSet.Add(parentIndex);
}
var outputParticleTransformIndex = GetTransformIndex(particle.Transform, new TransformInfo
{
TransformType = TransformType.Particle,
ParentIndex = parentIndex,
InitLocalPosition = vrm.DefaultTransformStates[particle.Transform].LocalPosition,
InitLocalRotation = vrm.DefaultTransformStates[particle.Transform].LocalRotation,
Settings = particle.Settings,
WarpIndex = warpIndex,
Branch = branch,
}, info, positions);
// parentIndex = outputParticleTransformIndex.index;
}
}
foreach (var group in warp.ColliderGroups)
{
if (group != null)
{
colliderGroupRef.Add(_colliderGroups.IndexOf(group));
}
}
}
warps.Add(new WarpInfo
{
StartIndex = start,
EndIndex = _transforms.Count,
PrticleRange = new ArrayRange
{
Start = start,
End = _transforms.Count,
},
ColliderGroupRefRange = new ArrayRange
{
Start = colliderGroupRefStart,
End = colliderGroupRef.Count,
},
});
await awaitCaller.NextFrame();
@@ -204,18 +347,48 @@ namespace UniVRM10.ClothWarp.Jobs
_nextPositions = new(pos.Length, Allocator.Persistent);
_nextRotations = new(pos.Length, Allocator.Persistent);
_info = new(info.ToArray(), Allocator.Persistent);
_strandCollision = new(pos.Length, Allocator.Persistent);
_clothCollisionCount = new(pos.Length, Allocator.Persistent);
_clothCollisionDelta = new(pos.Length, Allocator.Persistent);
_forces = new(pos.Length, Allocator.Persistent);
_warpCollision = new(pos.Length, Allocator.Persistent);
_rectCollisionCount = new(pos.Length, Allocator.Persistent);
_rectCollisionDelta = new(pos.Length, Allocator.Persistent);
_impulsiveForces = new(pos.Length, Allocator.Persistent);
//
// cloths
//
var clothRects = new ClothRectList(_transforms, vrm);
_clothRects = new(clothRects.List.ToArray(), Allocator.Persistent);
_clothRectResults = new(clothRects.List.Count, Allocator.Persistent);
_clothUsedParticles = new(clothRects.ClothUsedParticles, Allocator.Persistent);
_building = false;
List<ClothInfo> cloths = new();
foreach (var grid in clothRects.ClothGrids)
{
var colliderGroupRefStart = colliderGroupRef.Count;
HashSet<VRM10SpringBoneColliderGroup> groups = new();
foreach (var warp in grid.Warps)
{
foreach (var group in warp.ColliderGroups)
{
if (group != null && !groups.Contains(group))
{
groups.Add(group);
colliderGroupRef.Add(_colliderGroups.IndexOf(group));
}
}
}
cloths.Add(new ClothInfo
{
ColliderGroupRefRange = new ArrayRange
{
Start = colliderGroupRefStart,
End = colliderGroupRef.Count,
},
});
}
_cloths = new(cloths.ToArray(), Allocator.Persistent);
_colliderGroupRef = new(colliderGroupRef.ToArray(), Allocator.Persistent);
}
private static BlittableColliderType TranslateColliderType(VRM10SpringBoneColliderTypes colliderType)
@@ -259,10 +432,10 @@ namespace UniVRM10.ClothWarp.Jobs
Info = _info,
InputData = _inputData,
CurrentPositions = _currentPositions,
Forces = _forces,
ImpulsiveForces = _impulsiveForces,
CollisionCount = _clothCollisionCount,
CollisionDelta = _clothCollisionDelta,
CollisionCount = _rectCollisionCount,
CollisionDelta = _rectCollisionDelta,
}.Schedule(_transformAccessArray, handle);
// spring(cloth weft)
@@ -271,8 +444,8 @@ namespace UniVRM10.ClothWarp.Jobs
ClothRects = _clothRects,
CurrentPositions = _currentPositions,
Force = _forces,
}.Schedule(_clothRects.Length, 128, handle);
ImpulsiveForces = _impulsiveForces,
}.Schedule(_clothRects.Length, 1, handle);
// verlet
handle = new VerletJob
@@ -282,58 +455,86 @@ namespace UniVRM10.ClothWarp.Jobs
CurrentTransforms = _inputData,
PrevPositions = _prevPositions,
CurrentPositions = _currentPositions,
Forces = _forces,
ImpulsiveForces = _impulsiveForces,
NextPositions = _nextPositions,
NextRotations = _nextRotations,
}.Schedule(_info.Length, 128, handle);
}.Schedule(_info.Length, 1, handle);
// 親子の長さで拘束
handle = new ParentLengthConstraintJob
{
Warps = _warps,
Info = _info,
Data = _inputData,
NextPositions = _nextPositions,
}.Schedule(_warps.Length, 16, handle);
}.Schedule(_warps.Length, 1, handle);
// collision
{
var handle0 = new StrandCollisionJob
var handle0 = new WarpCollisionJob
{
Colliders = _colliders,
Colliders = _colliderInfo,
CurrentColliders = _currentColliders,
Info = _info,
NextPositions = _nextPositions,
ClothUsedParticles = _clothUsedParticles,
StrandCollision = _strandCollision,
}.Schedule(_info.Length, 128, handle);
StrandCollision = _warpCollision,
var handle1 = new ClothCollisionJob
Warps = _warps,
ColliderGroupRef = _colliderGroupRef,
ColliderGroup = _colliderGroup,
ColliderRef = _colliderRef,
}.Schedule(_info.Length, 1, handle);
var handle1 = new RectCollisionJob
{
Colliders = _colliders,
ClothRects = _clothRects,
ClothRectResults = _clothRectResults,
Colliders = _colliderInfo,
CurrentColliders = _currentColliders,
Info = _info,
NextPositions = _nextPositions,
CollisionCount = _clothCollisionCount,
CollisionDelta = _clothCollisionDelta,
Cloths = _cloths,
ColliderGroupRef = _colliderGroupRef,
ColliderGroup = _colliderGroup,
ColliderRef = _colliderRef,
}.Schedule(_clothRects.Length, 1, handle);
handle1 = new RectCollisionReduceJob
{
ClothRects = _clothRects,
}.Schedule(_clothRects.Length, 128, handle);
ClothRectResults = _clothRectResults,
NextPositions = _nextPositions,
RectCollisionCount = _rectCollisionCount,
RectCollisionDelta = _rectCollisionDelta,
}.Schedule(handle1);
handle = JobHandle.CombineDependencies(handle0, handle1);
handle = new CollisionApplyJob
{
ClothUsedParticles = _clothUsedParticles,
StrandCollision = _strandCollision,
ClothCollisionCount = _clothCollisionCount,
ClothCollisionDelta = _clothCollisionDelta,
StrandCollision = _warpCollision,
RectCollisionCount = _rectCollisionCount,
RectCollisionDelta = _rectCollisionDelta,
NextPosition = _nextPositions,
}.Schedule(_info.Length, 128, handle);
}.Schedule(_info.Length, 1, handle);
}
// 親子の長さで拘束. TODO: ApplyRotationJob と合体
handle = new ParentLengthConstraintJob
{
Warps = _warps,
Info = _info,
Data = _inputData,
NextPositions = _nextPositions,
}.Schedule(_warps.Length, 1, handle);
// NextPositions から NextRotations を作る
handle = new ApplyRotationJob
{
@@ -342,7 +543,7 @@ namespace UniVRM10.ClothWarp.Jobs
CurrentTransforms = _inputData,
NextPositions = _nextPositions,
NextRotations = _nextRotations,
}.Schedule(_warps.Length, 16, handle);
}.Schedule(_warps.Length, 1, handle);
// output
handle = new OutputTransformJob
@@ -362,7 +563,7 @@ namespace UniVRM10.ClothWarp.Jobs
{
foreach (var warp in _warps)
{
for (int i = warp.StartIndex; i < warp.EndIndex; ++i)
for (int i = warp.PrticleRange.Start; i < warp.PrticleRange.End; ++i)
{
var p = _info[i];
var t = _transforms[i];
@@ -392,11 +593,11 @@ namespace UniVRM10.ClothWarp.Jobs
break;
case TransformType.ClothWarp:
Gizmos.color = Color.white;
Gizmos.DrawSphere(v, info.Settings.radius);
Gizmos.DrawSphere(v, info.Settings.Radius);
break;
case TransformType.Particle:
Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere(v, info.Settings.radius);
Gizmos.DrawWireSphere(v, info.Settings.Radius);
break;
}
}
@@ -408,7 +609,7 @@ namespace UniVRM10.ClothWarp.Jobs
if (i != -1)
{
var info = _info[i];
info.Settings = jointSettings;
info.Settings.FromBlittableJointMutable(jointSettings);
_info[i] = info;
}
}

View File

@@ -1,319 +0,0 @@
using System;
using System.Collections.Generic;
using SphereTriangle;
using UniGLTF.SpringBoneJobs.Blittables;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Jobs;
namespace UniVRM10.ClothWarp.Jobs
{
public struct InputColliderJob : IJobParallelForTransform
{
[WriteOnly] public NativeArray<Matrix4x4> CurrentCollider;
public void Execute(int colliderIndex, TransformAccess transform)
{
CurrentCollider[colliderIndex] = transform.localToWorldMatrix;
}
}
public struct StrandCollisionJob : IJobParallelFor
{
// collider
[ReadOnly] public NativeArray<BlittableCollider> Colliders;
[ReadOnly] public NativeArray<Matrix4x4> CurrentColliders;
// particle
[ReadOnly] public NativeArray<TransformInfo> Info;
[ReadOnly] public NativeArray<Vector3> NextPositions;
[ReadOnly] public NativeArray<bool> ClothUsedParticles;
[WriteOnly] public NativeArray<Vector3> StrandCollision;
public void Execute(int particleIndex)
{
if (!ClothUsedParticles[particleIndex])
{
var info = Info[particleIndex];
var pos = NextPositions[particleIndex];
for (int colliderIndex = 0; colliderIndex < Colliders.Length; ++colliderIndex)
{
var c = Colliders[colliderIndex];
var m = CurrentColliders[colliderIndex];
if (c.colliderType == BlittableColliderType.Capsule)
{
if (TryCollideCapsuleAndSphere(m.MultiplyPoint(c.offset), m.MultiplyPoint(c.tailOrNormal), c.radius,
pos, info.Settings.radius, out var l))
{
pos += l.GetDelta(c.radius);
}
}
else
{
if (TryCollideSphereAndSphere(m.MultiplyPoint(c.offset), c.radius,
pos, info.Settings.radius, out var l))
{
pos += l.GetDelta(c.radius);
}
}
StrandCollision[particleIndex] = pos;
}
}
}
/// <summary>
/// collide sphere a and sphere b.
/// move sphere b to resolved if collide.
/// </summary>
/// <param name="from"></param>
/// <param name="ra"></param>
/// <param name="to"></param>
/// <param name="ba"></param>
/// <param name="resolved"></param>
/// <returns></returns>
static bool TryCollideSphereAndSphere(
in Vector3 from, float ra,
in Vector3 to, float rb,
out LineSegment resolved
)
{
var d = Vector3.Distance(from, to);
if (d > (ra + rb))
{
resolved = default;
return false;
}
Vector3 normal = (to - from).normalized;
resolved = new(from, from + normal * (d - rb));
return true;
}
/// <summary>
/// collide capsule and sphere b.
/// move sphere b to resolved if collide.
/// </summary>
/// <param name="capsuleHead"></param>
/// <param name="capsuleTail"></param>
/// <param name="capsuleRadius"></param>
/// <param name="b"></param>
/// <param name="rb"></param>
static bool TryCollideCapsuleAndSphere(
in Vector3 capsuleHead,
in Vector3 capsuleTail,
float capsuleRadius,
in Vector3 b,
float rb,
out LineSegment resolved
)
{
var P = (capsuleTail - capsuleHead).normalized;
var Q = b - capsuleHead;
var dot = Vector3.Dot(P, Q);
if (dot <= 0)
{
// head側半球の球判定
return TryCollideSphereAndSphere(capsuleHead, capsuleRadius, b, rb, out resolved);
}
var t = dot / P.magnitude;
if (t >= 1.0f)
{
// tail側半球の球判定
return TryCollideSphereAndSphere(capsuleTail, capsuleRadius, b, rb, out resolved);
}
// head-tail上の m_transform.position との最近点
var p = capsuleHead + P * t;
return TryCollideSphereAndSphere(p, capsuleRadius, b, rb, out resolved);
}
}
public struct ClothCollisionJob : IJobParallelFor
{
// collider
[ReadOnly] public NativeArray<BlittableCollider> Colliders;
[ReadOnly] public NativeArray<Matrix4x4> CurrentColliders;
// particle
[ReadOnly] public NativeArray<TransformInfo> Info;
[ReadOnly] public NativeArray<Vector3> NextPositions;
[NativeDisableParallelForRestriction] public NativeArray<int> CollisionCount;
[NativeDisableParallelForRestriction] public NativeArray<Vector3> CollisionDelta;
// cloth
[ReadOnly] public NativeArray<(SpringConstraint, ClothRect)> ClothRects;
private void CollisionMove(int particleIndex, Vector3 delta)
{
CollisionCount[particleIndex] += 1;
CollisionDelta[particleIndex] += delta;
}
public void Execute(int rectIndex)
{
var (spring, rect) = ClothRects[rectIndex];
// using (new ProfileSample("Rect: Prepare"))
// _s0.BeginFrame();
// _s1.BeginFrame();
var a = NextPositions[rect._a];
var b = NextPositions[rect._b];
var c = NextPositions[rect._c];
var d = NextPositions[rect._d];
var aabb = GetBoundsFrom4(a, b, c, d);
// d x-x c
// |/
// a x
var _triangle1 = new Triangle(c, d, a);
// x c
// /|
// a x-x b
var _triangle0 = new Triangle(a, b, c);
for (int colliderIndex = 0; colliderIndex < Colliders.Length; ++colliderIndex)
{
var collider = Colliders[colliderIndex];
var collider_matrix = CurrentColliders[colliderIndex];
if (!aabb.Intersects(GetBounds(collider, collider_matrix)))
{
continue;
}
// 面の片側だけにヒットさせる
// 行き過ぎて戻るときに素通りする
// var p = _triangle0.Plane.ClosestPointOnPlane(col_pos);
// var dot = Vector3.Dot(_triangle0.Plane.normal, col_pos - p);
// if (_initialColliderNormalSide[collider] * dot < 0)
// {
// // 片側
// continue;
// }
if (TryCollide(collider, collider_matrix, _triangle0, out var l0))
{
CollisionMove(rect._a, l0.GetDelta(collider.radius));
CollisionMove(rect._b, l0.GetDelta(collider.radius));
CollisionMove(rect._c, l0.GetDelta(collider.radius));
}
if (TryCollide(collider, collider_matrix, _triangle1, out var l1))
{
CollisionMove(rect._c, l1.GetDelta(collider.radius));
CollisionMove(rect._d, l1.GetDelta(collider.radius));
CollisionMove(rect._a, l1.GetDelta(collider.radius));
}
}
}
static bool TryCollide(BlittableCollider collider, in Matrix4x4 colliderMatrix, in Triangle t, out LineSegment l)
{
var col_pos = colliderMatrix.MultiplyPoint(collider.offset);
if (collider.colliderType == BlittableColliderType.Capsule)
{
// capsule
var tail_pos = colliderMatrix.MultiplyPoint(collider.tailOrNormal);
var result = TriangleCapsuleCollisionSolver.Collide(t, new LineSegment(col_pos, tail_pos), collider.radius);
var type = result.TryGetClosest(out l);
return type.HasValue;
}
else
{
// sphere
return TryCollideSphere(t, col_pos, collider.radius, out l);
}
}
/// <summary>
///
/// </summary>
/// <param name="triangle"></param>
/// <param name="collider"></param>
/// <param name="radius"></param>
/// <returns>collider => 衝突点 への線分を返す</returns>
static bool TryCollideSphere(in Triangle triangle, in Vector3 collider, float radius, out LineSegment l)
{
var p = triangle.Plane.ClosestPointOnPlane(collider);
var distance = Vector3.Distance(p, collider);
if (distance > radius)
{
l = default;
return false;
}
if (triangle.IsSameSide(p))
{
l = new LineSegment(collider, p);
return true;
}
var (closestPoint, d) = triangle.GetClosest(collider);
if (d > radius)
{
l = default;
return false;
}
l = new LineSegment(collider, closestPoint);
return true;
}
public static Bounds GetBoundsFrom4(in Vector3 a, in Vector3 b, in Vector3 c, in Vector3 d)
{
var aabb = new Bounds(a, Vector3.zero);
aabb.Encapsulate(b);
aabb.Encapsulate(c);
aabb.Encapsulate(d);
return aabb;
}
public static Bounds GetBounds(BlittableCollider collider, Matrix4x4 m)
{
switch (collider.colliderType)
{
case BlittableColliderType.Capsule:
{
var h = m.MultiplyPoint(collider.offset);
var t = m.MultiplyPoint(collider.tailOrNormal);
var d = h - t;
var aabb = new Bounds((h + t) * 0.5f, new Vector3(Mathf.Abs(d.x), Mathf.Abs(d.y), Mathf.Abs(d.z)));
aabb.Expand(collider.radius * 2);
return aabb;
}
case BlittableColliderType.Sphere:
return new Bounds(m.MultiplyPoint(collider.offset), new Vector3(collider.radius, collider.radius, collider.radius));
default:
throw new NotImplementedException();
}
}
}
public struct CollisionApplyJob : IJobParallelFor
{
[ReadOnly] public NativeArray<bool> ClothUsedParticles;
[ReadOnly] public NativeArray<Vector3> StrandCollision;
[ReadOnly] public NativeArray<int> ClothCollisionCount;
[ReadOnly] public NativeArray<Vector3> ClothCollisionDelta;
[NativeDisableParallelForRestriction] public NativeArray<Vector3> NextPosition;
public void Execute(int particleIndex)
{
if (ClothUsedParticles[particleIndex])
{
if (ClothCollisionCount[particleIndex] > 0)
{
NextPosition[particleIndex] += (ClothCollisionDelta[particleIndex] / ClothCollisionCount[particleIndex]);
}
}
else
{
NextPosition[particleIndex] = StrandCollision[particleIndex];
}
}
}
}

View File

@@ -0,0 +1,51 @@
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Jobs;
namespace UniVRM10.ClothWarp.Jobs
{
public struct ClothInfo
{
public ArrayRange ColliderGroupRefRange;
}
public struct InputColliderJob : IJobParallelForTransform
{
[WriteOnly] public NativeArray<Matrix4x4> CurrentCollider;
public void Execute(int colliderIndex, TransformAccess transform)
{
CurrentCollider[colliderIndex] = transform.localToWorldMatrix;
}
}
public struct CollisionApplyJob : IJobParallelFor
{
[ReadOnly] public NativeArray<bool> ClothUsedParticles;
[ReadOnly] public NativeArray<Vector3> StrandCollision;
[ReadOnly] public NativeArray<int> RectCollisionCount;
[ReadOnly] public NativeArray<Vector3> RectCollisionDelta;
public NativeArray<Vector3> NextPosition;
public void Execute(int particleIndex)
{
if (ClothUsedParticles[particleIndex])
{
var count = RectCollisionCount[particleIndex];
if (count > 0)
{
// 一つの頂点が最大で近接する4つの rect で当たり判定をされる
// 衝突結果を足して割る
NextPosition[particleIndex] += RectCollisionDelta[particleIndex] / count;
}
}
else
{
NextPosition[particleIndex] = StrandCollision[particleIndex];
}
}
}
}

View File

@@ -12,16 +12,31 @@ namespace UniVRM10.ClothWarp.Jobs
{
[ReadOnly] public NativeArray<WarpInfo> Warps;
[ReadOnly] public NativeArray<TransformInfo> Info;
[ReadOnly] public NativeArray<TransformData> Data;
[NativeDisableParallelForRestriction] public NativeArray<Vector3> NextPositions;
public void Execute(int warpIndex)
{
var warp = Warps[warpIndex];
for (int particleIndex = warp.StartIndex; particleIndex < warp.EndIndex - 1; ++particleIndex)
for (int particleIndex = warp.PrticleRange.Start; particleIndex < warp.PrticleRange.End - 1; ++particleIndex)
{
// 位置を長さで拘束
NextPositions[particleIndex + 1] = NextPositions[particleIndex] +
(NextPositions[particleIndex + 1] - NextPositions[particleIndex]).normalized
* Info[particleIndex + 1].InitLocalPosition.magnitude;
var particle = Info[particleIndex + 1];
if (particle.Branch.HasValue)
{
var branch = particle.Branch.Value;
// 1番目の兄弟の情報を使う
// 枝分かれ。特別処理
var firstSibling = Info[branch.FirstSiblingIndex];
var firstPosition = NextPositions[branch.FirstSiblingIndex];
var local_d = particle.InitLocalPosition - firstSibling.InitLocalPosition;
NextPositions[particleIndex + 1] = firstPosition + Data[particle.ParentIndex].Rotation * local_d;
}
else
{
// 位置を長さで拘束
NextPositions[particleIndex + 1] = NextPositions[particleIndex] +
(NextPositions[particleIndex + 1] - NextPositions[particleIndex]).normalized
* Info[particleIndex + 1].InitLocalPosition.magnitude;
}
}
}
}
@@ -29,21 +44,20 @@ namespace UniVRM10.ClothWarp.Jobs
public struct WeftConstraintJob : IJobParallelFor
{
public float Hookean;
FrameInfo Frame;
[ReadOnly] public NativeArray<(SpringConstraint, ClothRect)> ClothRects;
[ReadOnly] public NativeArray<(int, SpringConstraint, ClothRect)> ClothRects;
[ReadOnly] public NativeArray<Vector3> CurrentPositions;
[NativeDisableParallelForRestriction] public NativeArray<Vector3> Force;
[NativeDisableParallelForRestriction] public NativeArray<Vector3> ImpulsiveForces;
public void Execute(int rectIndex)
{
var (spring, rect) = ClothRects[rectIndex];
var (clothGridIndex, spring, rect) = ClothRects[rectIndex];
var p0 = CurrentPositions[spring._p0];
var p1 = CurrentPositions[spring._p1];
var d = Vector3.Distance(p0, p1);
var f = (d - spring._rest) * Hookean;
var dx = (p1 - p0).normalized * f / Frame.SqDeltaTime;
Force[spring._p0] += dx;
Force[spring._p1] -= dx;
var dx = (p1 - p0).normalized * f;
ImpulsiveForces[spring._p0] += dx;
ImpulsiveForces[spring._p1] -= dx;
}
}
@@ -60,7 +74,7 @@ namespace UniVRM10.ClothWarp.Jobs
public void Execute(int warpIndex)
{
var warp = Warps[warpIndex];
for (int particleIndex = warp.StartIndex; particleIndex < warp.EndIndex - 1; ++particleIndex)
for (int particleIndex = warp.PrticleRange.Start; particleIndex < warp.PrticleRange.End - 1; ++particleIndex)
{
//回転を適用
var p = Info[particleIndex];

View File

@@ -1,66 +1,52 @@
using System;
using UniGLTF.SpringBoneJobs.Blittables;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Jobs;
namespace UniVRM10.ClothWarp.Jobs
{
public struct TransformInfo
/// <summary>
/// UniGLTF.SpringBoneJobs.Blittables.BlittableJointMutable と同じ。
/// Range が違う。
/// </summary>
[Serializable]
public struct ParticleSettings
{
public TransformType TransformType;
public int ParentIndex;
public Quaternion InitLocalRotation;
public Vector3 InitLocalPosition;
public BlittableJointMutable Settings;
}
[Range(0, 1)]
public float Stiffness;
[Range(0, 1)]
public float Deceleration;
public Vector3 Gravity;
public float Radius;
public struct TransformData
{
public Matrix4x4 ToWorld;
public Vector3 Position => ToWorld.GetPosition();
public Quaternion Rotation => ToWorld.rotation;
public Matrix4x4 ToLocal;
public TransformData(TransformAccess t)
public static readonly ParticleSettings Default = new ParticleSettings
{
ToWorld = t.localToWorldMatrix;
ToLocal = t.worldToLocalMatrix;
Stiffness = 0.08f,
Gravity = new Vector3(0, -1.0f, 0),
Deceleration = 0.4f,
Radius = 0.02f,
};
public void FromBlittableJointMutable(BlittableJointMutable src)
{
Stiffness = src.stiffnessForce;
Gravity = src.gravityDir * src.gravityPower;
Deceleration = src.dragForce;
Radius = src.radius;
}
public TransformData(Transform t)
public BlittableJointMutable ToBlittableJointMutable()
{
ToWorld = t.localToWorldMatrix;
ToLocal = t.worldToLocalMatrix;
}
}
// [Input]
public struct InputTransformJob : IJobParallelForTransform
{
[ReadOnly] public NativeArray<TransformInfo> Info;
[WriteOnly] public NativeArray<TransformData> InputData;
[WriteOnly] public NativeArray<Vector3> CurrentPositions;
[WriteOnly] public NativeArray<int> CollisionCount;
[WriteOnly] public NativeArray<Vector3> CollisionDelta;
[WriteOnly] public NativeArray<Vector3> Forces;
public void Execute(int particleIndex, TransformAccess transform)
{
InputData[particleIndex] = new TransformData(transform);
var particle = Info[particleIndex];
if (particle.TransformType.PositionInput())
return new BlittableJointMutable
{
// only warp root position update
CurrentPositions[particleIndex] = transform.position;
}
// clear cloth
CollisionCount[particleIndex] = 0;
CollisionDelta[particleIndex] = Vector3.zero;
Forces[particleIndex] = Vector3.zero;
stiffnessForce = Stiffness,
gravityPower = 1.0f,
gravityDir = Gravity,
dragForce = Deceleration,
radius = Radius,
};
}
}
@@ -69,7 +55,7 @@ namespace UniVRM10.ClothWarp.Jobs
public FrameInfo Frame;
[ReadOnly] public NativeArray<TransformInfo> Info;
[ReadOnly] public NativeArray<TransformData> CurrentTransforms;
[ReadOnly] public NativeArray<Vector3> Forces;
[ReadOnly] public NativeArray<Vector3> ImpulsiveForces;
[ReadOnly] public NativeArray<Vector3> CurrentPositions;
[ReadOnly] public NativeArray<Vector3> PrevPositions;
[WriteOnly] public NativeArray<Vector3> NextPositions;
@@ -80,18 +66,19 @@ namespace UniVRM10.ClothWarp.Jobs
var particle = Info[particleIndex];
if (particle.TransformType.Movable())
{
var parentIndex = particle.ParentIndex;
// var parentPosition = CurrentPositions[parentIndex];
var parent = Info[parentIndex];
var parent = Info[particle.ParentIndex];
var parentParentRotation = CurrentTransforms[parent.ParentIndex].Rotation;
var external = (particle.Settings.gravityDir * particle.Settings.gravityPower + Frame.Force) * Frame.DeltaTime;
var local_rest = parentParentRotation * parent.InitLocalRotation * particle.InitLocalPosition;
var world_rest = CurrentPositions[particle.ParentIndex] + local_rest;
var resilience_force = world_rest - CurrentPositions[particleIndex];
var velocity = (CurrentPositions[particleIndex] - PrevPositions[particleIndex]) * (1.0f - particle.Settings.Deceleration);
var newPosition = CurrentPositions[particleIndex]
+ (CurrentPositions[particleIndex] - PrevPositions[particleIndex]) * (1.0f - particle.Settings.dragForce)
+ parentParentRotation * parent.InitLocalRotation * particle.InitLocalPosition *
particle.Settings.stiffnessForce * Frame.DeltaTime // 親の回転による子ボーンの移動目標
+ external
+ velocity
+ ImpulsiveForces[particleIndex]
+ resilience_force * particle.Settings.Stiffness
+ (particle.Settings.Gravity + Frame.Force) * Frame.SqDeltaTime
;
NextPositions[particleIndex] = newPosition;
@@ -105,19 +92,4 @@ namespace UniVRM10.ClothWarp.Jobs
NextRotations[particleIndex] = CurrentTransforms[particleIndex].Rotation;
}
}
// [Output]
public struct OutputTransformJob : IJobParallelForTransform
{
[ReadOnly] public NativeArray<TransformInfo> Info;
[ReadOnly] public NativeArray<Quaternion> NextRotations;
public void Execute(int particleIndex, TransformAccess transform)
{
var info = Info[particleIndex];
if (info.TransformType.Writable())
{
transform.rotation = NextRotations[particleIndex];
}
}
}
}

View File

@@ -0,0 +1,242 @@
using System;
using SphereTriangle;
using UniGLTF.SpringBoneJobs.Blittables;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
namespace UniVRM10.ClothWarp.Jobs
{
public struct RectCollisionJob : IJobParallelFor
{
// cloth
[ReadOnly] public NativeArray<(int, SpringConstraint, ClothRect)> ClothRects;
[WriteOnly] public NativeArray<(Vector3, Vector3, Vector3, Vector3)> ClothRectResults;
// collider
[ReadOnly] public NativeArray<BlittableCollider> Colliders;
[ReadOnly] public NativeArray<Matrix4x4> CurrentColliders;
// particle
[ReadOnly] public NativeArray<TransformInfo> Info;
[ReadOnly] public NativeArray<Vector3> NextPositions;
// collider group
[ReadOnly] public NativeArray<ClothInfo> Cloths;
[ReadOnly] public NativeArray<int> ColliderGroupRef;
[ReadOnly] public NativeArray<ArrayRange> ColliderGroup;
[ReadOnly] public NativeArray<int> ColliderRef;
public void Execute(int rectIndex)
{
var (clothGridIndex, spring, rect) = ClothRects[rectIndex];
var a = NextPositions[rect._a];
var b = NextPositions[rect._b];
var c = NextPositions[rect._c];
var d = NextPositions[rect._d];
var aabb = GetBoundsFrom4(a, b, c, d);
// d x-x c
// |/
// a x
var _triangle1 = new Triangle(c, d, a);
// x c
// /|
// a x-x b
var _triangle0 = new Triangle(a, b, c);
var cloth = Cloths[clothGridIndex];
for (int groupRefIndex = cloth.ColliderGroupRefRange.Start; groupRefIndex < cloth.ColliderGroupRefRange.End; ++groupRefIndex)
{
var groupIndex = ColliderGroupRef[groupRefIndex];
var group = ColliderGroup[groupIndex];
for (int colliderRefIndex = group.Start; colliderRefIndex < group.End; ++colliderRefIndex)
{
var colliderIndex = ColliderRef[colliderRefIndex];
var collider = Colliders[colliderIndex];
var collider_matrix = CurrentColliders[collider.transformIndex];
if (!aabb.Intersects(GetBounds(collider, collider_matrix)))
{
continue;
}
// 面の片側だけにヒットさせる
// 行き過ぎて戻るときに素通りする
// var p = _triangle0.Plane.ClosestPointOnPlane(col_pos);
// var dot = Vector3.Dot(_triangle0.Plane.normal, col_pos - p);
// if (_initialColliderNormalSide[collider] * dot < 0)
// {
// // 片側
// continue;
// }
var abc = TryCollide(collider, collider_matrix, _triangle0, out var l0);
var cda = TryCollide(collider, collider_matrix, _triangle1, out var l1);
if (!Info[rect._c].TransformType.Movable())
{
// cloth の上端。cd が固定
if (abc)
{
a += l0.GetDelta(collider.radius);
b += l0.GetDelta(collider.radius);
}
else if (cda)
{
a += l1.GetDelta(collider.radius);
b += l1.GetDelta(collider.radius);
}
}
else
{
if (abc && cda)
{
a += l0.GetDelta(collider.radius);
b += l0.GetDelta(collider.radius);
c += l1.GetDelta(collider.radius);
d += l1.GetDelta(collider.radius);
}
else if (abc)
{
a += l0.GetDelta(collider.radius);
b += l0.GetDelta(collider.radius);
c += l0.GetDelta(collider.radius);
}
else if (cda)
{
c += l1.GetDelta(collider.radius);
d += l1.GetDelta(collider.radius);
a += l1.GetDelta(collider.radius);
}
}
}
}
ClothRectResults[rectIndex] = (a, b, c, d);
}
static bool TryCollide(BlittableCollider collider, in Matrix4x4 colliderMatrix, in Triangle t, out LineSegment l)
{
var col_pos = colliderMatrix.MultiplyPoint(collider.offset);
if (collider.colliderType == BlittableColliderType.Capsule)
{
// capsule
var tail_pos = colliderMatrix.MultiplyPoint(collider.tailOrNormal);
var result = TriangleCapsuleCollisionSolver.Collide(t, new LineSegment(col_pos, tail_pos), collider.radius);
var type = result.TryGetClosest(out l);
return type.HasValue;
}
else
{
// sphere
return TryCollideSphere(t, col_pos, collider.radius, out l);
}
}
/// <summary>
///
/// </summary>
/// <param name="triangle"></param>
/// <param name="collider"></param>
/// <param name="radius"></param>
/// <returns>collider => 衝突点 への線分を返す</returns>
static bool TryCollideSphere(in Triangle triangle, in Vector3 collider, float radius, out LineSegment l)
{
var p = triangle.Plane.ClosestPointOnPlane(collider);
var distance = Vector3.Distance(p, collider);
if (distance > radius)
{
l = default;
return false;
}
if (triangle.IsSameSide(p))
{
l = new LineSegment(collider, p);
return true;
}
var (closestPoint, d) = triangle.GetClosest(collider);
if (d > radius)
{
l = default;
return false;
}
l = new LineSegment(collider, closestPoint);
return true;
}
public static Bounds GetBoundsFrom4(in Vector3 a, in Vector3 b, in Vector3 c, in Vector3 d)
{
var aabb = new Bounds(a, Vector3.zero);
aabb.Encapsulate(b);
aabb.Encapsulate(c);
aabb.Encapsulate(d);
return aabb;
}
public static Bounds GetBounds(BlittableCollider collider, Matrix4x4 m)
{
switch (collider.colliderType)
{
case BlittableColliderType.Capsule:
{
var h = m.MultiplyPoint(collider.offset);
var t = m.MultiplyPoint(collider.tailOrNormal);
var d = h - t;
var aabb = new Bounds((h + t) * 0.5f, new Vector3(Mathf.Abs(d.x), Mathf.Abs(d.y), Mathf.Abs(d.z)));
aabb.Expand(collider.radius * 2);
return aabb;
}
case BlittableColliderType.Sphere:
return new Bounds(m.MultiplyPoint(collider.offset), new Vector3(collider.radius, collider.radius, collider.radius));
default:
throw new NotImplementedException();
}
}
}
public struct RectCollisionReduceJob : IJob
{
[ReadOnly] public NativeArray<(int, SpringConstraint, ClothRect)> ClothRects;
[ReadOnly] public NativeArray<(Vector3, Vector3, Vector3, Vector3)> ClothRectResults;
[ReadOnly] public NativeArray<Vector3> NextPositions;
public NativeArray<int> RectCollisionCount;
public NativeArray<Vector3> RectCollisionDelta;
public void Execute()
{
for (int rectIndex = 0; rectIndex < ClothRects.Length; ++rectIndex)
{
var (clothGridIndex, spring, rect) = ClothRects[rectIndex];
var (a, b, c, d) = ClothRectResults[rectIndex];
if (a != NextPositions[rect._a])
{
RectCollisionDelta[rect._a] += a - NextPositions[rect._a];
RectCollisionCount[rect._a] += 1;
}
if (b != NextPositions[rect._b])
{
RectCollisionDelta[rect._b] += b - NextPositions[rect._b];
RectCollisionCount[rect._b] += 1;
}
if (c != NextPositions[rect._c])
{
RectCollisionDelta[rect._c] += c - NextPositions[rect._c];
RectCollisionCount[rect._c] += 1;
}
if (d != NextPositions[rect._d])
{
RectCollisionDelta[rect._d] += d - NextPositions[rect._d];
RectCollisionCount[rect._d] += 1;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 127d372d227187449960f33a08791017
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,86 @@
using Unity.Collections;
using UnityEngine;
using UnityEngine.Jobs;
namespace UniVRM10.ClothWarp.Jobs
{
public struct BranchInfo
{
public int FirstSiblingIndex;
}
public struct TransformInfo
{
public TransformType TransformType;
public int ParentIndex;
public Quaternion InitLocalRotation;
public Vector3 InitLocalPosition;
public ParticleSettings Settings;
public int WarpIndex;
public BranchInfo? Branch;
}
public struct TransformData
{
public Matrix4x4 ToWorld;
public Vector3 Position => ToWorld.GetPosition();
public Quaternion Rotation => ToWorld.rotation;
public Matrix4x4 ToLocal;
public TransformData(TransformAccess t)
{
ToWorld = t.localToWorldMatrix;
ToLocal = t.worldToLocalMatrix;
}
public TransformData(Transform t)
{
ToWorld = t.localToWorldMatrix;
ToLocal = t.worldToLocalMatrix;
}
}
// [Input]
public struct InputTransformJob : IJobParallelForTransform
{
[ReadOnly] public NativeArray<TransformInfo> Info;
[WriteOnly] public NativeArray<TransformData> InputData;
[WriteOnly] public NativeArray<Vector3> CurrentPositions;
[WriteOnly] public NativeArray<int> CollisionCount;
[WriteOnly] public NativeArray<Vector3> CollisionDelta;
[WriteOnly] public NativeArray<Vector3> ImpulsiveForces;
public void Execute(int particleIndex, TransformAccess transform)
{
InputData[particleIndex] = new TransformData(transform);
var particle = Info[particleIndex];
if (particle.TransformType.PositionInput())
{
// only warp root position update
CurrentPositions[particleIndex] = transform.position;
}
// clear cloth
CollisionCount[particleIndex] = 0;
CollisionDelta[particleIndex] = Vector3.zero;
ImpulsiveForces[particleIndex] = Vector3.zero;
}
}
// [Output]
public struct OutputTransformJob : IJobParallelForTransform
{
[ReadOnly] public NativeArray<TransformInfo> Info;
[ReadOnly] public NativeArray<Quaternion> NextRotations;
public void Execute(int particleIndex, TransformAccess transform)
{
var info = Info[particleIndex];
if (info.TransformType.Writable())
{
transform.rotation = NextRotations[particleIndex];
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5f3379ab5e1202d43a95e72ece6bda3a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,14 @@
namespace UniVRM10.ClothWarp.Jobs
{
public struct ArrayRange
{
public int Start;
public int End;
}
public struct WarpInfo
{
public int StartIndex;
public int EndIndex;
public ArrayRange PrticleRange;
public ArrayRange ColliderGroupRefRange;
}
}

View File

@@ -0,0 +1,73 @@
using SphereTriangle;
using UniGLTF.SpringBoneJobs.Blittables;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
namespace UniVRM10.ClothWarp.Jobs
{
public struct WarpCollisionJob : IJobParallelFor
{
// collider
[ReadOnly] public NativeArray<BlittableCollider> Colliders;
[ReadOnly] public NativeArray<Matrix4x4> CurrentColliders;
// particle
[ReadOnly] public NativeArray<TransformInfo> Info;
[ReadOnly] public NativeArray<Vector3> NextPositions;
[ReadOnly] public NativeArray<bool> ClothUsedParticles;
[WriteOnly] public NativeArray<Vector3> StrandCollision;
// collider group
[ReadOnly] public NativeArray<WarpInfo> Warps;
[ReadOnly] public NativeArray<int> ColliderGroupRef;
[ReadOnly] public NativeArray<ArrayRange> ColliderGroup;
[ReadOnly] public NativeArray<int> ColliderRef;
public void Execute(int particleIndex)
{
if (
// cloth でない
!ClothUsedParticles[particleIndex]
// 枝のjointでない
&& !Info[particleIndex].Branch.HasValue
)
{
var info = Info[particleIndex];
var pos = NextPositions[particleIndex];
var warp = Warps[info.WarpIndex];
for (int groupRefIndex = warp.ColliderGroupRefRange.Start; groupRefIndex < warp.ColliderGroupRefRange.End; ++groupRefIndex)
{
var groupIndex = ColliderGroupRef[groupRefIndex];
var group = ColliderGroup[groupIndex];
for (int colliderRefIndex = group.Start; colliderRefIndex < group.End; ++colliderRefIndex)
{
var colliderIndex = ColliderRef[colliderRefIndex];
var c = Colliders[colliderIndex];
var m = CurrentColliders[c.transformIndex];
if (c.colliderType == BlittableColliderType.Capsule)
{
if (SphereSphereCollision.TryCollideCapsuleAndSphere(m.MultiplyPoint(c.offset), m.MultiplyPoint(c.tailOrNormal), c.radius,
pos, info.Settings.Radius, out var l))
{
pos += l.GetDelta(c.radius);
}
}
else
{
if (SphereSphereCollision.TryCollideSphereAndSphere(m.MultiplyPoint(c.offset), c.radius,
pos, info.Settings.Radius, out var l))
{
pos += l.GetDelta(c.radius);
}
}
}
}
StrandCollision[particleIndex] = pos;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 80e53200fa3841748ad1d1416efdd53b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -23,7 +23,7 @@ namespace UniVRM10.ClothWarp
foreach (var particle in warp.Particles)
{
var child_index = _MakeAParticle(joint, env, particle.Transform,
particle.Settings.radius, 1);
particle.Settings.Radius, 1);
var child = _particles[child_index];
strand.Particles.Add(child);
joint.Children.Add(child);

View File

@@ -230,19 +230,19 @@ namespace SphereTriangle
return true;
}
// public void DrawGizmos()
// {
// var r = Vector3.Distance(_triangle0.b, _triangle0.c) * 0.1f;
// _DrawGizmos(_triangle0, _s0, _trinagle0Collision, r);
// _DrawGizmos(_triangle1, _s1, _triangle1Collision, r);
// public void DrawGizmos()
// {
// var r = Vector3.Distance(_triangle0.b, _triangle0.c) * 0.1f;
// _DrawGizmos(_triangle0, _s0, _trinagle0Collision, r);
// _DrawGizmos(_triangle1, _s1, _triangle1Collision, r);
// #if AABB_DEBUG
// Gizmos.matrix = Matrix4x4.identity;
// Gizmos.color = Color.cyan;
// var aabb = GetBoundsFrom4(_triangle0.a, _triangle0.b, _triangle1.a, _triangle1.b);
// Gizmos.DrawWireCube(aabb.center, aabb.size);
// #endif
// }
// #if AABB_DEBUG
// Gizmos.matrix = Matrix4x4.identity;
// Gizmos.color = Color.cyan;
// var aabb = GetBoundsFrom4(_triangle0.a, _triangle0.b, _triangle1.a, _triangle1.b);
// Gizmos.DrawWireCube(aabb.center, aabb.size);
// #endif
// }
// void _DrawGizmos(in Triangle t, TriangleCapsuleCollisionSolver solver, float collision, float radius)
// {

View File

@@ -0,0 +1,74 @@
using UnityEngine;
namespace SphereTriangle
{
public static class SphereSphereCollision
{
/// <summary>
/// collide sphere a and sphere b.
/// move sphere b to resolved if collide.
/// </summary>
/// <param name="from"></param>
/// <param name="ra"></param>
/// <param name="to"></param>
/// <param name="ba"></param>
/// <param name="resolved"></param>
/// <returns></returns>
public static bool TryCollideSphereAndSphere(
in Vector3 from, float ra,
in Vector3 to, float rb,
out LineSegment resolved
)
{
var d = Vector3.Distance(from, to);
if (d > (ra + rb))
{
resolved = default;
return false;
}
Vector3 normal = (to - from).normalized;
resolved = new(from, from + normal * (d - rb));
return true;
}
/// <summary>
/// collide capsule and sphere b.
/// move sphere b to resolved if collide.
/// </summary>
/// <param name="capsuleHead"></param>
/// <param name="capsuleTail"></param>
/// <param name="capsuleRadius"></param>
/// <param name="b"></param>
/// <param name="rb"></param>
public static bool TryCollideCapsuleAndSphere(
in Vector3 capsuleHead,
in Vector3 capsuleTail,
float capsuleRadius,
in Vector3 b,
float rb,
out LineSegment resolved
)
{
var P = (capsuleTail - capsuleHead);
var Q = b - capsuleHead;
var dot = Vector3.Dot(P.normalized, Q);
if (dot <= 0)
{
// head側半球の球判定
return TryCollideSphereAndSphere(capsuleHead, capsuleRadius, b, rb, out resolved);
}
var t = dot / P.magnitude;
if (t >= 1.0f)
{
// tail側半球の球判定
return TryCollideSphereAndSphere(capsuleTail, capsuleRadius, b, rb, out resolved);
}
// head-tail上の m_transform.position との最近点
var p = capsuleHead + P * t;
return TryCollideSphereAndSphere(p, capsuleRadius, b, rb, out resolved);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 990d1e921d604e348ab7fd04216b01d2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: