実行時の構築

概要


MagicaCloth2は実行中の構築を完全にサポートします。
ここでは実行中にスクリプトからMagicaClothコンポーネントを構築する方法を解説します。
なお読者はUnityでのC#プログラミングを理解していることを前提としています。

サンプルシーン


実行時の構築についてはサンプルシーンが用意されています。
これらは次のフォルダにありテストする場合はお使いのレンダーパイプラインのシーンを選択してください。

このサンプルシーンにあるRuntimeBuildDemoにアタッチされているRuntimeBuildDemo.csがテストコードとなっています。
このページでもこのテストコードの内容を抜粋して説明してきます。

なお、シーンが分かれている理由は単に描画マテリアルの切り替えのためです。
内部に含まれるサンプルコードは同一です。

構築手順


構築は次の手順で行います。

  1. MagicaClothコンポーネントの生成
  2. パラメーターを設定する
  3. クロスデータの作成と実行を開始する

これらについては事例を元に解説していきます。
また、構築の際に留意すべき点が幾つかあります。

頂点ペイントの設定

エディタでの事前構築では頂点ペイント機能を使って手動で各頂点の属性を設定しました。
しかしこの方法は実行時には利用できません。
そのため実行時のMeshClothではペイントマップを別途用意して属性を付与します。
ペイントマップはMeshClothでは必須となります。
詳しくはテクスチャによる頂点ペイントのページを参照してください。

実行開始の遅延

MagicaCloth2ではクロスデータは実行時に構築されます。
この構築は別スレッドで行われるためメインスレッドにはほとんど影響はありません。
しかし、構築には数フレームの時間が必要です。

そのためコンポーネントを構築してから実際にクロスシミュレーションが開始されるまで数フレームの遅延があります。

事例


ここに幾つかの構築事例を記載します。
事例はサンプルシーンのRuntimeBuildDemo.csから抜粋していますので、デモシーンも合わせて参照してください。
使われるAPIについてはScripttingAPIのページを参照してください。

コードに書かれるcharacterはキャラクターのGameObjectです。
またgameObjectContainerはキャラクターのGameObjectを名前から取得できる簡単なクラスです。

BoneCloth構築例(1)

/// <summary>
/// BoneCloth construction example (1).
/// Set all parameters from a script.
/// </summary>
void SetupHairTail_BoneCloth()
{
  if (character == null)
    return;

  var obj = new GameObject("HairTail_BoneCloth");
  obj.transform.SetParent(character.transform, false);

  // add Magica Cloth
  var cloth = obj.AddComponent<MagicaCloth>();
  var sdata = cloth.SerializeData;

  // bone cloth
  sdata.clothType = ClothProcess.ClothType.BoneCloth;
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_L_HairTail_00_B").transform);
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_R_HairTail_00_B").transform);

  // setup parameters
  sdata.gravity = 3.0f;
  sdata.damping.SetValue(0.05f);
  sdata.angleRestorationConstraint.stiffness.SetValue(0.15f, 1.0f, 0.15f, true);
  sdata.angleRestorationConstraint.velocityAttenuation = 0.6f;
  sdata.tetherConstraint.distanceCompression = 0.5f;
  sdata.inertiaConstraint.particleSpeedLimit.SetValue(true, 3.0f);
  sdata.colliderCollisionConstraint.mode = ColliderCollisionConstraint.Mode.None;

  // start build
  cloth.BuildAndRun();
}

この例ではBoneClothを作成して、すべてのパラメータをスクリプトから設定しています。
重要なのはSerializeDataクラスです。
スクリプトから操作可能なパラメータはすべてこのSerializeDataクラスが内包しています。
そのためこのクラスを書き換えることで設定を行います。

最後にBuildAndRun()を実行します。
これによりスレッドでクロスデータの作成が開始され、完了すると自動的にシミュレーションが開始されます。

BoneCloth構築例(2)

