Sunday, March 27, 2011

Python Hermite Curve Calculation And Display

A Hermite curve defines a unique path between two points based off of two control tangents.

I put together a little python program to play around with the calculation of Hermite curves to make sure I understood them before attempting to use it in other applications.





import wx
import time
import threading

class point():
  def __init__(self, x=0, y=0, z=0):
    self.x = x
    self.y = y
    self.z = z
    
def curve(p1, p2, t1, t2, time):
  t = time
  b0 = 2*t*t*t - 3*t*t + 1
  b1 = -2*t*t*t + 3*t*t
  b2 = t*t*t - 2*t*t + t
  b3 = t*t*t - t*t
  x = b0*p1.x + b1*p2.x + b2*t1.x + b3*t2.x
  y = b0*p1.y + b1*p2.y + b2*t1.y + b3*t2.y
  z = b0*p1.z + b1*p2.z + b2*t1.z + b3*t2.z
  return point(x,y,z)
  
class CurveFrame(wx.Frame):
  def __init__(self, parent):
    wx.Frame.__init__(self, parent, id=wx.ID_ANY, title='HermiteCurve.py', style=wx.DEFAULT_FRAME_STYLE & ~wx.RESIZE_BORDER)
    
    # Initilize curve data.
    self.p1 = point()
    self.p2 = point()
    self.t1 = point()
    self.t2 = point()
    self.data = list()
    
    # Create wx panel to draw the curve on.
    self.CurvePanel = CurvePanel(self)
    
    # Control Panel
    self.ControlPanel = wx.Panel(self)
    
    # Controls for curve data.
    self.p1x = wx.Slider(self.ControlPanel, -1, 0, -200, 200, size=(200,-1), style=wx.SL_LABELS)
    self.p2x = wx.Slider(self.ControlPanel, -1, 0, -200, 200, size=(200,-1), style=wx.SL_LABELS)
    self.t1x = wx.Slider(self.ControlPanel, -1, 0, -5000, 5000, size=(200,-1), style=wx.SL_LABELS)
    self.t2x = wx.Slider(self.ControlPanel, -1, 0, -5000, 5000, size=(200,-1), style=wx.SL_LABELS)
    
    # Size it up!
    controlsizer = wx.GridBagSizer(0,0)
    controlsizer.Add(wx.StaticText(self.ControlPanel, label='Point 1 Value:'), (1,0), flag=wx.ALL, border=5)
    controlsizer.Add(self.p1x, (2,0), flag=wx.ALL, border=5)
    controlsizer.Add(wx.StaticText(self.ControlPanel, label='Point 2 Value:'), (3,0), flag=wx.ALL, border=5)
    controlsizer.Add(self.p2x, (4,0), flag=wx.ALL, border=5)
    controlsizer.Add(wx.StaticText(self.ControlPanel, label='Tangent 1 Magnitude:'), (5,0), flag=wx.ALL, border=5)
    controlsizer.Add(self.t1x, (6,0), flag=wx.ALL, border=5)
    controlsizer.Add(wx.StaticText(self.ControlPanel, label='Tangent 2 Magnitude:'), (7,0), flag=wx.ALL, border=5)
    controlsizer.Add(self.t2x, (8,0), flag=wx.ALL, border=5)
    self.ControlPanel.SetSizerAndFit(controlsizer)
    
    sizer = wx.GridBagSizer(0,0)
    sizer.Add(self.CurvePanel, (0,0), flag=wx.ALL, border=5)
    sizer.Add(self.ControlPanel, (0,1), flag=wx.ALL, border=5)    
    self.SetSizerAndFit(sizer)
    
    # Show the frame.
    self.Show(True)
    
    # Thread to update.
    self.thread = threading.Thread(target=self.Update)
    self.thread.start()
  
  def Update(self):
    while(True):
      self.p1.x = self.p1x.GetValue()
      self.p2.x = self.p2x.GetValue()
      self.t1.x = self.t1x.GetValue()
      self.t2.x = self.t2x.GetValue()
      self.CalcCurve()
      self.Draw()
      time.sleep(.1)
    
  def CalcCurve(self):
    time = 0
    self.data = list()
    while time < 1.01:
      p = curve(self.p1, self.p2, self.t1, self.t2, time)
      self.data.append(time)
      self.data.append(p.x)
      self.data.append(p.y)
      self.data.append(p.z)
      time = time + .01
    
  def Draw(self):
    # Redraw the curve panel.
    self.CurvePanel.Refresh()
    
class CurvePanel(wx.Panel):
  def __init__(self, parent):
    wx.Panel.__init__(self, parent, id=wx.ID_ANY, size=(600,500), style=wx.SIMPLE_BORDER)
    self.parent = parent
    self.Bind(wx.EVT_PAINT, self.OnPaint)
    self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
    
  def OnEraseBackground(self, event):
    pass
    
  def OnPaint(self, event):
    dc = wx.BufferedPaintDC(self)
    self.Draw(dc)
    
  def Draw(self, dc):  
    dc.SetBrush(wx.WHITE_BRUSH)
    dc.DrawRectangle(-1, -1, self.Size.width, self.Size.height)
    
    data = self.parent.data
    d = self.parent.data

    width = self.GetSize()[0]
    height = self.GetSize()[1]
    padding = 20
      
    # Scale the data to the graph size panel's size.
    for x in range(len(data)/4):
      d[4*x+0] = (width-2*padding) * data[4*x+0] + padding
      d[4*x+1] = -1 * data[4*x+1] + (height/2)
    
    # Draw the graph axis.
    dc.DrawLine(padding, height/2, width-padding, height/2)
    dc.DrawLine(padding, padding, padding, height-padding)
    
    # Draw the data.
    dc.SetPen(wx.Pen(wx.BLUE, 2))
    for x in range(len(data)/4):
      try:
        x1 = d[4*x]
        y1 = d[4*x+1]
        x2 = d[4*(x+1)]
        y2 = d[4*(x+1)+1]
        dc.DrawLine(x1,y1,x2,y2)
      except:
        pass
        
if __name__ =='__main__':
  app = wx.App(0)
  frame = CurveFrame(None)
  app.MainLoop()
Download HermiteCurve.py code here.

No comments:

Post a Comment