ハニーセレクト2・コイカツでフルトラッキング

未分類

はじめに

内容自体はC#とGOLマクロの話ですが、適用対象がアダルトゲームなので18歳未満はご遠慮願います。

1. 動作の様子

ハニーセレクト2・コイカツでフルトラッキング

2. できること

タイトルの通りですが、ハニーセレクト2とコイカツサンシャインでフルトラッキングが可能になります。

3. 前提

(1) 頭・両肩・両肘・両手・腰・両股関節・両ひざ・両足 (計14点) をトラッキングできること

MocapForAllを使っているのでトラッキング点数を増やすのは簡単でしたが、VIVEトラッカーでフルトラを実現している場合はここが一番の鬼門かもです。
MocapForAllはカメラでのトラッキングなのでトラック点がプルプルしてますが、VIVEトラッカー・HaritoraX等でトラッキングできればきれいにトラッキングできるはずです。

(2) GOL(GameObjectList)の、バージョン20180504aを使える状態であること

導入方法は他サイト様を参考に導入してください。
バージョン20180504aはこちらからダウンロードできるようです。

フォルダ構成は、ハニーセレクト2・コイカツサンシャインインストールフォルダの直下にPluginsフォルダを配置してます。
もし別の配置とされている場合は適宜読み替えが必要です。

(3) (コイカツサンシャインのみ) コンパイラを指定すること

ハニーセレクト2なら特に指定せずともうまいこと動いてくれましたが、コイカツサンシャインの場合は.NET Frameworkのバージョンが合わないのか外部コンパイラの指定が必要でした。

golconfig.ini内の設定を以下のように変更が必要です。
McsExePath=C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe

細かいところはこちらのサイト様にまとまっております。

3. GOLマクロ・rspファイル・サンプルシーン

ハニーセレクト2とコイカツサンシャインでオブジェクト名が異なるため別のマクロが必要になります。オブジェクト名が異なるのみで基本的な動作は同じです。

また、SteamVRで定義されているコンポーネントを使用する必要があるため、rspファイルを作成して外部DLLを読み込む必要があります。

各種ファイルは、Plugins / GameObjectList / Macro 配下に配置してください。

マクロ作成にあたって、GameObjectの再帰的な検索方法はこちらを参考にさせていただきました。

(1) ハニーセレクト2用ファイル

以下ファイルの配置のほか、VRGIN.dllをPluginsフォルダにコピーする必要があります。
VRGIN.dllは、BepInEx / plugins / HS2VR にあるはずです。

ハニーセレクト2用GOLマクロ
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using GameObjectListPlugin;
using System.Reflection;
using System.Collections;

public class GameObjectListMacro
{
	public static Transform FindAllChildren(GameObject obj, String name){
		Transform find_obj= obj.transform.Find(name);
		
		//見つかれば終了
		if (find_obj != null){
			return find_obj;
		}
		
		Transform children = obj.GetComponentInChildren<Transform> ();
		//子要素がいなければ終了
		if (children.childCount == 0) {
			return null;
		}
		
		//子要素がいれば再帰的に検索
		foreach (Transform child in children) {
			//対象が自分自身だったらスキップ
			if (child.gameObject == obj){
				continue;
			}
			
			//子要素を再帰的に検索
			find_obj = FindAllChildren(child.gameObject, name);
			if (find_obj != null){
				return find_obj;
			}
		}
		
		//配下を全検索して見つからなければnull
		return null;
	}

	public static void FindChildrenName(GameObject obj, ref List<Transform> findList, String part_of_name){
		//与えられたobjの名前をチェック
		if(obj.name.Contains(part_of_name)){
			findList.Add(obj.transform);
		}
		
		Transform children = obj.GetComponentInChildren<Transform> ();
		
		//子要素がいなければ終了
		if (children.childCount == 0) {
			return;
		}
		
		//子要素がいれば再帰的に検索
		foreach (Transform child in children) {
			//対象が自分自身だったらスキップ
			if (child.gameObject == obj){
				continue;
			}
			
			//子要素を再帰的に検索
			FindChildrenName(child.gameObject, ref findList, part_of_name);
		}
	}

	public static List<Transform> FindList_PartOfName(GameObject obj, String part_of_name){
		List<Transform> findList = new List<Transform> ();
		FindChildrenName(obj, ref findList, part_of_name);
		return findList;
	}

