From cdd062300946e7ce38152a28f2ae55c5ba754428 Mon Sep 17 00:00:00 2001
From: scrawl <scrawl@baseoftrash.de>
Date: Wed, 3 Jun 2015 01:18:36 +0200
Subject: [PATCH] Terrain rendering

---
 CMakeLists.txt                             |   2 +-
 apps/opencs/model/world/data.cpp           |   5 +
 apps/opencs/model/world/data.hpp           |   2 +
 apps/opencs/view/render/cell.cpp           |  20 +-
 apps/opencs/view/render/cell.hpp           |   2 -
 apps/opencs/view/render/terrainstorage.cpp |   3 +-
 apps/openmw/CMakeLists.txt                 |   4 +-
 apps/openmw/mwrender/localmap.cpp          |   4 +-
 apps/openmw/mwrender/renderingmanager.cpp  |  20 +-
 apps/openmw/mwrender/renderingmanager.hpp  |   8 +
 apps/openmw/mwrender/terrainstorage.cpp    |   3 +-
 apps/openmw/mwrender/terrainstorage.hpp    |   2 +-
 apps/openmw/mwrender/vismask.hpp           |   7 +-
 apps/openmw/mwrender/water.cpp             |   7 +-
 apps/openmw/mwrender/water.hpp             |   8 +-
 apps/openmw/mwworld/worldimp.cpp           |   4 +-
 components/CMakeLists.txt                  |   4 +-
 components/esm/loadland.hpp                |   2 +-
 components/esmterrain/storage.cpp          | 227 ++++----
 components/esmterrain/storage.hpp          |  48 +-
 components/terrain/buffercache.cpp         | 129 ++---
 components/terrain/buffercache.hpp         |  14 +-
 components/terrain/chunk.cpp               | 169 ------
 components/terrain/chunk.hpp               |  75 ---
 components/terrain/defaultworld.cpp        | 336 -----------
 components/terrain/defaultworld.hpp        | 177 ------
 components/terrain/defs.hpp                |  42 +-
 components/terrain/material.cpp            | 393 +++----------
 components/terrain/material.hpp            |  68 +--
 components/terrain/quadtreenode.cpp        | 611 ---------------------
 components/terrain/quadtreenode.hpp        | 189 -------
 components/terrain/storage.cpp             |   2 +
 components/terrain/storage.hpp             |  41 +-
 components/terrain/terraingrid.cpp         | 227 ++++----
 components/terrain/terraingrid.hpp         |  49 +-
 components/terrain/world.cpp               |  56 +-
 components/terrain/world.hpp               |  82 +--
 37 files changed, 541 insertions(+), 2501 deletions(-)
 delete mode 100644 components/terrain/chunk.cpp
 delete mode 100644 components/terrain/chunk.hpp
 delete mode 100644 components/terrain/defaultworld.cpp
 delete mode 100644 components/terrain/defaultworld.hpp
 delete mode 100644 components/terrain/quadtreenode.cpp
 delete mode 100644 components/terrain/quadtreenode.hpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 43d40c2ef9..d134372141 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -174,7 +174,7 @@ if (${OGRE_VERSION} VERSION_LESS "1.9")
     message(FATAL_ERROR "OpenMW requires Ogre 1.9 or later, please install the latest stable version from http://ogre3d.org")
 endif()
 
-find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgGA osgAnimation osgParticle osgQt osgUtil)
+find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgGA osgAnimation osgParticle osgQt osgUtil osgFX)
 include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS})
 
 find_package(MyGUI REQUIRED)
diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp
index 064e96150a..9772a68fa7 100644
--- a/apps/opencs/model/world/data.cpp
+++ b/apps/opencs/model/world/data.cpp
@@ -488,6 +488,11 @@ Resource::ResourceSystem* CSMWorld::Data::getResourceSystem()
     return &mResourceSystem;
 }
 
+const Resource::ResourceSystem* CSMWorld::Data::getResourceSystem() const
+{
+    return &mResourceSystem;
+}
+
 const CSMWorld::IdCollection<ESM::Global>& CSMWorld::Data::getGlobals() const
 {
     return mGlobals;
diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp
index 93cd46d861..a41946687d 100644
--- a/apps/opencs/model/world/data.hpp
+++ b/apps/opencs/model/world/data.hpp
@@ -140,6 +140,8 @@ namespace CSMWorld
 
             Resource::ResourceSystem* getResourceSystem();
 
+            const Resource::ResourceSystem* getResourceSystem() const;
+
             const IdCollection<ESM::Global>& getGlobals() const;
 
             IdCollection<ESM::Global>& getGlobals();
diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp
index 343d0d2e68..063413248a 100644
--- a/apps/opencs/view/render/cell.cpp
+++ b/apps/opencs/view/render/cell.cpp
@@ -64,7 +64,6 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st
 
     addObjects (0, rows-1);
 
-    /*
     const CSMWorld::IdCollection<CSMWorld::Land>& land = mData.getLand();
     int landIndex = land.searchId(mId);
     if (landIndex != -1)
@@ -72,27 +71,18 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st
         const ESM::Land* esmLand = land.getRecord(mId).get().mLand.get();
         if(esmLand && esmLand->mDataTypes&ESM::Land::DATA_VHGT)
         {
-            mTerrain.reset(new Terrain::TerrainGrid(sceneManager, new TerrainStorage(mData), Element_Terrain, true,
-                                                    Terrain::Align_XY));
+            mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem(), NULL, new TerrainStorage(mData), Element_Terrain<<1));
             mTerrain->loadCell(esmLand->mX,
                                esmLand->mY);
 
-            //float verts = ESM::Land::LAND_SIZE;
-            //float worldsize = ESM::Land::REAL_SIZE;
             mX = esmLand->mX;
             mY = esmLand->mY;
-            //mPhysics->addHeightField(sceneManager,
-            //        esmLand->mLandData->mHeights, mX, mY, 0, worldsize / (verts-1), verts);
         }
     }
-    */
 }
 
 CSVRender::Cell::~Cell()
 {
-    //if (mTerrain.get())
-    //    mPhysics->removeHeightField(mSceneMgr, mX, mY);
-
     for (std::map<std::string, Object *>::iterator iter (mObjects.begin());
         iter!=mObjects.end(); ++iter)
         delete iter->second;
@@ -221,11 +211,3 @@ bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int
 
     return addObjects (start, end);
 }
-
-float CSVRender::Cell::getTerrainHeightAt(const Ogre::Vector3 &pos) const
-{
-    if(mTerrain.get() != NULL)
-        return mTerrain->getHeightAt(pos);
-    else
-        return -std::numeric_limits<float>::max();
-}
diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp
index 259ab1779e..f4272b8879 100644
--- a/apps/opencs/view/render/cell.hpp
+++ b/apps/opencs/view/render/cell.hpp
@@ -75,8 +75,6 @@ namespace CSVRender
             /// \return Did this call result in a modification of the visual representation of
             /// this cell?
             bool referenceAdded (const QModelIndex& parent, int start, int end);
-
-            float getTerrainHeightAt(const Ogre::Vector3 &pos) const;
     };
 }
 
diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp
index a14eea5dd6..fe302cef14 100644
--- a/apps/opencs/view/render/terrainstorage.cpp
+++ b/apps/opencs/view/render/terrainstorage.cpp
@@ -4,7 +4,8 @@ namespace CSVRender
 {
 
     TerrainStorage::TerrainStorage(const CSMWorld::Data &data)
-        : mData(data)
+        : ESMTerrain::Storage(data.getResourceSystem()->getVFS())
+        , mData(data)
     {
     }
 
diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt
index bb71cb6c9b..ef0c4e1351 100644
--- a/apps/openmw/CMakeLists.txt
+++ b/apps/openmw/CMakeLists.txt
@@ -22,8 +22,8 @@ source_group(game FILES ${GAME} ${GAME_HEADER})
 add_openmw_dir (mwrender
     actors objects renderingmanager animation rotatecontroller sky npcanimation vismask
     creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation
-    bulletdebugdraw globalmap characterpreview camera localmap water
-#    occlusionquery shadows ripplesimulation terrainstorage
+    bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage
+#    occlusionquery shadows ripplesimulation
     )
 
 add_openmw_dir (mwinput
diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp
index 3003e47366..16957a585f 100644
--- a/apps/openmw/mwrender/localmap.cpp
+++ b/apps/openmw/mwrender/localmap.cpp
@@ -157,7 +157,7 @@ osg::ref_ptr<osg::Camera> LocalMap::createOrthographicCamera(float x, float y, f
     camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     camera->setRenderOrder(osg::Camera::PRE_RENDER);
 
-    camera->setCullMask(MWRender::Mask_Scene|MWRender::Mask_Water);
+    camera->setCullMask(Mask_Scene|Mask_Water|Mask_Terrain);
     camera->setNodeMask(Mask_RenderToTexture);
 
     osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
@@ -309,7 +309,7 @@ void LocalMap::requestExteriorMap(MWWorld::CellStore* cell)
 void LocalMap::requestInteriorMap(MWWorld::CellStore* cell)
 {
     osg::ComputeBoundsVisitor computeBoundsVisitor;
-    computeBoundsVisitor.setTraversalMask(Mask_Scene);
+    computeBoundsVisitor.setTraversalMask(Mask_Scene|Mask_Terrain);
     mSceneRoot->accept(computeBoundsVisitor);
 
     osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox();
diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp
index a708bbe88c..6205c3ad87 100644
--- a/apps/openmw/mwrender/renderingmanager.cpp
+++ b/apps/openmw/mwrender/renderingmanager.cpp
@@ -26,6 +26,8 @@
 #include <components/sceneutil/lightmanager.hpp>
 #include <components/sceneutil/statesetupdater.hpp>
 
+#include <components/terrain/terraingrid.hpp>
+
 #include <components/esm/loadcell.hpp>
 
 #include "sky.hpp"
@@ -35,6 +37,7 @@
 #include "pathgrid.hpp"
 #include "camera.hpp"
 #include "water.hpp"
+#include "terrainstorage.hpp"
 
 namespace MWRender
 {
@@ -132,7 +135,10 @@ namespace MWRender
 
         mEffectManager.reset(new EffectManager(lightRoot, mResourceSystem));
 
-        mWater.reset(new Water(lightRoot, mResourceSystem));
+        mWater.reset(new Water(lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation()));
+
+        mTerrain.reset(new Terrain::TerrainGrid(lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(),
+                                                new TerrainStorage(mResourceSystem->getVFS(), false), Mask_Terrain));
 
         mCamera.reset(new Camera(mViewer->getCamera()));
 
@@ -236,12 +242,18 @@ namespace MWRender
         mPathgrid->addCell(store);
 
         mWater->changeCell(store);
+
+        if (store->getCell()->isExterior())
+            mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY());
     }
 
     void RenderingManager::removeCell(const MWWorld::CellStore *store)
     {
         mPathgrid->removeCell(store);
         mObjects->removeCell(store);
+
+        if (store->getCell()->isExterior())
+            mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY());
     }
 
     void RenderingManager::setSkyEnabled(bool enabled)
@@ -482,7 +494,6 @@ namespace MWRender
 
     void RenderingManager::clear()
     {
-        //mLocalMap->clear();
         notifyWorldSpaceChanged();
     }
 
@@ -595,6 +606,11 @@ namespace MWRender
         return mNearClip;
     }
 
+    float RenderingManager::getTerrainHeightAt(const osg::Vec3f &pos)
+    {
+        return mTerrain->getHeightAt(pos);
+    }
+
     bool RenderingManager::vanityRotateCamera(const float *rot)
     {
         if(!mCamera->isVanityOrPreviewModeEnabled())
diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp
index d90a75c865..c22d3c7bfd 100644
--- a/apps/openmw/mwrender/renderingmanager.hpp
+++ b/apps/openmw/mwrender/renderingmanager.hpp
@@ -32,6 +32,11 @@ namespace ESM
     struct Cell;
 }
 
+namespace Terrain
+{
+    class World;
+}
+
 namespace MWRender
 {
 
@@ -119,6 +124,8 @@ namespace MWRender
 
         float getNearClipDistance() const;
 
+        float getTerrainHeightAt(const osg::Vec3f& pos);
+
         // camera stuff
         bool vanityRotateCamera(const float *rot);
         void setCameraDistance(float dist, bool adjust, bool override);
@@ -147,6 +154,7 @@ namespace MWRender
         std::auto_ptr<Pathgrid> mPathgrid;
         std::auto_ptr<Objects> mObjects;
         std::auto_ptr<Water> mWater;
+        std::auto_ptr<Terrain::World> mTerrain;
         std::auto_ptr<SkyManager> mSky;
         std::auto_ptr<EffectManager> mEffectManager;
         std::auto_ptr<NpcAnimation> mPlayerAnimation;
diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp
index 8ad2ea3213..269e7f99fc 100644
--- a/apps/openmw/mwrender/terrainstorage.cpp
+++ b/apps/openmw/mwrender/terrainstorage.cpp
@@ -9,7 +9,8 @@
 namespace MWRender
 {
 
-    TerrainStorage::TerrainStorage(bool preload)
+    TerrainStorage::TerrainStorage(const VFS::Manager* vfs, bool preload)
+        : ESMTerrain::Storage(vfs)
     {
         if (preload)
         {
diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp
index e6f4a04ad3..93531a5523 100644
--- a/apps/openmw/mwrender/terrainstorage.hpp
+++ b/apps/openmw/mwrender/terrainstorage.hpp
@@ -16,7 +16,7 @@ namespace MWRender
 
         ///@param preload Preload all Land records at startup? If using the multithreaded terrain component, this
         /// should be set to "true" in order to avoid race conditions.
-        TerrainStorage(bool preload);
+        TerrainStorage(const VFS::Manager* vfs, bool preload);
 
         /// Get bounds of the whole terrain in cell units
         virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY);
diff --git a/apps/openmw/mwrender/vismask.hpp b/apps/openmw/mwrender/vismask.hpp
index 3a0336f822..b794ac24a9 100644
--- a/apps/openmw/mwrender/vismask.hpp
+++ b/apps/openmw/mwrender/vismask.hpp
@@ -16,13 +16,14 @@ namespace MWRender
         Mask_Player = (1<<4),
         Mask_Sky = (1<<5),
         Mask_Water = (1<<6),
+        Mask_Terrain = (1<<7),
 
         // top level masks
-        Mask_Scene = (1<<7),
-        Mask_GUI = (1<<8),
+        Mask_Scene = (1<<8),
+        Mask_GUI = (1<<9),
 
         // Set on cameras within the main scene graph
-        Mask_RenderToTexture = (1<<9)
+        Mask_RenderToTexture = (1<<10)
 
         // reserved: (1<<16) for SceneUtil::Mask_Lit
     };
diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp
index 2dd843e4ed..c2149358bd 100644
--- a/apps/openmw/mwrender/water.cpp
+++ b/apps/openmw/mwrender/water.cpp
@@ -9,6 +9,8 @@
 #include <osg/PositionAttitudeTransform>
 #include <osg/Depth>
 
+#include <osgUtil/IncrementalCompileOperation>
+
 #include <components/resource/resourcesystem.hpp>
 #include <components/resource/texturemanager.hpp>
 
@@ -105,7 +107,7 @@ namespace MWRender
 
 // --------------------------------------------------------------------------------------------------------------------------------
 
-Water::Water(osg::Group *parent, Resource::ResourceSystem *resourceSystem)
+Water::Water(osg::Group *parent, Resource::ResourceSystem *resourceSystem, osgUtil::IncrementalCompileOperation *ico)
     : mParent(parent)
     , mResourceSystem(resourceSystem)
     , mEnabled(true)
@@ -118,6 +120,9 @@ Water::Water(osg::Group *parent, Resource::ResourceSystem *resourceSystem)
     geode->addDrawable(waterGeom);
     geode->setNodeMask(Mask_Water);
 
+    if (ico)
+        ico->add(geode);
+
     createWaterStateSet(mResourceSystem, geode);
 
     mWaterNode = new osg::PositionAttitudeTransform;
diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp
index 45b4b38a60..d389392ba8 100644
--- a/apps/openmw/mwrender/water.hpp
+++ b/apps/openmw/mwrender/water.hpp
@@ -11,6 +11,11 @@ namespace osg
     class PositionAttitudeTransform;
 }
 
+namespace osgUtil
+{
+    class IncrementalCompileOperation;
+}
+
 namespace Resource
 {
     class ResourceSystem;
@@ -27,6 +32,7 @@ namespace MWRender
         osg::ref_ptr<osg::Group> mParent;
         osg::ref_ptr<osg::PositionAttitudeTransform> mWaterNode;
         Resource::ResourceSystem* mResourceSystem;
+        osg::ref_ptr<osgUtil::IncrementalCompileOperation> mIncrementalCompileOperation;
 
         bool mEnabled;
         bool mToggled;
@@ -36,7 +42,7 @@ namespace MWRender
         void updateVisible();
 
     public:
-        Water(osg::Group* parent, Resource::ResourceSystem* resourceSystem);
+        Water(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico);
         ~Water();
 
         void setEnabled(bool enabled);
diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp
index 799f82015d..e676977de3 100644
--- a/apps/openmw/mwworld/worldimp.cpp
+++ b/apps/openmw/mwworld/worldimp.cpp
@@ -1298,7 +1298,9 @@ namespace MWWorld
             return;
         }
 
-        float terrainHeight = -FLT_MAX;//mRendering->getTerrainHeightAt(Ogre::Vector3(pos.pos));
+        float terrainHeight = -std::numeric_limits<float>::max();
+        if (isCellExterior())
+            terrainHeight = mRendering->getTerrainHeightAt(pos.asVec3());
 
         if (pos.pos[2] < terrainHeight)
             pos.pos[2] = terrainHeight;
diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt
index 6a5b7f59a9..51cb55aee2 100644
--- a/components/CMakeLists.txt
+++ b/components/CMakeLists.txt
@@ -104,10 +104,8 @@ add_component_dir (translation
     translation
     )
 
-#add_definitions(-DTERRAIN_USE_SHADER=1)
-add_definitions(-DTERRAIN_USE_SHADER=0)
 add_component_dir (terrain
-    quadtreenode chunk world defaultworld terraingrid storage material buffercache defs
+    storage world buffercache defs terraingrid material
     )
 
 add_component_dir (loadinglistener
diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp
index e510616aff..edaeadb03d 100644
--- a/components/esm/loadland.hpp
+++ b/components/esm/loadland.hpp
@@ -80,7 +80,7 @@ struct Land
         VNML mNormals[LAND_NUM_VERTS * 3];
         uint16_t mTextures[LAND_NUM_TEXTURES];
 
-        char mColours[3 * LAND_NUM_VERTS];
+        unsigned char mColours[3 * LAND_NUM_VERTS];
         int mDataTypes;
 
         // low-LOD heightmap (used for rendering the global map)
diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp
index 5a69fd27a8..0795a3ffc9 100644
--- a/components/esmterrain/storage.cpp
+++ b/components/esmterrain/storage.cpp
@@ -1,34 +1,38 @@
 #include "storage.hpp"
 
-#include <OgreVector2.h>
-#include <OgreTextureManager.h>
-#include <OgreStringConverter.h>
-#include <OgreRenderSystem.h>
-#include <OgreResourceGroupManager.h>
-#include <OgreResourceBackgroundQueue.h>
-#include <OgreRoot.h>
+#include <set>
+
+#include <osg/Image>
+#include <osg/Plane>
+
+#include <iostream>
 
 #include <boost/algorithm/string.hpp>
 
-#include <components/terrain/quadtreenode.hpp>
 #include <components/misc/resourcehelpers.hpp>
+#include <components/vfs/manager.hpp>
 
 namespace ESMTerrain
 {
 
-    bool Storage::getMinMaxHeights(float size, const Ogre::Vector2 &center, float &min, float &max)
+    Storage::Storage(const VFS::Manager *vfs)
+        : mVFS(vfs)
+    {
+    }
+
+    bool Storage::getMinMaxHeights(float size, const osg::Vec2f &center, float &min, float &max)
     {
         assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell");
 
         /// \todo investigate if min/max heights should be stored at load time in ESM::Land instead
 
-        Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
+        osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f);
 
-        assert(origin.x == (int) origin.x);
-        assert(origin.y == (int) origin.y);
+        assert(origin.x == (int) origin.x());
+        assert(origin.y == (int) origin.y());
 
-        int cellX = static_cast<int>(origin.x);
-        int cellY = static_cast<int>(origin.y);
+        int cellX = static_cast<int>(origin.x());
+        int cellY = static_cast<int>(origin.y());
 
         const ESM::Land* land = getLand(cellX, cellY);
         if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT))
@@ -50,7 +54,7 @@ namespace ESMTerrain
         return true;
     }
 
-    void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row)
+    void Storage::fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row)
     {
         while (col >= ESM::Land::LAND_SIZE-1)
         {
@@ -75,27 +79,27 @@ namespace ESMTerrain
         ESM::Land* land = getLand(cellX, cellY);
         if (land && land->mDataTypes&ESM::Land::DATA_VNML)
         {
-            normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
-            normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
-            normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
-            normal.normalise();
+            normal.x() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
+            normal.y() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
+            normal.z() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
+            normal.normalize();
         }
         else
-            normal = Ogre::Vector3(0,0,1);
+            normal = osg::Vec3f(0,0,1);
     }
 
-    void Storage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row)
+    void Storage::averageNormal(osg::Vec3f &normal, int cellX, int cellY, int col, int row)
     {
-        Ogre::Vector3 n1,n2,n3,n4;
+        osg::Vec3f n1,n2,n3,n4;
         fixNormal(n1, cellX, cellY, col+1, row);
         fixNormal(n2, cellX, cellY, col-1, row);
         fixNormal(n3, cellX, cellY, col, row+1);
         fixNormal(n4, cellX, cellY, col, row-1);
         normal = (n1+n2+n3+n4);
-        normal.normalise();
+        normal.normalize();
     }
 
