2011/08/18

ブログ引っ越すかも。。

投稿する際に使用するエディタを「以前のエディタ」にしないとエディタがうまく
動いてくれないようです。。。

なんだかなぁ。。といった感じなので、ブログを引っ越す予定です。
引っ越しが完了したら、このブログは更新されなくなります。
(今までもあまり更新されてないですけどね。。笑)

新しいブログは

http://bl4etc.blog.fc2.com/

です。

Bloggerに越してきてあまり投稿してないのに。。残念。。

2011/08/11

Gimpのプラグインを作ってみました(Simple Sprite Sheet Maker)

複数の画像を一枚にまとめたファイル(Sprite Sheetというらしいですが)が必要になったので、
あれこれ探していたんですが、なかなか見つかりませんでした。。

手始めに先日購入したPixelmator用のプラグインとして作れないかどうか試してみたんですが
Pixelmatorだと(というよりもQuartz Composerプラグインだと?)、複数の画像ファイルやレイヤーを
扱うような処理はとっても作りにくいことがわかって断念しました。
(おしいところまでは作ったんですが、どうも納得できなくて)

次にGimpのプラグインで既存のものがないかと探してみると、SpriteSheet.scmというのが見つかりました。
さっそく使ってみたんですが、期待していた動きとは違っていて画像を横一列にしか配置してくれない
感じでした。

だったらということで、自分で作ってみることにしました。
参考にしたのは
GIMP Python Document
・http://zwell.net/content/pygimp.html
などです。


いきなりですが、ソースコードから。
コメントなどを英語で書く練習もかねてますが、間違いだらけな気が。。笑

#!/usr/bin/env python

'''
SimpleSpriteSheet

This script help to generate sprite sheet for game texture.

When you use this script, all images size must be the same.
Otherwize, you could not get the good result.

Option help:
  1. Sprite cell width
     Set the width of individual Sprite.
     The width of Sprite should be the same as the height of the image.
     When the width of the image is bigger, the image is trimmed.
    
  2. Sprite cell height
     Set the height of individual Sprite.
     The height of Sprite should be the same as the height of the image.
     When the height of the image is bigger, the image is trimmed.
    
  3. Columns per row
     Set the number of the lateral cells.
    
  4. Image source
     When "Layers" is chosen, this script use the layer of current image from the top.
     If "Directory" is chosen, this script use the image files in a directory.
    
  5. Acive layers only
     This option becomes effective when the image source is "Layers".
     If this option is "Yes", script ignores the invisible layers.
    
  6. Directory
     This option becomes effective when the image source is "Directory".
     Script use the image in this directory.
    
  7. Image filter
     This option becomes effective when the image source is "Directory".
     Script processes ignores other image types.
'''

import os
from gimpfu import *


IMG_SOURCE_LAYER = 0
IMG_SOURCE_DIRECTORY = 1
IMG_FILTER_PNG = 0
IMG_FILTER_JPG = 1

SOURCE_FILTER_DICT = {
    IMG_FILTER_PNG: ('.png', '.PNG'),
    IMG_FILTER_JPG: ('.jpg', 'jpeg'),
    }


def plugin_main(timg, tdrawable, spriteWidth, spriteHeight, columnCount,
                imageSource, activeLayersOnly, directory, imageFilter):
    '''
    '''
    newImg = None
    # "Layers" source
    if imageSource == IMG_SOURCE_LAYER:
        newImg = _make_sprite_sheet(timg, spriteWidth, spriteHeight, columnCount)
    # "Directory" source
    elif imageSource == IMG_SOURCE_DIRECTORY:
        # Filter the image file by extention
        extFilters = SOURCE_FILTER_DICT.get(imageFilter)
        filesInDir = os.listdir(directory)
        if extFilters:
            sourceNames = [os.path.join(directory, fname) for fname in filesInDir
                       if os.path.splitext(fname)[1] in extFilters]
        else:
            sourceNames = [os.path.join(directory, fname) for fname in filesInDir]
        # Create the temporary image that contains source images(layers)
        loadedImage = _load_layerd_iamge(sourceNames, spriteWidth, spriteHeight, columnCount)
        if loadedImage:
            # Make the sprite sheet from the image which contains the layers.
            newImg = _make_sprite_sheet(loadedImage, spriteWidth, spriteHeight, columnCount)
            gimp.delete(loadedImage)

    # Show the result image
    if newImg:
        gimp.pdb.gimp_display_new(newImg)

