Merge remote-tracking branch 'upstream/master' into use_maintexture_offset_scale

This commit is contained in:
notargs
2024-12-13 21:30:09 +09:00
173 changed files with 22748 additions and 331 deletions

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

@@ -4,6 +4,8 @@ using Unity.Jobs;
using UnityEngine.Profiling;
using UniGLTF.SpringBoneJobs.InputPorts;
using UnityEngine;
using Unity.Collections;
using System.Linq;
namespace UniGLTF.SpringBoneJobs
{
@@ -20,19 +22,17 @@ namespace UniGLTF.SpringBoneJobs
private FastSpringBoneCombinedBuffer _combinedBuffer;
public FastSpringBoneCombinedBuffer Combined => _combinedBuffer;
private readonly LinkedList<FastSpringBoneBuffer> _buffers = new LinkedList<FastSpringBoneBuffer>();
private bool _isDirty;
private Queue<(bool isAdd, FastSpringBoneBuffer buffer)> _request = new();
public bool HasBuffer => _buffers.Count > 0 && _combinedBuffer != null;
public void Register(FastSpringBoneBuffer buffer)
{
_buffers.AddLast(buffer);
_isDirty = true;
_request.Enqueue((true, buffer));
}
public void Unregister(FastSpringBoneBuffer buffer)
{
_buffers.Remove(buffer);
_isDirty = true;
_request.Enqueue((false, buffer));
}
/// <summary>
@@ -40,14 +40,45 @@ namespace UniGLTF.SpringBoneJobs
/// </summary>
public JobHandle ReconstructIfDirty(JobHandle handle)
{
if (_isDirty)
if (_request.Count == 0)
{
var result = ReconstructBuffers(handle);
_isDirty = false;
return result;
return handle;
}
return handle;
if (_combinedBuffer is FastSpringBoneCombinedBuffer combined)
{
// index が変わる前に シミュレーションの状態を保存する。
// 状態の保存場所が BlittableJoint から CurrentTails に移動しているのでここでやる。
var logicsIndex = 0;
foreach (var buffer in _buffers)
{
if (_request.Any(x => !x.isAdd && x.buffer == buffer))
{
// 削除するので skip
continue;
}
buffer.BackupCurrentTails(combined.CurrentTails, combined.NextTails, logicsIndex);
logicsIndex += buffer.Logics.Length;
}
}
// buffer 増減
while (_request.Count > 0)
{
var (isAdd, buffer) = _request.Dequeue();
if (isAdd)
{
// 速度 0 にする
_buffers.AddLast(buffer);
}
else
{
_buffers.Remove(buffer);
}
}
// 再構築
return ReconstructBuffers(handle);
}
/// <summary>
@@ -60,10 +91,6 @@ namespace UniGLTF.SpringBoneJobs
Profiler.BeginSample("FastSpringBone.ReconstructBuffers.DisposeBuffers");
if (_combinedBuffer is FastSpringBoneCombinedBuffer combined)
{
Profiler.BeginSample("FastSpringBone.ReconstructBuffers.SaveToSourceBuffer");
combined.SaveToSourceBuffer();
Profiler.EndSample();
// TODO: Dispose せずに再利用?
combined.Dispose();
}

View File

@@ -97,7 +97,7 @@ namespace UniGLTF.SpringBoneJobs
springsCount += buffer.Springs.Length;
collidersCount += buffer.Colliders.Length;
logicsCount += buffer.Logics.Length;
transformsCount += buffer.BlittableTransforms.Length;
transformsCount += buffer.Transforms.Length;
}
Profiler.EndSample();
@@ -112,6 +112,24 @@ namespace UniGLTF.SpringBoneJobs
private JobHandle Batching(JobHandle handle)
{
// TransformAccessArrayの構築
Profiler.BeginSample("FastSpringBone.ReconstructBuffers.LoadTransformAccessArray");
var transforms = new Transform[_transforms.Length];
var transformAccessArrayOffset = 0;
foreach (var buffer in _batchedBuffers)
{
Array.Copy(buffer.Transforms, 0, transforms, transformAccessArrayOffset, buffer.Transforms.Length);
transformAccessArrayOffset += buffer.Transforms.Length;
}
_transformAccessArray = new TransformAccessArray(transforms);
Profiler.EndSample();
// Transforms を更新。後続の InitCurrentTails で使う
handle = new PullTransformJob
{
Transforms = Transforms
}.Schedule(TransformAccessArray, handle);
Profiler.BeginSample("FastSpringBone.ReconstructBuffers.ScheduleLoadBufferJobs");
var springsOffset = 0;
var collidersOffset = 0;
@@ -129,13 +147,8 @@ namespace UniGLTF.SpringBoneJobs
_jointMap.Add(buffer.Transforms[head], logicsOffset + j);
}
// バッファの読み込みをスケジュール
handle = new LoadTransformsJob
{
SrcTransforms = buffer.BlittableTransforms,
DestTransforms = new NativeSlice<BlittableTransform>(_transforms, transformOffset,
buffer.BlittableTransforms.Length)
}.Schedule(buffer.BlittableTransforms.Length, 1, handle);
// 速度の維持
buffer.RestoreCurrentTails(_currentTails, _nextTails, logicsOffset);
handle = new LoadSpringsJob
{
@@ -165,26 +178,10 @@ namespace UniGLTF.SpringBoneJobs
springsOffset += buffer.Springs.Length;
collidersOffset += buffer.Colliders.Length;
logicsOffset += buffer.Logics.Length;
transformOffset += buffer.BlittableTransforms.Length;
transformOffset += buffer.Transforms.Length;
}
handle = InitCurrentTails(handle);
// TransformAccessArrayの構築と並行してJobを行うため、この時点で走らせておく
JobHandle.ScheduleBatchedJobs();
Profiler.EndSample();
// TransformAccessArrayの構築
Profiler.BeginSample("FastSpringBone.ReconstructBuffers.LoadTransformAccessArray");
var transforms = new Transform[_transforms.Length];
var transformAccessArrayOffset = 0;
foreach (var buffer in _batchedBuffers)
{
Array.Copy(buffer.Transforms, 0, transforms, transformAccessArrayOffset, buffer.Transforms.Length);
transformAccessArrayOffset += buffer.BlittableTransforms.Length;
}
_transformAccessArray = new TransformAccessArray(transforms);
Profiler.EndSample();
return handle;
@@ -208,25 +205,6 @@ namespace UniGLTF.SpringBoneJobs
if (_transformAccessArray.isCreated) _transformAccessArray.Dispose();
}
/// <summary>
/// バッチングされたバッファから、個々のバッファへと値を戻す
/// Logics to _batchedBuffers[].Logics
/// バッファの再構築前にこの処理を行わないと、揺れの状態がリセットされてしまい、不自然な挙動になる
/// </summary>
internal void SaveToSourceBuffer()
{
var logicsIndex = 0;
for (var i = 0; i < _batchedBuffers.Length; ++i)
{
var length = _batchedBufferLogicSizes[i];
if (!_batchedBuffers[i].IsDisposed && length > 0)
{
NativeArray<BlittableJointImmutable>.Copy(Logics, logicsIndex, _batchedBuffers[i].Logics, 0, length);
}
logicsIndex += length;
}
}
public void FlipBuffer()
{
var tmp = _prevTails;
@@ -251,23 +229,6 @@ namespace UniGLTF.SpringBoneJobs
}
}
#if ENABLE_SPRINGBONE_BURST
[BurstCompile]
#endif
/// <summary>
///
/// </summary>
private struct LoadTransformsJob : IJobParallelFor
{
[ReadOnly] public NativeArray<BlittableTransform> SrcTransforms;
[WriteOnly] public NativeSlice<BlittableTransform> DestTransforms;
public void Execute(int index)
{
DestTransforms[index] = SrcTransforms[index];
}
}
#if ENABLE_SPRINGBONE_BURST
[BurstCompile]
#endif
@@ -328,49 +289,69 @@ namespace UniGLTF.SpringBoneJobs
#endif
private struct InitCurrentTailsJob : IJobParallelFor
{
[ReadOnly] public NativeArray<BlittableSpring> Springs;
[ReadOnly] public NativeArray<BlittableJointImmutable> Logics;
[ReadOnly] public NativeArray<BlittableTransform> Transforms;
[WriteOnly] public NativeSlice<Vector3> CurrentTails;
[WriteOnly] public NativeSlice<Vector3> PrevTails;
[WriteOnly] public NativeSlice<Vector3> NextTails;
[NativeDisableParallelForRestriction] public NativeSlice<Vector3> CurrentTails;
[NativeDisableParallelForRestriction] public NativeSlice<Vector3> PrevTails;
[NativeDisableParallelForRestriction] public NativeSlice<Vector3> NextTails;
public void Execute(int jointIndex)
public void Execute(int springIndex)
{
var tailIndex = Logics[jointIndex].tailTransformIndex;
if (tailIndex == -1)
var spring = Springs[springIndex];
for (int jointIndex = spring.logicSpan.startIndex; jointIndex < spring.logicSpan.EndIndex; ++jointIndex)
{
// tail 無い
var tail = Transforms[Logics[jointIndex].headTransformIndex];
CurrentTails[jointIndex] = tail.position;
PrevTails[jointIndex] = tail.position;
NextTails[jointIndex] = tail.position;
}
else
{
var tail = Transforms[tailIndex];
CurrentTails[jointIndex] = tail.position;
PrevTails[jointIndex] = tail.position;
NextTails[jointIndex] = tail.position;
if (float.IsNaN(CurrentTails[jointIndex].x))
{
// Transsform の現状を使う。velocity を zero にする
int tailIndex;
if (Logics[jointIndex].tailTransformIndex == -1)
{
// tail 無い
tailIndex = spring.transformIndexOffset + Logics[jointIndex].headTransformIndex;
}
else
{
tailIndex = spring.transformIndexOffset + Logics[jointIndex].tailTransformIndex;
}
var tail = Transforms[tailIndex];
CurrentTails[jointIndex] = tail.position;
PrevTails[jointIndex] = tail.position;
NextTails[jointIndex] = tail.position;
}
}
}
}
/// <summary>
/// Transform から currentTail を更新。
/// prevTail も同じ内容にする(速度0)。
/// <summary>
/// # CurrentTails[i] == NAN
///
/// Transforms から Current, Prev, Next を代入する。
/// 速度 0 で初期化することになる。
///
/// # CurrentTails[i] != NAN
///
/// 本処理はスキップされて Current, Next の利用が継続されます。
///
/// # NAN
///
/// Batching 関数内の FastSpringBoneBuffer.RestoreCurrentTails にて backup の Current, Next が無かったときに
/// 目印として NAN が代入されます。
/// </summary>
/// <param name="handle"></param>
/// <returns></returns>
public JobHandle InitCurrentTails(JobHandle handle)
{
return new InitCurrentTailsJob
{
Springs = Springs,
Logics = Logics,
Transforms = Transforms,
CurrentTails = CurrentTails,
PrevTails = PrevTails,
NextTails = NextTails,
}.Schedule(Logics.Length, 1, handle);
}.Schedule(Springs.Length, 1, handle);
}
public void InitializeJointsLocalRotation(FastSpringBoneBuffer buffer)
@@ -379,9 +360,9 @@ namespace UniGLTF.SpringBoneJobs
for (var i = 0; i < _batchedBuffers.Length; ++i)
{
var length = _batchedBufferLogicSizes[i];
Debug.Assert(length == buffer.Logics.Length);
if (_batchedBuffers[i] == buffer)
{
Debug.Assert(length == buffer.Logics.Length);
for (var j = 0; j < length; ++j)
{
var logic = buffer.Logics[j];
@@ -401,22 +382,23 @@ namespace UniGLTF.SpringBoneJobs
public void DrawGizmos()
{
foreach (var collider in _colliders)
{
collider.DrawGizmo(_transforms[collider.transformIndex]);
}
foreach (var spring in _springs)
{
for (int i = spring.colliderSpan.startIndex; i < spring.colliderSpan.EndIndex; ++i)
{
var collider = _colliders[i];
collider.DrawGizmo(_transforms[spring.transformIndexOffset + collider.transformIndex]);
}
for (int i = spring.logicSpan.startIndex; i < spring.logicSpan.EndIndex; ++i)
{
var joint = _logics[i];
joint.DrawGizmo(_transforms[joint.tailTransformIndex], _joints[i]);
joint.DrawGizmo(_transforms[spring.transformIndexOffset + joint.tailTransformIndex], _joints[i]);
Gizmos.matrix = Matrix4x4.identity;
Gizmos.DrawLine(
_transforms[joint.tailTransformIndex].position,
_transforms[joint.headTransformIndex].position);
_transforms[spring.transformIndexOffset + joint.tailTransformIndex].position,
_transforms[spring.transformIndexOffset + joint.headTransformIndex].position);
}
}
}

View File

@@ -22,9 +22,9 @@ namespace UniGLTF.SpringBoneJobs.InputPorts
public NativeArray<BlittableJointMutable> Joints { get; }
public NativeArray<BlittableCollider> Colliders { get; }
public NativeArray<BlittableJointImmutable> Logics { get; }
public NativeArray<BlittableTransform> BlittableTransforms { get; }
private NativeArray<Vector3> _currentTailsBackup;
private NativeArray<Vector3> _nextTailsBackup;
public Transform[] Transforms { get; }
public bool IsDisposed { get; private set; }
/// <summary>
/// Joint, Collider, Center の Transform のリスト
@@ -103,16 +103,6 @@ namespace UniGLTF.SpringBoneJobs.InputPorts
Joints = new NativeArray<BlittableJointMutable>(blittableJoints.ToArray(), Allocator.Persistent);
Colliders = new NativeArray<BlittableCollider>(blittableColliders.ToArray(), Allocator.Persistent);
Logics = new NativeArray<BlittableJointImmutable>(blittableLogics.ToArray(), Allocator.Persistent);
BlittableTransforms = new NativeArray<BlittableTransform>(Transforms.Select(transform => new BlittableTransform
{
position = transform.position,
rotation = transform.rotation,
localPosition = transform.localPosition,
localRotation = transform.localRotation,
localScale = transform.localScale,
localToWorldMatrix = transform.localToWorldMatrix,
worldToLocalMatrix = transform.worldToLocalMatrix
}).ToArray(), Allocator.Persistent);
Profiler.EndSample();
}
@@ -151,15 +141,57 @@ namespace UniGLTF.SpringBoneJobs.InputPorts
}
}
public void BackupCurrentTails(NativeArray<Vector3> currentTails, NativeArray<Vector3> nextTails, int offset)
{
if (!Logics.IsCreated || Logics.Length == 0)
{
return;
}
if (!_currentTailsBackup.IsCreated)
{
_currentTailsBackup = new(Logics.Length, Allocator.Persistent);
}
if (!_nextTailsBackup.IsCreated)
{
_nextTailsBackup = new(Logics.Length, Allocator.Persistent);
}
NativeArray<Vector3>.Copy(currentTails, offset, _currentTailsBackup, 0, Logics.Length);
NativeArray<Vector3>.Copy(nextTails, offset, _nextTailsBackup, 0, Logics.Length);
}
public void RestoreCurrentTails(NativeArray<Vector3> currentTails, NativeArray<Vector3> nextTails, int offset)
{
if (_currentTailsBackup.IsCreated)
{
NativeArray<Vector3>.Copy(_currentTailsBackup, 0, currentTails, offset, Logics.Length);
NativeArray<Vector3>.Copy(_nextTailsBackup, 0, nextTails, offset, Logics.Length);
}
else
{
var end = offset + Logics.Length;
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
}
}
}
public void Dispose()
{
if (IsDisposed) return;
IsDisposed = true;
Springs.Dispose();
Joints.Dispose();
BlittableTransforms.Dispose();
Colliders.Dispose();
Logics.Dispose();
if (Springs.IsCreated) Springs.Dispose();
if (Joints.IsCreated) Joints.Dispose();
if (Colliders.IsCreated) Colliders.Dispose();
if (Logics.IsCreated) Logics.Dispose();
if (_currentTailsBackup.IsCreated) _currentTailsBackup.Dispose();
if (_nextTailsBackup.IsCreated) _nextTailsBackup.Dispose();
}
}
}

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

