2026年8月28日金曜日

師匠の難題2

今回はブレゼンハムアルゴリズムで線を引いていきます

ソースここから

import pyxel

class VBresenham:
    
    def __init__(self):
        self.l_side = 0
        self.s_side = 0
        
        # horizonatial side is longer
        self.b_long_hs = True

        # Direction (1 or -1)
        self.dct_x = 1
        self.dct_y = 1

        self.counter = 0
        self.adder_x = 0
        self.adder_y = 0
        
        self.start_x = 0
        self.start_y = 0

    # start x,y terminus x,y
    def setup(self,s_x,s_y,t_x,t_y):
        
        self.b_long_hs = True
        self.dct_x = 1
        self.dct_y = 1
        self.counter = 0
        self.adder_x = 0
        self.adder_y = 0

        self.start_x = s_x
        self.start_y = s_y
        
        if (s_x > t_x):
            self.l_side = s_x - t_x
            self.dct_x = -1
        else:
            self.l_side = t_x - s_x

        if (s_y > t_y):
            self.s_side = s_y - t_y
            self.dct_y = -1
        else:
            self.s_side = t_y - s_y
            
        if (self.s_side > self.l_side):
            # swap
            self.s_side,self.l_side = self.l_side,self.s_side
            self.b_long_hs = False

        self.l_side += 1
        self.s_side += 1
        return self.l_side

    def step(self):
        self.counter += self.s_side
        i = self.counter - self.l_side
        
        ret_x = self.start_x+self.adder_x
        ret_y = self.start_y+self.adder_y

        if self.b_long_hs:
            self.adder_x += self.dct_x
            if i >= 0:
                self.counter = i
                self.adder_y += self.dct_y
        else:
            self.adder_y += self.dct_y
            if i >= 0:
                self.counter = i
                self.adder_x += self.dct_x
                
        return ret_x,ret_y

class App:

    def __init__(self):
        # 画面サイズ 160x120 で初期化
        pyxel.init(160, 120, title="Line")

        self.vbre = VBresenham()

        pyxel.run(self.update, self.draw)

    def update(self):
        pass

    def draw(self):
        # 画面を黒(色番号0)でクリア
        pyxel.cls(0)

        reg = self.vbre.setup(80,60,pyxel.mouse_x,pyxel.mouse_y)

        for i in range (reg):
            x,y = self.vbre.step()
            pyxel.pset(x,y,6)

App()

ソースここまで

※8/29 ブレゼンハムのクラスを流用した際に暴走するので修正しました

マウスの動きに合わせて線を引くプログラムです。
…今回初めて複数の戻り値を持つ関数や、変数のスワップを使ったのですが、Python って便利な言語なんですね…言語の進化を感じます。

解説
  • setup の最後でなぜ長辺と短辺に1を加算しているのか?

self.l_side += 1
self.s_side += 1

の部分ですね。コレ、前回もやっていたのですが、解説してませんでした…

前回の図を使うとこんな感じ…
ざっくり言うと、計算は「格子」の座標系、描画は「格子の中」としているから。
実は加算しなくても動作するのですが、どうしてそんな事をしているのか?
例えば、始点と終点が同一点だったら?短辺の値が同一(=短辺が0だったら)?
これが3次元ポリゴンであったなら理屈上ポリゴンは厚みが無いので「描画しない」が正解になりますが、2次元の描画の場合は描画を行います。
つまり、最低限の描画を保障する為に加算しています。…あと、この方が描画が綺麗だったりするんですよね…。

  • アセンブラで書くなら
前回に引き続き、setup 関数でループ回数を取得し、 step 関数をループ回数分実行する処理になっています。

…と言う事は当然中に書かれている

if self.b_long_hs:

の分岐処理はループ回数実行されますが…毎回結果は同じです。
これを毎回分岐処理するといろいろ勿体ないので、アセンブラで書く場合はジャンプ先を上書きしたりします。

C言語で書くなら

goto vs_longer

// X軸の方が長かった場合の処理
goto end

// Y軸の方が長かった場合の処理
vs_longer:
// 以後最終処理
end:

のような記述をして、状況に応じて初期化時に goto vs_longer を書いたり消したりします。
アセンブラで変数として使用するレジスターは有限な上に数が少ないので使わないで済むならその方が良いですし、分岐処理が減ると数ステップ(クロック)処理が早くなります。線を引く際のドット数分全てにこの高速化がかかって来るのでバカにできません。
…まあ、現代のCPUパワーでこんな事を気にしないといけない状況は皆無だと思いますが。

同様に

self.dct_x
self.dct_y

この二つの変数、ベクトルのXとYがどちらに進むのかを1、もしくは-1で保存して、step 関数内で加算しているのですが…
ほぼ全てのCPUには1加算する Inc 命令と 1減算する Dec 命令が存在し、大抵高速に動作します。
なので