	public static void MacroMain()
	{
		var obj=GameObject.Find("TrackExecuting");
		if (obj)
		{
			//関連オブジェクト破壊
			GameObject.DestroyImmediate(obj);
			
			// Debugファイルを削除
			var go=GameObject.Find("Debug");
			for(int i=0; i<20; i++){
				String debugName = "Debug" + i.ToString();
				go=GameObject.Find(debugName);
				while (go){
					GameObject.DestroyImmediate(go);
					go = GameObject.Find(debugName);
				}
				for(int j=0; j<20; j++){
					debugName = "Debug" + i.ToString() + "-" + j.ToString();
					go=GameObject.Find(debugName);
					while (go){
						GameObject.DestroyImmediate(go);
						go = GameObject.Find(debugName);
					}
				}
			}

			var vrgin = GameObject.Find("VRGIN_Camera (origin)").gameObject;
			var maleObjectListName = new string[]{
				"cf_J_NoseBridge_t",	//maleHead
				"f_t_hips(work)",		//maleHip
				"f_t_arm_L(work)",		//maleLeftHand
				"f_t_elbo_L(work)",		//maleLeftElbowr
				"f_t_shoulder_L(work)",	//maleLeftShoulder
				"f_t_arm_R(work)", 		//maleLRightHand
				"f_t_elbo_R(work)",		//maleLRightElbowr
				"f_t_shoulder_R(work)",	//maleLRightShoulder
				"f_t_leg_L(work)",		//maleLeftFoot
				"f_t_knee_L(work)",		//maleLeftKnee
				"f_t_thigh_L(work)",	//maleLeftThigh
				"f_t_leg_R(work)",		//maleLRightFoot
				"f_t_knee_R(work)",		//maleLRightKnee
				"f_t_thigh_R(work)"		//maleRightThigh
			};
			var findList = FindList_PartOfName(vrgin, "chaM_");
			findList[0].parent = vrgin.transform;
			for(int index=1; index<maleObjectListName.Length; index++){
				go = FindAllChildren(vrgin, maleObjectListName[index]).gameObject;
				go.transform.parent = vrgin.transform;
			}

			var findResult = FindList_PartOfName(vrgin, "tra_arw(");
			foreach (Transform arrow02 in findResult){
				var comp = arrow02.gameObject.GetComponent<SteamVR_TrackedObject>();
		   		GameObject.DestroyImmediate(comp);
				GameObject.DestroyImmediate(arrow02.gameObject);
			}

			go=GameObject.Find("acs_O_arrow02");
			while (go){
				go.SetActive(true);
				go = GameObject.Find("acs_O_arrow02");
			}
			
			go=GameObject.Find("isCalibrating");
			while (go){
				GameObject.DestroyImmediate(go);
				go = GameObject.Find("isCalibrating");
			}
			go=GameObject.Find("isCalibrated");
			while (go){
				GameObject.DestroyImmediate(go);
				go = GameObject.Find("isCalibrated");
			}

			return;
		}
		new GameObject("TrackExecuting").AddComponent<MyComponent>();
	}
}

public class MyComponent : MonoBehaviour
{
	private GameObject trackerTemplate;
	private GameObject leftController;
	private GameObject rightController;

	private GameObject male;
	private int[] trackerIndexList;
	private Transform[] sortedTrackedList;
	private int trackerNumber_wo_controller;

	private string[] maleObjectListName;
	private Transform[] maleObjectList;
	private Transform[] trackedTransformList;

	private DateTime startTime;
	private bool isCalibrated = false;


	//基準となる中心がcenter,leftVecが左を示すベクトルで,trackerの位置座標から右左の判定をして返す
	Transform[] detectTransformSetting(Transform[] trackers, Vector3 center, Vector3 leftVec) {
		var sortedTrackers = trackers.OrderBy(tracker => {
			var vec = tracker.position - center;
			return Vector2.Dot(leftVec, vec);
		}).ToArray();

		Transform right = sortedTrackers[0];
		Transform left = sortedTrackers[1];

		return new Transform[]{right, left};
	}


	Transform[] Detect(Transform[] trackers){
		//y座標で降順にソートする
		var sortedTrackers = trackers.OrderBy(a => -a.position.y).ToArray();
		var head = sortedTrackers[0]; //一番最初の要素は頭のトラッカー
		var body = sortedTrackers[7]; //頭+右肩+右ひじ+右手+左肩+左ひじ+左手の後,体(pelvis)のトラッカー
		var vecTop = (head.position - body.position).normalized; //体から頭方向へのベクトル

		//腕(eblowとhand)と肩のグループ
		var armGroup = new Transform[]{
			sortedTrackers[1],
			sortedTrackers[2],
			sortedTrackers[3],
			sortedTrackers[4],
			sortedTrackers[5],
			sortedTrackers[6]
		};

		//体の中心に近いもの順にソート
		var averageSortedArmGroup = armGroup.OrderBy(tracker => {
			var vec = tracker.position - body.position;
			var magnitude = Vector3.Dot(vec, vecTop);
			return (vec - magnitude * vecTop).magnitude;
		}).ToArray();

		//体の中心から遠いものは手(hand)
		var handGroup = new Transform[] {
			averageSortedArmGroup[4],
			averageSortedArmGroup[5]
		};

		//体の中心から手へのベクトルを定義
		var vec1 = handGroup[0].position - body.position;
		var vec2 = handGroup[1].position - body.position;

		//手の高さから手の方向へのベクトル
		var modVec1 = vec1 - Vector3.Dot(vecTop, vec1) * vecTop;
		var modVec2 = vec2 - Vector3.Dot(vecTop, vec2) * vecTop;

		//外積ベクトル
		var crossVec = Vector3.Cross(modVec1, modVec2);

		Transform rightHand;
		Transform leftHand;

		//外積ベクトルと体のベクトルの方向から右手か左手を判定する
		//vec1が右手の時,外積ベクトルは下を向く(左手系だから)
		if (Vector3.Dot(vecTop, crossVec) < 0)
		{
			rightHand = handGroup[0];
			leftHand = handGroup[1];
		}
		else
		{
			rightHand = handGroup[1];
			leftHand = handGroup[0];
		}

		//左手方向へのベクトル
		var leftVec = (leftHand.position - body.position).normalized;

		//ArmGroupの中心に近いものは肩
		var shoulderGroup = new Transform[] {
			averageSortedArmGroup[0],
			averageSortedArmGroup[1]
		};

		//次にArmGroupの中心に近いものは肘
		var elbowGroup = new Transform[] {
			averageSortedArmGroup[2],
			averageSortedArmGroup[3]
		};
		
		//頭,肩*2,肘*2,手*2,体,"右股関節","左股関節"
		var thighGroup = new Transform[] {
			sortedTrackers[8],
			sortedTrackers[9]
		};

		//頭,肩*2,肘*2,手*2,体,"右ひざ","左ひざ"
		var kneeGroup = new Transform[] {
			sortedTrackers[10],
			sortedTrackers[11]
		};

		//頭,肘*2,手*2,体,ひざ*2,"右足","左足"
		var footGroup = new Transform[] {
			sortedTrackers[12],
			sortedTrackers[13]
		};

		var shoulderSetting = detectTransformSetting(shoulderGroup, body.position, leftVec);
		var elbowSetting = detectTransformSetting(elbowGroup, body.position, leftVec);
		var thighSetting = detectTransformSetting(thighGroup, body.position, leftVec);
		var kneeSetting = detectTransformSetting(kneeGroup, body.position, leftVec);
		var footSetting = detectTransformSetting(footGroup, body.position, leftVec);
		
		return new Transform[]{
			head,
			body,
			leftHand,
			elbowSetting[1],
			shoulderSetting[1],
			rightHand,
			elbowSetting[0],
			shoulderSetting[0],
			footSetting[1],
			kneeSetting[1],
			thighSetting[1],
			footSetting[0],
			kneeSetting[0],
			thighSetting[0]
		};
	}


