Swizzle演算子をT4Templateで作る

#さてみなさま
Shader Forgeの無料化や、Shader Graphの標準Component入りなど、UnityもShader周りが手厚くなってきましたね。
どうですか？今まで以上にUnityでShaderを触る事が多くなってきてるんじゃないですか？

#ところで
shaderで使われるcg言語では `float3` とか `float4` てのが使われますよね。
これらって面白くて、

```glsl
float4 a = float4(1,2,3,4);

float2 b = a.xy; //b = (1,2)
float2 c = a.ww; //c = (4,4)

float4 z = b.xxyy; //z = (1,1,2,2)
```

このように、各要素を `.****` で入れ替えたり、増やして変換しつつ参照したりできるんですね。

これを**swizzle(スウィズル）演算子**って言うそうです。

#Unityでは
Vector2からVector4まで用途としては同じようなのものがありますが、残念ながらswizzle演算子は使えません。　使えたら良いですよね。　とりあえず使い道が思いつかなくてもなんか良さそうですよね。

**では、作りましょう**
まず、`Vector2` から、`Vector2` へのswizzle演算子っぽいものを`拡張メソッド`で用意してみます。

```csharp
using UnityEngine;
namespace VectorExtensions
{
	public static class Vector2SwizzleExtension
	{
		public static Vector2 xx(this Vector2 v)
		{
			return new Vector2(v.x,v.x);
		}
		public static Vector2 xy(this Vector2 v)
		{
			return new Vector2(v.x,v.y);
		}
		public static Vector2 yx(this Vector2 v)
		{
			return new Vector2(v.y,v.x);
		}
		public static Vector2 yy(this Vector2 v)
		{
			return new Vector2(v.y,v.y);
		}
	}
}
```

これで、
`using VectorExtendions`
の一文を入れてあげれば、**swizzle演算子**(っぽいもの)が使えるって寸法ですよ!

以下、動作確認

```csharp:SwizzleTest.cs
using UnityEngine;
using VectorExtensions;

public class SwizzleTest : MonoBehaviour
{
	void Start ()
	{
		var v = new Vector2(1,2);
	    var v2 = v.yx();//拡張メソッドが呼ばれる

        Debug.Log("v2="+v2);
	}
}
```

```text:Console出力
v2=(2.0, 1.0)
UnityEngine.Debug:Log(Object)
SwizzleTest:Start() (at Assets/SwizzleTest.cs:11)
```

おっちゃんとxとyの１と２が入れ替わってます。良い感じですね。

じゃぁ、次に`Vector2`から`Vector3`！　レッツゴー！

```cs
		public static Vector3 xxx(this Vector2 v)
		{
			return new Vector3(v.x,v.x,v.x);
		}
		public static Vector3 xxy(this Vector2 v)
		{
			return new Vector3(v.x,v.x,v.y);
		}
		public static Vector3 xyx(this Vector2 v)
		{
			return new Vector3(v.x,v.y,v.x);
		}
		public static Vector3 xyy(this Vector2 v)
		{
			return new Vector3(v.x,v.y,v.y);
		}
		public static Vector3 yxx(this Vector2 v)
		{
			return new Vector3(v.y,v.x,v.x);
		}
		public static Vector3 yxy(this Vector2 v)
		{
			return new Vector3(v.y,v.x,v.y);
		}
		public static Vector3 yyx(this Vector2 v)
		{
			return new Vector3(v.y,v.y,v.x);
		}
		public static Vector3 yyy(this Vector2 v)
		{
			return new Vector3(v.y,v.y,v.y);
		}
```

で…できた。　うん。動いてる。

```csharp
using UnityEngine;
using VectorExtensions;

public class SwizzleTest : MonoBehaviour
{
	void Start ()
	{
		var v = new Vector2(1,2);
	    var v2 = v.yx();
	    var v3 = v.yxy();
	    Debug.Log("v2=" + v2);
	    Debug.Log("v3=" + v3);
	}
}
```

```text:出力
v2=(2.0, 1.0)
UnityEngine.Debug:Log(Object)
SwizzleTest:Start() (at Assets/SwizzleTest.cs:11)
v3=(2.0, 1.0, 2.0)
UnityEngine.Debug:Log(Object)
SwizzleTest:Start() (at Assets/SwizzleTest.cs:12)
```

つ、つぎ・・・。 `Vector2`から`Vector4`…　う・・・うわぁぁぁああああーー！

**はい。地獄です。**
この調子で作っていくと`Vector4`から`Vector4`のswizzleメソッドを作ると**256メソッド**。
最終的には全部で**484の拡張メソッド**作ることになります。
仮に手作業で全部作ったとして、言うても人力。抜けが無いか心配ですよね。

#そこでT4Template
T4Templateというのは、ちょっと特殊な記法で書かれたソースコードによって**テキストやソースコードを生成**する仕組みの事です。

VisualStudioならソリューションエクスプローラーで、Assembly-CSharpを右クリックして「追加」→「新しい項目」→「全般」→「テキストテンプレート」で追加できます（拡張子は*.tt)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/37184/d5a2b82a-bf2a-8e7c-0d27-dd734ebf365d.png)