self.adder_y += self.dct_y


self.adder_x += self.dct_x

にあたる処理部分を初期化時に Inc や Dec に書き換えればレジスターも節約できるし、処理も高速化できます。


ただし…
これらのプログラム自身が自分のプログラムを上書きする…と言う動作は昔のプログラムでは有効でしたが、現代ではセキュリティの関係からメモリを書き換え禁止で保護して不可能になっているかもしれません。
そもそも、プログラム実行中にプログラムが書き換わるのでデバッグのやり辛さが尋常ではありません。
それでも、これらの処理で高速化を積み重ねる事が有効だった時代があった(今でも有効ですが)のですよ…遥か昔のお話なんですけどね…。


今回直線のプログラムを書きましたし、次回は回転を構成するもう一つのパーツ、三角関数テーブルを作って線の回転を作ってみます。

2026年8月24日月曜日

ひょっとしなくても

ニコ動でアニメを観てる身として、今期観ている「対あり」。
核となる格ゲーシーンが作画労力0という画期的なアニメ(と言っても、50年前の「まんがはじめて物語」等からある手法ですが)で、その分の労力をちゃんとアニメパートに回しているので作画クオリティも高い良作なんですが…

やたら丁寧なパロディネタ
ミリ残りから逆転できる白百合
カフェの使用キャラが春麗

…原作はミリも知らないのですが

レッツゴージャスティーン!

…この流れでやらない方がびっくりですよ。調べてみたら22年前のEVOでのウメハラの試合なんですが…
スト3とスト6の違いもありますが、撮影協力ウメハラが乗るんでしょうか?
実は対戦した二人が10年後の14年にエキシビジョンでこの時のネタを再現(しかも左右逆)したらしいのですが…今もビーストはこの操作をできるのか?
場外戦にも期待ですね。

師匠の難題1

 ではいきなりソースコード。

ソースここから

import pyxel

class Bresenham:
    
    def __init__(self):
        self.l_side = 0
        self.s_side = 0
        self.counter = 0
        self.adder = 0

    # 長辺、短辺を入力
    def setup(self,l_side,s_side):
        self.l_side = l_side+1
        self.s_side = s_side+1
        return self.l_side
        
    # 現在の短辺の位置を返し、次の短辺の位置を計算
    def step(self):
        ret = self.adder
        self.counter += self.s_side
        i = self.counter - self.l_side
        if i >= 0:
            self.counter = i
            self.adder += 1
        return ret

class App:

    def __init__(self):
        # 画面サイズ 160x120 で初期化
        pyxel.init(160, 120, title="RollBlt")
        self.add_x = 0
        pyxel.load("yajirushi.pyxres")
        
        pyxel.run(self.update, self.draw)

    def update(self):
        # 処理の都合上制限をかけている
        if pyxel.btn(pyxel.KEY_LEFT):
            self.add_x -= 1
        if pyxel.btn(pyxel.KEY_RIGHT):
            self.add_x += 1
        if self.add_x < 0:
            self.add_x = 0
        if self.add_x > 15:
            self.add_x = 15

    def draw(self):
        # 画面を黒(色番号0)でクリア
        pyxel.cls(0)

        bre = Bresenham()
        rag = bre.setup(l_side=15,s_side=self.add_x)

        for y in range (rag):
            add_x = bre.step()
            for x in range (32):
                pyxel.blt(80+add_x + x, 60 + y, 0, x, y, 1, 1)

App()

ソースここまで

そして実行結果
なんだか、斜めにずれていきます。
今回の主役はこの斜めを作るアルゴリズム…ブレゼンハムアルゴリズムです。
まあ、検索すればいくらでも解説が出てくる古典の描画アルゴリズムですが…
基本、こんな感じ。
カウンター0から、長辺方向(この場合は横)に向けて短辺の数(この場合は3)を足していく。で、カウンターが長辺より大きくなったら、カウンターの値を長辺の数(この場合は5)引いて次の描画の際に1段ずらす…以後繰り返し。

はえー、昔の人は賢いな…と、思うのですが原理は簡単です。
小学生が掛け算を習う際に、掛け算とは一方の数をもう一方の数の分だけ足したものと習います。
短編の数を長辺の数分足しているのですから、長辺で引く部分を除いたカウンターの総和は短辺と長辺をかけたもの(この場合は15)となり、当然、その数は長辺で割り切れて、割った答えは短辺。
カウンターの最終値になるまでに長辺で割り切れる回数は当然短辺と同じ(この場合3回)。
では、その割り切れるタイミングは?
…と考えれば理解が早いと思います。

ただし、このプログラムは穴だらけ
Pyxel 座標系はこんな感じ(右方向がXの正の方向、下方向がYの正の方向)ですが。今回のアルゴリズムは長辺>短辺かつ、どちらもプラス方向に値が加算される前提で組まれています。