	Transform FindAllChildren(GameObject obj, String name){
		Transform find_obj= obj.transform.Find(name);
		
		//見つかれば終了
		if (find_obj != null){
			return find_obj;
		}
		
		Transform children = obj.GetComponentInChildren<Transform> ();
		//子要素がいなければ終了
		if (children.childCount == 0) {
			return null;
		}
		
		//子要素がいれば再帰的に検索
		foreach (Transform child in children) {
			//対象が自分自身だったらスキップ
			if (child.gameObject == obj){
				continue;
			}
			
			//子要素を再帰的に検索
			find_obj = FindAllChildren(child.gameObject, name);
			if (find_obj != null){
				return find_obj;
			}
		}
		
		//配下を全検索して見つからなければnull
		return null;
	}


	void FindChildrenName(GameObject obj, ref List<Transform> findList, String part_of_name){
		//与えられたobjの名前をチェック
		if(obj.name.Contains(part_of_name)){
			findList.Add(obj.transform);
		}
		
		Transform children = obj.GetComponentInChildren<Transform> ();
		
		//子要素がいなければ終了
		if (children.childCount == 0) {
			return;
		}
		
		//子要素がいれば再帰的に検索
		foreach (Transform child in children) {
			//対象が自分自身だったらスキップ
			if (child.gameObject == obj){
				continue;
			}
			
			//子要素を再帰的に検索
			FindChildrenName(child.gameObject, ref findList, part_of_name);
		}
	}


	List<Transform> FindList_PartOfName(GameObject obj, String part_of_name){
		List<Transform> findList = new List<Transform> ();
		FindChildrenName(obj, ref findList, part_of_name);
		return findList;
	}


	void Start(){
		// Debugファイルを削除
		var go=GameObject.Find("Debug");
		for(int i=0; i<20; i++){
			String debugName = "Debug" + i.ToString();
			go=GameObject.Find(debugName);
			while (go){
				GameObject.DestroyImmediate(go);
				go = GameObject.Find(debugName);
			}
			for(int j=0; j<20; j++){
				debugName = "Debug" + i.ToString() + "-" + j.ToString();
				go=GameObject.Find(debugName);
				while (go){
					GameObject.DestroyImmediate(go);
					go = GameObject.Find(debugName);
				}
			}
		}

		// スタジオシーンのトップフォルダを定義
		GameObject topFolder = GameObject.Find("CommonSpace").gameObject;

		// トラッキング表示用に作成した矢印オブジェクトが残っていたら削除
		var findResult = FindList_PartOfName(topFolder, "tra_arw(");
		foreach (Transform arrow02 in findResult){
			GameObject.DestroyImmediate(arrow02.gameObject);
		}

		new GameObject("Debug0");

		// トラッキング表示用のテンプレートオブジェクトを指定
		trackerTemplate = GameObject.Find("p_ai_stu_arrow01_01_02");
		go=FindAllChildren(trackerTemplate, "acs_O_arrow02").gameObject;
		go.SetActive(true);

		new GameObject("Debug1");

		var vrgin = GameObject.Find("VRGIN_Camera (origin)"); //HMD root
		List<Transform> findList = FindList_PartOfName(topFolder, "chaM_");
		var chaFolder = topFolder;
		if(findList.Count != 0){
			male = findList[0].gameObject;
			chaFolder = male;
			new GameObject("Debug2-1");
		}else{
			findList = FindList_PartOfName(vrgin, "chaM_");
			if(findList.Count != 0){
				male = findList[0].gameObject;
				chaFolder = vrgin.gameObject;
				new GameObject("Debug2-2");
			}else{
				new GameObject("Debug2-3");
			}
		}

		// maleのheadとを非表示に変更
		var head = FindAllChildren(male, "cf_J_Head");
		head.gameObject.SetActive(false);
		new GameObject("Debug3-1");

		maleObjectListName = new string[]{
			"cf_J_NoseBridge_t",	//maleHead
			"f_t_hips(work)",		//maleHip
			"f_t_arm_L(work)",		//maleLeftHand
			"f_t_elbo_L(work)",		//maleLeftElbowr
			"f_t_shoulder_L(work)",	//maleLeftShoulder
			"f_t_arm_R(work)", 		//maleLRightHand
			"f_t_elbo_R(work)",		//maleLRightElbowr
			"f_t_shoulder_R(work)",	//maleLRightShoulder
			"f_t_leg_L(work)",		//maleLeftFoot
			"f_t_knee_L(work)",		//maleLeftKnee
			"f_t_thigh_L(work)",	//maleLeftThigh
			"f_t_leg_R(work)",		//maleLRightFoot
			"f_t_knee_R(work)",		//maleLRightKnee
			"f_t_thigh_R(work)"		//maleRightThigh
		};
		trackerNumber_wo_controller = maleObjectListName.Length;

		// maleObjectListNameからオブジェクトを検索
		maleObjectList = new Transform[trackerNumber_wo_controller];
		for(int index=0; index<trackerNumber_wo_controller; index++){
			maleObjectList[index] = FindAllChildren(chaFolder, maleObjectListName[index]);
		}
		new GameObject("Debug3-3");


		// VRモードか判断
		if(vrgin){
			// トラッカーのキャリブレーション用タイマーの開始時間を取得
			startTime = DateTime.Now;

			// 左右のコントローラを取得
			leftController = GameObject.Find("VRGIN_Camera (origin)/Left Controller");//左コントローラ
			rightController = GameObject.Find("VRGIN_Camera (origin)/Right Controller");//右コントローラ

			var leftController_index = leftController.GetComponent<SteamVR_TrackedObject>().index;
			var rightController_index = rightController.GetComponent<SteamVR_TrackedObject>().index;

			// コントローラ以外のトラッカーのインデックスを調べる
			trackerIndexList = new int[trackerNumber_wo_controller];
			var listIndex = 0;
			// コントローラも含めて trackerNumber_wo_controller+2 個のトラッカーがあるので全数検索
			for (int tmpIndex = 0; tmpIndex < trackerNumber_wo_controller+2; tmpIndex++){
				// 左右のコントローラのインデックスとは異なるようなら使えるインデックスとしてリストに格納
				if(leftController_index != (SteamVR_TrackedObject.EIndex)(tmpIndex)
				&& rightController_index != (SteamVR_TrackedObject.EIndex)(tmpIndex)){
					trackerIndexList[listIndex] = tmpIndex;
					listIndex++;
				}
			}
			new GameObject("Debug4-1");
			
			
			// トラッカーの位置を反映するオブジェクトを作成
			trackedTransformList = new Transform[trackerNumber_wo_controller];
			for (int index = 0; index < trackerNumber_wo_controller; index++){
				// テンプレートをクローン
				var tracker = GameObject.Instantiate(trackerTemplate) as GameObject;
				// 親をvrginに指定
				tracker.transform.parent = vrgin.transform;
				// SteamVR_TrackedObject を追加
				tracker.AddComponent<SteamVR_TrackedObject>().SetDeviceIndex(trackerIndexList[index]);
				// リストに格納
				trackedTransformList[index] = tracker.transform;
			}
			new GameObject("Debug4-2");
		}
	}

