Browse Source

Initial terrain LOD. No stitching yet.
Limit DebugRenderer total vertices to prevent possible crash.
Perform coarse culling in DebugRenderer to avoid rendering lines outside the view.

Lasse Öörni 13 years ago
parent
commit
ec8394b3de

+ 10 - 8
Docs/ScriptAPI.dox

@@ -1680,6 +1680,13 @@ Properties:<br>
 - RenderSurface@[] renderSurface (readonly)
 
 
+BiasParameters
+
+Properties:<br>
+- float constantBias
+- float slopeScaledBias
+
+
 Pass
 
 Properties:<br>
@@ -1733,6 +1740,7 @@ Properties:<br>
 - bool occlusion (readonly)
 - CullMode cullMode
 - CullMode shadowCullMode
+- BiasParameters depthBias
 
 
 PostProcessPass
@@ -1822,13 +1830,6 @@ Properties:<br>
 - BoundingBox worldBoundingBox (readonly)
 
 
-BiasParameters
-
-Properties:<br>
-- float constantBias
-- float slopeScaledBias
-
-
 CascadeParameters
 
 Properties:<br>
@@ -2291,7 +2292,7 @@ Methods:<br>
 - void Remove()
 - void MarkNetworkUpdate() const
 - void DrawDebugGeometry(DebugRenderer@, bool)
-- bool AddDecal(Drawable@, const Vector3&, const Quaternion&, float, float, float, const Vector2&, const Vector2&, float arg8 = 0.0, float arg9 = 0.1, float arg10 = 0.001, uint arg11 = 0xffffffff)
+- bool AddDecal(Drawable@, const Vector3&, const Quaternion&, float, float, float, const Vector2&, const Vector2&, float arg8 = 0.0, float arg9 = 0.1, uint arg10 = 0xffffffff)
 - void RemoveDecals(uint)
 - void RemoveAllDecals()
 
@@ -2378,6 +2379,7 @@ Methods:<br>
 - void MarkNetworkUpdate() const
 - void DrawDebugGeometry(DebugRenderer@, bool)
 - float GetHeight(const Vector3&) const
+- TerrainPatch@ GetPatch(int, int) const
 
 Properties:<br>
 - ShortStringHash type (readonly)

+ 2 - 1
Engine/Engine/GraphicsAPI.cpp

