            :config config
            :dispatcher dispatcher
            :broadphase broad
-           :solver solver)))
+           :constraint-solver solver)))
+
+(defn ^:private soft-rigid-dynamics
+  []
+  (let [config (btSoftBodyRigidBodyCollisionConfiguration.)
+        dispatcher (btCollisionDispatcher. config)
+        broad (btDbvtBroadphase.)
+        solver (btSequentialImpulseConstraintSolver.)]
+    (assoc (World3D. (btSoftRigidDynamicsWorld. dispatcher broad solver config))
+           :config config
+           :dispatcher dispatcher
+           :broadphase broad
+           :constraint-solver solver)))
 
 (defn bullet-3d*
   [type]
   @init
   (case type
-    :discrete-dynamics (discrete-dynamics)
+    :rigid (discrete-dynamics)
+    :soft-rigid (soft-rigid-dynamics)
     (u/throw-key-not-found type)))
 
 (defmacro bullet-3d
-  "Returns a world based on btCollisionWorld.
-
-    (bullet-3d :discrete-dynamics)"
+  "Returns a world based on btDynamicsWorld.
+
+    (bullet-3d :rigid) ; can only handle rigid bodies
+    (bullet-3d :soft-rigid) ; can handle soft and rigid bodies"
   [type & options]
   `(let [world# (bullet-3d* ~type)
-         ^btCollisionWorld object# (:object world#)]
+         ^btDynamicsWorld object# (:object world#)]
      (u/calls! object# ~@options)
      world#))
 
 (defmacro bullet-3d!
   "Calls a single method on a `bullet-3d`."
   [screen k & options]
-  `(let [^btCollisionWorld object# (:object (u/get-obj ~screen :world))]
+  `(let [^btDynamicsWorld object# (:object (u/get-obj ~screen :world))]
      (u/call! object# ~k ~@options)))
 
 ; bodies
-
-(defn basic-body*
-  []
-  (Body3D. (btCollisionObject.)))
-
-(defmacro basic-body
-  "Returns a body based on btCollisionObject."
-  [& options]
-  `(let [body# (basic-body*)
-         ^btCollisionObject object# (:object body#)]
-     (u/calls! object# ~@options)
-     body#))
-
-(defmacro basic-body!
-  "Calls a single method on a `basic-body`."
-  [object k & options]
-  `(let [^btCollisionObject object# (:object (u/get-obj ~object :body))]
-     (u/call! object# ~k ~@options)))
 
 (defn rigid-body*
   [info]

           [com.badlogic.gdx.physics.bullet Bullet]
           [com.badlogic.gdx.physics.bullet.collision btBoxShape
            btCollisionDispatcher btCylinderShape btCollisionObject
            btCollisionWorld btDefaultCollisionConfiguration btDbvtBroadphase
            btSphereShape]
           [com.badlogic.gdx.physics.bullet.dynamics btDiscreteDynamicsWorld
            btDynamicsWorld btRigidBody btRigidBody$btRigidBodyConstructionInfo
            btSequentialImpulseConstraintSolver]
           [com.badlogic.gdx.physics.bullet.linearmath btMotionState]))

(def ^:private init (delay (Bullet/init)))


(defmethod c/update-physics!
  World3D
  [screen & [entities]]
  ; initialize bodies if necessary
  (doseq [e entities]
    (let [object (u/get-obj e :object)
          body (u/get-obj e :body)]
      (when (and object (isa? (type (:object body)) btRigidBody))
        (when-not (rigid-body! e :get-motion-state)
          (->> (proxy [btMotionState] []
                 (getWorldTransform [world-t])
                 (setWorldTransform [world-t]
                   (m/matrix-4! (. object transform) :set world-t)))
               (rigid-body! e :set-motion-state))))))
  ; remove bodies that no longer exist
  (when entities
    (doseq [^btCollisionObject body (get-bodies screen)]
      (when-not (some #(= body (-> % :body :object)) entities)
        (cond
          (isa? (type body) btRigidBody)
          (bullet-3d! screen :remove-rigid-body body)
          (isa? (type body) btSoftBody)
          (bullet-3d! screen :remove-soft-body body))
        (.dispose body)))))

(defmethod c/step!
  World3D
