球体の表面を色分けした図を描いて 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()