@@ -778,6 +778,7 @@ static void RegisterTerrain(asIScriptEngine* engine)
     RegisterDrawable<TerrainPatch>(engine, "TerrainPatch");
     RegisterComponent<Terrain>(engine, "Terrain");
     engine->RegisterObjectMethod("Terrain", "float GetHeight(const Vector3&in) const", asMETHOD(Terrain, GetHeight), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Terrain", "TerrainPatch@+ GetPatch(int, int) const", asMETHODPR(Terrain, GetPatch, (int, int) const, TerrainPatch*), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "void set_material(Material@+)", asMETHOD(Terrain, SetMaterial), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "Material@+ get_material() const", asMETHOD(Terrain, GetMaterial), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "void set_heightMap(Image@+)", asMETHOD(Terrain, SetHeightMap), asCALL_THISCALL);
@@ -788,7 +789,7 @@ static void RegisterTerrain(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Terrain", "const Vector3& get_spacing() const", asMETHOD(Terrain, GetSpacing), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "const IntVector2& get_numVertices() const", asMETHOD(Terrain, GetNumVertices), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "const IntVector2& get_numPatches() const", asMETHOD(Terrain, GetNumPatches), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Terrain", "TerrainPatch@+ get_patches(uint) const", asMETHOD(Terrain, GetPatch), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Terrain", "TerrainPatch@+ get_patches(uint) const", asMETHODPR(Terrain, GetPatch, (unsigned) const, TerrainPatch*), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "void set_visible(bool)", asMETHOD(Terrain, SetVisible), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "bool get_visible() const", asMETHOD(Terrain, IsVisible), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "void set_castShadows(bool)", asMETHOD(Terrain, SetCastShadows), asCALL_THISCALL);

+ 81 - 113
Engine/Graphics/DebugRenderer.cpp

@@ -39,6 +39,9 @@
 
 #include "DebugNew.h"
 
+// Cap the amount of lines to prevent crash when eg. debug rendering large heightfields
+static const unsigned MAX_LINES = 1000000;
+
 OBJECTTYPESTATIC(DebugRenderer);
 
 DebugRenderer::DebugRenderer(Context* context) :
@@ -70,6 +73,17 @@ void DebugRenderer::SetView(Camera* camera)
 
 void DebugRenderer::AddLine(const Vector3& start, const Vector3& end, const Color& color, bool depthTest)
 {
+    if (lines_.Size() + noDepthLines_.Size() >= MAX_LINES)
+        return;
+    
+    // Perform sphere culling to reject lines outside the view
+    /// \todo This is slow to do on a per line basis
+    Vector3 center = (start + end) * 0.5f;
+    Vector3 extent = end - center;
+    Sphere sphere(center, extent.Length());
+    if (frustum_.IsInsideFast(sphere) == OUTSIDE)
+        return;
+    
     if (depthTest)
         lines_.Push(DebugLine(start, end, color.ToUInt()));
     else
@@ -78,6 +92,15 @@ void DebugRenderer::AddLine(const Vector3& start, const Vector3& end, const Colo
 
 void DebugRenderer::AddLine(const Vector3& start, const Vector3& end, unsigned color, bool depthTest)
 {
+    if (lines_.Size() + noDepthLines_.Size() >= MAX_LINES)
+        return;
+    
+    Vector3 center = (start + end) * 0.5f;
+    Vector3 extent = end - center;
+    Sphere sphere(center, extent.Length());
+    if (frustum_.IsInsideFast(sphere) == OUTSIDE)
+        return;
+    
     if (depthTest)
         lines_.Push(DebugLine(start, end, color));
     else
@@ -92,18 +115,9 @@ void DebugRenderer::AddNode(Node* node, float scale, bool depthTest)
     Vector3 start = node->GetWorldPosition();
     Quaternion rotation = node->GetWorldRotation();
     
-    if (depthTest)
-    {
-        lines_.Push(DebugLine(start, start + rotation * (scale * Vector3::RIGHT), Color::RED.ToUInt()));
-        lines_.Push(DebugLine(start, start + rotation * (scale * Vector3::UP), Color::GREEN.ToUInt()));
-        lines_.Push(DebugLine(start, start + rotation * (scale * Vector3::FORWARD), Color::BLUE.ToUInt()));
-    }
-    else
-    {
-        noDepthLines_.Push(DebugLine(start, start + rotation * (scale * Vector3::RIGHT), Color::RED.ToUInt()));
-        noDepthLines_.Push(DebugLine(start, start + rotation * (scale * Vector3::UP), Color::GREEN.ToUInt()));
-        noDepthLines_.Push(DebugLine(start, start + rotation * (scale * Vector3::FORWARD), Color::BLUE.ToUInt()));
-    }
+    AddLine(start, start + rotation * (scale * Vector3::RIGHT), Color::RED.ToUInt(), depthTest);
+    AddLine(start, start + rotation * (scale * Vector3::UP), Color::GREEN.ToUInt(), depthTest);
+    AddLine(start, start + rotation * (scale * Vector3::FORWARD), Color::BLUE.ToUInt(), depthTest);
 }
 
 void DebugRenderer::AddBoundingBox(const BoundingBox& box, const Color& color, bool depthTest)
@@ -120,22 +134,18 @@ void DebugRenderer::AddBoundingBox(const BoundingBox& box, const Color& color, b
     
     unsigned uintColor = color.ToUInt();
     
-    PODVector<DebugLine>* dest = &lines_;
-    if (!depthTest)
-        dest = &noDepthLines_;
-    
-    dest->Push(DebugLine(min, v1, uintColor));
-    dest->Push(DebugLine(v1, v2, uintColor));
-    dest->Push(DebugLine(v2, v3, uintColor));
-    dest->Push(DebugLine(v3, min, uintColor));
-    dest->Push(DebugLine(v4, v5, uintColor));
-    dest->Push(DebugLine(v5, max, uintColor));
-    dest->Push(DebugLine(max, v6, uintColor));
-    dest->Push(DebugLine(v6, v4, uintColor));
-    dest->Push(DebugLine(min, v4, uintColor));
-    dest->Push(DebugLine(v1, v5, uintColor));
-    dest->Push(DebugLine(v2, max, uintColor));
-    dest->Push(DebugLine(v3, v6, uintColor));
+    AddLine(min, v1, uintColor, depthTest);
+    AddLine(v1, v2, uintColor, depthTest);
+    AddLine(v2, v3, uintColor, depthTest);
+    AddLine(v3, min, uintColor, depthTest);
+    AddLine(v4, v5, uintColor, depthTest);
+    AddLine(v5, max, uintColor, depthTest);
+    AddLine(max, v6, uintColor, depthTest);
+    AddLine(v6, v4, uintColor, depthTest);
+    AddLine(min, v4, uintColor, depthTest);
+    AddLine(v1, v5, uintColor, depthTest);
+    AddLine(v2, max, uintColor, depthTest);
+    AddLine(v3, v6, uintColor, depthTest);
 }
 
 void DebugRenderer::AddBoundingBox(const BoundingBox& box, const Matrix3x4& transform, const Color& color, bool depthTest)
@@ -154,22 +164,18 @@ void DebugRenderer::AddBoundingBox(const BoundingBox& box, const Matrix3x4& tran
     
     unsigned uintColor = color.ToUInt();
     
-    PODVector<DebugLine>* dest = &lines_;
-    if (!depthTest)
-        dest = &noDepthLines_;
-    
-    dest->Push(DebugLine(v0, v1, uintColor));
-    dest->Push(DebugLine(v1, v2, uintColor));
-    dest->Push(DebugLine(v2, v3, uintColor));
-    dest->Push(DebugLine(v3, v0, uintColor));
-    dest->Push(DebugLine(v4, v5, uintColor));
-    dest->Push(DebugLine(v5, v7, uintColor));
-    dest->Push(DebugLine(v7, v6, uintColor));
-    dest->Push(DebugLine(v6, v4, uintColor));
-    dest->Push(DebugLine(v0, v4, uintColor));
-    dest->Push(DebugLine(v1, v5, uintColor));
-    dest->Push(DebugLine(v2, v7, uintColor));
-    dest->Push(DebugLine(v3, v6, uintColor));
+    AddLine(v0, v1, uintColor, depthTest);
+    AddLine(v1, v2, uintColor, depthTest);
+    AddLine(v2, v3, uintColor, depthTest);
+    AddLine(v3, v0, uintColor, depthTest);
+    AddLine(v4, v5, uintColor, depthTest);
+    AddLine(v5, v7, uintColor, depthTest);
+    AddLine(v7, v6, uintColor, depthTest);
+    AddLine(v6, v4, uintColor, depthTest);
+    AddLine(v0, v4, uintColor, depthTest);
+    AddLine(v1, v5, uintColor, depthTest);
+    AddLine(v2, v7, uintColor, depthTest);
+    AddLine(v3, v6, uintColor, depthTest);
 }
 
 
@@ -178,39 +184,31 @@ void DebugRenderer::AddFrustum(const Frustum& frustum, const Color& color, bool
     const Vector3* vertices = frustum.vertices_;
     unsigned uintColor = color.ToUInt();
     
-    PODVector<DebugLine>* dest = &lines_;
-    if (!depthTest)
-        dest = &noDepthLines_;
-    
-    dest->Push(DebugLine(vertices[0], vertices[1], uintColor));
-    dest->Push(DebugLine(vertices[1], vertices[2], uintColor));
-    dest->Push(DebugLine(vertices[2], vertices[3], uintColor));
-    dest->Push(DebugLine(vertices[3], vertices[0], uintColor));
-    dest->Push(DebugLine(vertices[4], vertices[5], uintColor));
-    dest->Push(DebugLine(vertices[5], vertices[6], uintColor));
-    dest->Push(DebugLine(vertices[6], vertices[7], uintColor));
-    dest->Push(DebugLine(vertices[7], vertices[4], uintColor));
-    dest->Push(DebugLine(vertices[0], vertices[4], uintColor));
-    dest->Push(DebugLine(vertices[1], vertices[5], uintColor));
-    dest->Push(DebugLine(vertices[2], vertices[6], uintColor));
-    dest->Push(DebugLine(vertices[3], vertices[7], uintColor));
+    AddLine(vertices[0], vertices[1], uintColor, depthTest);
+    AddLine(vertices[1], vertices[2], uintColor, depthTest);
+    AddLine(vertices[2], vertices[3], uintColor, depthTest);
+    AddLine(vertices[3], vertices[0], uintColor, depthTest);
+    AddLine(vertices[4], vertices[5], uintColor, depthTest);
+    AddLine(vertices[5], vertices[6], uintColor, depthTest);
+    AddLine(vertices[6], vertices[7], uintColor, depthTest);
+    AddLine(vertices[7], vertices[4], uintColor, depthTest);
+    AddLine(vertices[0], vertices[4], uintColor, depthTest);
+    AddLine(vertices[1], vertices[5], uintColor, depthTest);
+    AddLine(vertices[2], vertices[6], uintColor, depthTest);
+    AddLine(vertices[3], vertices[7], uintColor, depthTest);
 }
 
 void DebugRenderer::AddPolyhedron(const Polyhedron& poly, const Color& color, bool depthTest)
 {
     unsigned uintColor = color.ToUInt();
     
-    PODVector<DebugLine>* dest = &lines_;
-    if (!depthTest)
-        dest = &noDepthLines_;
-    
     for (unsigned i = 0; i < poly.faces_.Size(); ++i)
     {
         const PODVector<Vector3>& face = poly.faces_[i];
         if (face.Size() >= 3)
         {
             for (unsigned j = 0; j < face.Size(); ++j)
-                dest->Push(DebugLine(face[j], face[(j + 1) % face.Size()], uintColor));
+                AddLine(face[j], face[(j + 1) % face.Size()], uintColor, depthTest);
         }
     }
 }
@@ -248,12 +246,7 @@ void DebugRenderer::AddSkeleton(const Skeleton& skeleton, const Color& color, bo
     if (!bones.Size())
         return;
     
-    DebugLine newLine;
-    newLine.color_ = color.ToUInt();
-    
-    PODVector<DebugLine>* dest = &lines_;
-    if (!depthTest)
-        dest = &noDepthLines_;
+    unsigned uintColor = color.ToUInt();
     
     for (unsigned i = 0; i < bones.Size(); ++i)
     {
@@ -265,18 +258,19 @@ void DebugRenderer::AddSkeleton(const Skeleton& skeleton, const Color& color, bo
         if (!boneNode)
             continue;
         
-        newLine.start_ = boneNode->GetWorldPosition();
+        Vector3 start = boneNode->GetWorldPosition();
+        Vector3 end;
         
         unsigned j = bones[i].parentIndex_;
         Node* parentNode = boneNode->GetParent();
         
         // If bone has a parent defined, and it also skins geometry, draw a line to it. Else draw the bone as a point
         if (parentNode && (bones[j].radius_ >= M_EPSILON || bones[j].boundingBox_.Size().LengthSquared() >= M_EPSILON))
-            newLine.end_ = parentNode->GetWorldPosition();
+            end = parentNode->GetWorldPosition();
         else
-            newLine.end_ = newLine.start_;
+            end = start;
         
-        dest->Push(newLine);
+        AddLine(start, end, uintColor, depthTest);
     }
 }
 
@@ -284,15 +278,7 @@ void DebugRenderer::AddTriangleMesh(const void* vertexData, unsigned vertexSize,
     unsigned indexStart, unsigned indexCount, const Matrix3x4& transform, const Color& color, bool depthTest)
 {
     unsigned uintColor = color.ToUInt();
-    
-    PODVector<DebugLine>* dest = &lines_;
-    if (!depthTest)
-        dest = &noDepthLines_;
-    
     const unsigned char* srcData = (const unsigned char*)vertexData;
-    unsigned oldSize = dest->Size();
-    dest->Resize(oldSize + indexCount);
-    DebugLine* destLines = &dest->At(oldSize);
     
     // 16-bit indices
     if (indexSize == sizeof(unsigned short))
@@ -302,24 +288,15 @@ void DebugRenderer::AddTriangleMesh(const void* vertexData, unsigned vertexSize,
         
         while (indices < indicesEnd)
         {
-            const Vector3& v0 = *((const Vector3*)(&srcData[indices[0] * vertexSize]));
-            const Vector3& v1 = *((const Vector3*)(&srcData[indices[1] * vertexSize]));
-            const Vector3& v2 = *((const Vector3*)(&srcData[indices[2] * vertexSize]));
-            
-            destLines[0].start_ = transform * v0;
-            destLines[0].end_ = transform * v1;
-            destLines[0].color_ = uintColor;
+            Vector3 v0 = transform * *((const Vector3*)(&srcData[indices[0] * vertexSize]));
+            Vector3 v1 = transform * *((const Vector3*)(&srcData[indices[1] * vertexSize]));
+            Vector3 v2 = transform * *((const Vector3*)(&srcData[indices[2] * vertexSize]));
             
-            destLines[1].start_ = destLines[0].end_;
-            destLines[1].end_ = transform * v2;
-            destLines[1].color_ = uintColor;
-            
-            destLines[2].start_ = destLines[1].end_;
-            destLines[2].end_ = destLines[0].start_;
-            destLines[2].color_ = uintColor;
+            AddLine(v0, v1, uintColor, depthTest);
+            AddLine(v1, v2, uintColor, depthTest);
+            AddLine(v2, v0, uintColor, depthTest);
             
             indices += 3;
-            destLines += 3;
         }
     }
     else
@@ -329,24 +306,15 @@ void DebugRenderer::AddTriangleMesh(const void* vertexData, unsigned vertexSize,
         
         while (indices < indicesEnd)
         {
-            const Vector3& v0 = *((const Vector3*)(&srcData[indices[0] * vertexSize]));
-            const Vector3& v1 = *((const Vector3*)(&srcData[indices[1] * vertexSize]));
-            const Vector3& v2 = *((const Vector3*)(&srcData[indices[2] * vertexSize]));
-            
-            destLines[0].start_ = transform * v0;
-            destLines[0].end_ = transform * v1;
-            destLines[0].color_ = uintColor;
-            
-            destLines[1].start_ = destLines[0].end_;
-            destLines[1].end_ = transform * v2;
-            destLines[1].color_ = uintColor;
+            Vector3 v0 = transform * *((const Vector3*)(&srcData[indices[0] * vertexSize]));
+            Vector3 v1 = transform * *((const Vector3*)(&srcData[indices[1] * vertexSize]));
+            Vector3 v2 = transform * *((const Vector3*)(&srcData[indices[2] * vertexSize]));
             
-            destLines[2].start_ = destLines[1].end_;
-            destLines[2].end_ = destLines[0].start_;
-            destLines[2].color_ = uintColor;
+            AddLine(v0, v1, uintColor, depthTest);
+            AddLine(v1, v2, uintColor, depthTest);
+            AddLine(v2, v0, uintColor, depthTest);
             
             indices += 3;
-            destLines += 3;
         }
     }
 }

+ 189 - 42
Engine/Graphics/Terrain.cpp

@@ -45,8 +45,7 @@
 OBJECTTYPESTATIC(Terrain);
 
 static const Vector3 DEFAULT_SPACING(1.0f, 0.25f, 1.0f);
-static const unsigned DEFAULT_LOD_LEVELS = 3;
-static const unsigned MAX_LOD_LEVELS = 4;
+static const unsigned MAX_LOD_LEVELS = 8;
 static const int DEFAULT_PATCH_SIZE = 16;
 static const int MIN_PATCH_SIZE = 4;
 static const int MAX_PATCH_SIZE = 128;
@@ -59,7 +58,7 @@ Terrain::Terrain(Context* context) :
     patchWorldOrigin_(Vector2::ZERO),
     numVertices_(IntVector2::ZERO),
     numPatches_(IntVector2::ZERO),
-    numLodLevels_(DEFAULT_LOD_LEVELS),
+    numLodLevels_(1),
     patchSize_(DEFAULT_PATCH_SIZE),
     visible_(true),
     castShadows_(false),
@@ -326,6 +325,14 @@ TerrainPatch* Terrain::GetPatch(unsigned index) const
     return index < patches_.Size() ? patches_[index] : (TerrainPatch*)0;
 }
 
+TerrainPatch* Terrain::GetPatch(int x, int z) const
+{
+    if (x < 0 || x >= numPatches_.x_ || z < 0 || z >= numPatches_.y_)
+        return 0;
+    else
+        return GetPatch(z * numPatches_.x_ + x);
+}
+
 float Terrain::GetHeight(const Vector3& worldPosition) const
 {
     if (node_)
@@ -360,7 +367,7 @@ float Terrain::GetHeight(const Vector3& worldPosition) const
         return 0.0f;
 }
 
-void Terrain::UpdatePatchGeometry(TerrainPatch* patch)
+void Terrain::CreatePatchGeometry(TerrainPatch* patch)
 {
     unsigned vertexDataRow = patchSize_ + 1;
     VertexBuffer* vertexBuffer = patch->GetVertexBuffer();
@@ -423,16 +430,26 @@ void Terrain::UpdatePatchGeometry(TerrainPatch* patch)
     }
     
     patch->SetBoundingBox(box);
-    geometry->SetIndexBuffer(indexBuffer_);
-    geometry->SetDrawRange(TRIANGLE_LIST, 0, indexBuffer_->GetIndexCount());
-    geometry->SetRawVertexData(cpuVertexData, sizeof(Vector3), MASK_POSITION);
-    maxLodGeometry->SetIndexBuffer(indexBuffer_);
-    maxLodGeometry->SetDrawRange(TRIANGLE_LIST, 0, indexBuffer_->GetIndexCount());
-    maxLodGeometry->SetRawVertexData(cpuVertexData, sizeof(Vector3), MASK_POSITION);
+    
+    if (drawRanges_.Size())
+    {
+        patch->ResetLod();
+        geometry->SetIndexBuffer(indexBuffer_);
+        geometry->SetDrawRange(TRIANGLE_LIST, drawRanges_[0].first_, drawRanges_[0].second_);
+        geometry->SetRawVertexData(cpuVertexData, sizeof(Vector3), MASK_POSITION);
+        maxLodGeometry->SetIndexBuffer(indexBuffer_);
+        maxLodGeometry->SetDrawRange(TRIANGLE_LIST, drawRanges_[0].first_, drawRanges_[0].second_);
+        maxLodGeometry->SetRawVertexData(cpuVertexData, sizeof(Vector3), MASK_POSITION);
+    }
 }
 
 void Terrain::UpdatePatchLod(TerrainPatch* patch)
 {
+    /// \todo Use stitching
+    unsigned lodLevel = patch->GetLodLevel();
+    Geometry* geometry = patch->GetGeometry();
+    if (lodLevel < drawRanges_.Size())
+        geometry->SetDrawRange(TRIANGLE_LIST, drawRanges_[lodLevel].first_, drawRanges_[lodLevel].second_);
 }
 
 void Terrain::SetMaterialAttr(ResourceRef value)
@@ -580,33 +597,15 @@ void Terrain::CreateGeometry()
         }
         
         // Create the shared index data
-        /// \todo Create LOD levels
-        indexBuffer_->SetSize(patchSize_ * patchSize_ * 6, false);
-        unsigned vertexDataRow = patchSize_ + 1;
-        unsigned short* indexData = (unsigned short*)indexBuffer_->Lock(0, indexBuffer_->GetIndexCount());
-        
-        if (indexData)
-        {
-            for (int z = 0; z < patchSize_; ++z)
-            {
-                for (int x = 0; x < patchSize_; ++x)
-                {
-                    *indexData++ = x + (z + 1) * vertexDataRow;
-                    *indexData++ = x + z * vertexDataRow + 1;
-                    *indexData++ = x + z * vertexDataRow;
-                    
-                    *indexData++ = x + (z + 1) * vertexDataRow;
-                    *indexData++ = x + (z + 1) * vertexDataRow + 1;
-                    *indexData++ = x + z * vertexDataRow + 1;
-                }
-            }
-            
-            indexBuffer_->Unlock();
-        }
+        CreateIndexData();
         
         // Create vertex data for patches
         for (Vector<WeakPtr<TerrainPatch> >::Iterator i = patches_.Begin(); i != patches_.End(); ++i)
-            UpdatePatchGeometry(*i);
+        {
+            CreatePatchGeometry(*i);
+            CalculateLodErrors(*i);
+            SetNeighbors(*i);
+        }
     }
     
     // Send event only if new geometry was generated, or the old was cleared
@@ -620,6 +619,52 @@ void Terrain::CreateGeometry()
     }
 }
 