-    void Storage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row)
+    void Storage::fixColour (osg::Vec4f& color, int cellX, int cellY, int col, int row)
     {
         if (col == ESM::Land::LAND_SIZE-1)
         {
@@ -110,42 +114,42 @@ namespace ESMTerrain
         ESM::Land* land = getLand(cellX, cellY);
         if (land && land->mDataTypes&ESM::Land::DATA_VCLR)
         {
-            color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
-            color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
-            color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
+            color.r() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
+            color.g() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
+            color.b() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
         }
         else
         {
-            color.r = 1;
-            color.g = 1;
-            color.b = 1;
+            color.r() = 1;
+            color.g() = 1;
+            color.b() = 1;
         }
 
     }
 
-    void Storage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
-                                            std::vector<float>& positions,
-                                            std::vector<float>& normals,
-                                            std::vector<Ogre::uint8>& colours)
+    void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
+                                            osg::ref_ptr<osg::Vec3Array> positions,
+                                            osg::ref_ptr<osg::Vec3Array> normals,
+                                            osg::ref_ptr<osg::Vec4Array> colours)
     {
         // LOD level n means every 2^n-th vertex is kept
         size_t increment = 1 << lodLevel;
 
-        Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
-        assert(origin.x == (int) origin.x);
-        assert(origin.y == (int) origin.y);
+        osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f);
+        assert(origin.x() == (int) origin.x());
+        assert(origin.y() == (int) origin.y());
 
-        int startX = static_cast<int>(origin.x);
-        int startY = static_cast<int>(origin.y);
+        int startX = static_cast<int>(origin.x());
+        int startY = static_cast<int>(origin.y());
 
         size_t numVerts = static_cast<size_t>(size*(ESM::Land::LAND_SIZE - 1) / increment + 1);
 
-        colours.resize(numVerts*numVerts*4);
-        positions.resize(numVerts*numVerts*3);
-        normals.resize(numVerts*numVerts*3);
+        positions->resize(numVerts*numVerts);
+        normals->resize(numVerts*numVerts);
+        colours->resize(numVerts*numVerts);
 
-        Ogre::Vector3 normal;
-        Ogre::ColourValue color;
+        osg::Vec3f normal;
+        osg::Vec4f color;
 
         float vertY = 0;
         float vertX = 0;
@@ -175,22 +179,24 @@ namespace ESMTerrain
                     vertX = vertX_;
                     for (int row=rowStart; row<ESM::Land::LAND_SIZE; row += increment)
                     {
-                        positions[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3)] = ((vertX / float(numVerts - 1) - 0.5f) * size * 8192);
-                        positions[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3 + 1)] = ((vertY / float(numVerts - 1) - 0.5f) * size * 8192);
+                        float height = -2048;
                         if (land)
-                            positions[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3 + 2)] = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE + row];
-                        else
-                            positions[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3 + 2)] = -2048;
+                            height = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE + row];
+
+                        (*positions)[static_cast<unsigned int>(vertX*numVerts + vertY)]
+                            = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * 8192,
+                                         (vertY / float(numVerts - 1) - 0.5f) * size * 8192,
+                                         height);
 
                         if (land && land->mDataTypes&ESM::Land::DATA_VNML)
                         {
-                            normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
-                            normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
-                            normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
-                            normal.normalise();
+                            normal.x() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
+                            normal.y() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
+                            normal.z() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
+                            normal.normalize();
                         }
                         else
-                            normal = Ogre::Vector3(0,0,1);
+                            normal = osg::Vec3f(0,0,1);
 
                         // Normals apparently don't connect seamlessly between cells
                         if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
@@ -200,33 +206,30 @@ namespace ESMTerrain
                         if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1))
                             averageNormal(normal, cellX, cellY, col, row);
 
-                        assert(normal.z > 0);
+                        assert(normal.z() > 0);
 
-                        normals[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3)] = normal.x;
-                        normals[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3 + 1)] = normal.y;
-                        normals[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3 + 2)] = normal.z;
+                        (*normals)[static_cast<unsigned int>(vertX*numVerts + vertY)] = normal;
 
                         if (land && land->mDataTypes&ESM::Land::DATA_VCLR)
                         {
-                            color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
-                            color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
-                            color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
+                            color.r() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
+                            color.g() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
+                            color.b() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
                         }
                         else
                         {
-                            color.r = 1;
-                            color.g = 1;
-                            color.b = 1;
+                            color.r() = 1;
+                            color.g() = 1;
+                            color.b() = 1;
                         }
 
                         // Unlike normals, colors mostly connect seamlessly between cells, but not always...
                         if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
                             fixColour(color, cellX, cellY, col, row);
 
-                        color.a = 1;
-                        Ogre::uint32 rsColor;
-                        Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor);
-                        memcpy(&colours[static_cast<unsigned int>(vertX*numVerts * 4 + vertY * 4)], &rsColor, sizeof(Ogre::uint32));
+                        color.a() = 1;
+
+                        (*colours)[static_cast<unsigned int>(vertX*numVerts + vertY)] = color;
 
                         ++vertX;
                     }
@@ -281,39 +284,22 @@ namespace ESMTerrain
         // NB: All vtex ids are +1 compared to the ltex ids
         const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second);
 
-        //TODO this is needed due to MWs messed up texture handling
-        assert(0 && "no vfs here yet");
-        std::string texture = ltex->mTexture; //Misc::ResourceHelpers::correctTexturePath(ltex->mTexture);
+        // this is needed due to MWs messed up texture handling
+        std::string texture = Misc::ResourceHelpers::correctTexturePath(ltex->mTexture, mVFS);
 
         return texture;
     }
 
-    void Storage::getBlendmaps (const std::vector<Terrain::QuadTreeNode*>& nodes, std::vector<Terrain::LayerCollection>& out, bool pack)
-    {
-        for (std::vector<Terrain::QuadTreeNode*>::const_iterator it = nodes.begin(); it != nodes.end(); ++it)
-        {
-            out.push_back(Terrain::LayerCollection());
-            out.back().mTarget = *it;
-            getBlendmapsImpl(static_cast<float>((*it)->getSize()), (*it)->getCenter(), pack, out.back().mBlendmaps, out.back().mLayers);
-        }
-    }
-
-    void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter,
-        bool pack, std::vector<Ogre::PixelBox> &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
-    {
-        getBlendmapsImpl(chunkSize, chunkCenter, pack, blendmaps, layerList);
-    }
-
-    void Storage::getBlendmapsImpl(float chunkSize, const Ogre::Vector2 &chunkCenter,
-        bool pack, std::vector<Ogre::PixelBox> &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
+    void Storage::getBlendmaps(float chunkSize, const osg::Vec2f &chunkCenter,
+        bool pack, ImageVector &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
     {
         // TODO - blending isn't completely right yet; the blending radius appears to be
         // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap
         // and interpolate the rest of the cell by hand? :/
 
-        Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f);
-        int cellX = static_cast<int>(origin.x);
-        int cellY = static_cast<int>(origin.y);
+        osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize/2.f, chunkSize/2.f);
+        int cellX = static_cast<int>(origin.x());
+        int cellY = static_cast<int>(origin.y());
 
         // Save the used texture indices so we know the total number of textures
         // and number of required blend maps
@@ -353,11 +339,11 @@ namespace ESMTerrain
 
         for (int i=0; i<numBlendmaps; ++i)
         {
-            Ogre::PixelFormat format = pack ? Ogre::PF_A8B8G8R8 : Ogre::PF_A8;
+            GLenum format = pack ? GL_RGBA : GL_ALPHA;
 
-            Ogre::uchar* pData =
-                            OGRE_ALLOC_T(Ogre::uchar, blendmapSize*blendmapSize*channels, Ogre::MEMCATEGORY_GENERAL);
-            memset(pData, 0, blendmapSize*blendmapSize*channels);
+            osg::ref_ptr<osg::Image> image (new osg::Image);
+            image->allocateImage(blendmapSize, blendmapSize, 1, format, GL_UNSIGNED_BYTE);
+            unsigned char* pData = image->data();
 
             for (int y=0; y<blendmapSize; ++y)
             {
@@ -374,14 +360,15 @@ namespace ESMTerrain
                         pData[y*blendmapSize*channels + x*channels + channel] = 0;
                 }
             }
-            blendmaps.push_back(Ogre::PixelBox(blendmapSize, blendmapSize, 1, format, pData));
+
+            blendmaps.push_back(image);
         }
     }
 
-    float Storage::getHeightAt(const Ogre::Vector3 &worldPos)
+    float Storage::getHeightAt(const osg::Vec3f &worldPos)
     {
-        int cellX = static_cast<int>(std::floor(worldPos.x / 8192.f));
-        int cellY = static_cast<int>(std::floor(worldPos.y / 8192.f));
+        int cellX = static_cast<int>(std::floor(worldPos.x() / 8192.f));
+        int cellY = static_cast<int>(std::floor(worldPos.y() / 8192.f));
 
         ESM::Land* land = getLand(cellX, cellY);
         if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT))
@@ -390,8 +377,8 @@ namespace ESMTerrain
         // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition
 
         // Normalized position in the cell
-        float nX = (worldPos.x - (cellX * 8192))/8192.f;
-        float nY = (worldPos.y - (cellY * 8192))/8192.f;
+        float nX = (worldPos.x() - (cellX * 8192))/8192.f;
+        float nY = (worldPos.y() - (cellY * 8192))/8192.f;
 
         // get left / bottom points (rounded down)
         float factor = ESM::Land::LAND_SIZE - 1.0f;
@@ -423,22 +410,23 @@ namespace ESMTerrain
         */
 
         // Build all 4 positions in normalized cell space, using point-sampled height
-        Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f);
-        Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f);
-        Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f);
-        Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f);
+        osg::Vec3f v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f);
+        osg::Vec3f v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f);
+        osg::Vec3f v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f);
+        osg::Vec3f v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f);
         // define this plane in terrain space
-        Ogre::Plane plane;
-        // (At the moment, all rows have the same triangle alignment)
+        osg::Plane plane;
+        // FIXME: deal with differing triangle alignment
         if (true)
         {
             // odd row
             bool secondTri = ((1.0 - yParam) > xParam);
             if (secondTri)
-                plane.redefine(v0, v1, v3);
+                plane = osg::Plane(v0, v1, v3);
             else
-                plane.redefine(v1, v2, v3);
+                plane = osg::Plane(v1, v2, v3);
         }
+        /*
         else
         {
             // even row
@@ -448,11 +436,12 @@ namespace ESMTerrain
             else
                 plane.redefine(v0, v1, v2);
         }
+        */
 
         // Solve plane equation for z
-        return (-plane.normal.x * nX
-                -plane.normal.y * nY
-                - plane.d) / plane.normal.z * 8192;
+        return (-plane.getNormal().x() * nX
+                -plane.getNormal().y() * nY
+                - plane[3]) / plane.getNormal().z() * 8192;
 
     }
 
@@ -477,7 +466,7 @@ namespace ESMTerrain
         std::string texture_ = texture;
         boost::replace_last(texture_, ".", "_nh.");
 
-        if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_))
+        if (mVFS->exists(texture_))
         {
             info.mNormalMap = texture_;
             info.mParallax = true;
@@ -486,24 +475,18 @@ namespace ESMTerrain
         {
             texture_ = texture;
             boost::replace_last(texture_, ".", "_n.");
-            if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_))
+            if (mVFS->exists(texture_))
                 info.mNormalMap = texture_;
         }
 
         texture_ = texture;
         boost::replace_last(texture_, ".", "_diffusespec.");
-        if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_))
+        if (mVFS->exists(texture_))
         {
             info.mDiffuseMap = texture_;
             info.mSpecular = true;
         }
 
-        // This wasn't cached, so the textures are probably not loaded either.
-        // Background load them so they are hopefully already loaded once we need them!
-        Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mDiffuseMap, "General");
-        if (!info.mNormalMap.empty())
-            Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mNormalMap, "General");
-
         mLayerInfoMap[texture] = info;
 
         return info;
diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp
index e184cbc4cd..8f4a3aa921 100644
--- a/components/esmterrain/storage.hpp
+++ b/components/esmterrain/storage.hpp
@@ -6,6 +6,11 @@
 #include <components/esm/loadland.hpp>
 #include <components/esm/loadltex.hpp>
 
+namespace VFS
+{
+    class Manager;
+}
+
 namespace ESMTerrain
 {
 
@@ -20,6 +25,7 @@ namespace ESMTerrain
         virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0;
 
     public:
+        Storage(const VFS::Manager* vfs);
 
         // Not implemented in this class, because we need different Store implementations for game and editor
         /// Get bounds of the whole terrain in cell units
@@ -33,11 +39,10 @@ namespace ESMTerrain
         /// @param min min height will be stored here
         /// @param max max height will be stored here
         /// @return true if there was data available for this terrain chunk
-        virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max);
+        virtual bool getMinMaxHeights (float size, const osg::Vec2f& center, float& min, float& max);
 
         /// Fill vertex buffers for a terrain chunk.
         /// @note May be called from background threads. Make sure to only call thread-safe functions from here!
-        /// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue.
         /// @note Vertices should be written in row-major order (a row is defined as parallel to the x-axis).
         ///       The specified positions should be in local space, i.e. relative to the center of the terrain chunk.
         /// @param lodLevel LOD level, 0 = most detailed
@@ -46,10 +51,10 @@ namespace ESMTerrain
         /// @param positions buffer to write vertices
         /// @param normals buffer to write vertex normals
         /// @param colours buffer to write vertex colours
-        virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
-                                std::vector<float>& positions,
-                                std::vector<float>& normals,
-                                std::vector<Ogre::uint8>& colours);
+        virtual void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
+                                osg::ref_ptr<osg::Vec3Array> positions,
+                                osg::ref_ptr<osg::Vec3Array> normals,
+                                osg::ref_ptr<osg::Vec4Array> colours);
 
         /// Create textures holding layer blend values for a terrain chunk.
         /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might
@@ -62,23 +67,11 @@ namespace ESMTerrain
         ///        can utilize packing, FFP can't.
         /// @param blendmaps created blendmaps will be written here
         /// @param layerList names of the layer textures used will be written here
