Final ENNTool commit.

This commit is contained in:
Lucio Andrés Illanes Albornoz 2018-07-05 15:14:37 +02:00
parent 4817313bbc
commit 52ffd7f4c4
16 changed files with 178 additions and 80 deletions

View File

View File

@ -5,13 +5,20 @@
# This project is licensed under the terms of the MIT license.
#
# TODO:
# 1) -A: render frame #1, render frame #2, ...
# 2) -s: effects: rotate, smash into bricks, swirl, wave, ...
# 3) Feature: include ETA @ progress bar
# 4) Feature: autodetect video width from widest mircart
# 5) Feature: render mircart as 3D blocks vs flat surface
# 6) OpenGL: use VAOs + glVertexAttribFormat + glVertexAttribBinding
# 7) use names @ optdict[] + set from optdefaults
# 1) -A, -S: replace w/ -s, implement animation script: render frame #1, render frame #2, ...; scrolling script; effects: rotate, smash into bricks, swirl, wave, ...
# 2) Feature: include ETA(s) @ progress bar(s)
# 3) Feature: autodetect video width from widest mircart
# 4) Feature: render mircart as 3D blocks vs flat surface
#
# 1) Optimisation: speed up ENNToolMiRCARTImporter
# 2) Cleanup: use names @ optdict + set from optdefaults
# 3) Feature: scrolling speed as <how many Y units>x<count of frame(s)>
# 4) Cleanup: use VAOs + glVertexAttribFormat + glVertexAttribBinding
# 5) Optimisation: split mIRC art into separate VBOs & implement rudimentary culling
# 6) Optimisation: only call glReadPixels() when changes were made relative to the last call
# 7) Split video output into separate module, switch to GUI
# 8) FBOs http://www.songho.ca/opengl/gl_fbo.html
#
from getopt import getopt, GetoptError
@ -20,7 +27,7 @@ from OpenGL.GL import *
import os, sys, time
import wx
from ENNToolGLCanvasPanel import ENNToolGLCanvasPanel
from ENNToolGLCanvasPanel import ENNToolGLCanvas, ENNToolGLPanel
from ENNToolGLTTFTexture import ENNToolGLTTFTexture
from ENNToolGLVideoWriter import ENNToolGLVideoWriter
from ENNToolMiRCARTImporter import ENNToolMiRCARTImporter
@ -52,8 +59,6 @@ class ENNToolApp(object):
if "-h" in optdict:
usage(sys.argv[0]); exit(0);
elif not "-o" in optdict:
raise GetoptError("-o fname must be specified")
elif not len(argv):
raise GetoptError("at least one MiRCART input fname must be specified")
@ -82,8 +87,8 @@ class ENNToolApp(object):
print("\r[{:<50}] {}%".format(
("=" * int(progressDiv * 50)), int(progressDiv * 100)), end=endChar)
# }}}
# {{{ modeScroll(self, argv, optdict, GLVideoWriter, panelGLCanvas, fps=25, scrollRate=0.25): XXX
def modeScroll(self, argv, optdict, GLVideoWriter, panelGLCanvas, fps=25, scrollRate=0.25):
# {{{ modeScroll(self, argv, optdict, GLVideoWriter, GLpanel, GLpanel, fps=25, scrollRate=0.1): XXX
def modeScroll(self, argv, optdict, GLVideoWriter, GLcanvas, GLpanel, fps=25, scrollRate=0.1):
MiRCART = []
if "-v" in optdict:
time0 = time.time()
@ -93,53 +98,74 @@ class ENNToolApp(object):
if "-v" in optdict:
print("mIRC art import delta {:.3f}ms".format((time.time() - time0) * 1000))
curY, rotateX, rotateY, translateY = 0, 0, 0, scrollRate
if "-v" in optdict:
time0 = time.time()
artTextureId, artInfo = ENNToolGLTTFTexture(MiRCART, optdict["-R"], optdict["-r"]).getParams()
if "-v" in optdict:
print("TTF texture generation delta {:.3f}ms".format((time.time() - time0) * 1000))
artVbo, artVboLen, lastY, numVertices = panelGLCanvas.renderMiRCART(artInfo, MiRCART, cubeSize=optdict["-R"])
artVbo, artVboLen, lastY, numVertices = GLcanvas.renderMiRCART(artInfo, MiRCART, cubeSize=optdict["-R"])
if "-v" in optdict:
print("{} vertices".format(numVertices))
w, h = panelGLCanvas.GetClientSize(); w, h = max(w, 1.0), max(h, 1.0);
while True:
self.printProgress(curY, lastY)
for numFrame in range(fps):
panelGLCanvas.renderFrame(artTextureId, artVbo, artVboLen)
def scrollFrameFun():
curY, rotateX, rotateY, translateY = 0, 0, 0, scrollRate
w, h = GLcanvas.GetClientSize(); w, h = max(w, 1.0), max(h, 1.0);
def scrollFrame():
nonlocal curY
self.printProgress(curY, lastY)
GLcanvas.renderFrame(artTextureId, artVbo, artVboLen)
if translateY:
glTranslatef(0, translateY, 0); curY += translateY
if rotateX:
glRotatef(rotateX * (180.0/w), 0.0, 1.0, 0.0)
if rotateY:
glRotatef(rotateY * (180.0/h), 1.0, 0.0, 0.0)
GLVideoWriter.saveFrame()
if curY >= lastY:
self.printProgress(curY, lastY); break;
GLVideoWriter.saveVideo()
if "-o" in optdict:
GLVideoWriter.saveFrame()
else:
GLcanvas.SwapBuffers()
if curY >= lastY:
self.printProgress(curY, lastY)
if "-o" in optdict:
GLVideoWriter.saveVideo()
return False
return True
return scrollFrame
if "-o" in optdict:
frameFun = scrollFrameFun()
while True:
if not frameFun():
break
else:
GLpanel.frameFun = scrollFrameFun()
self.wxApp.MainLoop()
# }}}
# {{{ __init__(self, argv): XXX
def __init__(self, argv):
argv, optdict = self.parseArgv(argv)
wxApp = wx.App(False)
appFrameSize = optdict["-r"]
appFrame = wx.Frame(None, size=appFrameSize); appFrame.Hide();
appPanelSkin = wx.Panel(appFrame, wx.ID_ANY)
self.wxApp = wx.App(False)
appFrameSize = [c + 128 for c in optdict["-r"]]
self.appFrame = wx.Frame(None, size=appFrameSize)
appPanelSkin = wx.Panel(self.appFrame, wx.ID_ANY)
videoFps, videoPath = int(optdict["-f"]), optdict["-o"]
panelGLCanvas = ENNToolGLCanvasPanel(appPanelSkin, size=appFrameSize)
panelGLCanvas.initOpenGL()
panelGLCanvas.initShaders()
GLVideoWriter = ENNToolGLVideoWriter(videoPath, panelGLCanvas.GetClientSize(), videoFps=videoFps)
videoFps, videoPath = int(optdict["-f"]), optdict["-o"] if "-o" in optdict else None
GLpanel = ENNToolGLPanel(appPanelSkin, size=optdict["-r"], parentFrame=self.appFrame)
GLcanvas = ENNToolGLCanvas(GLpanel, optdict["-r"])
GLcanvas.initOpenGL()
GLcanvas.initShaders()
GLVideoWriter = ENNToolGLVideoWriter(videoPath, GLpanel.GetClientSize(), videoFps=videoFps)
if "-o" in optdict:
self.appFrame.Hide()
else:
self.appFrame.Show(); self.appFrame.SetFocus();
if "-v" in optdict:
time0 = time.time()
self.modeScroll(argv, optdict, GLVideoWriter, panelGLCanvas, fps=videoFps)
self.modeScroll(argv, optdict, GLVideoWriter, GLcanvas, GLpanel, fps=videoFps)
if "-v" in optdict:
print("delta {}s".format(time.time() - time0))
if "-p" in optdict:
if "-o" in optdict \
and "-p" in optdict:
os.startfile(videoPath)
# }}}