	void Update(){
		var obj=GameObject.Find("TrackExecuting");
		var vrgin = GameObject.Find("VRGIN_Camera (origin)"); //HMD root

		// TrackExecuting と vrgin があるか判断
		if(obj!=null && vrgin!=null){
			if(isCalibrated){
				// キャリブレーション済みなら、腰のトラッカーとmaleの腰の位置をそろえるため、
				// maleの位置をずらす
				male.transform.position += sortedTrackedList[1].position - maleObjectList[1].position;
				male.transform.localRotation = Quaternion.identity;

				for (int index = 2; index < trackerNumber_wo_controller; index++){
					maleObjectList[index].transform.localPosition = Vector3.zero;
					maleObjectList[index].transform.localRotation = Quaternion.identity;
				}

			}else{
				var timeDelta = DateTime.Now - startTime;
				var calibratingObj=GameObject.Find("isCalibrating");
				if(10 < timeDelta.TotalSeconds && calibratingObj==null){
					// 開始から10秒経っている かつ キャリブレーションに失敗していないならキャリブレーションを開始
					calibratingObj = new GameObject("isCalibrating");
					sortedTrackedList = Detect(trackedTransformList);
					new GameObject("Debug10");

					// トラッカーの名前を変更
					for (int index = 0; index < trackerNumber_wo_controller; index++){
						int trackerIndex = (int)(sortedTrackedList[index].gameObject.GetComponent<SteamVR_TrackedObject>().index);
						sortedTrackedList[index].gameObject.name = "tra_arw(" + trackerIndex.ToString() + "_" + maleObjectListName[index] + ")";
					}
					new GameObject("Debug11");

					// maleの親を sortedTrackedList[1] (腰のトラッカー) に設定
					male.transform.parent = sortedTrackedList[1];
					male.transform.position += sortedTrackedList[1].position - maleObjectList[1].position;
					male.transform.localRotation = Quaternion.identity;
					new GameObject("Debug12");

					// maleの各IKオブジェクトの親をトラッカーに設定
					// 0(HMD)は追従しないので1からループ
					for (int index = 1; index < trackerNumber_wo_controller; index++){
						if(maleObjectList[index] == null){
							new GameObject("Debug13-" + index.ToString());
						}
						if(sortedTrackedList[index] == null){
							new GameObject("Debug14-" + index.ToString());
						}
						maleObjectList[index].transform.parent = sortedTrackedList[index];
						maleObjectList[index].transform.localPosition = Vector3.zero;
						maleObjectList[index].transform.localRotation = Quaternion.identity;
					}
					new GameObject("Debug15");
					
					// キャリブレーションが終わったら矢印は非表示にする
					var go=GameObject.Find("acs_O_arrow02");
					while (go){
						go.SetActive(false);
						go = GameObject.Find("acs_O_arrow02");
					}

					GameObject.DestroyImmediate(calibratingObj);
					new GameObject("isCalibrated");
					isCalibrated = true;
				}
			}
		}
	}
}
ハニーセレクト2用rspファイル
/r:Plugins/VRGIN.dll
ハニーセレクト2用サンプルシーン

(2) コイカツサンシャイン用ファイル

以下ファイルの配置のほか、VRGIN_OpenXR.dllをPluginsフォルダにコピーする必要があります。
VRGIN_OpenXR.dllは、BepInEx / plugins / KKS_VR にあるはずです。

※なぜか、コイカツサンシャインを起動するたびにVRGIN_OpenXR.dllが 0byte になるので、GOLマクロのコンパイルに失敗する場合で、もしファイルが0byteになっていたら再コピーしてください。

