ETC1圧縮テクスチャ2013/11/13

OpenGLの中でもAndroidで共通に使える圧縮テクスチャ、ETC1に対応します。

圧縮テクスチャは、画像を圧縮した状態(サイズが小さい)でVRAMに置いておくテクスチャです。
PNG等の圧縮画像フォーマットは展開して元のサイズに戻してVRAMに転送しますが、ETC等の圧縮テクスチャではVRAM上でも小さいままなので、
・VRAMを占有するサイズが小さくなる
・描画時、転送サイズが小さいので高速
というメリットがあります。

そのかわり、
・不可逆圧縮なので、画質が落ちる
・ETC1圧縮テクスチャではαに未対応で、不透明画像のみ
といったデメリットもあります。

Android SDKでは etc1tool({sdkのフォルダ}/tools/etc1tool.exe)というPNG画像をETC1に変換するツールがありますので、そのツールで出力されるPKMフォーマットに対応します。

Texture.cpp
/**********************************
    PKM読み込み
		引数	data = PKMデータ
 **********************************/
void	Texture::load_pkm(const u8* data)
{
	format	= FORMAT_ETC;
	width	= (u16)(((u16)data[8] << 8) | data[9]);
	height	= (u16)(((u16)data[10] << 8) | data[11]);
	make(data + 0x10);
}

読み込み部分です。
ヘッダから幅と高さを取得して、データをそのまま作成関数に送っています。
"圧縮テクスチャ"というフォーマットのデータであってデータ自体は圧縮されていないので、展開処理は必要ありません。逆にいうと、データ自体にはまだ圧縮の余地があるということになります。サンプルでは assetsのzip圧縮に任せちゃっていますが。

/*****************************************
    作成
		引数	data = テクスチャデータ
 *****************************************/
void	Texture::make(const u8* data)
{
	clear();

	glGenTextures(1, &texture);						// テクスチャオブジェクト作成
	glBindTexture(GL_TEXTURE_2D, texture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	switch ( format ) {
	  case FORMAT_RGBA :
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
		break;

	  case FORMAT_RGB :
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
		break;

	  case FORMAT_ETC :
		glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_ETC1_RGB8_OES, width, height, 0, width*height/2, data);
		break;

	  default :
		assert(FALSE);
		break;
	}
}

テクスチャ作成部分です。
画像転送が glTexImage2Dから glCompressedTexImage2Dに代わり、フォーマットに GL_ETC1_RGB8_OESを指定しています。

あとは、読み込み部分でPNGとPKMに分けたり、キャッシュのサイズ計算でPKMに対応したりと細かな変更はありますが、ソースは省略します。

見た目はたいして変わらないので、スクリーンショットも省略していますが、
※ビー玉の画像は「かわいいフリー素材集 いらすとや」から使わせていただいています。どうもありがとうございます。

プロジェクト一式は、こちらから。

気になるポーズ(閃の軌跡)2013/11/14

今作からポリゴン表示となった「閃の軌跡」。
モデリングはまあ良いとして、モーションのパターンが少ないのが気になります。

ときどき見られる、立っているときに腰に手をあてるポーズ。会話の時に手持ち無沙汰なのか、よくこの仕草をします。

なんか 偉そう

これが、先生や先輩のような目上の人間を相手に話しているときにもするので、どうも違和感があります。
この世界の文化では特に問題ないということなのでしょうが、日本人にはなんか失礼な態度に見えてしまいます。

あくまで"貴族"がするポーズなのかと見ていましたが、"平民"や"おのぼりさん"もしていました。しかも揃って…

fふたりそろって

ルパン三世 TVスペシャル2013/11/15

…録れてなかった。
レコーダーの「おまかせ録画」におまかせしていたのが間違いだった。
しかし、キーワード「ルパン三世」でも引っかからないとはどういうこった。

ちゃんと見ていないんじゃ、批判もできやしない。

タッチパネル入力 その12013/11/16

画面への出力がひと通り落ち着いたので、次はタッチパネルからの入力を行います。

タッチパネルの入力情報は java側で受け取ります。
まず一番上にあるビューに通知され、そこでスルーされると次のビュー、最後にはアクティビティに通知されるようになっています。

ここでは一つだけのビュー、サーフェスビューで処理します。
また、せっかくなのでマルチタッチに対応してみます。

