mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-04-25 07:28:51 -05:00
rename to ClothWarp
This commit is contained in:
parent
c04723fc36
commit
7ba7028f03
|
|
@ -1,8 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RotateParticle;
|
||||
using RotateParticle.Components;
|
||||
using SphereTriangle;
|
||||
using ClothWarpLib.Components;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
|
|
@ -24,7 +22,7 @@ namespace UniVRM10.Cloth.Viewer
|
|||
if (TryAddGroup(animator, HumanBodyBones.Hips,
|
||||
new[] { "skirt", "スカート", "スカート" }, out var g))
|
||||
{
|
||||
var c = g[0].gameObject.AddComponent<RectCloth>();
|
||||
var c = g[0].gameObject.AddComponent<ClothGrid>();
|
||||
c.Warps = g;
|
||||
c.LoopIsClosed = true;
|
||||
}
|
||||
|
|
@ -33,7 +31,7 @@ namespace UniVRM10.Cloth.Viewer
|
|||
if (TryAddGroupChildChild(animator, HumanBodyBones.Hips,
|
||||
new[] { "skirt", "スカート", "スカート" }, new string[] { }, out var g))
|
||||
{
|
||||
var c = g[0].gameObject.AddComponent<RectCloth>();
|
||||
var c = g[0].gameObject.AddComponent<ClothGrid>();
|
||||
c.Warps = g;
|
||||
c.LoopIsClosed = true;
|
||||
}
|
||||
|
|
@ -48,7 +46,7 @@ namespace UniVRM10.Cloth.Viewer
|
|||
if (TryAddGroup(animator, HumanBodyBones.Hips,
|
||||
new[] { "裾" }, out var g))
|
||||
{
|
||||
var c = g[0].gameObject.AddComponent<RectCloth>();
|
||||
var c = g[0].gameObject.AddComponent<ClothGrid>();
|
||||
c.Warps = g;
|
||||
}
|
||||
}
|
||||
|
|
@ -56,7 +54,7 @@ namespace UniVRM10.Cloth.Viewer
|
|||
if (TryAddGroupChildChild(animator, HumanBodyBones.LeftUpperArm,
|
||||
new[] { "袖" }, new[] { "ひじ袖" }, out var g))
|
||||
{
|
||||
var c = g[0].gameObject.AddComponent<RectCloth>();
|
||||
var c = g[0].gameObject.AddComponent<ClothGrid>();
|
||||
c.Warps = g;
|
||||
c.LoopIsClosed = true;
|
||||
}
|
||||
|
|
@ -65,7 +63,7 @@ namespace UniVRM10.Cloth.Viewer
|
|||
if (TryAddGroupChildChild(animator, HumanBodyBones.LeftLowerArm,
|
||||
new[] { "袖" }, new string[] { }, out var g))
|
||||
{
|
||||
var c = g[0].gameObject.AddComponent<RectCloth>();
|
||||
var c = g[0].gameObject.AddComponent<ClothGrid>();
|
||||
c.Warps = g;
|
||||
c.LoopIsClosed = true;
|
||||
}
|
||||
|
|
@ -74,7 +72,7 @@ namespace UniVRM10.Cloth.Viewer
|
|||
if (TryAddGroupChildChild(animator, HumanBodyBones.RightUpperArm,
|
||||
new[] { "袖" }, new[] { "ひじ袖" }, out var g))
|
||||
{
|
||||
var c = g[0].gameObject.AddComponent<RectCloth>();
|
||||
var c = g[0].gameObject.AddComponent<ClothGrid>();
|
||||
c.Warps = g;
|
||||
c.LoopIsClosed = true;
|
||||
}
|
||||
|
|
@ -83,7 +81,7 @@ namespace UniVRM10.Cloth.Viewer
|
|||
if (TryAddGroupChildChild(animator, HumanBodyBones.RightLowerArm,
|
||||
new[] { "袖" }, new string[] { }, out var g))
|
||||
{
|
||||
var c = g[0].gameObject.AddComponent<RectCloth>();
|
||||
var c = g[0].gameObject.AddComponent<ClothGrid>();
|
||||
c.Warps = g;
|
||||
}
|
||||
}
|
||||
|
|
@ -91,7 +89,7 @@ namespace UniVRM10.Cloth.Viewer
|
|||
if (TryAddGroup(animator, HumanBodyBones.Chest, new[] { "マント" },
|
||||
out var g))
|
||||
{
|
||||
var c = g[0].gameObject.AddComponent<RectCloth>();
|
||||
var c = g[0].gameObject.AddComponent<ClothGrid>();
|
||||
c.Warps = g;
|
||||
}
|
||||
}
|
||||
|
|
@ -113,7 +111,7 @@ namespace UniVRM10.Cloth.Viewer
|
|||
static bool TryAddGroupChildChild(
|
||||
Animator animator, HumanBodyBones humanBone,
|
||||
string[] targets, string[] excludes,
|
||||
out List<WarpRoot> group)
|
||||
out List<ClothWarpLib.Components.ClothWarp> group)
|
||||
{
|
||||
var bone = animator.GetBoneTransform(humanBone);
|
||||
if (bone == null)
|
||||
|
|
@ -123,7 +121,7 @@ namespace UniVRM10.Cloth.Viewer
|
|||
return false;
|
||||
}
|
||||
|
||||
List<WarpRoot> transforms = new();
|
||||
List<ClothWarp> transforms = new();
|
||||
foreach (Transform child in bone)
|
||||
{
|
||||
foreach (Transform childchild in child)
|
||||
|
|
@ -137,7 +135,7 @@ namespace UniVRM10.Cloth.Viewer
|
|||
{
|
||||
if (childchild.name.ToLower().Contains(target.ToLower()))
|
||||
{
|
||||
var warp = childchild.gameObject.AddComponent<WarpRoot>();
|
||||
var warp = childchild.gameObject.AddComponent<ClothWarp>();
|
||||
// Name = name,
|
||||
// CollisionMask = mask,
|
||||
warp.BaseSettings.radius = 0.02f;
|
||||
|
|
@ -160,7 +158,7 @@ namespace UniVRM10.Cloth.Viewer
|
|||
}
|
||||
|
||||
static bool TryAddGroup(Animator animator, HumanBodyBones humanBone, string[] targets,
|
||||
out List<WarpRoot> group)
|
||||
out List<ClothWarpLib.Components.ClothWarp> group)
|
||||
{
|
||||
var bone = animator.GetBoneTransform(humanBone);
|
||||
if (bone == null)
|
||||
|
|
@ -170,14 +168,14 @@ namespace UniVRM10.Cloth.Viewer
|
|||
return false;
|
||||
}
|
||||
|
||||
List<WarpRoot> transforms = new();
|
||||
List<ClothWarpLib.Components.ClothWarp> transforms = new();
|
||||
foreach (Transform child in bone)
|
||||
{
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (child.name.ToLower().Contains(target.ToLower()))
|
||||
{
|
||||
var warp = child.gameObject.AddComponent<WarpRoot>();
|
||||
var warp = child.gameObject.AddComponent<ClothWarp>();
|
||||
if (warp != null)
|
||||
{
|
||||
// CollisionMask = mask,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using RotateParticle.Components;
|
||||
using ClothWarpLib.Components;
|
||||
using UniGLTF;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
|
@ -103,7 +103,7 @@ namespace UniVRM10.Cloth.Viewer
|
|||
private CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
Loaded m_loaded;
|
||||
RotateParticle.HumanoidPose m_init;
|
||||
ClothWarpLib.HumanoidPose m_init;
|
||||
|
||||
static class ArgumentChecker
|
||||
{
|
||||
|
|
@ -313,7 +313,7 @@ namespace UniVRM10.Cloth.Viewer
|
|||
return;
|
||||
}
|
||||
m_loaded.Runtime.SpringBone.ReconstructSpringBone();
|
||||
// var system = m_loaded.Instance.GetComponent<RotateParticle.RotateParticleSystem>();
|
||||
// var system = m_loaded.Instance.GetComponent<ClothWarp.RotateParticleSystem>();
|
||||
// system.ResetParticle();
|
||||
}
|
||||
|
||||
|
|
@ -331,10 +331,10 @@ namespace UniVRM10.Cloth.Viewer
|
|||
// {
|
||||
// var start = m_init;
|
||||
// var animator = m_loaded.Instance.GetComponent<Animator>();
|
||||
// var end = new RotateParticle.HumanoidPose(animator);
|
||||
// var end = new ClothWarp.HumanoidPose(animator);
|
||||
// return (float t) =>
|
||||
// {
|
||||
// RotateParticle.HumanoidPose.ApplyLerp(animator, start, end, t);
|
||||
// ClothWarp.HumanoidPose.ApplyLerp(animator, start, end, t);
|
||||
// };
|
||||
// }
|
||||
|
||||
|
|
@ -345,7 +345,7 @@ namespace UniVRM10.Cloth.Viewer
|
|||
|
||||
// void ResetStrandPose(Action<float> setPose, int iteration, float timeDelta, int finish)
|
||||
// {
|
||||
// var system = m_loaded.Instance.GetComponent<RotateParticle.RotateParticleSystem>();
|
||||
// var system = m_loaded.Instance.GetComponent<ClothWarp.RotateParticleSystem>();
|
||||
|
||||
// // init
|
||||
// setPose(0);
|
||||
|
|
@ -392,7 +392,7 @@ namespace UniVRM10.Cloth.Viewer
|
|||
if (vrm.SpringBone.ColliderGroups.Count == 0)
|
||||
{
|
||||
HumanoidCollider.AddColliders(animator);
|
||||
var warps = animator.GetComponentsInChildren<WarpRoot>();
|
||||
var warps = animator.GetComponentsInChildren<ClothWarp>();
|
||||
var colliderGroups = animator.GetComponentsInChildren<VRM10SpringBoneColliderGroup>();
|
||||
foreach (var warp in warps)
|
||||
{
|
||||
|
|
@ -402,17 +402,17 @@ namespace UniVRM10.Cloth.Viewer
|
|||
}
|
||||
else
|
||||
{
|
||||
RotateParticleRuntimeProvider.FromVrm10(vrm,
|
||||
go => go.AddComponent<WarpRoot>(),
|
||||
ClothWarpRuntimeProvider.FromVrm10(vrm,
|
||||
go => go.AddComponent<ClothWarp>(),
|
||||
o => GameObject.DestroyImmediate(o));
|
||||
}
|
||||
|
||||
if (animator.GetBoneTransform(HumanBodyBones.Hips) is var hips)
|
||||
{
|
||||
var cloth = hips.GetComponent<RectCloth>();
|
||||
var cloth = hips.GetComponent<ClothGrid>();
|
||||
if (cloth == null)
|
||||
{
|
||||
cloth = hips.gameObject.AddComponent<RectCloth>();
|
||||
cloth = hips.gameObject.AddComponent<ClothGrid>();
|
||||
cloth.Reset();
|
||||
cloth.LoopIsClosed = true;
|
||||
}
|
||||
|
|
@ -445,8 +445,8 @@ namespace UniVRM10.Cloth.Viewer
|
|||
materialGenerator: GetVrmMaterialDescriptorGenerator(true),
|
||||
vrmMetaInformationCallback: m_texts.UpdateMeta,
|
||||
springboneRuntime: m_useJob.isOn
|
||||
? new RotateParticle.Jobs.RotateParticleJobRuntime(OnInit)
|
||||
: new RotateParticle.RotateParticleSpringboneRuntime(OnInit)
|
||||
? new ClothWarpLib.Jobs.ClothWarpJobRuntime(OnInit)
|
||||
: new ClothWarpLib.ClothWarpRuntime(OnInit)
|
||||
);
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
|
|
@ -464,7 +464,7 @@ namespace UniVRM10.Cloth.Viewer
|
|||
instance.ShowMeshes();
|
||||
instance.EnableUpdateWhenOffscreen();
|
||||
m_loaded = new Loaded(instance, m_target.transform);
|
||||
m_init = new RotateParticle.HumanoidPose(vrm10Instance.GetComponent<Animator>());
|
||||
m_init = new ClothWarpLib.HumanoidPose(vrm10Instance.GetComponent<Animator>());
|
||||
m_showBoxMan.isOn = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using RotateParticle;
|
||||
using ClothWarpLib;
|
||||
using SphereTriangle;
|
||||
using UnityEngine;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "RotateParticle.Editor",
|
||||
"name": "ClothWarp.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:308b348fb80d89d42a9620951b0f60db",
|
||||
|
|
@ -4,12 +4,12 @@ using UnityEngine;
|
|||
using UniVRM10;
|
||||
|
||||
|
||||
namespace RotateParticle.Components
|
||||
namespace ClothWarpLib.Components
|
||||
{
|
||||
[CustomEditor(typeof(RotateParticleRuntimeProvider))]
|
||||
[CustomEditor(typeof(ClothWarpRuntimeProvider))]
|
||||
public class RotateParticleRuntimeProviderEditor : Editor
|
||||
{
|
||||
const string FROM_VRM10_MENU = "Replace VRM10 Springs to RotateParticle Warps";
|
||||
const string FROM_VRM10_MENU = "Replace VRM10 Springs to ClothWarp Warps";
|
||||
|
||||
[MenuItem(FROM_VRM10_MENU, true)]
|
||||
public static bool IsFromVrm10()
|
||||
|
|
@ -24,7 +24,7 @@ namespace RotateParticle.Components
|
|||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var provider = target as RotateParticleRuntimeProvider;
|
||||
var provider = target as ClothWarpRuntimeProvider;
|
||||
if (provider == null)
|
||||
{
|
||||
return;
|
||||
|
|
@ -32,14 +32,14 @@ namespace RotateParticle.Components
|
|||
var instance = provider.GetComponent<Vrm10Instance>();
|
||||
using (new EditorGUI.DisabledScope(instance == null))
|
||||
{
|
||||
if (GUILayout.Button("Replace VRM10 Springs to RotateParticle Warps"))
|
||||
if (GUILayout.Button("Replace VRM10 Springs to ClothWarp Warps"))
|
||||
{
|
||||
Undo.IncrementCurrentGroup();
|
||||
Undo.SetCurrentGroupName(FROM_VRM10_MENU);
|
||||
var undo = Undo.GetCurrentGroup();
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(instance, "RegisterCompleteObjectUndo");
|
||||
RotateParticleRuntimeProvider.FromVrm10(instance, Undo.AddComponent<WarpRoot>, Undo.DestroyObjectImmediate);
|
||||
ClothWarpRuntimeProvider.FromVrm10(instance, Undo.AddComponent<ClothWarp>, Undo.DestroyObjectImmediate);
|
||||
Undo.RegisterFullObjectHierarchyUndo(instance.gameObject, "RegisterFullObjectHierarchyUndo");
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(provider, "RegisterCompleteObjectUndo");
|
||||
|
|
@ -7,12 +7,12 @@ using UnityEngine;
|
|||
using UnityEngine.UIElements;
|
||||
using UniVRM10;
|
||||
|
||||
namespace RotateParticle.Components
|
||||
namespace ClothWarpLib.Components
|
||||
{
|
||||
[CustomEditor(typeof(WarpRoot))]
|
||||
[CustomEditor(typeof(ClothWarp))]
|
||||
class WarpRootEditor : Editor
|
||||
{
|
||||
private WarpRoot m_target;
|
||||
private ClothWarp m_target;
|
||||
private Vrm10Instance m_vrm;
|
||||
private MultiColumnTreeView m_treeview;
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ namespace RotateParticle.Components
|
|||
return;
|
||||
}
|
||||
|
||||
m_target = (WarpRoot)target;
|
||||
m_target = (ClothWarp)target;
|
||||
m_vrm = m_target.GetComponentInParent<Vrm10Instance>();
|
||||
}
|
||||
|
||||
|
|
@ -85,14 +85,14 @@ namespace RotateParticle.Components
|
|||
s.SetEnabled(false);
|
||||
root.Add(s);
|
||||
}
|
||||
root.Add(new PropertyField { bindingPath = nameof(WarpRoot.BaseSettings) });
|
||||
root.Add(new PropertyField { bindingPath = nameof(WarpRoot.Center) });
|
||||
root.Add(new PropertyField { bindingPath = nameof(ClothWarp.BaseSettings) });
|
||||
root.Add(new PropertyField { bindingPath = nameof(ClothWarp.Center) });
|
||||
|
||||
// root.Add(new PropertyField { bindingPath = "m_particles" });
|
||||
{
|
||||
Func<int, bool> isCustom = (i) =>
|
||||
{
|
||||
return m_target.Particles[i].Mode == WarpRoot.ParticleMode.Custom;
|
||||
return m_target.Particles[i].Mode == ClothWarp.ParticleMode.Custom;
|
||||
};
|
||||
|
||||
m_treeview = new MultiColumnTreeView();
|
||||
|
|
@ -109,7 +109,7 @@ namespace RotateParticle.Components
|
|||
root.Add(m_treeview);
|
||||
}
|
||||
|
||||
root.Add(new PropertyField { bindingPath = nameof(WarpRoot.ColliderGroups) });
|
||||
root.Add(new PropertyField { bindingPath = nameof(ClothWarp.ColliderGroups) });
|
||||
|
||||
return root;
|
||||
}
|
||||
|
|
@ -142,7 +142,7 @@ namespace RotateParticle.Components
|
|||
return;
|
||||
}
|
||||
|
||||
if (item is WarpRoot.Particle p)
|
||||
if (item is ClothWarp.Particle p)
|
||||
{
|
||||
p = m_target.GetParticleFromTransform(p.Transform);
|
||||
var t = p.Transform;
|
||||
121
Assets/VRM10_Samples/ClothSample/ClothWarp/README.md
Normal file
121
Assets/VRM10_Samples/ClothSample/ClothWarp/README.md
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
# ClothWarp(仮)
|
||||
|
||||
これは UniVRM の cloth の開発版です。
|
||||
|
||||
## 概要
|
||||
|
||||
縦糸(Warp)を横に連結して四角格子(Grid)を作ります。
|
||||
|
||||
> 縦糸は従来の `SpringBone` とだいたい同じものです。
|
||||
|
||||
各四角格子にはバネによる横方向の拘束(フックの法則)と当たり判定(2枚の三角形) を持たせます。
|
||||
|
||||
これにより縦糸の間を `Collider`(球/カプセル) がすりぬけることを防止することができます。
|
||||
|
||||
## Components
|
||||
|
||||
設定置き場。布を構成する縦糸(Warp)と、縦糸を横に連結した四角格子(Grid)の2段階とする
|
||||
|
||||
### ClothWarp.Components.ClothWarp
|
||||
|
||||
各 particle にはVRM-0.X のように子孫を自動登録し Base 設定を適用する。
|
||||
Base 設定を変更する場合は変える方を列挙設定できる。
|
||||
Custom と Disable を選択できる。
|
||||
|
||||
- ゆれものの根元にアタッチする
|
||||
- [ ] 枝分かれ
|
||||
- [ ] 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 移動を複製する(独自の速度、衝突はしない)
|
||||
|
||||
### 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本以上必要。
|
||||
|
||||
## 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
|
||||
|
||||
- [ ] 衝突グループ(現状総当たり)
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RotateParticle.Components;
|
||||
using ClothWarpLib.Components;
|
||||
using SphereTriangle;
|
||||
using UnityEngine;
|
||||
using UniVRM10;
|
||||
|
||||
namespace RotateParticle
|
||||
namespace ClothWarpLib
|
||||
{
|
||||
class ClothRectList
|
||||
{
|
||||
|
|
@ -19,14 +19,14 @@ namespace RotateParticle
|
|||
_particles = particles;
|
||||
ClothUsedParticles = new bool[_particles.Count];
|
||||
|
||||
var cloths = vrm.GetComponentsInChildren<RectCloth>();
|
||||
var cloths = vrm.GetComponentsInChildren<ClothGrid>();
|
||||
foreach (var cloth in cloths)
|
||||
{
|
||||
AddCloth(cloth, vrm);
|
||||
}
|
||||
}
|
||||
|
||||
void AddCloth(RectCloth cloth, Vrm10Instance vrm)
|
||||
void AddCloth(ClothGrid cloth, Vrm10Instance vrm)
|
||||
{
|
||||
for (int i = 1; i < cloth.Warps.Count; ++i)
|
||||
{
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "RotateParticle",
|
||||
"name": "ClothWarp",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:3e5d614bc16b50d41bd94c8d7444ca46",
|
||||
|
|
@ -4,16 +4,16 @@ using SphereTriangle;
|
|||
using UnityEngine;
|
||||
|
||||
|
||||
namespace RotateParticle
|
||||
namespace ClothWarpLib
|
||||
{
|
||||
[Serializable]
|
||||
public class RotateParticle
|
||||
public class ClothWarpNode
|
||||
{
|
||||
public ParticleInitState Init;
|
||||
public ParticleRuntimeState State;
|
||||
|
||||
public readonly List<RotateParticle> Children = new();
|
||||
public readonly RotateParticle Parent;
|
||||
public readonly List<ClothWarpNode> Children = new();
|
||||
public readonly ClothWarpNode Parent;
|
||||
|
||||
// 現フレームの力積算
|
||||
public Vector3 Force = Vector3.zero;
|
||||
|
|
@ -21,7 +21,7 @@ namespace RotateParticle
|
|||
// 直前で接触があった
|
||||
public bool HasCollide = false;
|
||||
|
||||
public RotateParticle(int index, RotateParticle parent, SimulationEnv env, Transform transform, float radius, float mass)
|
||||
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);
|
||||
|
|
@ -2,7 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using RotateParticle.Components;
|
||||
using ClothWarpLib.Components;
|
||||
using SphereTriangle;
|
||||
using UniGLTF;
|
||||
using UniGLTF.SpringBoneJobs.Blittables;
|
||||
|
|
@ -10,9 +10,9 @@ using UnityEngine;
|
|||
using UniVRM10;
|
||||
|
||||
|
||||
namespace RotateParticle
|
||||
namespace ClothWarpLib
|
||||
{
|
||||
public class RotateParticleSpringboneRuntime : IVrm10SpringBoneRuntime
|
||||
public class ClothWarpRuntime : IVrm10SpringBoneRuntime
|
||||
{
|
||||
Vrm10Instance _vrm;
|
||||
Action<Vrm10Instance> _onInit;
|
||||
|
|
@ -71,8 +71,8 @@ namespace RotateParticle
|
|||
}
|
||||
|
||||
_initialized = false;
|
||||
var strandMap = new Dictionary<WarpRoot, Strand>();
|
||||
var warps = vrm.GetComponentsInChildren<WarpRoot>();
|
||||
var strandMap = new Dictionary<Components.ClothWarp, Strand>();
|
||||
var warps = vrm.GetComponentsInChildren<Components.ClothWarp>();
|
||||
foreach (var warp in warps)
|
||||
{
|
||||
var strands = new List<Strand>();
|
||||
|
|
@ -143,7 +143,7 @@ namespace RotateParticle
|
|||
return;
|
||||
}
|
||||
|
||||
using var profile = new ProfileSample("RotateParticle");
|
||||
using var profile = new ProfileSample("ClothWarp");
|
||||
|
||||
_newPos.BoundsCache.Clear();
|
||||
|
||||
|
|
@ -404,7 +404,7 @@ namespace RotateParticle
|
|||
{
|
||||
}
|
||||
|
||||
public RotateParticleSpringboneRuntime(Action<Vrm10Instance> onInit = null)
|
||||
public ClothWarpRuntime(Action<Vrm10Instance> onInit = null)
|
||||
{
|
||||
_onInit = onInit;
|
||||
}
|
||||
|
|
@ -2,24 +2,24 @@ using System.Collections.Generic;
|
|||
using UnityEngine;
|
||||
|
||||
|
||||
namespace RotateParticle.Components
|
||||
namespace ClothWarpLib.Components
|
||||
{
|
||||
[AddComponentMenu("RotateParticle/RectCloth")]
|
||||
[AddComponentMenu("ClothWarp/ClothGrid")]
|
||||
[DisallowMultipleComponent]
|
||||
public class RectCloth : MonoBehaviour
|
||||
public class ClothGrid : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
public bool LoopIsClosed = false;
|
||||
|
||||
[SerializeField]
|
||||
public List<WarpRoot> Warps = new();
|
||||
public List<ClothWarp> Warps = new();
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
for (int i = 0; i < transform.childCount; ++i)
|
||||
{
|
||||
var child = transform.GetChild(i);
|
||||
if (child.TryGetComponent<WarpRoot>(out var warp))
|
||||
if (child.TryGetComponent<ClothWarp>(out var warp))
|
||||
{
|
||||
Warps.Add(warp);
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@ namespace RotateParticle.Components
|
|||
}
|
||||
}
|
||||
|
||||
void DrawWeft(WarpRoot w0, WarpRoot w1)
|
||||
void DrawWeft(ClothWarp w0, ClothWarp w1)
|
||||
{
|
||||
if (w0 == null || w1 == null)
|
||||
{
|
||||
|
|
@ -7,15 +7,15 @@ using UnityEngine.UIElements;
|
|||
using UniVRM10;
|
||||
|
||||
|
||||
namespace RotateParticle.Components
|
||||
namespace ClothWarpLib.Components
|
||||
{
|
||||
[AddComponentMenu("RotateParticle/WarpRoot")]
|
||||
[AddComponentMenu("ClothWarp/ClothWarp")]
|
||||
[DisallowMultipleComponent]
|
||||
/// <summary>
|
||||
/// Warp の root にアタッチする。
|
||||
/// 子孫の Transform がすべて登録される。
|
||||
/// </summary>
|
||||
public class WarpRoot : MonoBehaviour
|
||||
public class ClothWarp : MonoBehaviour
|
||||
{
|
||||
public static BlittableJointMutable DefaultSetting()
|
||||
{
|
||||
|
|
@ -4,17 +4,17 @@ using System.Linq;
|
|||
using UnityEngine;
|
||||
using UniVRM10;
|
||||
|
||||
namespace RotateParticle.Components
|
||||
namespace ClothWarpLib.Components
|
||||
{
|
||||
[AddComponentMenu("RotateParticle/RotateParticleRuntimeProvider")]
|
||||
[AddComponentMenu("ClothWarp/ClothWarpRuntimeProvider")]
|
||||
[DisallowMultipleComponent]
|
||||
public class RotateParticleRuntimeProvider : MonoBehaviour, IVrm10SpringBoneRuntimeProvider
|
||||
public class ClothWarpRuntimeProvider : MonoBehaviour, IVrm10SpringBoneRuntimeProvider
|
||||
{
|
||||
[SerializeField]
|
||||
public List<WarpRoot> Warps = new();
|
||||
public List<ClothWarp> Warps = new();
|
||||
|
||||
[SerializeField]
|
||||
public List<RectCloth> Cloths = new();
|
||||
public List<ClothGrid> Cloths = new();
|
||||
|
||||
[SerializeField]
|
||||
public bool UseJob;
|
||||
|
|
@ -23,16 +23,16 @@ namespace RotateParticle.Components
|
|||
public IVrm10SpringBoneRuntime CreateSpringBoneRuntime()
|
||||
{
|
||||
m_runtime = UseJob
|
||||
? new Jobs.RotateParticleJobRuntime()
|
||||
: new RotateParticleSpringboneRuntime()
|
||||
? new Jobs.ClothWarpJobRuntime()
|
||||
: new ClothWarpRuntime()
|
||||
;
|
||||
return m_runtime;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Warps = GetComponentsInChildren<WarpRoot>().ToList();
|
||||
Cloths = GetComponentsInChildren<RectCloth>().ToList();
|
||||
Warps = GetComponentsInChildren<ClothWarp>().ToList();
|
||||
Cloths = GetComponentsInChildren<ClothGrid>().ToList();
|
||||
}
|
||||
|
||||
void OnDrawGizmos()
|
||||
|
|
@ -45,7 +45,7 @@ namespace RotateParticle.Components
|
|||
}
|
||||
|
||||
public static void FromVrm10(Vrm10Instance instance,
|
||||
Func<GameObject, WarpRoot> addWarp,
|
||||
Func<GameObject, ClothWarp> addWarp,
|
||||
Action<UnityEngine.Object> deleteObject)
|
||||
{
|
||||
foreach (var spring in instance.SpringBone.Springs)
|
||||
|
|
@ -61,7 +61,7 @@ namespace RotateParticle.Components
|
|||
continue;
|
||||
}
|
||||
|
||||
var warp = root_joint.GetComponent<WarpRoot>();
|
||||
var warp = root_joint.GetComponent<ClothWarp>();
|
||||
if (warp == null)
|
||||
{
|
||||
// var warp = Undo.AddComponent<Warp>(root_joint);
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace RotateParticle
|
||||
namespace ClothWarpLib
|
||||
{
|
||||
public struct FrameTime
|
||||
{
|
||||
|
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RotateParticle
|
||||
namespace ClothWarpLib
|
||||
{
|
||||
public class HumanoidPose
|
||||
{
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using RotateParticle.Components;
|
||||
using UniGLTF;
|
||||
using UniGLTF.SpringBoneJobs.Blittables;
|
||||
using Unity.Collections;
|
||||
|
|
@ -12,9 +10,9 @@ using UnityEngine.Jobs;
|
|||
using UniVRM10;
|
||||
|
||||
|
||||
namespace RotateParticle.Jobs
|
||||
namespace ClothWarpLib.Jobs
|
||||
{
|
||||
public class RotateParticleJobRuntime : IVrm10SpringBoneRuntime
|
||||
public class ClothWarpJobRuntime : IVrm10SpringBoneRuntime
|
||||
{
|
||||
Vrm10Instance _vrm;
|
||||
Action<Vrm10Instance> _onInit;
|
||||
|
|
@ -137,7 +135,7 @@ namespace RotateParticle.Jobs
|
|||
List<TransformInfo> info = new();
|
||||
List<Vector3> positions = new();
|
||||
List<WarpInfo> warps = new();
|
||||
var warpSrcs = vrm.GetComponentsInChildren<WarpRoot>();
|
||||
var warpSrcs = vrm.GetComponentsInChildren<Components.ClothWarp>();
|
||||
for (int warpIndex = 0; warpIndex < warpSrcs.Length; ++warpIndex)
|
||||
{
|
||||
var warp = warpSrcs[warpIndex];
|
||||
|
|
@ -164,7 +162,7 @@ namespace RotateParticle.Jobs
|
|||
|
||||
var warpRootTransformIndex = GetTransformIndex(warp.transform, new TransformInfo
|
||||
{
|
||||
TransformType = TransformType.WarpRoot,
|
||||
TransformType = TransformType.ClothWarp,
|
||||
ParentIndex = warpRootParentTransformIndex.index,
|
||||
InitLocalPosition = vrm.DefaultTransformStates[warp.transform].LocalPosition,
|
||||
InitLocalRotation = vrm.DefaultTransformStates[warp.transform].LocalRotation,
|
||||
|
|
@ -175,14 +173,14 @@ namespace RotateParticle.Jobs
|
|||
var parentIndex = warpRootTransformIndex.index;
|
||||
foreach (var particle in warp.Particles)
|
||||
{
|
||||
if (particle.Transform != null && particle.Mode != WarpRoot.ParticleMode.Disabled)
|
||||
if (particle.Transform != null && particle.Mode != Components.ClothWarp.ParticleMode.Disabled)
|
||||
{
|
||||
var outputParticleTransformIndex = GetTransformIndex(particle.Transform, new TransformInfo
|
||||
var outputParticleTransformIndex = GetTransformIndex((Transform)particle.Transform, new TransformInfo
|
||||
{
|
||||
TransformType = TransformType.Particle,
|
||||
ParentIndex = parentIndex,
|
||||
InitLocalPosition = vrm.DefaultTransformStates[particle.Transform].LocalPosition,
|
||||
InitLocalRotation = vrm.DefaultTransformStates[particle.Transform].LocalRotation,
|
||||
InitLocalPosition = vrm.DefaultTransformStates[(Transform)particle.Transform].LocalPosition,
|
||||
InitLocalRotation = vrm.DefaultTransformStates[(Transform)particle.Transform].LocalRotation,
|
||||
Settings = particle.Settings,
|
||||
}, info, positions);
|
||||
parentIndex = outputParticleTransformIndex.index;
|
||||
|
|
@ -392,7 +390,7 @@ namespace RotateParticle.Jobs
|
|||
case TransformType.Center:
|
||||
case TransformType.WarpRootParent:
|
||||
break;
|
||||
case TransformType.WarpRoot:
|
||||
case TransformType.ClothWarp:
|
||||
Gizmos.color = Color.white;
|
||||
Gizmos.DrawSphere(v, info.Settings.radius);
|
||||
break;
|
||||
|
|
@ -415,7 +413,7 @@ namespace RotateParticle.Jobs
|
|||
}
|
||||
}
|
||||
|
||||
public RotateParticleJobRuntime(Action<Vrm10Instance> onInit = null)
|
||||
public ClothWarpJobRuntime(Action<Vrm10Instance> onInit = null)
|
||||
{
|
||||
_onInit = onInit;
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ using UnityEngine;
|
|||
using UnityEngine.Jobs;
|
||||
|
||||
|
||||
namespace RotateParticle.Jobs
|
||||
namespace ClothWarpLib.Jobs
|
||||
{
|
||||
public struct InputColliderJob : IJobParallelForTransform
|
||||
{
|
||||
|
|
@ -3,7 +3,7 @@ using Unity.Collections;
|
|||
using Unity.Jobs;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RotateParticle.Jobs
|
||||
namespace ClothWarpLib.Jobs
|
||||
{
|
||||
/// <summary>
|
||||
/// 親の位置に依存。再帰
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace RotateParticle.Jobs
|
||||
namespace ClothWarpLib.Jobs
|
||||
{
|
||||
public struct FrameInfo
|
||||
{
|
||||
|
|
@ -5,7 +5,7 @@ using UnityEngine;
|
|||
using UnityEngine.Jobs;
|
||||
|
||||
|
||||
namespace RotateParticle.Jobs
|
||||
namespace ClothWarpLib.Jobs
|
||||
{
|
||||
public struct TransformInfo
|
||||
{
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
namespace RotateParticle.Jobs
|
||||
namespace ClothWarpLib.Jobs
|
||||
{
|
||||
public enum TransformType
|
||||
{
|
||||
Center,
|
||||
WarpRootParent,
|
||||
WarpRoot,
|
||||
ClothWarp,
|
||||
Particle,
|
||||
}
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ namespace RotateParticle.Jobs
|
|||
{
|
||||
public static bool PositionInput(this TransformType t)
|
||||
{
|
||||
return t == TransformType.WarpRoot;
|
||||
return t == TransformType.ClothWarp;
|
||||
}
|
||||
|
||||
public static bool Movable(this TransformType t)
|
||||
|
|
@ -24,7 +24,7 @@ namespace RotateParticle.Jobs
|
|||
{
|
||||
switch (t)
|
||||
{
|
||||
case TransformType.WarpRoot:
|
||||
case TransformType.ClothWarp:
|
||||
case TransformType.Particle:
|
||||
return true;
|
||||
default:
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace RotateParticle.Jobs
|
||||
namespace ClothWarpLib.Jobs
|
||||
{
|
||||
public struct WarpInfo
|
||||
{
|
||||
|
|
@ -2,7 +2,7 @@ using System;
|
|||
using SphereTriangle;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RotateParticle
|
||||
namespace ClothWarpLib
|
||||
{
|
||||
[Serializable]
|
||||
public struct ParticleInitState
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RotateParticle.Components;
|
||||
using ClothWarpLib.Components;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace RotateParticle
|
||||
namespace ClothWarpLib
|
||||
{
|
||||
[Serializable]
|
||||
public class ParticleList
|
||||
{
|
||||
public List<RotateParticle> _particles = new();
|
||||
public List<ClothWarpNode> _particles = new();
|
||||
public List<Transform> _particleTransforms = new();
|
||||
|
||||
public Strand MakeParticleStrand(SimulationEnv env, WarpRoot warp)
|
||||
public Strand MakeParticleStrand(SimulationEnv env, Components.ClothWarp warp)
|
||||
{
|
||||
var strand = new Strand();
|
||||
|
||||
|
|
@ -34,11 +34,11 @@ namespace RotateParticle
|
|||
return strand;
|
||||
}
|
||||
|
||||
int _MakeAParticle(RotateParticle parent, SimulationEnv env, Transform t, float radius, float mass)
|
||||
int _MakeAParticle(ClothWarpNode parent, SimulationEnv env, Transform t, float radius, float mass)
|
||||
{
|
||||
var index = _particles.Count;
|
||||
_particleTransforms.Add(t);
|
||||
_particles.Add(new RotateParticle(index, parent, env, t, radius, mass));
|
||||
_particles.Add(new ClothWarpNode(index, parent, env, t, radius, mass));
|
||||
return index;
|
||||
}
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RotateParticle
|
||||
namespace ClothWarpLib
|
||||
{
|
||||
public class ParticleRuntimeState
|
||||
{
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace RotateParticle
|
||||
namespace ClothWarpLib
|
||||
{
|
||||
public struct RigidTransform
|
||||
{
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace RotateParticle
|
||||
namespace ClothWarpLib
|
||||
{
|
||||
[System.Serializable]
|
||||
public class SimulationEnv
|
||||
|
|
@ -2,7 +2,7 @@ using System.Collections.Generic;
|
|||
using UnityEngine;
|
||||
|
||||
|
||||
namespace RotateParticle
|
||||
namespace ClothWarpLib
|
||||
{
|
||||
public struct SpringConstraint
|
||||
{
|
||||
|
|
@ -24,7 +24,7 @@ namespace RotateParticle
|
|||
/// フックの法則
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void Resolve(FrameTime time, float hookean, List<RotateParticle> list)
|
||||
public void Resolve(FrameTime time, float hookean, List<ClothWarpNode> list)
|
||||
{
|
||||
var p0 = list[_p0];
|
||||
var p1 = list[_p1];
|
||||
|
|
@ -35,7 +35,7 @@ namespace RotateParticle
|
|||
p1.Force -= dx;
|
||||
}
|
||||
|
||||
// public void DrawGizmo(List<RotateParticle> list)
|
||||
// public void DrawGizmo(List<ClothWarp> list)
|
||||
// {
|
||||
// Gizmos.DrawLine(list[_p0].State.Current, list[_p1].State.Current);
|
||||
// }
|
||||
|
|
@ -2,7 +2,7 @@ using System.Collections.Generic;
|
|||
using SphereTriangle;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RotateParticle
|
||||
namespace ClothWarpLib
|
||||
{
|
||||
public class Strand
|
||||
{
|
||||
|
|
@ -10,7 +10,7 @@ namespace RotateParticle
|
|||
{
|
||||
}
|
||||
|
||||
public readonly List<RotateParticle> Particles = new();
|
||||
public readonly List<ClothWarpNode> Particles = new();
|
||||
|
||||
public void UpdateRoot(IReadOnlyList<Transform> transforms, SphereTriangle.PositionList positions, Vector3[] restPositions)
|
||||
{
|
||||
|
|
@ -30,7 +30,7 @@ namespace RotateParticle
|
|||
|
||||
void _CalcRest(IReadOnlyList<Transform> transforms, IReadOnlyList<Vector3> positions,
|
||||
Vector3[] restPositions,
|
||||
RotateParticle particle)
|
||||
ClothWarpNode particle)
|
||||
{
|
||||
var restRotation = particle.RestRotation(transforms);
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ namespace RotateParticle
|
|||
}
|
||||
}
|
||||
|
||||
void _ForceConstraint(IReadOnlyList<Transform> transforms, PositionList positions, int child_index, RotateParticle particle, in Vector3 parent)
|
||||
void _ForceConstraint(IReadOnlyList<Transform> transforms, PositionList positions, int child_index, ClothWarpNode particle, in Vector3 parent)
|
||||
{
|
||||
// update position
|
||||
Vector3 newPosition;
|
||||
|
|
@ -90,7 +90,7 @@ namespace RotateParticle
|
|||
}
|
||||
|
||||
void _ResetRecursive(IReadOnlyList<Transform> transforms,
|
||||
RotateParticle joint)
|
||||
ClothWarpNode joint)
|
||||
{
|
||||
var t = transforms[joint.Init.Index];
|
||||
t.localPosition = joint.Init.LocalPosition;
|
||||
|
|
@ -113,7 +113,7 @@ namespace RotateParticle
|
|||
}
|
||||
|
||||
void _ApplyRecursive(IReadOnlyList<Transform> transforms, IReadOnlyList<Vector3> positions,
|
||||
int child_index, RotateParticle joint)
|
||||
int child_index, ClothWarpNode joint)
|
||||
{
|
||||
var t = transforms[joint.Init.Index];
|
||||
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
# cloth 実装メモ
|
||||
|
||||
## component
|
||||
|
||||
設定置き場。布を構成する縦糸(WarpRoot)と、縦糸を横に連結した四角格子(RectCloth)の2段階とする
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] 衝突グループ(現状総当たり)
|
||||
- [ ] 枝分かれ
|
||||
- [ ] WarpRoot らからデフォルト以外の Warp を選び出す
|
||||
- [ ] Center
|
||||
- [ ] Scale
|
||||
- [ ] VRMC_springBone_extended_collider
|
||||
- [ ] Debug用の詳細な Gizmo
|
||||
- [ ] Cloth の片面衝突
|
||||
- [ ] 衝突時の velocity 下げ
|
||||
Loading…
Reference in New Issue
Block a user