ヘキサ日記 Blog

 

        

Switchの予約ができてゼルダが待ち遠しい某(なにがし)です
スプラトゥーン2の試射会もとても楽しみにしています

 

 

そういえば今日はバレンタインですね。

 

 

しかし、今回はUnityのプログラムネタです。ごめんなさい。
新しいことではないですが、uGUIでよくある問題の対処法をひとつ紹介します。

 

 

uGUIはとっつきやすく便利ですが、

 

スクロールビュー上のボタン押しにくい(クリックしにくい)

 

という問題に悩まされる人は多いんじゃないかと思います。

 

 

スクロールビュー上のボタンをドラッグするとスクロールが始まりますが、
この時点でボタンに指を離したイベントが発生して押下状態がキャンセルされ、
クリックイベントの対象から外れてしまいます

 

だから、少しでもドラッグするとボタンがクリックできず、押しにくいわけですね・・・

 

マウスならまだマシですが、
これがタッチ操作のときは気になるので、なんとかしたいと思います

 

 

考えられる方針は、

 

  1. ドラッグイベントを開始するドラッグ距離の閾値を与える(ドラッグ判定の遅延)。
  2. ドラッグ開始前に押下していたボタンに、
    ドラッグ終了時にクリックイベントを直接飛ばす(ドラッグで押下状態をキャンセルしない)。

 

こんなところでしょうか。

 

 

1.は一瞬でできます

 

EventSystemコンポーネントのDrag Threshold(pixelDragThreshold)がまさにドラッグ判定の閾値です。
これを大きめの値に設定すれば良いのですが、すべてのドラッグ判定に影響するのがイマイチな感じです

 

それに、スクロールビューのスクロール方向とは異なる向きの移動にも反応するため、
なんだか少しやりたいことと違う気がします

 

 

イベントの発行はBaseInputModuleの仕事であり、
普通なら、その派生クラスであるTouchInputModuleStandaloneInputModuleに任せていると思います。

 

BaseInputModuleを継承して、独自のイベント管理を実装するのも良い気がしますが、
それは仰々しいのでイベント処理側で簡単に対応します

 

 

今回は2.の方針で考えます。

 

 

実装の前に仕組みへの理解を深めるため、まずUnityのイベント処理の流れを簡単に説明します。

 

  1. タッチ入力されると、ポインティングされた座標にカメラからレイキャストを飛ばす。
    (対象はRaycastTargetにチェックを入れたGraphic派生コンポーネントを持つゲームオブジェクト)
  2. レイキャストに一番手前でヒットしたオブジェクトから親オブジェクトへと辿っていき、
    そのときのイベントに対応したイベントハンドラを持つゲームオブジェクトを探す。
    (例えば、OnPointerDownイベントならIPointerDownHandlerが対応)
  3. 見つけたゲームオブジェクトに対してイベント処理のメッセージを送信する。

 

起点となるオブジェクトを決定して、親を辿ってイベントハンドラを探す、
という流れを理解すれば、イベントシステムの使い方が見えてきます。

 

 

これもあるある話だと思うのですが、
スクロールビューの要素となるゲームオブジェクトにEventTriggerを持たせると、
ドラッグイベントも全てここで奪われてスクロールできなくなるといった問題が起きます。
上の処理内容を知らないと混乱しますよね。

 

 

このあたりの挙動はドキュメントでは詳しく説明されていなかったりしますが、
uGUIって実はブラックボックスではないんです。

 

Unity-Technologies / UI — Bitbucket

 

UnityさんがBitbucket上でuGUIのソースコードを公開しています。
何かと困ったらソースコードを見てみるのが早いです。

 

 

実際にソースコードを見てみると、PointerEventDataオブジェクトに
押下中オブジェクト(pointerPress)ドラッグ中オブジェクト(pointerDrag)への参照を持たせて、
これをそのままイベントシステムで利用しているのがわかります。

 

ドラッグイベント処理では、pointerPressとpointerDragが別のオブジェクトだった場合、
pointerPressにOnPointerUpイベントを送信して、参照をnullに差し替えています。

 

とすると、それまでにpointerPressで持っていた参照を独自で記憶するしかないですね

 

 

ということで、ScrollRectを拡張します。(ここまで長かった・・・)

 

  1. OnInitializePotentialDragでpointerPressを記憶
  2. OnEndDragのときの指の位置を見て、改めてレイキャストを飛ばして
    記憶していたpointerPressにヒットすれば、OnPointerClickイベントを送信する。

 

実装例を以下に示します。

 

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class CustomScrollRect : ScrollRect
{
	public override void OnInitializePotentialDrag(PointerEventData eventData)
	{
		var lastObj = EventSystem.current.currentSelectedGameObject;

		base.OnInitializePotentialDrag(eventData);
		if(!checkValidEvent(eventData)) return;

		// 直前の選択オブジェクトを記憶
		_lastSelectedObj = null;
		if(lastObj != null) {
			_lastSelectedObj = lastObj;
		}
	}

	public override void OnEndDrag(PointerEventData eventData)
	{
		base.OnEndDrag(eventData);
		if(!checkValidEvent(eventData)) return;

		// 直前の選択オブジェクトがあればイベント通知
		if(_lastSelectedObj != null) {
			EventSystem.current.RaycastAll(eventData, _raycastResults);
			var raycast = getFirstRaycast(_raycastResults);
			_raycastResults.Clear();

			notifyClickEvent(raycast, eventData);
		}

		_lastSelectedObj = null;
	}

	protected static RaycastResult getFirstRaycast(IEnumerable results)
	{
		foreach(var result in results) {
			if(result.gameObject != null) {
				return result;
			}
		}
		return new RaycastResult();
	}

	protected void notifyClickEvent(RaycastResult raycast, PointerEventData original)
	{
		if(_lastSelectedObj == null) return;

		var target = raycast.gameObject;
		if(target == null) return;

		// イベントハンドラを取得
		var handler = ExecuteEvents.GetEventHandler(target);
		if(handler == null) return;
		if(handler != _lastSelectedObj) return;

		// 通知用のイベントデータ生成
		var eventData = copyEventData(original);
		eventData.pointerCurrentRaycast = raycast;
		eventData.pointerPressRaycast = raycast;
		eventData.pointerPress = handler;
		eventData.rawPointerPress = target;
		eventData.eligibleForClick = true;

		// イベント実行
		ExecuteEvents.Execute(eventData.pointerPress, eventData, ExecuteEvents.pointerClickHandler);
	}

	protected bool checkValidEvent(PointerEventData eventData)
	{
		if(eventData.button != PointerEventData.InputButton.Left) {
			return false;
		}

		return IsActive();
	}

	protected PointerEventData copyEventData(PointerEventData original)
	{
		return new PointerEventData(EventSystem.current) {
			selectedObject = original.selectedObject,
			hovered = original.hovered,
			button = original.button,
			clickCount = original.clickCount,
			clickTime = original.clickTime,
			delta = original.delta,
			dragging = original.dragging,
			eligibleForClick = original.eligibleForClick,
			pointerCurrentRaycast = original.pointerCurrentRaycast,
			pointerDrag = original.pointerDrag,
			pointerEnter = original.pointerEnter,
			pointerId = original.pointerId,
			pointerPress = original.pointerPress,
			pointerPressRaycast = original.pointerPressRaycast,
			position = original.position,
			pressPosition = original.pressPosition,
			rawPointerPress = original.rawPointerPress,
			scrollDelta = original.scrollDelta,
			useDragThreshold = original.useDragThreshold,
		};
	}

	private static List _raycastResults = new List();

	private GameObject _lastSelectedObj = null;
}

 

使用中のイベントデータを書き換えると不都合がありそうなので、
強引ですが丸ごとコピーしたものを一部書き換えて使ってます。

 

これだけでだいぶボタンを押しやすくなり、押しやすさ改善の基礎はできました。

 

 

