2019年04月27日

wxLuaApp で OpenGL やってみた

 球体の表面を色分けした図を描いて GIF アニメーションにする、という案件が発生した。こういうやつです。

20190427-1.gif 20190427-2.gif

 OpenGL を使えばできそう、というのはすぐに思いついたのだが、どう実装するかでしばし悩んだ。最近の流行りで言えば WebGL なんだろうけど、少し調べてみてハードルが高そうと感じた。自分の OpenGL の知識が相当古いところで止まっているのが原因かもしれず、本当はそちらをアップデートするのが正攻法なのだが、今回はとにかく早く結果が欲しい。

 そこで、Molby のコードを流用することにした。さすがに C で書くのはどうかと思ったので、最近のマイブームである wxLuaApp で OpenGL に挑戦。実はこれが結構大変でありまして、WebGL を使ったほうが未来につながったかもと 15% ぐらいは思ったりしている。

sinCache = {}
sinCacheSect = 0
idle_count = 0
phase = 0
mode = 0
cflag = false
modefunc = function () return 1 end

function OnIdle(event, canvas)
  local max_count = 32
  if mode < 9 and idle_count < max_count then
    if idle_count == 0 then
      if mode == 0 then
        modefunc = function (x, y, z) return 1 end
      elseif mode == 1 then
        modefunc = function (x, y, z) return x end
      elseif mode == 2 then
        modefunc = function (x, y, z) return -y end
      elseif mode == 3 then
        modefunc = function (x, y, z) return z end
      elseif mode == 4 then
        modefunc = function (x, y, z) return 2 * z * z - x * x - y * y end
      elseif mode == 5 then
        modefunc = function (x, y, z) return 2 * z * x end
      elseif mode == 6 then
        modefunc = function (x, y, z) return -2 * y * z end
      elseif mode == 7 then
        modefunc = function (x, y, z) return x * x - y * y end
      elseif mode == 8 then
        modefunc = function (x, y, z) return -2 * x * y end
      end
    end
    CaptureCanvas(idle_count, max_count, mode, cflag)
    if cflag then
      idle_count = idle_count + 1
      if idle_count == max_count then
        idle_count = 0
        mode = mode + 1
      end
    end
    cflag = not cflag
  end
end

function ColoredVertex(c, r, x, y, z)
  local rgba
  local y2 = modefunc(x, y, z) * math.cos(phase)
  if y2 >= 0 then
    rgba = {(1 - y2) * 0.8, (1 - y2) * 0.8, 1 - (1 - y2) * 0.2, 1}
  else
    rgba = {1 - (1 + y2) * 0.2, (1 + y2) * 0.8, (1 + y2) * 0.8, 1}
  end
  gl.Material(gl.FRONT_AND_BACK, gl.DIFFUSE, rgba);
  gl.Vertex(r * x + c[1], r * y + c[2], r * z + c[3])
end

function DrawSphere(c, r, sect)
  local m = math.floor(sect / 4)
  local n = m * 4
  if sinCacheSect ~= n then
    sinCacheSect = n
    for i = 1, m * 5 + 2 do
      sinCache[i] = math.sin(math.pi * 2 / n * (i - 1))
    end
  end
  local s = sinCache
  for i = 1, n + 1 do
    gl.Begin(gl.QUAD_STRIP)
    for j = 1, n / 2 do
      gl.Normal(s[j] * s[i + m], s[j] * s[i], s[j + m])
      ColoredVertex(c, r, s[j] * s[i + m], s[j] * s[i], s[j + m])
      gl.Normal(s[j] * s[i + 1 + m], s[j] * s[i + 1], s[j + m])
      ColoredVertex(c, r, s[j] * s[i + 1 + m], s[j] * s[i + 1], s[j + m])
    end
    gl.End()
  end
  gl.Begin(gl.TRIANGLE_FAN)
  gl.Normal(0, 0, 1)
  ColoredVertex(c, r, 0, 0, 1)
  for i = n + 1, 1, -1 do
    gl.Normal(s[2] * s[i + m], s[2] * s[i], s[2 + m])
    ColoredVertex(c, r, s[2] * s[i + m], s[2] * s[i], s[2 + m])
  end
  gl.End()
  gl.Begin(gl.TRIANGLE_FAN)
  gl.Normal(0, 0, -1)
  ColoredVertex(c, r, 0, 0, -1)
  for i = 1, n + 1 do
    gl.Normal(s[2] * s[i + m], s[2] * s[i], -s[2 + m])
    ColoredVertex(c, r, s[2] * s[i + m], s[2] * s[i], -s[2 + m])
  end
  gl.End()