/// <summary>
/// BoneCloth construction example (2).
/// Copy parameters from an existing component.
/// </summary>
void SetupFrontHair_BoneCloth()
{
  if (character == null || frontHairSource == null)
    return;

  var obj = new GameObject("HairFront_BoneCloth");
  obj.transform.SetParent(character.transform, false);

  // add Magica Cloth
  var cloth = obj.AddComponent<MagicaCloth>();
  var sdata = cloth.SerializeData;

  // bone cloth
  sdata.clothType = ClothProcess.ClothType.BoneCloth;
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_L_HairFront_00_B").transform);
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_L_HairSide2_00_B").transform);
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_L_HairSide_00_B").transform);
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_R_HairFront_00_B").transform);
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_R_HairSide2_00_B").transform);
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_R_HairSide_00_B").transform);

  // Normal direction setting for backstop
  sdata.normalAlignmentSetting.alignmentMode = NormalAlignmentSettings.AlignmentMode.Transform;
  sdata.normalAlignmentSetting.adjustmentTransform = gameObjectContainer.GetGameObject("HeadCenter").transform;

  // setup parameters
  // Copy from source settings
  sdata.Import(frontHairSource, false);

  // start build
  cloth.BuildAndRun();
}

この例ではパラメータを外部の別のクロスコンポーネントであるfrontHairSourceからインポートしています。
これによりパラメータの設定が簡略化できます。

ただしパラメータにはインポートできるものとできないものがあります。
これはソースコードにコメントとして記載されています。

/// [OK] Runtime changes.
/// [NG] Export/Import with Presets

この例ではnormalAlignmentSettingはインポートすることができないため手動で設定しています。

BoneCloth構築例(3)

/// <summary>
/// BoneCloth construction example (3).
/// Load parameters from saved presets.
/// </summary>
void SetupRibbon_BoneCloth()
{
  if (character == null || string.IsNullOrEmpty(ribbonPresetName))
    return;

  var obj = new GameObject("Ribbon_BoneCloth");
  obj.transform.SetParent(character.transform, false);

  // add Magica Cloth
  var cloth = obj.AddComponent<MagicaCloth>();
  var sdata = cloth.SerializeData;

  // bone cloth
  sdata.clothType = ClothProcess.ClothType.BoneCloth;
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_L_HeadRibbon_00_B").transform);
  sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_R_HeadRibbon_00_B").transform);

  // setup parameters
  // Load presets from the Resource folder.
  // Since presets are in TextAssets format, they can also be used as asset bundles.
  var presetText = Resources.Load<TextAsset>(ribbonPresetName);
  sdata.ImportJson(presetText.text);

  // start build
  cloth.BuildAndRun();
}

この例ではパラメータをプリセットファイル(Json)としてインポートしています。
パラメータはJson形式で外部にエクスポートできます。
これは単純なテキストファイルなのでResourcesフォルダやアセットバンドルとして配置することで実行時に読み込めます。

BoneClothでの属性設定例

// Transform Attribute
var sdata2 = cloth.GetSerializeData2();
sdata2.boneAttributeDict.Add(bone1, VertexAttribute.Fixed);
sdata2.boneAttributeDict.Add(bone2, VertexAttribute.Invalid);
sdata2.boneAttributeDict.Add(bone3, VertexAttribute.Move);

BoneClothの構築時にはルートボーンを固定属性とし、それ以外は移動属性として自動設定されます。
しかし、ClothSerializeData2のboneAttributeDictを利用することでスクリプトから手動で変更が可能です。
これにより頂点ペイントと同じ効果が得られます。

MeshCloth構築例(1)

