2013年10月22日火曜日

Luaを使ってクラスっぽい物を生成してCから使う方法

2013.11.04 修正版記事を作りました。
2013.11.04 大きな間違いに気がついたので後日書き直します。本記事は大嘘です。
2013.11.01 誤って記事を消してしまったので書き直し。

本記事は間違いだらけです。修正版はこちら。


以下のようなコードによって、Luaで簡単なクラス風のテーブルを作成できる。

クラス風のテーブルを作成する方法はWEB上に多数のサンプルが見受けられたが、 Cとの連携を付記したコードは見当たらなかったので備忘録として作成。

継承にはもう少し工夫が必要だが、複雑なテーブルを作成するならC++で普通にクラスを書いたほうが良い気がする。主観だが。

Luaでグローバル領域に宣言されたテーブルのフィールドを利用する際は、一度テーブルをスタックに積んでからフィールドを取得する点に注意。

main.cpp

#include <lua.hpp>
#include <stdio.h>

int main( void ) {
	lua_State *L = luaL_newstate();	// Luaステートを生成
	luaL_openlibs( L );				// 標準ライブラリを読み込み
	
	// スクリプトの読み込み
	if (luaL_dofile(L, "script.lua") ) {
		// エラー処理
		printf("%s\n", lua_tostring(L, lua_gettop(L) ) );
		lua_close(L);
		return 0;
	}

	// まず犬を生成
	lua_getglobal( L, "Dog" );					// グローバルなテーブル"Dog"をスタックに積む
	lua_getfield( L, lua_gettop( L ), "new" );	// トップに積まれたテーブルのメンバ"new"をスタックに積む
	lua_pushstring( L, "Neko" );				// 名前はNeko。犬なのにNeko。
	lua_pcall( L, 1, 1, 0 );					// 実行!
	int idx_dog = lua_gettop( L );				// Nekoのスタック上の位置を取得

	// 次に猫を生成
	lua_getglobal( L, "Cat" );					// グローバルなテーブル"Cat"をスタックに積む
	lua_getfield( L, lua_gettop( L ), "new" );	// トップに積まれたテーブルのメンバ"new"をスタックに積む
	lua_pushstring( L, "Inu" );					// 名前はInu。猫なのにInu。
	lua_pcall( L, 1, 1, 0 );					// 実行
	int idx_cat = lua_gettop( L );				// Inuのスタック上の位置を取得

	lua_getfield( L, idx_dog, "show" );	// 関数をスタック
	lua_pcall( L, 0, 0, 0 );			// 呼び出し
	
	lua_getfield( L, idx_cat, "show" );	// 関数をスタック
	lua_pcall( L, 0, 0, 0 );			// 呼び出し

    lua_close( L );	// 終了
    return 0;
}

script.lua

-- Dog
Dog = {}
Dog.new = function( name )
	local obj = {}
	obj.name = name
	obj.show = function( self )
		s = string.format( "%s the DOG.", name )
		print( s )
	end
	obj:show()
	return obj
end

-- Cat
Cat = {}
Cat.new = function( name )
	local obj = {}
	obj.name = name
	obj.show = function( self )
		s = string.format( "%s the CAT.", name )
		print( s )
	end
	obj:show()
	return obj
end

2013年8月23日金曜日

glBeginとglMaterialfvの順番とGLSLの関係

OpenGLで描画を行う際、
glBeginよりも後にglMaterialfvを呼び出すと、
シェーダーを使用した際に色が設定されない。
シェーダーを使用しなかった場合は正しく描画されてしまうのが曲者である。

// NG
glUseProgram( shaderProgram );
glBegin( GL_POLYGON );
glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, red );
glVertex3f( 0.0, 0.0, 0.0 );
glVertex3f( 1.0, 0.0, 0.0 );
glVertex3f( 1.0, 1.0, 0.0 );
glVertex3f( 0.0, 1.0, 0.0 );
glEnd();

// OK
glUseProgram( shaderProgram );
glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, red );
glBegin( GL_POLYGON );
glVertex3f( 0.0, 0.0, 0.0 );
glVertex3f( 1.0, 0.0, 0.0 );
glVertex3f( 1.0, 1.0, 0.0 );
glVertex3f( 0.0, 1.0, 0.0 );
glEnd();

shaderProgramとredは書いていないが適切な変数だと思って頂きたい。

恐らく、glBeginの時点でGPUにカラー情報が渡され、後から変更できない為に、
このような挙動になっているのではないかと。
シェーダーを使用しない場合何故通ってしまうのかは不明。

描画するオブジェクトの種類によっては、
glBeginとglEndの間で普通にglMaterialfvを呼び出すオブジェクトもあるが、
そういったオブジェクトでglBeginとglEndの間でglMaterialfvを間で呼び出すと、
シェーダーを使用する際どうなるかは調べていないので不明。
もしかしたら環境によって挙動が異なる可能性すらある。

