Преглед изворни кода

Merge remote-tracking branch 'upstream/master'

Lumak пре 9 година
родитељ
комит
1a266d6b89

+ 2 - 2
CMake/Modules/FindUrho3D.cmake

@@ -170,7 +170,7 @@ else ()
     set (URHO3D_LIB_TYPE_SAVED ${URHO3D_LIB_TYPE})  # We need this to reset the auto-discovered URHO3D_LIB_TYPE variable before looping
     foreach (ABI_64BIT RANGE ${URHO3D_64BIT} 0)
         # Break if the compiler is not multilib-capable and the ABI is not its native
-        if ((MSVC OR MINGW OR ANDROID OR ARM OR WEB) AND NOT ABI_64BIT EQUAL NATIVE_64BIT)
+        if ((MSVC OR ANDROID OR ARM OR WEB) AND NOT ABI_64BIT EQUAL NATIVE_64BIT)
             break ()
         endif ()
         # Set to search in 'lib' or 'lib64' based on the ABI being tested
@@ -223,7 +223,7 @@ else ()
         if (URHO3D_COMPILE_RESULT)
             break ()    # Use the cached result instead of redoing try_run() each time
         elseif (URHO3D_LIBRARIES)
-            if (NOT (MSVC OR MINGW OR ANDROID OR ARM OR WEB OR XCODE) AND NOT ABI_64BIT)
+            if (NOT (MSVC OR ANDROID OR ARM OR WEB OR XCODE) AND NOT ABI_64BIT)
                 set (COMPILER_32BIT_FLAG -m32)
             endif ()
             # Below variables are loop invariant but there is no harm to keep them here

+ 1 - 1
CMake/Modules/Urho3D-CMake-common.cmake

@@ -98,7 +98,7 @@ set (CMAKE_EXE_LINKER_FLAGS "${INDIRECT_DEPS_EXE_LINKER_FLAGS} ${CMAKE_EXE_LINKE
 include (CMakeDependentOption)
 option (URHO3D_C++11 "Enable C++11 standard")
 cmake_dependent_option (IOS "Setup build for iOS platform" FALSE "XCODE" FALSE)
-cmake_dependent_option (URHO3D_64BIT "Enable 64-bit build, the default is set based on the native ABI of the chosen compiler toolchain" ${NATIVE_64BIT} "NOT MSVC AND NOT MINGW AND NOT ANDROID AND NOT (ARM AND NOT IOS) AND NOT WEB AND NOT POWERPC" ${NATIVE_64BIT})     # Intentionally only enable the option for iOS but not for tvOS as the latter is 64-bit only
+cmake_dependent_option (URHO3D_64BIT "Enable 64-bit build, the default is set based on the native ABI of the chosen compiler toolchain" ${NATIVE_64BIT} "NOT MSVC AND NOT ANDROID AND NOT (ARM AND NOT IOS) AND NOT WEB AND NOT POWERPC" ${NATIVE_64BIT})     # Intentionally only enable the option for iOS but not for tvOS as the latter is 64-bit only
 option (URHO3D_ANGELSCRIPT "Enable AngelScript scripting support" TRUE)
 option (URHO3D_LUA "Enable additional Lua scripting support" TRUE)
 option (URHO3D_NAVIGATION "Enable navigation support" TRUE)

+ 6 - 0
Docs/AngelScriptAPI.h

@@ -11316,6 +11316,7 @@ WrapMode GetAttributeAnimationWrapMode(const String&) const;
 Variant GetAttributeDefault(const String&) const;
 float GetHeight(const Vector3&) const;
 bool GetInterceptNetworkUpdate(const String&) const;
+TerrainPatch GetNeighborPatch(int, int) const;
 Vector3 GetNormal(const Vector3&) const;
 TerrainPatch GetPatch(int, int) const;
 bool HasSubscribedToEvent(Object, const String&);
@@ -11342,6 +11343,7 @@ void SetAttributeAnimationSpeed(const String&, float);
 void SetAttributeAnimationTime(const String&, float);
 void SetAttributeAnimationWrapMode(const String&, WrapMode);
 void SetInterceptNetworkUpdate(const String&, bool);
+void SetNeighbors(Terrain, Terrain, Terrain, Terrain);
 IntVector2 WorldToHeightMap(const Vector3&) const;
 
 // Properties:
@@ -11355,6 +11357,7 @@ bool castShadows;
 /* readonly */
 String category;
 float drawDistance;
+Terrain eastNeighbor;
 bool enabled;
 /* readonly */
 bool enabledEffective;
@@ -11368,6 +11371,7 @@ uint maxLights;
 uint maxLodLevels;
 /* readonly */
 Node node;
+Terrain northNeighbor;
 /* readonly */
 uint numAttributes;
 /* readonly */
@@ -11386,6 +11390,7 @@ int refs;
 float shadowDistance;
 uint shadowMask;
 bool smoothing;
+Terrain southNeighbor;
 Vector3 spacing;
 bool temporary;
 /* readonly */
@@ -11395,6 +11400,7 @@ String typeName;
 uint viewMask;
 /* readonly */
 int weakRefs;
+Terrain westNeighbor;
 uint zoneMask;
 };
 

+ 14 - 0
Docs/LuaScriptAPI.dox

@@ -6080,6 +6080,11 @@ Methods:
 - void SetSmoothing(bool enable)
 - bool SetHeightMap(Image* image)
 - void SetMaterial(Material* material)
+- void SetNorthNeighbor(Terrain* north)
+- void SetSouthNeighbor(Terrain* south)
+- void SetWestNeighbor(Terrain* west)
+- void SetEastNeighbor(Terrain* east)
+- void SetNeighbors(Terrain* north, Terrain* south, Terrain* west, Terrain* east)
 - void SetDrawDistance(float distance)
 - void SetShadowDistance(float distance)
 - void SetLodBias(float bias)