-        virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
-                           std::vector<Ogre::PixelBox>& blendmaps,
+        virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, bool pack,
+                           ImageVector& blendmaps,
                            std::vector<Terrain::LayerInfo>& layerList);
 
-        /// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information.
-        /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once.
-        /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might
-        ///       have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used.
-        /// @note May be called from background threads.
-        /// @param nodes A collection of nodes for which to retrieve the aforementioned data
-        /// @param out Output vector
-        /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) -
-        ///        otherwise, each texture contains blend values for one layer only. Shader-based rendering
-        ///        can utilize packing, FFP can't.
-        virtual void getBlendmaps (const std::vector<Terrain::QuadTreeNode*>& nodes, std::vector<Terrain::LayerCollection>& out, bool pack);
-
-        virtual float getHeightAt (const Ogre::Vector3& worldPos);
+        virtual float getHeightAt (const osg::Vec3f& worldPos);
 
         virtual Terrain::LayerInfo getDefaultLayer();
 
@@ -89,9 +82,11 @@ namespace ESMTerrain
         virtual int getCellVertices();
 
     private:
-        void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
-        void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row);
-        void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
+        const VFS::Manager* mVFS;
+
+        void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row);
+        void fixColour (osg::Vec4f& colour, int cellX, int cellY, int col, int row);
+        void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row);
 
         float getVertexHeight (const ESM::Land* land, int x, int y);
 
@@ -107,11 +102,6 @@ namespace ESMTerrain
         std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;
 
         Terrain::LayerInfo getLayerInfo(const std::string& texture);
-
-        // Non-virtual
-        void getBlendmapsImpl (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
-                           std::vector<Ogre::PixelBox>& blendmaps,
-                           std::vector<Terrain::LayerInfo>& layerList);
     };
 
 }
diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp
index 1b000fabb1..7dc93f1d65 100644
--- a/components/terrain/buffercache.cpp
+++ b/components/terrain/buffercache.cpp
@@ -21,15 +21,17 @@
  */
 #include "buffercache.hpp"
 
-#include <OgreHardwareBufferManager.h>
+#include <cassert>
+
+#include <osg/PrimitiveSet>
 
 #include "defs.hpp"
 
 namespace
 {
 
-template <typename IndexType>
-Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigned int verts, Ogre::HardwareIndexBuffer::IndexType type)
+template <typename IndexArrayType>
+osg::ref_ptr<IndexArrayType> createIndexBuffer(unsigned int flags, unsigned int verts)
 {
     // LOD level n means every 2^n-th vertex is kept
     size_t lodLevel = (flags >> (4*4));
@@ -42,8 +44,9 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
 
     size_t increment = 1 << lodLevel;
     assert(increment < verts);
-    std::vector<IndexType> indices;
-    indices.reserve((verts-1)*(verts-1)*2*3 / increment);
+
+    osg::ref_ptr<IndexArrayType> indices (new IndexArrayType(osg::PrimitiveSet::TRIANGLES));
+    indices->reserve((verts-1)*(verts-1)*2*3 / increment);
 
     size_t rowStart = 0, colStart = 0, rowEnd = verts-1, colEnd = verts-1;
     // If any edge needs stitching we'll skip all edges at this point,
@@ -62,23 +65,23 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
             // diamond pattern
             if ((row + col%2) % 2 == 1)
             {
-                indices.push_back(verts*(col+increment)+row);
-                indices.push_back(verts*(col+increment)+row+increment);
-                indices.push_back(verts*col+row+increment);
+                indices->push_back(verts*(col+increment)+row);
+                indices->push_back(verts*(col+increment)+row+increment);
+                indices->push_back(verts*col+row+increment);
 
-                indices.push_back(verts*col+row);
-                indices.push_back(verts*(col+increment)+row);
-                indices.push_back(verts*(col)+row+increment);
+                indices->push_back(verts*col+row);
+                indices->push_back(verts*(col+increment)+row);
+                indices->push_back(verts*(col)+row+increment);
             }
             else
             {
-                indices.push_back(verts*col+row);
-                indices.push_back(verts*(col+increment)+row+increment);
-                indices.push_back(verts*col+row+increment);
+                indices->push_back(verts*col+row);
+                indices->push_back(verts*(col+increment)+row+increment);
+                indices->push_back(verts*col+row+increment);
 
-                indices.push_back(verts*col+row);
-                indices.push_back(verts*(col+increment)+row);
-                indices.push_back(verts*(col+increment)+row+increment);
+                indices->push_back(verts*col+row);
+                indices->push_back(verts*(col+increment)+row);
+                indices->push_back(verts*(col+increment)+row+increment);
             }
         }
     }
@@ -94,22 +97,22 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
         size_t outerStep = 1 << (lodDeltas[Terrain::South] + lodLevel);
         for (size_t col = 0; col < verts-1; col += outerStep)
         {
-            indices.push_back(verts*col+row);
-            indices.push_back(verts*(col+outerStep)+row);
+            indices->push_back(verts*col+row);
+            indices->push_back(verts*(col+outerStep)+row);
             // Make sure not to touch the right edge
             if (col+outerStep == verts-1)
-                indices.push_back(verts*(col+outerStep-innerStep)+row+innerStep);
+                indices->push_back(verts*(col+outerStep-innerStep)+row+innerStep);
             else
-                indices.push_back(verts*(col+outerStep)+row+innerStep);
+                indices->push_back(verts*(col+outerStep)+row+innerStep);
 
             for (size_t i = 0; i < outerStep; i += innerStep)
             {
                 // Make sure not to touch the left or right edges
                 if (col+i == 0 || col+i == verts-1-innerStep)
                     continue;
-                indices.push_back(verts*(col)+row);
-                indices.push_back(verts*(col+i+innerStep)+row+innerStep);
-                indices.push_back(verts*(col+i)+row+innerStep);
+                indices->push_back(verts*(col)+row);
+                indices->push_back(verts*(col+i+innerStep)+row+innerStep);
+                indices->push_back(verts*(col+i)+row+innerStep);
             }
         }
 
@@ -118,22 +121,22 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
         outerStep = size_t(1) << (lodDeltas[Terrain::North] + lodLevel);
         for (size_t col = 0; col < verts-1; col += outerStep)
         {
-            indices.push_back(verts*(col+outerStep)+row);
-            indices.push_back(verts*col+row);
+            indices->push_back(verts*(col+outerStep)+row);
+            indices->push_back(verts*col+row);
             // Make sure not to touch the left edge
             if (col == 0)
-                indices.push_back(verts*(col+innerStep)+row-innerStep);
+                indices->push_back(verts*(col+innerStep)+row-innerStep);
             else
-                indices.push_back(verts*col+row-innerStep);
+                indices->push_back(verts*col+row-innerStep);
 
             for (size_t i = 0; i < outerStep; i += innerStep)
             {
                 // Make sure not to touch the left or right edges
                 if (col+i == 0 || col+i == verts-1-innerStep)
                     continue;
-                indices.push_back(verts*(col+i)+row-innerStep);
-                indices.push_back(verts*(col+i+innerStep)+row-innerStep);
-                indices.push_back(verts*(col+outerStep)+row);
+                indices->push_back(verts*(col+i)+row-innerStep);
+                indices->push_back(verts*(col+i+innerStep)+row-innerStep);
+                indices->push_back(verts*(col+outerStep)+row);
             }
         }
 
@@ -142,22 +145,22 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
         outerStep = size_t(1) << (lodDeltas[Terrain::West] + lodLevel);
         for (size_t row = 0; row < verts-1; row += outerStep)
         {
-            indices.push_back(verts*col+row+outerStep);
-            indices.push_back(verts*col+row);
+            indices->push_back(verts*col+row+outerStep);
+            indices->push_back(verts*col+row);
             // Make sure not to touch the top edge
             if (row+outerStep == verts-1)
-                indices.push_back(verts*(col+innerStep)+row+outerStep-innerStep);
+                indices->push_back(verts*(col+innerStep)+row+outerStep-innerStep);
             else
-                indices.push_back(verts*(col+innerStep)+row+outerStep);
+                indices->push_back(verts*(col+innerStep)+row+outerStep);
 
             for (size_t i = 0; i < outerStep; i += innerStep)
             {
                 // Make sure not to touch the top or bottom edges
                 if (row+i == 0 || row+i == verts-1-innerStep)
                     continue;
-                indices.push_back(verts*col+row);
-                indices.push_back(verts*(col+innerStep)+row+i);
-                indices.push_back(verts*(col+innerStep)+row+i+innerStep);
+                indices->push_back(verts*col+row);
+                indices->push_back(verts*(col+innerStep)+row+i);
+                indices->push_back(verts*(col+innerStep)+row+i+innerStep);
             }
         }
 
@@ -166,31 +169,27 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
         outerStep = size_t(1) << (lodDeltas[Terrain::East] + lodLevel);
         for (size_t row = 0; row < verts-1; row += outerStep)
         {
-            indices.push_back(verts*col+row);
-            indices.push_back(verts*col+row+outerStep);
+            indices->push_back(verts*col+row);
+            indices->push_back(verts*col+row+outerStep);
             // Make sure not to touch the bottom edge
             if (row == 0)
-                indices.push_back(verts*(col-innerStep)+row+innerStep);
+                indices->push_back(verts*(col-innerStep)+row+innerStep);
             else
-                indices.push_back(verts*(col-innerStep)+row);
+                indices->push_back(verts*(col-innerStep)+row);
 
             for (size_t i = 0; i < outerStep; i += innerStep)
             {
                 // Make sure not to touch the top or bottom edges
                 if (row+i == 0 || row+i == verts-1-innerStep)
                     continue;
-                indices.push_back(verts*col+row+outerStep);
-                indices.push_back(verts*(col-innerStep)+row+i+innerStep);
-                indices.push_back(verts*(col-innerStep)+row+i);
+                indices->push_back(verts*col+row+outerStep);
+                indices->push_back(verts*(col-innerStep)+row+i+innerStep);
+                indices->push_back(verts*(col-innerStep)+row+i);
             }
         }
     }
 
-    Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
-    Ogre::HardwareIndexBufferSharedPtr buffer = mgr->createIndexBuffer(type,
-                                                                       indices.size(), Ogre::HardwareBuffer::HBU_STATIC);
-    buffer->writeData(0, buffer->getSizeInBytes(), &indices[0], true);
-    return buffer;
+    return indices;
 }
 
 }
