RDKitのC++ライブラリをUnityで利用する[Windows編]

# 前回と前々回

https://qiita.com/kyusque/items/957933f1cc457011b66a

https://qiita.com/kyusque/items/031a6f4088faecb84da9

# 今回

前回はC#アプリでRDKitを動かせるようになったので今回はUnityで利用出来るようにします。

# 環境

- Unity 2021.2.9f1
    - (多分これ以降のバージョンでも大丈夫です)


# ラッパーライブラリのビルド及びC#コンソールアプリのビルド

C#に必要なライブラリを取り出すため、予め[前回の記事](https://qiita.com/kyusque/items/031a6f4088faecb84da9)の[C#コンソールアプリの作成]まで進めます。
ただ少し変更があり、コンソールアプリのプロジェクト作成の後、SkiaSharpのインポートを以下のようにバージョン指定してください。
これは、最新バージョンのSkiaSharpのライブラリが現行のUnityのランタイムで動かせないためです。(そのうち対応されると思います)

```
dotnet new console -lang C#
dotnet add package SkiaSharp.Svg -v 1.60.0
dotnet add package SkiaSharp -v 1.60.3
```

# Unityでのアプリ作成

Unityで新しい3Dプロジェクトを作成してください。
例のごとく簡単のため、Cドライブ直下にRDKitUnityというプロジェクトを作成します。

## フォルダ構成

一連の作業を終えるとこのような構成になります。
順次説明します。

RDKitUnity/Assets
├─ Scenes
│　└─ SampleScene.unity
├─ win64
│　├─ libSkiaSharp.dll
│　├─ RDKitWrapper.dll
│　├─ SkiaSharp.dll
│　└─ SkiaSharp.Extended.Svg.dll
└─ DrawSmiles.cs


## ライブラリファイルの配置

先のC#コンソールアプリのビルドで出力されたDLLファイルをUnityのAssetsフォルダに配置します。
win64フォルダを作成してその中にライブラリ群を配置します。
この際、コンソールアプリのフォルダ内のruntimes内に存在するネイティブプラグインもすべて配置します。

```
cd C:
mkdir RDKitUnity/Assets/win64
cp RDKitCSharpCli/bin/Release/net6.0/RDKitWrapper.dll RDKitUnity/Assets/win64
cp RDKitCSharpCli/bin/Release/net6.0/SkiaSharp.dll RDKitUnity/Assets/win64
cp RDKitCSharpCli/bin/Release/net6.0/SkiaSharp.Extended.Svg.dll RDKitUnity/Assets/win64
cp RDKitCSharpCli/bin/Release/net6.0/runtimes/win7-x64/native/libSkiaSharp.dll RDKitUnity/Assets/win64
```

## Unityのスクリプトを作成

Assets内にDrawSmiles.csを以下のように作成します。
エディター上で配置したRawImageにRDKitラッパーライブラリで作成したsvgをpng変換してテクスチャを張り付ける形になります。

```c#:DrawSmiles.cs
using System;
using System.IO;
using System.Text;
using SkiaSharp;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;

public class DrawSmiles : MonoBehaviour
{
    [SerializeField] private RawImage rawImage;
    [SerializeField] private string smiles;
    [SerializeField] private Int32 bufSize = 100000000;
    private string tempSmiles;

    private void Start()
    {
        UpdateMolecule();
    }

    private void Update()
    {
        if (tempSmiles != smiles)
        {
            UpdateMolecule();
        }
    }

    void UpdateMolecule()
    {
        var stringBuilder = new StringBuilder(bufSize);
        var tempSvgPath = Path.Combine(Application.persistentDataPath, "temp_" + Path.GetRandomFileName());
        try
        {
            if (RDKitWrapper.DrawSVG(smiles, stringBuilder, bufSize) < 0)
            {
                return;
            }
        }
        catch
        {
            return;
        }

        File.WriteAllText(tempSvgPath, stringBuilder.ToString());
        var svg = new SKSvg(new SKSize(300, 300));
        svg.Load(tempSvgPath);
        var bitmap = new SKBitmap((int)svg.CanvasSize.Width, (int)svg.CanvasSize.Height);
        var canvas = new SKCanvas(bitmap);
        canvas.DrawPicture(svg.Picture);
        canvas.Flush();
        canvas.Save();
        
        using var image = SKImage.FromBitmap(bitmap);
        using var data = image.Encode(SKEncodedImageFormat.Png, 80);
        {
            var bytes = data.ToArray();
            var texture = new Texture2D(200, 200);
            texture.filterMode = FilterMode.Trilinear;
            texture.LoadImage(bytes);
            rawImage.texture = texture;
            rawImage.SetNativeSize();
        }
        File.Delete(tempSvgPath);
        tempSmiles = smiles;
    }
    
    public static class RDKitWrapper
    {
        [DllImport("RDKitWrapper")]
        public static extern int DrawSVG(string smiles, [Out] StringBuilder buf, int bufsize);
    }
}

```

## Unityエディタ上での処理

Scene/SampleSceneをそのまま開いて利用します。

### RawImageの作成

Hierarchyを右クリックしてUI/Raw Imageを作成します。
その後、インスペクター上でpositonを(0,0,0)に設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/158368/a90eddc6-3183-b879-2af3-0f8aa6ba7f06.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/158368/c6f63e45-2baa-331c-dba7-8e98d47b8307.png)


### Main CameraにDrawSmilesをアタッチ

Main CameraにDrawSmilesをアタッチして、インスペクター上で以下のように設定します。
RawImageはシーン上のものをそのままアタッチします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/158368/006765a1-c711-cdae-31b1-f6725c2bf9b0.png)

### 完成

再生ボタンを押すとSmilesで生成された分子が表示されます。

![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/158368/f43df637-20e6-bb85-5ce5-e4e240d3f675.gif)

# 今後

ひとまずこれでUnity上で動くようになりました。
今度はAndroidでも動かせるようAndroidのネイティブプラグインを作成する記事を書こうと思います。