Commits

Daniel K. O. committed 293d997

added cylinders, new sample to stack objects

Comments (0)

Files changed (32)

include/kode/Mass.hpp

 
         Mass inverse() const;
 
+        static Mass BoxTotal(const Vector3& size, Real total);
+        static Mass BoxDensity(const Vector3& size, Real density);
+
         static Mass SphereTotal(Real radius, Real total);
         static Mass SphereDensity(Real radius, Real density);
 
-        static Mass BoxTotal(const Vector3& size, Real total);
-        static Mass BoxDensity(const Vector3& size, Real density);
+        static Mass CapsuleTotal(Real radius, Real length, Real total);
+        static Mass CapsuleDensity(Real radius, Real length, Real density);
+
     };
 
 

include/kode/collision/Collider.hpp

 #include <kode/collision/Box.hpp>
 #include <kode/collision/Capsule.hpp>
 #include <kode/collision/ContactPoint.hpp>
+#include <kode/collision/Cylinder.hpp>
 #include <kode/collision/Sphere.hpp>
 #include <kode/collision/Plane.hpp>
 
                                  const Plane& plane,
                                  const Callback& func) const;
 
+        void collideCylinderPlane(const Cylinder& cylinder,
+                                  const Plane& plane,
+                                  const Callback& func) const;
+
         void collideGeomGeom(const Geom& g1,
                              const Geom& g2,
                              const Callback& func) const;
                                  const Capsule& capsule,
                                  const Callback& func) const;
 
+        void collidePlaneCylinder(const Plane& plane,
+                                  const Cylinder& cylinder,
+                                  const Callback& func) const;
+
         void collidePlanePlane(const Plane&,
                                const Plane&,
                                const Callback&) const;

include/kode/collision/Cylinder.hpp