@@ -198,7 +197,7 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
 namespace Terrain
 {
 
-    Ogre::HardwareVertexBufferSharedPtr BufferCache::getUVBuffer()
+    osg::ref_ptr<osg::Vec2Array> BufferCache::getUVBuffer()
     {
         if (mUvBufferMap.find(mNumVerts) != mUvBufferMap.end())
         {
@@ -207,30 +206,23 @@ namespace Terrain
 
         int vertexCount = mNumVerts * mNumVerts;
 
-        std::vector<float> uvs;
-        uvs.reserve(vertexCount*2);
+        osg::ref_ptr<osg::Vec2Array> uvs (new osg::Vec2Array);
+        uvs->reserve(vertexCount);
 
         for (unsigned int col = 0; col < mNumVerts; ++col)
         {
             for (unsigned int row = 0; row < mNumVerts; ++row)
             {
-                uvs.push_back(col / static_cast<float>(mNumVerts-1)); // U
-                uvs.push_back(row / static_cast<float>(mNumVerts-1)); // V
+                uvs->push_back(osg::Vec2f(col / static_cast<float>(mNumVerts-1),
+                                          row / static_cast<float>(mNumVerts-1)));
             }
         }
 
-        Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
-        Ogre::HardwareVertexBufferSharedPtr buffer = mgr->createVertexBuffer(
-                    Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2),
-                                                vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
-
-        buffer->writeData(0, buffer->getSizeInBytes(), &uvs[0], true);
-
-        mUvBufferMap[mNumVerts] = buffer;
-        return buffer;
+        mUvBufferMap[mNumVerts] = uvs;
+        return uvs;
     }
 
-    Ogre::HardwareIndexBufferSharedPtr BufferCache::getIndexBuffer(unsigned int flags)
+    osg::ref_ptr<osg::DrawElements> BufferCache::getIndexBuffer(unsigned int flags)
     {
         unsigned int verts = mNumVerts;
 
@@ -239,11 +231,12 @@ namespace Terrain
             return mIndexBufferMap[flags];
         }
 
-        Ogre::HardwareIndexBufferSharedPtr buffer;
+        osg::ref_ptr<osg::DrawElements> buffer;
+
         if (verts*verts > (0xffffu))
-            buffer = createIndexBuffer<unsigned int>(flags, verts, Ogre::HardwareIndexBuffer::IT_32BIT);
+            buffer = createIndexBuffer<osg::DrawElementsUShort>(flags, verts);
         else
-            buffer = createIndexBuffer<unsigned short>(flags, verts, Ogre::HardwareIndexBuffer::IT_16BIT);
+            buffer = createIndexBuffer<osg::DrawElementsUInt>(flags, verts);
 
         mIndexBufferMap[flags] = buffer;
         return buffer;
diff --git a/components/terrain/buffercache.hpp b/components/terrain/buffercache.hpp
index 887f0822ef..575e9bca25 100644
--- a/components/terrain/buffercache.hpp
+++ b/components/terrain/buffercache.hpp
@@ -22,8 +22,8 @@
 #ifndef COMPONENTS_TERRAIN_BUFFERCACHE_H
 #define COMPONENTS_TERRAIN_BUFFERCACHE_H
 
-#include <OgreHardwareIndexBuffer.h>
-#include <OgreHardwareVertexBuffer.h>
+#include <osg/ref_ptr>
+#include <osg/Array>
 
 #include <map>
 
@@ -38,16 +38,18 @@ namespace Terrain
 
         /// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each)
         ///              next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices)
-        Ogre::HardwareIndexBufferSharedPtr getIndexBuffer (unsigned int flags);
+        osg::ref_ptr<osg::DrawElements> getIndexBuffer (unsigned int flags);
 
-        Ogre::HardwareVertexBufferSharedPtr getUVBuffer ();
+        osg::ref_ptr<osg::Vec2Array> getUVBuffer();
+
+        // TODO: add releaseGLObjects() for our vertex/element buffer objects
 
     private:
         // Index buffers are shared across terrain batches where possible. There is one index buffer for each
         // combination of LOD deltas and index buffer LOD we may need.
-        std::map<int, Ogre::HardwareIndexBufferSharedPtr> mIndexBufferMap;
+        std::map<int, osg::ref_ptr<osg::DrawElements> > mIndexBufferMap;
 
-        std::map<int, Ogre::HardwareVertexBufferSharedPtr> mUvBufferMap;
+        std::map<int, osg::ref_ptr<osg::Vec2Array> > mUvBufferMap;
 
         unsigned int mNumVerts;
     };
diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp
deleted file mode 100644
index e3bae11732..0000000000
--- a/components/terrain/chunk.cpp
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
-
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-#include "chunk.hpp"
-
-#include <OgreSceneNode.h>
-#include <OgreHardwareBufferManager.h>
-#include <OgreRenderQueue.h>
-#include <OgreMaterialManager.h>
-#include <OgreStringConverter.h>
-
-namespace Terrain
-{
-
-    Chunk::Chunk(Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds,
-                 const std::vector<float>& positions, const std::vector<float>& normals, const std::vector<Ogre::uint8>& colours)
-        : mBounds(bounds)
-        , mOwnMaterial(false)
-    {
-        mVertexData = OGRE_NEW Ogre::VertexData;
-        mVertexData->vertexStart = 0;
-        mVertexData->vertexCount = positions.size()/3;
-
-        // Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc)
-        Ogre::VertexDeclaration* vertexDecl = mVertexData->vertexDeclaration;
-
-        Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
-        size_t nextBuffer = 0;
-
-        // Positions
-        vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
-        Ogre::HardwareVertexBufferSharedPtr vertexBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3),
-                                                mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
-
-        // Normals
-        vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);
-        Ogre::HardwareVertexBufferSharedPtr normalBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3),
-                                                mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
-
-
-        // UV texture coordinates
-        vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT2,
-                               Ogre::VES_TEXTURE_COORDINATES, 0);
-
-        // Colours
-        vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE);
-        Ogre::HardwareVertexBufferSharedPtr colourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR),
-                                                mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
-
-        vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true);
-        normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true);
-        colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colours[0], true);
-
-        mVertexData->vertexBufferBinding->setBinding(0, vertexBuffer);
-        mVertexData->vertexBufferBinding->setBinding(1, normalBuffer);
-        mVertexData->vertexBufferBinding->setBinding(2, uvBuffer);
-        mVertexData->vertexBufferBinding->setBinding(3, colourBuffer);
-
-        // Assign a default material in case terrain material fails to be created
-        mMaterial = Ogre::MaterialManager::getSingleton().getByName("BaseWhite");
-
-        mIndexData = OGRE_NEW Ogre::IndexData();
-        mIndexData->indexStart = 0;
-    }
-
-    void Chunk::setIndexBuffer(Ogre::HardwareIndexBufferSharedPtr buffer)
-    {
-        mIndexData->indexBuffer = buffer;
-        mIndexData->indexCount = buffer->getNumIndexes();
-    }
-
-    Chunk::~Chunk()
-    {
-        if (!mMaterial.isNull() && mOwnMaterial)
-        {
-#if TERRAIN_USE_SHADER
-            sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName());
-#endif
-            Ogre::MaterialManager::getSingleton().remove(mMaterial->getName());
-        }
-        OGRE_DELETE mVertexData;
-        OGRE_DELETE mIndexData;
-    }
-
-    void Chunk::setMaterial(const Ogre::MaterialPtr &material, bool own)
-    {
-        // Clean up the previous material, if we own it
-        if (!mMaterial.isNull() && mOwnMaterial)
-        {
-#if TERRAIN_USE_SHADER
-            sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName());
-#endif
-            Ogre::MaterialManager::getSingleton().remove(mMaterial->getName());
-        }
-
-        mMaterial = material;
-        mOwnMaterial = own;
-    }
-
-    const Ogre::AxisAlignedBox& Chunk::getBoundingBox(void) const
-    {
-        return mBounds;
-    }
-
-    Ogre::Real Chunk::getBoundingRadius(void) const
-    {
-        return mBounds.getHalfSize().length();
-    }
-
-    void Chunk::_updateRenderQueue(Ogre::RenderQueue* queue)
-    {
-        queue->addRenderable(this, mRenderQueueID);
-    }
-
-    void Chunk::visitRenderables(Ogre::Renderable::Visitor* visitor,
-        bool debugRenderables)
-    {
-        visitor->visit(this, 0, false);
-    }
-
-    const Ogre::MaterialPtr& Chunk::getMaterial(void) const
-    {
-        return mMaterial;
-    }
-
-    void Chunk::getRenderOperation(Ogre::RenderOperation& op)
-    {
-        assert (!mIndexData->indexBuffer.isNull() && "Trying to render, but no index buffer set!");
-        assert(!mMaterial.isNull() && "Trying to render, but no material set!");
-        op.useIndexes = true;
-        op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;
-        op.vertexData = mVertexData;
-        op.indexData = mIndexData;
-    }
-
-    void Chunk::getWorldTransforms(Ogre::Matrix4* xform) const
-    {
-        *xform = getParentSceneNode()->_getFullTransform();
-    }
-
-    Ogre::Real Chunk::getSquaredViewDepth(const Ogre::Camera* cam) const
-    {
-        return getParentSceneNode()->getSquaredViewDepth(cam);
-    }
-
-    const Ogre::LightList& Chunk::getLights(void) const
-    {
-        return queryLights();
-    }
-
-}
diff --git a/components/terrain/chunk.hpp b/components/terrain/chunk.hpp
deleted file mode 100644
index 22b4f26ef4..0000000000
--- a/components/terrain/chunk.hpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
-
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-#ifndef COMPONENTS_TERRAIN_TERRAINBATCH_H
-#define COMPONENTS_TERRAIN_TERRAINBATCH_H
-
-#include <OgreRenderable.h>
-#include <OgreMovableObject.h>
-
-namespace Terrain
-{
-
-    /**
-     * @brief A movable object representing a chunk of terrain.
-     */
-    class Chunk : public Ogre::Renderable, public Ogre::MovableObject
-    {
-    public:
-        Chunk (Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds,
-               const std::vector<float>& positions,
-               const std::vector<float>& normals,
-               const std::vector<Ogre::uint8>& colours);
-
-        virtual ~Chunk();
-
-        /// @param own Should we take ownership of the material?
-        void setMaterial (const Ogre::MaterialPtr& material, bool own=true);
-
-        void setIndexBuffer(Ogre::HardwareIndexBufferSharedPtr buffer);
-
-        // Inherited from MovableObject
-        virtual const Ogre::String& getMovableType(void) const { static Ogre::String t = "MW_TERRAIN"; return t; }
-        virtual const Ogre::AxisAlignedBox& getBoundingBox(void) const;
-        virtual Ogre::Real getBoundingRadius(void) const;
-        virtual void _updateRenderQueue(Ogre::RenderQueue* queue);
-        virtual void visitRenderables(Renderable::Visitor* visitor,
-            bool debugRenderables = false);
-
-        // Inherited from Renderable
-        virtual const Ogre::MaterialPtr& getMaterial(void) const;
-        virtual void getRenderOperation(Ogre::RenderOperation& op);
-        virtual void getWorldTransforms(Ogre::Matrix4* xform) const;
-        virtual Ogre::Real getSquaredViewDepth(const Ogre::Camera* cam) const;
-        virtual const Ogre::LightList& getLights(void) const;
-
-    private:
-        Ogre::AxisAlignedBox mBounds;
-        Ogre::MaterialPtr mMaterial;
-        bool mOwnMaterial; // Should we remove mMaterial on destruction?
-
-        Ogre::VertexData* mVertexData;
-        Ogre::IndexData* mIndexData;
-    };
-
-}
-
-#endif
diff --git a/components/terrain/defaultworld.cpp b/components/terrain/defaultworld.cpp
deleted file mode 100644
index 7bc73ddda8..0000000000
--- a/components/terrain/defaultworld.cpp
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
-
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-#include "defaultworld.hpp"
-
-#include <OgreAxisAlignedBox.h>
-#include <OgreCamera.h>
-#include <OgreHardwarePixelBuffer.h>
-#include <OgreTextureManager.h>
-#include <OgreRenderTexture.h>
-#include <OgreSceneNode.h>
-#include <OgreRoot.h>
-
-#include "storage.hpp"
-#include "quadtreenode.hpp"
-
-namespace
-{
-
-    bool isPowerOfTwo(int x)
-    {
-        return ( (x > 0) && ((x & (x - 1)) == 0) );
-    }
-
-    int nextPowerOfTwo (int v)
-    {
-        if (isPowerOfTwo(v)) return v;
-        int depth=0;
-        while(v)
-        {
-            v >>= 1;
-            depth++;
-        }
-        return 1 << depth;
-    }
-
-    Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node)
-    {
-        if (center == node->getCenter())
-            return node;
-
-        if (center.x > node->getCenter().x && center.y > node->getCenter().y)
-            return findNode(center, node->getChild(Terrain::NE));
-        else if (center.x > node->getCenter().x && center.y < node->getCenter().y)
-            return findNode(center, node->getChild(Terrain::SE));
-        else if (center.x < node->getCenter().x && center.y > node->getCenter().y)
-            return findNode(center, node->getChild(Terrain::NW));
-        else //if (center.x < node->getCenter().x && center.y < node->getCenter().y)
-            return findNode(center, node->getChild(Terrain::SW));
-    }
-
-}
-
-namespace Terrain
-{
-
-    const Ogre::uint REQ_ID_CHUNK = 1;
-    const Ogre::uint REQ_ID_LAYERS = 2;
-
-    DefaultWorld::DefaultWorld(Ogre::SceneManager* sceneMgr,
-                     Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize)
-        : World(sceneMgr, storage, visibilityFlags, shaders, align)
-        , mWorkQueueChannel(0)
-        , mVisible(true)
-        , mChunksLoading(0)
-        , mMinX(0)
-        , mMaxX(0)
-        , mMinY(0)
-        , mMaxY(0)
-        , mMinBatchSize(minBatchSize)
-        , mMaxBatchSize(maxBatchSize)
-        , mLayerLoadPending(true)
-    {
-#if TERRAIN_USE_SHADER == 0
-        if (mShaders)
-            std::cerr << "Compiled Terrain without shader support, disabling..." << std::endl;
-        mShaders = false;
-#endif
-
-        mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC);
-
-        /// \todo make composite map size configurable
-        Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a");
-        mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual(
-                    "terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
-            Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET);
-        mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget();
-        mCompositeMapRenderTarget->setAutoUpdated(false);
-        mCompositeMapRenderTarget->addViewport(compositeMapCam);
-
-        storage->getBounds(mMinX, mMaxX, mMinY, mMaxY);
-
-        int origSizeX = static_cast<int>(mMaxX - mMinX);
-        int origSizeY = static_cast<int>(mMaxY - mMinY);
-
-        // Dividing a quad tree only works well for powers of two, so round up to the nearest one
-        int size = nextPowerOfTwo(std::max(origSizeX, origSizeY));
-
-        // Adjust the center according to the new size
-        float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f;
-        float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f;
-
-        mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
-
-        // While building the quadtree, remember leaf nodes since we need to load their layers
-        LayersRequestData data;
-        data.mPack = getShadersEnabled();
-
-        mRootNode = new QuadTreeNode(this, Root, static_cast<float>(size), Ogre::Vector2(centerX, centerY), NULL);
-        buildQuadTree(mRootNode, data.mNodes);
-        //loadingListener->indicateProgress();
-        mRootNode->initAabb();
-        //loadingListener->indicateProgress();
-        mRootNode->initNeighbours();
-        //loadingListener->indicateProgress();
-
-        Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue();
-        mWorkQueueChannel = wq->getChannel("LargeTerrain");
-        wq->addRequestHandler(mWorkQueueChannel, this);
-        wq->addResponseHandler(mWorkQueueChannel, this);
-
-        // Start loading layers in the background (for leaf nodes)
-        wq->addRequest(mWorkQueueChannel, REQ_ID_LAYERS, Ogre::Any(data));
-    }
-
-    DefaultWorld::~DefaultWorld()
-    {
-        Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue();
-        wq->removeRequestHandler(mWorkQueueChannel, this);
-        wq->removeResponseHandler(mWorkQueueChannel, this);
-
-        delete mRootNode;
-    }
-
-    void DefaultWorld::buildQuadTree(QuadTreeNode *node, std::vector<QuadTreeNode*>& leafs)
-    {
-        float halfSize = node->getSize()/2.f;
-
-        if (node->getSize() <= mMinBatchSize)
-        {
-            // We arrived at a leaf
-            float minZ,maxZ;
-            Ogre::Vector2 center = node->getCenter();
-            float cellWorldSize = getStorage()->getCellWorldSize();
-            if (mStorage->getMinMaxHeights(static_cast<float>(node->getSize()), center, minZ, maxZ))
-            {
-                Ogre::AxisAlignedBox bounds(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ),
-                                    Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ));
-                convertBounds(bounds);
-                node->setBoundingBox(bounds);
-                leafs.push_back(node);
-            }
-            else
-                node->markAsDummy(); // no data available for this node, skip it
-            return;
-        }
-
-        if (node->getCenter().x - halfSize > mMaxX
-                || node->getCenter().x + halfSize < mMinX
-                || node->getCenter().y - halfSize > mMaxY
-                || node->getCenter().y + halfSize < mMinY )
-            // Out of bounds of the actual terrain - this will happen because
-            // we rounded the size up to the next power of two
-        {
-            node->markAsDummy();
-            return;
-        }
-
-        // Not a leaf, create its children
-        node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f);
-        node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f));
-        node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f));
-        node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f);
-        buildQuadTree(node->getChild(SW), leafs);
-        buildQuadTree(node->getChild(SE), leafs);
-        buildQuadTree(node->getChild(NW), leafs);
-        buildQuadTree(node->getChild(NE), leafs);
-
-        // if all children are dummy, we are also dummy
-        for (int i=0; i<4; ++i)
-        {
-            if (!node->getChild((ChildDirection)i)->isDummy())
-                return;
-        }
-        node->markAsDummy();
-    }
-
-    void DefaultWorld::update(const Ogre::Vector3& cameraPos)
-    {
-        if (!mVisible)
-            return;
-        mRootNode->update(cameraPos);
-        mRootNode->updateIndexBuffers();
-    }
-
-    Ogre::AxisAlignedBox DefaultWorld::getWorldBoundingBox (const Ogre::Vector2& center)
-    {
-        if (center.x > mMaxX
-                 || center.x < mMinX
-                || center.y > mMaxY
-                || center.y < mMinY)
-            return Ogre::AxisAlignedBox::BOX_NULL;
-        QuadTreeNode* node = findNode(center, mRootNode);
-        return node->getWorldBoundingBox();
-    }
-
-    void DefaultWorld::renderCompositeMap(Ogre::TexturePtr target)
-    {
-        mCompositeMapRenderTarget->update();
-        target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer());
-    }
-
-    void DefaultWorld::clearCompositeMapSceneManager()
-    {
-        mCompositeMapSceneMgr->destroyAllManualObjects();
-        mCompositeMapSceneMgr->clearScene();
-    }
-
-    void DefaultWorld::applyMaterials(bool shadows, bool splitShadows)
-    {
-        mShadows = shadows;
-        mSplitShadows = splitShadows;
-        mRootNode->applyMaterials();
-    }
-
-    void DefaultWorld::setVisible(bool visible)
-    {
-        if (visible && !mVisible)
-            mSceneMgr->getRootSceneNode()->addChild(mRootSceneNode);
-        else if (!visible && mVisible)
-            mSceneMgr->getRootSceneNode()->removeChild(mRootSceneNode);
-
-        mVisible = visible;
-    }
-
-    bool DefaultWorld::getVisible()
-    {
-        return mVisible;
-    }
-
-    void DefaultWorld::syncLoad()
-    {
-        while (mChunksLoading || mLayerLoadPending)
-        {
-            OGRE_THREAD_SLEEP(0);
-            Ogre::Root::getSingleton().getWorkQueue()->processResponses();
-        }
-    }
-
-    Ogre::WorkQueue::Response* DefaultWorld::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ)
-    {
-        if (req->getType() == REQ_ID_CHUNK)
-        {
-            const LoadRequestData data = Ogre::any_cast<LoadRequestData>(req->getData());
-
-            QuadTreeNode* node = data.mNode;
-
-            LoadResponseData* responseData = new LoadResponseData();
-
-            getStorage()->fillVertexBuffers(node->getNativeLodLevel(), static_cast<float>(node->getSize()), node->getCenter(), getAlign(),
-                                            responseData->mPositions, responseData->mNormals, responseData->mColours);
-
-            return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData));
-        }
-        else // REQ_ID_LAYERS
-        {
-            const LayersRequestData data = Ogre::any_cast<LayersRequestData>(req->getData());
-
-            LayersResponseData* responseData = new LayersResponseData();
-
-            getStorage()->getBlendmaps(data.mNodes, responseData->mLayerCollections, data.mPack);
-
-            return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData));
-        }
-    }
-
-    void DefaultWorld::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ)
-    {
-        assert(res->succeeded() && "Response failure not handled");
-
-        if (res->getRequest()->getType() == REQ_ID_CHUNK)
-        {
-            LoadResponseData* data = Ogre::any_cast<LoadResponseData*>(res->getData());
-
-            const LoadRequestData requestData = Ogre::any_cast<LoadRequestData>(res->getRequest()->getData());
-
-            requestData.mNode->load(*data);
-
-            delete data;
-
-            --mChunksLoading;
-        }
-        else // REQ_ID_LAYERS
-        {
-            LayersResponseData* data = Ogre::any_cast<LayersResponseData*>(res->getData());
-
-            for (std::vector<LayerCollection>::iterator it = data->mLayerCollections.begin(); it != data->mLayerCollections.end(); ++it)
-            {
-                it->mTarget->loadLayers(*it);
-            }
-
-            delete data;
-
-            mRootNode->loadMaterials();
-
-            mLayerLoadPending = false;
-        }
-    }
-
-    void DefaultWorld::queueLoad(QuadTreeNode *node)
-    {
-        LoadRequestData data;
-        data.mNode = node;
-
-        Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data));
-        ++mChunksLoading;
-    }
-}
diff --git a/components/terrain/defaultworld.hpp b/components/terrain/defaultworld.hpp
deleted file mode 100644
index 4caef8462f..0000000000
--- a/components/terrain/defaultworld.hpp
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
-
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-#ifndef COMPONENTS_TERRAIN_H
-#define COMPONENTS_TERRAIN_H
-
-#include <OgreAxisAlignedBox.h>
-#include <OgreTexture.h>
-#include <OgreWorkQueue.h>
-
-#include "world.hpp"
-
-namespace Ogre
-{
-    class Camera;
-}
-
-namespace Terrain
-{
-
-    class QuadTreeNode;
-    class Storage;
-
-    /**
-     * @brief A quadtree-based terrain implementation suitable for large data sets. \n
-     *        Near cells are rendered with alpha splatting, distant cells are merged
-     *        together in batches and have their layers pre-rendered onto a composite map. \n
-     *        Cracks at LOD transitions are avoided using stitching.
-     * @note  Multiple cameras are not supported yet
-     */
-    class DefaultWorld : public World, public Ogre::WorkQueue::RequestHandler, public Ogre::WorkQueue::ResponseHandler
-    {
-    public:
-        /// @note takes ownership of \a storage
-        /// @param sceneMgr scene manager to use
-        /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..)
-        /// @param visbilityFlags visibility flags for the created meshes
-        /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually
-        ///         faster so this is just here for compatibility.
-        /// @param align The align of the terrain, see Alignment enum
-        /// @param minBatchSize Minimum size of a terrain batch along one side (in cell units). Used for building the quad tree.
-        /// @param maxBatchSize Maximum size of a terrain batch along one side (in cell units). Used when traversing the quad tree.
-        DefaultWorld(Ogre::SceneManager* sceneMgr,
-                Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize);
-        ~DefaultWorld();
-
-        /// Update chunk LODs according to this camera position
-        /// @note Calling this method might lead to composite textures being rendered, so it is best
-        /// not to call it when render commands are still queued, since that would cause a flush.
-        virtual void update (const Ogre::Vector3& cameraPos);
-
-        /// Get the world bounding box of a chunk of terrain centered at \a center
-        virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center);
-
-        Ogre::SceneNode* getRootSceneNode() { return mRootSceneNode; }
-
-        /// Show or hide the whole terrain
-        /// @note this setting will be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden
-        virtual void setVisible(bool visible);
-        virtual bool getVisible();
-
-        /// Recreate materials used by terrain chunks. This should be called whenever settings of
-        /// the material factory are changed. (Relying on the factory to update those materials is not
-        /// enough, since turning a feature on/off can change the number of texture units available for layer/blend
-        /// textures, and to properly respond to this we may need to change the structure of the material, such as
-        /// adding or removing passes. This can only be achieved by a full rebuild.)
-        virtual void applyMaterials(bool shadows, bool splitShadows);
-
-        int getMaxBatchSize() { return static_cast<int>(mMaxBatchSize); }
-
-        /// Wait until all background loading is complete.
-        void syncLoad();
-
-    private:
-        // Called from a background worker thread
-        virtual Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ);
-        // Called from the main thread
-        virtual void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ);
-        Ogre::uint16 mWorkQueueChannel;
-
-        bool mVisible;
-
-        QuadTreeNode* mRootNode;
-        Ogre::SceneNode* mRootSceneNode;
-
-        /// The number of chunks currently loading in a background thread. If 0, we have finished loading!
-        int mChunksLoading;
-
-        Ogre::SceneManager* mCompositeMapSceneMgr;
-
-        /// Bounds in cell units
-        float mMinX, mMaxX, mMinY, mMaxY;
-
-        /// Minimum size of a terrain batch along one side (in cell units)
-        float mMinBatchSize;
-        /// Maximum size of a terrain batch along one side (in cell units)
-        float mMaxBatchSize;
-
-        void buildQuadTree(QuadTreeNode* node, std::vector<QuadTreeNode*>& leafs);
-
-        // Are layers for leaf nodes loaded? This is done once at startup (but in a background thread)
-        bool mLayerLoadPending;
-
-    public:
-        // ----INTERNAL----
-        Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; }
-
-        bool areLayersLoaded() { return !mLayerLoadPending; }
-
-        // Delete all quads
-        void clearCompositeMapSceneManager();
-        void renderCompositeMap (Ogre::TexturePtr target);
-
-        // Adds a WorkQueue request to load a chunk for this node in the background.
-        void queueLoad (QuadTreeNode* node);
-
-    private:
-        Ogre::RenderTarget* mCompositeMapRenderTarget;
-        Ogre::TexturePtr mCompositeMapRenderTexture;
-    };
-
-    struct LoadRequestData
-    {
-        QuadTreeNode* mNode;
-
-        friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r)
-        { return o; }
-    };
-
-    struct LoadResponseData
-    {
-        std::vector<float> mPositions;
-        std::vector<float> mNormals;
-        std::vector<Ogre::uint8> mColours;
-
-        friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r)
-        { return o; }
-    };
-
-    struct LayersRequestData
-    {
-        std::vector<QuadTreeNode*> mNodes;
-        bool mPack;
-
-        friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r)
-        { return o; }
-    };
-
-    struct LayersResponseData
-    {
-        std::vector<LayerCollection> mLayerCollections;
-
-        friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r)
-        { return o; }
-    };
-
-}
-
-#endif
diff --git a/components/terrain/defs.hpp b/components/terrain/defs.hpp
index 6d173d1361..7b40ad4792 100644
--- a/components/terrain/defs.hpp
+++ b/components/terrain/defs.hpp
@@ -22,41 +22,10 @@
 #ifndef COMPONENTS_TERRAIN_DEFS_HPP
 #define COMPONENTS_TERRAIN_DEFS_HPP
 