@@ -6101,8 +6106,13 @@ Methods:
 - bool GetSmoothing() const
 - Image* GetHeightMap() const
 - Material* GetMaterial() const
+- Terrain* GetNorthNeighbor() const
+- Terrain* GetSouthNeighbor() const
+- Terrain* GetWestNeighbor() const
+- Terrain* GetEastNeighbor() const
 - TerrainPatch* GetPatch(unsigned index) const
 - TerrainPatch* GetPatch(int x, int z) const
+- TerrainPatch* GetNeighborPatch(int x, int z) const
 - float GetHeight(const Vector3& worldPosition) const
 - Vector3 GetNormal(const Vector3& worldPosition) const
 - IntVector2 WorldToHeightMap(const Vector3& worldPosition) const
@@ -6131,6 +6141,10 @@ Properties:
 - bool smoothing
 - Image* heightMap
 - Material* material
+- Terrain* northNeighbor
+- Terrain* southNeighbor
+- Terrain* westNeighbor
+- Terrain* eastNeighbor
 - float drawDistance
 - float shadowDistance
 - float lodBias

+ 10 - 0
Docs/ScriptAPI.dox

@@ -2050,6 +2050,10 @@ namespace Urho3D
 - %Is %Enabled : bool
 - %Height %Map : ResourceRef
 - %Material : ResourceRef
+- %North %Neighbor %NodeID : int
+- %South %Neighbor %NodeID : int
+- %West %Neighbor %NodeID : int
+- %East %Neighbor %NodeID : int
 - %Vertex %Spacing : Vector3
 - %Patch %Size : int
 - %Max %LOD %Levels : int
@@ -12559,6 +12563,7 @@ Methods:
 - Variant GetAttributeDefault(const String&) const
 - float GetHeight(const Vector3&) const
 - bool GetInterceptNetworkUpdate(const String&) const
+- TerrainPatch@ GetNeighborPatch(int, int) const
 - Vector3 GetNormal(const Vector3&) const
 - TerrainPatch@ GetPatch(int, int) const
 - bool HasSubscribedToEvent(Object@, const String&)
@@ -12585,6 +12590,7 @@ Methods:
 - void SetAttributeAnimationTime(const String&, float)
 - void SetAttributeAnimationWrapMode(const String&, WrapMode)
 - void SetInterceptNetworkUpdate(const String&, bool)
+- void SetNeighbors(Terrain@, Terrain@, Terrain@, Terrain@)
 - IntVector2 WorldToHeightMap(const Vector3&) const
 
 Properties:
@@ -12596,6 +12602,7 @@ Properties:
 - bool castShadows
 - String category // readonly
 - float drawDistance
+- Terrain@ eastNeighbor
 - bool enabled
 - bool enabledEffective // readonly
 - Image@ heightMap
@@ -12606,6 +12613,7 @@ Properties:
 - uint maxLights
 - uint maxLodLevels
 - Node@ node // readonly
+- Terrain@ northNeighbor
 - uint numAttributes // readonly
 - IntVector2 numPatches // readonly
 - IntVector2 numVertices // readonly
@@ -12619,12 +12627,14 @@ Properties:
 - float shadowDistance
 - uint shadowMask
 - bool smoothing
+- Terrain@ southNeighbor
 - Vector3 spacing
 - bool temporary
 - StringHash type // readonly
 - String typeName // readonly
 - uint viewMask
 - int weakRefs // readonly
+- Terrain@ westNeighbor
 - uint zoneMask
 
 <a name="Class_TerrainPatch"></a>

+ 6 - 1
Docs/Urho3D.dox

@@ -121,6 +121,7 @@ Urho3D development, contributions and bugfixes by:
 - MonkeyFirst
 - Newb I the Newbd
 - OvermindDL1
+- Scellow
 - Skrylar
 - TheComet93
 - Y-way
