20191021のC#に関する記事は10件です。

【Nuget】C#の自作ライブラリをNugetパッケージ化する方法

社内業務改善において、WindowsデスクトップアプリケーションをC#で開発することは結構有用だと思っています。

その中で、色々な社内アプリケーションで共通部分の設計っていっぱいあるよなあってよく感じています。

例えば

  • 社員番号から個人情報(氏名・IDなど)を入手
  • 社内メールを配信
  • 部品リストの操作

等。

こういう同じような機能を社内の色々な人が開発するのは無駄なので、C#で自作したライブラリをNugetで配布する方法をメモしておきます。

Nuget

Visual Studioにおけるパッケージ管理ツール。
インストールしたライブラリが依存しているライブラリも自動でインストールしてくれるので便利。

用意するもの

ビルドしたライブラリをNugetパッケージ化するソフト。コマンドラインから使用します。

  • ビルドした.NETFrameworkライブラリ

当たり前ですが。

準備

  • nuspecファイルを生成

Nugetパッケージ化はプロジェクトと同名の.nuspecファイルに基づいて行われますので、nuspecファイルを用意します。その正体はxmlファイル。
下記のコマンドで生成します。

nuget spec

すると、Package.nuspecというファイルが生成されます。
中身はこんなの

<?xml version="1.0"?>
<package >
  <metadata>
    <id>Package</id>
    <version>1.0.0</version>
    <authors></authors>
    <owners></owners>
    <licenseUrl>http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE</licenseUrl>
    <projectUrl>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE</projectUrl>
    <iconUrl>http://ICON_URL_HERE_OR_DELETE_THIS_LINE</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Package description</description>
    <releaseNotes>Summary of changes made in this release of the package.</releaseNotes>
    <copyright>Copyright 2019</copyright>
    <tags>Tag1 Tag2</tags>
    <dependencies>
      <dependency id="SampleDependency" version="1.0" />
    </dependencies>
  </metadata>
</package>

内容のリファレンスはこちら

このファイル名を「プロジェクト名.nuspec」にして、プロジェクトのルートフォルダに入れておきます。(ソリューションに登録しておくと楽かも?)

発行する

発行するのは下記コマンド

nuget pack [パッケージ化するディレクトリ]

Nugetパッケージが生成されるので、無事発行完了。

おまけ

社内でNuget配布するには?

ファイル共有サーバーのどのフォルダをNuget用のフォルダにするかを社内で決めておけば十分です。
Visual Studioのパッケージソースでそのフォルダを登録しておけば、Nugetがいい感じに一覧・バージョン表示してくれるので、発行したパッケージをホイホイそのフォルダにぶち込んでいけばOK。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】Noitom Hi5とPerception Neuron PROを使った全身トラッキング環境の構築(+モーション記録)

最初に

Hi5とPerception Neuron PROはVTuberハッカソン 全国ツアー2019に参加した際に借りた機材になります。
個人で所有していないため、本機材に関する質問には、返答できない場合があります。
ご了承ください。

本記事の目標

Unity上でVRM形式のモデルをNeuron、Hi5で動かし、そのモーションを記録する

環境

【PC】Windows10 1903
【Unity】2019.2f1

トラッカー

SDK

お借りしたモデル

お借りしたスクリプト

モデルインポート

モーショントラッキング関係

モーション記録関係

設定

Noitom Perception Neuron PRO SDK

  1. ダウンロードページからAXIS NEURON PROをダウンロード、インストールし起動する

  2. [File]->[Settings]->[Output Format]のBVH Dataの項目のDisplacementの項目のチェックを外す
    output_format.png

  3. [File]->[Settings]->[Broadcasting]のBVHのEnableの項目にチェックを入れる
    bvh_enable.png

  4. OKで設定を反映

Unity

モデルインポート

  1. UniVRMリリースページから最新版の.unitypackageをダウンロード

  2. UnityのプロジェクトにUnity Packageをインポート

  3. VRM形式のモデルデータをAssetフォルダ直下にコピペする

  4. UniVRMがUnity用にプレハブを作成する

Noitom Perception Neuron PRO

  1. Perception Neuron PRO、Hi5設定ツールをダウンロードし、解凍する

  2. 解凍後のフォルダのAssetフォルダから、
    ・Neuronフォルダ
    ・NotionHi5フォルダ
    ・calibration.unity
    をUnityプロジェクトのAsset直下にコピペする
    コピーデータ.png

  3. 動かしたいモデルにNeuronAnimator.csをアタッチする

  4. 設定完了
    気になる人はUnityを再生して、AXIS NEURON PRO上の動きがモデルに反映されているか確認する

Noitom Hi5

  1. 上記Noitom Perception Neuron PROの項目1.、2.を行っていない人は行う

  2. モデルの右手首、左手首にHi5_InertiaInstance.csをアタッチ

  3. それぞれを以下の画像のように設定する
    設定が面倒な方はHi5_InertiaInstance.csの設定が面倒な人向けの項目を確認してください
    左手の設定サンプル(右手はそれぞれを右手のモデルに読み替えてください)
    Hi5_InertiaInstance設定.png

Hi5_InertiaInstance.csの設定が面倒な人向け

自動設定機能を追加します。

  1. Hi5_InertiaInstance.csにhttps://gist.github.com/neon-izm/395709df5af70021490625e4c03e59bdより引用した以下のコードを加える
/// <summary>
/// モデルを変えたときに HandBone をセットし直すのが面倒だったため、自動的にアタッチしてくれる関数
/// HandBones[0] に対象モデルのRoot(Animatorがアタッチされてるオブジェクト)を入れてから実行する。
/// </summary>
[ContextMenu("Automatic Set HandBone")]
void AutomaticSetHandBone()
{
    if (HandBones[0] == null)
    {
        Debug.LogError("HandBones[0] にモデルのRoot(Animatorとかあるオブジェクト)を入れてください。");
        return;
    }

    //Animator からボーン情報を持ってきたいので、Animator を取得
    var animator = HandBones[0].GetComponent<Animator>();
    if (animator == null)
    {
        Debug.LogError("Animator が見つかりません。");
        return;
    }

    //念の為初期化
    HandBones = new Transform[(int) Bones.NumOfHI5Bones];

    //左手と右手で取得すべきボーンが違うから判定
    switch (HandType)
    {
        case Hand.LEFT:
            HandBones[1] = animator.GetBoneTransform(HumanBodyBones.LeftHand);
            HandBones[2] = animator.GetBoneTransform(HumanBodyBones.LeftThumbProximal);
            HandBones[3] = animator.GetBoneTransform(HumanBodyBones.LeftThumbIntermediate);
            HandBones[4] = animator.GetBoneTransform(HumanBodyBones.LeftThumbDistal);

            HandBones[6] = animator.GetBoneTransform(HumanBodyBones.LeftIndexProximal);
            HandBones[7] = animator.GetBoneTransform(HumanBodyBones.LeftIndexIntermediate);
            HandBones[8] = animator.GetBoneTransform(HumanBodyBones.LeftIndexDistal);

            HandBones[10] = animator.GetBoneTransform(HumanBodyBones.LeftMiddleProximal);
            HandBones[11] = animator.GetBoneTransform(HumanBodyBones.LeftMiddleIntermediate);
            HandBones[12] = animator.GetBoneTransform(HumanBodyBones.LeftMiddleDistal);

            HandBones[14] = animator.GetBoneTransform(HumanBodyBones.LeftRingProximal);
            HandBones[15] = animator.GetBoneTransform(HumanBodyBones.LeftRingIntermediate);
            HandBones[16] = animator.GetBoneTransform(HumanBodyBones.LeftRingDistal);

            HandBones[18] = animator.GetBoneTransform(HumanBodyBones.LeftLittleProximal);
            HandBones[19] = animator.GetBoneTransform(HumanBodyBones.LeftLittleIntermediate);
            HandBones[20] = animator.GetBoneTransform(HumanBodyBones.LeftLittleDistal);
            break;

        case Hand.RIGHT:
            HandBones[1] = animator.GetBoneTransform(HumanBodyBones.RightHand);
            HandBones[2] = animator.GetBoneTransform(HumanBodyBones.RightThumbProximal);
            HandBones[3] = animator.GetBoneTransform(HumanBodyBones.RightThumbIntermediate);
            HandBones[4] = animator.GetBoneTransform(HumanBodyBones.RightThumbDistal);

            HandBones[6] = animator.GetBoneTransform(HumanBodyBones.RightIndexProximal);
            HandBones[7] = animator.GetBoneTransform(HumanBodyBones.RightIndexIntermediate);
            HandBones[8] = animator.GetBoneTransform(HumanBodyBones.RightIndexDistal);

            HandBones[10] = animator.GetBoneTransform(HumanBodyBones.RightMiddleProximal);
            HandBones[11] = animator.GetBoneTransform(HumanBodyBones.RightMiddleIntermediate);
            HandBones[12] = animator.GetBoneTransform(HumanBodyBones.RightMiddleDistal);

            HandBones[14] = animator.GetBoneTransform(HumanBodyBones.RightRingProximal);
            HandBones[15] = animator.GetBoneTransform(HumanBodyBones.RightRingIntermediate);
            HandBones[16] = animator.GetBoneTransform(HumanBodyBones.RightRingDistal);

            HandBones[18] = animator.GetBoneTransform(HumanBodyBones.RightLittleProximal);
            HandBones[19] = animator.GetBoneTransform(HumanBodyBones.RightLittleIntermediate);
            HandBones[20] = animator.GetBoneTransform(HumanBodyBones.RightLittleDistal);
            break;

        default:
            Debug.LogError("HandType が不正なものです。");
            break;
    }
}

  1. 下図の通り、
    ・Hand Baseの要素0にVRMのアニメーターがついているオブジェクトを設定
    ・自動設定を実行
    ・追加の設定
    を行う
    hi5設定.gif

  2. Asset直下のcalibration.unityを再生し、Hi5のキャリブレーションを行う(スペースキーで画面遷移します)
    詳細な手順は以下の動画をご確認ください
    https://youtu.be/FN1wvcpdOjk?t=157