def _get_sprite_row(index, columnCount):
    '''
    index: origin 0
    RETURN: origin 0
    '''
    row  = int(math.ceil(float(index + 1) / float(columnCount)))
    return row - 1

def _get_sprite_column(index, columnCount):
    '''
    index: origin 0
    RETURN: origin 0
    '''
    return index % columnCount

def _get_sprite_position(index, columnCount):
    '''
    RETURN: Tuple (row index, column index)
                  index : origin 0
    '''
    return (_get_sprite_row(index, columnCount),
            _get_sprite_column(index, columnCount))
  
def _get_image_size(sourceCount, spriteWidth, spriteHeight, columnCount):
    '''
    RETURN: Tuple (image widht), image height)
    '''
    cols = columnCount if sourceCount >= columnCount else sourceCount
    w = cols * spriteWidth
    h = (_get_sprite_row(sourceCount, columnCount) + 1) * spriteHeight
  
    return w, h

def _load_layerd_iamge(filenames, spriteWidth, spriteHeight, columnCount):
    '''
    RETURN: gimp.Image
    '''
    imageW, imageH = _get_image_size(len(filenames), spriteWidth, spriteHeight, columnCount)

    newImg = gimp.Image(imageW, imageH, RGB)
  
    for fname in filenames:
        newLayer = gimp.pdb.gimp_file_load_layer(newImg, fname)
        gimp.pdb.gimp_image_add_layer(newImg, newLayer, len(newImg.layers))

    return newImg

def _make_sprite_sheet(image, spriteWidth, spriteHeight, columnCount):
    '''
    return gimp.Image
    '''
    # Validate arguments
    if not image or spriteWidth <= 0 or spriteHeight <= 0 or columnCount <= 0:
        return None

    # Resize image
    visibleLayerCount = len([la for la in image.layers if la.visible])
    if visibleLayerCount == 0:
        print 'No visible layers.'
        return None
    imageW, imageH = _get_image_size(visibleLayerCount, spriteWidth, spriteHeight, columnCount)
    tmpImg = gimp.Image(imageW, imageH, RGB)
    tmpLayer = gimp.Layer(tmpImg, 'Sprite-Sheet', imageW, imageH,
                          RGBA_IMAGE, 100, NORMAL_MODE)
    tmpImg.add_layer(tmpLayer)
    gimp.pdb.gimp_drawable_fill(tmpLayer, TRANSPARENT_FILL)

    # Destination layer pixel region
    dstPixRgn = tmpLayer.get_pixel_rgn(0, 0, imageW, imageH)
  
    # Arange the images
    gimp.progress_init()

    progressBarUnit = 100.0 / float(len(image.layers))
    for idx, layer in enumerate(image.layers):
        row, col = _get_sprite_position(idx, columnCount)
        dstX = col * spriteWidth
        dstY = row * spriteHeight

        pixelsX = layer.width if layer.width <= spriteWidth else spriteWidth
        pixelsY = layer.height if layer.height <= spriteHeight else spriteHeight

        # Source layer pixel region
        srcPixRgn = layer.get_pixel_rgn(0, 0, pixelsX, pixelsY)

        # Copy source pixel regin to destination.
        dstPixRgn[dstX:dstX + pixelsX, dstY:dstY + pixelsY] = srcPixRgn[0:pixelsX, 0:pixelsY]
        gimp.progress_update(idx * progressBarUnit)
        ## === SLOWER ===
        ## for x in range(pixelsX):
        ##     for y in range(pixelsY):
        ##         dstPixRgn[dstX + x, dstY + y] = srcPixRgn[x, y]

        ## === SLOWEST ===
        ## for x in range(pixelsX):
        ##     for y in range(pixelsY):
        ##         channels, pixel = gimp.pdb.gimp_drawable_get_pixel(layer, x, y)               
        ##         gimp.pdb.gimp_drawable_set_pixel(tmpLayer, dstX + x, dstY + y, channels, pixel)

    return tmpImg