@@ -204,7 +205,11 @@ Jack and mushroom models from the realXtend project. (https://www.realxtend.org)
 Ninja model and terrain, water, smoke, flare and status bar textures from OGRE.<br>
 BlueHighway font from Larabie Fonts.<br>
 Anonymous Pro font by Mark Simonson.<br>
-NinjaSnowWar sounds by Veli-Pekka T&auml;til&auml;.
+NinjaSnowWar sounds by Veli-Pekka T&auml;til&auml;.<br>
+PBR textures from Substance Share. (https://share.allegorithmic.com)<br>
+IBL textures from HDRLab's sIBL Archive.<br>
+Dieselpunk Moto model by allexandr007.<br>
+Mutant model from Mixamo.<br>
 
 
 \page License License

+ 5 - 0
README.md

@@ -73,6 +73,7 @@ Urho3D development, contributions and bugfixes by:
 - MonkeyFirst
 - Newb I the Newbd
 - OvermindDL1
+- Scellow
 - Skrylar
 - TheComet93
 - Y-way
@@ -167,6 +168,10 @@ Ninja model and terrain, water, smoke, flare and status bar textures from OGRE.
 BlueHighway font from Larabie Fonts.
 Anonymous Pro font by Mark Simonson.
 NinjaSnowWar sounds by Veli-Pekka Tätilä.
+PBR textures from Substance Share. (https://share.allegorithmic.com)
+IBL textures from HDRLab's sIBL Archive.
+Dieselpunk Moto model by allexandr007.
+Mutant model from Mixamo.
 
 ##Documentation
 Urho3D classes have been sparsely documented using Doxygen notation. To

+ 2 - 2
Source/Samples/18_CharacterDemo/Character.cpp

@@ -151,10 +151,10 @@ void Character::HandleNodeCollision(StringHash eventType, VariantMap& eventData)
         /*float contactDistance = */contacts.ReadFloat();
         /*float contactImpulse = */contacts.ReadFloat();
 
-        // If contact is below node center and mostly vertical, assume it's a ground contact
+        // If contact is below node center and pointing up, assume it's a ground contact
         if (contactPosition.y_ < (node_->GetPosition().y_ + 1.0f))
         {
-            float level = Abs(contactNormal.y_);
+            float level = contactNormal.y_;
             if (level > 0.75)
                 onGround_ = true;
         }

+ 1 - 1
Source/Urho3D/.soversion

@@ -1 +1 @@
-0.0.251
+0.0.252

+ 10 - 0
Source/Urho3D/AngelScript/GraphicsAPI.cpp

@@ -1728,7 +1728,9 @@ static void RegisterTerrain(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Terrain", "float GetHeight(const Vector3&in) const", asMETHOD(Terrain, GetHeight), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "Vector3 GetNormal(const Vector3&in) const", asMETHOD(Terrain, GetNormal), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "TerrainPatch@+ GetPatch(int, int) const", asMETHODPR(Terrain, GetPatch, (int, int) const, TerrainPatch*), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Terrain", "TerrainPatch@+ GetNeighborPatch(int, int) const", asMETHODPR(Terrain, GetNeighborPatch, (int, int) const, TerrainPatch*), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "IntVector2 WorldToHeightMap(const Vector3&in) const", asMETHOD(Terrain, WorldToHeightMap), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Terrain", "void SetNeighbors(Terrain@+, Terrain@+, Terrain@+, Terrain@+)", asMETHOD(Terrain, SetNeighbors), 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_maxLodLevels(uint)", asMETHOD(Terrain, SetMaxLodLevels), asCALL_THISCALL);
@@ -1768,6 +1770,14 @@ static void RegisterTerrain(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Terrain", "uint get_zoneMask() const", asMETHOD(Terrain, GetZoneMask), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "void set_maxLights(uint)", asMETHOD(Terrain, SetMaxLights), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "uint get_maxLights() const", asMETHOD(Terrain, GetMaxLights), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Terrain", "void set_northNeighbor(Terrain@+)", asMETHOD(Terrain, SetNorthNeighbor), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Terrain", "Terrain@+ get_northNeighbor() const", asMETHOD(Terrain, GetNorthNeighbor), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Terrain", "void set_southNeighbor(Terrain@+)", asMETHOD(Terrain, SetSouthNeighbor), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Terrain", "Terrain@+ get_southNeighbor() const", asMETHOD(Terrain, GetSouthNeighbor), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Terrain", "void set_westNeighbor(Terrain@+)", asMETHOD(Terrain, SetEastNeighbor), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Terrain", "Terrain@+ get_westNeighbor() const", asMETHOD(Terrain, GetEastNeighbor), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Terrain", "void set_eastNeighbor(Terrain@+)", asMETHOD(Terrain, SetWestNeighbor), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Terrain", "Terrain@+ get_eastNeighbor() const", asMETHOD(Terrain, GetWestNeighbor), asCALL_THISCALL);
 }
 
 

+ 4 - 0
Source/Urho3D/CMakeLists.txt

@@ -268,6 +268,10 @@ if (URHO3D_LUA)
     # This is more practical than patching its header files in many places to make them work with relative path
     list (APPEND INCLUDE_DIRS ${CMAKE_BINARY_DIR}/${DEST_INCLUDE_DIR}/ThirdParty/Lua${JIT})
 endif ()
+# Workaround for GCC 5.4 and above when building a SHARED lib type for Linux platform to fix the undefined symbol "__cpu_model" issue (see #1519)
+if (CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 5.3.1 AND CMAKE_SYSTEM_NAME STREQUAL Linux AND URHO3D_LIB_TYPE STREQUAL SHARED)  # 5.3.1 was the last known good version
+    list (APPEND LIBS gcc)
+endif ()
 
 # Setup library output path
 if (ANDROID)

+ 193 - 7
Source/Urho3D/Graphics/Terrain.cpp

@@ -105,7 +105,12 @@ Terrain::Terrain(Context* context) :
     shadowDistance_(0.0f),
     lodBias_(1.0f),
     maxLights_(0),
-    recreateTerrain_(false)
+    northID_(0),
+    southID_(0),
+    westID_(0),
+    eastID_(0),
+    recreateTerrain_(false),
+    neighborsDirty_(false)
 {
     indexBuffer_->SetShadowed(true);
 }
@@ -123,6 +128,10 @@ void Terrain::RegisterObject(Context* context)
         AM_DEFAULT);
     URHO3D_MIXED_ACCESSOR_ATTRIBUTE("Material", GetMaterialAttr, SetMaterialAttr, ResourceRef, ResourceRef(Material::GetTypeStatic()),
         AM_DEFAULT);
+    URHO3D_ATTRIBUTE("North Neighbor NodeID", unsigned, northID_, 0, AM_DEFAULT | AM_NODEID);
+    URHO3D_ATTRIBUTE("South Neighbor NodeID", unsigned, southID_, 0, AM_DEFAULT | AM_NODEID);
+    URHO3D_ATTRIBUTE("West Neighbor NodeID", unsigned, westID_, 0, AM_DEFAULT | AM_NODEID);
+    URHO3D_ATTRIBUTE("East Neighbor NodeID", unsigned, eastID_, 0, AM_DEFAULT | AM_NODEID);
     URHO3D_ATTRIBUTE("Vertex Spacing", Vector3, spacing_, DEFAULT_SPACING, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Patch Size", GetPatchSize, SetPatchSizeAttr, int, DEFAULT_PATCH_SIZE, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Max LOD Levels", GetMaxLodLevels, SetMaxLodLevelsAttr, unsigned, MAX_LOD_LEVELS, AM_DEFAULT);
@@ -145,15 +154,35 @@ void Terrain::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
 {
     Serializable::OnSetAttribute(attr, src);
 
-    // Change of any non-accessor attribute requires recreation of the terrain
+    // Change of any non-accessor attribute requires recreation of the terrain, or setting the neighbor terrains
     if (!attr.accessor_)
-        recreateTerrain_ = true;
+    {
+        if (attr.mode_ & AM_NODEID)
+            neighborsDirty_ = true;
+        else
+            recreateTerrain_ = true;
+    }
 }
 
 void Terrain::ApplyAttributes()
 {
     if (recreateTerrain_)
         CreateGeometry();
+    
+    if (neighborsDirty_)
+    {
+        Scene* scene = GetScene();
+        Node* north = scene ? scene->GetNode(northID_) : (Node*)0;
+        Node* south = scene ? scene->GetNode(southID_) : (Node*)0;
+        Node* west = scene ? scene->GetNode(westID_) : (Node*)0;
+        Node* east = scene ? scene->GetNode(eastID_) : (Node*)0;
+        Terrain* northTerrain = north ? north->GetComponent<Terrain>() : (Terrain*)0;
+        Terrain* southTerrain = south ? south->GetComponent<Terrain>() : (Terrain*)0;
+        Terrain* westTerrain = west ? west->GetComponent<Terrain>() : (Terrain*)0;
+        Terrain* eastTerrain = east ? east->GetComponent<Terrain>() : (Terrain*)0;
+        SetNeighbors(northTerrain, southTerrain, westTerrain, eastTerrain);
+        neighborsDirty_ = false;
+    }
 }
 
 void Terrain::OnSetEnabled()
@@ -248,6 +277,122 @@ void Terrain::SetMaterial(Material* material)
     MarkNetworkUpdate();
 }
 
+void Terrain::SetNorthNeighbor(Terrain* north)
+{
+    if (north == north_)
+        return;
+
+    if (north_ && north_->GetNode())
+        UnsubscribeFromEvent(north_->GetNode(), E_TERRAINCREATED);
+
+    north_ = north;
+    if (north_ && north_->GetNode())
+    {
+        northID_ = north_->GetNode()->GetID();
+        SubscribeToEvent(north_->GetNode(), E_TERRAINCREATED, URHO3D_HANDLER(Terrain, HandleNeighborTerrainCreated));
+    }
+
+    UpdateEdgePatchNeighbors();
+    MarkNetworkUpdate();
+}
+
+void Terrain::SetSouthNeighbor(Terrain* south)
+{
+    if (south == south_)
+        return;
+
+    if (south_ && south_->GetNode())
+        UnsubscribeFromEvent(south_->GetNode(), E_TERRAINCREATED);
+
+    south_ = south;
+    if (south_ && south_->GetNode())
+    {
+        southID_ = south_->GetNode()->GetID();
+        SubscribeToEvent(south_->GetNode(), E_TERRAINCREATED, URHO3D_HANDLER(Terrain, HandleNeighborTerrainCreated));
+    }
+
+    UpdateEdgePatchNeighbors();
+    MarkNetworkUpdate();
+}
+
+void Terrain::SetWestNeighbor(Terrain* west)
+{
+    if (west == west_)
+        return;
+
+    if (west_ && west_->GetNode())
+        UnsubscribeFromEvent(west_->GetNode(), E_TERRAINCREATED);
+
+    west_ = west;
+    if (west_ && west_->GetNode())
+    {
+        westID_ = west_->GetNode()->GetID();
+        SubscribeToEvent(west_->GetNode(), E_TERRAINCREATED, URHO3D_HANDLER(Terrain, HandleNeighborTerrainCreated));
+    }
+
+    UpdateEdgePatchNeighbors();
+    MarkNetworkUpdate();
+}
+
+void Terrain::SetEastNeighbor(Terrain* east)
+{
+    if (east == east_)
+        return;
+
+    if (east_ && east_->GetNode())
+        UnsubscribeFromEvent(east_->GetNode(), E_TERRAINCREATED);
+
+    east_ = east;
+    if (east_ && east_->GetNode())
+    {
+        eastID_ = east_->GetNode()->GetID();
+        SubscribeToEvent(east_->GetNode(), E_TERRAINCREATED, URHO3D_HANDLER(Terrain, HandleNeighborTerrainCreated));
+    }
+
+    UpdateEdgePatchNeighbors();
+    MarkNetworkUpdate();
+}
+
+void Terrain::SetNeighbors(Terrain* north, Terrain* south, Terrain* west, Terrain* east)
+{
+    if (north_ && north_->GetNode())
+        UnsubscribeFromEvent(north_->GetNode(), E_TERRAINCREATED);
+    if (south_ && south_->GetNode())
+        UnsubscribeFromEvent(south_->GetNode(), E_TERRAINCREATED);
+    if (west_ && west_->GetNode())
+        UnsubscribeFromEvent(west_->GetNode(), E_TERRAINCREATED);
+    if (east_ && east_->GetNode())
+        UnsubscribeFromEvent(east_->GetNode(), E_TERRAINCREATED);
+
+    north_ = north;
+    if (north_ && north_->GetNode())
+    {
+        northID_ = north_->GetNode()->GetID();
+        SubscribeToEvent(north_->GetNode(), E_TERRAINCREATED, URHO3D_HANDLER(Terrain, HandleNeighborTerrainCreated));
+    }
+    south_ = south;
+    if (south_ && south_->GetNode())
+    {
+        southID_ = south_->GetNode()->GetID();
+        SubscribeToEvent(south_->GetNode(), E_TERRAINCREATED, URHO3D_HANDLER(Terrain, HandleNeighborTerrainCreated));
+    }
+    west_ = west;
+    if (west_ && west_->GetNode())
+    {
+        westID_ = west_->GetNode()->GetID();
+        SubscribeToEvent(west_->GetNode(), E_TERRAINCREATED, URHO3D_HANDLER(Terrain, HandleNeighborTerrainCreated));
+    }
+    east_ = east;
+    if (east_ && east_->GetNode())
+    {
+        eastID_ = east_->GetNode()->GetID();
+        SubscribeToEvent(east_->GetNode(), E_TERRAINCREATED, URHO3D_HANDLER(Terrain, HandleNeighborTerrainCreated));
+    }
+
+    UpdateEdgePatchNeighbors();
+    MarkNetworkUpdate();
+}
+
 void Terrain::SetDrawDistance(float distance)
 {
     drawDistance_ = distance;
@@ -409,6 +554,20 @@ TerrainPatch* Terrain::GetPatch(int x, int z) const
         return GetPatch((unsigned)(z * numPatches_.x_ + x));
 }
 
+TerrainPatch* Terrain::GetNeighborPatch(int x, int z) const
+{
+    if (z >= numPatches_.y_ && north_)
+        return north_->GetPatch(x, z - numPatches_.y_);
+    else if (z < 0 && south_)
+        return south_->GetPatch(x, z + south_->GetNumPatches().y_);
+    else if (x < 0 && west_)
+        return west_->GetPatch(x + west_->GetNumPatches().x_, z);
+    else if (x >= numPatches_.x_ && east_)
+        return east_->GetPatch(x - numPatches_.x_, z);
+    else 
+        return GetPatch(x, z);
+}
+
 float Terrain::GetHeight(const Vector3& worldPosition) const
 {
     if (node_)
@@ -962,7 +1121,7 @@ void Terrain::CreateGeometry()
                 CalculateLodErrors(patch);
             }
 
-            SetNeighbors(patch);
+            SetPatchNeighbors(patch);
         }
     }
 