コイカツサンシャイン用GOLマクロ
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using GameObjectListPlugin;
using System.Reflection;
using System.Collections;
using Valve.VR;


public class GameObjectListMacro
{
	public static Transform FindAllChildren(GameObject obj, String name){
		Transform find_obj= obj.transform.Find(name);
		
		//見つかれば終了
		if (find_obj != null){
			return find_obj;
		}
		
		Transform children = obj.GetComponentInChildren<Transform> ();
		//子要素がいなければ終了
		if (children.childCount == 0) {
			return null;
		}
		
		//子要素がいれば再帰的に検索
		foreach (Transform child in children) {
			//対象が自分自身だったらスキップ
			if (child.gameObject == obj){
				continue;
			}
			
			//子要素を再帰的に検索
			find_obj = FindAllChildren(child.gameObject, name);
			if (find_obj != null){
				return find_obj;
			}
		}
		
		//配下を全検索して見つからなければnull
		return null;
	}

	public static void FindChildrenName(GameObject obj, ref List<Transform> findList, String part_of_name){
		//与えられたobjの名前をチェック
		if(obj.name.Contains(part_of_name)){
			findList.Add(obj.transform);
		}
		
		Transform children = obj.GetComponentInChildren<Transform> ();
		
		//子要素がいなければ終了
		if (children.childCount == 0) {
			return;
		}
		
		//子要素がいれば再帰的に検索
		foreach (Transform child in children) {
			//対象が自分自身だったらスキップ
			if (child.gameObject == obj){
				continue;
			}
			
			//子要素を再帰的に検索
			FindChildrenName(child.gameObject, ref findList, part_of_name);
		}
	}

	public static List<Transform> FindList_PartOfName(GameObject obj, String part_of_name){
		List<Transform> findList = new List<Transform> ();
		FindChildrenName(obj, ref findList, part_of_name);
		return findList;
	}

	public static void MacroMain()
	{
		var obj=GameObject.Find("TrackExecuting");
		if (obj)
		{
			//関連オブジェクト破壊
			GameObject.DestroyImmediate(obj);
			
			// Debugファイルを削除
			var go=GameObject.Find("Debug");
			for(int i=0; i<20; i++){
				String debugName = "Debug" + i.ToString();
				go=GameObject.Find(debugName);
				while (go){
					GameObject.DestroyImmediate(go);
					go = GameObject.Find(debugName);
				}
				for(int j=0; j<20; j++){
					debugName = "Debug" + i.ToString() + "-" + j.ToString();
					go=GameObject.Find(debugName);
					while (go){
						GameObject.DestroyImmediate(go);
						go = GameObject.Find(debugName);
					}
				}
			}

			var vrgin = GameObject.Find("VRGIN_Camera (origin)").gameObject;
			var maleObjectListName = new string[]{
				"cf_J_NoseBridge_t",		//maleHead
				"cf_t_hips(work)",			//maleHip
				"cf_t_hand_L(work)",			//maleLeftHand
				"cf_t_elbo_L(work)",		//maleLeftElbowr
				"cf_t_shoulder_L(work)",	//maleLeftShoulder
				"cf_t_hand_R(work)", 		//maleLRightHand
				"cf_t_elbo_R(work)",		//maleLRightElbowr
				"cf_t_shoulder_R(work)",	//maleLRightShoulder
				"cf_t_leg_L(work)",			//maleLeftFoot
				"cf_t_knee_L(work)",		//maleLeftKnee
				"cf_t_waist_L(work)",		//maleLeftwaist
				"cf_t_leg_R(work)",			//maleLRightFoot
				"cf_t_knee_R(work)",		//maleLRightKnee
				"cf_t_waist_R(work)"		//maleRightwaist
			};
			var findList = FindList_PartOfName(vrgin, "chaM_");
			findList[0].parent = vrgin.transform;
			for(int index=1; index<maleObjectListName.Length; index++){
				go = FindAllChildren(vrgin, maleObjectListName[index]).gameObject;
				go.transform.parent = vrgin.transform;
			}

			var findResult = FindList_PartOfName(vrgin, "tra_arw(");
			foreach (Transform arrow02 in findResult){
				var comp = arrow02.gameObject.GetComponent<SteamVR_TrackedObject>();
		   		GameObject.DestroyImmediate(comp);
				GameObject.DestroyImmediate(arrow02.gameObject);
			}

			go=GameObject.Find("acs_O_arrow02");
			while (go){
				go.SetActive(true);
				go = GameObject.Find("acs_O_arrow02");
			}
			
			go=GameObject.Find("isCalibrating");
			while (go){
				GameObject.DestroyImmediate(go);
				go = GameObject.Find("isCalibrating");
			}
			go=GameObject.Find("isCalibrated");
			while (go){
				GameObject.DestroyImmediate(go);
				go = GameObject.Find("isCalibrated");
			}

			return;
		}
		new GameObject("TrackExecuting").AddComponent<MyComponent>();
	}
}

public class MyComponent : MonoBehaviour
{
	private GameObject trackerTemplate;
	private GameObject leftController;
	private GameObject rightController;

	private GameObject male;
	private int[] trackerIndexList;
	private Transform[] sortedTrackedList;
	private int trackerNumber_wo_controller;

	private string[] maleObjectListName;
	private Transform[] maleObjectList;
	private Transform[] trackedTransformList;

	private DateTime startTime;
	private bool isCalibrated = false;


	//基準となる中心がcenter,leftVecが左を示すベクトルで,trackerの位置座標から右左の判定をして返す
	Transform[] detectTransformSetting(Transform[] trackers, Vector3 center, Vector3 leftVec) {
		var sortedTrackers = trackers.OrderBy(tracker => {
			var vec = tracker.position - center;
			return Vector2.Dot(leftVec, vec);
		}).ToArray();

		Transform right = sortedTrackers[0];
		Transform left = sortedTrackers[1];

		return new Transform[]{right, left};
	}