+/*
+   KODE Physics Library
+   Copyright 2013-2014 Daniel Kohler Osmari
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+#ifndef KODE_CYLINDER_H
+#define KODE_CYLINDER_H
+
+#include <kode/collision/Geom.hpp>
+
+namespace kode {
+
+    class Cylinder : public Geom {
+
+        Real radius;
+        Real hlength;
+
+
+        void computeAABB() noexcept override;
+
+        Real computePointDepth(const Vector3& point) const noexcept override;
+
+        Interval computeProjected(const Vector3& axis) const override;
+
+        Vector3 computeSupport(const Vector3& dir) const override;
+
+
+    public:
+
+        Cylinder(Real radius, Real length);
+
+        Type getType() const noexcept override;
+
+        inline
+        Real getRadius() const noexcept;
+
+        void setRadius(Real r);
+        
+
+        inline
+        Real getLength() const noexcept;
+
+        void setLength();
+
+        void setRadiusLength(Real rad, Real len);
+    };
+
+
+
+    // inline definitions
+    inline
+    Real
+    Cylinder::getRadius() const noexcept
+    {
+        return radius;
+    }
+
+
+    inline
+    Real
+    Cylinder::getLength() const noexcept
+    {
+        return hlength * 2;
+    }
+
+}
+
+#endif

include/kode/collision/Geom.hpp

         enum class Type : unsigned {
             Box,
             Capsule,
+            Cylinder,
             Plane,
             Sphere,
             Unknown

include/kode/collision/Makefile.am

     Capsule.hpp \
     Collider.hpp \
     ContactPoint.hpp \
+    Cylinder.hpp \
     Geom.hpp \
     HashSpace.hpp \
     Plane.hpp \

include/kode/collision/tests/CapsulePlane.hpp

 
 #include <vector>
 
-#include <kode/collision/Capsule.hpp>
-#include <kode/collision/ContactPoint.hpp>
-#include <kode/collision/Plane.hpp>
-
 namespace kode {
 
-    bool testCapsulePlane(const Capsule& cap,
+    class Capsule;
+    class ContactPoint;
+    class Plane;
+
+    bool testCapsulePlane(const Capsule& capsule,
                           const Plane& plane,
                           std::vector<ContactPoint>& cp);
 

include/kode/collision/tests/CylinderPlane.hpp

+/*
+   KODE Physics Library
+   Copyright 2013-2014 Daniel Kohler Osmari
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+#ifndef KODE_CYLINDER_PLANE_H
+#define KODE_CYLINDER_PLANE_H
+
+#include <vector>
+
+namespace kode {
+
+    class Cylinder;
+    class Plane;
+    class ContactPoint;
+
+    bool testCylinderPlane(const Cylinder& cylinder,
+                           const Plane& plane,
+                           std::vector<ContactPoint>& cp);
+
+}
+
+#endif

include/kode/collision/tests/Makefile.am

     BoxPlane.hpp \
     BoxSphere.hpp \
     CapsulePlane.hpp \
+    CylinderPlane.hpp \
     GeomGeomMPR.hpp \
     PlaneSphere.hpp \
     SphereSphere.hpp

include/kode/kode.hpp

 #include <kode/collision/ContactPoint.hpp>
 
 #include <kode/collision/Box.hpp>
+#include <kode/collision/Capsule.hpp>
+#include <kode/collision/Cylinder.hpp>
 #include <kode/collision/Plane.hpp>
 #include <kode/collision/Sphere.hpp>
-#include <kode/collision/Capsule.hpp>
 
 #include <kode/collision/Space.hpp>
 #include <kode/collision/HashSpace.hpp>

samples/KODEOgre/Makefile.am

     hinges \
     piston \
     rfriction \
-    sample
+    sample \
+    stack
 
 endif
 

samples/KODEOgre/data/samples.material

     }
 }
 
+material capsule : Chess
+{
+    technique shader
+    {
+        pass perlight
+        {
+            texture_unit decalmap
+            {
+                scale 0.5 0.5
+            }
+        }
+    }
+}
+
 material SkyBoxGradient
 {
     technique

samples/KODEOgre/include/BodyCapsule.hpp

+#ifndef KODE_OGRE_BODY_CAPSULE_H
+#define KODE_OGRE_BODY_CAPSULE_H
+
+#include "Body.hpp"
+#include "Capsule.hpp"
+
+#include <OgreSceneManager.h>
+
+
+namespace kode {
+    namespace ogre {
+
+        class BodyCapsule : public Body,
+                            public Capsule {
+
+        public:
+            BodyCapsule(Real radius, Real length, Real density=1, Ogre::SceneManager* smgr=nullptr);
+            BodyCapsule(World& world, Real radius, Real length, Real density=1, Ogre::SceneManager* smgr=nullptr);
+
+            using Body::getPosition;
+            using Body::setPosition;
+            using Body::getLocalAxes;
+            using Body::setLocalAxes;
+            using Body::getOrientation;
+            using Body::rotate;
+            using Body::translate;
+            using Body::localPointToWorld;
+
+        };
+
+    }
+}
+
+#endif

samples/KODEOgre/include/BodyCylinder.hpp

Empty file added.

samples/KODEOgre/include/BodySphere.hpp

             BodySphere(Real radius, Real density=1, Ogre::SceneManager* smgr=nullptr);
             BodySphere(World& world, Real radius, Real density=1, Ogre::SceneManager* smgr=nullptr);
 
+            using Body::getPosition;
             using Body::setPosition;
-            using Body::getPosition;
+            using Body::getLocalAxes;
+            using Body::setLocalAxes;
+            using Body::getOrientation;
             using Body::rotate;
+            using Body::translate;
+            using Body::localPointToWorld;
             // TODO: disambiguate all Body x Geom methods
         };
 

samples/KODEOgre/include/Capsule.hpp

+#ifndef KODE_OGRE_CAPSULE_H
+#define KODE_OGRE_CAPSULE_H
+
+#include <OgreSceneManager.h>
+
+#include <kode/collision/Capsule.hpp>
+
+#include "Shape.hpp"
+
+namespace kode {
+    namespace ogre {
+
+        class Capsule : public Shape,
+                        public kode::Capsule {
+            void moved() override;
+        public:
+            Capsule(Real radius, Real length, Ogre::SceneManager* smgr = nullptr);
+        };
+
+    }
+}
+
+#endif

samples/KODEOgre/include/Cylinder.hpp

Empty file added.

samples/KODEOgre/include/KODEOgre.hpp

 #include "BallSocket.hpp"
 #include "Body.hpp"
 #include "BodyBox.hpp"
+#include "BodyCapsule.hpp"
+#include "BodyCylinder.hpp"
 #include "BodySphere.hpp"
 #include "Box.hpp"
+#include "Capsule.hpp"
 #include "conv.hpp"
+#include "Cylinder.hpp"
 #include "Hinge.hpp"
 #include "Plane.hpp"
 #include "Shape.hpp"

samples/KODEOgre/include/Makefile.am

     BallSocket.hpp \
     Body.hpp \
     BodyBox.hpp \
+    BodyCapsule.hpp \
+    BodyCylinder.hpp \
     BodySphere.hpp \
     Box.hpp \
+    Capsule.hpp \
     conv.hpp \
+    Cylinder.hpp \
     Hinge.hpp \
     KODEOgre.hpp \
     Plane.hpp \

samples/KODEOgre/src/BodyCapsule.cpp

+#include "BodyCapsule.hpp"
+
+namespace kode {
+    namespace ogre {
+
+        BodyCapsule::BodyCapsule(Real radius, Real length,
+                                 Real density,
+                                 Ogre::SceneManager* smgr) :
+            Body{smgr},
+            Capsule{radius, length, smgr}
+        {
+            setMass(Mass::CapsuleDensity(radius, length, density));
+            attach(*this);
+            Body::getNode()->setVisible(false, false);
+        }
+
+
+        BodyCapsule::BodyCapsule(World& world,
+                                 Real radius, Real length,
+                                 Real density,
+                                 Ogre::SceneManager* smgr) :
+            Body{world, smgr},
+            Capsule{radius, length, smgr}
+        {
+            setMass(Mass::CapsuleDensity(radius, length, density));
+            attach(*this);
+            Body::getNode()->setVisible(false, false);
+        }
+
+    }
+}
+

samples/KODEOgre/src/BodyCylinder.cpp

Empty file added.

samples/KODEOgre/src/Capsule.cpp

+#include <cmath>
+#include <iostream>
+#include <assert.h>
+
+#include <OgreEntity.h>
+#include <OgreSceneNode.h>
+#include <OgreManualObject.h>
+#include <OgreEntity.h>
+#include <OgreMesh.h>
+#include <OgreMeshManager.h>
+
+#include "Capsule.hpp"
+
+#include "conv.hpp"
+#include "App.hpp"
+
+using std::cout;
+using std::endl;
+
+/*
+  Based on Ogre Procedural
+ */
+
+namespace kode {
+    namespace ogre {
+
+
+        Capsule::Capsule(Real radius, Real length,
+                         Ogre::SceneManager* smgr) :
+            kode::Capsule{radius, length}
+        {
+            using namespace Ogre;
+            using Ogre::Math;
+            using Ogre::Real;
+
+            if (!smgr)
+                smgr = App::instance->sceneMgr;
+
+
+            const int numRings = 32;
+            const int numSegments = 16;
+            const int numSegHeight = 2;
+
+            const Real hlength = length/2;
+
+            ManualObject* manual = smgr->createManualObject();
+            manual->estimateVertexCount((2*numRings+2)*(numSegments+1) + (numSegHeight-1)*(numSegments+1));
+            manual->estimateIndexCount((2*numRings+1)*(numSegments+1)*6 + (numSegHeight-1)*(numSegments+1)*6);
+
+            manual->begin("capsule", RenderOperation::OT_TRIANGLE_LIST);
+
+            const Real deltaRingAngle = (Math::HALF_PI / numRings);
+            const Real deltaSegAngle = (Math::TWO_PI / numSegments);
+
+            const Real sphereRatio = radius / (2 * radius + length);
+            const Real cylinderRatio = length / (radius + length);
+
+            int offset = 0;
+            // Top half sphere
+
+            // Generate the group of rings for the sphere
+            for (unsigned ring = 0; ring <= numRings; ring++ ) {
+		Real r0 = radius * std::sin(ring * deltaRingAngle);
+		Real z0 = radius * std::cos(ring * deltaRingAngle);
+
+		// Generate the group of segments for the current ring
+		for (unsigned int seg = 0; seg <= numSegments; seg++) {
+                    Real x0 = r0 * std::cos(seg * deltaSegAngle);
+                    Real y0 = r0 * std::sin(seg * deltaSegAngle);
+
+                    // Add one vertex to the strip which makes up the sphere
+                    manual->position(x0, y0, hlength + z0);
+                    manual->normal(x0/radius, y0/radius, z0/radius);
+                    manual->textureCoord(seg / (Real) numSegments,
+                                         ring / (Real) numRings * sphereRatio);
+
+                    // each vertex (except the last) has six indices pointing to it
+                    manual->triangle(offset + numSegments,
+                                     offset + numSegments + 1,
+                                     offset);
+                    manual->triangle(offset,
+                                     offset + numSegments + 1,
+                                     offset + 1);
+
+                    offset ++;
+		} // end for seg
+            } // end for ring
+
+
+            // Cylinder part
+            const Real deltaAngle = (Math::TWO_PI / numSegments);
+            const Real deltamHeight = hlength * 2 / (Real)numSegHeight;
+
+            for (unsigned i = 1; i < numSegHeight; i++)
+		for (unsigned j = 0; j<=numSegments; j++) {
+                    Real x0 = radius * std::cos(j*deltaAngle);
+                    Real y0 = radius * std::sin(j*deltaAngle);
+
+                    manual->position(x0, y0, hlength - i*deltamHeight);
+                    manual->normal(x0/radius, y0/radius, 0);
+                    manual->textureCoord(j/(Real)numSegments,
+                                         i/(Real)numSegHeight * cylinderRatio + sphereRatio);
+
+                    manual->triangle(offset + numSegments,
+                                     offset + numSegments + 1,
+                                     offset);
+                    manual->triangle(offset,
+                                     offset + numSegments + 1,
+                                     offset + 1);
+
+                    offset ++;
+		}
+
+        
+            // Bottom half sphere
+
+            // Generate the group of rings for the sphere
+            for (unsigned ring = 0; ring <= numRings; ring++) {
+		Real r0 = radius * std::sin(Math::HALF_PI + ring * deltaRingAngle);
+		Real z0 =  radius * std::cos(Math::HALF_PI + ring * deltaRingAngle);
+
+		// Generate the group of segments for the current ring
+		for (unsigned seg = 0; seg <= numSegments; seg++) {
+                    Real x0 = r0 * std::cos(seg * deltaSegAngle);
+                    Real y0 = r0 * std::sin(seg * deltaSegAngle);
+
+                    // Add one vertex to the strip which makes up the sphere
+                    manual->position(x0, y0, -hlength + z0);
+                    manual->normal(x0/radius, y0/radius, z0/radius);
+                    manual->textureCoord(seg / (Real) numSegments,
+                                         ring / (Real) numRings*sphereRatio + cylinderRatio + sphereRatio);
+
+                    if (ring != numRings) {
+                        // each vertex (except the last) has six indices pointing to it
+                        manual->triangle(offset + numSegments,
+                                         offset + numSegments + 1,
+                                         offset);
+                        manual->triangle(offset,
+                                         offset + numSegments + 1,
+                                         offset + 1);
+                    }
+                    offset ++;
+		} // end for seg
+            } // end for ring
+
+            manual->end();
+
+            MeshPtr mesh = manual->convertToMesh(manual->getName());
+            unsigned short src, dest;
+            if (!mesh->suggestTangentVectorBuildParams(VES_TANGENT, src, dest))
+                mesh->buildTangentVectors(VES_TANGENT, src, dest);
+
+            node = smgr->getRootSceneNode()->createChildSceneNode();
+            node->attachObject(smgr->createEntity(mesh));
+
+            moved();
+        }
+
+
+
+        void
+        Capsule::moved()
+        {
+            node->setPosition(conv( getPosition() ));
+            node->setOrientation(conv( getOrientation() ));
+        }
+
+    }
+}