レコーディング環境

1.EasyMotionRecorderリリースページから最新版のunitypackageをダウンロード、プロジェクトにインポート

2.Asset -> EasyMotionRecorder -> Prefubs -> EasyMotionRecorderをシーンにドラッグアンドドロップ

3.オブジェクトEasyMotionRecorderのMotionDataRecoder.csのアニメーターにモデルをドラッグアンドドロップ

動作

上記設定の項目を行った状態で再生すると、Hi5とPerception Neuron PROを着用した人のモーションがモデルに反映されます。
デモ用に取ったモーションを再生したもの
demo.gif

レコーディング

記録

Unityを再生中にEasyMotionRecorderのMotionDataRecorder.csに設定したキーで録画開始、終了ができます。
記録されたモーションはAsset -> Resourcesに記録されます。

アニメーションに変換

そのままでは、アニメーターで再生できないので、アニメーションに変換します。
Asset -> Resourcesに記録された.assetファイルを選択し、下図の通りHumanoid Animation Clipとしてエクスポートします。
アニメーション変換.png
あとは、アニメーションコントローラーに張り付けて使用してください。

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity(C#)】Emptyなオブジェクトを可視化してEdit簡略化

Transformだけのオブジェクト

Create EmptyしたObjcetって文字通りメッシュも何もないので、
位置調整が必要なオブジェクトとして設定してしまった場合、微調整がけっこう大変ですよね。

AudioSourceだけのオブジェクトで音源の位置を変化させた場合なんかも、
視覚情報だけでデバッグしたいところです。

そこで、ギズモを描画する機能を使って編集、デバッグをしやすくします。

もうすでにきれいにまとめてくださっている方がいらっしゃったので、らくちんでした。
UnityのGizmosを使ってシーンビューで視覚的なデバッグの補助をする

コード

#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
/// <summary>
/// ギズモちゃん
/// </summary>
public class DrowGizmo : MonoBehaviour {

    [SerializeField]
    enum MODE
    {
        CUBE,
        WIRECUBE,
        SPHERE,
        WIRESPHERE
    }

    [SerializeField,Header("描画モード選択")]
    MODE m_mode;

    [SerializeField, Range(0,5),Header("ギズモの大きさ")]
    float m_gizmoSize = 0.3f;

    [SerializeField, Header("ギズモの色")]
    Color m_gizmoColor = new Color(1f, 0, 0, 0.3f);

    void OnDrawGizmos()
    {
        Gizmos.color = m_gizmoColor;

        Vector3 thisObjPos = this.gameObject.transform.position;
        switch (m_mode)
        {
            case MODE.CUBE:
                Gizmos.DrawCube(thisObjPos,Vector3.one*m_gizmoSize);
                break;

            case MODE.SPHERE:
                Gizmos.DrawSphere(thisObjPos, m_gizmoSize);
                break;

            case MODE.WIRECUBE:
                Gizmos.DrawWireCube(thisObjPos, Vector3.one * m_gizmoSize);
                break;

            case MODE.WIRESPHERE:
                Gizmos.DrawWireSphere(thisObjPos, m_gizmoSize);
                break;
        }

    }

    [CustomEditor(typeof(DrowGizmo))]
    public class CustomWindow : Editor
    {
        public override void OnInspectorGUI()
        {
            EditorGUILayout.BeginVertical(GUI.skin.box);
            {
                EditorGUILayout.HelpBox("ギズモでEditサポート", MessageType.Info);
            }
            EditorGUILayout.EndVertical();

            base.OnInspectorGUI();
        }
    }
}
#endif

上記コードをアタッチしたらギズモが表示されます。
gizumoDemo.PNG

OnDrawGizmos

OnDrawGizmos内に書いた処理はオブジェクトを選択していなくても表示されます。
基本こちらしか使わないかな~?と思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# LINQメソッド一覧

要素の取得(単一)

該当の要素が無い場合
  OrDefaultを付けていないメソッドは例外をスローする
  OrDefaultを付けたメソッドは型の既定値を返す

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.ElementAtOrDefault(8);
// -> 0

ElementAt

指定した位置(インデックス)にある要素を返す

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.ElementAt(2);
// -> 52

First

条件に合う最初の要素を返す

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.First(e => e > 15);
// -> 16

Last

条件に合う最後の要素を返す

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.Last(e => e > 15);
// -> 21

Single

唯一の要素を返す
該当する要素が複数ある場合例外をスローする

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.Single(e => e > 50);
// -> 52

  

要素の取得(複数)

Where

条件を満たす要素を全て返す

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.Where(e => e > 20);
// -> {52, 21}

Distinct

重複を除いたシーケンスを返す

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.Distinct();
// -> {11, 16, 52, 21, 8}

Skip

先頭から指定された数の要素をスキップし、残りのシーケンスを返す

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.Skip(3);
// -> {21, 8, 8}

SkipWhile

先頭から指定された条件を満たさなくなるまで要素をスキップし、残りのシーケンスを返す

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.Skip(e => e < 20);
// -> {52, 21, 8, 8}

Take

先頭から指定された数の要素を返す

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.Take(3);
// -> {11, 16, 52}

TakeWhile

先頭から指定された条件を満たす要素を返す

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.TakeWhile(e => e < 20);
// -> {11, 16}

  

集計

Max

最大値を返す

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.Max():
// -> 52

Min

最小値を返す

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.Min():
// -> 8

Average

平均値を返す

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.Average();
// -> 19.3333333333333

Sum

合計値を返す

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.Sum();
// -> 116

Count

要素数を返す

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.Count();
// -> 6

Aggregate

アキュムレータ関数で処理した結果を返す

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.Aggregate((now, next) => now + next); //やっている事はSUMと同じ
// -> 116

  

判定

All

全ての要素が条件を満たしているか判定

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.All(e => e > 10);
// -> False

Any

条件を満たす要素が含まれているか判定

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.Any(e => e > 10);
// -> True

Contains

指定した要素が含まれているか判定

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.Contains(20);
// -> False

SequenceEqual

2つのシーケンスが等しいかどうか判定

var source = new[] { 11, 16, 52, 21, 8, 8 };
var output = source.SequenceEqual(new[] { 11, 16, 21, 52, 8, 8});
// -> False

  

集合

Union

指定したシーケンスとの和集合を返す

var first = new[] { 11, 16, 52, 21, 8, 8 };
var second = new[] { 16, 21, 20, 3 };
var output = first.Union(second);
// -> {11, 16, 52, 21, 8, 20, 3}

Except

指定したシーケンスとの差集合を返す

var first = new[] { 11, 16, 52, 21, 8, 8 };
var second = new[] { 16, 21, 20, 3 };
var output = first.Except(second);
// -> {11, 52, 8}

Intersect

指定したシーケンスとの積集合を返す

var first = new[] { 11, 16, 52, 21, 8, 8 };
var second = new[] { 16, 21, 20, 3 };
var output = first.Intersect(second);
// -> {16, 21}

  

ソート

OrderBy

昇順にソートしたシーケンスを返す

var source = new[] {
    new { Name = "C#", Version = 7 },
    new { Name = "C++", Version = 17 },
    new { Name = "Java", Version = 9 },
    new { Name = "PHP", Version = 7 },
};
var output = source.OrderBy(e => e.Version);
// -> {{ Name = C#, Version = 7 },
//     { Name = PHP, Version = 7 },
//     { Name = Java, Version = 9 },
//     { Name = C++, Version = 17 }}

OrderByDescending

降順にソートしたシーケンスを返す

var source = new[] {
    new{ Name = "C#", Version = 7 },
    new{ Name = "C++", Version = 17 },
    new{ Name = "Java", Version = 9 },
    new{ Name = "PHP", Version = 7 },
};
var output = source.OrderByDescending(e => e.Version);
// -> {{ Name = C++, Version = 17 },
//     { Name = Java, Version = 9 },
//     { Name = C#, Version = 7 },
//     { Name = PHP, Version = 7 }}

ThenBy

ソートしたシーケンスに対し、キーが等しい要素同士を昇順にソートしたシーケンスを返す

var source = new[] {
    new{ Name = "C#", Version = 7 },
    new{ Name = "C++", Version = 17 },
    new{ Name = "Java", Version = 9 },
    new{ Name = "PHP", Version = 7 },
};
var output = source.OrderBy(e => e.Version)
                   .ThenBy(e => e.Name.Length);
// -> {{ Name = C#, Version = 7 },
//     { Name = PHP, Version = 7 },
//     { Name = Java, Version = 9 },
//     { Name = C++, Version = 17 }}

ThenByDescending

ソートしたシーケンスに対し、キーが等しい要素同士を降順にソートしたシーケンスを返す

var source = new[] {
    new { Name = "C#", Version = 7 },
    new { Name = "C++", Version = 17 },
    new { Name = "Java", Version = 9 },
    new { Name = "PHP", Version = 7 },
};
var output = source.OrderBy(e => e.Version)
                   .ThenByDescending(e => e.Name.Length);
// -> {{ Name = PHP, Version = 7 },
//     { Name = C#, Version = 7 },
//     { Name = Java, Version = 9 },
//     { Name = C++, Version = 17 }}

Reverse

逆順にソートしたシーケンスを返す

var source = new[] {
    new{Name = "C#", Version = 7},
    new{Name = "C++", Version = 17},
    new{Name = "Java", Version = 9},
    new{Name = "PHP", Version = 7},
};
var output = source.Reverse();
// -> {{ Name = PHP, Version = 7 },
//     { Name = Java, Version = 9 },
//     { Name = C++, Version = 17 },
//     { Name = C#, Version = 7 }}

  

射影

Select

1つの要素を単一の要素に射影する

var source = new[] {
    new { Name = "C#", Version = 7 },
    new { Name = "C++", Version = 17 },
    new { Name = "Java", Version = 9 },
    new { Name = "PHP", Version = 7 },
};
var output = source.Select(e => e.Name);
// -> {{ C#, C++, Java, PHP }

SelectMany

1つの要素から複数の要素に射影し、その結果を1つのシーケンスとして返す

var source = new[] {
    new { Name = "C#", Version = 7 },
    new { Name = "C++", Version = 17 },
    new { Name = "Java", Version = 9 },
    new { Name = "PHP", Version = 7 },
};
var output = source.SelectMany(e => e.Name);
// -> {{ C, #, C, +, +, J, a, v, a, P, H, P }

GroupBy

指定のキーで要素をグループ化し、そのキーとグループのシーケンスを返す

var source = new[] {
    new { Name = "C#", Version = 7 },
    new { Name = "C++", Version = 17 },
    new { Name = "Java", Version = 9 },
    new { Name = "PHP", Version = 7 },
};
var output = source.GroupBy(e => e.Version);
// -> { Key=7, Source={{ Name = C#, Version = 7 }, { Name = PHP, Version = 7 }},
//      Key=17, Source={{ Name = C++, Version = 17}},
//      Key=9, Source={{ Name = Java, Version = 9}}}

  

結合

Join

内部結合を行ったシーケンスを返す

var outer = new[] {
    new { Name = "C#", Version = 7 },
    new { Name = "C++", Version = 17 },
    new { Name = "Java", Version = 9 },
    new { Name = "PHP", Version = 7 },
};
var inner = new[] {
    new { Name = "C#", DesignedBy = "Microsoft" },
    new { Name = "Java", DesignedBy = "Sun Microsystems" },
    new { Name = "Java", DesignedBy = "Oracle" },
};
var output = outer.Join(inner,
                        o => o.Name,
                        i => i.Name, 
                        (o, i) => new { o.Name, o.Version, i.DesignedBy});
// -> {{ Name = C#, Version = 7, DesignedBy = Microsoft },
//     { Name = Java, Version = 9, DesignedBy = Sun Microsystems },
//     { Name = Java, Version = 9, DesignedBy = Oracle }}

GroupJoin

左部結合を行って指定のキーでグループ化し、そのキーとグループのシーケンスを返す

var outer = new[] {
    new { Name = "C#", Version = 7 },
    new { Name = "C++", Version = 17 },
    new { Name = "Java", Version = 9 },
    new { Name = "PHP", Version = 7 },
};
var inner = new[] {
    new { Name = "C#", DesignedBy = "Microsoft" },
    new { Name = "Java", DesignedBy = "Sun Microsystems" },
    new { Name = "Java", DesignedBy = "Oracle" },
};
var output = outer.GroupJoin(inner,
                        o => o.Name,
                        i => i.Name, 
                        (o, i) => new { o.Name, o.Version, DesignedBy = i.Select(e => e.DesignedBy)});
// -> {{ Name = C#, Version = 7, DesigndBy = {Microsoft} },
//     { Name = C++, Version = 17, DesigndBy = {} },
//     { Name = Java, Version = 9, DesigndBy = {Sun Microsystems, Oracle} },
//     { Name = PHP, Version = 7, DesigndBy = {} }}

Concat

2つのシーケンスを連結する

var outer = new[] {
    new { Name = "C#", Version = 7 },
    new { Name = "C++", Version = 17 },
    new { Name = "Java", Version = 9 },
    new { Name = "PHP", Version = 7 },
};
var outer2 = new[] {
     new { Name = "Swift", Version = 4 },
     new { Name = "Visual Basic", Version = 6 },
};
var output = outer.Concat(outer2);
// -> {{ Name = C#, Version = 7 },
//     { Name = C++, Version = 17 },
//     { Name = java, Version = 9 },
//     { Name = PHP, Version = 7 },
//     { Name = Swift, Version = 4},
//     { Name = Visual Basic, Version = 6}}

DefaultIfEmpty

シーケンスを返す
シーケンスが空の場合、既定値もしくは任意の要素を返す

var outer = new[] {
    new { Name = "C#", Version = 7 },
    new { Name = "C++", Version = 17 },
    new { Name = "Java", Version = 9 },
    new { Name = "PHP", Version = 7 },
};
var output = outer.DefaultIfEmpty();
// -> {{ Name = C#, Version = 7 },
//     { Name = C++, Version = 17 },
//     { Name = java, Version = 9 },
//     { Name = PHP, Version = 7 }}

Zip

指定した関数で、2つのシーケンスを1つのシーケンスにマージする

var outer = new[] {
    new { Name = "C#", Version = 7 },
    new { Name = "C++", Version = 17 },
    new { Name = "Java", Version = 9 },
    new { Name = "PHP", Version = 7 },
};
var outer2 = new[] {
     new { Name = "Swift", Version = 4 },
     new { Name = "Visual Basic", Version = 6 },
};
var output = outer.Zip(outer2, (o1, o2) => o1.Name + "&" + o2.Name);
// -> { C#&Swift, C++&VisualBasic }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

今更聞けないモンテカルロ法(円周率編)

カジノを潰したという都市伝説を持つモンテカルロ法で、円周率を求めます。
C#ですけれど、ソースコードはこちらにあります。
https://github.com/ma3100/AlgorithmPrimer/blob/master/FirstAlgorithmForCSharp/First/MonteCalro.cs
※随時追加予定

モンテカルロ法とは?

モンテカルロ法は、問題を数値計算で解くのではなく確率を利用して解く事です。

例えば今回テーマにしている円周率の場合、「円周の長さと直径の比」から一生懸命計算する訳ですが、モンテカルロ法は上記の計算を必要としません。

そのため、今回学ぶ方法で求める円周率は正確な値ではないのですが、導入としては適しているのでその辺りは割り切ってください。

モンテカルロ法で円周率を求める際の考え方

※半径が1の円で説明します。

  1. 円周率を求めたい円を四分割し、直径と同じ辺を持つ正方形で1/4の円を囲みます。
  2. 1で出来た範囲の中に一様実数乱数を発生させます。3つしか点を打っていませんが、以下の様な状態になります。
    スクリーンショット 2019-10-21 14.03.21.png

  3. 一様実数乱数であれば、「1/4の円の中に収まる乱数と全ての乱数」の比は、「1/4の円の面積と正方形の面積の比」と同じになります。

1/4の円の面積 : 正方形の面積 = 1/4の円の中に収まる乱数の数 : 全ての乱数の数
1/4の円の中に収まる乱数の数をa,円の外に出た乱数をbと置くと、
円周率/4 : 1 = a : a + b
円周率 = 4a / (a + b)
= 4a / n(発生した乱数の総数)

実装(C#)

pymonte.cs
        const int RANDOMNUM = 10000;

        void GetMonteCalro()
        {
            double x, y, montePai;
            var random = new Random();
            var inCircle = 0;
            foreach(var i in Enumerable.Range(0,RANDOMNUM))
            {
                x = random.NextDouble();
                y = random.NextDouble();

                if(x * x + y * y <= 1)
                {
                    inCircle++;
                }
            }

            montePai = (double)4 * inCircle / RANDOMNUM;
            Console.WriteLine($"Answer:{montePai}");
        }

※x^2 + y ^2 が半径を超えないのであれば、円の中にあると見なせます。

例えばRANDOMNUMの値を100などにすると、かなり怪しい結果になるのですがこの値を大きくしていくと、だんだんと3.14に近しい数になっていきます。

今回は以上です、次はモンテカルロ法を用いて円の面積を求めていきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rust/tokioで非同期チャットサーバー

TL;DR

  • Rust/tokioで非同期チャットサーバーを作ってみました。
  • 比較のために、同期サーバー作ってみました。
  • テストしてパフォーマンスを比較しました。
  • 思った通り、非同期の方がCPU負荷が少なく、同じ処理を実行できました。
  • ついでに、C#でも非同期サーバーを実装して、比較しました。
  • Rustの方が速そうでした。

※若干説明が正確でないところがあります。正しい理解のためには、公式ドキュメントなど参照してください。

Rustとは

  • GCなしで、安全なメモリ管理
  • 実行時コストなしで、抽象化
  • 関数型言語からの影響

「プログラミング言語「Rust」は、「C」や「C++」並みのパフォーマンスと、開発者が墓穴を掘るのを防ぐ仕組みとを兼ね備えたプログラミング言語だ」

「プログラミング言語「Rust」--2019年こそ学ぶべき7つの理由」
https://japan.techrepublic.com/article/35131764.htm

Tokioとは

スレッドプールを使って、タスクを並列にいい感じに実行してくれるやつです。
tokio.png

※半年ほど前に作ったので、std::futureが入る前の古い方のtokioです。

tokio = "0.1.20"
futures = "0.1.25"

Tokioを使って非同期なチャットサーバーを作ってみよう

設計はこんな感じ。
送受信と入室処理がそれぞれ別タスク(別スレッド)で動く。
Channelとは、スレッドセーフなキューだと思ってもらえればOKです。
rust-chat-design.png

比較のために、同期のチャットも作ってみる
設計はこんな感じ。
送受信も入室処理も全部同じタスクで処理。
何もすることがない時は、ループがぐるぐる回る。
sync-chat.png

パフォーマンス測定

  • 50人が同時にアクセス
  • サーバーは、1部屋10人ずつでルームを作る
  • CLはそれぞれ、10〜100ミリ秒ずつランダムに間を開けながら、20〜200バイトのデータを繰り返し送信

結果:同期の場合

result-sync.png

CPU使用率が上がったり下がったり。誰も喋っていない時、ループをぐるぐる回っているので、その間CPUが100%になっている

結果:非同期の場合

result-async.png

いい感じにCPU使用率が安定している。計画どおり ( ̄ー ̄)ニヤリ

C#で非同期もやってみた

result-async-cs.png

CPU使用率はRustと同じくらい。でも、ネットワークの速度がRustより半分くらい遅い。
やっぱりRustは速い!ということに違いない。

わかったこと

  • 非同期の方がCPUを効率よく使える(あたりまえですが・・)
  • C#よりRustで書いた方が速度が出る

C#は async/awaitが使えるので非同期も簡単に書ける。
Rustももうすぐ async/awaitが来るので期待大。

Rustを使って良かったところ

  • トレイトを使ってソケットの送受信の機能を抽象化。Room側のソースはTCP/WebSocket を意識しないで書ける。しかも抽象化による実行時コストがゼロ。
  • Futureトレイトを使うことで、タスクが書きやすい(でも async/await があればもっと書きやすくなるはず)

Rustを使ってつらかったところ

  • Future使った時に型推論がどう動くのかが理解できずに苦しんだ
  • ドキュメントがあまり整備されてない
  • サンプルが少ない

今回つかったソース

サーバー:
https://github.com/mas-yo/rustgs/tree/lt20190605
テストクライアント:
https://github.com/mas-yo/rustgs-testcl/tree/lt20190605

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rustで非同期チャットサーバー

TL;DR

  • Rustで非同期チャットサーバーを作ってみました。
  • 比較のために、同期サーバー作ってみました。
  • テストしてパフォーマンスを比較しました。
  • 思った通り、非同期の方がCPU負荷が少なく、同じ処理を実行できました。
  • ついでに、C#でも非同期サーバーを実装して、比較しました。
  • Rustの方が速そうでした。

※若干説明が正確でないところがあります。正しい理解のためには、公式ドキュメントなど参照してください。

Rustとは

  • GCなしで、安全なメモリ管理
  • 実行時コストなしで、抽象化
  • 関数型言語からの影響

「プログラミング言語「Rust」は、「C」や「C++」並みのパフォーマンスと、開発者が墓穴を掘るのを防ぐ仕組みとを兼ね備えたプログラミング言語だ」

「プログラミング言語「Rust」--2019年こそ学ぶべき7つの理由」
https://japan.techrepublic.com/article/35131764.htm

Tokioとは

スレッドプールを使って、タスクを並列にいい感じに実行してくれるやつです。
tokio.png

※半年ほど前に作ったので、std::futureが入る前の古い方のtokioです。

tokio = "0.1.20"
futures = "0.1.25"

Tokioを使って非同期なチャットサーバーを作ってみよう

設計はこんな感じ。
送受信と入室処理がそれぞれ別タスク(別スレッド)で動く。
Channelとは、スレッドセーフなキューだと思ってもらえればOKです。
rust-chat-design.png

比較のために、同期のチャットも作ってみる
設計はこんな感じ。
送受信も入室処理も全部同じタスクで処理。
何もすることがない時は、ループがぐるぐる回る。
sync-chat.png

パフォーマンス測定

  • 50人が同時にアクセス
  • サーバーは、1部屋10人ずつでルームを作る
  • CLはそれぞれ、10〜100ミリ秒ずつランダムに間を開けながら、20〜200バイトのデータを繰り返し送信

結果:同期の場合

result-sync.png

CPU使用率が上がったり下がったり。誰も喋っていない時、ループをぐるぐる回っているので、その間CPUが100%になっている

結果:非同期の場合

result-async.png

いい感じにCPU使用率が安定している。計画どおり ( ̄ー ̄)ニヤリ

C#で非同期もやってみた

result-async-cs.png

CPU使用率はRustと同じくらい。でも、ネットワークの速度がRustより半分くらい遅い。
やっぱりRustは速い!ということに違いない。

わかったこと

  • 非同期の方がCPUを効率よく使える(あたりまえですが・・)
  • C#よりRustで書いた方が速度が出る

C#は async/awaitが使えるので非同期も簡単に書ける。
Rustももうすぐ async/awaitが来るので期待大。

Rustを使って良かったところ

  • トレイトを使ってソケットの送受信の機能を抽象化。Room側のソースはTCP/WebSocket を意識しないで書ける。しかも抽象化による実行時コストがゼロ。
  • Futureトレイトを使うことで、タスクが書きやすい(でも async/await があればもっと書きやすくなるはず)

Rustを使ってつらかったところ

  • Future使った時に型推論がどう動くのかが理解できずに苦しんだ
  • ドキュメントがあまり整備されてない
  • サンプルが少ない

今回つかったソース

サーバー:
https://github.com/mas-yo/rustgs/tree/lt20190605
テストクライアント:
https://github.com/mas-yo/rustgs-testcl/tree/lt20190605

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#で、文字と図形からなるWindowsアイコン(.Ico)【透過】を作成するプログラムを作ってみた

やれること

こんな感じのWindowsアイコンが作れる。
UIを作りこむのが大変なので、下記のソースコード(EasyDrawIcon.cs)を直接修正して使う方向で。

image.png

注意事項

作ったアイコンについては著作権に注意してください。特にフォントには著作権があります。
どのフォントがセーフなのかは自分はわかりません。

ソースコード

以前のコードをちょっとリファクタリングして、アイコンデータ作成部分をIconUtility.csとして分離した。

ソースコード - IconUtility.cs
IconUtility.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;


namespace IconUtility
{
    public class Icons
    {
        class IconEntry
        {
            [StructLayout(LayoutKind.Sequential)]
            public struct IconDir
            {
                public short  icoReserved; // must be 0
                public short  icoResourceType; //must be 1 for icon
                public short  icoResourceCount;

                public IconDir(int n)
                {
                    icoReserved = 0;
                    icoResourceType = 1;
                    icoResourceCount = (short)n;
                }
            }

            [StructLayout(LayoutKind.Sequential)]
            public struct IconDirEntry
            {
                byte   _Width;
                byte   _Height;
                public byte   ColorCount;
                public byte   reserved1;
                public short  reserved2;
                public short  reserved3;
                public int    icoDIBSize;
                public int    icoDIBOffset;

                public int Width{get{return (_Width>0)?_Width:256;}}
                public int Height{get{return (_Height>0)?_Height:256;}}

                public IconDirEntry(int w, int h)
                {
                    if ( w<0 || w>256 || h<0 || h>256 ) {
                        throw new Exception("Size parameter error");
                    }
                    _Width  = (byte)w;
                    _Height = (byte)h;
                    ColorCount=0;
                    reserved1=0;
                    reserved2=0;
                    reserved3=0;
                    icoDIBSize=0;
                    icoDIBOffset=0;
                }
            }

            [StructLayout(LayoutKind.Sequential)]
            struct BitmapInfoHeader
            {
                public int    biSize; // must be 40
                public int    biWidth;
                public int    biHeight;
                public short  biPlanes; // must be 1
                public short  biBitCount; // color
                public int    biCompression; // 0:not compress
                public int    biSizeImage;
                public int    biXPixPerMeter;
                public int    biYPixPerMeter;
                public int    biClrUsed;
                public int    biClrImportant;

                public BitmapInfoHeader(int w, int h)
                {
                    biSize = 40;
                    biWidth = w;
                    biHeight = h*2; // 本体とmaskを含むため2倍とする決まりらしい
                    biPlanes = 1;
                    biBitCount = 32;
                    biCompression=0;
                    biSizeImage=0;
                    biXPixPerMeter=0;
                    biYPixPerMeter=0;
                    biClrUsed=0;
                    biClrImportant=0;
                }
            }

            IconDirEntry     iconDirEntry;
            BitmapInfoHeader bitmapInfoHeader;
            byte[] bitmapBody;
            byte[] bitmapMask;

            public System.Drawing.Size Size{get{return new System.Drawing.Size(iconDirEntry.Width, iconDirEntry.Height);}}
            public int Width{get{return iconDirEntry.Width;}}
            public int Height{get{return iconDirEntry.Height;}}
            int BitPerPixel{get{return bitmapInfoHeader.biBitCount;}}

            int CalcDIBSize()
            {
                return Marshal.SizeOf(typeof(BitmapInfoHeader)) + bitmapBody.Length + bitmapMask.Length;
            }

            public int UpdateIconDirEntry(int icoDIBOffset)
            {
                iconDirEntry.icoDIBOffset = icoDIBOffset;
                iconDirEntry.icoDIBSize   = CalcDIBSize();
                return iconDirEntry.icoDIBSize;
            }

            public void WriteIconDirEntryTo(BinaryWriter writer)
            {
                Icons.CopyDataToByteArray<IconDirEntry>(writer, iconDirEntry);
            }

            public void WriteDataTo(BinaryWriter writer)
            {
                Icons.CopyDataToByteArray<BitmapInfoHeader>(writer, bitmapInfoHeader);
                writer.Write(bitmapBody);
                writer.Write(bitmapMask);
            }

            IconEntry(IconDirEntry _iconDirEntry, BitmapInfoHeader _bitmapInfoHeader, byte[] _bitmapBody, byte[] _bitmapMask)
            {
                iconDirEntry = _iconDirEntry;
                bitmapInfoHeader = _bitmapInfoHeader;
                bitmapBody = _bitmapBody;
                bitmapMask = _bitmapMask;
            }

            // 本体
            public IconEntry(Bitmap bmp, Color? alphaColor)
            {
                int w = bmp.Width;
                int h = bmp.Height;

                if ( w>256 || h>256 ) {
                    throw new Exception("size parameter error");
                }

                iconDirEntry = new IconDirEntry(w, h);
                bitmapInfoHeader = new BitmapInfoHeader(w, h);
                bitmapBody = new byte[Icons.GetBitmapBodySize(w, h, bitmapInfoHeader.biBitCount)];
                bitmapMask = new byte[Icons.GetBitmapMaskSize(w, h)];

                Draw32bppBitmapToData(bmp, alphaColor);
            }

            void Draw32bppBitmapToData(Bitmap bmp, Color? alphaColor)
            {
                Array.Clear(bitmapMask, 0, bitmapMask.Length);
                Array.Clear(bitmapBody, 0, bitmapBody.Length);

                BitmapData bd = bmp.LockBits(new Rectangle(0,0,bmp.Width,bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

                try {
                    IntPtr ptr = bd.Scan0;
                    byte[] pixels = new byte[bd.Stride * bmp.Height];
                    Marshal.Copy(ptr, pixels, 0, pixels.Length);

                    int maskStride = (((Width+7)/8+3)/4)*4;
                    int icoStride = Width*4;

                    for (int y = 0; y < bd.Height; y++) {
                        for (int x = 0; x < bd.Width; x++) {
                            int posIco = y * icoStride + 4*x;

                            int bytePosMask = y * maskStride + x/8;
                            int bitPosMask = 7-(x%8);

                            int pos = (bd.Height-1-y) * bd.Stride + x * 4;
                            bitmapBody[posIco  ] = pixels[pos];   //blue;
                            bitmapBody[posIco+1] = pixels[pos+1]; //green;
                            bitmapBody[posIco+2] = pixels[pos+2]; //red;
                            bitmapBody[posIco+3] = pixels[pos+3]; //alpha

                            if ( pixels[pos+3] == 0 ||
                            (alphaColor != null &&
                                pixels[pos]  ==alphaColor.Value.B &&
                                pixels[pos+1]==alphaColor.Value.G &&
                                pixels[pos+2]==alphaColor.Value.R  )) {
                                //bitmapMask[bytePosMask] |= (byte)(1<<bitPosMask);
                                // 32bit色のiconだとmaskではなくalpha channelが使用されるっぽい
                                bitmapBody[posIco+3] = 0x00;
                            }
                        }
                    }
                }
                finally {
                    bmp.UnlockBits(bd);
                }
            }
        }

        int UpdateIconDirEntries()
        {
            iconDir.icoResourceCount = (short)iconEntries.Count;

            int offset  =  Marshal.SizeOf(typeof(IconEntry.IconDir))  +  iconEntries.Count * Marshal.SizeOf(typeof(IconEntry.IconDirEntry));

            for (int i=0;i<iconEntries.Count;i++) {
                offset += iconEntries[i].UpdateIconDirEntry(offset);
            }
            return offset;
        }

        static int GetBitmapBodySize(int w, int h, int bitCount)
        {
            return ((((w*bitCount + 7)/8)+3)/4)*4 * h;
        }

        static int GetBitmapMaskSize(int w, int h)
        {
            return ((((w+7)/8)+3)/4)*4 * h;
        }

        static TStruct CopyDataToStruct<TStruct> (BinaryReader reader) where TStruct : struct
        {
            var size = Marshal.SizeOf(typeof(TStruct));
            var ptr = IntPtr.Zero;

            try {
                ptr = Marshal.AllocHGlobal(size);
                Marshal.Copy(reader.ReadBytes(size), 0, ptr, size);
                return (TStruct)Marshal.PtrToStructure(ptr, typeof(TStruct));
            }
            finally {
                if (ptr != IntPtr.Zero) {
                    Marshal.FreeHGlobal(ptr);
                }
            }
        }

        static void CopyDataToByteArray<TStruct>(BinaryWriter writer, TStruct s) where TStruct : struct
        {
            var size = Marshal.SizeOf(typeof(TStruct));
            var buffer = new byte[size];
            var ptr = IntPtr.Zero;

            try {
                ptr = Marshal.AllocHGlobal(size);
                Marshal.StructureToPtr(s, ptr, false);
                Marshal.Copy(ptr, buffer, 0, size);
            }
            finally {
                if (ptr != IntPtr.Zero) {
                    Marshal.FreeHGlobal(ptr);
                }
            }
            writer.Write(buffer);
        }



        IconEntry.IconDir iconDir;
        List<IconEntry> iconEntries;

        // ------------------------------------------------------------------------------
        // public members

        public int Count {get{return iconEntries.Count;}}

        public Icons()
        {
            iconDir      = new IconEntry.IconDir(0);
            iconEntries  = new List<IconEntry>();
        }

        public void AddIcon(Bitmap bmp, Color alphaColor)
        {
            iconEntries.Add(new IconEntry(bmp, alphaColor));
            UpdateIconDirEntries();
        }

        public void AddIcon(Bitmap bmp)
        {
            iconEntries.Add(new IconEntry(bmp, null));
            UpdateIconDirEntries();
        }

        public bool SaveToFile(string path)
        {
            //int size = 
            UpdateIconDirEntries();

            using ( var fs = new FileStream(path, FileMode.Create) ) {
                using ( var writer = new BinaryWriter(fs) ) {
                    CopyDataToByteArray<IconEntry.IconDir>(writer, iconDir);

                    foreach(var t in iconEntries) {
                        t.WriteIconDirEntryTo(writer);
                    }

                    foreach(var t in iconEntries) {
                        t.WriteDataTo(writer);
                    }
                }
            }

            return true;
        }
    }
}

ソースコード - EasyDrawIcon.cs
EasyDrawIcon.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using IconUtility;



class EasyDraw:Form
{
    PictureBox pct;
    const int MAX_SIZE = 64;
    const int CANVAS_SIZE = 512;
//    const int ZOOM = 2;

    Font curFont;
    int curFontStyle;
    Pen curPen;
    Brush curBrush;
    PointF curPoint;
    GraphicsPath curPath;
    Graphics curG;

    const string DefaultFontName = "メイリオ";  // フォントを変える場合はここを変更

    EasyDraw()
    {
        curPen = Pens.Black;
        curBrush = Brushes.White;
        curPath = new GraphicsPath();
        curFont = new Font(DefaultFontName, 30.0f);  // フォントサイズを変える場合はここを変更

        if (curFont.Name != DefaultFontName) {
            MessageBox.Show("フォント \""+DefaultFontName+"\"の取得に失敗しました。\r\n"
                          + "代わりにフォント\"" + curFont.Name + "\"が使用されます。\r\n");
        }
        MessageBox.Show("アイコンに文字を使う場合は、フォントの著作権に注意してください。");

        curFontStyle = (int)FontStyle.Regular; // Bold , Italic , Strikeout , Underline の bitOR を設定可能

        pct = new PictureBox();
        pct.Image = new Bitmap(CANVAS_SIZE, CANVAS_SIZE);
        pct.Dock = DockStyle.Fill;
        Controls.Add(pct);

        ClientSize = new Size(CANVAS_SIZE, CANVAS_SIZE);

        Load+=(sender,e)=>{Draw();};
    }

    void moveto(float x, float y)
    {
        curPoint.X = x;
        curPoint.Y = y;
    }

    void rmoveto(float rx, float ry)
    {
        curPoint.X += rx;
        curPoint.Y += ry;
    }

    void lineto(float x, float y)
    {
        curPath.AddLine(curPoint.X, curPoint.Y, x, y);
        curPoint.X = x;
        curPoint.Y = y;
    }

    void rlineto(float rx, float ry)
    {
        curPath.AddLine(curPoint.X, curPoint.Y, curPoint.X+rx, curPoint.Y+ry);
        curPoint.X += rx;
        curPoint.Y += ry;
    }

    void charpath(string s)
    {
        var sf = new StringFormat();
        sf.Alignment     = StringAlignment.Center;    // 横方向の中央
        sf.LineAlignment = StringAlignment.Center;    // 縦方向の中央
        FontFamily ff = curFont.FontFamily;
        curPath.AddString(s, ff, curFontStyle, curFont.Size, curPoint, sf);
    }

    void newpath()
    {
        curPath.Reset();
        curPath.StartFigure();
    }

    void closepath()
    {
        curPath.CloseFigure();
    }

    void stroke()
    {
        if ( curG != null ) {
            curG.DrawPath(curPen, curPath);
        }
    }
    void fill()
    {
        if ( curG != null ) {
            curPath.FillMode = FillMode.Winding;
            curG.FillPath(curBrush, curPath);
        }
    }
    void eofill()
    {
        if ( curG != null ) {
            curPath.FillMode = FillMode.Alternate;
            curG.FillPath(curBrush, curPath);
        }
    }


    float cos(float degree)
    {
        return (float)Math.Cos((degree/180.0)*Math.PI);
    }
    float sin(float degree)
    {
        return (float)Math.Sin((degree/180.0)*Math.PI);
    }


    void Draw()
    {
        Bitmap bmp = CreateTransparentBitmap(MAX_SIZE, MAX_SIZE);
        curG = Graphics.FromImage(bmp);
        curG.SmoothingMode = SmoothingMode.AntiAlias;

        try {
            // ---- ここの中を変更すればよい
            curBrush = new LinearGradientBrush(new Point(0, 0), new Point(64, 64), Color.Red, Color.Yellow);

            newpath();
            for ( int deg=0 ; deg<360 ; deg+=60 ) {
                float x = 32+31*cos(deg);
                float y = 32+31*sin(deg);
                if ( deg == 0 ) {
                    moveto(x,y);
                }
                else {
                    lineto(x,y);
                }
            }
            closepath();
            fill();

            curBrush = new LinearGradientBrush(new Point(0, 0), new Point(64, 64), Color.Blue, Color.Black);
            newpath();
            moveto(32,36); // (32,32)が中央だが、文字列描画位置がずれるので微調整
            charpath("C#");
            closepath();
            fill();

            // ----
        }
        finally {
            curG.Dispose();
            curG = null;
        }

        int zoom = 2;
        Graphics g = Graphics.FromImage(pct.Image);
        g.FillRectangle(Brushes.White, 0, 0, pct.Image.Width, pct.Image.Height);
        g.DrawImage(bmp, 0, 0, bmp.Width*zoom, bmp.Height*zoom);
        g.Dispose();


        Icons icons;
        icons = new Icons();
        icons.AddIcon(bmp);
        icons.SaveToFile("Output.ico");

    }

    // 透過色で初期化
    Bitmap CreateTransparentBitmap(int width, int height)
    {
        Bitmap bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
        BitmapData bd = bmp.LockBits(new Rectangle(0,0,width,height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);

        try {
            unsafe {
                // 書き込み
                byte* ptr = (byte*)bd.Scan0;
                for ( int y=0 ; y<height ; y++ ) {
                    for ( int x=0 ; x<width ; x++ ) {
                        ptr[y*bd.Stride + 4*x    ] = 0;// B
                        ptr[y*bd.Stride + 4*x + 1] = 0;// G
                        ptr[y*bd.Stride + 4*x + 2] = 0;// R
                        ptr[y*bd.Stride + 4*x + 3] = 0;// alpha = 0 (透過)
                    }
                }
            }
        }
        finally {
            bmp.UnlockBits(bd);
        }

        return bmp;
    }


    [STAThread]
    static void Main()
    {
        Application.Run(new EasyDraw());
    }
}

メソッド名について

PostScript言語をまねてみた。

コンパイル方法

csc /unsafe EasyDrawIcon.cs IconUtility.cs

アイコンのキャッシュを更新する方法

同じファイル名のアイコンをつくると、アイコンの表示が更新されない。

コマンドプロンプトなどで
Windows 10では「ie4uinit.exe -show」、
それ以外のバージョンでは「ie4uinit.exe -ClearIconCache
を実行すればよい。

参考サイト: https://news.mynavi.jp/article/20180131-windows_icon/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ハードエッジでも縁崩れしない理想的なアウトラインシェーダを作ってみた

はじめに

仕事でUnityゲーム開発しているyoship1639です。
ハードエッジでもこの様に縁崩れせずにアウトラインを奇麗に出すシェーダを思いついたので公開します。

edge003.png

縁取りをするシェーダは多々ありますが、特定のパターンで上手くいかない場合があります。
それぞれ代表的な既存のアウトラインシェーダの特徴と、上手くいかないパターンは以下の通りです。

  • モデルを拡大する手法
    特徴:一般的な手法。1パス目で拡大したモデルをアウトライン色で描画し、2パス目で通常描画する
    ダメなパターン:ハードエッジモデルの場合やモデルの中心(各頂点の中心)が0じゃない場合に破綻する

  • ステンシルバッファを使う手法
    特徴:これも一般的な手法。1パス目で拡大したモデルをステンシルバッファに描画し、2パス目で通常描画する。ポストプロセス等でアウトラインに色を付ける
    ダメなパターン:モデルを拡大する手法と同様

  • 法線を使う手法
    特徴:カメラ視点から見てモデルの法線がほぼ垂直になる(内積がほぼ0になる)ピクセルをアウトライン色にする手法
    ダメなパターン:アウトラインが超絶汚い

  • 深度バッファを使う手法
    特徴:深度バッファを使って深度が急変する箇所をアウトラインとみなす手法。
    ダメなパターン:シーン全体にアウトラインがかかってしまう

各手法の細かい説明等はこちらを参考にしてください
【Unity】【シェーダ】4種のアウトライン描画方法とその特徴

と、上記の様に一長一短があります。

理想的なのは、特定のハードエッジなモデルでもきれいなアウトラインを表示することです。
そこで、上記のダメなパターンを払拭したアウトラインシェーダを思いついたので解説したいと思います。

アルゴリズム解説

今回思いついたのは、GrabPassを用いたアウトラインシェーダです。
GrabPassは簡単に説明すると、直前までのパスの描画結果を背景含めてテクスチャとして出力することです。Grabテクスチャはその描画結果のテクスチャを指します。

アルゴリズム概要は以下の通りです。

  1. 【1パス目】モデルをシーン上で絶対使わないような色(ダミー色)でソリッド描画する
  2. 1の結果をGrabPassとして2パス目に渡す
  3. 【2パス目】ピクセルシェーダでGrabテクスチャのUV値付近をアウトライン幅で一定数サンプリングする
  4. サンプリングの結果ダミー色が含まれていなかった場合、アウトライン付近と判断しアウトライン色で描画する。それ以外は通常描画する。

上記のアルゴリズムでモデルを描画すると、ハードエッジであってもきれいにアウトラインを描画することができます。

それぞれ説明します。

1. 【1パス目】でモデルをシーン上で絶対使わないような色(ダミー色)でソリッド描画する

まず、1パス目でアウトライン描画したいモデルをシーン上で絶対使わないような色でモデルを膨らませずに塗りつぶし描画します。なぜシーン上で絶対使わないような色でないといけないかというと、Grabテクスチャの背景と被る色だとアウトラインが破綻する可能性があるからです。この色をダミー色と呼んでおきます。アウトライン色ではありませんのでご注意。

使いやすいダミー色はrgb(255,0,255)なので、この色を使います。

Pass
{
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag

    #include "UnityCG.cginc"

    struct appdata
    {
        half4 vertex : POSITION;
    };

    struct v2f
    {
        half4 vertex : SV_POSITION;
    };

    v2f vert(appdata v)
    {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        return o;
    }

    fixed4 frag(v2f i) : SV_Target
    {
        return fixed4(1.0, 0.0, 1.0, 0);
    }
    ENDCG
}

2. 1の結果をGrabPassとして2パス目に渡す

これは特に何も考えずにシェーダコード内でGrabPass {}と書けば大丈夫です。これで1パス目の結果をテクスチャとして2パス目に渡すことができます。

3. 【2パス目】ピクセルシェーダでGrabテクスチャのUV値付近をアウトライン幅で一定数サンプリングする

ここが今回の手法のミソです。
頂点シェーダはいつもの感じに行いますが、ピクセルシェーダは少し違います。

まず、GrabPassで入手したGrabテクスチャのピクセルのUV値を算出します。これは頂点シェーダでComputeGrabScreenPosを呼べばUnityが勝手に算出してくれます。このUV値のサンプリング結果はモデルのピクセル値と一致します。(つまりrgb(255,0,255)です)

UV値を算出したら、そのUV値の付近をアウトライン幅分ずらして一定数サンプリングします。一定数というのはアウトラインと判定するのに十分な数です。今回の手法は6方向分サンプリングすれば十分です。

#define SAMPLE_NUM 6
#define SAMPLE_INV 0.16666666
#define PI2 6.2831852
#define EPSILON 0.001
#define DUMMY_COLOR fixed3(1.0, 0.0, 1.0)

sampler2D _GrabTexture;
half _OutlineWidth;

v2f vert(appdata v)
{
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.grabPos = ComputeGrabScreenPos(o.pos);

    return o;
}

fixed4 frag(v2f i) : SV_Target
{
    // サンプリングのオフセット(アウトラインの幅)
    half2 delta = (1 / _ScreenParams.xy) * _OutlineWidth;

    int edge = 0;
    [unroll]
    for (int j = 0; j < SAMPLE_NUM && edge == 0; j++)
    {
        // オフセット分ずらしてサンプリング
        fixed4 tex = tex2D(_GrabTexture, i.grabPos.xy / i.grabPos.w + half2(sin(SAMPLE_INV * j * PI2) * delta.x, cos(SAMPLE_INV * j * PI2) * delta.y));
        // ダミー色と同でないならアウトラインであると判定
        edge += distance(tex.rgb, DUMMY_COLOR) < EPSILON ? 0 : 1;
    }
    ...     

4. サンプリングの結果ダミー色が含まれていなかった場合、アウトライン付近と判断しアウトライン色で描画する。それ以外は通常描画する。

edge002.png

サンプリングした結果、ダミー色しか含まれていない場合はアウトライン付近ではないと判断し通常のモデル描画を行います。
ダミー色以外が1つ以上含まれていた場合は、アウトライン付近であると判断できるので、アウトライン色で塗りつぶします。
これを実現すると、ハードエッジであっても関係なく、かつ特定のモデルのみアウトラインを適用させることができます。

シェーダコード

今回作成したアウトラインシェーダのシェーダコード全文です。コピペすれば動きます。
必要最小限のコードしか書いていないので、ライト処理やシャドー処理などは行いません。

Shader "Custom/Outline"
{
    Properties
    {
        _MainColor("Main Color", Color) = (1, 1, 1, 1)
        _MainTex("Texture", 2D) = "white" {}
        _OutlineColor("Outline Color", Color) = (0.0, 0.0, 0.0, 1)
        [Slider(0.1)] _OutlineWidth("Outline Width", Range(0.0, 10.0)) = 3
    }
    SubShader
    {
        Tags { "RenderType" = "Opaque" }
        LOD 100

        // 【1パス目】ダミー色で塗りつぶし
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                half4 vertex : POSITION;
            };

            struct v2f
            {
                half4 vertex : SV_POSITION;
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                return fixed4(1.0, 0.0, 1.0, 0);
            }
            ENDCG
        }

        GrabPass {}

        // 【2パス目】Grabテクスチャを使ってアウトライン+通常描画
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            #define SAMPLE_NUM 6
            #define SAMPLE_INV 0.16666666
            #define PI2 6.2831852
            #define EPSILON 0.001
            #define DUMMY_COLOR fixed3(1.0, 0.0, 1.0)

            struct appdata
            {
                half4 vertex : POSITION;
                half2 uv : TEXCOORD0;
            };

            struct v2f
            {
                half4 pos : SV_POSITION;
                half2 uv : TEXCOORD0;
                half4 grabPos : TEXCOORD1;
            };

            sampler2D _GrabTexture;
            fixed4 _MainColor;
            sampler2D _MainTex;
            half4 _MainTex_ST;
            fixed4 _OutlineColor;
            half _OutlineWidth;

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.grabPos = ComputeGrabScreenPos(o.pos);

                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                half2 delta = (1 / _ScreenParams.xy) * _OutlineWidth;

                int edge = 0;
                [unroll]
                for (int j = 0; j < SAMPLE_NUM && edge == 0; j++)
                {
                    fixed4 tex = tex2D(_GrabTexture, i.grabPos.xy / i.grabPos.w + half2(sin(SAMPLE_INV * j * PI2) * delta.x, cos(SAMPLE_INV * j * PI2) * delta.y));
                    edge += distance(tex.rgb, DUMMY_COLOR) < EPSILON ? 0 : 1;
                }

                fixed4 col = lerp(tex2D(_MainTex, i.uv) * _MainColor, _OutlineColor, edge);
                return col;
            }
            ENDCG
        }
    }
}

下図の様にハードエッジモデルと通常のモデル両方で扱えます。
038.png

おわりに

上記のシェーダコードはアウトラインを表示するための必要最小限の機能しか実装していません。しかし、アウトライン部分以外はいくらでも追加実装することができるので汎用性は高いのではないかと思います。

また、良い感じに描画してくれるアウトラインシェーダですが弱点があります。それはアウトラインシェーダを適用させたモデルを重ねた時です。この場合、GrabPassがモデルと背景の境界を正しく判定できなくなるので、アウトラインが多少破綻してしまいます。そのため、ダミー色をマテリアルごとに変えたりといった工夫が必要になるかもしれません。それ以外の場面では現状問題なく描画できます。

その他不具合があったらご連絡ください。改良版を考えます。
良きUnityライフを。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者がC#でグーグルスピーカーを動かす(備忘録:挫折編、Javescriptで書く)

これは挫折したところまで書いた話です。
google homeでは下記の通りにデータの受け渡しがされてます。

image.png

引用:https://qiita.com/kenz_firespeed/items/0979ceb05e4e3299f313
Dialogfrowではbotの作成とかも出来て非常に面白そうなので
時期をみてやりたいなと思ってます。

ただ、今回はC#で書きたいので
もっと簡単にコード書いて、グーグルホームにお話させたいな
と思いました。

そこで見つけたのが、Node.jsで書く方法
ざっくり言うと、google-home-notifierというNode.jsのライブラリがあって
それをポチッとするとJavascriptで書くことが出来ます。

google-home-notifierでJavascriptを書く方法を残しておきます。

まず、Node.jsを有効にする。
https://nodejs.org/ja/でもいいけど
https://qiita.com/taketakekaho/items/dd08cf01b4fe86b2e218
この記事が非常に参考になりました。
ただ、初心者には、bashの使い方に要注意。

次にNode.jsの環境が整ったら、
npmを入れて、いざgoogle-home-notifierを導入へ
https://qiita.com/SatoTakumi/items/c9de7ff27e5b70508066
上記を参考にしました。

$ # package.jsonを作ります
$ npm init

$ # google-home-notifierを入れます
$ npm install google-home-notifier

$ # yarn の方は下記のようにします。
$ yarn add google-home-notifier

このようにgoogle-home-notifier導入

その後、
Javascriptでファイルを作ります。
xxxx.js
とかのNode.jsのファイルを作って

とりあえず、サンプルコードを作ります。
jconst googlehome = require('google-home-notifier')
const language = 'ja';

googlehome.device('Google-Home', language);

googlehome.notify('こんにちは。私はグーグルホームです。', function(res) {
console.log(res);
});

こんな感じ。

さて、コンソールでこのファイルを動かして見ると、
喋るーーーーーーー

と言うことで、Jacascriptでかけました。

C#ではどうするんだ?
と言うことで、次回は、C#の中にJavascriptを食い込ませたやり方を
考えたいと思います。

まだまだ初心者なので、
配列を改めて勉強してるレベル。。。

精進します。

参考にしたサイト
https://smarthacks.jp/mag/37615
https://qiita.com/kenz_firespeed/items/0979ceb05e4e3299f313
https://blog.okazuki.jp/entry/2018/06/28/141410
https://azure.microsoft.com/ja-jp/services/functions/

最近学んだ言葉。
サーバーレス・コンピューティング
何かをWeb上で運用するとき、本来ならサーバーの管理が必要だが
サーバーを管理しなくていいものが作れる

オーケストレーション
オーケストラのように指揮者がいて、楽器を演奏する人がいる
指揮者を管理、開発者に例えて、サーバーなり、コードなり諸々を指揮すること

イベント ドリブン型
イベントの発生をきっかけとして処理を始めること

デプロイ
ビルドしてできた実行ファイルを、実行する環境に合わせて、実際に実行できるようにする

トリガー
きっかけになる出来事が起こったら自動的に特定の処理を起動するソフトウェアの仕組み

バインド
何らかの要素やデータ、ファイルなどが相互に結び付けられること

functions
複雑なオーケストレーションの問題も解決できる、イベント ドリブン型のサーバーレス コンピューティング プラットフォームである Functions を使用して開発を効率化しましょう。追加のセットアップを必要としないローカルでのビルドとデバッグ、クラウドでの大規模なデプロイと運用、トリガーとバインドを使用したサービスの統合が可能です
要するに、サーバーレスで、簡単にものが作れまっせってこと。
楽チンにサービス作れますよってこと

Json
配列の()の中のものがkey
配列の箱の中にはなんでも(データ型は決まってるが)入るから
keyに対応させた表を作ること
簡単に言うと配列を表にしちゃえ!もっといえば、それをまとめたものJsonってのを作っておけば、引っ張ってくるとき楽やねん

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む