2013年8月11日日曜日

DGZ サポートページ

DGZのサポートページです。

DGZはソースコードを公開しているフリーゲームです。
ユーザーは自由にコードを改変し、コード及びプログラムを公開することができます。
また、その際に報告の義務はありません。
ただし、コード以外のリソースは二次利用できません。
予めご了承下さい。

バグ報告・感想等は当記事のコメント欄にお願い致します。
ダウンロード

2013年7月11日木曜日

Luaの標準ライブラリのロード方法

今まで一部で躓いていたCからのLuaステートへの標準ライブラリのロードがうまくいったのでアウトプット。

以下はうまくいかないコード。
#include

int main( void ) {
    lua_State *L = luaL_newstate();
    luaopen_package( L );
    lua_close( L );
    return 0;
}

出力:
PANIC: unprotected error in call to Lua API (no calling environment)

以下が正しいコード。
#include

int main( void ) {
    lua_State *L = luaL_newstate();
    lua_pushcfunction(L, luaopen_package);
    lua_pushstring(L, LUA_LOADLIBNAME);
    lua_call(L, 1, 0);
    lua_close( L );
    return 0;
}

リファレンスを見る限りは、全てのライブラリのロードで下の方法を用いるべきと思われる。上の方法でうまく動くライブラリとそうでないライブラリが存在する。
luaL_openlibs( L ); // OK
luaopen_base( L ); // OK
luaopen_math( L ); // OK
luaopen_string( L ); // OK
luaopen_table( L ); // OK
luaopen_debug( L ); // OK
luaopen_package( L ); // NG
luaopen_io( L ); // NG
環境によるかもしれないので必ず上を使いましょう。

2013年7月10日水曜日

luaopen_mathでスタックに謎のLUA_TTABLEが積まれる件について

以下のコードは動作はするが、予期せぬ結果となる。

script.lua

function func(num)
    return num;
end

main.cpp

#include "lua.hpp"
int main( void ) {
    lua_State* p = luaL_newstate();
    luaopen_math( p );
    luaL_dofile( p, "script.lua" );
    int num = 100;
    lua_getglobal( p, "func" );
    lua_pushnumber( p, num );
    lua_pcall( p, 1, 1, 0 );
    num = lua_tonumber( p, 1 );
    lua_close( p );

    return 0;
}

最終的にnumに入っている値は100であると予想されるが、
実際には0が入っている。
luaopen_mathを呼び出した際に、謎の LUA_TTABLE がプッシュされるため、
スタックの一番下には func の返り値ではなく、
テーブルが入っており、その上に返り値が積まれている。

NG: num = lua_tonumber( p, 1 );
OK: num = lua_tonumber( p, -1 );

何が積まれているのかは調べたけど分からなかった。
誰か、Luaに詳しい方で分かる方いらっしゃったら教えて下さい。

2013年3月2日土曜日

この際だから言うけど、eneloop stick boosterはモバイルバッテリに適さない

 ここ数日eneloopのデザイン変更が世間を賑わせているeneloopを再評価する記事や、eneloopとそれらを取り巻く市場についての現状をまとめた記事は興味深い内容が多い。
 それとは別に、だいぶ前にGalaxy Nexus用の予備バッテリとしてeneloop stick booster(KBC-D1AS)を購入したのだが、思ったよりも使い勝手が良くなかったので、その理由を書いてみようと思う。
 まず、eneloop stick boosterは単体では充電できない。充電の際には中の単三電池を取り出して、充電器に接続する必要がある。シンプルな構造故取り出すのは容易だが、このちょっとした手間がそれなりにストレスになってしまう。
 次に、電池2本で3800mAhという容量は存外に少ない。10000mAh前後の安いモバイルバッテリを購入したほうが確実に良いだろう。
 そしてeneloop stick boosterには電池の残存量を知るためのインジケータが存在しない。stick booster以外ならばLEDインジケータのついた機種が存在するので、モバイルバッテリ用途ではインジケータのついた物の方が確実に良い。
 最後に、スティック型の形状は思った程のメリットは無い。アルマイトの細長いスティックというデザインは美しいが、細長い形状で大きくメリットがある人以外にとってはただそれだけだ。

 バッテリは、それが充電池であるにしろそうでないにしろ消耗品だということを実感させられた。単三電池のeneloopを買うのなら、モバイルバッテリではなくリモコン等の単三電池を使う機器で使用した方が良いし、モバイルバッテリとして使うならば容量が大きく安価な物を使ったほうが良いだろう。結局私はGalaxy Nexus用の大容量バッテリを買ってしまった。専用バッテリなら携帯電話本体を充電器として利用できるし、モバイルバッテリを接続したままで利用する煩わしさも無いので、消耗品として使い潰すには最適だと思う。ちなみに私はこのバッテリーを使用している。

 その他のGalaxy Nexus向けバッテリーモバイルバッテリー