@@ -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

@@ -61,7 +61,7 @@ namespace UniHumanoid
new Keyframe(0, pose.bodyPosition.x),
});
var muscle = "RootT.x";
clip.SetCurve(null, typeof(Animator), muscle, curve);
clip.SetCurve(string.Empty, typeof(Animator), muscle, curve);
}
{
var curve = new AnimationCurve(new Keyframe[]
@@ -69,7 +69,7 @@ namespace UniHumanoid
new Keyframe(0, pose.bodyPosition.y),
});
var muscle = "RootT.y";
clip.SetCurve(null, typeof(Animator), muscle, curve);
clip.SetCurve(string.Empty, typeof(Animator), muscle, curve);
}
{
var curve = new AnimationCurve(new Keyframe[]
@@ -77,7 +77,7 @@ namespace UniHumanoid
new Keyframe(0, pose.bodyPosition.z),
});
var muscle = "RootT.z";
clip.SetCurve(null, typeof(Animator), muscle, curve);
clip.SetCurve(string.Empty, typeof(Animator), muscle, curve);
}
// rot
@@ -87,7 +87,7 @@ namespace UniHumanoid
new Keyframe(0, pose.bodyRotation.x),
});
var muscle = "RootQ.x";
clip.SetCurve(null, typeof(Animator), muscle, curve);
clip.SetCurve(string.Empty, typeof(Animator), muscle, curve);
}
{
var curve = new AnimationCurve(new Keyframe[]
@@ -95,7 +95,7 @@ namespace UniHumanoid
new Keyframe(0, pose.bodyRotation.y),
});
var muscle = "RootQ.y";
clip.SetCurve(null, typeof(Animator), muscle, curve);
clip.SetCurve(string.Empty, typeof(Animator), muscle, curve);
}
{
var curve = new AnimationCurve(new Keyframe[]
@@ -103,7 +103,7 @@ namespace UniHumanoid
new Keyframe(0, pose.bodyRotation.z),
});
var muscle = "RootQ.z";
clip.SetCurve(null, typeof(Animator), muscle, curve);
clip.SetCurve(string.Empty, typeof(Animator), muscle, curve);
}
{
var curve = new AnimationCurve(new Keyframe[]
@@ -111,7 +111,7 @@ namespace UniHumanoid
new Keyframe(0, pose.bodyRotation.w),
});
var muscle = "RootQ.w";
clip.SetCurve(null, typeof(Animator), muscle, curve);
clip.SetCurve(string.Empty, typeof(Animator), muscle, curve);
}
// muscles
@@ -126,7 +126,7 @@ namespace UniHumanoid
{
muscle = TraitPropMap[muscle];
}
clip.SetCurve(null, typeof(Animator), muscle, curve);
clip.SetCurve(string.Empty, typeof(Animator), muscle, curve);
}
return clip;
}

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));

View File

@@ -1,3 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using UniGLTF;
using UnityEditor;
using UnityEngine;
@@ -9,6 +12,7 @@ namespace VRM
{
private VRMSpringBone m_target;
private SerializedProperty m_script;
private SerializedProperty m_commentProp;
private SerializedProperty m_gizmoColorProp;
private SerializedProperty m_stiffnessForceProp;
@@ -29,6 +33,7 @@ namespace VRM
}
m_target = (VRMSpringBone)target;
m_script = serializedObject.FindProperty("m_Script");
m_commentProp = serializedObject.FindProperty(nameof(VRMSpringBone.m_comment));
m_gizmoColorProp = serializedObject.FindProperty("m_gizmoColor");
m_stiffnessForceProp = serializedObject.FindProperty(nameof(VRMSpringBone.m_stiffnessForce));
@@ -42,32 +47,54 @@ namespace VRM
m_updateTypeProp = serializedObject.FindProperty(nameof(VRMSpringBone.m_updateType));
}
public void OnSceneGUI()
{
foreach (var valiation in m_target.Validations)
{
var t = (Transform)valiation.Context.Context;
if (t != null)
{
Handles.Label(t.position, "duplicate rootBone !");
}
}
}
public override void OnInspectorGUI()
{
serializedObject.Update();
// header
using (new EditorGUI.DisabledScope(true))
{
EditorGUILayout.PropertyField(m_script);
}
EditorGUILayout.PropertyField(m_commentProp);
EditorGUILayout.PropertyField(m_gizmoColorProp);
EditorGUILayout.Space();
foreach (var validation in m_target.Validations)
{
validation.DrawGUI();
}
// settings
EditorGUILayout.LabelField("Settings", EditorStyles.boldLabel);
LimitBreakSlider(m_stiffnessForceProp, 0.0f, 4.0f, 0.0f, Mathf.Infinity);
LimitBreakSlider(m_gravityPowerProp, 0.0f, 2.0f, 0.0f, Mathf.Infinity);
EditorGUILayout.PropertyField(m_gravityDirProp);
EditorGUILayout.PropertyField(m_dragForceProp);
EditorGUILayout.PropertyField(m_centerProp);
EditorGUILayout.PropertyField(m_rootBonesProp);
EditorGUILayout.Space();
// collision
EditorGUILayout.LabelField("Collision", EditorStyles.boldLabel);
LimitBreakSlider(m_hitRadiusProp, 0.0f, 0.5f, 0.0f, Mathf.Infinity);
EditorGUILayout.PropertyField(m_colliderGroupsProp);
EditorGUILayout.PropertyField(m_updateTypeProp);
EditorGUILayout.Space();
// runtime
EditorGUILayout.LabelField("Runtime", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(m_updateTypeProp);
serializedObject.ApplyModifiedProperties();
}

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,4 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using UniGLTF;
using UnityEngine;
namespace VRM
@@ -47,15 +49,52 @@ namespace VRM
}
[SerializeField] public SpringBoneUpdateType m_updateType = SpringBoneUpdateType.LateUpdate;
List<Transform> m_rootBonesNonNullUnique = new();
List<Transform> RootBonesNonNullUnique
{
get
{
m_rootBonesNonNullUnique.Clear();
m_rootBonesNonNullUnique.AddRange(RootBones.Where(x => x != null).Distinct());
return m_rootBonesNonNullUnique;
}
}
SpringBone.SpringBoneSystem m_system = new();
Dictionary<Transform, int> m_rootCount = new();
List<Validation> m_validations = new();
public List<Validation> Validations => m_validations;
public void OnValidate()
{
Validations.Clear();
m_rootCount.Clear();
foreach (var root in RootBones)
{
if (m_rootCount.TryGetValue(root, out var count))
{
m_rootCount[root] = count + 1;
}
else
{
m_rootCount.Add(root, 1);
}
}
foreach (var (k, v) in m_rootCount)
{
if (v > 1)
{
Validations.Add(Validation.Error($"Duplicate rootBone: {k} => {v}", ValidationContext.Create(k)));
}
}
}
void Awake()
{
Setup();
}
SpringBone.SceneInfo Scene => new(
rootBones: RootBones,
rootBones: RootBonesNonNullUnique,
center: m_center,
colliderGroups: ColliderGroups,
externalForce: ExternalForce);

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