+void Terrain::CreateIndexData()
+{
+    /// \todo Create stitching combinations
+    unsigned totalIndexSize = 0;
+    unsigned vertexDataRow = patchSize_ + 1;
+    
+    for (unsigned i = 0; i < numLodLevels_; ++i)
+    {
+        unsigned lodPatchSize = patchSize_ >> i;
+        totalIndexSize += lodPatchSize * lodPatchSize * 6;
+    }
+    
+    drawRanges_.Clear();
+    indexBuffer_->SetSize(totalIndexSize, false);
+    unsigned short* indexData = (unsigned short*)indexBuffer_->Lock(0, indexBuffer_->GetIndexCount());
+    
+    if (indexData)
+    {
+        unsigned index = 0;
+        
+        for (unsigned i = 0; i < numLodLevels_; ++i)
+        {
+            unsigned indexStart = index;
+            int skip = 1 << i;
+            
+            for (int z = 0; z < patchSize_; z += skip)
+            {
+                for (int x = 0; x < patchSize_; x += skip)
+                {
+                    *indexData++ = x + (z + skip) * vertexDataRow;
+                    *indexData++ = x + z * vertexDataRow + skip;
+                    *indexData++ = x + z * vertexDataRow;
+                    *indexData++ = x + (z + skip) * vertexDataRow;
+                    *indexData++ = x + (z + skip) * vertexDataRow + skip;
+                    *indexData++ = x + z * vertexDataRow + skip;
+                    index += 6;
+                }
+            }
+            
+            drawRanges_.Push(MakePair(indexStart, index - indexStart));
+        }
+        
+        indexBuffer_->Unlock();
+    }
+}
+
 float Terrain::GetRawHeight(int x, int z) const
 {
     if (!heightData_)
@@ -630,6 +675,34 @@ float Terrain::GetRawHeight(int x, int z) const
     return heightData_[z * numVertices_.x_ + x];
 }
 
