import os
import sys
import argparse
import numpy as np
from PIL import Image
import signal
import time
signal.signal(signal.SIGINT, signal.SIG_DFL)
import OpenGL.GL as gl
from glfwBackend import glfwApp
import imgui
from imgui.integrations.glfw import GlfwRenderer
# Local imports
from fieldanimation import FieldAnimation, glInfo
import glfw
CHOICES = (
'epole',
"Duffing's equation",
'Structurally stable system',
'Transcritical bifurcation',
'Reaction diffusion',
'Positive invariant set',
'Spiral ccw',
'Spiral cw',
'wind',
'gmod',
)
#------------------------------------------------------------------------------
def ElectricField(q, r0, x, y):
""" Return the electric field vector E=(Ex, Ey) due to charge q at r0.
"""
den = np.hypot(x - r0[0], y - r0[1]) ** 1.5
return q * (x - r0[0]) / den, q * (y - r0[1]) / den
#------------------------------------------------------------------------------
def createField(eq='Spiral ccw', m=64, n=64):
# def createField(eq='Spiral ccw', m=81, n=201):
""" Equations are taken from
http://reference.wolfram.com/language/ref/StreamPlot.html
Args:
eq (string): equation name
m (integer): number of rows
n (integer): number of columns
"""
mj = m * 1j
nj = n * 1j
Y, X = np.mgrid[-3:3:mj, -3:3:nj]
if eq == 'epole':
# Return n x m x 2 2D array with Electric field values
# generated by a (2 * charges) electric charges configuration
charges = 1
nq = 2 ** int(charges)
charges = []
for i in range(nq):
q = i % 2 * 2 - 1
charges.append((q,
(np.cos(2 * np.pi * i / nq), np.sin(2 * np.pi * i / nq))))
U, V = np.zeros((m, n)), np.zeros((m, n))
for charge in charges:
ex, ey = ElectricField(*charge, x=X, y=Y)
U += ex
V += ey
elif eq == "Duffing's equation":
# Duffing's equation
U = Y.copy()
V = X - X**3
elif eq == 'Structurally stable system':
# Structurally stable system
U = 2 * X + Y - X * (X**2 + Y**2)
V = -Y - Y * (X**2 + Y**2)
elif eq == 'Transcritical bifurcation':
# Transcritical bifurcation
U = X**2
V = -Y
elif eq == 'Reaction diffusion':
U = 2 * (Y - X) + X * (1 - X ** 2)
V = -2 * (Y - X) + Y * (1 - Y ** 2)
elif eq == 'Positive invariant set':
U = Y.copy()
V = - X + Y * (1 - X ** 2 - 2 * Y ** 2)
elif eq == 'Spiral ccw':
origin = (0.8, 0.8)
X, Y = X - origin[0], Y - origin[1]
U = X - Y
V = X + Y
elif eq == 'Spiral cw':
origin = (0.8, 0.8)
X, Y = X - origin[0], Y - origin[1]
U = Y - X
V = -X - Y
elif eq == 'wind':
field = np.load("wind_2016-11-20T00-00Z.npy")
r, c, bands = field.shape
Y, X = np.mgrid[0:r, 0:c]
U = field[:, :, 0][::-1]
V = - field[:, :, 1][::-1]
elif eq == 'gmod':
U = np.load('vx.npy')[::-1]
V = np.load('vy.npy')[::-1]
r,c = U.shape
Y, X = np.mgrid[0:r, 0:c]
else:
raise SystemExit("Unknown field. Giving up...")
return np.flipud(np.dstack((U, -V)))
#------------------------------------------------------------------------------
def userInterface(renderer, graphicItem, app):
""" Control graphicItem parameters interactively
"""
if not renderer:
return
renderer.process_inputs()
imgui.set_next_window_position(0, 0)
imgui.new_frame()
toOpen = True
dummy, toOpen = imgui.begin('Controls', closable=True,
flags=imgui.WINDOW_ALWAYS_AUTO_RESIZE)
if not toOpen:
app.restoreKeyCallback()
return toOpen
# field to show
current = app.ifield
changed, current = imgui.combo('Field', current, list(CHOICES))
if changed:
app.setField(current)
# Speed Rate
#drag_float(str label, float value, float change_speed=1.0,
# float max_value=0.0, float min_value=0.0,
changed, speed = imgui.drag_float('Speed',
graphicItem.speedFactor, 0.01, 0.0, 10.0)
if changed:
graphicItem.speedFactor = speed
# Drop Rate
changed, dropRate = imgui.drag_float('Drop rate',
graphicItem.dropRate, 0.001, 0.001, 1.0)
if changed:
graphicItem.dropRate = dropRate
# Drop Rate Bump
changed, dropRateBump = imgui.drag_float('Drop rate bump',
graphicItem.dropRateBump, 0.01, 0.001, 1.0)
if changed:
graphicItem.dropRateBump = dropRateBump
# Unbknown const
changed, opacity = imgui.drag_float('Opacity',
graphicItem.fadeOpacity, 0.001, 0.900, 0.999, '%.4f')
if changed:
graphicItem.fadeOpacity = opacity
# Palette
changed, color = imgui.color_edit3('Color', *graphicItem.color)
if changed:
graphicItem.color = color
imgui.same_line()
changed, palette = imgui.checkbox("Palette", graphicItem.palette)
if changed:
graphicItem.palette = palette
changed, bg_color = imgui.color_edit4('Background color', *app.bg_color)
if changed:
app.bg_color = bg_color
# Point size
changed, pointSize = imgui.input_int("Point size",
graphicItem.pointSize, 1, 1, 1)
if changed:
if pointSize > 5:
pointSize = 5
elif pointSize < 1:
pointSize = 1
graphicItem.pointSize = pointSize
# Number of Points
changed, tracersCount = imgui.drag_int("Number of "
"Tracers", graphicItem.tracersCount, 1000.0, 4000, 10000000)
if changed:
graphicItem.tracersCount = tracersCount
# Periodic border
changed, periodic = imgui.checkbox("Periodic", graphicItem.periodic)
if changed:
graphicItem.periodic = periodic
# Draw field
changed, drawfield = imgui.checkbox("Draw Field", graphicItem.drawField)
if changed:
graphicItem.drawField = drawfield
imgui.end()
imgui.render()
return True
#==============================================================================
class GLApp(glfwApp):
def __init__(self, title, width, height, options):
super(GLApp, self).__init__(title, width, height)
if options.gui:
self._renderer = GlfwRenderer(self.window(), True)
else:
self._renderer = None
if options.image is not None:
options.image = np.flipud(
np.asarray(Image.open(options.image), np.uint8))
self.ifield = CHOICES.index(options.choose)
field = createField(CHOICES[self.ifield])
glversion = float("%d.%d" % (glInfo()['major'], glInfo()['minor']))
if not options.use_fragment and glversion < 4.3:
print("WARNING..... Compute shaders not available with OpenGL"
" ver. %.1f." % glversion)
useCompute = False
else:
useCompute = True
# Add Field Animation overlay
self._fa = FieldAnimation(width, height, field, useCompute,
options.image)
if options.draw_field:
self._fa.drawField = True
self._t0 = time.time()
self._fps = 0
self.options = options
def renderScene(self):
super(GLApp, self).renderScene()
self._fa.draw()
self._fps += 1
status = userInterface(self._renderer, self._fa, self)
if not status:
self._renderer = None
now = time.time()
if now - self._t0 >= 1:
if self.options.fps:
self.setTitle("%s - %s FPS" % (self.title(), self._fps))
self._fps = 0
self._t0 = time.time()
def onKeyboard(self, window, key, scancode, action, mode):
if key == GLApp.KEY_G and action == GLApp.PRESS:
# Draw the GUI
if self._renderer is None:
self._renderer = GlfwRenderer(self.window(), True)
super(GLApp, self).onKeyboard(window, key, scancode, action, mode)
def onResize(self, window, width, height):
gl.glViewport(0, 0, width, height)
self._fa.setSize(width, height)
def setField(self, ifield):
field = createField(CHOICES[ifield])
self._fa.setField(field)
self.setTitle('%s' % CHOICES[ifield])
self.ifield = ifield
#------------------------------------------------------------------------------
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="\nField Animation example",
add_help=True,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog=os.path.basename(sys.argv[0]))
parser.add_argument('-f', '--draw_field', action='store_true',
default=False,
help=("Draw vector field as background image ")
)
parser.add_argument('-c', '--choose',
choices=CHOICES,
default="epole",
help=("Choose field to animate ")
)
parser.add_argument('-p', '--fps', action='store_true', default=False,
help=("Count Frames Per Second ")
)
parser.add_argument('-u', '--use-fragment', action='store_true',
default=False, help=("Use fragment instead of compute shader ")
)
parser.add_argument('-g', '--gui', action='store_true', default=False,
help=("Add gui control window ")
)
parser.add_argument('-i', '--image', action='store', default=None,
help=("Load image as background texture")
)
options = parser.parse_args(sys.argv[1:])
app = GLApp('Field Animation', 800, 800, options)
app.run()