そして、以下のように、t4templateの書式に従って、**ソースコードを生成するコード**を書きます。
（T4Templateについて詳しく知りたい方は
https://msdn.microsoft.com/ja-jp/library/bb126445.aspx
などを参照してください。）

```text
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>

using UnityEngine;
namespace VectorExtensions
{
<#
	var target = new[]{"","x","y","z","w"};
	var min = 2;
	var max = 4;
	var classExtension = "Vector";

	for(var baseNum = min;baseNum <= max;baseNum++)
	{
		var alreadyUsedMethod = new List<string>();

#>
	public static class Vector<#=baseNum#>SwizzleExtension
	{
<#
		for(var targetNum = min;targetNum <= max;targetNum++)
		{
			for(var a = 0;a < 5;++a)
			{
				for(var b = 0;b < 5;++b)
				{
					for(var c = 0;c < 5;++c)
					{
						for(var d = 0;d < 5;++d)
						{
							var methodName = target[a]+target[b]+target[c]+target[d];
							if(methodName.Length < 2)continue;
							if(methodName.Length > targetNum)continue;
							if(a > baseNum || b > baseNum || c > baseNum || d > baseNum)continue;
							if(alreadyUsedMethod.Contains(methodName))continue;
							alreadyUsedMethod.Add(methodName);
#>
		public static <#=classExtension+targetNum#> <#=methodName#>(this <#=classExtension+baseNum#> v)
		{
			return new <#=classExtension+targetNum#>(<#=string.Join(",",methodName.Select(field => "v." + field))#>);
		}
<#
							}
						}
					}
				}
			}
#>
	}
<#
		}
#>
}
```

この`<#` から `#>` に挟まれている所にはC#などでScriptを書くことが出来ます。（逆にそれ以外の箇所は、テキストとして生成されます）
この`<#～#>`に着目すると、T4Templateを知らない人でもわかる、恐怖の6重forループが確認できると思います。これだけ見ても人の手には余る事がわかりますね。
（組み合わせ作成にあたっては、本当は再起とかを使えば良いんですが、今回は最大でも4要素(xyzw)と分かっているので、多重for文になってます）

#T4Templateのビルド
T4Tempolateファイルは保存をした瞬間に変換・生成処理が走りるようになっているので、
保存すると、以下のように作ったttファイルの子要素的にcsファイルが生成されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/37184/8ba5c7f2-a125-3963-3b5c-c0e52667b24d.png)

（または、ツールバーの「ビルド」→「すべてのT4テンプレートの変換」でも明示的に変換・生成処理が走ります）
![image.png](https://qiita-image-store.s3.amazonaws.com/0/37184/522249b4-2d21-bf76-7346-2685c5d7db52.png)
↓
![image.png](https://qiita-image-store.s3.amazonaws.com/0/37184/d2a05c94-5428-66c1-7ff3-f12ecc50e289.png)

#完成品(VectorNSwizzleExtension)

<script src="https://gist.github.com/divide-by-zero/7655d05b2b71c58fe1c385cd283ae633.js"></script>

https://gist.github.com/divide-by-zero/7655d05b2b71c58fe1c385cd283ae633


全部で1939行、484メソッドが一瞬にして生成されました。やったね！

#大事なことを言い忘れてました
このT4Templateの仕組みはうまく使うと色んな作業が自動化出来て素晴らしいんですが、**Unityでサポートされてません**
なので、一度Unity側のウインドウを開いて再コンパイルなどが走ると、**Unityがcsprojを把握しているcsファイルなどで再構築するため、ttファイルがプロジェクトから除外されてしまいます（ファイルとしては残る）**
なので、もう一度変換したい場合はVisualStudioなどのIDE上でまたttファイルをプロジェクトに追加してから変換実行する必要があったりします（今回のは1回作れば要件を満たすので良いと言えば良いんですが）

これ、どうにかならないんですかね。
https://issuetracker.unity3d.com/issues/t4-templates-disappear-from-monodevelop-project-after-refreshing-solution
みたらもう2013年にはまったく同じような事がissueに上がってるので、まぁ、望み薄ですね・・・。