samples/KODEOgre/src/Cylinder.cpp

Empty file added.

samples/KODEOgre/src/Makefile.am

     BallSocket.cpp \
     Body.cpp \
     BodyBox.cpp \
+    BodyCapsule.cpp \
+    BodyCylinder.cpp \
     BodySphere.cpp \
     Box.cpp \
+    Capsule.cpp \
+    Cylinder.cpp \
     Hinge.cpp \
     Plane.cpp \
     Shape.cpp \

samples/KODEOgre/stack.cpp

+#include <stdexcept>
+#include <iostream>
+#include <memory>
+#include <vector>
+#include <utility>
+#include <random>
+
+#include <kode/kode.hpp>
+
+#include "KODEOgre.hpp"
+
+using namespace kode;
+using namespace kode::ogre;
+
+enum class ObjType {
+    Box,
+    Capsule,
+    Sphere
+};
+
+std::random_device rd;
+std::default_random_engine en(rd());
+std::uniform_real_distribution<Real> dist(0, 1);
+
+Real getRand()
+{
+    return dist(en);
+}
+
+struct Demo : public App {
+
+    bool paused = false;
+
+    bool randomPos = false;
+
+    HashSpace space;
+    Collider collider;
+
+    std::vector<std::unique_ptr<ogre::Body>> objects;
+
+    ogre::Plane ground{0, 1, 0, 0};
+    QuickStepSolver solver;
+
+    Demo()
+    {
+        world.setSolver(&solver);
+        solver.setIterations(200);
+        solver.setRelaxation(1.3);
+        world.setStepSize(0.05);
+        space.add(ground);
+    }
+
+
+    void step()
+    {
+        // update the simulation state
+        if (!paused) {
+
+            // check for collisions
+            std::vector<Contact> contacts;
+            std::vector<ContactPoint> points;
+
+            space.findPairs([&contacts, &points, this](Geom& a, Geom& b)
+                            {
+                                kode::Body* bodyA = a.getBody();
+                                kode::Body* bodyB = b.getBody();
+
+                                points.clear();
+
+                                this->collider.collide(a, b, points);
+
+                                for (const auto& p : points) {
+                                    Contact cont{bodyA, bodyB, p};
+                                    cont.setBounciness(0.25);
+                                    cont.setSoftCFM(0.1);
+                                    cont.setSoftERP(0.2);
+                                    cont.setMu(0.5);
+                                    cont.enablePyramidApprox();
+                                    contacts.push_back(std::move(cont));
+                                }
+                            });
+            world.step();
+        }
+    }
+
+
+    void dropObject(ObjType t)
+    {
+        std::unique_ptr<ogre::Body> obj;
+
+        switch (t) {
+            case ObjType::Box:
+                obj.reset(new BodyBox(world,
+                                      0.5*getRand() + 0.1,
+                                      0.5*getRand() + 0.1,
+                                      0.5*getRand() + 0.1));
+                break;
+            case ObjType::Capsule:
+                obj.reset(new BodyCapsule(world,
+                                          0.25*getRand() + 0.1,
+                                          getRand() + 0.1));
+                obj->setMass(Mass::SphereDensity(1, 1));
+                break;
+            case ObjType::Sphere:
+                obj.reset(new BodySphere(world,
+                                         0.25*getRand() + 0.1));
+                break;
+        }
+
+        obj->setDampingScale(0.01, 0.01);
+        obj->disableGyroscopic();
+
+        Geom& g = dynamic_cast<Geom&>(*obj);
+        space.add(g);
+
+        if (randomPos) {
+            obj->setPosition(4*getRand() - 2,
+                             3*getRand() + 2,
+                             4*getRand() - 2);
+            Quaternion q = {getRand(), getRand(), getRand(), getRand()};
+            if (!q.normalize())
+                q = {1,0,0,0};
+            obj->setOrientation(q);
+        } else {
+            obj->setPosition(0, 4, 0);
+            obj->rotate(90_deg, {1,0,0});
+        }
+
+        objects.push_back(std::move(obj));
+    }
+
+
+    bool keyPressed(const OIS::KeyEvent& evt) override
+    {
+        if (App::keyPressed(evt))
+            return true;
+
+        if (evt.key == OIS::KC_SPACE) {
+            paused = !paused;
+            return true;
+        }
+
+        if (evt.key == OIS::KC_R) {
+            randomPos = !randomPos;
+            return true;
+        }
+
+        if (evt.key == OIS::KC_1) {
+            dropObject(ObjType::Box);
+            return true;
+        }
+        if (evt.key == OIS::KC_2) {
+            dropObject(ObjType::Capsule);
+            return true;
+        }
+        if (evt.key == OIS::KC_3) {
+            dropObject(ObjType::Sphere);
+            return true;
+        }
+
+        return false;
+    }
+};
+
+int main()
+{
+    try {
+        Demo app;
+        app.run();
+    }
+    catch (std::exception& e) {
+        std::cout << e.what() << std::endl;
+    }
+}
+
 
 
     Mass