あとは、

 

  • ドラッグ開始時にOnPointerUpイベントが呼ばれてしまうのを
    OnPointerClickとタイミングを合わせる
    (ボタンの押下時アニメーションを継続したい)。
  • 一定距離スクロールしたら、記憶したpointerPressを忘れて、
    クリックイベントが飛ばないようにする。

 

このあたり対応すれば、なんだかとっても良い感じになるでしょう。

 

記事が長くなったので、この先の説明は割愛しますが、
今回の話を理解していれば難しい話ではないと思います。

 

 

実は、去年秋にリリースした「アイテム代は経費で落ちない ~no item, no quest~」では、
ここまで手が回らなかったんです。やり残したことのひとつでした・・・

 

このタイトルはあと少しだけアップデート予定があるので、
3月のアップデート内容にスクロールの挙動改善を入れました
アップデートによってどのように変わるか見てみると、なるほどなと思っていただけると思います

 

それでは、
「アイテム代は経費で落ちない ~no item, no quest~」
をよろしくお願いします(宣伝)


        

みなさんこんにちは。グリフォンです。

今日は東京でも雪が舞うくらい寒くて冬眠したい気分です。

 

さて今回も前回の続き「フォントのDistanceFieldTexture生成ツールをつくる」のその3です。

 

大まかな処理の流れは以下

フォントを1文字ずつ読み出してピクセル情報を取得

ピクセル情報からDistance Field情報を生成

CharacterInfoを生成

アトラステクスチャに書き込む

アトラステクスチャとFontSettingsを出力保存

 

 

今回はを説明していきます。

var srcWidth  = _BufferTexture.width;
var srcHeight = _BufferTexture.height;
var dstWidth  = _OutputFontTexture.width;
var dstHeight = _OutputFontTexture.height;

// 文字情報を成形
{
    var charInfo = new CharacterInfo();

    // 文字のインデックスは文字コードのこと
    charInfo.index = (int)writeChar[0];

    // アトラステクスチャ内のUV座標を成形
    // _OutputOffsetXはアトラステクスチャのX座標
    // _OutputOffsetYはアトラステクスチャのY座標
    var x0 = (float)_OutputOffsetX / (float)dstWidth;
    var y0 = (float)_OutputOffsetY / (float)dstHeight;
    var x1 = (float)(_OutputOffsetX + srcWidth) / (float)dstWidth;
    var y1 = (float)(_OutputOffsetY + srcHeight) / (float)dstHeight;
    charInfo.uvBottomLeft  = new Vector2(x0, y0);
    charInfo.uvBottomRight = new Vector2(x1, y0);
    charInfo.uvTopLeft     = new Vector2(x0, y1);
    charInfo.uvTopRight    = new Vector2(x1, y1);

    // ソースの文字情報を取得
    var bearing = 0;
    CharacterInfo srcCharInfo;
    if( _TextArea.font.GetCharacterInfo((char)charInfo.index, out srcCharInfo, _TextArea.fontSize * _Param_QualityScale, _TextArea.fontStyle) ) {
        // UV座標の中で、実際の文字の領域はどこからどこまでか指定する
        // この領域が文字を描画するときに切り出される範囲となる
        charInfo.minX    = (srcCharInfo.minX / _Param_QualityScale) - _Param_PaddingLeft;   // 追加したパディング領域を補正
        charInfo.minY    = (srcCharInfo.minY / _Param_QualityScale) - _Param_PaddingTop;    // 追加したパディング領域を補正
        charInfo.maxX    = (srcCharInfo.maxX / _Param_QualityScale) + _Param_PaddingRight;  // 追加したパディング領域を補正
        charInfo.maxY    = (srcCharInfo.maxY / _Param_QualityScale) + _Param_PaddingBottom; // 追加したパディング領域を補正
        charInfo.advance = (srcCharInfo.advance / _Param_QualityScale) + (_Param_PaddingLeft + _Param_PaddingRight); // 追加したパディング領域を補正
        bearing          = (srcCharInfo.minX / _Param_QualityScale) - _Param_PaddingLeft;
    }
    else {
        charInfo.minX    = 0;
        charInfo.minY    = 0;
        charInfo.maxX    = srcWidth;
        charInfo.maxY    = srcHeight;
        charInfo.advance = srcWidth;
    }

    // ベアリング情報を成形
    var channel      = (BEARING_CHANNEL_TABLE[_OutputPageNo] << 8); // uvのチャンネル情報を間借りしている
    var xoffset      = (bearing + 128); // xoffset値を-128~127を0~255に変換
    charInfo.bearing = channel + xoffset;

    // 文字情報追加
    _OutputFontModel._chars._charInfos.Add(charInfo);
}

 

8行目でCharacterInfoを作成して情報を入れていきます。

 

28行目で _TextArea(Textコンポーネント)から情報を取得します。

 

31~35行目ではパディング領域(文字の周りの隙間)を計算しています。

 

47~49行目ではベアリング値を設定しているのですが、このベアリング値に独自の情報を持たせています。

 

ベアリングについてはこちら

 

今回は、出力するアトラステクスチャのRGBAチャンネルを別々に使用してフォント情報を格納しています。

DistanceFieldTexture は白黒情報なので4チャンネルを有効に使ってアトラステクスチャが大きくなるのを防いでいます。

第2水準までのフォントで1024×1024のRGBAテクスチャ1枚に収まりました。

 

そこで、必要な文字がどのチャンネルに入っているかという情報が必要になる訳です。

ベアリングの上位8ビットにチャンネルのインデックス0~3を格納し、下位8ビットに元々のベアリング情報を0~255に変換して格納しています。

本来ベアリングはマイナス値も格納できますが、下位8ビットで表現するために0~255に変換しています。

もちろん、このフォントを描画するシェーダー側にベアリングを再変換する処理を入れておかないと、うまく描画されません。

 

最後に52行目で、_OutputFontModel(FontModelクラス)に格納します。

 

 

今回はここまで。以降はまた次回のお楽しみに


        

「花粉シーズン突入、予想飛散量は約1.2倍」というニュースを見て、

もう外に出たくないなぁ~なんて思い始めてます。

こんにちは、だっちです

花粉症の人は一刻も早く対策を始めたほうが良いですよ

 

 

突然ですが、ゲームを作るときに必要になるデータってたくさんありますよね。

その中でも敵とか武器とかアイテムとかのパラメーター、何で管理していますか?

社内では、データを入力する企画の方々が使い慣れているExcelを使う事が多いです。

 

 

今回は、Excelで作成した各種データを、

ゲーム内で扱いやすい形に変換するためのプログラムを、

 ・ゲーム内で扱うための型を自動的に生成

 ・型を生成するための情報はExcel内に定義

できるように、作っていこうと思います。

 

 

Excelファイルを扱うためのライブラリは色々ありますが、

今回は「NPOI」というライブラリをC#経由で使ってみようと思います。

 

 

早速、NPOIを使用してExcelファイルの中身を取得していこうと思います。

適当なExcelファイルを作成して、

適当にC#プロジェクトを作成して、DLしてきたNPOIを参照に加えておきます。

 

 

今回はこんな感じのExcelデータを用意しました。

NPOITestExcelDataSample

 

 

NPOIを使用してExcelファイルを読み込むためのコードはこんな感じ

using System;
using System.IO;
using NPOI.SS.UserModel;

namespace NPOITest
{
	class Program
	{
		// エントリポイント
		public static void Main(string[] args)
		{
			// 入力受付
			Console.Write("Prease input excel file path : ");
			var srcPath = Console.ReadLine();

			// ファイル存在確認
			if( !File.Exists(srcPath) ) {
				Console.WriteLine("file not exists.");
				return;
			}

			// 読み込み
			using( var fs = new FileStream(srcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite) ) {
				var workbook = WorkbookFactory.Create(fs);

				// 中にあるシートを順番に取得
				Console.WriteLine("Sheets :");
				foreach( ISheet sheet in workbook ) {
					if( sheet == null ) continue;

					// シート名表示
					Console.WriteLine("	Name : " + sheet.SheetName);

					// A1セルの内容を読み込み
					// まずは行を取得
					var row = sheet.GetRow(0);
					if( row == null ) continue;

					// 行からセルを取得
					var cell = row.GetCell(0);
					if( cell == null ) continue;

					// セルが取得できたら表示
					Console.WriteLine("		A1 : " + cell.ToString());
				}
			}
		}
	}
}

 

 