end

function DrawModel()
  gl.MatrixMode(gl.MODELVIEW)
  gl.LoadIdentity()
  gl.Translate(0, 0, -1)
  gl.Rotate(130, 1, 0, 0)
  gl.Rotate(-45, 0, 0, 1)
  gl.Material(gl.FRONT_AND_BACK, gl.DIFFUSE, {1, 0, 1, 1});
  gl.Enable(gl.NORMALIZE)
  DrawSphere({0, 0, 0}, 0.85, 48)
end

function CaptureCanvas(n, m, mode, cflag)
  phase = 3.1415926 * 2 * n / m
  canvas:Refresh()
  canvas:Update()
  if not cflag then return end
  canvas.context:SetCurrent(canvas)
  capture = gl.ReadPixels(0, 0, 120, 120, gl.RGBA)
  data = {}
  alpha = {}
  for i = 1, #capture do
    capture[i] = math.floor(capture[i] * 255 + 0.5)
  end
  local j = 0
  for y = 1, 120 do
    for x = 1, 120 do
      local i = ((120 - y) * 120 + x - 1) * 4 + 1
      local j = (y - 1) * 120 + x
      data[j * 3 + 1] = capture[i]
      data[j * 3 + 2] = capture[i + 1]
      data[j * 3 + 3] = capture[i + 2]
      alpha[j + 1] = capture[i + 3]
    end
  end
  image = wx.wxImage(120, 120)
  datastr = string.fromtable(data)
  alphastr = string.fromtable(alpha)
  image:SetData(datastr)
  image:SetAlpha(alphastr)
  image:SaveFile(string.format("sample%d_%02d.png", mode, n))
end

function OnPaint(event, canvas)
  local dc = wx.wxPaintDC(canvas)
  if not canvas.is_initialized then
    canvas.context = wx.wxGLContext(canvas)
    canvas.context:SetCurrent(canvas)
    sz = canvas:GetClientSize()
    gl.Viewport(0, 0, sz.x, sz.y)
    gl.Enable(gl.DEPTH_TEST)
    gl.Enable(gl.LIGHTING)
    gl.LightModel(gl.LIGHT_MODEL_AMBIENT, {0.8, 0.8, 0.8, 1.0})
    gl.Light(gl.LIGHT0, gl.DIFFUSE, {1, 1, 1, 1})
    gl.Enable(gl.LIGHT0)
    gl.Light(gl.LIGHT1, gl.AMBIENT, {0.4, 0.4, 0.4, 1})
    gl.Enable(gl.LIGHT1)
    gl.Enable(gl.BLEND)
    gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
    canvas.is_initialized = true
  end
  canvas.context:SetCurrent(canvas)
  gl.ClearColor(0, 0, 0, 0)
  gl.Clear(bit.bor(gl.COLOR_BUFFER_BIT, gl.DEPTH_BUFFER_BIT))
  DrawModel()
  gl.Flush()
  canvas:SwapBuffers()
  dc:delete()
end