	Transform[] Detect(Transform[] trackers){
		//y座標で降順にソートする
		var sortedTrackers = trackers.OrderBy(a => -a.position.y).ToArray();
		var head = sortedTrackers[0]; //一番最初の要素は頭のトラッカー
		var body = sortedTrackers[7]; //頭+右肩+右ひじ+右手+左肩+左ひじ+左手の後,体(pelvis)のトラッカー
		var vecTop = (head.position - body.position).normalized; //体から頭方向へのベクトル

		//腕(eblowとhand)と肩のグループ
		var armGroup = new Transform[]{
			sortedTrackers[1],
			sortedTrackers[2],
			sortedTrackers[3],
			sortedTrackers[4],
			sortedTrackers[5],
			sortedTrackers[6]
		};

		//体の中心に近いもの順にソート
		var averageSortedArmGroup = armGroup.OrderBy(tracker => {
			var vec = tracker.position - body.position;
			var magnitude = Vector3.Dot(vec, vecTop);
			return (vec - magnitude * vecTop).magnitude;
		}).ToArray();

		//体の中心から遠いものは手(hand)
		var handGroup = new Transform[] {
			averageSortedArmGroup[4],
			averageSortedArmGroup[5]
		};

		//体の中心から手へのベクトルを定義
		var vec1 = handGroup[0].position - body.position;
		var vec2 = handGroup[1].position - body.position;

		//手の高さから手の方向へのベクトル
		var modVec1 = vec1 - Vector3.Dot(vecTop, vec1) * vecTop;
		var modVec2 = vec2 - Vector3.Dot(vecTop, vec2) * vecTop;

		//外積ベクトル
		var crossVec = Vector3.Cross(modVec1, modVec2);

		Transform rightHand;
		Transform leftHand;

		//外積ベクトルと体のベクトルの方向から右手か左手を判定する
		//vec1が右手の時,外積ベクトルは下を向く(左手系だから)
		if (Vector3.Dot(vecTop, crossVec) < 0)
		{
			rightHand = handGroup[0];
			leftHand = handGroup[1];
		}
		else
		{
			rightHand = handGroup[1];
			leftHand = handGroup[0];
		}

		//左手方向へのベクトル
		var leftVec = (leftHand.position - body.position).normalized;

		//ArmGroupの中心に近いものは肩
		var shoulderGroup = new Transform[] {
			averageSortedArmGroup[0],
			averageSortedArmGroup[1]
		};

		//次にArmGroupの中心に近いものは肘
		var elbowGroup = new Transform[] {
			averageSortedArmGroup[2],
			averageSortedArmGroup[3]
		};
		
		//頭,肩*2,肘*2,手*2,体,"右股関節","左股関節"
		var thighGroup = new Transform[] {
			sortedTrackers[8],
			sortedTrackers[9]
		};

		//頭,肩*2,肘*2,手*2,体,"右ひざ","左ひざ"
		var kneeGroup = new Transform[] {
			sortedTrackers[10],
			sortedTrackers[11]
		};

		//頭,肘*2,手*2,体,ひざ*2,"右足","左足"
		var footGroup = new Transform[] {
			sortedTrackers[12],
			sortedTrackers[13]
		};

		var shoulderSetting = detectTransformSetting(shoulderGroup, body.position, leftVec);
		var elbowSetting = detectTransformSetting(elbowGroup, body.position, leftVec);
		var thighSetting = detectTransformSetting(thighGroup, body.position, leftVec);
		var kneeSetting = detectTransformSetting(kneeGroup, body.position, leftVec);
		var footSetting = detectTransformSetting(footGroup, body.position, leftVec);
		
		return new Transform[]{
			head,
			body,
			leftHand,
			elbowSetting[1],
			shoulderSetting[1],
			rightHand,
			elbowSetting[0],
			shoulderSetting[0],
			footSetting[1],
			kneeSetting[1],
			thighSetting[1],
			footSetting[0],
			kneeSetting[0],
			thighSetting[0]
		};
	}


	Transform FindAllChildren(GameObject obj, String name){
		Transform find_obj= obj.transform.Find(name);
		
		//見つかれば終了
		if (find_obj != null){
			return find_obj;
		}
		
		Transform children = obj.GetComponentInChildren<Transform> ();
		//子要素がいなければ終了
		if (children.childCount == 0) {
			return null;
		}
		
		//子要素がいれば再帰的に検索
		foreach (Transform child in children) {
			//対象が自分自身だったらスキップ
			if (child.gameObject == obj){
				continue;
			}
			
			//子要素を再帰的に検索
			find_obj = FindAllChildren(child.gameObject, name);
			if (find_obj != null){
				return find_obj;
			}
		}
		
		//配下を全検索して見つからなければnull
		return null;
	}


	void FindChildrenName(GameObject obj, ref List<Transform> findList, String part_of_name){
		//与えられたobjの名前をチェック
		if(obj.name.Contains(part_of_name)){
			findList.Add(obj.transform);
		}
		
		Transform children = obj.GetComponentInChildren<Transform> ();
		
		//子要素がいなければ終了
		if (children.childCount == 0) {
			return;
		}
		
		//子要素がいれば再帰的に検索
		foreach (Transform child in children) {
			//対象が自分自身だったらスキップ
			if (child.gameObject == obj){
				continue;
			}
			
			//子要素を再帰的に検索
			FindChildrenName(child.gameObject, ref findList, part_of_name);
		}
	}


	List<Transform> FindList_PartOfName(GameObject obj, String part_of_name){
		List<Transform> findList = new List<Transform> ();
		FindChildrenName(obj, ref findList, part_of_name);
		return findList;
	}