+float Terrain::GetLodHeight(float x, float z, unsigned lodLevel) const
+{
+    unsigned offset = 1 << lodLevel;
+    unsigned xPos = (unsigned)x;
+    unsigned zPos = (unsigned)z;
+    float divisor = (float)offset;
+    float xFrac = (x / divisor) - floorf(x / divisor);
+    float zFrac = (z / divisor) - floorf(z / divisor);
+    float h1, h2, h3;
+    
+    if (xFrac + zFrac >= 1.0f)
+    {
+        h1 = GetRawHeight(xPos + offset, zPos + offset);
+        h2 = GetRawHeight(xPos, zPos + offset);
+        h3 = GetRawHeight(xPos + offset, zPos);
+        xFrac = 1.0f - xFrac;
+        zFrac = 1.0f - zFrac;
+    }
+    else
+    {
+        h1 = GetRawHeight(xPos, zPos);
+        h2 = GetRawHeight(xPos + offset, zPos);
+        h3 = GetRawHeight(xPos, zPos + offset);
+    }
+    
+    return h1 * (1.0f - xFrac - zFrac) + h2 * xFrac + h3 * zFrac;
+}
+
 Vector3 Terrain::GetNormal(int x, int z) const
 {
     float baseHeight = GetRawHeight(x, z);
@@ -641,15 +714,89 @@ Vector3 Terrain::GetNormal(int x, int z) const
     float swSlope = GetRawHeight(x - 1, z + 1) - baseHeight;
     float wSlope = GetRawHeight(x - 1, z) - baseHeight;
     float nwSlope = GetRawHeight(x - 1, z - 1) - baseHeight;
+    float up = 0.5f * (spacing_.x_ + spacing_.z_);
+    
+    return (Vector3(0.0f, up, nSlope) +
+        Vector3(-neSlope, up, neSlope) +
+        Vector3(-eSlope, up, 0.0f) +
+        Vector3(-seSlope, up, -seSlope) +
+        Vector3(0.0f, up, -sSlope) +
+        Vector3(swSlope, up, -swSlope) + 
+        Vector3(wSlope, up, 0.0f) +
+        Vector3(nwSlope, up, nwSlope)).Normalized();
+}
+
+void Terrain::CalculateLodErrors(TerrainPatch* patch)
+{
+    PROFILE(CalculateLodErrors);
+    
+    const IntVector2& coords = patch->GetCoordinates();
+    PODVector<float>& lodErrors = patch->GetLodErrors();
+    lodErrors.Clear();
+    
+    int xStart = coords.x_ * patchSize_;
+    int zStart = coords.y_ * patchSize_;
+    int xEnd = xStart + patchSize_;
+    int zEnd = zStart + patchSize_;
+    
+    for (unsigned i = 0; i < numLodLevels_; ++i)
+    {
+        float maxError = 0.0f;
+        int divisor = 1 << i;
+        
+        if (i > 0)
+        {
+            for (int z = zStart; z <= zEnd; ++z)
+            {
+                for (int x = xStart; x <= xEnd; ++x)
+                {
+                    if (x % divisor || z % divisor)
+                    {
+                        float error = Abs(GetLodHeight((float)x, (float)z, i) - GetRawHeight(x, z));
+                        maxError = Max(error, maxError);
+                    }
+                }
+            }
+            
+            // Set error metric always at least same as vertex spacing to prevent horizontal stretches getting too low LOD
+            maxError = Max(maxError, 0.5f * (spacing_.x_ + spacing_.z_));
+        }
+        
+        lodErrors.Push(maxError);
+    }
+}
+
+void Terrain::SetNeighbors(TerrainPatch* patch)
+{
+    const IntVector2& coords = patch->GetCoordinates();
+    patch->SetNeighbors(GetPatch(coords.x_, coords.y_ + 1), GetPatch(coords.x_, coords.y_ - 1),
+        GetPatch(coords.x_ - 1, coords.y_), GetPatch(coords.x_ + 1, coords.y_));
+}
+
+unsigned Terrain::GetDrawRangeIndex(unsigned lod, unsigned northLod, unsigned southLod, unsigned westLod, unsigned eastLod)
+{
+    /*
+    // If neighbor patches have more accurate LOD, no need to perform stitching
+    northLod = Max((int)lod, (int)northLod);
+    southLod = Max((int)lod, (int)southLod);
+    westLod = Max((int)lod, (int)westLod);
+    eastLod = Max((int)lod, (int)eastLod);
+    
+    unsigned index = 0;
+    
+    // Skip higher LOD levels
+    for (unsigned i = 0; i < lod; ++i)
+        index += 16;
+    
+    // Each LOD level can stitch to max. 1 level coarser LOD to reduce amount of draw range combinations
+    index += northLod > lod ? 1 : 0;
+    index += southLod > lod ? 2 : 0;
+    index += westLod > lod ? 4 : 0;
+    index += eastLod > lod ? 8 : 0;
     
-    return (Vector3(0.0f, 1.0f, nSlope) +
-        Vector3(-neSlope, 1.0f, neSlope) +
-        Vector3(-eSlope, 1.0f, 0.0f) +
-        Vector3(-seSlope, 1.0f, -seSlope) +
-        Vector3(0.0f, 1.0f, -sSlope) +
-        Vector3(swSlope, 1.0f, -swSlope) + 
-        Vector3(wSlope, 1.0f, 0.0f) +
-        Vector3(nwSlope, 1.0f, nwSlope)).Normalized();
+    return index;
+    */
+    return lod;
 }
 
 bool Terrain::SetHeightMapInternal(Image* image, bool recreateNow)