※中心から各方向へ延ばしたベクトルのX、Y要素の関係

具体的には45度しかカバーできていません。
次回は、ちゃんとした2次元ベクトル上のブレゼンハムクラスを作って、直線を引くプログラムを書いていきます。

2026年8月21日金曜日

掘り、終了

E2-3乙1周目(A勝利)

E2-3乙7周目(初のS勝利)

油と弾が26日の友軍まで待つと自然回復量を回復しきってしまう為、周回をしてみました…案の定A勝利しかできず…(というか、途中撤退1C敗北1だったりします)。
初めてのS勝利で出てくれました、流石に大和、アイオワ連撃をセットしているのでこれ以上掘りたくは無かったですね。

これにて我が艦隊に不在の艦はアイツボルとデイスのみになりました
今後掘る物があるとすれば…

E5
ジャンバール(2隻目)
グローリアス(2隻目)
プリンツオイゲン(3隻目)
アークロイヤル(5隻目)
まるゆ

E4
モガドール(2隻目)

E3
百一号(3隻目)

E1
海防艦

…まあ、正直強く欲しいのはジャンバールとモガドールくらいですかね。
空母のレベリングがあるのでE3とE4は回りますが…それほど本気で回る事は無くなる予定です。とりあえず、全部終わってほっとしてます。

※8/24追記
E3を回っていて、2隻目のラングレーが出て、まあ御駄賃的によかったね…とか思っていたのですが…
2隻目。
君、出るんかい。しかも丁で。なんか申し訳ない感じですわ。レキシントンとサラトガの育成は終わりましたが、しばらくメリーランド、ワスプ、ラングレーの育成でも良いのかも。
ただ、流石にベアルンが75まで育ったら欧州の周回をしようかと思うのですが…
正直、ハロウィン用の天霧及び三隈が溢れていて新艦を拾う枠も無いのですよね。仕方なく三隈改を合成に回していますが、若干勿体ない気もしています。

2026年8月18日火曜日

師匠の難題0

ゲーム業界を引退し、プログラマーを辞めて20年近くが経ちます。
技術不足ゆえ早期退職を促され、受理して辞めた訳で、未練は無いのですですが、一つ心残りは有りました。

プログラマーになって最初の年に師匠(先輩プログラマ)から出された課題を解いておらず、そのまま有耶無耶になり、引退してしまったのです。

で、その課題が画像の回転コピーを作ってみよでした。
私がプログラマーになった年は、ゲーム業界に入って2年目の97年です。
当時のVC++でも BitBlt 関数で回転コピーはできたと思いますが、当然ライブラリや出来合いの関数を使えと言っているわけでは無いことは明白です。ちなみに師匠はアセンブラの名手で、自作のライブラリを作り、ゲーム機のCPUに合わせてカスタマイズし続けていました。

97年はペンティアム2が出たてくらいで、インテルの486や下手すると386すら現役の時代でした。386は後に386SXと改名され、コプロセッサが搭載された拡張版が386DXとなります。で、このコプロセッサが無いとどうなるか?私もはっきり覚えていないのですが、実数の計算ができませんでした。

つまり…アセンブラ化を前提とした実数を使用しない回転コピーを作ってみよ、という意図だったのではないかと推測されます。

なのでひとまず、縛りと仕様を決めます

仕様
転送元画像の一辺の長さは最大128ドット
転送先の画面の一辺の長さは最大512ドット

縛り
実数は使わない
割り算は使わない
三角関数は計算済みテーブルのみを使用(単位長128で、128度法)

まあ、日和って掛け算は使うんですけどね。
以前セットアップした Pyxel ライブラリを使います。…というのも、以前 Kivy の描画関数で描画プログラムを書いた際は遅すぎてとても実用に耐えなかったからです。
ちなみに Pyxel はパレットカラーマッピングですが、本質的に画像の転送方法が課題で色はどうでも良いので気にしません。


で、ひとまず作ってみたのはこんなプログラム。

ソースここから

import pyxel


class App:

    def __init__(self):
        # 画面サイズ 160x120 で初期化
        pyxel.init(160, 120, title="RollBlt")
        
        # 矢印画像をバンクへ読み込み
        pyxel.load("yajirushi.pyxres")
        
        pyxel.run(self.update, self.draw)

    def update(self):
        pass

    def draw(self):
        # 画面を黒(色番号0)でクリア
        pyxel.cls(0)
        
        # 1ドットずつ転送
        for y in range (16):
            for x in range (32):
                pyxel.blt(80 + x, 60 + y, 0, x, y, 1, 1)

App()

ソースここまで

出力結果

バンクからわざわざ1ドット分の画像を縦横の長さ分転送を繰り返して送っています。
普通こんな無駄な転送を行いませんが今後必要になるコードです。次回からはこれを改造していきます。

