ClothObject.py
from OpenGL.GLUT import *
from OpenGL.GL import *
from OpenGL.GLU import *
import numpy as np
import math
################### new functions for Lab 4 ################
def convertLongVector(x) :
# concatenate all 3-d vectors stored in array x
# to create a large vector longX
longX = np.zeros(shape=(len(x)*3,))
for i in range(0, len(x)) :
longX[i*3:i*3+3] = x[i]
return longX
def set33SubMatrix(M, i, j, m) :
# set the submatrix of a larg matrix M at the i-th row and the j-th column
# with a 3x3 small matrix m
for row in range(0,3) :
for col in range(0,3) :
M[i*3+row, j*3+col] = m[row,col]
def set33SubMatrixSymmetric(M, i, j, m):
set33SubMatrix(M, i, j, m)
set33SubMatrix(M, j, i, m)
#############################################################
class ClothObject :
def __init__(self, sizeX, sizeZ, nW, nH):
self.dx = sizeX / (nW-1)
self.dz = sizeZ / (nH-1)
self.dDiag = math.sqrt(self.dx**2.0 + self.dz**2.0)
self.damp = 0.0
self.nW, self.nH = nW, nH
self.clothSize = (sizeX, sizeZ)
# vertex array (x,y,z)
self.verts = np.zeros(shape=(nW * nH, 3))
self.velocity = np.zeros(shape=(nW*nH, 3))
self.force = np.zeros(shape=(nW*nH, 3))
self.totalMass = 1.0
self.stiffness = 1.0
self.totalNumberOfParticles = nW*nH
self.mass = np.zeros(shape=(len(self.verts),))
particleMass = self.totalMass / self.totalNumberOfParticles
for i in range(0, self.totalNumberOfParticles) :
self.mass[i] = particleMass
# spring links (i, j)
self.springs = np.zeros(shape=((nW - 1) * nH + (nH - 1) * nW + (nW - 1) * (nH - 1) * 2, 2), dtype=np.int32)
# relaxed length of spring : l0
self.l0 = np.zeros(shape=(len(self.springs),))
self.numXSprings = (self.nW - 1) * self.nH
self.numZSprings = (self.nH - 1) * self.nW
self.numDiagonalSprings = (self.nW - 1) * (self.nH - 1)
################### new data for Lab 4 ################
# data for force derivative
self.J = np.zeros(shape=(self.totalNumberOfParticles * 3,
self.totalNumberOfParticles * 3))
#############################################################
self.resetMassSpring()
def resetMassSpring(self):
for i in range(0, self.nW * self.nH):
self.verts[i] = [(i % self.nW) * self.dx, 1, (int(i / self.nW)) * self.dz]
self.velocity[i] = [0,0,0]
for i in range(0, self.numXSprings):
row = int(i / (self.nW - 1))
col = i % (self.nW - 1)
self.springs[i] = [row * self.nW + col, row * self.nW + col + 1]
self.l0[i] = self.dx
for i in range(0, self.numZSprings):
row = int(i / (self.nW))
col = i % (self.nW)
self.springs[self.numXSprings + i] = [row * self.nW + col, (row + 1) * self.nW + col]
self.l0[(self.nW - 1) * self.nH + i] = self.dz
for i in range(0, self.numDiagonalSprings):
row = int(i / (self.nW - 1))
col = i % (self.nW - 1)
self.springs[self.numXSprings + self.numZSprings + 2 * i] = [row * self.nW + col,
(row + 1) * self.nW + col + 1]
self.l0[self.numXSprings + self.numZSprings + 2 * i] = self.dDiag
self.springs[self.numXSprings + self.numZSprings + 2 * i + 1] = [row * self.nW + col + 1,
(row + 1) * self.nW + col]
self.l0[self.numXSprings + self.numZSprings + 2 * i + 1] = self.dDiag
def drawSpring(self):
glColor3f(1, 1, 1)
glBegin(GL_LINES)
for i in range(0, len(self.springs)) :
idx0 = self.springs[i][0]
idx1 = self.springs[i][1]
loc0 = self.verts[idx0]
loc1 = self.verts[idx1]
glVertex3fv(loc0)
glVertex3fv(loc1)
glEnd()
def computeForce(self):
## reset forces
for i in range(0, self.totalNumberOfParticles) :
self.force[i] = np.array([0.0, 0.0, 0.0])
## compute spring forces
for i in range(0, len(self.springs)) :
idx0 = self.springs[i][0]
idx1 = self.springs[i][1]
xi = self.verts[idx0]
xj = self.verts[idx1]
xji = xj - xi
vi = self.velocity[idx0]
vj = self.velocity[idx1]
vji = vj - vi
l = np.linalg.norm(xji)
dir = xji / l
deform = l - self.l0[i]
f = self.stiffness * deform * dir + self.damp * vji
self.force[idx0] = self.force[idx0] + f
self.force[idx1] = self.force[idx1] - f
################### new functions for Lab 4 ################
def integrateImplicit(self, dt) :
Jii = np.zeros(shape=(len(self.verts), 3, 3))
bigM = np.zeros(shape=(len(self.verts) * 3, len(self.verts) * 3))
for i in range(0, len(self.verts)):
set33SubMatrix(bigM, i, i, self.mass[i] * np.identity(3))
# compute force derivative
for e in range(0, len(self.springs)):
i, j = self.springs[e][0], self.springs[e][1]
xji = self.verts[j] - self.verts[i]
lt = np.linalg.norm(xji)
Jij = self.stiffness * (((lt - self.l0[e]) / lt) * np.identity(3) +
(self.l0[e] / (lt ** 3.0)) * np.outer(xji, xji))
Jii[i], Jii[j] = Jii[i] - Jij, Jii[j] - Jij
set33SubMatrixSymmetric(self.J, i, j, Jij)
for i in range(0, len(self.verts)):
set33SubMatrix(self.J, i, i, Jii[i])
# compute velocity change with implicit method
bigM = bigM - dt * dt * self.J
bigMinv = np.linalg.inv(bigM)
longV = convertLongVector(self.velocity)
longF = convertLongVector(self.force)
return bigMinv.dot(longF * dt + (dt ** 2) * self.J.dot(longV))
#############################################################
################### new functions for Lab 4 ################
def update(self, dt):
gravity = np.array([0.0, -9.8, 0.0])
self.computeForce()
dVel = self.integrateImplicit(dt)
gravity = np.array([0.0, -9.8, 0.0])
for i in range (0, self.totalNumberOfParticles) :
if i is not 0 and i is not self.nW-1 : # these two masses are static
self.velocity[i] = self.velocity[i] + dVel[i*3:i*3+3] + gravity*dt
self.verts[i] = self.verts[i] + self.velocity[i]*dt
#### Simple Euler method for gravity simulation
#############################################################
main.py
import wx # requires wxPython package
from wx import glcanvas
from OpenGL.GL import *
from OpenGL.GLU import *
import ClothObject
class MyFrame(wx.Frame) :
def __init__(self):
self.size = (1280, 720)
wx.Frame.__init__(self, None, title = "wx frame", size = self.size,
style = wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
self.panel = MyPanel(self)
class MyPanel(wx.Panel) :
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.canvas = OpenGLCanvas(self)
self.bAnimation = False
self.resetButton = wx.Button(self, wx.ID_ANY, "Mass-Spring Reset", pos=(1030, 20), size=(200,40), style=0 )
self.animationButton = wx.Button(self, wx.ID_ANY, "Animate/Stop", pos=(1030, 60), size=(200, 40), style=0)
self.stiffnessLabel = wx.StaticText(self, -1, pos=(1030, 150), style=wx.ALIGN_CENTER)
self.stiffnessLabel.SetLabel("Stiffness: " + str(self.canvas.clothObject.stiffness))
self.stiffnessSlider = wx.Slider(self, -1, pos=(1030, 180), size=(200,50),
style = wx.SL_HORIZONTAL|wx.SL_AUTOTICKS,
value=1, minValue=1, maxValue = 30)
self.stepLabel = wx.StaticText(self, -1, pos=(1030, 250), style=wx.ALIGN_CENTER)
self.stepLabel.SetLabel("Time Interval: " + str(self.canvas.stepSize))
self.stepSlider = wx.Slider(self, -1, pos=(1030, 280), size=(200, 50),
style=wx.SL_HORIZONTAL | wx.SL_AUTOTICKS,
value=10, minValue=1, maxValue=300)
self.dampLabel = wx.StaticText(self, -1, pos=(1030, 350), style=wx.ALIGN_CENTER)
self.dampLabel.SetLabel("Damping: " + str(self.canvas.clothObject.damp))
self.dampSlider = wx.Slider(self, -1, pos=(1030, 380), size=(200, 50),
style=wx.SL_HORIZONTAL | wx.SL_AUTOTICKS,
value=0, minValue=0, maxValue=30)
self.Bind(wx.EVT_BUTTON, self.OnAnimationButton, self.animationButton)
self.Bind(wx.EVT_BUTTON, self.OnResetButton, self.resetButton)
self.Bind(wx.EVT_SLIDER, self.OnStiffnessSlider, self.stiffnessSlider)
self.Bind(wx.EVT_SLIDER, self.OnStepSlider, self.stepSlider)
self.Bind(wx.EVT_SLIDER, self.OnDampSlider, self.dampSlider)
def OnAnimationButton(self, event) :
if self.bAnimation is False :
self.bAnimation = True
else :
self.bAnimation = False
self.canvas.bAnimation = self.bAnimation
def OnResetButton(self, event) :
self.canvas.clothObject.resetMassSpring()
def OnStiffnessSlider(self, event):
val = event.GetEventObject().GetValue()
stiffness = 2**val
self.stiffnessLabel.SetLabel("Stiffness :" + str(stiffness))
self.canvas.clothObject.stiffness = stiffness
def OnStepSlider(self, event):
val = event.GetEventObject().GetValue()
stepSize = 0.0001*val
self.stepLabel.SetLabel("Time Interval :" + str(stepSize))
self.canvas.stepSize = stepSize
def OnDampSlider(self, event):
val = event.GetEventObject().GetValue()
damp = val/10.0
self.dampLabel.SetLabel("Damping Coeff. :" + str(damp))
self.canvas.clothObject.damp = damp
class OpenGLCanvas(glcanvas.GLCanvas):
def __init__(self, parent) :
self.initialized = False
self.size = (1024,720)
self.aspect_ratio = 1
self.stepSize = 0.001
glcanvas.GLCanvas.__init__(self, parent, -1, size = self.size)
self.context = glcanvas.GLContext(self)
self.SetCurrent(self.context)
self.Bind(wx.EVT_PAINT, self.OnDraw)
self.Bind(wx.EVT_IDLE, self.OnIdle)
self.InitGL()
self.clothObject = ClothObject.ClothObject(1,1,10,10)
self.bAnimation = False
def setAnimationFlag(self, bOption):
self.bAnimation = bOption
def resetMesh(self):
self.clothObject.resetMassSpring()
def InitGL(self):
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
self.aspect_ratio = float(self.size[0]) / self.size[1]
gluPerspective(60, self.aspect_ratio, 0.1, 100.0)
glViewport(0,0,self.size[0], self.size[1])
def OnDraw(self, event):
# clear color and depth buffers
if not self.initialized :
self.InitGL()
self.initialized = True
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
# position viewer
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
gluLookAt(2,2,2, 0,0,0, 0,1,0)
self.clothObject.drawSpring()
self.SwapBuffers()
def OnIdle(self, event):
if self.bAnimation :
self.clothObject.update(self.stepSize)
self.Refresh()
def main() :
app = wx.App()
frame = MyFrame()
frame.Show()
app.MainLoop()
if __name__ == "__main__" :
main()```