BaseActivity.java

	private short[]		touch_status = new short[5*3];	// タッチパネル状態


サーフェスビュー BaseView
		/********************
			タッチイベント
		 ********************/
		public boolean	onTouchEvent(final MotionEvent event)
		{
			if ( future == null ) {
				return	false;
			}

			int		_action = event.getAction();
			int		_index, _id;

			switch ( _action & MotionEvent.ACTION_MASK ) {
			  case MotionEvent.ACTION_DOWN :
			  case MotionEvent.ACTION_POINTER_DOWN :
				_index	= (_action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
				_id		= event.getPointerId(_index)*3;
				touch_status[_id + 1] = (short)event.getX(_index);		// X座標
				touch_status[_id + 2] = (short)event.getY(_index);		// Y座標
				touch_status[_id + 0] = 1;								// タッチ中
				break;

			  case MotionEvent.ACTION_MOVE :
				for (_index = 0; _index < event.getPointerCount(); _index++) {
					_id = event.getPointerId(_index)*3;
					touch_status[_id + 1] = (short)event.getX(_index);	// X座標
					touch_status[_id + 2] = (short)event.getY(_index);	// Y座標
					touch_status[_id + 0] = 1;							// タッチ中
				}
				break;

			  case MotionEvent.ACTION_UP :
			  case MotionEvent.ACTION_POINTER_UP :
				_index	= (_action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
				_id		= event.getPointerId(_index)*3;
				touch_status[_id + 0] = 0;								// 非タッチ
				break;
			}
			return true;
		}

画面をタッチする、離す等のタッチイベントが起こると onTouchEventが呼ばれます。
Android SDKでダブルタップ等の動作を判定することもできるのですが、ここでは
・画面を押しているか
・押している場合、その位置
だけを取得して、後の処理は native側に任せることとします。

取得した状態は、どのみち nativeはフレーム単位で動いているのでイベントが起きる度ではなく、updateNativeのタイミングで送るようにします。

レンダラー BaseRenderer
		public void	onDrawFrame(GL10 gl)
		{
			synchronized (renderer)
			{
				if ( (future != null) && !updateNative(touch_status) ) {		// native部稼働
					finish();
				}
			}
		}

最新の状態だけを送るので、炎のコマで秒間200万回入力しても無視されます。

native側の処理は、また次回に。

タッチパネル入力 その22013/11/17

前回に引き続きタッチパネルからの入力、今回は native側の処理を行います。

タッチパネル入力管理クラス sys::TouchManagerです。

TouchPanel.h
/********************************

		タッチパネル

 ********************************/

#ifndef	___TOUCH_PANEL_H___
#define	___TOUCH_PANEL_H___

#include "common.h"


namespace sys
{

/**********************
    タッチパネル管理
 **********************/
class TouchManager
{
public :

	static void		init_manager(void);					// 管理システム初期化
	static void		quit_manager(void);					// 管理システム終了
	static void		update_manager(short const*);		// 管理システム稼働

private :

	int		repeat_cnt;			// リピート用カウンタ
	int		prev_x, prev_y;		// 前回のタッチ位置

public :

static const int	TOUCH_MAX = 5;						// マルチタッチ数

enum
{
	TOUCH	= (1 << 0),			// タッチ
	TRIG	= (1 << 1),			// 初回タッチ
	REPEAT	= (1 << 2),			// リピート
	RELEASE	= (1 << 3),			// リリース
};

	u32		flag;				// タッチ状態フラグ
	int		x, y;				// タッチ位置
	int		move_x, move_y;		// 移動距離

		TouchManager(void)				// コンストラクタ
		{
			flag = 0x00;
		}
	void	update(short const*);		// 稼働
};

extern TouchManager*	TouchPanel;

}

#endif
/*************** End of File ****************************************/

TouchPanel.cpp
/***********************************

		タッチパネル管理

 ***********************************/

#include	"TouchPanel.h"
#include	"Renderer.h"


namespace sys
{

TouchManager*	TouchPanel;

/************
    初期化
 ************/
void	TouchManager::init_manager(void)
{
	TouchPanel = new TouchManager[TOUCH_MAX];
}

/**********
    終了
 **********/
void	TouchManager::quit_manager(void)
{
	delete[]	TouchPanel;
}

/***********************************************
    稼働
		引数	_status[0] = タッチしているか
				_status[1] = X座標
				_status[2] = Y座標
 ***********************************************/
void	TouchManager::update_manager(short const* _status)
{
	for (int i = 0; i < TOUCH_MAX; i++) {
		TouchPanel[i].update(_status);
		_status += 3;
	}
}

void	TouchManager::update(short const* _status)
{
	if ( _status[0] ) {						// タッチ中
		// タッチ位置補正
		x = (_status[1] - sys::Renderer::screen_rect.x)*SCREEN_WIDTH/sys::Renderer::screen_rect.w - SCREEN_WIDTH/2;
		y = (_status[2] - sys::Renderer::screen_rect.y)*SCREEN_HEIGHT/sys::Renderer::screen_rect.h - SCREEN_HEIGHT/2;
		if ( !(flag & TOUCH) ) {					// 初回
			flag = TOUCH | TRIG | REPEAT;
			repeat_cnt = TOUCH_REPEAT1;
			move_x = 0;
			move_y = 0;
			prev_x = x;
			prev_y = y;
		}
		else {										// 継続
			flag = TOUCH;
			if ( --repeat_cnt == 0 ) {				// リピート
				flag |= REPEAT;
				repeat_cnt = TOUCH_REPEAT2;
			}
			move_x	= x - prev_x;
			move_y	= y - prev_y;
			prev_x	= x;
			prev_y	= y;
		}
	}
	else {									// 非タッチ
		flag = (flag & TOUCH) ? RELEASE : 0;
	}
}

}

/*************** End of File *****************************************/

マルチタッチ対応のため、配列 TouchPanelで複数のインスタンスを処理します。

フレーム毎の処理 updateでは javaで取得した状態を受け取り、各メンバに格納します。
タッチしているときは、flagのビットTOUCHを立てます。
タッチ位置座標は画面描画と同じ、(-SCREEN_WIDTH/2, -SCREEN_HEIGHT/2) - (SCREEN_WIDTH/2, SCREEN_HEIGHT/2)になるように変換します。

基本的にはこれだけですが、他によく使う状態を計算しておきます。

flagのビット
・TRIG         離した状態からタッチしたとき
・RELEASE   タッチした状態から離したとき
・REPEAT    TRIGと同じですが、押しっぱなしのとき一定間隔でビットが立ちます

move_x, move_y
タッチしているときの前フレームからの移動距離


次に、TouchManagerを呼び出すシステム部分です。

SysMain.cpp
/**********
    稼働
 **********/
JNIEXPORT jboolean JNICALL	Java_sys_BaseActivity_updateNative(JNIEnv* env, jobject, jbyteArray touch)
{
	Renderer::update();							// 描画管理稼働

	jbyte*	dst = (jbyte*)env->GetPrimitiveArrayCritical(touch, NULL);

	TouchManager::update_manager((short*)dst);	// タッチパネル管理稼働
	env->ReleasePrimitiveArrayCritical(touch, dst, 0);

	if ( update_app() ) {						// アプリメイン稼働
		Renderer::draw();						// 描画後処理
		return	JNI_TRUE;
	}
	init_flag = FALSE;
	return	JNI_FALSE;							// アプリ終了
}

初期化・終了部分は、それぞれ init_managerと quit_managerを呼んでいるだけなので省略しています。
稼働部分では javaから受け取った jbyteArrayから GetPrimitiveArrayCriticalで配列のポインタを取得して TouchManagerに渡しています。

※javaから配列を渡すときはいくつか方法があり、GetPrimitiveArrayCriticalはそのままポインタを渡す(らしい)ので高速なのですが、その分いろいろと制限がありますので使うときは気をつけてください。


サンプルのタッチパネルテスト部分です。
タッチしたところにビー玉の絵が表示されます。

AppMain.cpp
	for (int i = 0; i < sys::TouchManager::TOUCH_MAX; i++) {			// タッチパネルテスト
		if ( sys::TouchPanel[i].flag & sys::TouchManager::TOUCH ) {
			sprite[SPR_BALL_BLUE + (i % 4)].draw(sys::TouchPanel[i].x, sys::TouchPanel[i].y, 3.0f + sinf((cnt % 60)*M_PI*2/60)*2.0f);
		}
	}
	cnt++;


タッチパネルテスト


※ビー玉の画像は「かわいいフリー素材集 いらすとや」から使わせていただきました。どうもありがとうございます。

プロジェクト一式は、こちらから。