@@ -1247,11 +1406,14 @@ void Terrain::CalculateLodErrors(TerrainPatch* patch)
     }
 }
 
-void Terrain::SetNeighbors(TerrainPatch* patch)
+void Terrain::SetPatchNeighbors(TerrainPatch* patch)
 {
+    if (!patch)
+        return;
+
     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_));
+    patch->SetNeighbors(GetNeighborPatch(coords.x_, coords.y_ + 1), GetNeighborPatch(coords.x_, coords.y_ - 1),
+        GetNeighborPatch(coords.x_ - 1, coords.y_), GetNeighborPatch(coords.x_ + 1, coords.y_));
 }
 
 bool Terrain::SetHeightMapInternal(Image* image, bool recreateNow)
@@ -1283,4 +1445,28 @@ void Terrain::HandleHeightMapReloadFinished(StringHash eventType, VariantMap& ev
     CreateGeometry();
 }
 
+void Terrain::HandleNeighborTerrainCreated(StringHash eventType, VariantMap& eventData)
+{
+    UpdateEdgePatchNeighbors();
+}
+
+void Terrain::UpdateEdgePatchNeighbors()
+{
+    for (int x = 1; x < numPatches_.x_ - 1; ++x)
+    {
+        SetPatchNeighbors(GetPatch(x, 0));
+        SetPatchNeighbors(GetPatch(x, numPatches_.y_ - 1));
+    }
+    for (int z = 1; z < numPatches_.y_ - 1; ++z)
+    {
+        SetPatchNeighbors(GetPatch(0, z));
+        SetPatchNeighbors(GetPatch(numPatches_.x_ - 1, z));
+    }
+
+    SetPatchNeighbors(GetPatch(0, 0));
+    SetPatchNeighbors(GetPatch(numPatches_.x_ - 1, 0));
+    SetPatchNeighbors(GetPatch(0, numPatches_.y_ - 1));
+    SetPatchNeighbors(GetPatch(numPatches_.x_ - 1, numPatches_.y_ - 1));
+}
+
 }