+#include <string>
+
 namespace Terrain
 {
-    class QuadTreeNode;
-
-    /// The alignment of the terrain
-    enum Alignment
-    {
-        /// Terrain is in the X/Z plane
-        Align_XZ = 0,
-        /// Terrain is in the X/Y plane
-        Align_XY = 1,
-        /// Terrain is in the Y/Z plane.
-        /// UNTESTED - use at own risk.
-        /// Besides, X as up axis? What is wrong with you? ;)
-        Align_YZ = 2
-    };
-
-    inline void convertPosition(Alignment align, float &x, float &y, float &z)
-    {
-        switch (align)
-        {
-        case Align_XY:
-            return;
-        case Align_XZ:
-            std::swap(y, z);
-            // This is since -Z should be going *into* the screen
-            // If not doing this, we'd get wrong vertex winding
-            z *= -1;
-            return;
-        case Align_YZ:
-            std::swap(x, y);
-            std::swap(y, z);
-            return;
-        }
-    }
 
     enum Direction
     {
@@ -74,13 +43,6 @@ namespace Terrain
         bool mSpecular; // Specular info in diffuse map alpha channel?
     };
 
-    struct LayerCollection
-    {
-        QuadTreeNode* mTarget;
-        // Since we can't create a texture from a different thread, this only holds the raw texel data
-        std::vector<Ogre::PixelBox> mBlendmaps;
-        std::vector<LayerInfo> mLayers;
-    };
 }
 
 #endif
diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp
index 4d2318aa6b..17acd940b9 100644
--- a/components/terrain/material.cpp
+++ b/components/terrain/material.cpp
@@ -21,353 +21,86 @@
  */
 #include "material.hpp"
 
-#include <OgreMaterialManager.h>
-#include <OgreTechnique.h>
-#include <OgrePass.h>
+#include <iostream>
 
-#include <boost/functional/hash.hpp>
+#include <osg/Depth>
+#include <osg/TexEnvCombine>
+#include <osg/Texture2D>
+#include <osg/TexMat>
+#include <osg/Material>
 
-#if TERRAIN_USE_SHADER
-#include <extern/shiny/Main/Factory.hpp>
-#endif
-
-namespace
-{
-
-int getBlendmapIndexForLayer (int layerIndex)
-{
-    return static_cast<int>(std::floor((layerIndex - 1) / 4.f));
-}
-
-std::string getBlendmapComponentForLayer (int layerIndex)
-{
-    int n = (layerIndex-1)%4;
-    if (n == 0)
-        return "x";
-    if (n == 1)
-        return "y";
-    if (n == 2)
-        return "z";
-    else
-        return "w";
-}
-
-}
+#include <osg/io_utils>
 
 namespace Terrain
 {
 
-    MaterialGenerator::MaterialGenerator()
-        : mShaders(true)
-        , mShadows(false)
-        , mSplitShadows(false)
-        , mNormalMapping(true)
-        , mParallaxMapping(true)
+    FixedFunctionTechnique::FixedFunctionTechnique(const std::vector<osg::ref_ptr<osg::Texture2D> >& layers,
+                                                   const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps)
     {
-
-    }
-
-    Ogre::MaterialPtr MaterialGenerator::generate()
-    {
-        assert(!mLayerList.empty() && "Can't create material with no layers");
-
-        return create(false, false);
-    }
-
-    Ogre::MaterialPtr MaterialGenerator::generateForCompositeMapRTT()
-    {
-        assert(!mLayerList.empty() && "Can't create material with no layers");
-
-        return create(true, false);
-    }
-
-    Ogre::MaterialPtr MaterialGenerator::generateForCompositeMap()
-    {
-        return create(false, true);
-    }
-
-    Ogre::MaterialPtr MaterialGenerator::create(bool renderCompositeMap, bool displayCompositeMap)
-    {
-        assert(!renderCompositeMap || !displayCompositeMap);
-
-        static int count = 0;
-        std::stringstream name;
-        name << "terrain/mat" << count++;
-
-        if (!mShaders)
+        bool firstLayer = true;
+        int i=0;
+        for (std::vector<osg::ref_ptr<osg::Texture2D> >::const_iterator it = layers.begin(); it != layers.end(); ++it)
         {
-            Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().create(name.str(),
-                                                               Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
-            Ogre::Technique* technique = mat->getTechnique(0);
-            technique->removeAllPasses();
+            osg::ref_ptr<osg::StateSet> stateset (new osg::StateSet);
 
-            if (displayCompositeMap)
+            if (!firstLayer)
             {
-                Ogre::Pass* pass = technique->createPass();
-                pass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE);
-                pass->createTextureUnitState(mCompositeMap)->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
-            }
-            else
-            {
-                assert(mLayerList.size() == mBlendmapList.size()+1);
-                std::vector<Ogre::TexturePtr>::iterator blend = mBlendmapList.begin();
-                for (std::vector<LayerInfo>::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer)
-                {
-                    Ogre::Pass* pass = technique->createPass();
-                    pass->setLightingEnabled(false);
-                    pass->setVertexColourTracking(Ogre::TVC_NONE);
-                    // TODO: How to handle fog?
-                    pass->setFog(true, Ogre::FOG_NONE);
-
-                    bool first = (layer == mLayerList.begin());
-
-                    Ogre::TextureUnitState* tus;
-
-                    if (!first)
-                    {
-                        pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
-                        pass->setDepthFunction(Ogre::CMPF_EQUAL);
-
-                        tus = pass->createTextureUnitState((*blend)->getName());
-                        tus->setAlphaOperation(Ogre::LBX_BLEND_TEXTURE_ALPHA,
-                                               Ogre::LBS_TEXTURE,
-                                               Ogre::LBS_TEXTURE);
-                        tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
-                                                  Ogre::LBS_TEXTURE,
-                                                  Ogre::LBS_TEXTURE);
-                        tus->setIsAlpha(true);
-                        tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
-
-                        float scale = (16/(16.f+1.f));
-                        tus->setTextureScale(1.f/scale,1.f/scale);
-                    }
-
-                    // Add the actual layer texture on top of the alpha map.
-                    tus = pass->createTextureUnitState(layer->mDiffuseMap);
-                    if (!first)
-                        tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
-                                                  Ogre::LBS_TEXTURE,
-                                                  Ogre::LBS_CURRENT);
-
-                    tus->setTextureScale(1/16.f,1/16.f);
-
-                    if (!first)
-                        ++blend;
-                }
-
-                if (!renderCompositeMap)
-                {
-                    Ogre::Pass* lightingPass = technique->createPass();
-                    lightingPass->setSceneBlending(Ogre::SBT_MODULATE);
-                    lightingPass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE);
-                    lightingPass->setFog(true, Ogre::FOG_NONE);
-                }
+                stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
+                osg::ref_ptr<osg::Depth> depth (new osg::Depth);
+                depth->setFunction(osg::Depth::EQUAL);
+                stateset->setAttributeAndModes(depth, osg::StateAttribute::ON);
             }
 
-            return mat;
+            int texunit = 0;
+            if(!firstLayer)
+            {
+                osg::ref_ptr<osg::Texture2D> blendmap = blendmaps.at(i++);
+
+                stateset->setTextureAttributeAndModes(texunit, blendmap.get());
+
+                // This is to map corner vertices directly to the center of a blendmap texel.
+                osg::Matrixf texMat;
+                float scale = (16/(16.f+1.f));
+                texMat.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f));
+                texMat.preMultScale(osg::Vec3f(scale, scale, 1.f));
+                texMat.preMultTranslate(osg::Vec3f(-0.5f, -0.5f, 0.f));
+
+                stateset->setTextureAttributeAndModes(texunit, new osg::TexMat(texMat));
+
+                ++texunit;
+            }
+
+            // Add the actual layer texture multiplied by the alpha map.
+            osg::ref_ptr<osg::Texture2D> tex = *it;
+            stateset->setTextureAttributeAndModes(texunit, tex.get());
+
+            osg::ref_ptr<osg::TexMat> texMat (new osg::TexMat);
+            float scale = 16.f;
+            texMat->setMatrix(osg::Matrix::scale(osg::Vec3f(scale,scale,1.f)));
+            stateset->setTextureAttributeAndModes(texunit, texMat, osg::StateAttribute::ON);
+
+            firstLayer = false;
+
+            addPass(stateset);
         }
-#if TERRAIN_USE_SHADER
-        else
-        {
-            sh::MaterialInstance* material = sh::Factory::getInstance().createMaterialInstance (name.str());
-            material->setProperty ("allow_fixed_function", sh::makeProperty<sh::BooleanValue>(new sh::BooleanValue(false)));
+    }
 
-            if (displayCompositeMap)
-            {
-                sh::MaterialInstancePass* p = material->createPass ();
+    Effect::Effect(const std::vector<osg::ref_ptr<osg::Texture2D> > &layers, const std::vector<osg::ref_ptr<osg::Texture2D> > &blendmaps)
+        : mLayers(layers)
+        , mBlendmaps(blendmaps)
+    {
+        osg::ref_ptr<osg::Material> material (new osg::Material);
+        material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
+        getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON);
 
-                p->setProperty ("vertex_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_vertex")));
-                p->setProperty ("fragment_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_fragment")));
-                p->mShaderProperties.setProperty ("is_first_pass", sh::makeProperty(new sh::BooleanValue(true)));
-                p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(false)));
-                p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(true)));
-                p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue("0")));
-                p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue("0")));
-                p->mShaderProperties.setProperty ("normal_map_enabled", sh::makeProperty (new sh::BooleanValue(false)));
-                p->mShaderProperties.setProperty ("parallax_enabled", sh::makeProperty (new sh::BooleanValue(false)));
-                p->mShaderProperties.setProperty ("normal_maps",
-                                                  sh::makeProperty (new sh::IntValue(0)));
+        selectTechnique(0);
+    }
 
-                sh::MaterialInstanceTextureUnit* tex = p->createTextureUnit ("compositeMap");
-                tex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mCompositeMap)));
-                tex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp")));
+    bool Effect::define_techniques()
+    {
+        addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps));
 
-                // shadow. TODO: repeated, put in function
-                if (mShadows)
-                {
-                    for (int i = 0; i < (mSplitShadows ? 3 : 1); ++i)
-                    {
-                        sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i));
-                        shadowTex->setProperty ("content_type", sh::makeProperty<sh::StringValue> (new sh::StringValue("shadow")));
-                    }
-                }
-                p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue(
-                    Ogre::StringConverter::toString(1))));
-
-                p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(0)));
-            }
-            else
-            {
-
-                bool shadows = mShadows && !renderCompositeMap;
-
-                int layerOffset = 0;
-                while (layerOffset < (int)mLayerList.size())
-                {
-                    int blendmapOffset = (layerOffset == 0) ? 1 : 0; // the first layer of the first pass is the base layer and does not need a blend map
-
-                    // Check how many layers we can fit in this pass
-                    int numLayersInThisPass = 0;
-                    int numBlendTextures = 0;
-                    std::vector<std::string> blendTextures;
-                    int remainingTextureUnits = OGRE_MAX_TEXTURE_LAYERS;
-                    if (shadows)
-                        remainingTextureUnits -= (mSplitShadows ? 3 : 1);
-                    while (remainingTextureUnits && layerOffset + numLayersInThisPass < (int)mLayerList.size())
-                    {
-                        int layerIndex = numLayersInThisPass + layerOffset;
-
-                        int neededTextureUnits=0;
-                        int neededBlendTextures=0;
-
-                        if (layerIndex != 0)
-                        {
-                            std::string blendTextureName = mBlendmapList[getBlendmapIndexForLayer(layerIndex)]->getName();
-                            if (std::find(blendTextures.begin(), blendTextures.end(), blendTextureName) == blendTextures.end())
-                            {
-                                blendTextures.push_back(blendTextureName);
-                                ++neededBlendTextures;
-                                ++neededTextureUnits; // blend texture
-                            }
-                        }
-                        ++neededTextureUnits; // layer texture
-
-                        // Check if this layer has a normal map
-                        if (mNormalMapping && !mLayerList[layerIndex].mNormalMap.empty() && !renderCompositeMap)
-                            ++neededTextureUnits; // normal map
-                        if (neededTextureUnits <= remainingTextureUnits)
-                        {
-                            // We can fit another!
-                            remainingTextureUnits -= neededTextureUnits;
-                            numBlendTextures += neededBlendTextures;
-                            ++numLayersInThisPass;
-                        }
-                        else
-                            break; // We're full
-                    }
-
-
-                    sh::MaterialInstancePass* p = material->createPass ();
-                    p->setProperty ("vertex_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_vertex")));
-                    p->setProperty ("fragment_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_fragment")));
-                    if (layerOffset != 0)
-                    {
-                        p->setProperty ("scene_blend", sh::makeProperty(new sh::StringValue("alpha_blend")));
-                        // Only write if depth is equal to the depth value written by the previous pass.
-                        p->setProperty ("depth_func", sh::makeProperty(new sh::StringValue("equal")));
-                    }
-
-                    p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(renderCompositeMap)));
-                    p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(displayCompositeMap)));
-
-                    p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numLayersInThisPass))));
-                    p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures))));
-                    p->mShaderProperties.setProperty ("normal_map_enabled",
-                                                      sh::makeProperty (new sh::BooleanValue(false)));
-
-                    // blend maps
-                    // the index of the first blend map used in this pass
-                    int blendmapStart;
-                    if (mLayerList.size() == 1) // special case. if there's only one layer, we don't need blend maps at all
-                        blendmapStart = 0;
-                    else
-                        blendmapStart = getBlendmapIndexForLayer(layerOffset+blendmapOffset);
-                    for (int i = 0; i < numBlendTextures; ++i)
-                    {
-                        sh::MaterialInstanceTextureUnit* blendTex = p->createTextureUnit ("blendMap" + Ogre::StringConverter::toString(i));
-                        blendTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mBlendmapList[blendmapStart+i]->getName())));
-                        blendTex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp")));
-                    }
-
-                    // layer maps
-                    bool anyNormalMaps = false;
-                    bool anyParallax = false;
-                    size_t normalMaps = 0;
-                    for (int i = 0; i < numLayersInThisPass; ++i)
-                    {
-                        const LayerInfo& layer = mLayerList[layerOffset+i];
-                        // diffuse map
-                        sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i));
-                        diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mDiffuseMap)));
-
-                        // normal map (optional)
-                        bool useNormalMap = mNormalMapping && !mLayerList[layerOffset+i].mNormalMap.empty() && !renderCompositeMap;
-                        bool useParallax = useNormalMap && mParallaxMapping && layer.mParallax;
-                        bool useSpecular = layer.mSpecular;
-                        if (useNormalMap)
-                        {
-                            anyNormalMaps = true;
-                            anyParallax = anyParallax || useParallax;
-                            sh::MaterialInstanceTextureUnit* normalTex = p->createTextureUnit ("normalMap" + Ogre::StringConverter::toString(i));
-                            normalTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mNormalMap)));
-                        }
-                        p->mShaderProperties.setProperty ("use_normal_map_" + Ogre::StringConverter::toString(i),
-                                                          sh::makeProperty (new sh::BooleanValue(useNormalMap)));
-                        p->mShaderProperties.setProperty ("use_parallax_" + Ogre::StringConverter::toString(i),
-                                                          sh::makeProperty (new sh::BooleanValue(useParallax)));
-                        p->mShaderProperties.setProperty ("use_specular_" + Ogre::StringConverter::toString(i),
-                                                          sh::makeProperty (new sh::BooleanValue(useSpecular)));
-                        boost::hash_combine(normalMaps, useNormalMap);
-                        boost::hash_combine(normalMaps, useNormalMap && layer.mParallax);
-                        boost::hash_combine(normalMaps, useSpecular);
-
-                        if (i+layerOffset > 0)
-                        {
-                            int blendTextureIndex = getBlendmapIndexForLayer(layerOffset+i);
-                            std::string blendTextureComponent = getBlendmapComponentForLayer(layerOffset+i);
-                            p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i),
-                                                              sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + blendTextureComponent)));
-                        }
-                        else
-                        {
-                            // just to make it shut up about blendmap_component_0 not existing in the first pass.
-                            // it might be retrieved, but will never survive the preprocessing step.
-                            p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i),
-                                sh::makeProperty (new sh::StringValue("")));
-                        }
-                    }
-                    p->mShaderProperties.setProperty ("normal_map_enabled",
-                                                      sh::makeProperty (new sh::BooleanValue(anyNormalMaps)));
-                    p->mShaderProperties.setProperty ("parallax_enabled",
-                                                      sh::makeProperty (new sh::BooleanValue(anyParallax)));
-                    // Since the permutation handler can't handle dynamic property names,
-                    // combine normal map settings for all layers into one value
-                    p->mShaderProperties.setProperty ("normal_maps",
-                                                      sh::makeProperty (new sh::IntValue(normalMaps)));
-
-                    // shadow
-                    if (shadows)
-                    {
-                        for (int i = 0; i < (mSplitShadows ? 3 : 1); ++i)
-                        {
-                            sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i));
-                            shadowTex->setProperty ("content_type", sh::makeProperty<sh::StringValue> (new sh::StringValue("shadow")));
-                        }
-                    }
-                    p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue(
-                        Ogre::StringConverter::toString(numBlendTextures + numLayersInThisPass))));
-
-                    // Make sure the pass index is fed to the permutation handler, because blendmap components may be different
-                    p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(layerOffset)));
-
-                    assert ((int)p->mTexUnits.size() == OGRE_MAX_TEXTURE_LAYERS - remainingTextureUnits);
-
-                    layerOffset += numLayersInThisPass;
-                }
-            }
-        }
-#endif
-        return Ogre::MaterialManager::getSingleton().getByName(name.str());
+        return true;
     }
 
 }
diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp
index b79df9f48c..47c5142c94 100644
--- a/components/terrain/material.hpp
+++ b/components/terrain/material.hpp
@@ -22,51 +22,55 @@
 #ifndef COMPONENTS_TERRAIN_MATERIAL_H
 #define COMPONENTS_TERRAIN_MATERIAL_H
 
-#include <OgreMaterial.h>
+#include <osgFX/Technique>
+#include <osgFX/Effect>
 
-#include "storage.hpp"
+#include "defs.hpp"
+
+namespace osg
+{
+    class Texture2D;
+}
 
 namespace Terrain
 {
 
-    class MaterialGenerator
+    class FixedFunctionTechnique : public osgFX::Technique
     {
     public:
-        MaterialGenerator ();
+        FixedFunctionTechnique(
+                const std::vector<osg::ref_ptr<osg::Texture2D> >& layers,
+                const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps);
 
-        void setLayerList (const std::vector<LayerInfo>& layerList) { mLayerList = layerList; }
-        bool hasLayers() { return mLayerList.size() > 0; }
-        void setBlendmapList (const std::vector<Ogre::TexturePtr>& blendmapList) { mBlendmapList = blendmapList; }
-        const std::vector<Ogre::TexturePtr>& getBlendmapList() { return mBlendmapList; }
-        void setCompositeMap (const std::string& name) { mCompositeMap = name; }
+    protected:
+        virtual void define_passes() {}
+    };
 
-        void enableShaders(bool shaders) { mShaders = shaders; }
-        void enableShadows(bool shadows) { mShadows = shadows; }
-        void enableNormalMapping(bool normalMapping) { mNormalMapping = normalMapping; }
-        void enableParallaxMapping(bool parallaxMapping) { mParallaxMapping = parallaxMapping; }
-        void enableSplitShadows(bool splitShadows) { mSplitShadows = splitShadows; }
+    class Effect : public osgFX::Effect
+    {
+    public:
+        Effect(
+                const std::vector<osg::ref_ptr<osg::Texture2D> >& layers,
+                const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps);
 
-        /// Creates a material suitable for displaying a chunk of terrain using alpha-blending.
-        Ogre::MaterialPtr generate ();
+        virtual bool define_techniques();
 
-        /// Creates a material suitable for displaying a chunk of terrain using a ready-made composite map.
-        Ogre::MaterialPtr generateForCompositeMap ();
-
-        /// Creates a material suitable for rendering composite maps, i.e. for "baking" several layer textures
-        /// into one. The main difference compared to a normal material is that no shading is applied at this point.
-        Ogre::MaterialPtr generateForCompositeMapRTT ();
+        virtual const char *effectName() const
+        {
+            return NULL;
+        }
+        virtual const char *effectDescription() const
+        {
+            return NULL;
+        }
+        virtual const char *effectAuthor() const
+        {
+            return NULL;
+        }
 
     private:
-        Ogre::MaterialPtr create (bool renderCompositeMap, bool displayCompositeMap);
-
-        std::vector<LayerInfo> mLayerList;
-        std::vector<Ogre::TexturePtr> mBlendmapList;
-        std::string mCompositeMap;
-        bool mShaders;
-        bool mShadows;
-        bool mSplitShadows;
-        bool mNormalMapping;
-        bool mParallaxMapping;
+        std::vector<osg::ref_ptr<osg::Texture2D> > mLayers;
+        std::vector<osg::ref_ptr<osg::Texture2D> > mBlendmaps;
     };
 
 }
diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp
deleted file mode 100644
index 89e5e34a36..0000000000
--- a/components/terrain/quadtreenode.cpp
+++ /dev/null
@@ -1,611 +0,0 @@
-/*
- * Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
-
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-#include "quadtreenode.hpp"
-
-#include <OgreSceneManager.h>
-#include <OgreManualObject.h>
-#include <OgreSceneNode.h>
-#include <OgreMaterialManager.h>
-#include <OgreTextureManager.h>
-
-#include "defaultworld.hpp"
-#include "chunk.hpp"
-#include "storage.hpp"
-#include "buffercache.hpp"
-#include "material.hpp"
-
-using namespace Terrain;
-
-namespace
-{
-    int Log2( int n )
-    {
-        assert(n > 0);
-        int targetlevel = 0;
-        while (n >>= 1) ++targetlevel;
-        return targetlevel;
-    }
-
-    // Utility functions for neighbour finding algorithm
-    ChildDirection reflect(ChildDirection dir, Direction dir2)
-    {
-        assert(dir != Root);
-
-        const int lookupTable[4][4] =
-        {
-            // NW  NE  SW  SE
-            {  SW, SE, NW, NE }, // N
-            {  NE, NW, SE, SW }, // E
-            {  SW, SE, NW, NE }, // S
-            {  NE, NW, SE, SW }  // W
-        };
-        return (ChildDirection)lookupTable[dir2][dir];
-    }
-
-    bool adjacent(ChildDirection dir, Direction dir2)
-    {
-        assert(dir != Root);
-        const bool lookupTable[4][4] =
-        {
-            // NW    NE    SW     SE
-            {  true, true, false, false }, // N
-            {  false, true, false, true }, // E
-            {  false, false, true, true }, // S
-            {  true, false, true, false }  // W
-        };
-        return lookupTable[dir2][dir];
-    }
-
-    // Algorithm described by Hanan Samet - 'Neighbour Finding in Quadtrees'
-    // http://www.cs.umd.edu/~hjs/pubs/SametPRIP81.pdf
-    QuadTreeNode* searchNeighbourRecursive (QuadTreeNode* currentNode, Direction dir)
-    {
-        if (!currentNode->getParent())
-            return NULL; // Arrived at root node, the root node does not have neighbours
-
-        QuadTreeNode* nextNode;
-        if (adjacent(currentNode->getDirection(), dir))
-            nextNode = searchNeighbourRecursive(currentNode->getParent(), dir);
-        else
-            nextNode = currentNode->getParent();
-
-        if (nextNode && nextNode->hasChildren())
-            return nextNode->getChild(reflect(currentNode->getDirection(), dir));
-        else
-            return NULL;
-    }
-
-    // Create a 2D quad
-    void makeQuad(Ogre::SceneManager* sceneMgr, float left, float top, float right, float bottom, Ogre::MaterialPtr material)
-    {
-        Ogre::ManualObject* manual = sceneMgr->createManualObject();
-
-        // Use identity view/projection matrices to get a 2d quad
-        manual->setUseIdentityProjection(true);
-        manual->setUseIdentityView(true);
-
-        manual->begin(material->getName());
-
-        float normLeft = left*2-1;
-        float normTop = top*2-1;
-        float normRight = right*2-1;
-        float normBottom = bottom*2-1;
-
-        manual->position(normLeft, normTop, 0.0);
-        manual->textureCoord(0, 1);
-        manual->position(normRight, normTop, 0.0);
-        manual->textureCoord(1, 1);
-        manual->position(normRight, normBottom, 0.0);
-        manual->textureCoord(1, 0);
-        manual->position(normLeft, normBottom, 0.0);
-        manual->textureCoord(0, 0);
-
-        manual->quad(0,1,2,3);
-
-        manual->end();
-
-        Ogre::AxisAlignedBox aabInf;
-        aabInf.setInfinite();
-        manual->setBoundingBox(aabInf);
-
-        sceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(manual);
-    }
-}
-
-QuadTreeNode::QuadTreeNode(DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2 &center, QuadTreeNode* parent)
-    : mMaterialGenerator(NULL)
-    , mLoadState(LS_Unloaded)
-    , mIsDummy(false)
-    , mSize(size)
-    , mLodLevel(Log2(static_cast<int>(mSize)))
-    , mBounds(Ogre::AxisAlignedBox::BOX_NULL)
-    , mWorldBounds(Ogre::AxisAlignedBox::BOX_NULL)
-    , mDirection(dir)
-    , mCenter(center)
-    , mSceneNode(NULL)
-    , mParent(parent)
-    , mChunk(NULL)
-    , mTerrain(terrain)
-{
-    mBounds.setNull();
-    for (int i=0; i<4; ++i)
-        mChildren[i] = NULL;
-    for (int i=0; i<4; ++i)
-        mNeighbours[i] = NULL;
-
-    if (mDirection == Root)
-        mSceneNode = mTerrain->getRootSceneNode();
-    else
-        mSceneNode = mTerrain->getSceneManager()->createSceneNode();
-    Ogre::Vector2 pos (0,0);
-    if (mParent)
-        pos = mParent->getCenter();
-    pos = mCenter - pos;
-    float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
-
-    Ogre::Vector3 sceneNodePos (pos.x*cellWorldSize, pos.y*cellWorldSize, 0);
-    mTerrain->convertPosition(sceneNodePos);
-
-    mSceneNode->setPosition(sceneNodePos);
-
-    mMaterialGenerator = new MaterialGenerator();
-    mMaterialGenerator->enableShaders(mTerrain->getShadersEnabled());
-}
-
-void QuadTreeNode::createChild(ChildDirection id, float size, const Ogre::Vector2 &center)
-{
-    mChildren[id] = new QuadTreeNode(mTerrain, id, size, center, this);
-}
-
-QuadTreeNode::~QuadTreeNode()
-{
-    for (int i=0; i<4; ++i)
-        delete mChildren[i];
-    delete mChunk;
-    delete mMaterialGenerator;
-}
-
-QuadTreeNode* QuadTreeNode::getNeighbour(Direction dir)
-{
-    return mNeighbours[static_cast<int>(dir)];
-}
-
-void QuadTreeNode::initNeighbours()
-{
-    for (int i=0; i<4; ++i)
-        mNeighbours[i] = searchNeighbourRecursive(this, (Direction)i);
-
-    if (hasChildren())
-        for (int i=0; i<4; ++i)
-            mChildren[i]->initNeighbours();
-}
-
-void QuadTreeNode::initAabb()
-{
-    float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
-    if (hasChildren())
-    {
-        for (int i=0; i<4; ++i)
-        {
-            mChildren[i]->initAabb();
-            mBounds.merge(mChildren[i]->getBoundingBox());
-        }
-        float minH, maxH;
-        switch (mTerrain->getAlign())
-        {
-            case Terrain::Align_XY:
-                minH = mBounds.getMinimum().z;
-                maxH = mBounds.getMaximum().z;
-                break;
-            case Terrain::Align_XZ:
-                minH = mBounds.getMinimum().y;
-                maxH = mBounds.getMaximum().y;
-                break;
-            case Terrain::Align_YZ:
-                minH = mBounds.getMinimum().x;
-                maxH = mBounds.getMaximum().x;
-                break;
-        }
-        Ogre::Vector3 min(-mSize/2*cellWorldSize, -mSize/2*cellWorldSize, minH);
-        Ogre::Vector3 max(Ogre::Vector3(mSize/2*cellWorldSize, mSize/2*cellWorldSize, maxH));
-        mBounds = Ogre::AxisAlignedBox (min, max);
-        mTerrain->convertBounds(mBounds);
-    }
-    Ogre::Vector3 offset(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0);
-    mTerrain->convertPosition(offset);
-    mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + offset,
-                                        mBounds.getMaximum() + offset);
-}
-
-void QuadTreeNode::setBoundingBox(const Ogre::AxisAlignedBox &box)
-{
-    mBounds = box;
-}
-
-const Ogre::AxisAlignedBox& QuadTreeNode::getBoundingBox()
-{
-    return mBounds;
-}
-
-const Ogre::AxisAlignedBox& QuadTreeNode::getWorldBoundingBox()
-{
-    return mWorldBounds;
-}
-
-bool QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
-{
-    if (isDummy())
-        return true;
-
-    if (mBounds.isNull())
-        return true;
-
-    float dist = mWorldBounds.distance(cameraPos);
-
-    // Make sure our scene node is attached
-    if (!mSceneNode->isInSceneGraph())
-    {
-        mParent->getSceneNode()->addChild(mSceneNode);
-    }
-
-    // Simple LOD selection
-    /// \todo use error metrics?
-    size_t wantedLod = 0;
-    float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
-
-    if (dist > cellWorldSize*64)
-        wantedLod = 6;
-    else if (dist > cellWorldSize*32)
-        wantedLod = 5;
-    else if (dist > cellWorldSize*12)
-        wantedLod = 4;
-    else if (dist > cellWorldSize*5)
-        wantedLod = 3;
-    else if (dist > cellWorldSize*2)
-        wantedLod = 2;
-    else if (dist > cellWorldSize * 1.42) // < sqrt2 so the 3x3 grid around player is always highest lod
-        wantedLod = 1;
-
-    bool wantToDisplay = mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod;
-
-    if (wantToDisplay)
-    {
-        // Wanted LOD is small enough to render this node in one chunk
-        if (mLoadState == LS_Unloaded)
-        {
-            mLoadState = LS_Loading;
-            mTerrain->queueLoad(this);
-            return false;
-        }
-
-        if (mLoadState == LS_Loaded)
-        {
-            // Additional (index buffer) LOD is currently disabled.
-            // This is due to a problem with the LOD selection when a node splits.
-            // After splitting, the distance is measured from the children's bounding boxes, which are possibly
-            // further away than the original node's bounding box, possibly causing a child to switch to a *lower* LOD
-            // than the original node.
-            // In short, we'd sometimes get a switch to a lesser detail when actually moving closer.
-            // This wouldn't be so bad, but unfortunately it also breaks LOD edge connections if a neighbour
-            // node hasn't split yet, and has a higher LOD than our node's child:
-            //  ----- ----- ------------
-            // | LOD | LOD |            |
-            // |  1  |  1  |            |
-            // |-----|-----|   LOD 0    |
-            // | LOD | LOD |            |
-            // |  0  |  0  |            |
-            //  ----- ----- ------------
-            // To prevent this, nodes of the same size need to always select the same LOD, which is basically what we're
-            // doing here.
-            // But this "solution" does increase triangle overhead, so eventually we need to find a more clever way.
-            //mChunk->setAdditionalLod(wantedLod - mLodLevel);
-
-            if (!mChunk->getVisible() && hasChildren())
-            {
-                for (int i=0; i<4; ++i)
-                    mChildren[i]->unload(true);
-            }
-            mChunk->setVisible(true);
-
-            return true;
-        }
-        return false; // LS_Loading
-    }
-
-    // We do not want to display this node - delegate to children if they are already loaded
-    if (!wantToDisplay && hasChildren())
-    {
-        if (mChunk)
-        {
-            // Are children already loaded?
-            bool childrenLoaded = true;
-            for (int i=0; i<4; ++i)
-                if (!mChildren[i]->update(cameraPos))
-                    childrenLoaded = false;
-
-            if (!childrenLoaded)
-            {
-                mChunk->setVisible(true);
-                // Make sure child scene nodes are detached until all children are loaded
-                mSceneNode->removeAllChildren();
-            }
-            else
-            {
-                // Delegation went well, we can unload now
-                unload();
-
-                for (int i=0; i<4; ++i)
-                {
-                    if (!mChildren[i]->getSceneNode()->isInSceneGraph())
-                        mSceneNode->addChild(mChildren[i]->getSceneNode());
-                }
-            }
-            return true;
-        }
-        else
-        {
-            bool success = true;
-            for (int i=0; i<4; ++i)
-                success = mChildren[i]->update(cameraPos) & success;
-            return success;
-        }
-    }
-    return false;
-}
-
-void QuadTreeNode::load(const LoadResponseData &data)
-{
-    assert (!mChunk);
-
-    mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data.mPositions, data.mNormals, data.mColours);
-    mChunk->setVisibilityFlags(mTerrain->getVisibilityFlags());
-    mChunk->setCastShadows(true);
-    mSceneNode->attachObject(mChunk);
-
-    mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
-    mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
-
-    if (mTerrain->areLayersLoaded())
-    {
-        if (mSize == 1)
-        {
-            mChunk->setMaterial(mMaterialGenerator->generate());
-        }
-        else
-        {
-            ensureCompositeMap();
-            mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
-            mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap());
-        }
-    }
-    // else: will be loaded in loadMaterials() after background thread has finished loading layers
-    mChunk->setVisible(false);
-
-    mLoadState = LS_Loaded;
-}
-
-void QuadTreeNode::unload(bool recursive)
-{
-    if (mChunk)
-    {
-        mSceneNode->detachObject(mChunk);
-
-        delete mChunk;
-        mChunk = NULL;
-
-        if (!mCompositeMap.isNull())
-        {
-            Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName());
-            mCompositeMap.setNull();
-        }
-
-        // Do *not* set this when we are still loading!
-        mLoadState = LS_Unloaded;
-    }
-
-    if (recursive && hasChildren())
-    {
-        for (int i=0; i<4; ++i)
-            mChildren[i]->unload(true);
-    }
-}
-
-void QuadTreeNode::updateIndexBuffers()
-{
-    if (hasChunk())
-    {
-        // Fetch a suitable index buffer (which may be shared)
-        size_t ourLod = getActualLodLevel();
-
-        unsigned int flags = 0;
-
-        for (int i=0; i<4; ++i)
-        {
-            QuadTreeNode* neighbour = getNeighbour((Direction)i);
-
-            // If the neighbour isn't currently rendering itself,
-            // go up until we find one. NOTE: We don't need to go down,
-            // because in that case neighbour's detail would be higher than
-            // our detail and the neighbour would handle stitching by itself.
-            while (neighbour && !neighbour->hasChunk())
-                neighbour = neighbour->getParent();
-            size_t lod = 0;
-            if (neighbour)
-                lod = neighbour->getActualLodLevel();
-            if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are -
-                lod = 0;         // neighbours with more detail will do the stitching themselves
-            // Use 4 bits for each LOD delta
-            if (lod > 0)
-            {
-                assert (lod - ourLod < (1 << 4));
-                flags |= static_cast<unsigned int>(lod - ourLod) << (4*i);
-            }
-        }
-        flags |= 0 /*((int)mAdditionalLod)*/ << (4*4);
-
-        mChunk->setIndexBuffer(mTerrain->getBufferCache().getIndexBuffer(flags));
-    }
-    else if (hasChildren())
-    {
-        for (int i=0; i<4; ++i)
-            mChildren[i]->updateIndexBuffers();
-    }
-}
-
-bool QuadTreeNode::hasChunk()
-{
-    return mSceneNode->isInSceneGraph() && mChunk && mChunk->getVisible();
-}
-
-size_t QuadTreeNode::getActualLodLevel()
-{
-    assert(hasChunk() && "Can't get actual LOD level if this node has no render chunk");
-    return mLodLevel /* + mChunk->getAdditionalLod() */;
-}
-
-void QuadTreeNode::loadLayers(const LayerCollection& collection)
-{
-    assert (!mMaterialGenerator->hasLayers());
-
-    std::vector<Ogre::TexturePtr> blendTextures;
-    for (std::vector<Ogre::PixelBox>::const_iterator it = collection.mBlendmaps.begin(); it != collection.mBlendmaps.end(); ++it)
-    {
-        // TODO: clean up blend textures on destruction
-        static int count=0;
-        Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/"
-            + Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
-            Ogre::TEX_TYPE_2D, it->getWidth(), it->getHeight(), 0, it->format);
-
-        Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(it->data, it->getWidth()*it->getHeight()*Ogre::PixelUtil::getNumElemBytes(it->format), true));
-        map->loadRawData(stream, it->getWidth(), it->getHeight(), it->format);
-        blendTextures.push_back(map);
-    }
-
-    mMaterialGenerator->setLayerList(collection.mLayers);
-    mMaterialGenerator->setBlendmapList(blendTextures);
-}
-
-void QuadTreeNode::loadMaterials()
-{
-    if (isDummy())
-        return;
-
-    // Load children first since we depend on them when creating a composite map
-    if (hasChildren())
-    {
-        for (int i=0; i<4; ++i)
-            mChildren[i]->loadMaterials();
-    }
-
-    if (mChunk)
-    {
-        if (mSize == 1)
-        {
-            mChunk->setMaterial(mMaterialGenerator->generate());
-        }
-        else
-        {
-            ensureCompositeMap();
-            mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
-            mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap());
-        }
-    }
-}
-
-void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
-{
-    Ogre::SceneManager* sceneMgr = mTerrain->getCompositeMapSceneManager();
-
-    if (mIsDummy)
-    {
-        // TODO - store this default material somewhere instead of creating one for each empty cell
-        MaterialGenerator matGen;
-        matGen.enableShaders(mTerrain->getShadersEnabled());
-        std::vector<LayerInfo> layer;
-        layer.push_back(mTerrain->getStorage()->getDefaultLayer());
-        matGen.setLayerList(layer);
-        makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT());
-        return;
-    }
-    if (mSize > 1)
-    {
-        assert(hasChildren());
-
-        // 0,0 -------- 1,0
-        //  |     |      |
-        //  |-----|------|
-        //  |     |      |
-        // 0,1 -------- 1,1
-
-        float halfW = area.width()/2.f;
-        float halfH = area.height()/2.f;
-        mChildren[NW]->prepareForCompositeMap(Ogre::TRect<float>(area.left, area.top, area.right-halfW, area.bottom-halfH));
-        mChildren[NE]->prepareForCompositeMap(Ogre::TRect<float>(area.left+halfW, area.top, area.right, area.bottom-halfH));
-        mChildren[SW]->prepareForCompositeMap(Ogre::TRect<float>(area.left, area.top+halfH, area.right-halfW, area.bottom));
-        mChildren[SE]->prepareForCompositeMap(Ogre::TRect<float>(area.left+halfW, area.top+halfH, area.right, area.bottom));
-    }
-    else
-    {
-        // TODO: when to destroy?
-        Ogre::MaterialPtr material = mMaterialGenerator->generateForCompositeMapRTT();
-        makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, material);
-    }
-}
-
-void QuadTreeNode::ensureCompositeMap()
-{
-    if (!mCompositeMap.isNull())
-        return;
-
-    static int i=0;
-    std::stringstream name;
-    name << "terrain/comp" << i++;
-
-    const int size = 128;
-    mCompositeMap = Ogre::TextureManager::getSingleton().createManual(
-                name.str(), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
-        Ogre::TEX_TYPE_2D, size, size, Ogre::MIP_DEFAULT, Ogre::PF_A8B8G8R8);
-
-    // Create quads for each cell
-    prepareForCompositeMap(Ogre::TRect<float>(0,0,1,1));
-
-    mTerrain->renderCompositeMap(mCompositeMap);
-
-    mTerrain->clearCompositeMapSceneManager();
-
-}
-
-void QuadTreeNode::applyMaterials()
-{
-    if (mChunk)
-    {
-        mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
-        mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
-        if (mSize <= 1)
-            mChunk->setMaterial(mMaterialGenerator->generate());
-        else
-            mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap());
-    }
-    if (hasChildren())
-        for (int i=0; i<4; ++i)
-            mChildren[i]->applyMaterials();
-}
diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp
deleted file mode 100644
index e44b646004..0000000000
--- a/components/terrain/quadtreenode.hpp
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
-
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-#ifndef COMPONENTS_TERRAIN_QUADTREENODE_H
-#define COMPONENTS_TERRAIN_QUADTREENODE_H
-
-#include <OgreAxisAlignedBox.h>
-#include <OgreVector2.h>
-#include <OgreTexture.h>
-
-#include "defs.hpp"
-
-namespace Ogre
-{
-    class Rectangle2D;
-}
-
-namespace Terrain
-{
-    class DefaultWorld;
-    class Chunk;
-    class MaterialGenerator;
-    struct LoadResponseData;
-
-    enum ChildDirection
-    {
-        NW = 0,
-        NE = 1,
-        SW = 2,
-        SE = 3,
-        Root
-    };
-
-    enum LoadState
-    {
-        LS_Unloaded,
-        LS_Loading,
-        LS_Loaded
-    };
-
-    /**
-     * @brief A node in the quad tree for our terrain. Depending on LOD,
-     *        a node can either choose to render itself in one batch (merging its children),
-     *        or delegate the render process to its children, rendering each child in at least one batch.
-     */
-    class QuadTreeNode
-    {
-    public:
-        /// @param terrain
-        /// @param dir relative to parent, or Root if we are the root node
-        /// @param size size (in *cell* units!)
-        /// @param center center (in *cell* units!)
-        /// @param parent parent node
-        QuadTreeNode (DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent);
-        ~QuadTreeNode();
-
-        /// Rebuild all materials
-        void applyMaterials();
-
-        /// Initialize neighbours - do this after the quadtree is built
-        void initNeighbours();
-        /// Initialize bounding boxes of non-leafs by merging children bounding boxes.
-        /// Do this after the quadtree is built - note that leaf bounding boxes
-        /// need to be set first via setBoundingBox!
-        void initAabb();
-
-        /// @note takes ownership of \a child
-        void createChild (ChildDirection id, float size, const Ogre::Vector2& center);
-
-        /// Mark this node as a dummy node. This can happen if the terrain size isn't a power of two.
-        /// For the QuadTree to work, we need to round the size up to a power of two, which means we'll
-        /// end up with empty nodes that don't actually render anything.
-        void markAsDummy() { mIsDummy = true; }
-        bool isDummy() { return mIsDummy; }
-
-        QuadTreeNode* getParent() { return mParent; }
-
-        Ogre::SceneNode* getSceneNode() { return mSceneNode; }
-
-        int getSize() { return static_cast<int>(mSize); }
-        Ogre::Vector2 getCenter() { return mCenter; }
-
-        bool hasChildren() { return mChildren[0] != 0; }
-        QuadTreeNode* getChild(ChildDirection dir) { return mChildren[dir]; }
-
-        /// Get neighbour node in this direction
-        QuadTreeNode* getNeighbour (Direction dir);
-
-        /// Returns our direction relative to the parent node, or Root if we are the root node.
-        ChildDirection getDirection() { return mDirection; }
-
-        /// Set bounding box in local coordinates. Should be done at load time for leaf nodes.
-        /// Other nodes can merge AABB of child nodes.
-        void setBoundingBox (const Ogre::AxisAlignedBox& box);
-
-        /// Get bounding box in local coordinates
-        const Ogre::AxisAlignedBox& getBoundingBox();
-
-        const Ogre::AxisAlignedBox& getWorldBoundingBox();
-
-        DefaultWorld* getTerrain() { return mTerrain; }
-
-        /// Adjust LODs for the given camera position, possibly splitting up chunks or merging them.
-        /// @return Did we (or all of our children) choose to render?
-        bool update (const Ogre::Vector3& cameraPos);
-
-        /// Adjust index buffers of chunks to stitch together chunks of different LOD, so that cracks are avoided.
-        /// Call after QuadTreeNode::update!
-        void updateIndexBuffers();
-
-        /// Destroy chunks rendered by this node *and* its children (if param is true)
-        void destroyChunks(bool children);
-
-        /// Get the effective LOD level if this node was rendered in one chunk
-        /// with Storage::getCellVertices^2 vertices
-        size_t getNativeLodLevel() { return mLodLevel; }
-
-        /// Get the effective current LOD level used by the chunk rendering this node
-        size_t getActualLodLevel();
-
-        /// Is this node currently configured to render itself?
-        bool hasChunk();
-
-        /// Add a textured quad to a specific 2d area in the composite map scenemanager.
-        /// Only nodes with size <= 1 can be rendered with alpha blending, so larger nodes will simply
-        /// call this method on their children.
-        /// @note Do not call this before World::areLayersLoaded() == true
-        /// @param area area in image space to put the quad
-        void prepareForCompositeMap(Ogre::TRect<float> area);
-
-        /// Create a chunk for this node from the given data.
-        void load (const LoadResponseData& data);
-        void unload(bool recursive=false);
-        void loadLayers (const LayerCollection& collection);
-        /// This is recursive! Call it once on the root node after all leafs have loaded layers.
-        void loadMaterials();
-
-        LoadState getLoadState() { return mLoadState; }
-
-    private:
-        // Stored here for convenience in case we need layer list again
-        MaterialGenerator* mMaterialGenerator;
-
-        LoadState mLoadState;
-
-        bool mIsDummy;
-        float mSize;
-        size_t mLodLevel; // LOD if we were to render this node in one chunk
-        Ogre::AxisAlignedBox mBounds;
-        Ogre::AxisAlignedBox mWorldBounds;
-        ChildDirection mDirection;
-        Ogre::Vector2 mCenter;
-
-        Ogre::SceneNode* mSceneNode;
-
-        QuadTreeNode* mParent;
-        QuadTreeNode* mChildren[4];
-        QuadTreeNode* mNeighbours[4];
-
-        Chunk* mChunk;
-
-        DefaultWorld* mTerrain;
-
-        Ogre::TexturePtr mCompositeMap;
-
-        void ensureCompositeMap();
-    };
-
-}
-
-#endif
diff --git a/components/terrain/storage.cpp b/components/terrain/storage.cpp
index 14009127d2..857713a82b 100644
--- a/components/terrain/storage.cpp
+++ b/components/terrain/storage.cpp
@@ -19,3 +19,5 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
+
+#include "storage.hpp"
diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp
index 7846e91c6e..a302c8f8ce 100644
--- a/components/terrain/storage.hpp
+++ b/components/terrain/storage.hpp
@@ -22,10 +22,20 @@
 #ifndef COMPONENTS_TERRAIN_STORAGE_H
 #define COMPONENTS_TERRAIN_STORAGE_H
 
-#include <OgreHardwareVertexBuffer.h>
+#include <vector>
+
+#include <osg/Vec2f>
+#include <osg/Vec3f>
+#include <osg/ref_ptr>
+#include <osg/Array>
 
 #include "defs.hpp"
 
+namespace osg
+{
+    class Image;
+}
+
 namespace Terrain
 {
     /// We keep storage of terrain data abstract here since we need different implementations for game and editor
@@ -46,7 +56,7 @@ namespace Terrain
         /// @param min min height will be stored here
         /// @param max max height will be stored here
         /// @return true if there was data available for this terrain chunk
-        virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max) = 0;
+        virtual bool getMinMaxHeights (float size, const osg::Vec2f& center, float& min, float& max) = 0;
 
         /// Fill vertex buffers for a terrain chunk.
         /// @note May be called from background threads. Make sure to only call thread-safe functions from here!
@@ -59,11 +69,12 @@ namespace Terrain
         /// @param positions buffer to write vertices
         /// @param normals buffer to write vertex normals
         /// @param colours buffer to write vertex colours
-        virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
-                                std::vector<float>& positions,
-                                std::vector<float>& normals,
-                                std::vector<Ogre::uint8>& colours) = 0;
+        virtual void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
+                                osg::ref_ptr<osg::Vec3Array> positions,
+                                osg::ref_ptr<osg::Vec3Array> normals,
+                                osg::ref_ptr<osg::Vec4Array> colours) = 0;
 
+        typedef std::vector<osg::ref_ptr<osg::Image> > ImageVector;
         /// Create textures holding layer blend values for a terrain chunk.
         /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might
         ///       have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used.
@@ -75,23 +86,11 @@ namespace Terrain
         ///        can utilize packing, FFP can't.
         /// @param blendmaps created blendmaps will be written here
         /// @param layerList names of the layer textures used will be written here
-        virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
-                           std::vector<Ogre::PixelBox>& blendmaps,
+        virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, bool pack,
+                           ImageVector& blendmaps,
                            std::vector<LayerInfo>& layerList) = 0;
 
-        /// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information.
-        /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once.
-        /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might
-        ///       have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used.
-        /// @note May be called from background threads. Make sure to only call thread-safe functions from here!
-        /// @param nodes A collection of nodes for which to retrieve the aforementioned data
-        /// @param out Output vector
-        /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) -
-        ///        otherwise, each texture contains blend values for one layer only. Shader-based rendering
-        ///        can utilize packing, FFP can't.
-        virtual void getBlendmaps (const std::vector<QuadTreeNode*>& nodes, std::vector<LayerCollection>& out, bool pack) = 0;
-
-        virtual float getHeightAt (const Ogre::Vector3& worldPos) = 0;
+        virtual float getHeightAt (const osg::Vec3f& worldPos) = 0;
 
         virtual LayerInfo getDefaultLayer() = 0;
 
diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp
index bb99ca23e4..e99126a2a8 100644
--- a/components/terrain/terraingrid.cpp
+++ b/components/terrain/terraingrid.cpp
@@ -19,22 +19,54 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
+
 #include "terraingrid.hpp"
 
-#include <OgreSceneManager.h>
-#include <OgreSceneNode.h>
-#include <OgreAxisAlignedBox.h>
+#include <memory>
 
-#include "chunk.hpp"
+#include <components/resource/resourcesystem.hpp>
+#include <components/resource/texturemanager.hpp>
+
+#include <components/sceneutil/lightmanager.hpp>
+
+#include <osg/PositionAttitudeTransform>
+#include <osg/Geometry>
+#include <osg/Geode>
+
+#include <osgFX/Effect>
+
+#include <osgUtil/IncrementalCompileOperation>
+
+#include "material.hpp"
+#include "storage.hpp"
+
+namespace
+{
+    class StaticBoundingBoxCallback : public osg::Drawable::ComputeBoundingBoxCallback
+    {
+    public:
+        StaticBoundingBoxCallback(const osg::BoundingBox& bounds)
+            : mBoundingBox(bounds)
+        {
+        }
+
+        virtual osg::BoundingBox computeBound(const osg::Drawable&) const
+        {
+            return mBoundingBox;
+        }
+
+    private:
+        osg::BoundingBox mBoundingBox;
+    };
+}
 
 namespace Terrain
 {
 
-TerrainGrid::TerrainGrid(Ogre::SceneManager *sceneMgr, Terrain::Storage *storage, int visibilityFlags, bool shaders, Terrain::Alignment align)
-    : Terrain::World(sceneMgr, storage, visibilityFlags, shaders, align)
-    , mVisible(true)
+TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico,
+                         Storage* storage, int nodeMask)
+    : Terrain::World(parent, resourceSystem, ico, storage, nodeMask)
 {
-    mRootNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
 }
 
 TerrainGrid::~TerrainGrid()
@@ -43,81 +75,105 @@ TerrainGrid::~TerrainGrid()
     {
         unloadCell(mGrid.begin()->first.first, mGrid.begin()->first.second);
     }