View File

@ -14,13 +14,12 @@
# Tue, 03 Jul 2018 14:34:57 +0200 [7] <https://gamedev.stackexchange.com/questions/107793/binding-and-unbinding-what-would-you-do>
#
from ENNToolMiRCARTColours import ENNToolMiRCARTColoursFloat
from OpenGL.GL import *
from OpenGL.GL import shaders
import ctypes, wx, wx.glcanvas
class ENNToolGLCanvasPanel(wx.glcanvas.GLCanvas, wx.Panel):
"""XXX"""
class ENNToolGLCanvas(wx.glcanvas.GLCanvas):
# {{{ initOpenGL(self): XXX
def initOpenGL(self):
self.glContext = wx.glcanvas.GLContext(self)
@ -32,7 +31,6 @@ class ENNToolGLCanvasPanel(wx.glcanvas.GLCanvas, wx.Panel):
glLoadIdentity(); glFrustum(-1, 1, -1, 1, 1, 100);
glMatrixMode(GL_MODELVIEW)
glEnable(GL_DEPTH_TEST)
glClearColor(0, 0, 0, 1); glClearDepth(1);
glTranslatef(-5.0, 3.0, -5)
# }}}
# {{{ initShaders(self): XXX
@ -41,12 +39,20 @@ class ENNToolGLCanvasPanel(wx.glcanvas.GLCanvas, wx.Panel):
fs = shaders.compileShader("""
#version 330 core
in vec2 fgTexCoord;
in vec2 frgTexCoord;
in vec3 frgFgColour;
in vec3 frgBgColour;
uniform sampler2D texture;
layout(location = 0) out vec4 fragColour;
void main() {
vec4 texel = texture2D(texture, fgTexCoord);
gl_FragColor = vec4(texel.r, texel.g, texel.b, 1.0);
vec4 texel = texture2D(texture, frgTexCoord);
if (texel.r == 0.0 && texel.g == 0.0 && texel.b == 0.0 && texel.a == 0.0) {
fragColour = vec4(frgBgColour.r, frgBgColour.g, frgBgColour.b, 1.0);
} else {
fragColour = vec4(frgFgColour.r, frgFgColour.g, frgFgColour.b, 1.0);
}
}
""", GL_FRAGMENT_SHADER)
@ -56,15 +62,21 @@ class ENNToolGLCanvasPanel(wx.glcanvas.GLCanvas, wx.Panel):
layout(location = 0) in vec4 vertex;
layout(location = 1) in vec2 texcoord;
layout(location = 2) in vec3 vexFgColour;
layout(location = 3) in vec3 vexBgColour;
out vec2 fgTexCoord;
out vec2 frgTexCoord;
out vec3 frgFgColour;
out vec3 frgBgColour;
uniform mat4 modelview;
uniform mat4 projection;
void main() {
gl_Position = projection * modelview * vertex;
fgTexCoord = texcoord;
frgTexCoord = texcoord;
frgFgColour = vexFgColour;
frgBgColour = vexBgColour;
}
""", GL_VERTEX_SHADER)
self.shader = shaders.compileProgram(vs, fs)
@ -86,13 +98,23 @@ class ENNToolGLCanvasPanel(wx.glcanvas.GLCanvas, wx.Panel):
# VBO vertices location
glEnableVertexAttribArray(0)
glVertexAttribPointer(0, 3, GL_FLOAT, False, 20, ctypes.c_void_p(0))
glVertexPointer(3, GL_FLOAT, 20, ctypes.c_void_p(0))
glVertexAttribPointer(0, 3, GL_FLOAT, False, 44, ctypes.c_void_p(0))
glVertexPointer(3, GL_FLOAT, 44, ctypes.c_void_p(0))
# VBO texture coordinates
glEnableVertexAttribArray(1)
glVertexAttribPointer(1, 2, GL_FLOAT, False, 20, ctypes.c_void_p(12))
glTexCoordPointer(2, GL_FLOAT, 20, ctypes.c_void_p(12))
glVertexAttribPointer(1, 2, GL_FLOAT, False, 44, ctypes.c_void_p(12))
glTexCoordPointer(2, GL_FLOAT, 44, ctypes.c_void_p(12))
# VBO foreground colours
glEnableVertexAttribArray(2)
glVertexAttribPointer(2, 3, GL_FLOAT, False, 44, ctypes.c_void_p(20))
glTexCoordPointer(3, GL_FLOAT, 44, ctypes.c_void_p(20))
# VBO background colours
glEnableVertexAttribArray(3)
glVertexAttribPointer(3, 3, GL_FLOAT, False, 44, ctypes.c_void_p(32))
glTexCoordPointer(3, GL_FLOAT, 44, ctypes.c_void_p(32))
# Clear colour and depth buffer, draw quads from VBO & clear state
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
@ -111,19 +133,27 @@ class ENNToolGLCanvasPanel(wx.glcanvas.GLCanvas, wx.Panel):
cubeBg = artMap[numRow][numCol][1]
cubeAttrs = artMap[numRow][numCol][2]
cubeChar = artMap[numRow][numCol][3]
artCell = artInfo[cubeFg][cubeBg][cubeAttrs][cubeChar]
artCell = artInfo[cubeAttrs][cubeChar]
# Top Right, Top Left
vertices += curPos
vertices += artCell[0:2]
vertices += [*ENNToolMiRCARTColoursFloat[cubeFg]]
vertices += [*ENNToolMiRCARTColoursFloat[cubeBg]]
vertices += [curPos[0] - cubeSize[0], curPos[1], curPos[2]]
vertices += artCell[2:4]
vertices += ENNToolMiRCARTColoursFloat[cubeFg]
vertices += ENNToolMiRCARTColoursFloat[cubeBg]
# Bottom Left, Bottom Right
vertices += [curPos[0] - cubeSize[0], curPos[1] - cubeSize[1], curPos[2]]
vertices += artCell[4:6]
vertices += [*ENNToolMiRCARTColoursFloat[cubeFg]]
vertices += [*ENNToolMiRCARTColoursFloat[cubeBg]]
vertices += [curPos[0], curPos[1] - cubeSize[1], curPos[2]]
vertices += artCell[6:8]
vertices += ENNToolMiRCARTColoursFloat[cubeFg]
vertices += ENNToolMiRCARTColoursFloat[cubeBg]
curPos[0], numVertices = curPos[0] + cubeSize[0], numVertices + 4
curPos[0], curPos[1] = 0, curPos[1] - cubeSize[1]
@ -135,10 +165,40 @@ class ENNToolGLCanvasPanel(wx.glcanvas.GLCanvas, wx.Panel):
GL_STATIC_DRAW)
return artVbo, len(vertices), -curPos[1], numVertices
# }}}
# {{{ __init__(self, parent, size, defaultPos=(24,24)): initialisation method
def __init__(self, parent, size, defaultPos=(24,24)):
# {{{ __init__(self, parentCanvas, size): initialisation method
def __init__(self, parentCanvas, size):
super().__init__(parentCanvas, size=size)
self.curSize = list(size)
self.parentCanvas = parentCanvas
# }}}
class ENNToolGLPanel(wx.Panel):
"""XXX"""
# {{{ onPaint(self, event): XXX
def onPaint(self, event):
eventDc = wx.PaintDC(self)
eventUpdates = wx.RegionIterator(self.GetUpdateRegion())
paintFlag = True if eventUpdates.HaveRects() else False
if self.frameFun != None:
self.frameFun()
# }}}
# {{{ onTimer(self, event): XXX
def onTimer(self, event):
if self.frameFun != None:
if not self.frameFun():
event.GetTimer().Stop()
event.GetTimer().Destroy()
# }}}
# {{{ __init__(self, parent, size, defaultPos=(24,24), parentFrame=None): initialisation method
def __init__(self, parent, size, defaultPos=(24,24), parentFrame=None):
super().__init__(parent, pos=defaultPos, size=size)
self.curPos = list(defaultPos); self.curSize = list(size);
self.Bind(wx.EVT_PAINT, self.onPaint)
self.frameFun = None
self.timerTimer = wx.Timer(self, 1)
self.timerTimer.Start(40)
self.Bind(wx.EVT_TIMER, self.onTimer, self.timerTimer)
# }}}
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -10,6 +10,7 @@
# Wed, 27 Jun 2018 16:02:12 +0200 [3] <https://www.khronos.org/opengl/wiki/How_lighting_works#Good_Settings.>
# Wed, 27 Jun 2018 16:02:13 +0200 [4] <https://www.khronos.org/opengl/wiki/Common_Mistakes>
# Wed, 27 Jun 2018 16:02:14 +0200 [5] <https://www.khronos.org/opengl/wiki/Pixel_Transfer#Pixel_layout>
# Wed, 04 Jul 2018 10:57:09 +0200 [6] <https://stackoverflow.com/questions/466204/rounding-up-to-next-power-of-2>
#
from collections import defaultdict
@ -33,33 +34,38 @@ class ENNToolGLTTFTexture(object):
def _nestedDict():
return defaultdict(ENNToolGLTTFTexture._nestedDict)
# }}}
# {{{ _drawCharList(self, artInfo, charList, pilFontBold, pilFontNormal, pilFontSize, pilImageDraw, pilImageSize): XXX
def _drawCharList(self, artInfo, charList, pilFontBold, pilFontNormal, pilFontSize, pilImageDraw, pilImageSize):
# {{{ _drawCharList(self, artInfo, charList, pilFontBold, pilFontNormal, pilImageDraw, pilImageSize): XXX
def _drawCharList(self, artInfo, charList, pilFontBold, pilFontNormal, pilImageDraw, pilImageSize):
curPos = [0, 0]
for newChar in charList:
pilFont, underLine = pilFontNormal, False
if newChar[2] & ENNToolMiRCARTImporter._CellState.CS_BOLD:
pilFont = pilFontBold
pilFont, pilFontSize = pilFontBold, [pilFontBold.getsize(newChar[3])[0], pilFontBold.getsize(newChar[3])[1]]
else:
pilFont, pilFontSize = pilFontNormal, [pilFontNormal.getsize(newChar[3])[0], pilFontNormal.getsize(newChar[3])[1]]
if newChar[2] & ENNToolMiRCARTImporter._CellState.CS_UNDERLINE:
underLine = True
pilImageDraw.rectangle((*curPos, curPos[0] + pilFontSize[0], curPos[1] + pilFontSize[1] - 1),
fill=(*ENNToolMiRCARTColours[newChar[1]], 255))
pilImageDraw.text(curPos, newChar[3], (*ENNToolMiRCARTColours[newChar[0]], 255), pilFont)
if underLine:
else:
underLine = False
if newChar[3] != " ":
pilImageDraw.text(curPos, newChar[3], (255, 255, 255, 255), pilFont)
elif newChar[0] == newChar[1]:
pilImageDraw.rectangle((*curPos, curPos[0] + pilFontSize[0], curPos[1] + pilFontSize[1] - 1),
fill=(255, 255, 255, 255))
if underLine and False:
pilImageDraw.line(
xy=(curPos[0], curPos[1] + (pilFontSize[1] - 2),
curPos[0] + pilFontSize[0], curPos[1] + pilFontSize[1]),
fill=(*ENNToolMiRCARTColours[newChar[0]], 255))
artInfo[newChar[0]][newChar[1]][newChar[2]][newChar[3]] = []
artInfo[newChar[2]][newChar[3]] = []
# Top Right
artInfo[newChar[0]][newChar[1]][newChar[2]][newChar[3]] += [float(curPos[0] + pilFontSize[0]) / pilImageSize[0], 0.0]
artInfo[newChar[2]][newChar[3]] += [float(curPos[0] + pilFontSize[0]) / pilImageSize[0], 0.0]
# Top Left
artInfo[newChar[0]][newChar[1]][newChar[2]][newChar[3]] += [float(curPos[0]) / pilImageSize[0], 0.0]
artInfo[newChar[2]][newChar[3]] += [float(curPos[0]) / pilImageSize[0], 0.0]
# Bottom Left
artInfo[newChar[0]][newChar[1]][newChar[2]][newChar[3]] += [float(curPos[0]) / pilImageSize[0], float(pilFontSize[1]) / pilImageSize[1]]
artInfo[newChar[2]][newChar[3]] += [float(curPos[0]) / pilImageSize[0], float(pilFontSize[1]) / pilImageSize[1]]
# Bottom Right
artInfo[newChar[0]][newChar[1]][newChar[2]][newChar[3]] += [float(curPos[0] + pilFontSize[0]) / pilImageSize[0], float(pilFontSize[1]) / pilImageSize[1]]
artInfo[newChar[2]][newChar[3]] += [float(curPos[0] + pilFontSize[0]) / pilImageSize[0], float(pilFontSize[1]) / pilImageSize[1]]
curPos[0] += pilFontSize[0]
return artInfo
@ -73,24 +79,33 @@ class ENNToolGLTTFTexture(object):
artBg = artMap[numRow][numCol][1]
artAttrs = artMap[numRow][numCol][2]
artChar = artMap[numRow][numCol][3]
if artInfo[artFg][artBg][artAttrs][artChar] == {}:
artInfo[artFg][artBg][artAttrs][artChar] = None
if artInfo[artAttrs][artChar] == {}:
artInfo[artAttrs][artChar] = None
charList += [[artFg, artBg, artAttrs, artChar]]
return artInfo, charList
# }}}
# {{{ _initFonts(self): XXX
def _initFonts(self):
# {{{ _initFonts(self, charMap): XXX
def _initFonts(self, charMap):
fontBoldPathName = os.path.join("assets", "DejaVuSansMono-Bold.ttf")
fontNormalPathName = os.path.join("assets", "DejaVuSansMono.ttf")
fontSize = int("26")
pilFontBold = ImageFont.truetype(fontBoldPathName, fontSize)
pilFontMaxSize = [16, 32] # TODO
pilFontNormal = ImageFont.truetype(fontNormalPathName, fontSize)
pilFontSize = list(pilFontNormal.getsize("_")) # XXX
return pilFontBold, pilFontNormal, pilFontSize
return pilFontBold, pilFontMaxSize, pilFontNormal
# }}}
# {{{ _initImage(self, charList, pilFontSize): XXX
def _initImage(self, charList, pilFontSize):
pilImageSize = (pilFontSize[0] * len(charList), pilFontSize[1])
# {{{ _initImage(self, charList, pilFontMaxSize): XXX
def _initImage(self, charList, pilFontMaxSize):
pilImageSize = [pilFontMaxSize[0] * len(charList), pilFontMaxSize[1]]
for numDim in range(len(pilImageSize)):
if (pilImageSize[numDim] & (pilImageSize[numDim] - 1)) != 0:
pilImageSize[numDim] -= 1
pilImageSize[numDim] |= pilImageSize[numDim] >> 1
pilImageSize[numDim] |= pilImageSize[numDim] >> 2
pilImageSize[numDim] |= pilImageSize[numDim] >> 4
pilImageSize[numDim] |= pilImageSize[numDim] >> 8
pilImageSize[numDim] |= pilImageSize[numDim] >> 16
pilImageSize[numDim] += 1
pilImage = Image.new("RGBA", pilImageSize, (0, 0, 0, 0))
pilImageDraw = ImageDraw.Draw(pilImage)
return pilImage, pilImageDraw, pilImageSize
@ -115,7 +130,6 @@ class ENNToolGLTTFTexture(object):
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
artTextureImage.size[0], artTextureImage.size[1],
0, GL_RGBA, GL_UNSIGNED_BYTE, artTextureImageData)
glGenerateMipmap(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, 0)
return artTextureId
@ -127,11 +141,9 @@ class ENNToolGLTTFTexture(object):
# {{{ __init__(self): initialisation method
def __init__(self, artMap, cubeSize, videoSize):
artInfo, charList = self._initArtInfoCharList(artMap)
pilFontBold, pilFontNormal, pilFontSize = self._initFonts()
pilImage, pilImageDraw, pilImageSize = self._initImage(charList, pilFontSize)
artInfo = self._drawCharList(artInfo, charList,
pilFontBold, pilFontNormal, pilFontSize,
pilImageDraw, pilImageSize)
pilFontBold, pilFontMaxSize, pilFontNormal = self._initFonts(charList)
pilImage, pilImageDraw, pilImageSize = self._initImage(charList, pilFontMaxSize)
artInfo = self._drawCharList(artInfo, charList, pilFontBold, pilFontNormal, pilImageDraw, pilImageSize)
artTextureId = self._initTexture(pilImage)
self.artTextureId = artTextureId
self.artInfo = artInfo

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
ENNTool/openh264-1.7.0-win64.dll Executable file

Binary file not shown.