#
# Register script
#
register(
    'python_fu_SSS',
    'Simple sprite sheet maker(S.S.S).',
    'Simple sprite sheet maker(S.S.S).',
    'Junichi Kawanishi aka jkani4 (jkani4@gmail.com)',
    'Copyright 2011 Junichi Kawanishi',
    '2011',
    '/Filters/_SSS...',
    '', # image types
    [(PF_INT, 'spriteWidth', 'Sprite cell width:', 128),
     (PF_INT, 'spriteHeight', 'Sprite cell height:', 128),
     (PF_INT, 'columnCount', 'Columns per row:', 8),
     (PF_RADIO, 'imageSource', 'Image source:', IMG_SOURCE_LAYER, (('Layer', IMG_SOURCE_LAYER), ('Directory', IMG_SOURCE_DIRECTORY))),
     (PF_TOGGLE, 'activeLayersOnly', 'Active layers only:', 1),
     (PF_DIRNAME, 'directory', 'Directory:', 0),
     (PF_OPTION, 'imageFilter', 'Image filter', IMG_FILTER_PNG, ['*.png', '*.jpg'])],
    [],
    plugin_main)

#
# Start
#
main()

ううむ、私のブログ、サイドバーが広いのでソースコードが見づらくてごめんなさい。
レイヤーの画像を並べている処理(_make_sprite_sheet)で、SLOWERとかSLOWESTとか
コメントアウトされている部分があります。
この部分は、あるImageから別のImageに画像をコピーする処理をしています。

SLOWESTとしてコメントアウトされた方法で実装してみたところ、
処理そくどがめちゃくちゃ遅かったので、ボツになりました。
(1pixelづつコピーしてるし、別のScript-fuを呼び出してるので遅いのは当然か。。)