-
-    mSceneMgr->destroySceneNode(mRootNode);
 }
 
-void TerrainGrid::update(const Ogre::Vector3 &cameraPos)
+class GridElement
 {
-}
+public:
+    osg::ref_ptr<osg::PositionAttitudeTransform> mNode;
+};
 
 void TerrainGrid::loadCell(int x, int y)
 {
     if (mGrid.find(std::make_pair(x, y)) != mGrid.end())
         return; // already loaded
 
-    Ogre::Vector2 center(x+0.5f, y+0.5f);
+    osg::Vec2f center(x+0.5f, y+0.5f);
     float minH, maxH;
     if (!mStorage->getMinMaxHeights(1, center, minH, maxH))
         return; // no terrain defined
 
-    Ogre::Vector3 min (-0.5f*mStorage->getCellWorldSize(),
-                       -0.5f*mStorage->getCellWorldSize(),
-                       minH);
-    Ogre::Vector3 max (0.5f*mStorage->getCellWorldSize(),
+    std::auto_ptr<GridElement> element (new GridElement);
+
+    osg::Vec2f worldCenter = center*mStorage->getCellWorldSize();
+    element->mNode = new osg::PositionAttitudeTransform;
+    element->mNode->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f));
+    mTerrainRoot->addChild(element->mNode);
+
+    osg::ref_ptr<osg::Vec3Array> positions (new osg::Vec3Array);
+    osg::ref_ptr<osg::Vec3Array> normals (new osg::Vec3Array);
+    osg::ref_ptr<osg::Vec4Array> colors (new osg::Vec4Array);
+
+    mStorage->fillVertexBuffers(0, 1, center, positions, normals, colors);
+
+    osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
+    geometry->setVertexArray(positions);
+    geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX);
+    geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX);
+    geometry->setUseDisplayList(false);
+    geometry->setUseVertexBufferObjects(true);
+
+    geometry->addPrimitiveSet(mCache.getIndexBuffer(0));
+
+    // we already know the bounding box, so no need to let OSG compute it.
+    osg::Vec3f min(-0.5f*mStorage->getCellWorldSize(),
+                   -0.5f*mStorage->getCellWorldSize(),
+                   minH);
+    osg::Vec3f max (0.5f*mStorage->getCellWorldSize(),
                        0.5f*mStorage->getCellWorldSize(),
                        maxH);
+    osg::BoundingBox bounds(min, max);
+    geometry->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(bounds));
 
-    Ogre::AxisAlignedBox bounds(min, max);
+    osg::ref_ptr<osg::Geode> geode (new osg::Geode);
+    geode->addDrawable(geometry);
 
-    GridElement element;
+    std::vector<LayerInfo> layerList;
+    std::vector<osg::ref_ptr<osg::Image> > blendmaps;
+    mStorage->getBlendmaps(1.f, center, false, blendmaps, layerList);
 
-    Ogre::Vector2 worldCenter = center*mStorage->getCellWorldSize();
-    element.mSceneNode = mRootNode->createChildSceneNode(Ogre::Vector3(worldCenter.x, worldCenter.y, 0));
+    // For compiling textures, I don't think the osgFX::Effect does it correctly
+    osg::ref_ptr<osg::Node> textureCompileDummy (new osg::Node);
 
-    std::vector<float> positions;
-    std::vector<float> normals;
-    std::vector<Ogre::uint8> colours;
-    mStorage->fillVertexBuffers(0, 1, center, mAlign, positions, normals, colours);
-
-    element.mChunk = new Terrain::Chunk(mCache.getUVBuffer(), bounds, positions, normals, colours);
-    element.mChunk->setIndexBuffer(mCache.getIndexBuffer(0));
-    element.mChunk->setVisibilityFlags(mVisibilityFlags);
-    element.mChunk->setCastShadows(true);
-
-    std::vector<Ogre::PixelBox> blendmaps;
-    std::vector<Terrain::LayerInfo> layerList;
-    mStorage->getBlendmaps(1, center, mShaders, blendmaps, layerList);
-
-    element.mMaterialGenerator.setLayerList(layerList);
-
-    // upload blendmaps to GPU
-    std::vector<Ogre::TexturePtr> blendTextures;
-    for (std::vector<Ogre::PixelBox>::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it)
+    std::vector<osg::ref_ptr<osg::Texture2D> > layerTextures;
+    for (std::vector<LayerInfo>::const_iterator it = layerList.begin(); it != layerList.end(); ++it)
     {
-        static int count=0;
-        Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/"
-            + Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
-            Ogre::TEX_TYPE_2D, it->getWidth(), it->getHeight(), 0, it->format);
-
-        Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(it->data, it->getWidth()*it->getHeight()*Ogre::PixelUtil::getNumElemBytes(it->format), true));
-        map->loadRawData(stream, it->getWidth(), it->getHeight(), it->format);
-        blendTextures.push_back(map);
+        layerTextures.push_back(mResourceSystem->getTextureManager()->getTexture2D(it->mDiffuseMap, osg::Texture::REPEAT, osg::Texture::REPEAT));
+        textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back());
     }
 
-    element.mMaterialGenerator.setBlendmapList(blendTextures);
+    std::vector<osg::ref_ptr<osg::Texture2D> > blendmapTextures;
+    for (std::vector<osg::ref_ptr<osg::Image> >::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it)
+    {
+        osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D);
+        texture->setImage(*it);
+        texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
+        texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
+        texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
+        texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
+        texture->setResizeNonPowerOfTwoHint(false);
+        blendmapTextures.push_back(texture);
 
-    element.mSceneNode->attachObject(element.mChunk);
-    updateMaterial(element);
+        textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back());
+    }
 
-    mGrid[std::make_pair(x,y)] = element;
-}
+    for (unsigned int i=0; i<blendmapTextures.size()+1; ++i)
+        geometry->setTexCoordArray(i, mCache.getUVBuffer());
 
-void TerrainGrid::updateMaterial(GridElement &element)
-{
-    element.mMaterialGenerator.enableShadows(getShadowsEnabled());
-    element.mMaterialGenerator.enableSplitShadows(getSplitShadowsEnabled());
-    element.mChunk->setMaterial(element.mMaterialGenerator.generate());
+    osg::ref_ptr<osgFX::Effect> effect (new Terrain::Effect(layerTextures, blendmapTextures));
+
+    effect->addCullCallback(new SceneUtil::LightListCallback);
+
+    effect->addChild(geode);
+    element->mNode->addChild(effect);
+
+    if (mIncrementalCompileOperation)
+    {
+        mIncrementalCompileOperation->add(geode);
+        mIncrementalCompileOperation->add(textureCompileDummy);
+    }
+
+    mGrid[std::make_pair(x,y)] = element.release();
 }
 
 void TerrainGrid::unloadCell(int x, int y)
@@ -126,60 +182,11 @@ void TerrainGrid::unloadCell(int x, int y)
     if (it == mGrid.end())
         return;
 
-    GridElement& element = it->second;
-    delete element.mChunk;
-    element.mChunk = NULL;
-
-    const std::vector<Ogre::TexturePtr>& blendmaps = element.mMaterialGenerator.getBlendmapList();
-    for (std::vector<Ogre::TexturePtr>::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it)
-        Ogre::TextureManager::getSingleton().remove((*it)->getName());
-
-    mSceneMgr->destroySceneNode(element.mSceneNode);
-    element.mSceneNode = NULL;
+    GridElement* element = it->second;
+    mTerrainRoot->removeChild(element->mNode);
+    delete element;
 
     mGrid.erase(it);
 }
 
-void TerrainGrid::applyMaterials(bool shadows, bool splitShadows)
-{
-    mShadows = shadows;
-    mSplitShadows = splitShadows;
-    for (Grid::iterator it = mGrid.begin(); it != mGrid.end(); ++it)
-    {
-        updateMaterial(it->second);
-    }
-}
-
-bool TerrainGrid::getVisible()
-{
-    return mVisible;
-}
-
-void TerrainGrid::setVisible(bool visible)
-{
-    mVisible = visible;
-    mRootNode->setVisible(visible);
-}
-
-Ogre::AxisAlignedBox TerrainGrid::getWorldBoundingBox (const Ogre::Vector2& center)
-{
-    int cellX = static_cast<int>(std::floor(center.x));
-    int cellY = static_cast<int>(std::floor(center.y));
-
-    Grid::iterator it = mGrid.find(std::make_pair(cellX, cellY));
-    if (it == mGrid.end())
-        return Ogre::AxisAlignedBox::BOX_NULL;
-
-    Terrain::Chunk* chunk = it->second.mChunk;
-    Ogre::SceneNode* node = it->second.mSceneNode;
-    Ogre::AxisAlignedBox box = chunk->getBoundingBox();
-    box = Ogre::AxisAlignedBox(box.getMinimum() + node->getPosition(), box.getMaximum() + node->getPosition());
-    return box;
-}
-
-void TerrainGrid::syncLoad()
-{
-
-}
-
 }
diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp
index 97ef6d14d3..3a6d717933 100644
--- a/components/terrain/terraingrid.hpp
+++ b/components/terrain/terraingrid.hpp
@@ -27,64 +27,23 @@
 
 namespace Terrain
 {
-    class Chunk;
 
-    struct GridElement
-    {
-        Ogre::SceneNode* mSceneNode;
-
-        Terrain::MaterialGenerator mMaterialGenerator;
-
-        Terrain::Chunk* mChunk;
-    };
+    class GridElement;
 
     /// @brief Simple terrain implementation that loads cells in a grid, with no LOD
     class TerrainGrid : public Terrain::World
     {
     public:
-        /// @note takes ownership of \a storage
-        /// @param sceneMgr scene manager to use
-        /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..)
-        /// @param visbilityFlags visibility flags for the created meshes
-        /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually
-        ///         faster so this is just here for compatibility.
-        /// @param align The align of the terrain, see Alignment enum
-        TerrainGrid(Ogre::SceneManager* sceneMgr,
-                Terrain::Storage* storage, int visibilityFlags, bool shaders, Terrain::Alignment align);
+        TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico,
+              Storage* storage, int nodeMask);
         ~TerrainGrid();
 
-        /// Update chunk LODs according to this camera position
-        virtual void update (const Ogre::Vector3& cameraPos);
-
         virtual void loadCell(int x, int y);
         virtual void unloadCell(int x, int y);
 
-        /// Get the world bounding box of a chunk of terrain centered at \a center
-        virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center);
-
-        /// Show or hide the whole terrain
-        /// @note this setting may be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden
-        virtual void setVisible(bool visible);
-        virtual bool getVisible();
-
-        /// Recreate materials used by terrain chunks. This should be called whenever settings of
-        /// the material factory are changed. (Relying on the factory to update those materials is not
-        /// enough, since turning a feature on/off can change the number of texture units available for layer/blend
-        /// textures, and to properly respond to this we may need to change the structure of the material, such as
-        /// adding or removing passes. This can only be achieved by a full rebuild.)
-        virtual void applyMaterials(bool shadows, bool splitShadows);
-
-        /// Wait until all background loading is complete.
-        virtual void syncLoad();
-
     private:
-        void updateMaterial (GridElement& element);
-
-        typedef std::map<std::pair<int, int>, GridElement> Grid;
+        typedef std::map<std::pair<int, int>, GridElement*> Grid;
         Grid mGrid;
-
-        Ogre::SceneNode* mRootNode;
-        bool mVisible;
     };
 
 }
diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp
index 3baaaed44e..1cfcc80acb 100644
--- a/components/terrain/world.cpp
+++ b/components/terrain/world.cpp
@@ -21,63 +21,39 @@
  */
 #include "world.hpp"
 
-#include <OgreAxisAlignedBox.h>
+#include <osg/Group>
+#include <osgUtil/IncrementalCompileOperation>
 
 #include "storage.hpp"
 
 namespace Terrain
 {
 
-World::World(Ogre::SceneManager* sceneMgr,
-                 Storage* storage, int visibilityFlags, bool shaders, Alignment align)
-    : mShaders(shaders)
-    , mShadows(false)
-    , mSplitShadows(false)
-    , mAlign(align)
-    , mStorage(storage)
-    , mVisibilityFlags(visibilityFlags)
-    , mSceneMgr(sceneMgr)
+World::World(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico,
+             Storage* storage, int nodeMask)
+    : mStorage(storage)
     , mCache(storage->getCellVertices())
+    , mParent(parent)
+    , mResourceSystem(resourceSystem)
+    , mIncrementalCompileOperation(ico)
 {
+    mTerrainRoot = new osg::Group;
+    mTerrainRoot->setNodeMask(nodeMask);
+    mTerrainRoot->getOrCreateStateSet()->setRenderingHint(osg::StateSet::OPAQUE_BIN);
+
+    mParent->addChild(mTerrainRoot);
 }
 
 World::~World()
 {
+    mParent->removeChild(mTerrainRoot);
+
     delete mStorage;
 }
 
-float World::getHeightAt(const Ogre::Vector3 &worldPos)
+float World::getHeightAt(const osg::Vec3f &worldPos)
 {
     return mStorage->getHeightAt(worldPos);
 }
 
-void World::convertPosition(float &x, float &y, float &z)
-{
-    Terrain::convertPosition(mAlign, x, y, z);
-}
-
-void World::convertPosition(Ogre::Vector3 &pos)
-{
-    convertPosition(pos.x, pos.y, pos.z);
-}
-
-void World::convertBounds(Ogre::AxisAlignedBox& bounds)
-{
-    switch (mAlign)
-    {
-    case Align_XY:
-        return;
-    case Align_XZ:
-        convertPosition(bounds.getMinimum());
-        convertPosition(bounds.getMaximum());
-        // Because we changed sign of Z
-        std::swap(bounds.getMinimum().z, bounds.getMaximum().z);
-        return;
-    case Align_YZ:
-        convertPosition(bounds.getMinimum());
-        convertPosition(bounds.getMaximum());
-        return;
-    }
-}
-
 }
diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp
index 3e63b4c936..70ec304102 100644
--- a/components/terrain/world.hpp
+++ b/components/terrain/world.hpp
@@ -22,14 +22,24 @@
 #ifndef COMPONENTS_TERRAIN_WORLD_H
 #define COMPONENTS_TERRAIN_WORLD_H
 
-#include <OgreVector3.h>
+#include <osg/ref_ptr>
 
 #include "defs.hpp"
 #include "buffercache.hpp"
 
-namespace Ogre
+namespace osg
 {
-    class SceneManager;
+    class Group;
+}
+
+namespace osgUtil
+{
+    class IncrementalCompileOperation;
+}
+
+namespace Resource
+{
+    class ResourceSystem;
 }
 
 namespace Terrain
@@ -44,79 +54,31 @@ namespace Terrain
     {
     public:
         /// @note takes ownership of \a storage
-        /// @param sceneMgr scene manager to use
         /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..)
-        /// @param visbilityFlags visibility flags for the created meshes
-        /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually
-        ///         faster so this is just here for compatibility.
-        /// @param align The align of the terrain, see Alignment enum
-        World(Ogre::SceneManager* sceneMgr,
-                Storage* storage, int visiblityFlags, bool shaders, Alignment align);
+        /// @param nodeMask mask for the terrain root
+        World(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico,
+              Storage* storage, int nodeMask);
         virtual ~World();
 
-        bool getShadersEnabled() { return mShaders; }
-        bool getShadowsEnabled() { return mShadows; }
-        bool getSplitShadowsEnabled() { return mSplitShadows; }
-
-        float getHeightAt (const Ogre::Vector3& worldPos);
-
-        /// Update chunk LODs according to this camera position
-        /// @note Calling this method might lead to composite textures being rendered, so it is best
-        /// not to call it when render commands are still queued, since that would cause a flush.
-        virtual void update (const Ogre::Vector3& cameraPos) = 0;
+        float getHeightAt (const osg::Vec3f& worldPos);
 
         // This is only a hint and may be ignored by the implementation.
         virtual void loadCell(int x, int y) {}
         virtual void unloadCell(int x, int y) {}
 
-        /// Get the world bounding box of a chunk of terrain centered at \a center
-        virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center) = 0;
-
-        Ogre::SceneManager* getSceneManager() { return mSceneMgr; }
-
         Storage* getStorage() { return mStorage; }
 
-        /// Show or hide the whole terrain
-        /// @note this setting may be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden
-        virtual void setVisible(bool visible) = 0;
-        virtual bool getVisible() = 0;
-
-        /// Recreate materials used by terrain chunks. This should be called whenever settings of
-        /// the material factory are changed. (Relying on the factory to update those materials is not
-        /// enough, since turning a feature on/off can change the number of texture units available for layer/blend
-        /// textures, and to properly respond to this we may need to change the structure of the material, such as
-        /// adding or removing passes. This can only be achieved by a full rebuild.)
-        virtual void applyMaterials(bool shadows, bool splitShadows) = 0;
-
-        int getVisibilityFlags() { return mVisibilityFlags; }
-
-        Alignment getAlign() { return mAlign; }
-
-        /// Wait until all background loading is complete.
-        virtual void syncLoad() {}
-
     protected:
-        bool mShaders;
-        bool mShadows;
-        bool mSplitShadows;
-        Alignment mAlign;
-
         Storage* mStorage;
 
-        int mVisibilityFlags;
-
-        Ogre::SceneManager* mSceneMgr;
-
         BufferCache mCache;
 
-    public:
-        // ----INTERNAL----
-        BufferCache& getBufferCache() { return mCache; }
+        osg::ref_ptr<osg::Group> mParent;
+        osg::ref_ptr<osg::Group> mTerrainRoot;
 
-        // Convert the given position from Z-up align, i.e. Align_XY to the wanted align set in mAlign
-        void convertPosition (float& x, float& y, float& z);
-        void convertPosition (Ogre::Vector3& pos);
-        void convertBounds (Ogre::AxisAlignedBox& bounds);
+        Resource::ResourceSystem* mResourceSystem;
+
+        osg::ref_ptr<osgUtil::IncrementalCompileOperation> mIncrementalCompileOperation;
     };
 
 }