+ 15 - 1
Engine/Graphics/Terrain.h

@@ -95,6 +95,8 @@ public:
     Material* GetMaterial() const;
     /// Return patch by index.
     TerrainPatch* GetPatch(unsigned index) const;
+    /// Return patch by patch coordinates.
+    TerrainPatch* GetPatch(int x, int z) const;
     /// Return height at world coordinates.
     float GetHeight(const Vector3& worldPosition) const;
     /// Return raw height data.
@@ -125,7 +127,7 @@ public:
     bool IsOccludee() const { return occludee_; }
     
     /// Regenerate patch geometry.
-    void UpdatePatchGeometry(TerrainPatch* patch);
+    void CreatePatchGeometry(TerrainPatch* patch);
     /// Update patch based on LOD and neighbor LOD.
     void UpdatePatchLod(TerrainPatch* patch);
     /// %Set heightmap attribute.
@@ -142,10 +144,20 @@ public:
 private:
     /// Fully regenerate terrain geometry.
     void CreateGeometry();
+    /// Create index data shared by all patches.
+    void CreateIndexData();
     /// Return an uninterpolated terrain height value, clamping to edges.
     float GetRawHeight(int x, int z) const;
+    /// Return interpolated height for a specific LOD level.
+    float GetLodHeight(float x, float z, unsigned lodLevel) const;
     /// Get terrain normal at position.
     Vector3 GetNormal(int x, int z) const;
