じゆうけんきゅうの時間

好きなことだけにひたすら向き合う...そんなひととき

<Unity, C#, 行列演算, ライブラリ> Unity Asset Storeで行列演算スクリプトをリリースしました

UnityやC#で扱える行列演算ライブラリが無かったので,自作していました.

Unity Asset Storeの審査が通ったのでアセットとして販売することにしました!アセットの名前はMatrix Operations Libraryです.


特徴

UnityのAssetStoreにプログラムを提出した

UnityのAssetStoreに自作したプログラムを提出しました.審査が通れば販売できます.私のプログラムのレビューが返ってくると思うので,それを確認して技術向上を図りたいと思っています.

審査に2週間かかるとのことです.さて,次の作品を作ろう.

<C#, Unity, Eigen> C#で行列演算ライブラリEigenの機能を使う

Unityで開発している際,4次以上の逆行列の演算が必要になったのでC#で使える行列演算ライブラリを探していました.しかしながら,使えそうなライブラリが見つからず,C++で扱える行列演算ライブラリ"Eigen"をC#で利用しようと考えました.以下,詳細です.

開発環境

概要

以下の手順でC#で"Eigen"の逆行列演算機能を使います.

  1. C++側で"Eigen"の逆行列演算機能をダイナミックリンクライブラリ(DLL)として出力
  2. UnityのPluginsフォルダにDLLファイルを置く
  3. C#側で出力したDLLの関数を呼ぶ(Wrapperを書く)
  4. C#側でWrapperを使う

Eigenの機能をDLLとして出力

以下,サンプルコードです.Visual StudioのDLLプロジェクトを新規作成し,サンプルコードを追加後,ビルドしてDLLファイルを作成します.ちなみに今回のプロジェクト名は"EigenFuncs"としています.Unityの環境に合わせて32bitもしくは64bitでビルドします.

.hファイル
/*--------------*/
/* EigenFuncs.h */
/*--------------*/
#pragma once

extern "C" {

#ifdef EIGENFUNCS_EXPORTS
#define EIGEN_FUNCS_API __declspec(dllexport)
#else
#define EIGEN_FUNCS_API __declspec(dllimport)
#endif
	EIGEN_FUNCS_API void InverseMat_FullPivLU(int dim, float a[], float ans[]);
} // extern "C"
.cppファイル
/*----------------*/
/* EigenFuncs.cpp */
/*----------------*/
// EigenFuncs.cpp : DLL アプリケーション用にエクスポートされる関数を定義します。
//

#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <vector>
#include <Eigen/Core>
#include <Eigen/Dense>
#include <Eigen/Geometry>
#include <Eigen/LU>
#include "EigenFuncs.h"

extern "C" {
	EIGEN_FUNCS_API void InverseMat_FullPivLU(int dim, float a[], float ans[]) {
		Eigen::MatrixXf mat = Eigen::MatrixXf::Zero(dim, dim);

		int count = 0;
		for (int c = 0; c < dim; c++) {
			for (int r = 0; r < dim; r++) {
				mat(c, r) = a[count];
				count++;
			}
		}

		Eigen::FullPivLU< Eigen::MatrixXf > lu(mat);
		Eigen::MatrixXf InvMat = mat.inverse();

		count = 0;
		for (int c = 0; c < dim; c++) {
			for (int r = 0; r < dim; r++) {
				ans[count] = InvMat(c, r);
				count++;
			}
		}
	}
} // extern "C"

DLLファイルをUnityのPluginsフォルダに置く

以下のようにする.Pluginsフォルダが無い場合は新規作成する.

Assets/Plugins/EigenFuncs.dll

C#でDLLのWrapperを書く

以下,DLLの関数をC#側で呼び出し,逆行列演算をする関数を実装したものです.

/*--------------*/
/* EigenFunc.cs */
/*--------------*/
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Text;
using System.Runtime.InteropServices;   //Dll


public class EigenFunc
{

	[DllImport("EigenFuncs")]
	private static extern void InverseMat_FullPivLU(int dim, float[] a, float[] ans);

	private void Matrix2Array(float[,] Mat, ref float[] Arr)
	{
		int count = 0;
		int dim = (int)Mathf.Sqrt(Mat.Length);
		for (int c = 0; c < dim; c++)
		{
			for (int r = 0; r < dim; r++)
			{
				Arr[count] = Mat[c, r];
				count++;
			}
		}
	}

	private void Array2Matrix(float[] Arr, float[,] Mat)
	{
		int count = 0;
		int dim = (int)Mathf.Sqrt(Mat.Length);
		for (int c = 0; c < dim; c++)
		{
			for (int r = 0; r < dim; r++)
			{
				Mat[c, r] = Arr[count];
				count++;
			}
		}
	}

	public float[,] InverseMatrix(float[,] Mat) {
		float[] arr = new float[Mat.Length];
		int dim = (int)Mathf.Sqrt(Mat.Length);
		float[,] AnsMat = new float[dim, dim];

		Matrix2Array(Mat, ref arr);
		float[] ansArr = new float[Mat.Length];
		InverseMat_FullPivLU(dim, arr, ansArr);
		Array2Matrix(ansArr, AnsMat);

		return (float[,])AnsMat.Clone();
	}
}

C#側で逆行列を計算するサンプルコード

以下

	void sampleSource() {
		/* EigenFunc.dll Sample */
		EigenFunc eigen = new EigenFunc();
		float[,] bufMat = new float[3, 3] { { 1f, 2f, 1f }, { 2f, 1f, 0f }, { 1f, 1f, 2f } };
		float[,] AnsMat = new float[3, 3];

		AnsMat = eigen.InverseMatrix(bufMat);

	}

<C#, Unity> class単位のログを取得する(参照渡しで渡さない方法)

C#でclass内のすべてのメンバ変数のログを取得したいと思い単純に以下のようにList型のオブジェクトでログを取得しようとしましたが,Listの中身が一番最後のデータですべて埋め尽くされました.classは参照渡しをされるため,メモリ空間をそのまま渡すことはしていないんですね.

class単位のログの取得に失敗した例

//ログを取得する対象となるclass
public class Point {
	float m_x;
	float m_y;
	float m_z;

	public void AddOne() {
		m_x += 1.0f;
		m_y += 1.0f;
		m_z += 1.0f;
	}
}

//ループを回してログを取得する側
public class MainClass {
	Point m_Point = new Point();
	List<Point> m_Log = new List<Point>();

	void Loop() {
		m_Point.AddOne();

		//classは参照渡しとして渡されるため,
		//最後のデータでログが埋め尽くされる
		m_Log.Add(m_Point);
	}
}


そこで,"MemberwiseClone"を使うと新たにメモリを確保しclassを複製することが出来ます.これを使用した例は以下の通りです.

class単位のログの取得に成功した例

//ログを取得する対象となるclass
public class Point : ICloneable {
	float m_x;
	float m_y;
	float m_z;

	public void AddOne() {
		m_x += 1.0f;
		m_y += 1.0f;
		m_z += 1.0f;
	}

	public object Clone(){
		return MemberwiseClone();
	}
}

//ループを回してログを取得する側
public class MainClass {
	Point m_Point = new Point();
	List<Point> m_Log = new List<Point>();

	void Loop() {
		m_Point.AddOne();

		// classのクローンを作ることでログの取得が可能になる
		m_Log.Add((Point)m_Point.Clone());
	}
}