実行結果

NPOITestExecResult001

中にあるシートとセルに入力されている文字列が取得できていますね

 

 

次回は、「型を生成するための情報」を定義していきたいと思います。

ではでは~

 

 


2017年1月11日

32とか16とかその6

        

こんにちは。

 

シュンスケです

 

前回、どれがキレイ(元画像に近い)なのかをプログラムで判断できれば、

数あるパターンから最適なものが見つけられるのではといった話がありました

 

そこで、今回のアプローチは、GA

 

ガーディアンエンジェルではありません。

ギャラクシーエンジェルでもありません。

 

Genetic Algorithmつまり、「遺伝的アルゴリズム」です

 

大まかに説明すると、とある解を求めるにあたって、生物の進化のように、

解候補たちを組み替えながら、正解に近いものは残して、遠いものを淘汰し、

時には突然変異を起こすといった感じで、多くのパターンからより良いものを

導き出す処理方法です

 

と、ここまで来て気付く方も多いと思いますが、正解に近いかどうか、それが今回でいうと、

元画像に近いかどうかということです。

 

その比較方法でお手軽なのがPSNR値を使うものなようなので、今回はそうします。

(ただし、人間の目を相手にするとなると、問題は多いようです)

 

そして今回、果敢にも全ピクセルの全チャンネルに関して、組み換え対象として

実装しました。

つまり、膨大なパターンがあるということです

 

さて、以下実装です。

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.IO;
using System.Runtime.InteropServices;

namespace Pic32bitTo16bitGA
{
	class Program : Form
	{
		struct Gene
		{
			public static void cross(ref Gene a, ref Gene b)
			{
				if( a.buf == null || b.buf == null ) return;

				// 2点交叉
				var r = new Random();
				var index1 = r.Next(0, (a.buf.Length - 1) / PIXEL_BYTES);
				var index2 = r.Next((index1 + 1) / PIXEL_BYTES, b.buf.Length / PIXEL_BYTES);
				for( int i = index1; i < index2; ++i ) {
					var tmp0 = a.buf[i + 0];
					var tmp1 = a.buf[i + 1];
					var tmp2 = a.buf[i + 2];
					var tmp3 = a.buf[i + 3];
					a.buf[i + 0] = b.buf[i + 0];
					a.buf[i + 1] = b.buf[i + 1];
					a.buf[i + 2] = b.buf[i + 2];
					a.buf[i + 3] = b.buf[i + 3];
					b.buf[i + 0] = tmp0;
					b.buf[i + 1] = tmp1;
					b.buf[i + 2] = tmp2;
					b.buf[i + 3] = tmp3;
				}
			}

			// コンストラクタ
			public Gene(int w, int h)
			{
				buf = new byte[w * h * PIXEL_BYTES];
				psnr = -1;
			}

			// ランダムで埋める
			public void fillRand()
			{
				// ランダムで色を埋める
				var r = new Random();
				r.NextBytes(buf);
			}

			// PSNRを算出して保持
			public void calcPSNR(byte[] org)
			{
				psnr = -1;
				if( org == null || buf == null || org.Length != buf.Length || org.Length == 0 ) return;

				// MSE算出
				ulong mse = 0;
				for( int i = 0; i < org.Length; ++i ) {
					long d = (buf[i] - org[i]);
					mse += (ulong)(d * d);
				}
				mse /= (ulong)org.Length;
				if( mse <= 0 ) return;

				// PSNR算出
				psnr = (10 * Math.Log10((double)(255 * 255) / (double)mse));
			}

			// 突然変異
			public void mutation()
			{
				if( buf == null ) return;

				var r = new Random();
				var index = r.Next(0, buf.Length / PIXEL_BYTES);
				buf[index + 0] = (byte)r.Next(0, 256);
				buf[index + 1] = (byte)r.Next(0, 256);
				buf[index + 2] = (byte)r.Next(0, 256);
				buf[index + 3] = (byte)r.Next(0, 256);
			}

			// 値を設定
			public void set(Gene g)
			{
				if( g.buf == null ) return;

				if( buf == null || buf.Length != g.buf.Length ) {
					buf = new byte[g.buf.Length];
				}

				Array.Copy(g.buf, buf, buf.Length);
				psnr = g.psnr;
			}

			public byte[]	buf;
			public double	psnr;
		}

		private const int PIXEL_BYTES				= 4;								//!< 1ピクセルあたりのバイト数