+    /// Calculate LOD errors for a patch.
+    void CalculateLodErrors(TerrainPatch* patch);
+    /// Set neighbors for a patch.
+    void SetNeighbors(TerrainPatch* patch);
+    /// Get draw range index for a given LOD and neighbor LODs.
+    unsigned GetDrawRangeIndex(unsigned lod, unsigned northLod, unsigned southLod, unsigned westLod, unsigned eastLod);
     /// Set heightmap image and optionally recreate the geometry immediately. Return true if successful.
     bool SetHeightMapInternal(Image* image, bool recreateNow);
     /// Handle heightmap image reload finished.
@@ -161,6 +173,8 @@ private:
     SharedPtr<Material> material_;
     /// Terrain patches.
     Vector<WeakPtr<TerrainPatch> > patches_;
+    /// Draw ranges for different LODs and stitching combinations.
+    PODVector<Pair<unsigned, unsigned> > drawRanges_;
     /// Vertex and height spacing.
     Vector3 spacing_;
     /// Origin of patches on the XZ-plane.

+ 21 - 5
Engine/Graphics/TerrainPatch.cpp

@@ -37,7 +37,7 @@
 #include "DebugNew.h"
 
 static const Vector3 DOT_SCALE(1 / 3.0f, 1 / 3.0f, 1 / 3.0f);