+ 47 - 1
Source/Urho3D/Graphics/Terrain.h

@@ -67,6 +67,16 @@ public:
     bool SetHeightMap(Image* image);
     /// Set material.
     void SetMaterial(Material* material);
+    /// Set north (positive Z) neighbor terrain for seamless LOD changes across terrains.
+    void SetNorthNeighbor(Terrain* north);
+    /// Set south (negative Z) neighbor terrain for seamless LOD changes across terrains.
+    void SetSouthNeighbor(Terrain* south);
+    /// Set west (negative X) neighbor terrain for seamless LOD changes across terrains.
+    void SetWestNeighbor(Terrain* west);
+    /// Set east (positive X) neighbor terrain for seamless LOD changes across terrains.
+    void SetEastNeighbor(Terrain* east);
+    /// Set all neighbor terrains at once.
+    void SetNeighbors(Terrain* north, Terrain* south, Terrain* west, Terrain* east);
     /// Set draw distance for patches.
     void SetDrawDistance(float distance);
     /// Set shadow draw distance for patches.
@@ -121,6 +131,8 @@ public:
     TerrainPatch* GetPatch(unsigned index) const;
     /// Return patch by patch coordinates.
     TerrainPatch* GetPatch(int x, int z) const;
+    /// Return patch by patch coordinates including neighbor terrains.
+    TerrainPatch* GetNeighborPatch(int x, int z) const;
     /// Return height at world coordinates.
     float GetHeight(const Vector3& worldPosition) const;
     /// Return normal at world coordinates.
@@ -128,6 +140,18 @@ public:
     /// Convert world position to heightmap pixel position. Note that the internal height data representation is reversed vertically, but in the heightmap image north is at the top.
     IntVector2 WorldToHeightMap(const Vector3& worldPosition) const;
 
