diff --git a/examples/harmony/glwin.py b/examples/harmony/glwin.py
new file mode 100644
index 0000000000000000000000000000000000000000..5558c70d7af68c0dd1febdd2f80f3fe5955e942b
--- /dev/null
+++ b/examples/harmony/glwin.py
@@ -0,0 +1,91 @@
+import math
+from ost import gfx, geom, mol
+from PyQt4.QtGui import *
+from PyQt4.QtCore import *
+from PyQt4.QtOpenGL import *
+
+TRANS_VAL = 20
+
+class DokkGLCanvas(QGLWidget):
+
+  def __init__(self, format, parent=None):
+    QGLWidget.__init__(self, format, parent)
+    self.last_pos_=QPoint()
+    self.setAutoFillBackground(False)
+    #self.setAttribute(Qt.WA_KeyCompression,True)
+    self.resize(800, 800)
+    self.atom=mol.AtomHandle()
+  def initializeGL(self):
+    gfx.Scene().InitGL()
+    self.startTimer(20)
+  def paintGL(self):
+    gfx.Scene().RenderGL()
+  def paintEvent(self, event):
+    self.makeCurrent()
+    self.paintGL()
+    #painter=QPainter(self)
+    #self.RenderHUD(painter)
+    self.swapBuffers()
+
+  def RenderHUD(self, painter):
+    painter.setPen(QColor(100, 100, 100, 50))
+    painter.setBrush(QColor(200, 200, 200, 50))
+    painter.drawRect(QRect(QPoint(0, 0), QSize(self.width(), 25)))
+    painter.setPen(QPen(QColor(255,255,255), Qt.SolidLine))
+    painter.setFont(QFont("Verdana"))
+  def resizeGL(self, w, h):
+    gfx.Scene().Resize(w, h)
+
+  def mousePressEvent(self, event):
+    self.last_point_=QPoint(event.x(), event.y())
+    v1=gfx.Scene().UnProject(geom.Vec3(event.x(), self.height()-event.y(), 0.0));
+    v2=gfx.Scene().UnProject(geom.Vec3(event.x(), self.height()-event.y(), 1.0));
+    self.atom=self.world.go.PickAtom(geom.Line3(v1, v2), 0.1)
+    self.world.go.SetColor(gfx.WHITE, '')
+    if self.atom.IsValid():
+      self.world.go.SetColorForAtom(gfx.RED, self.atom)
+  def mouseReleaseEvent(self, event):
+    self.world.go.SetColor(gfx.WHITE, '')
+    self.atom=mol.AtomHandle()
+  def mouseMoveEvent(self, event):
+    delta=QPoint(event.x(), event.y())-self.last_point_
+    self.last_point_=QPoint(event.x(), event.y())
+    if event.buttons() & Qt.LeftButton:
+      if self.atom.IsValid():
+        rot=gfx.Scene().GetTransform().GetRot()
+        force=-rot.GetRow(1)*delta.y()+rot.GetRow(0)*delta.x()
+        self.world.AddForce(self.atom, force*2)
+  def wheelEvent(self, event):
+    self.OnTransform(gfx.INPUT_COMMAND_TRANSZ,0,gfx.TRANSFORM_VIEW,
+              0.1*(-event.delta()))
+
+  def timerEvent(self, event):
+    self.world.Update()
+    self.update()
+  def OnTransform(self,com, indx, trg, val):
+    gfx.Scene().Apply(gfx.InputEvent(gfx.INPUT_DEVICE_MOUSE,
+                                     com, indx,trg,val*0.5),False)
+
+  def keyReleaseEvent(self, event):
+    if event.key() == Qt.Key_Escape:
+      QApplication.exit()
+
+class DokkGLWin(gfx.GLWinBase):
+    def _CreateFormat(self):
+      fmt=QGLFormat()
+      fmt.setAlpha(True)
+      return fmt
+    def __init__(self):
+        gfx.GLWinBase.__init__(self)
+        self.canvas_=DokkGLCanvas(self._CreateFormat())
+    def DoRefresh(self):
+      self.refresh_=True
+    def SetLevel(self, level):
+      self.canvas_.SetLevel(level)
+    def Show(self, fullscreen):
+      if fullscreen:
+        self.canvas_.showFullScreen()
+      else:
+        self.canvas_.show()
+    def SetStereo():
+      pass
diff --git a/examples/harmony/harmony b/examples/harmony/harmony
new file mode 100755
index 0000000000000000000000000000000000000000..4abd9d73dc2ac9b4a3b0beec87662cd27271cd67
--- /dev/null
+++ b/examples/harmony/harmony
@@ -0,0 +1,2 @@
+#!/bin/sh
+cat harmony.py | gosty Harmony $@
\ No newline at end of file
diff --git a/examples/harmony/harmony.py b/examples/harmony/harmony.py
new file mode 100644
index 0000000000000000000000000000000000000000..df4914e755fc4a9c7362cf911cbd8327ad1af1ea
--- /dev/null
+++ b/examples/harmony/harmony.py
@@ -0,0 +1,54 @@
+import glwin
+from ost import io, geom, gfx, mol
+
+class World:
+  def __init__(self):
+    self.atom_string=mol.CreateEntity()
+    edi=self.atom_string.RequestXCSEditor(mol.EditMode.BUFFERED_EDIT)
+    chain=edi.InsertChain("A")
+    r=edi.AppendResidue(chain, "STRING")
+    prev_atom=mol.AtomHandle()
+    for i in range(-5, 5):
+      atom=edi.InsertAtom(r, "X%02d" % (i+5), geom.Vec3(i, 0, 0))
+      if prev_atom.IsValid():
+        edi.Connect(prev_atom, atom)
+      prev_atom=atom
+    self.go=gfx.Entity("GO", gfx.CUSTOM, self.atom_string)
+    self.go.custom_options.SetBondRad(0.1)
+    self.go.custom_options.SetSphereRad(0.4)
+    scene.Add(self.go)
+    scene.CenterOn(self.go)
+    self.last_positions=[]
+    for atom in self.atom_string.atoms:
+      self.last_positions.append(atom.pos)
+    self.forces=[]
+    for i in range(len(self.last_positions)):
+      self.forces.append(geom.Vec3())
+  def AddForce(self, atom, force):
+    for index, aa in enumerate(self.atom_string.atoms):
+      if aa==atom:
+        self.forces[index]+=force
+        break
+  def Update(self):
+    last_atom=mol.AtomHandle()
+    for index, atom in enumerate(self.atom_string.atoms):
+      if last_atom.IsValid():
+        diff=last_atom.pos-atom.pos
+        length=geom.Length(diff)
+        diff/=length
+        force=(1.0-length)**2*diff
+        self.forces[index-1]-=force
+        self.forces[index]+=force
+      last_atom=atom
+    edi=self.atom_string.RequestXCSEditor(mol.EditMode.BUFFERED_EDIT)
+    for force, atom in zip(self.forces, self.atom_string.atoms):
+      edi.SetAtomPos(atom, atom.pos+force*0.01)
+    self.go.Rebuild()
+    for i in range(len(self.last_positions)):
+      self.forces[i]=geom.Vec3()
+
+dokk_win=glwin.DokkGLWin()
+
+world=World()
+dokk_win.canvas_.world=world
+dokk_win.Show(fullscreen=('--fullscreen' in sys.argv))