次にSLOWERとしてコメントアウトされた方法に修正しました。
まだ、懲りずに1pixelづつコピーしてます。。(笑
結構速くなりましたが、まだ全然だめな感じでいた。

そして最後、画像の矩形を一気にコピーするように修正すると、
我慢できる処理速度になりました。めでたしめでたし。


さてさて、実際に上記のスクリプトを~/Library/Application Support/Gimp/plug-ins(私の環境はMacなので)において、
Gimpを起動すると、フィルタメニューの項目にSSS...というものが表示されます。
SSS(Simple Sprite Sheet)の略なんですが、略す必要があるかと言われると。。ないです。(笑


使い方はその名の通り、シンプル(なはずです)。
このスクリプトの使い方は2種類あります。
(1) 現在の画像のレイヤーを順番に(上のレイヤーから順に)ならべる。
(2) 指定したディレクトリ中の画像ファイルを順番にならべる。

シンプルなだけあって、制約もあります。
並べる画像のサイズはすべて同じサイズでないといけません。
そうしないと、期待した結果が得られない可能性が高いです。


さて、スクリプトを起動すると以下のような画面が表示されます。

設定項目の説明ですが、
1. Sprite cell width
並べる画像の幅を指定します。
2. Sprite cell height
並べる画像の高さを指定します。
3. Columns per row
一行につき画像を何枚ならべるかを指定します。
4. Image source
Layerが選択されていた場合には、画像のレイヤーを上から順に並べて行きます。
Directoryが選択されている場合には、指定したディレクトリ中の画像を並べます。
5. Active layers only
Image sourceでLayerが選択された場合のみ有効です。
可視レイヤーのみを対象に処理を行います。
6. Directory
Image sourceでDirectoryが選択された場合のみ有効です。
読み込む画像の場所を指定します。
7. Image filter
Image sourceでDirectoryが選択された場合のみ有効です。
読み込む画像を拡張子でフィルタします。



処理結果のサンプル
Image sourceでLayerを選択して、5つのレイヤーを持っている画像に対して実行してみました。
Columns per rowは3です。
画像が左上から順番に並びました。



いやはや、なかなかいい経験でした。

2011/04/25

Blender2.57、ArchLinuxのTestingリポジトリに

Blender2.57がArchLinuxのTestingリポジトリに登場してますね。
現状では pacman -S blender とすると2.49bがインストールされるんですが、
もう少ししたら2.57がインストールされるようになるってことですねぇ。

ちなみに、AUR(ArchLinux User Repository)にはすでに2.57が
登録されているので、そちらからtarballを入手すれば、現状でも2.57を
比較的簡単にインストールできます。(AURのビルド方法さえわかっていれば)

公式リポジトリからBlender2.57がインストールできるようになる日が
とっても待ち遠しいです。

約1年ぶりにArchLinux復活!

ThinkPad T61にCFDのSSD(128G)を装着して、
LinuxMint、Ununtu、ArchLinuxなどなど
これまでインストールしたことのあるディストリビューション
を試してみたんですが。。ことごとく撃沈。。

まったくインストールできないわけではなくて、
インストールは簡単に完了するんです。が。。
パッケージを更新したりしているとファイルシステムが
壊れていくようでした。
パッケージ更新が失敗するようになって、いろいろと調べてみると
ターミナル上で、ファイルのパーミッション表示が????などとと
なっていました。

BIOSのSATAをACHIではなく、Compatibleに設定してもだめでした。

gdiskでパーティションを切り直せばいけるようですがその場合には、
ブートローダをGRUB2にする必要があるようです。

色々と考えた結果、Linux + SSDをLinux + HDDと同様に
安定して使用できるようになるのはもう少しかかるのかも。。と
いう結論に達しました。(Linux導入部分に時間をとられたくないというのが本音)

ということで、SSDには及びませんが、HITACHI TRAVELSTAR 500GB 7200rpm
に再度換装してArchLinuxをインストールしました。
快適です。

それにしても、HDD、安くなりましたね。。(笑

これでまたしばらくはArchLinux万歳な日々が続きそうです。

2011/03/17

Unityで敵が画面内におさまっているか調べる

先日から、ベジエ曲線を使ったビームを作っていますが、ターゲット(敵)をロックオンして
複数のターゲットに対してビームをうてるようになりました。

ただ、一旦ロックオンしてしまうと、ターゲット画面に表示されなくなった後もそのまま
ロックオンしたままなので、今日はターゲット(敵)が画面から見えなくなったら、その敵の
ロックオンは解除しよう。。ということに。

そこで、メインカメラ用のスクリプトに以下のようなメソッドを追加して、
逐次(Updateが呼ばれるたびに)呼び出すようにしました。

def IsInSight(target as GameObject):
    if not target:
        return false

    targetPos as Vector3 = camera.WorldToViewportPoint(target.collider.bounds.center)

    if targetPos.x < 0.0 or targetPos.x > 1.0:
        return false
    if targetPos.y < 0.0 or targetPos.y > 1.0:
        return false
    return true

いやはや、いろいろと考えることがありますね。。
ぼちぼちがんばります。

2011/03/16

ベジエ曲線のレーザー、ちょっと修正。

ベジエ曲線を使ったレーザービーム、自分の機体の向きによってビームの出る位置、射出方向が
まちまちになってしまったので、またまた苦手な計算とにらめっこ。。

今まではZ軸に回転させない状態のベジエ曲線の制御点をVector3で定義しておいて、
レーザー描画時に-90度, -45度, 45度, 90度回転させた制御点を計算していましたが、
その計算方法にミスがありました。

修正前: 制御点の回転角度だけしか考えてませんでした。
  rot = Quaternion.AngleAxis(angle, transform.forward)

修正後:制御点の回転角度、機体自体の回転状態を考慮するようにしました。
  rot = Quaternion.AngleAxis(angle, transform.forward) * transform.rotation


それ以外の処理は変更なしで、上記で求めたQuaternionに、制御点のVector3を積算して
Z軸まわりに制御点を回転させました。

いやはや、ようやく思い通りの結果になって、とてもスッキリしました。(笑

2011/03/14

ベジエ曲線のレーザー、4本にしてみました。

ベジエ曲線を使ったレーザー、4本に増やしてみました。
基本的には、1本の時と同じなのですが、それぞれのレーザーを生成する際、
ベジエ曲線の制御点の座標を自機の正面に対して任意の角度で回転させています。
任意の角度に回転させる方法として、今回は

rot = Quaternion.AngleAxis(angle, transform.forward)

で自機の正面に対するクォータニオンを求め、それをベジエ曲線の制御点(2番目、3番目)に
積算しています。



だた、自機の向きによっては、4本のレーザーが思ったように射出されないので、
計算方法は改良したほうがよさそうです。。(笑

まあ、ドラフトとしてはこんなものだということで。
これから少しづつブラシアップしていきます。

面白いんですが、頭のなかが沸騰しそうになります。。

2011/03/13

ベジエ曲線を使ってレーザービーム

いやはや。。ようやくベジエ曲線を使ってレーザービームらしきものを飛ばすことができるようになりました。
とはいっても、まだまだ手直ししないといけない部分が満載なのですが。。


赤色の線は、制御点を結んだもので黄緑色の線が3次ベジエ曲線。。のはず。(笑
でもなんか違っているような気もします。


それはさておき、とりあえずソースコードを貼付けてみました。
JavaScriptではなくBooで記述したのであまり参考にはならないかも(笑
(SyntaxHighlighterはBooのシンタックスカラーリングをしてくれないみたいなので、Pythonとして表示してみました。)

下記のBezierBeam1は、Weaponというクラスから派生したもので、BezierBeam1のメソッド_UpdatePositionは、その親クラスのメソッドUpdateから呼び出されます。つまり更新のタイミングでかならず呼び出されます。

やっていることはかなり単純で、メソッド_Blendで求めたベジエ曲線上の点の座標をtransform.positionのx, y, zそれぞれに設定しているだけです。

メソッド_Blendでは、ベジエ曲線の公式で単純に計算しているだけなので、高速化とかそのあたりについては全く考慮されていません。


import UnityEngine

class BezierBeam1 (Weapon):
    # PUBLICS
    public offsetQ1 as Vector3 = Vector3(0.0, 1.0, 1.0)
    public offsetQ2 as Vector3 = Vector3(0.0, 0.5, 1.5)
    # PRIVATES
    _goalPosition as Vector3 = Vector3.zero
    _lastT = 0.0
 
    virtual def _UpdatePosition(initialPos as Vector3):
        posQ0 = initialPos
        posQ1 = posQ0 + offsetQ1
        posQ2 = posQ0 + offsetQ2
        posQ3 = _goalPosition
 
        Debug.DrawLine(posQ0, posQ1, Color.red)
        Debug.DrawLine(posQ1, posQ2, Color.red)
        Debug.DrawLine(posQ2, posQ3, Color.red)

        _lastT += Time.deltaTime
        if _lastT > 1.0:
            _lastT = 0.0

        pos as Vector3
        pos.x = _Blend(_lastT, posQ0.x, posQ1.x, posQ2.x, posQ3.x)
        pos.y = _Blend(_lastT, posQ0.y, posQ1.y, posQ2.y, posQ3.y)
        pos.z = _Blend(_lastT, posQ0.z, posQ1.z, posQ2.z, posQ3.z)
        transform.position = pos
  
    def _Blend(t as single, q0 as single, q1 as single, q2 as single, q3 as single):
        wt as single = 1.0 - t
        val as single = (q0 * wt * wt * wt) + (3 * q1 * wt * wt * t) + (3 * q2 * wt * t * t) + (q3 * t * t * t)
        # Debug.Log("Blend = ${t} ${q0}, ${q1}, ${q2}, ${q3}, ${val}")
        return val

    def SetGoalPosition(goal as Vector3):
        _goalPosition = goal


さて、現状だと1本のビームだけで寂しいので、もう少し本数を増やしてみようかと思っています。