-static const float LOD_CONSTANT = 1.0f;
+static const float LOD_CONSTANT = 8.0f;
 
 OBJECTTYPESTATIC(TerrainPatch);
 
@@ -48,7 +48,7 @@ TerrainPatch::TerrainPatch(Context* context) :
     vertexBuffer_(new VertexBuffer(context)),
     coordinates_(IntVector2::ZERO),
     lodLevel_(0),
-    lodDirty_(true)
+    lodDirty_(false)
 {
     drawableFlags_ = DRAWABLE_GEOMETRY;
     
@@ -128,7 +128,7 @@ void TerrainPatch::UpdateBatches(const FrameInfo& frame)
     const Matrix3x4& worldTransform = node_->GetWorldTransform();
     distance_ = frame.camera_->GetDistance(GetWorldBoundingBox().Center());
     
-    float scale = GetWorldBoundingBox().Size().DotProduct(DOT_SCALE);
+    float scale = worldTransform.Scale().DotProduct(DOT_SCALE);
     lodDistance_ = frame.camera_->GetLodDistance(distance_, scale, lodBias_);
     
     batches_[0].distance_ = distance_;
@@ -137,12 +137,22 @@ void TerrainPatch::UpdateBatches(const FrameInfo& frame)
     unsigned newLodLevel = 0;
     for (unsigned i = 0; i < lodErrors_.Size(); ++i)
     {
-        if (lodErrors_[i] / lodDistance_ > LOD_CONSTANT)
+        if (lodErrors_[i] / lodDistance_ > LOD_CONSTANT / (float)frame.viewSize_.y_)
             break;
         else
             newLodLevel = i;
     }
     
+    // Check that the LOD is not more than 1 coarser than neighbors
+    if (north_)
+        newLodLevel = Min((int)newLodLevel, north_->GetLodLevel() + 1);
+    if (south_)
+        newLodLevel = Min((int)newLodLevel, south_->GetLodLevel() + 1);
+    if (west_)
+        newLodLevel = Min((int)newLodLevel, west_->GetLodLevel() + 1);
+    if (east_)
+        newLodLevel = Min((int)newLodLevel, east_->GetLodLevel() + 1);
+    
     if (newLodLevel != lodLevel_)
     {
         lodLevel_ = newLodLevel;
@@ -155,7 +165,7 @@ void TerrainPatch::UpdateGeometry(const FrameInfo& frame)
     if (vertexBuffer_->IsDataLost())
     {
         if (owner_)
-            owner_->UpdatePatchGeometry(this);
+            owner_->CreatePatchGeometry(this);
         else
             vertexBuffer_->ClearDataLost();
     }
@@ -259,6 +269,12 @@ void TerrainPatch::SetCoordinates(const IntVector2& coordinates)
     coordinates_ = coordinates;
 }
 
+void TerrainPatch::ResetLod()
+{
+    lodLevel_ = 0;
+    lodDirty_ = false;
+}
+
 Geometry* TerrainPatch::GetGeometry() const
 {
     return geometry_;

+ 6 - 2
Engine/Graphics/TerrainPatch.h

@@ -67,6 +67,8 @@ public:
     void SetBoundingBox(const BoundingBox& box);
     /// Set patch coordinates.
     void SetCoordinates(const IntVector2& coordinates);
+    /// Reset to LOD level 0.
+    void ResetLod();
     
     /// Return visible geometry.
     Geometry* GetGeometry() const;
@@ -84,6 +86,8 @@ public:
     TerrainPatch* GetWestPatch() const { return west_; }
     /// Return east neighbor patch.
     TerrainPatch* GetEastPatch() const { return east_; }
+    /// Return geometrical error array.
+    PODVector<float>& GetLodErrors() { return lodErrors_; }
     /// Return local-space bounding box.
     const BoundingBox& GetBoundingBox() const { return boundingBox_; }
     /// Return patch coordinates.
@@ -112,14 +116,14 @@ private:
     WeakPtr<TerrainPatch> west_;
     /// East neighbor patch.
     WeakPtr<TerrainPatch> east_;
+    /// Geometrical error per LOD level.
+    PODVector<float> lodErrors_;
     /// Local-space bounding box.
     BoundingBox boundingBox_;
     /// Patch coordinates in the terrain. (0,0) is the northwest corner.
     IntVector2 coordinates_;
     /// Current LOD level.
     unsigned lodLevel_;
-    /// Geometrical error per LOD level.
-    PODVector<float> lodErrors_;
     /// LOD level dirty flag.
     bool lodDirty_;
 };