		// エントリポイント
		static void Main(string[] args)
		{
			// 入力受付
			Console.WriteLine("please input picture path...");
			var srcPath = Console.ReadLine();

			// ファイル存在確認
			if( !File.Exists(srcPath) ) {
				Console.WriteLine("file not exists.");
				return;
			}

			// 画像読み込み
			byte[] buf = null;
			int width = 0;
			int height = 0;
			using( var img = Image.FromFile(srcPath, true) as Bitmap ) {
				if( img == null ) {
					Console.WriteLine("file type is not support.");
					return;
				}
				// メモリに保持
				var dat = img.LockBits(new Rectangle(Point.Empty, img.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
				width = img.Width;
				height = img.Height;
				buf = new byte[width * height * PIXEL_BYTES];
				Marshal.Copy(dat.Scan0, buf, 0, buf.Length);

				// 解放
				img.UnlockBits(dat);
			}
			if( buf == null ) {
				Console.WriteLine("buf load failed.");
				return;
			}

			// フォーム開始
			Application.Run(new Program(ref buf, width, height));
		}

		// コンストラクタ
		public Program(ref byte[] buf, int w, int h)
		{
			if( buf == null ) return;

			_org = buf;
			_buf = new byte[buf.Length];
			_img = new Bitmap(w, h);

			// 第1世代生成
			for( int i = 0; i < _curGen.Length; ++i ) {
				_curGen[i] = new Gene(w, h);
				_curGen[i].fillRand();
			}

			// フォームのサイズ設定
			ClientSize = new Size(w, h * 2);

			// フォント作成
			_font = new Font(Font.Name, 10);
			_fontBrush = new SolidBrush(ForeColor);

			// 描画更新開始
			_sec = 0;
			_refreshTimer.Interval = 1000;
			_refreshTimer.Tick += new EventHandler(refresh);
			_refreshTimer.Start();

			// 処理更新開始
			_updateTimer.Interval = 1;
			_updateTimer.Tick += new EventHandler(update);
			_updateTimer.Start();
		}

		// 描画時処理
		protected override void OnPaint(PaintEventArgs e)
		{
			base.OnPaint(e);
			if( _img == null ) return;

			// 元画像描画
			if( _org != null ) {
				var newDat = _img.LockBits(new Rectangle(Point.Empty, _img.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
				Marshal.Copy(_org, 0, newDat.Scan0, _org.Length);
				_img.UnlockBits(newDat);
				e.Graphics.DrawImage(_img, 0, 0);
			}

			// 生成画像描画
			if( _buf != null ) {
				var newDat = _img.LockBits(new Rectangle(Point.Empty, _img.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
				Marshal.Copy(_buf, 0, newDat.Scan0, _buf.Length);
				_img.UnlockBits(newDat);
				e.Graphics.DrawImage(_img, 0, _img.Size.Height);
			}

			// フレーム数表示
			e.Graphics.DrawString("sec : " + _sec, _font, _fontBrush, 0, 0);

			// 世代数表示
			e.Graphics.DrawString("gen : " + _genCnt, _font, _fontBrush, 100, 0);
		}

		// 描画更新
		private void refresh(object sender, EventArgs e)
		{
			// 保持したトップを描画バッファに設定
			if( _topGen.buf != null ) {
				Array.Copy(_topGen.buf, _buf, _buf.Length);
			}

			// 画面をリフレッシュ
			++_sec;
			Invalidate();
		}

		// 処理更新
		private void update(object sender, EventArgs e)
		{
			// 評価値算出
			for( int i = 0; i < _curGen.Length; ++i ) {
				_curGen[i].calcPSNR(_org);
			}

			// 評価値でソート
			Array.Sort(_curGen, (a, b) => {
				if( a.psnr == b.psnr ) return 0;

				return (b.psnr > a.psnr) ? 1 : -1;
			});

			// 1位を保持
			_topGen.set(_curGen[0]);

			// 上位いくつかはそのまま残す
			var newIndex = 0;
			for( int i = 0; i < 2; ++newIndex, ++i ) {
				_nextGen[newIndex].set(_curGen[i]);
			}

			while( newIndex < _nextGen.Length ) {
				// 交叉対象1を選出(上位が選ばれやすいように)
				_cross1Buf.set(_curGen[0]);
				for( int i = 1; i < _curGen.Length; ++i ) {
					var rate = _selectRnd.Next(0, 100);
					if( rate < 10 ) {
						_cross1Buf.set(_curGen[i]);
						break;
					}
				}

				// 交叉対象2を選出(上位が選ばれやすいように)
				_cross2Buf.set(_curGen[1]);
				for( int i = 2; i < _curGen.Length; ++i ) {
					var rate = _selectRnd.Next(0, 100);
					if( rate < 10 ) {
						_cross2Buf.set(_curGen[i]);
						break;
					}
				}

				// 交叉
				Gene.cross(ref _cross1Buf, ref _cross2Buf);
				_nextGen[newIndex].set(_cross1Buf);
				++newIndex;
				if( _nextGen.Length <= newIndex ) break;

				_nextGen[newIndex].set(_cross2Buf);
				++newIndex;
				if( _nextGen.Length <= newIndex ) break;

				// 突然変異対象を選出
				var mutIndex = _selectRnd.Next(0, _curGen.Length);
				_nextGen[newIndex].set(_curGen[mutIndex]);
				_nextGen[newIndex].mutation();
				++newIndex;
				if( _nextGen.Length <= newIndex ) break;

				// そのままコピー
				var copyIndex = _selectRnd.Next(0, _curGen.Length);
				_nextGen[newIndex].set(_curGen[copyIndex]);
				++newIndex;
				if( _nextGen.Length <= newIndex ) break;
			}

			// 次世代を現世代に移す
			for( int i = 0; i < _nextGen.Length; ++i ) {
				_curGen[i].set(_nextGen[i]);
			}
			++_genCnt;
		}

		private Bitmap	_img			= null;
		private byte[]	_org			= null;
		private byte[]	_buf			= null;

		private Timer	_refreshTimer	= new Timer();
		private Timer	_updateTimer	= new Timer();
		private Font	_font			= null;
		private Brush	_fontBrush		= null;
		private int		_sec			= 0;
		private int		_genCnt			= 0;

		private Gene[]	_curGen			= new Gene[64];
		private Gene[]	_nextGen		= new Gene[64];
		private Random	_selectRnd		= new Random();
		private Gene	_cross1Buf		= new Gene();
		private Gene	_cross2Buf		= new Gene();
		private Gene	_topGen			= new Gene();
	}
}

 

このままでは処理速度に難がありそうですが、実行してみます

 

ウィンドウの中で、上が目指す画像で、下が完全ランダム値からはじめて、

最終的には同じ絵になって欲しい部分です。

20170111_01

はじめの方

20170111_02

 

約50分後

 

う、うーん…ダメだ

さすがにパターンが多すぎたようです。

それに、交叉や突然変異の確率、評価処理もかなー、 見直しが必要そうです。

というか、そもそも全ピクセルランダムスタートで辿り着けるのか。。。

 

ただ、薄っすらと緑っぽく近づいてきてはいるので、アプローチの方向はまだ捨てずに

おこうと 思いますが、今回はここまで

 

では


2016年12月2日

優しいC++解析(その2)

        

ついに今年も最後の月になりましたね。
ここにきて期待のホラー映画ブレアウィッチまで登場してワクワクが止まりません。
お久しぶりです、good sunこと山口です。

さて、今回は前回に引き続いてclangを使ったC++のソース解析の続きを行います。

前回ではクラスや構造体の情報を解析するコードについて簡単に触れました。

今回はそこから発展して、特定の変数について情報を追加してみたいと思います。

具体的には以下の様なコードで

class Sample{
public:
	Sample();
	~Sample();
private:
	PARAM(_SERIALIZE("no"), _HINT("hint text"))
	long		_value;
};

_valueという変数に対して

・シリアライズを行わない

・ヒント情報としてhint textという情報を持つ

という情報を付与する事を目標とします。

その前にNuGetで取得できるClangSharpの不具合を修正します。

clang.parseTranslationUnit2の引数でUnsavedFileをout属性で渡すという処理になっていますが、

こちらは配列を渡すのが本来の挙動になります。

このUnsavedFileはメモリ上のファイルを読み込ませたりする目的で使うのでout属性ではそれが達成出来ません。

まずは開発者のGitHubのページを見てみます。(オープンソースって素晴らしいですね)

https://github.com/Microsoft/ClangSharp

見てみると実はその辺対策済みなコードが上がっています。

のでこちらをcloneするかdownloadします。

(私はGitHub Desktopがエラーになったので男らしくdownloadを選びました)

20161202_00

downloadもしくはcloneしたファイルにはClangSharp.slnが入っているのでコレを開きます。

※公式ページのBuilding ClangSharpの手順に従うと修正されているハズのファイルが上書きされてしまうので、

公式に反旗を翻すアウトローになり切ってください

次にExtensions.csを開いて8行目をコメントアウトします。

※時間的に原因調査していませんが…そのままではここのdisposeによってハングしてしまいます。

コメントアウトが完了したらソリューションエクスプローラから

ClangSharpのプロジェクトを右クリックでビルドします。

20161202_01

出来上がったClangSharp.dllをpdbと共にNuGetで追加したプロジェクトの

package\ClangSharp.3.8.0\lib\net40に上書きします。

念のためClangSharpがclang3.9対応版との事なのでlibclang.dllも3.9のものに差し替えます。

llvmをビルドすると大変なのでClang for Windowsをインストールします。

http://llvm.org/releases/download.html

20161202_02

インストール先のLLVM\binの下のlibclang.dllを

packages\ClangSharp.3.8.0\content\x64に上書きコピーします。

因みにこの状態になるまで主に例のdisposeの所で原因不明の停止を繰り返すことになりましたが

対策としてClangSharpを使うプロジェクトのデバッグ設定で

ネイティブコードデバッグを有効にするのチェックを入れ忘れていて調査に手間取ったという事があったので、

この項目へのチェックは推奨です

20161202_03

はい、準備が整いましたので本題です。

流れとしましてはUnrealEngineでやっているようなマクロを作ってそれを活用することになります。

ちょっと今回手抜きをしてマクロ全部入りのコードで紹介をしますが…

C++のコードはこんなコードを使います。

#define CLASS_DEFS()	\
					virtual void serialize();	\
					virtual void deserialize();	\

#define _HINT(n) const char* hint=n
#define _SERIALIZE(n) const char* serialize=n

#define _COMBINE__(a,b)	a##b
#define _COMBINE_(name,l)	_COMBINE__(name, l)
#define PARAM(...)	void _COMBINE_(__Paramater_, __LINE__)(__VA_ARGS__);

namespace test{

class Sample{
public:
	Sample();
	~Sample();
private:
	PARAM(_SERIALIZE("no"), _HINT("hint text等"))
	long		_value;
};

}

CLASS_DEFS()の定義はゲームとしてビルドするときは空になる様にifdefで分けるようにする必要があります。

今回は仕組みの説明なので…

解析時はこのマクロが出てきたところに関数宣言を展開してキーワードとしてClangから抽出を行おうと考えています。

さらにデフォルト引数を使って入力パラメータの解釈を行います。

前回の解析用コードを少し変更してこんな感じのコードを使って解析します。

using System;
using System.Collections.Generic;
using System.Text;
using ClangSharp;

namespace ClangTest
{
	// パラメータ解析用クラス
	public class Param
	{
		public bool Serialize { get; set; } = true;
		public string Hint { get; set; }
		public void parseFunc(CXCursor cursor)
		{
			var param = clang.getCursorSpelling(cursor).ToString();
			switch (param) {
			case "serialize":
				clang.visitChildren(cursor, new CXCursorVisitor(visitSerialize), new CXClientData());
				break;
			case "hint":
				clang.visitChildren(cursor, new CXCursorVisitor(visitHint), new CXClientData());
				break;
			}
		}
		// シリアライズの有無のチェック
		CXChildVisitResult visitSerialize(CXCursor cursor, CXCursor parent, IntPtr client_data)
		{
			if (clang.getCursorKind(cursor) == CXCursorKind.CXCursor_StringLiteral)
			{
				var val = clang.getCursorSpelling(cursor).ToString().Replace("\"","");
				switch (val.ToLower()) {
				case "no":
				case "false":
					Serialize = false;
					break;
				default:
					break;
				}
				return CXChildVisitResult.CXChildVisit_Break;
			}
			return CXChildVisitResult.CXChildVisit_Recurse;
		}
		// ヒントテキストを取得
		CXChildVisitResult visitHint(CXCursor cursor, CXCursor parent, IntPtr client_data)
		{
			if (clang.getCursorKind(cursor) == CXCursorKind.CXCursor_StringLiteral) {
				var val = clang.getCursorSpelling(cursor).ToString();
				val = val.Substring(1, val.Length - 2);
				Hint = val;
				return CXChildVisitResult.CXChildVisit_Break;
			}
			return CXChildVisitResult.CXChildVisit_Recurse;
		}
		public override string ToString()
		{
			return string.Format("serialize={0},hint={1}", Serialize, Hint);
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			var index = clang.createIndex(0, 0);
			var ufile = new CXUnsavedFile();
			var trans = new CXTranslationUnit();
			ufile.Filename = "dummy.cpp";
			ufile.Contents = "#include \"sample.h\"";
			ufile.Length = ufile.Contents.Length;

			var erro = clang.parseTranslationUnit2(index, "dummy.cpp", new String[] { "-std=c++11" }, 1, new CXUnsavedFile[] { ufile }, 1, (uint)(ClangSharp.CXTranslationUnit_Flags.CXTranslationUnit_SkipFunctionBodies), out trans);
			var cursor = clang.getTranslationUnitCursor(trans);
			var kind = clang.getCursorKind(cursor);
			var pg = new Program();
			clang.visitChildren(cursor, new CXCursorVisitor(pg.visitChild), new CXClientData());

		}
		protected List _namespace = new List();
		protected String _namespaceStr = "";
		protected Dictionary<uint, Param> _params = new Dictionary<uint, Param>();
		CXChildVisitResult visitChild(CXCursor cursor, CXCursor parent, IntPtr client_data)
		{
			switch (cursor.kind) {
			case CXCursorKind.CXCursor_Namespace:
				readNamespace(cursor);
				clang.visitChildren(cursor, new CXCursorVisitor(visitChild), new CXClientData());
				removeNamespace();
				break;
			case CXCursorKind.CXCursor_ClassDecl:
				readClass(cursor);
				break;
			case CXCursorKind.CXCursor_EnumDecl:
				readEnum(cursor);
				break;
			case CXCursorKind.CXCursor_FieldDecl:
				readField(cursor);
				break;
			case CXCursorKind.CXCursor_ClassTemplate:
				readTClass(cursor);
				break;
			case CXCursorKind.CXCursor_CXXMethod:
				readMethod(cursor);
				break;
			default:
				break;
			}
			return CXChildVisitResult.CXChildVisit_Continue;
		}
		CXChildVisitResult visitEnum(CXCursor cursor, CXCursor parent, IntPtr client_data)
		{
			var itype = clang.getEnumDeclIntegerType(cursor);
			if (itype.kind == CXTypeKind.CXType_UInt) {
				Console.WriteLine("{0} {1}", clang.getCursorSpelling(cursor), clang.getEnumConstantDeclUnsignedValue(cursor));
			}
			else {
				Console.WriteLine("{0} {1}", clang.getCursorSpelling(cursor), clang.getEnumConstantDeclValue(cursor));
			}
			return CXChildVisitResult.CXChildVisit_Continue;
		}

		void readNamespace(CXCursor cursor)
		{
			var cxName = clang.getCursorSpelling(cursor);
			String name = cxName.ToString();
			_namespace.Add(name);
			_namespaceStr = String.Join("::", _namespace);
		}
		void removeNamespace()
		{
			_namespace.RemoveAt(_namespace.Count - 1);
			_namespaceStr = String.Join("::", _namespace);
		}
		void readClass(CXCursor cursor)
		{
			Console.WriteLine("クラス定義発見 {1}::{0}", clang.getCursorSpelling(cursor), _namespaceStr);
			// さらに子を検索する
			clang.visitChildren(cursor, new CXCursorVisitor(visitChild), new CXClientData());
			_params.Clear();
		}
		void readTClass(CXCursor cursor)
		{
			Console.WriteLine("テンプレートクラス定義発見 {0}", clang.getCursorSpelling(cursor));
			// さらに子を検索する
			clang.visitChildren(cursor, new CXCursorVisitor(visitChild), new CXClientData());
		}
		void readEnum(CXCursor cursor)
		{
			Console.WriteLine("enum定義発見 {0}", clang.getCursorSpelling(cursor));
			// さらに子を検索する
			clang.visitChildren(cursor, new CXCursorVisitor(visitEnum), new CXClientData());
		}
		void readMethod(CXCursor cursor)
		{
			var name = clang.getCursorSpelling(cursor).ToString();
			// パラメータ解析する為のキーワードを見つけたら
			if (name.StartsWith("__Paramater_"))
			{
				var location = clang.getCursorLocation(cursor);
				CXFile file;
				uint line;
				uint column;
				uint offset;
				// 行番号とセットで情報を覚えさせる
				clang.getSpellingLocation(location, out file, out line, out column, out offset);
				Console.WriteLine("パラメータ宣言発見 {0} {1}", name, line);
				var param = new ClangTest.Param();
				// これで引数を解析
				var n = clang.Cursor_getNumArguments(cursor);
				for (uint i = 0; i < n; ++i)
				{
					var arg = clang.Cursor_getArgument(cursor, i);
					param.parseFunc(arg);
				}
				_params.Add(line + 1, param);
			}
		}
		void readField(CXCursor cursor)
		{
			var type = clang.getCursorType(cursor);
			String typeName;
			var arrCount = clang.getArraySize(type);
			if (arrCount > 0) {
				var arrType = clang.getArrayElementType(type);
				typeName = String.Format("{0}=>({1})", arrType, arrCount);
			}
			else {
				typeName = type.ToString();
			}
			var location = clang.getCursorLocation(cursor);
			CXFile file;
			uint line;
			uint column;
			uint offset;
			// フィールドが出てきたときに覚えていた行数とパラメータ情報に一致するものがあったらその情報を適応する
			clang.getSpellingLocation(location, out file, out line, out column, out offset);
			Param pm;
			if (_params.TryGetValue(line, out pm)) {
				Console.WriteLine("パラメタ付変数宣言発見 {0} {1} {2}", clang.getCursorSpelling(cursor), typeName, pm);
			}
			else {
				Console.WriteLine("変数宣言発見 {0} {1}", clang.getCursorSpelling(cursor), typeName);
			}
		}
	}
}

という感じで、

特定の名前の関数が登場した行数とそこから取得できるデフォルト引数を記録して、

フィールドが出てきたときに行数をチェックして該当のパラメータを持っているという解釈をさせるという事で

フィールドに情報の追加を行う事が出来ます。

デフォルト引数の取得にちょっと苦戦しましたが…思いの外すんなり出来てしまうものですね

ここまでくるとそろそろ次回辺りは実践編になるかもですね。

という事で今回はここまでです。

ではまた次回にお会いしましょう


2016年12月1日

C#のLINQが便利!

        

シワッスプログラマーの平尾です。

 

今回はプログラマー向けの話です。
C#3.0からLINQという凄い便利な機能が使えるので、簡単に紹介します。

 

LINQで一体何が出来るのかというと、
「コレクションの操作を非常に簡潔に記述すること」
が出来ます。

 

と言われてもLINQを知らない方はピンと来ないと思いますので、
サンプルプログラムを書いてみました。
(LINQはメソッド構文で記述しています)

 

【ソース】

using System;
using System.Linq;    // LINQを使用する際に必要なusing

class Program
{
    public class Data
    {
        public string    name;
        public string    home;
        public int       power;
    }

    static void Main(string[] args)
    {
        // キャラクターデータ
        var src = new Data[]
        {
            new Data { name = "筋肉男", home = "日本",     power = 95   },
            new Data { name = "星肩男", home = "アメリカ", power = 95   },
            new Data { name = "鉄仮面", home = "イギリス", power = 95   },
            new Data { name = "拳法男", home = "中国",     power = 97   },
            new Data { name = "軍服男", home = "ドイツ",   power = 90   },
            new Data { name = "熊爪男", home = "ロシア",   power = 100  },
            new Data { name = "猛牛男", home = "スペイン", power = 1000 },
            new Data { name = "元人間", home = "アメリカ", power = 83   },
        };

        // 名前だけ取り出す
        var dst = src.Select( x => x.name );
        Console.WriteLine("■名前一覧");
        foreach ( var data in dst ) {
            Console.WriteLine("name = {0}", data);
        }

        // パワーが100以上のキャラクターを取得
        var dst2 = src.Where( x => x.power >= 100 );
        Console.WriteLine("\n■パワーが100以上のキャラクター");
        foreach( var data in dst2 ) {
            Console.WriteLine("power = {0, 4}, name = {1}", data.power, data.name);
        }

        // 日本またはアメリカ出身のキャラクター数
        int count = src.Count( x => x.home == "日本" || x.home == "アメリカ" );
        Console.WriteLine("\n■日本/アメリカ出身者数 = {0}人", count);

        // パワーが高い順にソート
        var dst3 = src.OrderByDescending( x => x.power );
        Console.WriteLine("\n■パワーが高い順");
        foreach( var data in dst3 ) {
            Console.WriteLine("power = {0, 4}, name = {1}", data.power, data.name);
        }

        // 出身地でグループ化する
        var dst4 = src.GroupBy(x => x.home, x => x.name );
        Console.Write("\n■出身地別");
        foreach (var group in dst4) {
            Console.Write("\n{0}:", group.Key);
            foreach (var name in group) {
                Console.Write("{0}, ", name);
            }
        }
    }
}

 

【出力結果】

■名前一覧
name = 筋肉男
name = 星肩男
name = 鉄仮面
name = 拳法男
name = 軍服男
name = 熊爪男
name = 猛牛男
name = 元人間

■パワーが100以上のキャラクター
power =  100, name = 熊爪男
power = 1000, name = 猛牛男

■日本/アメリカ出身者数 = 3人

■パワーが高い順
power = 1000, name = 猛牛男
power =  100, name = 熊爪男
power =   97, name = 拳法男
power =   95, name = 筋肉男
power =   95, name = 星肩男
power =   95, name = 鉄仮面
power =   90, name = 軍服男
power =   83, name = 元人間

■出身地別
日本:筋肉男,
アメリカ:星肩男, 元人間,
イギリス:鉄仮面,
中国:拳法男,
ドイツ:軍服男,
ロシア:熊爪男,
スペイン:猛牛男, 

 

プログラム中で以下のように呼び出しているのがLINQクエリです。

src.Where()
src.Select()
src.Count()
src.OrderByDescending()
src.GroupBy()

 

本来ならループして書くところが、とんでもなく簡潔に書けていると思います。
データを取り出す処理はどれも1行
むしろ表示部分の方が長いという…

 

 

この他にも様々な機能が存在していますし、
次のようにLINQクエリを重ねて書くこともできます。

src.Where.().Select()

 

LINQはこのように複雑な処理を簡潔に記述でき、とても便利です

気になった方はネットで詳しい使用方法を検索してみて下さい~


2016年11月15日

2つの言語を知ること

        

ハローハロー

 

最近はポルノグラフィティの新曲
真っ白な灰になるまで、燃やし尽くせ
をヘビロテしている某(なにがし)です

 

今回はプログラマらしく、軽くプログラミングネタを書きますね。

 

プログラマはたいてい複数のプログラミング言語を扱います。
何故なら、どんな場面でもこれひとつあればいい
という万能な言語が存在しないからですね。
銀の弾丸などない、という言葉もあります。

 

言語ごとに個性があって、得意不得意があります。
なので、いろんな言語を知ることは、
できることの幅が広がっていいんじゃないかと思います

 

そういう自分も学生の頃からいろいろな言語に触れてきたものですが、
中でも気に入っているのがC#Pythonです。

 

こうして複数の言語に触れてみると、
共通点や相違点がわかってきます

 

Pythonは文字列処理が簡単に済むのでよく使うのですが、
例えば、下記のようなプログラムが書けます。

# 'abc' と 'def' の連結
'abc' + 'def'

# 前後の空白文字の削除
'  abc  '.strip()

# カンマ区切りで分割して文字列リストを得る
'abc,def'.split(',')

# カンマ区切りで文字列リストを連結
','.join(['abc', 'def'])

# 小文字にする
'ABCDEF'.lower()

これと同じことをC#で書いてみると、だいたい似たような感じになります。

// "abc" と "def" の連結
var s = "abc" + "def";

// 前後の空白文字の削除
"  abc  ".Trim();

// カンマ区切りで分割して文字列リストを得る
"abc,def".Split(',');

// カンマ区切りで文字列リストを連結
string.Join(",", new string[] {"abc", "def"});

// 小文字にする
"ABCDEF".ToLowerInvariant();

決まりごとがあるわけではないのですが、
よくある処理はだいたい名前が伝統的に決まっていて、
それを知っていると他の言語に移っても、すぐにそれを利用できたりします。

 

ところで、Pythonを見ていると、
インスタンスメソッドが充実していて、記述がとても簡潔ですね。スマート

 

C#でも、Pythonみたいに書けたらスッキリするのになぁ。

public static class StringExtension
{
	public static string Join(this string separator, string[] values)
	{
		return string.Join(separator, values);
	}
}

",".Join(new []{"abc", "def"});

拡張メソッドという機能でなんかそれっぽいのできました。
既に定義されている型に対して、こうした柔軟な拡張ができるC#は面白いと思います。

 

ただ、言語ごとに流儀というものがありますので、
このぐらいのことで自分の美徳を優先して、チーム開発に取り込むのは良くないですね

 

それでも、こういうことができるよという発想があると、いつか良い実装アイデアが思い浮ぶかもしれません

 

最近、とあるプロジェクトで、
桁数の多い数字をカンマ区切りで表示することが多かったので、こういうの作ってました。

public static class IntegerExtension
{
	public static string toCommaSeparatedString(this int value)
	{
		return value.ToString("N0");
	}
	public static string toCommaSeparatedString(this long value)
	{
		return value.ToString("N0");
	}
	public static string toCommaSeparatedString(this uint value)
	{
		return value.ToString("N0");
	}
	public static string toCommaSeparatedString(this ulong value)
	{
		return value.ToString("N0");
	}
}

// "10,000,000,000"という文字列を得る
10000000000.toCommaSeparatedString();

すっっっっごい地味なんですが、この記述スタイル好きです

 

はい、好みの問題でもあります

 

それでも、このIntegerExtensionというクラス名を覚える必要がない上に、
とりあえず整数に”to”つけたらIDEが補完してくれるので、実装スピードが上がるメリットがあります。

 

なんだか思ったよりも長い文章になってしまいましたが、
いろんな言語を知ることは、直接その言語を使う場面でなくても、十分武器になりうるよ、という話でした。

 

Pythonに興味ありましたら、ヘキサブログの過去記事もいくらかありますので見てみてください

 

それでは。


        

みなさんこんにちは。グリフォンです。

最近いっきに冷え込んできて、先週の土日は風邪で家から一歩も出れませんでした

 

さて今回は前回の続き「フォントのDistanceFieldTexture生成ツールをつくる」のその2です。

 

大まかな処理の流れは以下

フォントを1文字ずつ読み出してピクセル情報を取得

ピクセル情報からDistance Field情報を生成

CharacterInfoを生成

アトラステクスチャに書き込む

アトラステクスチャとFontSettingsを出力保存

 

 

今回はを説明していきます。

private bool GenerateDistanceFieldTexture()
{
    // 有効領域を切り出す
    if( !ClippingValidRect() ) {
        return false;
    }

    // 入力テクスチャ情報をバッファ
    var srcPixels = _BufferTexture.GetPixels32();
    var srcWidth  = _BufferTexture.width;
    var srcHeight = _BufferTexture.height;

    // 出力用のテクスチャを生成
    var dstWidth  = srcWidth / _Param_QualityScale;
    var dstHeight = srcHeight / _Param_QualityScale;
    var DFTexture = new Texture2D(dstWidth, dstHeight, TextureFormat.ARGB32, false, false);

    // 距離テクスチャを生成
    var dstPixels = DFTexture.GetPixels32();
    for( int y=0; y<dstHeight; ++y ) {
        for( int x=0; x<dstWidth; ++x ) {
            var srcCenterX = (x * _Param_QualityScale) + (_Param_QualityScale / 2);
            var srcCenterY = (y * _Param_QualityScale) + (_Param_QualityScale / 2);
            var distance   = FindSignedDistance(srcCenterX, srcCenterY, srcWidth, srcHeight, srcPixels);
            dstPixels[(y * dstWidth) + x] = new Color32(255, 255, 255, CalcDistanceToAlpha32(distance / _Param_SpreadDist));
        }
    }
    DFTexture.SetPixels32(dstPixels);
    DFTexture.Apply();

    // 生成した距離テクスチャでバッファに上書き
    _BufferTexture    = DFTexture;

    // 有効ピクセル情報追加
    _regularCharsWidthSum    += dstWidth;
    _regularCharsHeightSum    += dstHeight;

    return true;
}

 

4行目の ClippingValidRect() は、の処理のときに少し大きめなテクスチャにフォントをキャプチャしたので、ピクセル情報を調べて余計な余白を削除しています。

また、フォントに存在しない文字など全て空白の場合などは false としてスキップしています。

 

次に各種外部パラメータの説明です。

_Param_QualityScale は、より綺麗に距離テクスチャを生成するため、

『拡大してキャプチャ → 距離情報計算 → 縮小して保存 』 という手順を踏んでいるので、そのためのスケール値です。

_Param_SpreadDist は、境界からどの程度の距離を 0.0 ~ 1.0 にするかという閾値です。詳しくはこちらを見てください。

 

続いて、24行目の FindSignedDistance() では指定のピクセル位置から最近傍境界点までの距離を計算しています。

private float FindSignedDistance(int srcCenterX, int srcCenterY, int srcWidth, int srcHeight, Color32[] srcPixels)
{
    var delta   = _Param_SpreadDist * _Param_QualityScale;
    var srcMinX = Mathf.Max(0, srcCenterX - delta);
    var srcMinY = Mathf.Max(0, srcCenterY - delta);
    var srcMaxX = Mathf.Min(srcWidth - 1, srcCenterX + delta);
    var srcMaxY = Mathf.Min(srcHeight - 1, srcCenterY + delta);
    
    var curInside     = srcPixels[(srcCenterY * srcWidth) + srcCenterX].r;
    var closestDistSq = delta * delta;

    // 最近傍境界点までの距離を取得
    for( int y=srcMinY; y<srcMaxY; ++y ) {
        for( int x=srcMinX; x<srcMaxX; ++x ) {
            if( curInside != srcPixels[(y * srcWidth) + x].r ) {
                var distSq = CalcDistSq(srcCenterX, srcCenterY, x, y);
                if( distSq < closestDistSq ) {
                    closestDistSq = distSq;
                }
            }
        }
    }

    // 二乗距離を変換して符号判定する
    var closestDist = Mathf.Sqrt((float)closestDistSq) / _Param_QualityScale;
        closestDist = Mathf.Min(closestDist, _Param_SpreadDist);
    if( 0 == curInside ) {
        // 白で描画しているので、検索ピクセルが黒の場合は符号を反転
        closestDist = -closestDist;
    }

    return closestDist;
}

 

これらの処理で注意することは、ソーステクスチャより結果テクスチャのほうが _Param_QualityScale の分、小さくなるということです。

このスケール値が大きくなれば、より綺麗になりますが計算負荷が増し時間が掛かります。

スマホのゲームなどで使用する分には8倍くらいが費用対効果として妥協点かと思います。

※第2水準まで8000文字弱を処理したときは10分程度かかりました。

 

 

今回はここまで。以降はまた次回をお楽しみに


2016年10月14日

32とか16とか番外編

        

こんにちは。

 

シュンスケです

 

前回、次で最後のような事を書いていましたが、もう1つ既存の技術で減色の話が

あったので、なんだか少し続きそうです

 

そこで今回は少し休憩として、番外編です

 

今まで作成した手法は2つありましたが、それぞれにも与えるパラメータに種類が

ありました。それを変えていくとどんな感じになるのか、参考までに並べて見ようと思います。

組織的ディザ(Bayer)

組織的ディザ(Bayer)


組織的ディザ(ハーフトーン)

組織的ディザ(ハーフトーン)


組織的ディザ(Screw)

組織的ディザ(Screw)


組織的ディザ(中間調強調)

組織的ディザ(中間調強調)


誤差拡散(Floyd-Steinberg)

誤差拡散(Floyd-Steinberg)


誤差拡散(JaJuNi)

誤差拡散(JaJuNi)


誤差拡散(Stucki)

誤差拡散(Stucki)


誤差拡散(Burkes)

誤差拡散(Burkes)

 

こうやって見てみると、特色がそれぞれあるのも見えますが、誤差拡散、キレイですねぇ

これを超えるのは中々大変そうです

 

ちなみに、組織的ディザ法のディザ行列を毎回ランダム生成した所、

↓のような絵になりました。

20161014_test_logo_mtd_rnd

ノイズで石の質感を当てたみたいになりましたが、馴染み方としては少し可能性も

感じます

 

ただ、どんどん数を出していくと、どれがキレイなのかもプログラムで判断出来れば、

ひたすらキレイなものを探して貰えるのでは?と思ってしまいます

次回はそんなお話にしようかと思います

 

では


2016年9月7日

優しいC++解析(その1)

        

お久しぶりです。

 

夏は暑いのでお年寄りに交じって早朝ジョギングをしているgood sunこと山口です
5:30にはすでにお年寄りは散歩中という現実に驚きです。

 

以前の悪ダイコンChanで立体造形も一区切りついたので、
次は編みぐるみの話でも始めようかと思ったのですが、
そろそろプログラマらしいネタをやらないといけないのではないか?
そんな使命感に駆られたので簡単なテーマを決めて、
シリーズ化して紹介していきたいと思います。

 

今回選んだテーマはC++のソース解析についてです

 

motivation:(<=論文のこの表現が好き)

ヘキサエンジンで採用しているリフレクション,シリアライズ機能を今風にしたい。

 

ヘキサエンジンではC++のソースのみで完結できる形で
リフレクションとシリアライズ機能を実装しています。
(特にソースを自動生成とかせずに頑張ってメタを埋め込む感じ)
これはこれでコンパイラさえあれば機能が実現できるので良いのですが、
機能拡張が困難だったり書式的に冗長性が高かったりするので、
少し前から巷で噂のClangを利用したアノテーションを埋め込むスタイルを実現してみたい。

 

Clang:

クランって何?って方の為に簡単な説明をwikipediaさんがしてくれているのでリンクを紹介
https://ja.wikipedia.org/wiki/Clang
LLVMという比較的新しめのコンパイラを作る仕組みを利用した、
C言語系のコンパイラらしいです。

 

因みにClangそのものを利用するとコンパイル結果を取得する事になったりして面倒ぽいので、
今回はlibclangというC言語から利用するライブラリを利用して、
解析結果を利用したいと思います。

 

ClangSharp:

libclangを使うと言いましたが、
正直C言語で文字列扱ったりするのは正気の沙汰ではありません。
ネットを徘徊するとPythonのバインディング等があるのですが、
デバッグが面倒だなぁと感じたりしたので
(VisualStudioで良い感じにデバッグは出来るんですが…)
C#経由で使ってみたいと思います。

 

今回紹介するのはClangSharpというパッケージです。
他にもバインディングはあるのですが、
NuGetから導入可能というお手軽さだったのでこれを選択しました。
https://www.nuget.org/packages/ClangSharp/

 

demonstration:

今回は簡単にC++のクラスの内容を取得する事をやってみたいと思います。

 

ではまずはC#で適当にプロジェクトを作成します。
プロジェクトを作成したら、
ソリューションにNuGetからClangSharp3.8.Xをインストールします。
20160907_01
※ClangSharpは3.6.X系がありますが、ネームスペース以外全く別物なので要注意!

 

さらに一つ問題がありまして、
ClangSharp3.8.XはAnyCPUでビルドできないのでx64かx86のターゲットを作成します。
20160907_02
20160907_03

そうしましたら今回解析するソースはこんな感じです。

sample.cpp

typedef short s16;

namespace test{

class Sample{
public:
	enum class EnumType : s16{
		E0,
		E1=10,
		E2,
	};
	Sample(){}
	~Sample(){}
private:
	long		_value;
	EnumType	_e;
	s16			_array[3];
};

template
class TSample
{
public:
	TSample(){}
	~TSample(){}
private:
	T			_v;
};

}

 

最低限なC++の構成としてネームスペースとクラス定義と各種変数が定義されています。
このようなソースにlibclangを使うと上記が解析されて各要素が木にぶら下がった状態で取得が出来るらしいです

 

ではこれを解析するC#のコードをザックリとこんな感じで用意します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ClangSharp;

namespace ClangTest
{
	class Program
	{
		static void Main(string[] args)
		{
			var index = clang.createIndex(0, 0);
			var ufile = new CXUnsavedFile();
			var trans = new CXTranslationUnit();
			var erro = clang.parseTranslationUnit2(index, "sample.cpp", new String[] { "-std=c++11" }, 1, out ufile, 0, 0, out trans);
			// 本当はここでエラーチェックが好ましい
			var cursor = clang.getTranslationUnitCursor(trans);
			var pg = new Program();
			clang.visitChildren(cursor, new CXCursorVisitor(pg.visitChild), new CXClientData());
		}
		protected List _namespace = new List();
		protected String _namespaceStr = "";
		CXChildVisitResult visitChild(CXCursor cursor, CXCursor parent, IntPtr client_data)
		{
			switch (cursor.kind) {
			case CXCursorKind.CXCursor_Namespace:
				readNamespace(cursor);
				clang.visitChildren(cursor, new CXCursorVisitor(visitChild), new CXClientData());
				removeNamespace();
				break;
			case CXCursorKind.CXCursor_ClassDecl:
				readClass(cursor);
				break;
			case CXCursorKind.CXCursor_EnumDecl:
				readEnum(cursor);
				break;
			case CXCursorKind.CXCursor_FieldDecl:
				readField(cursor);
				break;
			case CXCursorKind.CXCursor_ClassTemplate:
				readTClass(cursor);
				break;
			default:
				break;
			}
			return CXChildVisitResult.CXChildVisit_Continue;
		}
		CXChildVisitResult visitEnum(CXCursor cursor, CXCursor parent, IntPtr client_data)
		{
			var itype = clang.getEnumDeclIntegerType(cursor);
			if (itype.kind == CXTypeKind.CXType_UInt) {
				Console.WriteLine("{0} {1}", clang.getCursorSpelling(cursor), clang.getEnumConstantDeclUnsignedValue(cursor));
			}
			else {
				Console.WriteLine("{0} {1}", clang.getCursorSpelling(cursor), clang.getEnumConstantDeclValue(cursor));
			}
			return CXChildVisitResult.CXChildVisit_Continue;
		}
		void readNamespace(CXCursor cursor)
		{
			String name = clang.getCursorSpelling(cursor).ToString();
			_namespace.Add(name);
			_namespaceStr = String.Join("::", _namespace);
		}
		void removeNamespace()
		{
			_namespace.RemoveAt(_namespace.Count - 1);
			_namespaceStr = String.Join("::", _namespace);
		}
		void readClass(CXCursor cursor)
		{
			Console.WriteLine("クラス定義発見 ({1}){0}", clang.getCursorSpelling(cursor), _namespaceStr);
			// さらに子を検索する
			clang.visitChildren(cursor, new CXCursorVisitor(visitChild), new CXClientData());
		}
		void readTClass(CXCursor cursor)
		{
			Console.WriteLine("テンプレートクラス定義発見 {0}", clang.getCursorSpelling(cursor));
			// さらに子を検索する
			clang.visitChildren(cursor, new CXCursorVisitor(visitChild), new CXClientData());
		}
		void readEnum(CXCursor cursor)
		{
			Console.WriteLine("enum定義発見 {0}", clang.getCursorSpelling(cursor));
			// さらに子を検索する
			clang.visitChildren(cursor, new CXCursorVisitor(visitEnum), new CXClientData());
		}
		void readField(CXCursor cursor)
		{
			var type = clang.getCursorType(cursor);
			String typeName;
			var arrCount = clang.getArraySize(type);
			if (arrCount > 0) {
				var arrType = clang.getArrayElementType(type);
				typeName = String.Format("{0}=>({1})", arrType, arrCount);
			}
			else {
				typeName = type.ToString();
			}
			Console.WriteLine("変数宣言発見 {0} {1}", clang.getCursorSpelling(cursor), typeName);
		}
	}
}

※急いで書いたコードなので汚い…
最初ClangSharp3.6.X使っていて情報取れないなぁ…って状態からClangSharp3.8.Xにしたら 様変わりしている上にどうやって使っていいかドキュメントが無くて、
最初のclang.parseTranslationUnit2がどうしてもinvalid argmentしてしまったりで心折れ掛かりました

 

因みにClangSharpの3.8.Xはほぼlibclangなのでlibclangのドキュメントさえあれば何とかなります。
http://clang.llvm.org/doxygen/group__CINDEX.html

 

そしてこれを実行するとこんな感じになります。

20160907_04
結構すんなり取得出来てしまいました。

 

こんな感じで構文木状態のデータ構造を読み取っていると、
自作スクリプトのコンパイラを作ってるみたいでワクワクしますよね。

 

尚cppを解析させていますが、
これはネットの情報によるとClangが解釈するのは翻訳単位なので ヘッダでは十分な解析が行えないという情報に基づきます。
ClangSharp3.6.Xで試した時は確かに情報は取得できませんでした。
将来的にやりたい事はヘッダにアノテーションを追加して
それを解析できれば十分なのでこの辺の対応方法は今後の課題ですね。

 

とは言えとても簡単にC++の構成を取得できる事が分かりましたので、
今後はこの情報を利用して色々な機能を実現していく事でシリーズ化していこうと思います。

 

という事で一応プログラマアピールをするブログその1はこれまでです。

では次回も続くことを祈って


ヘキサブログ ピックアップ



過去の日記はこちら

2017年3月
« 2月    
 12345
6789101112
13141516171819
20212223242526
2728293031