function main()
  --  フレームを作成
  frame = wx.wxFrame(wx.NULL, wx.wxID_ANY, "", wx.wxPoint(50, 50), wx.wxSize(120, 200))
  frame:SetClientSize(120, 120)
  --  GLCanvas を作成
  canvas = wx.wxGLCanvas(frame, wx.wxID_ANY, {}, wx.wxDefaultPosition, wx.wxSize(120, 120), 0, "GLCanvas")
  canvas.is_initialized = false
  --  イベントを接続
  canvas:Connect(wx.wxEVT_PAINT, function (event) OnPaint(event, canvas) end)
  canvas:Connect(wx.wxEVT_IDLE, function (event) OnIdle(event, canvas) end)
  frame:Show(true)
end

main()
posted by toshinagata at 00:55| 日記

2019年04月14日

ウメとナツミカン

 ウメの花が終わった後、実がたくさんついている。

20190414-1.jpg

20190414-2.jpg

 実を収穫することを目指すのかどうか、ちょっと悩ましいところ。このウメは、実ウメとして植えたものではないので、大した実は成らないような気がする。でも、これだけ実がついていると、落ちるに任せるのもちょっともったいない気もするんだよな。一応実を間引いて、根元に肥料はあげておいた。

 一方、ナツミカンの方は、今のところ葉芽ばかり出てきている。芽吹いてくる前に大きく剪定したので、木が葉を伸ばす方に向いてしまったかも。今年成りすぎたので来年は実は少ない年だけど、ゼロはさびしいな。なんとかこれから少しでも花芽が出てきてくれるといいのだけど。

20190414-3.jpg

タグ:園芸
posted by toshinagata at 15:57| 日記

2019年04月13日

ドラゴンズ3年ぶり貯金

 京田・堂上の満塁本塁打でタイガースに大勝、3年ぶりに貯金だって。

 京田と堂上が同時に出てるってどういうこと?と思ったが、遊撃と二塁なんですね。確か開幕戦は堂上が遊撃で京田はベンチだったよな。調べてみると、固定されているのは一塁のビシエドと三塁の高橋周平だけで、二塁・遊撃は京田・堂上・阿部(・三ツ俣)、外野手は平田・大島・アルモンテ・福田、捕手は加藤・大野をそれぞれ併用している。ドラゴンズは今年から与田監督が指揮を執っている。新しい監督は新戦力を試したがる、というけど、この中で新顔と言えそうなのは加藤匠馬ぐらいだから、結局は昨年までのメンバーがイマイチ伸び悩んでいたということなんだろうな。

 堂上直倫、30歳。愛工大名電〜ドラゴンズ1位指名、父も兄もドラゴンズの選手。やりにくくてしょうがなかっただろう。一昨年から京田の控えに回され、このまま沈んでいくのかと思ったら、今年の開幕はその京田を控えに追いやってスタメン出場。今日の試合など、一番遊撃京田、五番(!)二塁堂上、と、すっかり「両雄並び立つ」趣きとなっている。堂上が活躍したら、古参のドラゴンズファンは嬉しいと思うよ。

 京田陽太、24歳。期待の新人として一昨年から半ば「特別扱い」で抜擢され、昨年も(やや成績を落としたとはいえ)堅実に結果を残したが、昨オフに根尾昂がドラフト1位指名されて、突然雲行きが怪しくなった。冷静に考えれば、大卒で2年間結果を残した選手と高卒の新人選手が横一線で競争できるわけもないのだけど、マスコミ(というか主に中日新聞と中日スポーツ)がとにかく煽るので、まあこちらもやりにくかったことだろう。ただ、もともと真面目で熱心な選手なはずなのだけど、オープン戦終盤で与田監督の評価がえらく下がっていた。まさか堂上に追い抜かれることはないだろう、と高をくくっているようなところが見え隠れしていたのかな。

 野球は基本パ・リーグを見ているのだけど、名古屋に住んでいる以上、ドラゴンズは強いに越したことはないのですよ。一応セ・リーグではドラゴンズファンだしな。うまく選手を伸ばして、いい結果を出して欲しい。

タグ:野球
posted by toshinagata at 23:55| 日記
email.png
Powered by さくらのブログ