-    Mass::SphereTotal(Real radius, Real total)
-    {
-        const Real i = 0.4 * total * radius*radius;
-        return {total, i * Matrix3::Identity(), {0,0,0}};
-    }
-
-
-    Mass
-    Mass::SphereDensity(Real radius, Real density)
-    {
-        const Real total = density * 4 * Math::Pi * radius*radius*radius / 3;
-        return SphereTotal(radius, total);
-    }
-
-
-    Mass
     Mass::BoxTotal(const Vector3& size, Real total)
     {
         using Math::sqr;
     {
         return BoxTotal(size, density * size.x * size.y * size.z);
     }
+
+
+    Mass
+    Mass::SphereTotal(Real radius, Real total)
+    {
+        const Real i = 0.4 * total * radius*radius;
+        return {total, i * Matrix3::Identity(), {0,0,0}};
+    }
+
+
+    Mass
+    Mass::SphereDensity(Real radius, Real density)
+    {
+        const Real total = density * 4 * Math::Pi * radius*radius*radius / 3;
+        return SphereTotal(radius, total);
+    }
+
+
+    Mass
+    Mass::CapsuleTotal(Real radius, Real length, Real total)
+    {
+        const Real volume = Math::Pi*radius*radius*(4*radius/3 + length);
+        const Real density = total / volume;
+        return CapsuleDensity(radius, length, density);
+    }
+
+
+    Mass
+    Mass::CapsuleDensity(Real radius, Real length, Real density)
+    {
+        const Real M1 = Math::Pi * radius*radius*length * density; // cylinder mass
+        const Real M2 = (4*Math::Pi/3 * radius*radius*radius * density); // total cap mass
+        const Real total = M1 + M2;
+
+        const Real Ia = M1 * (radius*radius/4 + length*length/12) +
+                        M2 * (0.4 * radius*radius + 0.375*radius*length + length*length/4);
+        const Real Ib = (M1/2 + M2*0.4)*radius*radius;
+        const Matrix3 I = { Ia, 0, 0,
+                            0, Ia, 0,
+                            0, 0, Ib};
+        return {total, I, {0,0,0}};
+    }
 }