@@ -223,8 +223,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

@@ -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

@@ -87,7 +87,10 @@ namespace UniVRM10
{
foreach (var collider in Colliders)
{
collider.DrawGizmos();
if (collider != null)
{
collider.DrawGizmos();
}
}
}
}

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

@@ -26,6 +26,7 @@ namespace UniVRM10
if (fastSpringBoneBuffer != null)
{
fastSpringBoneBuffer.Dispose();
fastSpringBoneBuffer = null;
}
Func<Transform, TransformState> GetOrAddDefaultTransformState = (Transform tf) =>
@@ -41,7 +42,7 @@ namespace UniVRM10
// create(Spring情報の再収集。設定変更の反映)
var springs = vrm.SpringBone.Springs.Select(spring => new FastSpringBoneSpring
{
{
center = spring.Center,
colliders = spring.ColliderGroups
.SelectMany(group => group.Colliders)

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

@@ -244,6 +244,7 @@ namespace UniVRM10
{
switch (material?.alphaMode)
{
case null:
case "OPAQUE":
return MToon10AlphaMode.Opaque;
case "MASK":

View File

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

View File

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

View File

@@ -0,0 +1,73 @@
using System.Collections;
using UnityEngine;
namespace UniVRM10.Cloth.Viewer
{
public class ClothAIUEO : MonoBehaviour
{
[SerializeField]
public Vrm10Instance Controller;
private void Reset()
{
Controller = GetComponent<Vrm10Instance>();
}
Coroutine m_coroutine;
[SerializeField]
float m_wait = 0.5f;
private void Awake()
{
if (Controller == null)
{
Controller = GetComponent<Vrm10Instance>();
}
}
IEnumerator RoutineNest(ExpressionPreset preset, float velocity, float wait)
{
for (var value = 0.0f; value <= 1.0f; value += velocity)
{
Controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(preset), value);
yield return null;
}
Controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(preset), 1.0f);
yield return new WaitForSeconds(wait);
for (var value = 1.0f; value >= 0; value -= velocity)
{
Controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(preset), value);
yield return null;
}
Controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(preset), 0);
yield return new WaitForSeconds(wait * 2);
}
IEnumerator Routine()
{
while (true)
{
yield return new WaitForSeconds(1.0f);
var velocity = 0.1f;
yield return RoutineNest(ExpressionPreset.aa, velocity, m_wait);
yield return RoutineNest(ExpressionPreset.ih, velocity, m_wait);
yield return RoutineNest(ExpressionPreset.ou, velocity, m_wait);
yield return RoutineNest(ExpressionPreset.ee, velocity, m_wait);
yield return RoutineNest(ExpressionPreset.oh, velocity, m_wait);
}
}
private void OnEnable()
{
m_coroutine = StartCoroutine(Routine());
}
private void OnDisable()
{
StopCoroutine(m_coroutine);
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 915733a6f44f9554cae9a093cca75906
timeCreated: 1517463794
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,76 @@
using System.Collections;
using UnityEngine;
namespace UniVRM10.Cloth.Viewer
{
/// <summary>
/// 喜怒哀楽驚を循環させる
/// </summary>
public class ClothAutoExpression : MonoBehaviour
{
[SerializeField]
public Vrm10Instance Controller;
private void Reset()
{
Controller = GetComponent<Vrm10Instance>();
}
Coroutine m_coroutine;
[SerializeField]
float m_wait = 0.5f;
private void Awake()
{
if (Controller == null)
{
Controller = GetComponent<Vrm10Instance>();
}
}
IEnumerator RoutineNest(ExpressionPreset preset, float velocity, float wait)
{
for (var value = 0.0f; value <= 1.0f; value += velocity)
{
Controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(preset), value);
yield return null;
}
Controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(preset), 1.0f);
yield return new WaitForSeconds(wait);
for (var value = 1.0f; value >= 0; value -= velocity)
{
Controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(preset), value);
yield return null;
}
Controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(preset), 0);
yield return new WaitForSeconds(wait * 2);
}
IEnumerator Routine()
{
while (true)
{
yield return new WaitForSeconds(1.0f);
var velocity = 0.01f;
yield return RoutineNest(ExpressionPreset.happy, velocity, m_wait);
yield return RoutineNest(ExpressionPreset.angry, velocity, m_wait);
yield return RoutineNest(ExpressionPreset.sad, velocity, m_wait);
yield return RoutineNest(ExpressionPreset.relaxed, velocity, m_wait);
yield return RoutineNest(ExpressionPreset.surprised, velocity, m_wait);
}
}
private void OnEnable()
{
m_coroutine = StartCoroutine(Routine());
}
private void OnDisable()
{
StopCoroutine(m_coroutine);
}
}
}

View File

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

View File

@@ -0,0 +1,116 @@
using System.Collections;
using UnityEngine;
using UnityEngine.Serialization;
namespace UniVRM10.Cloth.Viewer
{
/// <summary>
/// VRMBlendShapeProxy によるランダムに瞬きするサンプル。
/// VRMBlendShapeProxy のある GameObject にアタッチする。
/// </summary>
public class ClothBlinker : MonoBehaviour
{
Vrm10Instance m_controller;
[FormerlySerializedAs("m_interVal")]
[SerializeField]
public float Interval = 5.0f;
[FormerlySerializedAs("m_closingTime")]
[SerializeField]
public float ClosingTime = 0.06f;
[FormerlySerializedAs("m_openingSeconds")]
[SerializeField]
public float OpeningSeconds = 0.03f;
[FormerlySerializedAs("m_closeSeconds")]
[SerializeField]
public float CloseSeconds = 0.1f;
Coroutine m_coroutine;
float m_nextRequest;
bool m_request;
public bool Request
{
get { return m_request; }
set
{
if (Time.time < m_nextRequest)
{
return;
}
m_request = value;
m_nextRequest = Time.time + 1.0f;
}
}
IEnumerator BlinkRoutine()
{
while (true)
{
var waitTime = Time.time + Random.value * Interval;
while (waitTime > Time.time)
{
if (Request)
{
m_request = false;
break;
}
yield return null;
}
// close
var value = 0.0f;
var closeSpeed = 1.0f / CloseSeconds;
while (true)
{
value += Time.deltaTime * closeSpeed;
if (value >= 1.0f)
{
break;
}
m_controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(ExpressionPreset.blink), value);
yield return null;
}
m_controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(ExpressionPreset.blink), 1.0f);
// wait...
yield return new WaitForSeconds(ClosingTime);
// open
value = 1.0f;
var openSpeed = 1.0f / OpeningSeconds;
while (true)
{
value -= Time.deltaTime * openSpeed;
if (value < 0)
{
break;
}
m_controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(ExpressionPreset.blink), value);
yield return null;
}
m_controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(ExpressionPreset.blink), 0);
}
}
private void OnEnable()
{
m_controller = GetComponent<Vrm10Instance>();
m_coroutine = StartCoroutine(BlinkRoutine());
}
private void OnDisable()
{
if (m_coroutine != null)
{
StopCoroutine(m_coroutine);
m_coroutine = null;
}
}
}
}

View File

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

View File