+    /// Return north neighbor terrain.
+    Terrain* GetNorthNeighbor() const { return north_; }
+    
+    /// Return south neighbor terrain.
+    Terrain* GetSouthNeighbor() const { return south_; }
+    
+    /// Return west neighbor terrain.
+    Terrain* GetWestNeighbor() const { return west_; }
+    
+    /// Return east neighbor terrain.
+    Terrain* GetEastNeighbor() const { return east_; }
+
     /// Return raw height data.
     SharedArrayPtr<float> GetHeightData() const { return heightData_; }
 
@@ -202,11 +226,15 @@ private:
     /// Calculate LOD errors for a patch.
     void CalculateLodErrors(TerrainPatch* patch);
     /// Set neighbors for a patch.
-    void SetNeighbors(TerrainPatch* patch);
+    void SetPatchNeighbors(TerrainPatch* patch);
     /// Set heightmap image and optionally recreate the geometry immediately. Return true if successful.
     bool SetHeightMapInternal(Image* image, bool recreateNow);
     /// Handle heightmap image reload finished.
     void HandleHeightMapReloadFinished(StringHash eventType, VariantMap& eventData);
+    /// Handle neighbor terrain geometry being created. Update the edge patch neighbors as necessary.
+    void HandleNeighborTerrainCreated(StringHash eventType, VariantMap& eventData);
+    /// Update edge patch neighbors when neighbor terrain(s) change or are recreated.
+    void UpdateEdgePatchNeighbors();
 
     /// Shared index buffer.
     SharedPtr<IndexBuffer> indexBuffer_;
@@ -222,6 +250,14 @@ private:
     Vector<WeakPtr<TerrainPatch> > patches_;
     /// Draw ranges for different LODs and stitching combinations.
     PODVector<Pair<unsigned, unsigned> > drawRanges_;
+    /// North neighbor terrain.
+    WeakPtr<Terrain> north_;
+    /// South neighbor terrain.
+    WeakPtr<Terrain> south_;
+    /// West neighbor terrain.
+    WeakPtr<Terrain> west_;
+    /// East neighbor terrain.
+    WeakPtr<Terrain> east_;
     /// Vertex and height spacing.
     Vector3 spacing_;
     /// Vertex and height sacing at the time of last update.
@@ -272,8 +308,18 @@ private:
     float lodBias_;
     /// Maximum lights.
     unsigned maxLights_;
+    /// Node ID of north neighbor.
+    unsigned northID_;
+    /// Node ID of south neighbor.
+    unsigned southID_;
+    /// Node ID of west neighbor.
+    unsigned westID_;
+    /// Node ID of east neighbor.
+    unsigned eastID_;
     /// Terrain needs regeneration flag.
     bool recreateTerrain_;
+    /// Terrain neighbor attributes dirty flag.
+    bool neighborsDirty_;
 };
 
 }

+ 15 - 1
Source/Urho3D/LuaScript/pkgs/Graphics/Terrain.pkg

@@ -9,6 +9,11 @@ class Terrain : public Component
     void SetSmoothing(bool enable);
     bool SetHeightMap(Image* image);
     void SetMaterial(Material* material);
+    void SetNorthNeighbor(Terrain* north);
+    void SetSouthNeighbor(Terrain* south);
+    void SetWestNeighbor(Terrain* west);
+    void SetEastNeighbor(Terrain* east);
+    void SetNeighbors(Terrain* north, Terrain* south, Terrain* west, Terrain* east);
     void SetDrawDistance(float distance);
     void SetShadowDistance(float distance);
     void SetLodBias(float bias);
@@ -31,8 +36,13 @@ class Terrain : public Component
     bool GetSmoothing() const;
     Image* GetHeightMap() const;
     Material* GetMaterial() const;
+    Terrain* GetNorthNeighbor() const;
+    Terrain* GetSouthNeighbor() const;
+    Terrain* GetWestNeighbor() const;
+    Terrain* GetEastNeighbor() const;
     TerrainPatch* GetPatch(unsigned index) const;
     TerrainPatch* GetPatch(int x, int z) const;
+    TerrainPatch* GetNeighborPatch(int x, int z) const;
     float GetHeight(const Vector3& worldPosition) const;
     Vector3 GetNormal(const Vector3& worldPosition) const;
     IntVector2 WorldToHeightMap(const Vector3& worldPosition) const;
@@ -49,7 +59,7 @@ class Terrain : public Component
     bool GetCastShadows() const;
     bool IsOccluder() const;
     bool IsOccludee() const;
-    
+
     tolua_property__get_set int patchSize;
     tolua_property__get_set Vector3& spacing;
     tolua_readonly tolua_property__get_set IntVector2& numVertices;
@@ -59,6 +69,10 @@ class Terrain : public Component
     tolua_property__get_set bool smoothing;
     tolua_property__get_set Image* heightMap;
     tolua_property__get_set Material* material;
+    tolua_property__get_set Terrain* northNeighbor;
+    tolua_property__get_set Terrain* southNeighbor;
+    tolua_property__get_set Terrain* westNeighbor;
+    tolua_property__get_set Terrain* eastNeighbor;
     tolua_property__get_set float drawDistance;
     tolua_property__get_set float shadowDistance;
     tolua_property__get_set float lodBias;

+ 58 - 21
Source/Urho3D/Physics/PhysicsWorld.cpp