src/collision/Capsule.cpp

     Capsule::computeAABB() noexcept
     {
         const Matrix3& R = getLocalAxes();
-        const Vector3 hsize = hlength * R.col(2) + Vector3{radius,radius,radius};
+        const Vector3 hsize = hlength * abs(R.col(2)) + Vector3{radius,radius,radius};
         aabb.setBounds(getPosition() - hsize,
                        getPosition() + hsize);
     }

src/collision/Collider.cpp

 #include <kode/collision/tests/BoxPlane.hpp>
 #include <kode/collision/tests/BoxSphere.hpp>
 #include <kode/collision/tests/CapsulePlane.hpp>
+#include <kode/collision/tests/CylinderPlane.hpp>
 #include <kode/collision/tests/GeomGeomMPR.hpp>
 #include <kode/collision/tests/SphereSphere.hpp>
 #include <kode/collision/tests/PlaneSphere.hpp>
                         return collideGeomGeom(g1, g2, func);
                 }
 
+            case Type::Cylinder:
+                switch (t2) {
+                    case Type::Plane:
+                        return collideCylinderPlane(static_cast<const Cylinder&>(g1),
+                                                    static_cast<const Plane&>(g2),
+                                                    func);
+                    default:
+                        return collideGeomGeom(g1, g2, func);
+                }
+
             case Type::Plane:
                 switch (t2) {
                     case Type::Box:
                         return collidePlaneCapsule(static_cast<const Plane&>(g1),
                                                    static_cast<const Capsule&>(g2),
                                                    func);
+                    case Type::Cylinder:
+                        return collidePlaneCylinder(static_cast<const Plane&>(g1),
+                                                    static_cast<const Cylinder&>(g2),
+                                                    func);
                     case Type::Plane:
                         return collidePlanePlane(static_cast<const Plane&>(g1),
                                                  static_cast<const Plane&>(g2),
 
 
     void
+    Collider::collideCylinderPlane(const Cylinder& cylinder,
+                                   const Plane& plane,
+                                   const Callback& func) const
+    {
+        vector<ContactPoint> points;
+        testCylinderPlane(cylinder, plane, points);
+        for (ContactPoint& p : points)
+            func(p);
+    }
+
+
+
+    void
     Collider::collidePlaneBox(const Plane& plane,
                               const Box& box,
                               const Callback& func) const
 
 
     void
+    Collider::collidePlaneCylinder(const Plane& plane,
+                                   const Cylinder& cylinder,
+                                   const Callback& func) const
+    {
+        vector<ContactPoint> contacts;
+        testCylinderPlane(cylinder, plane, contacts);
+        for (ContactPoint& c : contacts) {
+            c.normal = -c.normal;
+            func(c);
+        }
+    }
+
+
+    void
     Collider::collidePlanePlane(const Plane&,
                                 const Plane&,
                                 const Callback&) const

src/collision/Cylinder.cpp

+/*
+   KODE Physics Library
+   Copyright 2013-2014 Daniel Kohler Osmari
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+#include <stdexcept>
+#include <algorithm>
+
+#include <kode/collision/Cylinder.hpp>
+
+namespace kode {
+
+    Cylinder::Cylinder(Real rad, Real len)
+    {
+        setRadiusLength(rad, len);
+    }
+
+
+    Type
+    Cylinder::getType() const noexcept
+    {
+        return Type::Cylinder;
+    }
+
+
+    void
+    Cylinder::computeAABB() noexcept
+    {
+        using Math::abs;
+        using Math::sqrt;
+        using Math::sqr;
+        using std::max;
+
+        const Vector3 up = getLocalAxes().col(2);
+
+        const Real xrange = hlength*abs(up.x) + radius*sqrt(max<Real>(0, 1 - sqr(up.x)));
+        const Real yrange = hlength*abs(up.y) + radius*sqrt(max<Real>(0, 1 - sqr(up.y)));
+        const Real zrange = hlength*abs(up.z) + radius*sqrt(max<Real>(0, 1 - sqr(up.z)));
+
+        aabb.setBounds(getPosition() - Vector3{xrange, yrange, zrange},
+                       getPosition() + Vector3{xrange, yrange, zrange});
+    }
+
+
+    Real
+    Cylinder::computePointDepth(const Vector3& point) const noexcept
+    {
+        const Vector3 localPoint = worldPointToLocal(point);
+        // first try depth towards the infinite cylinder surface
+        const Real zDepth = radius - localPoint.xy().length();
+        // now see if depth is smaller through the caps
+        const Real cDepth = hlength - Math::abs(localPoint.z);
+        return std::min(zDepth, cDepth);
+    }
+
+
+    Interval
+    Cylinder::computeProjected(const Vector3& axis) noexcept
+    {
+        const Vector3 up = getLocalAxes().col(2);
+
+        const Real c = Math::abs(dot(up, axis)); // cosine against the axis
+        const Real s = Math::sqrt(std::max<Real>(0, 1 - c*c)); // and the sine
+        const Real range = c*hlength + s*radius;
+        const Real pos = dot(getPosition(), axis);
+        return {pos - range, pos + range};
+    }
+
+
+    Vector3
+    Cylinder::computeSupport(const Vector3& dir) noexcept
+    {
+        const Vector3 localDir = worldVectorToLocal(dir);
+        Vector3 localSupport = {0, 0, Math::sign(localDir.z) * hlength};
+
+        const Real dist = localDir.xy().length();
+        if (dist > Epsilon) {
+            const Real scale = radius / dist;
+            localSupport.x = localDir.x * scale;
+            localSupport.y = localDir.y * scale;
+        }
+        return localPointToWorld(localSupport);
+    }
+
+
+    void
+    Cylinder::setRadius(Real rad)
+    {
+        if (!(rad > 0))
+            throw std::domain_error{"radius must be positive"};
+        radius = rad;
+        recomputeAABB();
+    }
+
+
+    void
+    Cylinder::setLength(Real len)
+    {
+        if (!(len > 0))
+            throw std::domain_error{"length must be positive"};
+        hlength = len/2;
+        recomputeAABB();
+    }
+
+
+    void
+    Cylinder::setRadiusLength(Real rad, Real len)
+    {
+        if (!(rad > 0))
+            throw std::domain_error{"radius must be positive"};
+        if (!(len > 0))
+            throw std::domain_error{"length must be positive"};
+        radius = rad;
+        hlength = len/2;
+        recomputeAABB();
+    }
+
+}

src/collision/HashSpace.cpp

                 for (int x=aabb.minX; x<=aabb.maxX; ++x)
                     for (int y=aabb.minY; y<=aabb.maxY; ++y)
                         for (int z=aabb.minZ; z<=aabb.maxZ; ++z) {
-                            Key k = {level, x, y, z};
+                            const Key k = {level, x, y, z};
                             table.insert(make_pair(k, g));
                         }
             }
                             auto b = table.bucket(k);
                             for (auto i=table.begin(b); i!=table.end(b); ++i) {
                                 Geom* g2 = i->second;
-                                // check if testing against itself, or out-of-order
-                                if (g1 >= g2)
+                                // check if testing against itself
+                                if (g1 == g2)
                                     continue;
                                 pair<Geom*,Geom*> k = {g1, g2};
                                 if (checked.count(k))

src/collision/tests/CapsulePlane.cpp

    limitations under the License.
 */
 
+#include <iostream> // debug
+
 #include <kode/collision/tests/CapsulePlane.hpp>
 
+#include <kode/collision/Capsule.hpp>
+#include <kode/collision/ContactPoint.hpp>
+#include <kode/collision/Plane.hpp>
+
+using std::clog;
+using std::endl;
+
 namespace kode {
 
     bool
-    testCapsulePlane(const Capsule& cap,
+    testCapsulePlane(const Capsule& capsule,
                      const Plane& plane,
                      std::vector<ContactPoint>& points)
     {
         // algorithm: collide both capping spheres against plane
-        const Real hlength = cap.getHalfLength();
-        const Real radius = cap.getRadius();
-        const Vector3 capZ = hlength * cap.getLocalAxes().col(2);
+        const Real hlength = capsule.getHalfLength();
+        const Real radius = capsule.getRadius();
+        const Vector3 capZ = hlength * capsule.getLocalAxes().col(2);
         const Vector3 normal = plane.getNormal();
 
         {
-            const Vector3 topCap = cap.getPosition() + capZ;
+            const Vector3 topCap = capsule.getPosition() + capZ;
             const Real dt = plane.distance(topCap);
-            if (dt <= radius) {
+            const Real depth = radius - dt;
+            if (depth >= 0) {
                 ContactPoint p = {topCap - radius*normal,
                                   normal,
-                                  radius - dt};
+                                  depth};
                 points.push_back(p);
             }
         }
 
         {
-            const Vector3 botCap = cap.getPosition() - capZ;
+            const Vector3 botCap = capsule.getPosition() - capZ;
             const Real db = plane.distance(botCap);
-            if (db <= radius) {
+            const Real depth = radius - db;
+            if (depth >= 0) {
                 ContactPoint p = {botCap - radius*normal,
                                   normal,
-                                  radius - db};
+                                  depth};
                 points.push_back(p);
             }
         }

src/collision/tests/CylinderPlane.cpp

+/*
+   KODE Physics Library
+   Copyright 2013-2014 Daniel Kohler Osmari
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+#include <kode/collision/tests/CylinderPlane.hpp>
+
+#include <kode/collision/Cylinder.hpp>
+#include <kode/collision/ContactPoint.hpp>
+#include <kode/collision/Plane.hpp>
+
+namespace kode {
+
+    bool
+    testCylinderPlane(const Cylinder& cylinder,
+                      const Plane& plane,
+                      std::vector<ContactPoint>& cp)
+    {
+        const Vector3 normal = plane.getNormal();
+        const Real cylRadius = cylinder.getRadius();
+        const Vector3 localDir = cylinder.worldVectorToLocal(normal);
+        const Real hlength = cylinder.getLength() / 2;
+
+        const Real dist = localDir.xy().length();
+
+        auto addIfDeep = [&plane,&cp,&normal](const Vector3& pt)
+                         {
+                             const Real depth = plane.pointDepth(pt);
+                             if (depth >= 0)
+                                 cp.push_back({pt, normal, depth});
+                         };
+
+        if (dist < Epsilon) {
+            // disc is parallel to plane, just generate arbitrary points for stability
+            const unsigned numPoints = 4;
+            for (unsigned i=0; i<numPoints; ++i) {
+                const Vector3 localPoint = { cylRadius * Math::cos(2*Math::Pi*i / numPoints),
+                                             cylRadius * Math::sin(2*Math::Pi*i / numPoints),
+                                             Math::sign(-localDir.z) * hlength};
+                const Vector3 p = cylinder.localPointToWorld(localPoint);
+                addIfDeep(p);
+            }
+        } else {
+            // check both discs, get only the support point for each
+            const Real scale = -cylRadius / dist;
+
+            const Vector3 p1 = cylinder.localPointToWorld({scale*localDir.x, scale*localDir.y, hlength});
+            addIfDeep(p1);
+
+            const Vector3 p2 = cylinder.localPointToWorld({scale*localDir.x, scale*localDir.y, -hlength});
+            addIfDeep(p2);
+        }
+
+        return !cp.empty();
+    }
+
+}

src/collision/tests/Makefile.am

     BoxSphere.cpp \
     GeomGeomMPR.cpp \
     CapsulePlane.cpp \
+    CylinderPlane.cpp \
     PlaneSphere.cpp \
     SphereSphere.cpp