/// <summary>
/// MeshCloth construction example (1).
/// Reads vertex attributes from a paintmap.
/// </summary>
void SetupSkirt_MeshCloth()
{
  if (character == null || skirtPaintMap == null)
    return;

  // skirt renderer
  var sobj = gameObjectContainer.GetGameObject(skirtName);
  if (sobj == null)
    return;
  Renderer skirtRenderer = sobj.GetComponent<Renderer>();
  if (skirtRenderer == null)
    return;

  // add Magica Cloth
  var obj = new GameObject("Skirt_MeshCloth");
  obj.transform.SetParent(character.transform, false);
  var cloth = obj.AddComponent<MagicaCloth>();
  var sdata = cloth.SerializeData;

  // mesh cloth
  sdata.clothType = ClothProcess.ClothType.MeshCloth;
  sdata.sourceRenderers.Add(skirtRenderer);

  // reduction settings
  sdata.reductionSetting.simpleDistance = 0.0212f;
  sdata.reductionSetting.shapeDistance = 0.0244f;

  // paint map settings
  // *** Paintmaps must have Read/Write attributes enabled! ***
  sdata.paintMode = ClothSerializeData.PaintMode.Texture_Fixed_Move;
  sdata.paintMaps.Add(skirtPaintMap);

  // setup parameters
  sdata.gravity = 1.0f;
  sdata.damping.SetValue(0.03f);
  sdata.angleRestorationConstraint.stiffness.SetValue(0.05f, 1.0f, 0.5f, true);
  sdata.angleRestorationConstraint.velocityAttenuation = 0.5f;
  sdata.angleLimitConstraint.useAngleLimit = true;
  sdata.angleLimitConstraint.limitAngle.SetValue(45.0f, 0.0f, 1.0f, true);
  sdata.distanceConstraint.stiffness.SetValue(0.5f, 1.0f, 0.5f, true);
  sdata.tetherConstraint.distanceCompression = 0.9f;
  sdata.inertiaConstraint.depthInertia = 0.7f;
  sdata.inertiaConstraint.movementSpeedLimit.SetValue(true, 3.0f);
  sdata.inertiaConstraint.particleSpeedLimit.SetValue(true, 3.0f);
  sdata.colliderCollisionConstraint.mode = ColliderCollisionConstraint.Mode.Point;

  // setup collider
  var lobj = new GameObject("CapsuleCollider_L");
  lobj.transform.SetParent(gameObjectContainer.GetGameObject("Character1_LeftUpLeg").transform);
  lobj.transform.localPosition = new Vector3(0.0049f, 0.0f, -0.0832f);
  lobj.transform.localEulerAngles = new Vector3(0.23f, 16.376f, -0.028f);
  var colliderL = lobj.AddComponent<MagicaCapsuleCollider>();
  colliderL.direction = MagicaCapsuleCollider.Direction.Z;
  colliderL.SetSize(0.082f, 0.094f, 0.3f);

  var robj = new GameObject("CapsuleCollider_R");
  robj.transform.SetParent(gameObjectContainer.GetGameObject("Character1_RightUpLeg").transform);
  robj.transform.localPosition = new Vector3(-0.0049f, 0.0f, -0.0832f);
  robj.transform.localEulerAngles = new Vector3(0.23f, -16.376f, -0.028f);
  var colliderR = robj.AddComponent<MagicaCapsuleCollider>();
  colliderR.direction = MagicaCapsuleCollider.Direction.Z;
  colliderR.SetSize(0.082f, 0.094f, 0.3f);

  sdata.colliderCollisionConstraint.colliderList.Add(colliderL);
  sdata.colliderCollisionConstraint.colliderList.Add(colliderR);

  // start build
  cloth.BuildAndRun();
}

この例ではMeshClothを構築しペイントマップにより頂点属性を付与しています。
ペイントマップは設定したレンダラーと同期していることに注意してください。
つまりレンダラーの数とペイントマップの数は同じである必要があります。

またカプセルコライダーを2つ作成してコライダーリストに追加しています。
コライダーの作成もUnityの一般的なコンポーネントを作成する手法と同じです。

それ以外はBoneClothの例と同じです。

end.