@@ -684,7 +684,7 @@ void PhysicsWorld::GetCollidingBodies(PODVector<RigidBody*>& result, const Rigid
 
     result.Clear();
 
-    for (HashMap<Pair<WeakPtr<RigidBody>, WeakPtr<RigidBody> >, btPersistentManifold*>::Iterator i = currentCollisions_.Begin();
+    for (HashMap<Pair<WeakPtr<RigidBody>, WeakPtr<RigidBody> >, ManifoldPair>::Iterator i = currentCollisions_.Begin();
          i != currentCollisions_.End(); ++i)
     {
         if (i->first_.first_ == body)
@@ -887,18 +887,22 @@ void PhysicsWorld::SendCollisionEvents()
             WeakPtr<RigidBody> bodyWeakA(bodyA);
             WeakPtr<RigidBody> bodyWeakB(bodyB);
 
+            // First only store the collision pair as weak pointers and the manifold pointer, so user code can safely destroy
+            // objects during collision event handling
             Pair<WeakPtr<RigidBody>, WeakPtr<RigidBody> > bodyPair;
             if (bodyA < bodyB)
+            {
                 bodyPair = MakePair(bodyWeakA, bodyWeakB);
+                currentCollisions_[bodyPair].manifold_ = contactManifold;
+            }
             else
+            {
                 bodyPair = MakePair(bodyWeakB, bodyWeakA);
-
-            // First only store the collision pair as weak pointers and the manifold pointer, so user code can safely destroy
-            // objects during collision event handling
-            currentCollisions_[bodyPair] = contactManifold;
+                currentCollisions_[bodyPair].flippedManifold_ = contactManifold;
+            }
         }
 
-        for (HashMap<Pair<WeakPtr<RigidBody>, WeakPtr<RigidBody> >, btPersistentManifold*>::Iterator i = currentCollisions_.Begin();
+        for (HashMap<Pair<WeakPtr<RigidBody>, WeakPtr<RigidBody> >, ManifoldPair>::Iterator i = currentCollisions_.Begin();
              i != currentCollisions_.End(); ++i)
         {
             RigidBody* bodyA = i->first_.first_;
@@ -906,8 +910,6 @@ void PhysicsWorld::SendCollisionEvents()
             if (!bodyA || !bodyB)
                 continue;
 
-            btPersistentManifold* contactManifold = i->second_;
-
             Node* nodeA = bodyA->GetNode();
             Node* nodeB = bodyB->GetNode();
             WeakPtr<Node> nodeWeakA(nodeA);
@@ -924,13 +926,31 @@ void PhysicsWorld::SendCollisionEvents()
 
             contacts_.Clear();
 
-            for (int j = 0; j < contactManifold->getNumContacts(); ++j)
+            // "Pointers not flipped"-manifold, send unmodified normals
+            btPersistentManifold* contactManifold = i->second_.manifold_;
+            if (contactManifold)
             {
-                btManifoldPoint& point = contactManifold->getContactPoint(j);
-                contacts_.WriteVector3(ToVector3(point.m_positionWorldOnB));
-                contacts_.WriteVector3(ToVector3(point.m_normalWorldOnB));
-                contacts_.WriteFloat(point.m_distance1);
-                contacts_.WriteFloat(point.m_appliedImpulse);
+                for (int j = 0; j < contactManifold->getNumContacts(); ++j)
+                {
+                    btManifoldPoint& point = contactManifold->getContactPoint(j);
+                    contacts_.WriteVector3(ToVector3(point.m_positionWorldOnB));
+                    contacts_.WriteVector3(ToVector3(point.m_normalWorldOnB));
+                    contacts_.WriteFloat(point.m_distance1);
+                    contacts_.WriteFloat(point.m_appliedImpulse);
+                }
+            }
+            // "Pointers flipped"-manifold, flip normals also
+            contactManifold = i->second_.flippedManifold_;
+            if (contactManifold)
+            {
+                for (int j = 0; j < contactManifold->getNumContacts(); ++j)
+                {
+                    btManifoldPoint& point = contactManifold->getContactPoint(j);
+                    contacts_.WriteVector3(ToVector3(point.m_positionWorldOnB));
+                    contacts_.WriteVector3(-ToVector3(point.m_normalWorldOnB));
+                    contacts_.WriteFloat(point.m_distance1);
+                    contacts_.WriteFloat(point.m_appliedImpulse);
+                }
             }
 
             physicsCollisionData_[PhysicsCollision::P_CONTACTS] = contacts_.GetBuffer();
@@ -966,14 +986,31 @@ void PhysicsWorld::SendCollisionEvents()
             if (!nodeWeakA || !nodeWeakB || !i->first_.first_ || !i->first_.second_)
                 continue;
 
+            // Flip perspective to body B
             contacts_.Clear();
-            for (int j = 0; j < contactManifold->getNumContacts(); ++j)
+            contactManifold = i->second_.manifold_;
+            if (contactManifold)
+            {
+                for (int j = 0; j < contactManifold->getNumContacts(); ++j)
+                {
+                    btManifoldPoint& point = contactManifold->getContactPoint(j);
+                    contacts_.WriteVector3(ToVector3(point.m_positionWorldOnB));
+                    contacts_.WriteVector3(-ToVector3(point.m_normalWorldOnB));
+                    contacts_.WriteFloat(point.m_distance1);
+                    contacts_.WriteFloat(point.m_appliedImpulse);
+                }
+            }
+            contactManifold = i->second_.flippedManifold_;
+            if (contactManifold)
             {
-                btManifoldPoint& point = contactManifold->getContactPoint(j);
-                contacts_.WriteVector3(ToVector3(point.m_positionWorldOnB));
-                contacts_.WriteVector3(-ToVector3(point.m_normalWorldOnB));
-                contacts_.WriteFloat(point.m_distance1);
-                contacts_.WriteFloat(point.m_appliedImpulse);
+                for (int j = 0; j < contactManifold->getNumContacts(); ++j)
+                {
+                    btManifoldPoint& point = contactManifold->getContactPoint(j);
+                    contacts_.WriteVector3(ToVector3(point.m_positionWorldOnB));
+                    contacts_.WriteVector3(ToVector3(point.m_normalWorldOnB));
+                    contacts_.WriteFloat(point.m_distance1);
+                    contacts_.WriteFloat(point.m_appliedImpulse);
+                }
             }
 
             nodeCollisionData_[NodeCollision::P_BODY] = bodyB;
@@ -996,7 +1033,7 @@ void PhysicsWorld::SendCollisionEvents()
     {
         physicsCollisionData_[PhysicsCollisionEnd::P_WORLD] = this;
 
-        for (HashMap<Pair<WeakPtr<RigidBody>, WeakPtr<RigidBody> >, btPersistentManifold*>::Iterator
+        for (HashMap<Pair<WeakPtr<RigidBody>, WeakPtr<RigidBody> >, ManifoldPair>::Iterator
                  i = previousCollisions_.Begin(); i != previousCollisions_.End(); ++i)
         {
             if (!currentCollisions_.Contains(i->first_))

+ 18 - 2
Source/Urho3D/Physics/PhysicsWorld.h

@@ -96,6 +96,22 @@ struct DelayedWorldTransform
     Quaternion worldRotation_;
 };
 
+/// Manifold pointers stored during collision processing.
+struct ManifoldPair
+{
+    /// Construct with defaults.
+    ManifoldPair() :
+        manifold_(0),
+        flippedManifold_(0)
+    {
+    }
+
+    /// Manifold without the body pointers flipped.
+    btPersistentManifold* manifold_;
+    /// Manifold with the body pointers flipped.
+    btPersistentManifold* flippedManifold_;
+};
+
 /// Custom overrides of physics internals. To use overrides, must be set before the physics component is created.
 struct PhysicsWorldConfig
 {
@@ -301,9 +317,9 @@ private:
     /// Constraints in the world.
     PODVector<Constraint*> constraints_;
     /// Collision pairs on this frame.
-    HashMap<Pair<WeakPtr<RigidBody>, WeakPtr<RigidBody> >, btPersistentManifold*> currentCollisions_;
+    HashMap<Pair<WeakPtr<RigidBody>, WeakPtr<RigidBody> >, ManifoldPair> currentCollisions_;
     /// Collision pairs on the previous frame. Used to check if a collision is "new." Manifolds are not guaranteed to exist anymore.
-    HashMap<Pair<WeakPtr<RigidBody>, WeakPtr<RigidBody> >, btPersistentManifold*> previousCollisions_;
+    HashMap<Pair<WeakPtr<RigidBody>, WeakPtr<RigidBody> >, ManifoldPair> previousCollisions_;
     /// Delayed (parented) world transform assignments.
     HashMap<RigidBody*, DelayedWorldTransform> delayedWorldTransforms_;
     /// Cache for trimesh geometry data by model and LOD level.

+ 1 - 1
Source/Urho3D/Scene/SplinePath.cpp

@@ -270,8 +270,8 @@ void SplinePath::SetControlPointIdsAttr(const VariantVector& value)
 void SplinePath::SetControlledIdAttr(unsigned value)
 {
     if (value > 0 && value < M_MAX_UNSIGNED)
-
         controlledIdAttr_ = value;
+
     dirty_ = true;
 }
 

+ 2 - 2
bin/Data/LuaScripts/18_CharacterDemo.lua

@@ -355,9 +355,9 @@ function Character:HandleNodeCollision(eventType, eventData)
         local contactDistance = contacts:ReadFloat()
         local contactImpulse = contacts:ReadFloat()
 
-        -- If contact is below node center and mostly vertical, assume it's a ground contact
+        -- If contact is below node center and pointing up, assume it's a ground contact
         if contactPosition.y < self.node.position.y + 1.0 then
-            local level = Abs(contactNormal.y)
+            local level = contactNormal.y
             if level > 0.75 then
                 self.onGround = true
             end

+ 2 - 2
bin/Data/Scripts/18_CharacterDemo.as

@@ -378,10 +378,10 @@ class Character : ScriptObject
             float contactDistance = contacts.ReadFloat();
             float contactImpulse = contacts.ReadFloat();
 
-            // If contact is below node center and mostly vertical, assume it's a ground contact
+            // If contact is below node center and pointing up, assume it's a ground contact
             if (contactPosition.y < (node.position.y + 1.0f))
             {
-                float level = Abs(contactNormal.y);
+                float level = contactNormal.y;
                 if (level > 0.75)
                     onGround = true;
             }

+ 2 - 2
bin/Data/Scripts/NinjaSnowWar/GameObject.as

@@ -110,10 +110,10 @@ class GameObject : ScriptObject
             float contactDistance = contacts.ReadFloat();
             float contactImpulse = contacts.ReadFloat();
 
-            // If contact is below node center and mostly vertical, assume it's ground contact
+            // If contact is below node center and pointing up, assume it's ground contact
             if (contactPosition.y < node.position.y)
             {
-                float level = Abs(contactNormal.y);
+                float level = contactNormal.y;
                 if (level > 0.75)
                     onGround = true;
                 else

+ 2 - 0
bin/Data/UI/DefaultStyle.xml

@@ -108,11 +108,13 @@
         <element type="Text" internal="true">
             <attribute name="Color" value="0.9 1 0.9 1" />
             <attribute name="Selection Color" value="0.3 0.4 0.7 1" />
+            <attribute name="Vert Alignment" value="Center" />
         </element>
         <element type="BorderImage" internal="true">
             <attribute name="Size" value="4 16" />
             <attribute name="Priority" value="1" />
             <attribute name="Image Rect" value="12 0 16 16" />
+            <attribute name="Vert Alignment" value="Center" />
         </element>
     </element>
     <element type="ListView" style="ScrollView">  <!-- Shortcut to copy all the styles from ScrollView -->