2026年8月17日月曜日

シックスパックは6個に分かれていても良い?

…と、言うわけで腹筋を絞ってみました。実は横腹については、肋骨がはみ出たりしています。で、腹直筋ですが…6個に分けました。そして、最上段は剣状突起より下に…腹筋は個人差が出やすく段違いになったりはするのですが、ここまで下にずれることがあるのでしょうか?正直、解剖学的に無いような気もするのですが、大腿骨の時点でデザインを優先してますし今更な話です。
これにて、縦長に別れていたシックスパックをほぼ正方形位の比率に、また、胸のスペースを広めに取りました。


大胸筋周辺が適当ですが、ここは作り込む意味がないのでこうなっています。
個人的にはふっ直筋と外腹斜筋の段差が揃ったのでポリゴンのセグメント分けがしやすくなりましたね。

動きは


まあ、以前と大して変わりません。流石に横腹が薄いのが気になりますが、裏返ってしまうポリゴンを調整するのがここら辺が精一杯でこれ以上の調整は難しい感じでした。

キック




うーん感慨深いですね。
ちなみにこの時点でボーンの数は700弱あったりします。
現在はここから前腕のモデリング中…実は手首に捻りボーンがあったり(正解は肘に捻り=尺骨と橈骨の動作があって、手首には無い)色々不具合が見つかったので修正中です。
冬までには手首が繋がると良いなあ… 

2026年8月10日月曜日

実は買ってた MINISFORUM Elite Mini X500


はい、以前買おうと思ったら盗難があって買えなかった奴ですが、実は先月末に入手はできてました…ほとんど触れていなかったのです。
見ての通りソフマップ大宮で購入。予算を1万円オーバーしたんですけどね…

んで、物は良さそうかと言うと…
当然中古ですが、状態は良かったです…ただ、ものそのものはと言いますと…

パースのせいでは無く、普通に巨大なACアダプター。

妙に出来の悪いケース。
合わせ目が上手く繋がっていないケース。実は本体横にあるネジのうち一本がCワッシャーでもかかっているかのように外れなかったのですが、開けてみたら、内部の薄い鉄板に直接ねじバイトを切っているのですが、このネジ穴が潰れてしまってネジが空回りして外れなくなっていました。
普通の工業製品の場合は鉄板にネジ受けを溶接するのですが、そう言う事をしていないようです…。
ネジを強く回せばネジ穴が潰れるのですが、問題はこの潰れたネジ穴から金属のくずが出る事で、これが内部の回路に触れてショートすればそこでアウトな訳です。
商品パッケージとしてはともかく、工業製品としての出来はかなり悪いという印象ですね。

※8/23追記
内側にある金属板に直接ネジ穴を切っている。
そもそもなぜ内側に金属板を付けたのかも謎で、ケースが樹脂なら樹脂ケースに金属製のネジ受けを埋め込めんだ方がコスト的にも良いハズだが、そうはなっていない。
ちなみに、樹脂パーツ埋め込み式の金属ネジ受けの例(Lenovo YOGA310)。

尚、ケースから本体を取り出す時は面倒でも必ず Wifi アンテナを外そう。配線がタイト過ぎてアンテナが千切れます(1敗)。


早速64GBメモリに載せ替えましたが
BIOSで設定できるVRAMメモリのサイズが16GBまでとなっています。
この為、ウィンドウズで使用する場合はデフォルトの16GBx2の状態のままで問題なく、増やしても基本的に効果はありません。

今後このマシンは Linux に載せ替えていきますが…とりあえず Windows の SSD も残しておきたいので別の Linux マシンに低容量 SSD を載せて玉突き的に 256GBSSD を載せようとしています。景気よく新品の SSD を買ってきて Linux に載せ替え~とできないご時世ですので。
で、VRAM64GBが出来たら背景画像のAI生成をやらせてみたいのですが…上手く行きますでしょうか。

※8/23 追記
例によって、LMDE7 と STEAM をインストール、試しにグラブルライジングを入れてみましたが…動かない事は無い…超必殺技がカクついたり、バトル前後のデモがカクついたりするが…。少なくとも、Core i5 1245u よりはかなりマシ。そりゃまあ、ミニPCと言ってもコレはデスクトップですからね。ただ、ちゃんとゲームになるかと聞かれれば辞めた方がいいレベルです。又、2GBのVRAMを持った dGPU アリより動いたので、VRAMが16GBと認識されている事も効果はあるようですね。

今後やりたい課題は、ライフワークのモデリング、そしてこれに合わせる背景のAI生成。
そしてもう一つプログラムの課題をやりたいのですが…なんで今の状況で時間に追われるやら…
ひとまず Linux マシンの整理にいそしむとします。