@@ -0,0 +1,118 @@
#if UNITY_STANDALONE_WIN
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
#endif
namespace UniVRM10.Cloth.Viewer
{
public static class ClothFileDialogForWindows
{
#if UNITY_STANDALONE_WIN
#region GetOpenFileName
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class OpenFileName
{
public int structSize = 0;
public IntPtr dlgOwner = IntPtr.Zero;
public IntPtr instance = IntPtr.Zero;
public String filter = null;
public String customFilter = null;
public int maxCustFilter = 0;
public int filterIndex = 0;
public String file = null;
public int maxFile = 0;
public String fileTitle = null;
public int maxFileTitle = 0;
public String initialDir = null;
public String title = null;
public int flags = 0;
public short fileOffset = 0;
public short fileExtension = 0;
public String defExt = null;
public IntPtr custData = IntPtr.Zero;
public IntPtr hook = IntPtr.Zero;
public String templateName = null;
public IntPtr reservedPtr = IntPtr.Zero;
public int reservedInt = 0;
public int flagsEx = 0;
}
[DllImport("Comdlg32.dll", SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto)]
public static extern bool GetOpenFileName([In, Out] OpenFileName ofn);
/*
public static bool GetOpenFileName1([In, Out] OpenFileName ofn)
{
return GetOpenFileName(ofn);
}
*/
[DllImport("Comdlg32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool GetSaveFileName([In, Out] OpenFileName ofn);
static string Filter(params string[] filters)
{
return string.Join("\0", filters) + "\0";
}
public static string FileDialog(string title, params string[] extensions)
{
OpenFileName ofn = new OpenFileName();
ofn.structSize = Marshal.SizeOf(ofn);
var filters = new List<string>();
filters.Add("All Files"); filters.Add("*.*");
foreach (var ext in extensions)
{
filters.Add(ext); filters.Add("*" + ext);
}
ofn.filter = Filter(filters.ToArray());
ofn.filterIndex = 2;
ofn.file = new string(new char[256]);
ofn.maxFile = ofn.file.Length;
ofn.fileTitle = new string(new char[64]);
ofn.maxFileTitle = ofn.fileTitle.Length;
ofn.initialDir = UnityEngine.Application.dataPath;
ofn.title = title;
//ofn.defExt = "PNG";
ofn.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000200 | 0x00000008;//OFN_EXPLORER|OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST| OFN_ALLOWMULTISELECT|OFN_NOCHANGEDIR
if (!GetOpenFileName(ofn))
{
return null;
}
return ofn.file;
}
public static string SaveDialog(string title, string path)
{
var extension = Path.GetExtension(path);
OpenFileName ofn = new OpenFileName();
ofn.structSize = Marshal.SizeOf(ofn);
ofn.filter = Filter("All Files", "*.*", extension, "*" + extension);
ofn.filterIndex = 2;
var chars = new char[256];
var it = Path.GetFileName(path).GetEnumerator();
for (int i = 0; i < chars.Length && it.MoveNext(); ++i)
{
chars[i] = it.Current;
}
ofn.file = new string(chars);
ofn.maxFile = ofn.file.Length;
ofn.fileTitle = new string(new char[64]);
ofn.maxFileTitle = ofn.fileTitle.Length;
ofn.initialDir = Path.GetDirectoryName(path);
ofn.title = title;
//ofn.defExt = "PNG";
ofn.flags = 0x00000002 | 0x00000004; // OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
if (!GetSaveFileName(ofn))
{
return null;
}
return ofn.file;
}
#endregion
#endif
}
}

View File

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

View File

@@ -0,0 +1,201 @@
using System.Collections.Generic;
using System.Linq;
using UniVRM10.ClothWarp.Components;
using UnityEngine;
namespace UniVRM10.Cloth.Viewer
{
public static class ClothGuess
{
public enum StrandConnectionType
{
Cloth,
ClothLoop,
Strand,
}
public static void Guess(Animator animator)
{
// skirt
{
if (TryAddGroup(animator, HumanBodyBones.Hips,
new[] { "skirt", "スカート", "スカート" }, out var g))
{
var c = g[0].gameObject.AddComponent<ClothGrid>();
c.Warps = g;
c.LoopIsClosed = true;
}
}
{
if (TryAddGroupChildChild(animator, HumanBodyBones.Hips,
new[] { "skirt", "スカート", "スカート" }, new string[] { }, out var g))
{
var c = g[0].gameObject.AddComponent<ClothGrid>();
c.Warps = g;
c.LoopIsClosed = true;
}
}
{
if (TryAddGroup(animator, HumanBodyBones.Head,
new[] { "髪", "hair" }, out var g))
{
}
}
{
if (TryAddGroup(animator, HumanBodyBones.Hips,
new[] { "裾" }, out var g))
{
var c = g[0].gameObject.AddComponent<ClothGrid>();
c.Warps = g;
}
}
{
if (TryAddGroupChildChild(animator, HumanBodyBones.LeftUpperArm,
new[] { "袖" }, new[] { "ひじ袖" }, out var g))
{
var c = g[0].gameObject.AddComponent<ClothGrid>();
c.Warps = g;
c.LoopIsClosed = true;
}
}
{
if (TryAddGroupChildChild(animator, HumanBodyBones.LeftLowerArm,
new[] { "袖" }, new string[] { }, out var g))
{
var c = g[0].gameObject.AddComponent<ClothGrid>();
c.Warps = g;
c.LoopIsClosed = true;
}
}
{
if (TryAddGroupChildChild(animator, HumanBodyBones.RightUpperArm,
new[] { "袖" }, new[] { "ひじ袖" }, out var g))
{
var c = g[0].gameObject.AddComponent<ClothGrid>();
c.Warps = g;
c.LoopIsClosed = true;
}
}
{
if (TryAddGroupChildChild(animator, HumanBodyBones.RightLowerArm,
new[] { "袖" }, new string[] { }, out var g))
{
var c = g[0].gameObject.AddComponent<ClothGrid>();
c.Warps = g;
}
}
{
if (TryAddGroup(animator, HumanBodyBones.Chest, new[] { "マント" },
out var g))
{
var c = g[0].gameObject.AddComponent<ClothGrid>();
c.Warps = g;
}
}
}
/// <summary>
///
/// </summary>
/// <param name="mask"></param>
/// <param name="name"></param>
/// <param name="animator"></param>
/// <param name="humanBone"></param>
/// <param name="targets"></param>
/// <param name="excludes"></param>
/// <param name="type"></param>
/// <param name="sort"></param>
/// <param name="group"></param>
/// <returns></returns>
static bool TryAddGroupChildChild(
Animator animator, HumanBodyBones humanBone,
string[] targets, string[] excludes,
out List<ClothWarpRoot> group)
{
var bone = animator.GetBoneTransform(humanBone);
if (bone == null)
{
Debug.LogWarning($"{humanBone} not found");
group = default;
return false;
}
List<ClothWarpRoot> transforms = new();
foreach (Transform child in bone)
{
foreach (Transform childchild in child)
{
if (excludes.Any(x => childchild.name.ToLower().Contains(x.ToLower())))
{
continue;
}
foreach (var target in targets)
{
if (childchild.name.ToLower().Contains(target.ToLower()))
{
var warp = childchild.gameObject.AddComponent<ClothWarpRoot>();
// Name = name,
// CollisionMask = mask,
warp.BaseSettings.Radius = 0.02f;
// Connection = type
transforms.Add(warp);
break;
}
}
}
}
if (transforms.Count == 0)
{
// Debug.LogWarning($"{string.Join(',', targets)} not found");
group = default;
return false;
}
group = transforms;
return true;
}
static bool TryAddGroup(Animator animator, HumanBodyBones humanBone, string[] targets,
out List<ClothWarpRoot> group)
{
var bone = animator.GetBoneTransform(humanBone);
if (bone == null)
{
Debug.LogWarning($"{humanBone} not found");
group = default;
return false;
}
List<ClothWarpRoot> transforms = new();
foreach (Transform child in bone)
{
foreach (var target in targets)
{
if (child.name.ToLower().Contains(target.ToLower()))
{
var warp = child.gameObject.AddComponent<ClothWarpRoot>();
if (warp != null)
{
// CollisionMask = mask,
warp.BaseSettings.Radius = 0.02f;
// Connection = type
transforms.Add(warp);
}
break;
}
}
}
if (transforms.Count == 0)
{
// Debug.LogWarning($"{string.Join(',', targets)} not found");
group = default;
return false;
}
group = transforms;
return true;
}
}
}

View File

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

View File

@@ -0,0 +1,93 @@
using System;
using UniGLTF;
using UnityEngine;
namespace UniVRM10.Cloth.Viewer
{
class Loaded : IDisposable
{
RuntimeGltfInstance m_instance;
public RuntimeGltfInstance Instance => m_instance;
Vrm10Instance m_controller;
public Vrm10RuntimeControlRig ControlRig => m_controller.Runtime.ControlRig;
public Vrm10Runtime Runtime => m_controller.Runtime;
ClothAIUEO m_lipSync;
bool m_enableLipSyncValue;
public bool EnableLipSyncValue
{
set
{
if (m_enableLipSyncValue == value) return;
m_enableLipSyncValue = value;
if (m_lipSync != null)
{
m_lipSync.enabled = m_enableLipSyncValue;
}
}
}
ClothAutoExpression m_autoExpression;
bool m_enableAutoExpressionValue;
public bool EnableAutoExpressionValue
{
set
{
if (m_enableAutoExpressionValue == value) return;
m_enableAutoExpressionValue = value;
if (m_autoExpression != null)
{
m_autoExpression.enabled = m_enableAutoExpressionValue;
}
}
}
ClothBlinker m_blink;
bool m_enableBlinkValue;
public bool EnableBlinkValue
{
set
{
if (m_blink == value) return;
m_enableBlinkValue = value;
if (m_blink != null)
{
m_blink.enabled = m_enableBlinkValue;
}
}
}
public Loaded(RuntimeGltfInstance instance, Transform lookAtTarget)
{
m_instance = instance;
m_controller = instance.GetComponent<Vrm10Instance>();
if (m_controller != null)
{
// VRM
m_controller.UpdateType = Vrm10Instance.UpdateTypes.LateUpdate; // after HumanPoseTransfer's setPose
{
m_lipSync = instance.gameObject.AddComponent<ClothAIUEO>();
m_blink = instance.gameObject.AddComponent<ClothBlinker>();
m_autoExpression = instance.gameObject.AddComponent<ClothAutoExpression>();
m_controller.LookAtTargetType = VRM10ObjectLookAt.LookAtTargetTypes.SpecifiedTransform;
m_controller.LookAtTarget = lookAtTarget;
}
}
var animation = instance.GetComponent<Animation>();
if (animation && animation.clip != null)
{
// GLTF animation
animation.Play(animation.clip.name);
}
}
public void Dispose()
{
// destroy GameObject
GameObject.Destroy(m_instance.gameObject);
}
}
}

View File

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

View File

@@ -0,0 +1,137 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace VRM.VRM10RokuroCamera
{
public class ClothRokuroCamera : MonoBehaviour
{
[Range(0.1f, 5.0f)]
public float RotateSpeed = 0.7f;
[Range(0.1f, 5.0f)]
public float GrabSpeed = 0.7f;
[Range(0.1f, 5.0f)]
public float DollySpeed = 1.0f;
struct PosRot
{
public Vector3 Position;
public Quaternion Rotation;
}
class _Rokuro
{
public float Yaw = 180.0f;
public float Pitch;
public float ShiftX;
public float ShiftY;
public float Distance = 2.0f;
public void Rotate(float x, float y)
{
Yaw += x;
Pitch -= y;
Pitch = Mathf.Clamp(Pitch, -90, 90);
}
public void Grab(float x, float y)
{
ShiftX += x * Distance;
ShiftY += y * Distance;
}
public void Dolly(float delta)
{
if (delta > 0)
{
Distance *= 0.9f;
}
else if (delta < 0)
{
Distance *= 1.1f;
}
}
public PosRot Calc()
{
var r = Quaternion.Euler(Pitch, Yaw, 0);
return new PosRot
{
Position = r * new Vector3(-ShiftX, -ShiftY, -Distance),
Rotation = r,
};
}
}
private _Rokuro _currentCamera = new _Rokuro();
private List<Coroutine> _activeCoroutines = new List<Coroutine>();
private void OnEnable()
{
// right mouse drag
_activeCoroutines.Add(StartCoroutine(MouseDragOperationCoroutine(1, diff =>
{
_currentCamera.Rotate(diff.x * RotateSpeed, diff.y * RotateSpeed);
})));
// middle mouse drag
_activeCoroutines.Add(StartCoroutine(MouseDragOperationCoroutine(2, diff =>
{
_currentCamera.Grab(
diff.x * GrabSpeed / Screen.height,
diff.y * GrabSpeed / Screen.height
);
})));
// mouse wheel
_activeCoroutines.Add(StartCoroutine(MouseScrollOperationCoroutine(diff =>
{
_currentCamera.Dolly(diff.y * DollySpeed);
})));
}
private void OnDisable()
{
foreach (var coroutine in _activeCoroutines)
{
StopCoroutine(coroutine);
}
_activeCoroutines.Clear();
}
private void Update()
{
var posRot = _currentCamera.Calc();
transform.localRotation = posRot.Rotation;
transform.localPosition = posRot.Position;
}
private IEnumerator MouseDragOperationCoroutine(int buttonIndex, Action<Vector2> dragOperation)
{
while (true)
{
while (!Input.GetMouseButtonDown(buttonIndex))
{
yield return null;
}
var prevPos = Input.mousePosition;
while (Input.GetMouseButton(buttonIndex))
{
var currPos = Input.mousePosition;
var diff = currPos - prevPos;
dragOperation(diff);
prevPos = currPos;
yield return null;
}
}
}
private IEnumerator MouseScrollOperationCoroutine(Action<Vector2> scrollOperation)
{
while (true)
{
scrollOperation(Input.mouseScrollDelta);
yield return null;
}
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 93dcf1b2cc4d917489620707e78e9f27
timeCreated: 1523878901
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UniVRM10.Cloth.Viewer
{
public class ClothTargetMover : MonoBehaviour
{
[SerializeField]
float m_radius = 5.0f;
[SerializeField]
float m_angularVelocity = 40.0f;
[SerializeField]
float m_y = 1.5f;
[SerializeField]
float m_height = 3.0f;
public IEnumerator Start()
{
var angle = 0.0f;
while (true)
{
angle += m_angularVelocity * Time.deltaTime * Mathf.Deg2Rad;
var x = Mathf.Cos(angle) * m_radius;
var z = Mathf.Sin(angle) * m_radius;
var y = m_y + m_height * Mathf.Cos(angle / 3);
transform.localPosition = new Vector3(x, y, z);
yield return null;
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 0bc0c019f7af45745aa41b25018fd61f
timeCreated: 1524045545
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
{
"name": "ClothViewer",
"rootNamespace": "",
"references": [
"GUID:b7aa47b240b57de44a4b2021c143c9bf",
"GUID:8d76e605759c3f64a957d63ef96ada7c",
"GUID:1cd941934d098654fa21a13f28346412",
"GUID:e47c917724578cc43b5506c17a27e9a0",
"GUID:308b348fb80d89d42a9620951b0f60db",
"GUID:3e5d614bc16b50d41bd94c8d7444ca46"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 623c6cdde4acc6641978d011f67c5b87
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: bedad7250033c0b49ac1e7db4d7ee63e
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,487 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using UniVRM10.ClothWarp.Components;
using UniGLTF;
using UnityEngine;
using UnityEngine.UI;
namespace UniVRM10.Cloth.Viewer
{
public class ClothViewerUI : MonoBehaviour
{
[SerializeField] Text m_version = default;
[Header("Model")]
[SerializeField] Toggle m_useAsync = default;
[SerializeField] Button m_openModel = default;
[SerializeField] Toggle m_showBoxMan = default;
[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;
[Header("Motion")]
[SerializeField] Button m_openMotion = default;
[SerializeField] Button m_pastePose = default;
[SerializeField] Toggle ToggleMotionTPose = default;
[SerializeField] Toggle ToggleMotionBVH = default;
[SerializeField] ToggleGroup ToggleMotion = default;
public bool IsTPose
{
get => ToggleMotion.ActiveToggles().FirstOrDefault() == ToggleMotionTPose;
set
{
ToggleMotionTPose.isOn = value;
ToggleMotionBVH.isOn = !value;
}
}
[Header("Expression")]
[SerializeField] Toggle m_enableLipSync = default;
[SerializeField] Toggle m_enableAutoBlink = default;
[SerializeField] Toggle m_enableAutoExpression = default;
[SerializeField] GameObject m_target = default;
[SerializeField] TextAsset m_motion;
[SerializeField] TextFields m_texts = default;
private void Reset()
{
var map = new ObjectMap(gameObject);
m_version = map.Get<Text>("VrmVersion");
m_useAsync = map.Get<Toggle>("UseAsync");
m_openModel = map.Get<Button>("OpenModel");
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");
m_openMotion = map.Get<Button>("OpenMotion");
m_pastePose = map.Get<Button>("PastePose");
ToggleMotionTPose = map.Get<Toggle>("TPose");
ToggleMotionBVH = map.Get<Toggle>("BVH");
ToggleMotion = map.Get<ToggleGroup>("_Motion_");
m_enableLipSync = map.Get<Toggle>("EnableLipSync");
m_enableAutoBlink = map.Get<Toggle>("EnableAutoBlink");
m_enableAutoExpression = map.Get<Toggle>("EnableAutoExpression");
m_texts.Reset(map);
m_target = GameObject.FindObjectOfType<ClothTargetMover>().gameObject;
}
// Runtime
GameObject m_root = default;
IVrm10Animation m_src = default;
public IVrm10Animation Motion
{
get { return m_src; }
set
{
if (m_src != null)
{
m_src.Dispose();
}
m_src = value;
TPose = new Vrm10TPose(m_src.ControlRig.Item1.GetRawHipsPosition());
}
}
public IVrm10Animation TPose;
private CancellationTokenSource _cancellationTokenSource;
Loaded m_loaded;
ClothWarp.HumanoidPose m_init;
static class ArgumentChecker
{
static string[] Supported = {
".gltf",
".glb",
".vrm",
".zip",
};
static string UnityHubPath => System.Environment.GetEnvironmentVariable("ProgramFiles") + "\\Unity\\Hub";
public static bool IsLoadable(string path)
{
if (!File.Exists(path))
{
// not exists
return false;
}
if (Application.isEditor)
{
// skip editor argument
// {UnityHub_Resources}\PackageManager\ProjectTemplates\com.unity.template.3d-5.0.4.tgz
if (path.StartsWith(UnityHubPath))
{
return false;
}
}
var ext = Path.GetExtension(path).ToLower();
if (!Supported.Contains(ext))
{
// unknown extension
return false;
}
return true;
}
public static bool TryGetFirstLoadable(out string cmd)
{
foreach (var arg in System.Environment.GetCommandLineArgs())
{
if (ArgumentChecker.IsLoadable(arg))
{
cmd = arg;
return true;
}
}
cmd = default;
return false;
}
}
[SerializeField]
public int Iteration = 32;
private void Start()
{
m_version.text = string.Format("VRMViewer {0}.{1}",
VRM10SpecVersion.MAJOR, VRM10SpecVersion.MINOR);
m_openModel.onClick.AddListener(OnOpenModelClicked);
m_openMotion.onClick.AddListener(OnOpenMotionClicked);
m_pastePose.onClick.AddListener(OnPastePoseClicked);
m_reconstructSprngBone.onClick.AddListener(OnReconstruct);
m_resetSpringBone.onClick.AddListener(OnReset);
// load initial bvh
if (m_motion != null)
{
Motion = BvhMotion.LoadBvhFromText(m_motion.text);
}
if (ArgumentChecker.TryGetFirstLoadable(out var cmd))
{
LoadModel(cmd);
}
m_texts.Start();
}
private void OnDestroy()
{
_cancellationTokenSource?.Dispose();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Tab))
{
if (m_root != null) m_root.SetActive(!m_root.activeSelf);
}
if (Input.GetKeyDown(KeyCode.Escape))
{
if (_cancellationTokenSource != null)
{
_cancellationTokenSource.Cancel();
}
}
if (Motion != null)
{
Motion.ShowBoxMan(m_showBoxMan.isOn);
}
if (m_loaded != null)
{
m_loaded.EnableLipSyncValue = m_enableLipSync.isOn;
m_loaded.EnableBlinkValue = m_enableAutoBlink.isOn;
m_loaded.EnableAutoExpressionValue = m_enableAutoExpression.isOn;
if (IsTPose)
{
m_loaded.Runtime.VrmAnimation = TPose;
}
else if (Motion != null)
{
// Automatically retarget in Vrm10Runtime.Process
m_loaded.Runtime.VrmAnimation = Motion;
}
}
}
void OnOpenModelClicked()
{
#if UNITY_EDITOR
var path = UnityEditor.EditorUtility.OpenFilePanel("Open VRM", "", "vrm");
#elif UNITY_STANDALONE_WIN
var path = ClothFileDialogForWindows.FileDialog("open VRM", "vrm");
#else
var path = Application.dataPath + "/default.vrm";
#endif
if (string.IsNullOrEmpty(path))
{
return;
}
var ext = Path.GetExtension(path).ToLower();
if (ext != ".vrm")
{
Debug.LogWarning($"{path} is not vrm");
return;
}
LoadModel(path);
}
async void OnOpenMotionClicked()
{
#if UNITY_EDITOR
var path = UnityEditor.EditorUtility.OpenFilePanel("Open Motion", "", "bvh");
#elif UNITY_STANDALONE_WIN
var path = ClothFileDialogForWindows.FileDialog("open Motion", "bvh", "gltf", "glb", "vrma");
#else
var path = Application.dataPath + "/default.bvh";
#endif
if (string.IsNullOrEmpty(path))
{
return;
}
var ext = Path.GetExtension(path).ToLower();
if (ext == ".bvh")
{
Motion = BvhMotion.LoadBvhFromPath(path);
return;
}
// gltf, glb etc...
using GltfData data = new AutoGltfFileParser(path).Parse();
using var loader = new VrmAnimationImporter(data);
var instance = await loader.LoadAsync(new ImmediateCaller());
Motion = instance.GetComponent<Vrm10AnimationInstance>();
instance.GetComponent<Animation>().Play();
}
async void OnPastePoseClicked()
{
var text = GUIUtility.systemCopyBuffer;
if (string.IsNullOrEmpty(text))
{
return;
}
try
{
Motion = await Vrm10PoseLoader.LoadVrmAnimationPose(text);
}
catch (UniJSON.ParserException)
{
Debug.LogWarning("UniJSON.ParserException");
}
catch (UniJSON.DeserializationException)
{
Debug.LogWarning("UniJSON.DeserializationException");
}
}
void OnReconstruct()
{
if (m_loaded == null)
{
return;
}
m_loaded.Runtime.SpringBone.ReconstructSpringBone();
// var system = m_loaded.Instance.GetComponent<ClothWarp.RotateParticleSystem>();
// system.ResetParticle();
}
void OnReset()
{
if (m_loaded == null)
{
return;
}
m_loaded.Runtime.SpringBone.RestoreInitialTransform();
// ResetStrandPose();
}
// Action<float> MakeSetPose()
// {
// var start = m_init;
// var animator = m_loaded.Instance.GetComponent<Animator>();
// var end = new ClothWarp.HumanoidPose(animator);
// return (float t) =>
// {
// ClothWarp.HumanoidPose.ApplyLerp(animator, start, end, t);
// };
// }
// void ResetStrandPose()
// {
// ResetStrandPose(MakeSetPose(), 32, 1.0f / 30, 60);
// }
// void ResetStrandPose(Action<float> setPose, int iteration, float timeDelta, int finish)
// {
// var system = m_loaded.Instance.GetComponent<ClothWarp.RotateParticleSystem>();
// // init
// setPose(0);
// system.ResetParticle();
// // lerp
// var t = 0.0f;
// var d = 1.0f / iteration;
// for (int i = 0; i < iteration; ++i, t += d)
// {
// setPose(t);
// system.Process(timeDelta);
// }
// // finish
// setPose(1.0f);
// for (int i = 0; i < finish; ++i)
// {
// system.Process(timeDelta);
// }
// }
static IMaterialDescriptorGenerator GetVrmMaterialDescriptorGenerator(bool useUrp)
{
if (useUrp)
{
return new UrpVrm10MaterialDescriptorGenerator();
}
else
{
return new BuiltInVrm10MaterialDescriptorGenerator();
}
}
void OnInit(Vrm10Instance vrm)
{
var animator = vrm.GetComponent<Animator>();
try
{
if (vrm.SpringBone.Springs.Count == 0)
{
ClothGuess.Guess(animator);
if (vrm.SpringBone.ColliderGroups.Count == 0)
{
HumanoidCollider.AddColliders(animator);
var warps = animator.GetComponentsInChildren<ClothWarpRoot>();
var colliderGroups = animator.GetComponentsInChildren<VRM10SpringBoneColliderGroup>();
foreach (var warp in warps)
{
warp.ColliderGroups = colliderGroups.ToList();
}
}
}
else
{
ClothWarpRuntimeProvider.FromVrm10(vrm,
go => go.AddComponent<ClothWarpRoot>());
}
if (m_addClothToHips.isOn)
{
if (animator.GetBoneTransform(HumanBodyBones.Hips) is var hips)
{
var cloth = hips.GetComponent<ClothGrid>();
if (cloth == null)
{
cloth = hips.gameObject.AddComponent<ClothGrid>();
cloth.Reset();
cloth.LoopIsClosed = true;
}
}
}
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
async void LoadModel(string path)
{
// cleanup
m_loaded?.Dispose();
m_loaded = null;
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = _cancellationTokenSource.Token;
try
{
Debug.LogFormat("{0}", path);
var vrm10Instance = await Vrm10.LoadPathAsync(path,
canLoadVrm0X: true,
showMeshes: false,
awaitCaller: m_useAsync.isOn
? new RuntimeOnlyAwaitCaller()
: new ImmediateCaller(),
materialGenerator: GetVrmMaterialDescriptorGenerator(true),
vrmMetaInformationCallback: m_texts.UpdateMeta,
springboneRuntime: m_useJob.isOn
? new UniVRM10.ClothWarp.Jobs.ClothWarpJobRuntime(OnInit)
: new UniVRM10.ClothWarp.ClothWarpRuntime(OnInit)
);
if (cancellationToken.IsCancellationRequested)
{
UnityObjectDestroyer.DestroyRuntimeOrEditor(vrm10Instance.gameObject);
cancellationToken.ThrowIfCancellationRequested();
}
if (vrm10Instance == null)
{
Debug.LogWarning("LoadPathAsync is null");
return;
}
var instance = vrm10Instance.GetComponent<RuntimeGltfInstance>();
instance.ShowMeshes();
instance.EnableUpdateWhenOffscreen();
m_loaded = new Loaded(instance, m_target.transform);
m_init = new ClothWarp.HumanoidPose(vrm10Instance.GetComponent<Animator>());
m_showBoxMan.isOn = false;
}
catch (Exception ex)
{
if (ex is OperationCanceledException)
{
Debug.LogWarning($"Canceled to Load: {path}");
}
else
{
Debug.LogError($"Failed to Load: {path}");
Debug.LogException(ex);
}
}
}
}
}

View File

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

View File

@@ -0,0 +1,62 @@
using System.Collections.Generic;
using UnityEngine;
namespace UniVRM10.Cloth.Viewer
{
public static class HumanoidCollider
{
static (string group, HumanBodyBones head, HumanBodyBones tail, float radius)[] Capsules = new[]
{
("Leg", HumanBodyBones.LeftUpperLeg, HumanBodyBones.LeftLowerLeg, 0.06f),
("Leg", HumanBodyBones.LeftLowerLeg, HumanBodyBones.LeftFoot, 0.05f),
("Leg", HumanBodyBones.RightUpperLeg, HumanBodyBones.RightLowerLeg, 0.06f),
("Leg", HumanBodyBones.RightLowerLeg, HumanBodyBones.RightFoot, 0.05f),
("Arm", HumanBodyBones.LeftUpperArm, HumanBodyBones.LeftLowerArm, 0.03f),
("Arm", HumanBodyBones.LeftLowerArm, HumanBodyBones.LeftHand, 0.03f),
("Arm", HumanBodyBones.LeftHand, HumanBodyBones.LeftMiddleProximal, 0.02f),
("Arm", HumanBodyBones.RightUpperArm, HumanBodyBones.RightLowerArm, 0.03f),
("Arm", HumanBodyBones.RightLowerArm, HumanBodyBones.RightHand, 0.03f),
("Arm", HumanBodyBones.RightHand, HumanBodyBones.RightMiddleProximal, 0.02f),
};
public static void AddColliders(Animator animator)
{
Dictionary<string, VRM10SpringBoneColliderGroup> map = new();
foreach (var (group, _head, _tail, radius) in Capsules)
{
if (!map.ContainsKey(group))
{
var g = animator.gameObject.AddComponent<VRM10SpringBoneColliderGroup>();
map.Add(group, g);
}
var head = animator.GetBoneTransform(_head);
var vrmCollider = head.gameObject.AddComponent<VRM10SpringBoneCollider>();
if (vrmCollider != null)
{
vrmCollider.Radius = radius;
vrmCollider.ColliderType = VRM10SpringBoneColliderTypes.Capsule;
var tail = animator.GetBoneTransform(_tail);
vrmCollider.Tail = head.worldToLocalMatrix.MultiplyPoint(tail.position);
map[group].Colliders.Add(vrmCollider);
}
}
}
static T GetOrAddComponent<T>(GameObject o) where T : Component
{
var t = o.GetComponent<T>();
if (t != null)
{
return t;
}
return o.AddComponent<T>();
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.IO;
using UniHumanoid;
using UnityEngine;
namespace UniVRM10.Cloth.Viewer
{
public class BvhMotion : IVrm10Animation
{
UniHumanoid.BvhImporterContext m_context;
public Transform Root => m_context?.Root.transform;
public SkinnedMeshRenderer m_boxMan;
public SkinnedMeshRenderer BoxMan => m_boxMan;
(INormalizedPoseProvider, ITPoseProvider) m_controlRig;
(INormalizedPoseProvider, ITPoseProvider) IVrm10Animation.ControlRig => m_controlRig;
IDictionary<ExpressionKey, Func<float>> _ExpressionMap = new Dictionary<ExpressionKey, Func<float>>();
public IReadOnlyDictionary<ExpressionKey, Func<float>> ExpressionMap => (IReadOnlyDictionary<ExpressionKey, Func<float>>)_ExpressionMap;
public LookAtInput? LookAt { get; set; }
public BvhMotion(UniHumanoid.BvhImporterContext context)
{
m_context = context;
var provider = new AnimatorPoseProvider(m_context.Root.transform, m_context.Root.GetComponent<Animator>());
m_controlRig = (provider, provider);
// create SkinnedMesh for bone visualize
var animator = m_context.Root.GetComponent<Animator>();
m_boxMan = SkeletonMeshUtility.CreateRenderer(animator);
var shaderName = UnityEngine.Rendering.GraphicsSettings.renderPipelineAsset == null
? "Standard"
: "Universal Render Pipeline/Lit"
;
var material = new Material(Shader.Find(shaderName));
BoxMan.sharedMaterial = material;
var mesh = BoxMan.sharedMesh;
mesh.name = "box-man";
}
public static BvhMotion LoadBvhFromText(string source, string path = "tmp.bvh")
{
var context = new UniHumanoid.BvhImporterContext();
context.Parse(path, source);
context.Load();
return new BvhMotion(context);
}
public static BvhMotion LoadBvhFromPath(string path)
{
return LoadBvhFromText(File.ReadAllText(path), path);
}
public void ShowBoxMan(bool enable)
{
m_boxMan.enabled = enable;
}
public void Dispose()
{
GameObject.Destroy(m_context.Root);
}
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 487929d3039a63544a0825523ac6a8ab
timeCreated: 1546851178
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace UniVRM10.Cloth.Viewer
{
public class ObjectMap
{
Dictionary<string, GameObject> _map = new();
public IReadOnlyDictionary<string, GameObject> Objects => _map;
public ObjectMap(GameObject root)
{
foreach (var x in root.GetComponentsInChildren<Transform>())
{
_map[x.name] = x.gameObject;
}
}
public T Get<T>(string name) where T : Component
{
return _map[name].GetComponent<T>();
}
}
[Serializable]
public class TextFields
{
[SerializeField]
Text m_textModelTitle = default;
[SerializeField]
Text m_textModelVersion = default;
[SerializeField]
Text m_textModelAuthor = default;
[SerializeField]
Text m_textModelCopyright = default;
[SerializeField]
Text m_textModelContact = default;
[SerializeField]
Text m_textModelReference = default;
[SerializeField]
RawImage m_thumbnail = default;
[SerializeField, Header("CharacterPermission")]
Text m_textPermissionAllowed = default;
[SerializeField]
Text m_textPermissionViolent = default;
[SerializeField]
Text m_textPermissionSexual = default;
[SerializeField]
Text m_textPermissionCommercial = default;
[SerializeField]
Text m_textPermissionOther = default;
[SerializeField, Header("DistributionLicense")]
Text m_textDistributionLicense = default;
[SerializeField]
Text m_textDistributionOther = default;
public void Reset(ObjectMap map)
{
m_textModelTitle = map.Get<Text>("Title (1)");
m_textModelVersion = map.Get<Text>("Version (1)");
m_textModelAuthor = map.Get<Text>("Author (1)");
m_textModelCopyright = map.Get<Text>("Copyright (1)");
m_textModelContact = map.Get<Text>("Contact (1)");
m_textModelReference = map.Get<Text>("Reference (1)");
m_textPermissionAllowed = map.Get<Text>("AllowedUser (1)");
m_textPermissionViolent = map.Get<Text>("Violent (1)");
m_textPermissionSexual = map.Get<Text>("Sexual (1)");
m_textPermissionCommercial = map.Get<Text>("Commercial (1)");
m_textPermissionOther = map.Get<Text>("Other (1)");
m_textDistributionLicense = map.Get<Text>("LicenseType (1)");
m_textDistributionOther = map.Get<Text>("OtherLicense (1)");
m_thumbnail = map.Get<RawImage>("RawImage");
}
public void Start()
{
m_textModelTitle.text = "";
m_textModelVersion.text = "";
m_textModelAuthor.text = "";
m_textModelCopyright.text = "";
m_textModelContact.text = "";
m_textModelReference.text = "";
m_textPermissionAllowed.text = "";
m_textPermissionViolent.text = "";
m_textPermissionSexual.text = "";
m_textPermissionCommercial.text = "";
m_textPermissionOther.text = "";
m_textDistributionLicense.text = "";
m_textDistributionOther.text = "";
}
public void UpdateMeta(Texture2D thumbnail, UniGLTF.Extensions.VRMC_vrm.Meta meta, Migration.Vrm0Meta meta0)
{
m_thumbnail.texture = thumbnail;
if (meta != null)
{
m_textModelTitle.text = meta.Name;
m_textModelVersion.text = meta.Version;
m_textModelAuthor.text = meta.Authors[0];
m_textModelCopyright.text = meta.CopyrightInformation;
m_textModelContact.text = meta.ContactInformation;
if (meta.References != null && meta.References.Count > 0)
{
m_textModelReference.text = meta.References[0];
}
m_textPermissionAllowed.text = meta.AvatarPermission.ToString();
m_textPermissionViolent.text = meta.AllowExcessivelyViolentUsage.ToString();
m_textPermissionSexual.text = meta.AllowExcessivelySexualUsage.ToString();
m_textPermissionCommercial.text = meta.CommercialUsage.ToString();
// m_textPermissionOther.text = meta.OtherPermissionUrl;
// m_textDistributionLicense.text = meta.ModificationLicense.ToString();
m_textDistributionOther.text = meta.OtherLicenseUrl;
}
if (meta0 != null)
{
m_textModelTitle.text = meta0.title;
m_textModelVersion.text = meta0.version;
m_textModelAuthor.text = meta0.author;
m_textModelContact.text = meta0.contactInformation;
m_textModelReference.text = meta0.reference;
m_textPermissionAllowed.text = meta0.allowedUser.ToString();
m_textPermissionViolent.text = meta0.violentUsage.ToString();
m_textPermissionSexual.text = meta0.sexualUsage.ToString();
m_textPermissionCommercial.text = meta0.commercialUsage.ToString();
m_textPermissionOther.text = meta0.otherPermissionUrl;
// m_textDistributionLicense.text = meta0.ModificationLicense.ToString();
m_textDistributionOther.text = meta0.otherLicenseUrl;
}
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,20 @@
{
"name": "ClothWarp.Editor",
"rootNamespace": "",
"references": [
"GUID:308b348fb80d89d42a9620951b0f60db",
"GUID:e47c917724578cc43b5506c17a27e9a0",
"GUID:3e5d614bc16b50d41bd94c8d7444ca46",
"GUID:8d76e605759c3f64a957d63ef96ada7c",
"GUID:5f875fdc81c40184c8333b9d63c6ddd5"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a3dafcb1fb23ff148bbc17714d01144c
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,162 @@
using System;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using UniGLTF;
using System.Linq;
namespace UniVRM10.ClothWarp.Components
{
[CustomEditor(typeof(ClothWarpRoot))]
class ClothWarpRootEditor : Editor
{
private ClothWarpRoot m_target;
private Vrm10Instance m_vrm;
private MultiColumnTreeView m_treeview;
VisualElement m_body;
void OnEnable()
{
if (target == null)
{
return;
}
m_target = (ClothWarpRoot)target;
m_vrm = m_target.GetComponentInParent<Vrm10Instance>();
}
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 = 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);
sb.Append("].");
sb.Append(subpath);
var s = sb.ToString();
// Debug.Log(s);
prop.BindProperty(serializedObject.FindProperty(s));
prop.SetEnabled(enableFunc(i));
}
},
});
}
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 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" });
{
Func<int, bool> isCustom = (i) =>
{
return m_target.Particles[i].Mode == ClothWarpRoot.ParticleMode.Custom;
};
m_treeview = new MultiColumnTreeView();
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);
m_body.Add(m_treeview);
}
m_body.Add(new PropertyField { bindingPath = nameof(ClothWarpRoot.ColliderGroups) });
return root;
}
private void OnValueChanged(SerializedObject so)
{
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());
}
}
}
m_treeview.RefreshItems();
m_body.style.display = m_target.Validations.All(x => x.ErrorLevel < ErrorLevels.Warning)
? DisplayStyle.Flex
: DisplayStyle.None
;
Repaint();
}
public void OnSceneGUI()
{
HandleUtility.Repaint();
if (m_treeview == null)
{
return;
}
var item = m_treeview.selectedItem;
if (item == null)
{
return;
}
if (item is ClothWarpRoot.Particle p)
{
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);
}
}
}
}

View File

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

View File

@@ -0,0 +1,116 @@
using UnityEngine.UIElements;
using UnityEditor;
using UnityEditor.UIElements;
namespace UniVRM10.ClothWarp.Components
{
[CustomEditor(typeof(ClothWarpRuntimeProvider))]
public class RotateParticleRuntimeProviderEditor : Editor
{
ClothWarpRuntimeProvider _target;
Vrm10Instance _vrm;
void OnEnable()
{
_target = (ClothWarpRuntimeProvider)target;
if (_target != null)
{
_vrm = _target.GetComponent<Vrm10Instance>();
}
}
public override VisualElement CreateInspectorGUI()
{
var root = new VisualElement();
root.Bind(serializedObject);
{
var s = new PropertyField { bindingPath = "m_Script" };
s.SetEnabled(false);
root.Add(s);
}
root.Add(new PropertyField { bindingPath = nameof(_target.UseJob) });
root.Add(new PropertyField { bindingPath = nameof(_target.Warps) });
root.Add(new PropertyField { bindingPath = nameof(_target.Cloths) });
{
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("Load Vrm-1.0 Springs to ClothWarp Warps");
var undo = Undo.GetCurrentGroup();
// attach ClothWarp from VRM10Instance.Springs
ClothWarpRuntimeProvider.FromVrm10(_vrm, Undo.AddComponent<ClothWarpRoot>);
Undo.RegisterFullObjectHierarchyUndo(_vrm.gameObject, "RegisterFullObjectHierarchyUndo");
// update ClothWarpRuntimeProvider
Undo.RegisterCompleteObjectUndo(_target, "RegisterCompleteObjectUndo");
_target.Reset();
Undo.CollapseUndoOperations(undo);
});
var clear_vrm10_springs = new Button { text = "Clear Vrm-1.0 springs" };
setup.Add(clear_vrm10_springs);
clear_vrm10_springs.RegisterCallback<ClickEvent>(e =>
{
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);
}
{
// 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();
});
}
return root;
}
}
}

View File

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

View File

@@ -0,0 +1,129 @@
# ClothWarp(仮)
これは UniVRM の cloth の開発版です。
## 概要
縦糸(Warp)を横に連結して四角格子(Grid)を作ります。
> 縦糸は従来の `SpringBone` とだいたい同じものです。
各四角格子にはバネによる横方向の拘束(フックの法則)と当たり判定(2枚の三角形) を持たせます。
これにより縦糸の間を `Collider`(球/カプセル) がすりぬけることを防止することができます。
## Components
設定置き場。布を構成する縦糸(Warp)と、縦糸を横に連結した四角格子(Grid)の2段階とする
### UniVRM10.ClothWarp.Components.ClothWarpRoot
各 particle にはVRM-0.X のように子孫を自動登録し Base 設定を適用する。
Base 設定を変更する場合は変える方を列挙設定できる。
Custom と Disable を選択できる。
- ゆれものの根元にアタッチする
- [x] 子孫に HumanoidBone がある場合にアタッチ不可
- [ ] 枝分かれ
- [ ] WarpRoot らからデフォルト以外の Warp を選び出す
- [ ] Center
- [ ] Scale
```
ひとつめの joint にアタッチする
| 子孫の Transform は自動的に登録される(デフォルト設定を適用)
| | 個別に無効化・カスタム設定をできるようにする
WarpRoot O=o=o=o=o
\
o 枝分かれも自動的に登録
```
| | MonoBehaviour | move | rotate |
| --------------- | ------------- | ------------------------- | ---------- |
| root | attach | 動かない | 回転する |
| particles[x] | | verlet 積分で慣性移動する | 回転する |
| particles[末端] | | verlet 積分で慣性移動する | 回転しない |
#### Settings
- [warp]verlet_center: 速度を算出する座標の原点(curernt-pvev は
- [warp]stiffness(剛性): 初期姿勢に戻る力
- [warp]force(Vec3): 下向きにすれば gravity
- [warp]dragforce(抗力): 減速率(1-dragforce を velocity に乗算する)
(current-current_center)-(prev-prev_center) に変わる)
- [particle]mode: base, custom, disabled
- [particle]stiffness(剛性): 初期姿勢に戻る力
- [particle]force(Vec3): 下向きにすれば gravity
- [particle]dragforce(抗力): 減速率(1-dragforce を velocity に乗算する)
#### 枝分かれの例外処理
- 枝分かれした particle は一番兄の particle の local 移動を複製する(独自の速度、衝突はしない)
### UniVRM10.ClothWarp.Components.ClothGrid
- 隣り合う Warp に横方向の拘束が追加される
- particle sphere の衝突のかわりに四角格子の三角形が衝突する
- [ ] デフォルト(WarpRoot から children[0] を選択する)以外の縦糸選択
- [ ] 三角形の厚み(法線方向の offset)
```
WarpRoot0 o=o=o=o
| | |
WarpRoot1 o=o=o <- 短い方の長さに合わせる
| | |
WarpRoot2 o=o=o
|
v
CloseLoop=true ならば WarpRoot0 と連結して輪を閉じる
```
#### Settings
- CloseLoop: 最後の Warp と 最初の Warp を接続して輪を作る。Warp が3本以上必要。
## Colliders
- UniVRM10.VRM10SpringBoneCollider
- UniVRM10.VRM10SpringBoneCollider.Group
を使う。
## Logic
### input phase
- current_position, prev_position が持ち越される
- {FromTransform}{位置を修正}{回転を得る} Update root position. each rotation.
### dynamics phase
- (if cloth){add force} weft constarint(横方向 ばね拘束)
- {位置を求める} 速度、力を解決
- {位置を修正} parent constraint(親子間の距離を一定に保つ。再帰)
### collision phase
- (if not cloth){位置を修正} particle(Sphere) x collider(Sphere or Capsule)
- (if cloth){位置を修正} cloth(Triangle) x collider(SPhere or Capsule)
### output phase
- {回転を修正} calc rotation from position(再帰) 👈 ここまで回転は出てこないことに注意
- {ToTransform} apply result rotation 👈 回転しかしない(伸びない)
## TODO
### Debug
- [ ] Debug 用の詳細な Gizmo
### 工夫
- [ ] Cloth の片面衝突
- [ ] 衝突時の velocity 下げ
### Optimize
- [x] 衝突グループ

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3c501aa9d3818bf419f0bcb53c59e91d
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,116 @@
using System.Collections.Generic;
using System.Linq;
using UniVRM10.ClothWarp.Components;
using SphereTriangle;
using UnityEngine;
namespace UniVRM10.ClothWarp
{
class ClothRectList
{
readonly List<Transform> _particles;
public readonly ClothGrid[] ClothGrids;
public List<(int, SpringConstraint, ClothRect)> List = new();
public readonly bool[] ClothUsedParticles;
public ClothRectList(List<Transform> particles, Vrm10Instance vrm)
{
_particles = particles;
ClothUsedParticles = new bool[_particles.Count];
ClothGrids = vrm.GetComponentsInChildren<ClothGrid>();
for (int i = 0; i < ClothGrids.Length; ++i)
{
AddCloth(i, 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];
var s1 = cloth.Warps[i];
for (int j = 0; j < s0.Particles.Count && j < s1.Particles.Count; ++j)
{
// d x x c
// | |
// a x-x b
var a = s0.Particles[j].Transform;
var b = s1.Particles[j].Transform;
var c = j == 0 ? s1.transform : s1.Particles[j - 1].Transform;
var d = j == 0 ? s0.transform : s0.Particles[j - 1].Transform;
ClothUsedParticles[_particles.IndexOf(a)] = true;
ClothUsedParticles[_particles.IndexOf(b)] = true;
ClothUsedParticles[_particles.IndexOf(c)] = true;
ClothUsedParticles[_particles.IndexOf(d)] = true;
if (i % 2 == 1)
{
// 互い違いに
// abcd to badc
(a, b) = (b, a);
(c, d) = (d, c);
}
List.Add((
clothGridIndex,
new SpringConstraint(
_particles.IndexOf(a),
_particles.IndexOf(b),
Vector3.Distance(
vrm.DefaultTransformStates[a].Position,
vrm.DefaultTransformStates[b].Position)),
new ClothRect(
_particles.IndexOf(a),
_particles.IndexOf(b),
_particles.IndexOf(c),
_particles.IndexOf(d))));
}
}
if (cloth.Warps.Count >= 3 && cloth.LoopIsClosed)
{
// close loop
var i = cloth.Warps.Count;
var s0 = cloth.Warps.Last();
var s1 = cloth.Warps.First();
for (int j = 0; j < s0.Particles.Count && j < s1.Particles.Count; ++j)
{
var a = s0.Particles[j].Transform;
var b = s1.Particles[j].Transform;
var c = j == 0 ? s1.transform : s1.Particles[j - 1].Transform;
var d = j == 0 ? s0.transform : s0.Particles[j - 1].Transform;
ClothUsedParticles[_particles.IndexOf(a)] = true;
ClothUsedParticles[_particles.IndexOf(b)] = true;
ClothUsedParticles[_particles.IndexOf(c)] = true;
ClothUsedParticles[_particles.IndexOf(d)] = true;
if (i % 2 == 1)
{
// 互い違いに
// abcd to badc
(a, b) = (b, a);
(c, d) = (d, c);
}
List.Add((
clothGridIndex,
new SpringConstraint(
_particles.IndexOf(a),
_particles.IndexOf(b),
Vector3.Distance(
vrm.DefaultTransformStates[a].Position,
vrm.DefaultTransformStates[b].Position)
),
new ClothRect(
_particles.IndexOf(a),
_particles.IndexOf(b),
_particles.IndexOf(c),
_particles.IndexOf(d)
)
));
}
}
}
}
}

View File

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

View File

@@ -0,0 +1,20 @@
{
"name": "ClothWarp",
"rootNamespace": "",
"references": [
"GUID:3e5d614bc16b50d41bd94c8d7444ca46",
"GUID:e47c917724578cc43b5506c17a27e9a0",
"GUID:8d76e605759c3f64a957d63ef96ada7c",
"GUID:1cd941934d098654fa21a13f28346412",
"GUID:5f875fdc81c40184c8333b9d63c6ddd5"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": false,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 308b348fb80d89d42a9620951b0f60db
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using SphereTriangle;
using UnityEngine;
namespace UniVRM10.ClothWarp
{
[Serializable]
public class ClothWarpNode
{
public ParticleInitState Init;
public ParticleRuntimeState State;
public readonly List<ClothWarpNode> Children = new();
public readonly ClothWarpNode Parent;
// 現フレームの力積算
public Vector3 Force = Vector3.zero;
// 直前で接触があった
public bool HasCollide = false;
public ClothWarpNode(int index, ClothWarpNode parent, SimulationEnv env, Transform transform, float radius, float mass)
{
Init = new ParticleInitState(index, transform, radius, mass);
State = new ParticleRuntimeState(env, transform);
Parent = parent;
}
public void BeginFrame(SimulationEnv env, FrameTime time, in Vector3 rest)
{
// integrate forces
Force = Vector3.zero;
// 曲げ
if (HasCollide)
{
// 震え防止。ちょっとマイルドになるような気もする?
}
else
{
// Stiffness: 1 で即時に元に戻る
Force += (rest - State.Current) * env.Stiffness / time.SqDt;
}
// 外力(sqDtで割るとピーキーすぎるのでこれでいいのでは?)
Force += env.External / time.DeltaTime;
}
public Vector3 Verlet(SimulationEnv env, FrameTime time)
{
var velocity = (State.Current - State.Prev);
if (HasCollide)
{
// 震え防止。ちょっとマイルドになるような気もする?
velocity = Vector3.zero;
}
else
{
// DragForce: 1 で即時停止
velocity *= (1 - env.DragForce);
}
HasCollide = false;
return State.Current + velocity + Force * time.SqDt;
}
/// <summary>
/// get ParentParent.rotatio * Parent.Init.LocalRotation
/// </summary>
/// <param name="transforms"></param>
/// <returns></returns>
public Quaternion RestRotation(IReadOnlyList<Transform> transforms)
{
if (Parent == null)
{
return Quaternion.identity;
}
var parent = transforms[Parent.Init.Index];
if (Parent.Parent == null)
{
var pt = parent.parent;
if (pt == null)
{
return Parent.Init.LocalRotation;
}
return pt.rotation * Parent.Init.LocalRotation;
}
var parentparent = transforms[Parent.Parent.Init.Index];
var restRotation = parentparent.rotation * Parent.Init.LocalRotation;
return restRotation;
}
public void OnDrawGizmos(Transform transform)
{
if (Init.Radius == 0)
{
return;
}
Gizmos.color = Init.Mass == 0 ? Color.red : Color.gray;
if (transform.parent != null && Init.Mass > 0)
{
Gizmos.matrix = Matrix4x4.identity;
Gizmos.DrawLine(transform.parent.position, transform.position);
}
Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.DrawWireSphere(Vector3.zero, Init.Radius);
var r = Init.Radius * 2;
Gizmos.color = Color.red;
Gizmos.DrawLine(Vector3.left * r, Vector3.right * r);
Gizmos.color = Color.green;
Gizmos.DrawLine(Vector3.down * r, Vector3.up * r);
Gizmos.color = Color.blue;
Gizmos.DrawLine(Vector3.back * r, Vector3.forward * r);
}
}
}

View File

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

View File

@@ -0,0 +1,437 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UniVRM10.ClothWarp.Components;
using SphereTriangle;
using UniGLTF;
using UniGLTF.SpringBoneJobs.Blittables;
using UnityEngine;
namespace UniVRM10.ClothWarp
{
/// <summary>
/// プロトタイプ。非 job
/// </summary>
public class ClothWarpRuntime : IVrm10SpringBoneRuntime
{
Vrm10Instance _vrm;
Action<Vrm10Instance> _onInit;
bool _initialized = false;
bool _building = false;
public SimulationEnv Env = new()
{
DragForce = 0.6f,
Stiffness = 0.07f,
};
public List<VRM10SpringBoneColliderGroup> _colliderGroups = new();
public float _clothFactor = 0.5f;
// runtime
public List<Strand> _strands = new List<Strand>();
public ParticleList _list = new();
ClothRectList _clothRects;
public List<ClothRectCollision> _clothRectCollisions = new();
public PositionList _newPos;
Vector3[] _restPositions;
static Color[] Colors = new Color[]
{
Color.yellow,
Color.green,
Color.magenta,
};
Color GetGizmoColor(VRM10SpringBoneColliderGroup g)
{
for (int i = 0; i < _colliderGroups.Count; ++i)
{
if (_colliderGroups[i] == g)
{
return Colors[i];
}
}
return Color.gray;
}
public Task InitializeAsync(Vrm10Instance vrm, IAwaitCaller awaitCaller)
{
_building = true;
_vrm = vrm;
if (_onInit != null)
{
_onInit(vrm);
_onInit = null;
}
_initialized = false;
var strandMap = new Dictionary<Components.ClothWarpRoot, Strand>();
var warps = vrm.GetComponentsInChildren<Components.ClothWarpRoot>();
foreach (var warp in warps)
{
var strands = new List<Strand>();
var strand = _list.MakeParticleStrand(Env, warp);
strands.Add(strand);
_strands.AddRange(strands);
strandMap.Add(warp, strand);
foreach (var g in warp.ColliderGroups)
{
foreach (var c in g.Colliders)
{
if (c != null)
{
AddColliderIfNotExists(g.name, c);
}
}
}
}
_clothRects = new ClothRectList(_list._particleTransforms, vrm);
_newPos = new(_list._particles.Count);
_list.EndInitialize(_newPos.Init);
_restPositions = new Vector3[_list._particles.Count];
_newPos.EndInitialize();
_clothRectCollisions = new();
for (int i = 0; i < _clothRects.List.Count; ++i)
{
var (grid, s, r) = _clothRects.List[i];
_clothRectCollisions.Add(new());
var c = _clothRectCollisions.Last();
c.InitializeColliderSide(_newPos, _colliderGroups, r);
}
// await awaitCaller.NextFrame();
_initialized = true;
_building = false;
return Task.CompletedTask;
}
/// <summary>
/// すべての Particle を Init 状態にする。
/// Verlet の Prev を現在地に更新する(速度0)。
/// </summary>
public void RestoreInitialTransform()
{
foreach (var strand in _strands)
{
strand.Reset(_list._particleTransforms);
}
// foreach (var p in _system._list._particleTransforms)
// {
// p.transform.localRotation = _vrm.DefaultTransformStates[p.transform].LocalRotation;
// }
}
public void Process()
{
Process(Time.deltaTime);
}
void Process(float deltaTime)
{
if (!_initialized)
{
return;
}
using var profile = new ProfileSample("ClothWarp");
_newPos.BoundsCache.Clear();
using (new ProfileSample("UpdateRoot"))
{
//
// input
//
// 各strandのrootの移動と回転を外部から入力する。
// それらを元に各 joint の方向を元に戻した場合の戻り位置を計算する
foreach (var strand in _strands)
{
strand.UpdateRoot(_list._particleTransforms, _newPos, _restPositions);
}
}
using (new ProfileSample("Verlet"))
{
//
// particle simulation
//
// verlet 積分
var time = new FrameTime(deltaTime);
_list.BeginFrame(Env, time, _restPositions);
foreach (var (gridIndex, spring, collision) in _clothRects.List)
{
// cloth constraint
spring.Resolve(time, _clothFactor, _list._particles);
}
_list.Verlet(Env, time, _newPos.Init);
// 長さで拘束
foreach (var strand in _strands)
{
strand.ForceLength(_list._particleTransforms, _newPos);
}
}
// collision
using (new ProfileSample("Collision"))
{
for (int i = 0; i < _colliderGroups.Count; ++i)
{
var g = _colliderGroups[i];
for (int j = 0; j < _clothRects.List.Count; ++j)
{
var (gridIndex, spring, rect) = _clothRects.List[j];
var collision = _clothRectCollisions[j];
// using var prof = new ProfileSample("Collision: Cloth");
// 頂点 abcd は同じ CollisionMask
// TODO:
// if (_list._particles[rect._a].Init.CollisionMask.HasFlag((CollisionGroupMask)(i + 1)))
{
// cloth
collision.Collide(_newPos, g.Colliders, rect);
}
}
for (int j = 0; j < _list._particles.Count; ++j)
{
// using var prof = new ProfileSample("Collision: Strand");
if (_clothRects.ClothUsedParticles[j])
{
// 布で処理された
continue;
}
var particle = _list._particles[j];
if (particle.Init.Mass == 0)
{
continue;
}
// 紐の当たり判定
// TODO:
// if (particle.Init.CollisionMask.HasFlag((CollisionGroupMask)(i + 1)))
{
var p = _newPos.Get(j);
foreach (var c in g.Colliders)
{
// strand
if (c != null && TryCollide(c, p, particle.Init.Radius, out var resolved))
{
_newPos.CollisionMove(particle.Init.Index, resolved, c.Radius);
}
}
}
}
}
}
using (new ProfileSample("Apply"))
{
for (int i = 0; i < _newPos.CollisionCount.Length; ++i)
{
if (_newPos.CollisionCount[i] > 0)
{
_list._particles[i].HasCollide = true;
}
}
var result = _newPos.Resolve();
//
// apply result
//
// apply positions and
// calc rotation from positions recursive
foreach (var strand in _strands)
{
strand.Apply(_list._particleTransforms, result);
}
}
}
public VRM10SpringBoneColliderGroup GetOrAddColliderGroup(string groupName, GameObject go)
{
foreach (var g in _colliderGroups)
{
if (g.Name == groupName)
{
return g;
}
}
var group = go.GetOrAddComponent<VRM10SpringBoneColliderGroup>();
_colliderGroups.Add(group);
return group;
}
void AddColliderIfNotExists(string groupName,
VRM10SpringBoneCollider c)
{
var group = GetOrAddColliderGroup(groupName, c.gameObject);
foreach (var collider in group.Colliders)
{
if (collider == null)
{
continue;
}
if (collider == c)
{
return;
}
}
// c.GizmoColor = GetGizmoColor(group);
group.Colliders.Add(c);
}
/// <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);
}
/// <summary>
/// collision for strand
/// </summary>
/// <param name="p"></param>
/// <param name="radius"></param>
/// <param name="resolved"></param>
/// <returns></returns>
public bool TryCollide(VRM10SpringBoneCollider c, in Vector3 p, float radius, out LineSegment resolved)
{
var headWorldPosition = c.transform.TransformPoint(c.Offset);
if (c.ColliderType == VRM10SpringBoneColliderTypes.Capsule)
{
var tailWorldPosition = c.transform.TransformPoint(c.TailOrNormal);
return TryCollideCapsuleAndSphere(headWorldPosition, tailWorldPosition, c.Radius, p, radius, out resolved);
}
else
{
return TryCollideSphereAndSphere(headWorldPosition, c.Radius, p, radius, out resolved);
}
}
void IVrm10SpringBoneRuntime.DrawGizmos()
{
_list.DrawGizmos();
for (int i = 0; i < _clothRectCollisions.Count; ++i)
{
// var (spring, rect) = _clothRects[i];
var collision = _clothRectCollisions[i];
// collision.DrawGizmos();
}
if (_newPos != null)
{
_newPos.DrawGizmos();
}
}
public void SetJointLevel(Transform joint, BlittableJointMutable jointSettings)
{
throw new NotImplementedException();
}
public void SetModelLevel(Transform modelRoot, BlittableModelLevel modelSettings)
{
}
public ClothWarpRuntime(Action<Vrm10Instance> onInit = null)
{
_onInit = onInit;
}
public void Dispose()
{
}
public bool ReconstructSpringBone()
{
if (_vrm == null)
{
return false;
}
if (_building)
{
return false;
}
var task = InitializeAsync(_vrm, new ImmediateCaller());
task.Wait();
return true;
}
}
}

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More