	void Start(){
		// Debugファイルを削除
		var go=GameObject.Find("Debug");
		for(int i=0; i<20; i++){
			String debugName = "Debug" + i.ToString();
			go=GameObject.Find(debugName);
			while (go){
				GameObject.DestroyImmediate(go);
				go = GameObject.Find(debugName);
			}
			for(int j=0; j<20; j++){
				debugName = "Debug" + i.ToString() + "-" + j.ToString();
				go=GameObject.Find(debugName);
				while (go){
					GameObject.DestroyImmediate(go);
					go = GameObject.Find(debugName);
				}
			}
		}

		// スタジオシーンのトップフォルダを定義
		GameObject topFolder = GameObject.Find("CommonSpace").gameObject;

		// トラッキング表示用に作成した矢印オブジェクトが残っていたら削除
		var findResult = FindList_PartOfName(topFolder, "tra_arw(");
		foreach (Transform arrow02 in findResult){
			GameObject.DestroyImmediate(arrow02.gameObject);
		}

		new GameObject("Debug0");

		// トラッキング表示用のテンプレートオブジェクトを指定
		trackerTemplate = GameObject.Find("p_koi_stu_arrow01_01_02");
		go=FindAllChildren(trackerTemplate, "acs_O_arrow02").gameObject;
		go.SetActive(true);

		new GameObject("Debug1");

		var vrgin = GameObject.Find("VRGIN_Camera (origin)"); //HMD root
		List<Transform> findList = FindList_PartOfName(topFolder, "chaM_");
		var chaFolder = topFolder;
		if(findList.Count != 0){
			male = findList[0].gameObject;
			chaFolder = male;
			new GameObject("Debug2-1");
		}else{
			findList = FindList_PartOfName(vrgin, "chaM_");
			if(findList.Count != 0){
				male = findList[0].gameObject;
				chaFolder = vrgin.gameObject;
				new GameObject("Debug2-2");
			}else{
				new GameObject("Debug2-3");
			}
		}

		// maleのheadとを非表示に変更
		var head = FindAllChildren(male, "cf_j_head");
		head.gameObject.SetActive(false);
		new GameObject("Debug3-1");

		maleObjectListName = new string[]{
			"cf_J_NoseBridge_t",		//maleHead
			"cf_t_hips(work)",			//maleHip
			"cf_t_hand_L(work)",			//maleLeftHand
			"cf_t_elbo_L(work)",		//maleLeftElbowr
			"cf_t_shoulder_L(work)",	//maleLeftShoulder
			"cf_t_hand_R(work)", 		//maleLRightHand
			"cf_t_elbo_R(work)",		//maleLRightElbowr
			"cf_t_shoulder_R(work)",	//maleLRightShoulder
			"cf_t_leg_L(work)",			//maleLeftFoot
			"cf_t_knee_L(work)",		//maleLeftKnee
			"cf_t_waist_L(work)",		//maleLeftwaist
			"cf_t_leg_R(work)",			//maleLRightFoot
			"cf_t_knee_R(work)",		//maleLRightKnee
			"cf_t_waist_R(work)"		//maleRightwaist
		};
		trackerNumber_wo_controller = maleObjectListName.Length;

		// maleObjectListNameからオブジェクトを検索
		maleObjectList = new Transform[trackerNumber_wo_controller];
		for(int index=0; index<trackerNumber_wo_controller; index++){
			maleObjectList[index] = FindAllChildren(chaFolder, maleObjectListName[index]);
		}
		new GameObject("Debug3-3");


		// VRモードか判断
		if(vrgin){
			// トラッカーのキャリブレーション用タイマーの開始時間を取得
			startTime = DateTime.Now;

			// 左右のコントローラを取得
			leftController = GameObject.Find("VRGIN_Camera (origin)/Left Controller");//左コントローラ
			rightController = GameObject.Find("VRGIN_Camera (origin)/Right Controller");//右コントローラ
			new GameObject("Debug4-1");

			// 左右のコントローラのインデックスを取得
			// SteamVR2.0で取得方法が変わった
			// https://github.com/ValveSoftware/steamvr_unity_plugin/blob/master/Assets/SteamVR/Input/SteamVR_Behaviour_Pose.cs
			var leftController_inputSource = leftController.GetComponent<SteamVR_Behaviour_Pose>().inputSource;
			var leftController_poseAction = leftController.GetComponent<SteamVR_Behaviour_Pose>().poseAction;
			var leftController_index = (SteamVR_TrackedObject.EIndex)leftController_poseAction[leftController_inputSource].trackedDeviceIndex;

			var rightController_inputSource = rightController.GetComponent<SteamVR_Behaviour_Pose>().inputSource;
			var rightController_poseAction = rightController.GetComponent<SteamVR_Behaviour_Pose>().poseAction;
			var rightController_index = (SteamVR_TrackedObject.EIndex)rightController_poseAction[rightController_inputSource].trackedDeviceIndex;
			new GameObject("Debug4-2");

			// コントローラ以外のトラッカーのインデックスを調べる
			trackerIndexList = new int[trackerNumber_wo_controller];
			var listIndex = 0;
			// コントローラも含めて trackerNumber_wo_controller+2 個のトラッカーがあるので全数検索
			for (int tmpIndex = 0; tmpIndex < trackerNumber_wo_controller+2; tmpIndex++){
				// 左右のコントローラのインデックスとは異なるようなら使えるインデックスとしてリストに格納
				if(leftController_index != (SteamVR_TrackedObject.EIndex)(tmpIndex)
				&& rightController_index != (SteamVR_TrackedObject.EIndex)(tmpIndex)){
					trackerIndexList[listIndex] = tmpIndex;
					listIndex++;
				}
			}
			new GameObject("Debug4-3");
			
			
			// トラッカーの位置を反映するオブジェクトを作成
			trackedTransformList = new Transform[trackerNumber_wo_controller];
			for (int index = 0; index < trackerNumber_wo_controller; index++){
				// テンプレートをクローン
				var tracker = GameObject.Instantiate(trackerTemplate) as GameObject;
				// 親をvrginに指定
				tracker.transform.parent = vrgin.transform;
				// SteamVR_TrackedObject を追加
				tracker.AddComponent<SteamVR_TrackedObject>().SetDeviceIndex(trackerIndexList[index]);
				// リストに格納
				trackedTransformList[index] = tracker.transform;
			}
			new GameObject("Debug4-4");
		}
	}

	void Update(){
		var obj=GameObject.Find("TrackExecuting");
		var vrgin = GameObject.Find("VRGIN_Camera (origin)"); //HMD root

		// TrackExecuting と vrgin があるか判断
		if(obj!=null && vrgin!=null){
			if(isCalibrated){
				// キャリブレーション済みなら、腰のトラッカーとmaleの腰の位置をそろえるため、
				// maleの位置をずらす
				male.transform.position += sortedTrackedList[1].position - maleObjectList[1].position;
				male.transform.localRotation = Quaternion.identity;

				for (int index = 2; index < trackerNumber_wo_controller; index++){
					maleObjectList[index].transform.localPosition = Vector3.zero;
					maleObjectList[index].transform.localRotation = Quaternion.identity;
				}

			}else{
				var timeDelta = DateTime.Now - startTime;
				var calibratingObj=GameObject.Find("isCalibrating");
				if(10 < timeDelta.TotalSeconds && calibratingObj==null){
					// 開始から10秒経っている かつ キャリブレーションに失敗していないならキャリブレーションを開始
					calibratingObj = new GameObject("isCalibrating");
					sortedTrackedList = Detect(trackedTransformList);
					new GameObject("Debug10");

					// トラッカーの名前を変更
					for (int index = 0; index < trackerNumber_wo_controller; index++){
						int trackerIndex = (int)(sortedTrackedList[index].gameObject.GetComponent<SteamVR_TrackedObject>().index);
						sortedTrackedList[index].gameObject.name = "tra_arw(" + trackerIndex.ToString() + "_" + maleObjectListName[index] + ")";
					}
					new GameObject("Debug11");

					// maleの親を sortedTrackedList[1] (腰のトラッカー) に設定
					male.transform.parent = sortedTrackedList[1];
					male.transform.position += sortedTrackedList[1].position - maleObjectList[1].position;
					male.transform.localRotation = Quaternion.identity;
					new GameObject("Debug12");

					// maleの各IKオブジェクトの親をトラッカーに設定
					// 0(HMD)は追従しないので1からループ
					for (int index = 1; index < trackerNumber_wo_controller; index++){
						if(maleObjectList[index] == null){
							new GameObject("Debug13-" + index.ToString());
						}
						if(sortedTrackedList[index] == null){
							new GameObject("Debug14-" + index.ToString());
						}
						maleObjectList[index].transform.parent = sortedTrackedList[index];
						maleObjectList[index].transform.localPosition = Vector3.zero;
						maleObjectList[index].transform.localRotation = Quaternion.identity;
					}
					new GameObject("Debug15");
					
					// キャリブレーションが終わったら矢印は非表示にする
					var go=GameObject.Find("acs_O_arrow02");
					while (go){
						go.SetActive(false);
						go = GameObject.Find("acs_O_arrow02");
					}

					GameObject.DestroyImmediate(calibratingObj);
					new GameObject("isCalibrated");
					isCalibrated = true;
				}
			}
		}
	}
}
コイカツサンシャイン用rspファイル
/r:Plugins/VRGIN_OpenXR.dll
コイカツサンシャイン用サンプルシーン

4. マクロの実行

実行方法は適宜調べていただきたいですが、ざっくり以下のような内容です。
また、すんなり実行できることの方が少ないので、めんどくさいですがgolconfig.iniでサイレントコンパイルをfalseにするのをお勧めします。

 SilentExternalCompile=False

(1) マクロのコンパイル・実行

Ctrl+Mを押すとGOLのマクロ実行ウィンドウが出てくるので、その中の [VRMOD]フルトラッキング を押します。
押すと、コマンドプロンプト画面が開きコンパイルが始まります。

warningが出る場合もありますが、errorが出ていなければコンパイルが正常に完了しています。
errorが出た場合はどうにかして解消してください。

コマンドプロンプトを右上の ×ボタン で閉じると実行開始となります。

※マクロを再実行するとフルトラッキングがOFF状態となります

(2) 10秒以内にLポーズをとる

SteramVR上では、トラッカーが体のどの部位に紐づいているか不明、かつ起動するたびに順番が変わる可能性があるためこちらの記事を参考にLポーズでキャリブレーションします。

マクロを開始すると各トラッカー点に矢印が現れるため、それを参考にして正常にトラッキングできていることを確認しつつLポーズをとります。
開始10秒後にキャリブレーションを行い、トラッカーの部位を確定させます。
もしキャリブレーションがおかしい場合は、マクロを一度OFFにした後再度キャリブレーションしてください。
※変な挙動になる場合は一度ゲーム自体を落として立ち上げなおすのが確実です。

(3) トラッキング開始

キャリブレーションが正常に実行できた場合はゲーム内の各部位が、現実の動きに従って動いてくれるはずです。

5. もし動かない場合

GOLマクロ内に、デバッグ用オブジェクト(DebugX-Y)の生成処理を仕込んでます。
Ctrl+Iで表示されるGOLウィンドウでデバッグ用オブジェクトがどこまで生成されているかを確認することでデバッグができます。

実行環境よって動かない可能性も多分にあるかと思うので、もし動かなかったら頑張ってデバッグしてみてください。

コメント

タイトルとURLをコピーしました