Browse Source

Normalize resource names in SanitateResourceName() if they also contain a registered resource dir name, ie. Data/Scripts/NinjaSnowWar.as becomes Scripts/NinjaSnowWar.as. This fixes script file possibly getting loaded twice.
Fixed renderer raycasts returning wrong distance.
Added caseSensitive parameter to several String functions.
Added option to convert file extension to lowercase in GetExtension() and GetFileNameAndExtension() functions.
Removed unnecessary lowercase conversion of extensions, and complicated string compare logic.

Lasse Öörni 12 years ago
parent
commit
372fbed88d

+ 106 - 34
Source/Engine/Container/Str.cpp

@@ -202,22 +202,34 @@ String& String::operator += (bool rhs)
     return *this += String(rhs);
 }
 
-void String::Replace(char replaceThis, char replaceWith)
+void String::Replace(char replaceThis, char replaceWith, bool caseSensitive)
 {
-    for (unsigned i = 0; i < length_; ++i)
+    if (caseSensitive)
     {
-        if (buffer_[i] == replaceThis)
-            buffer_[i] = replaceWith;
+        for (unsigned i = 0; i < length_; ++i)
+        {
+            if (buffer_[i] == replaceThis)
+                buffer_[i] = replaceWith;
+        }
+    }
+    else
+    {
+        replaceThis = tolower(replaceThis);
+        for (unsigned i = 0; i < length_; ++i)
+        {
+            if (tolower(buffer_[i]) == replaceThis)
+                buffer_[i] = replaceWith;
+        }
     }
 }
 
-void String::Replace(const String& replaceThis, const String& replaceWith)
+void String::Replace(const String& replaceThis, const String& replaceWith, bool caseSensitive)
 {
     unsigned nextPos = 0;
     
     while (nextPos < length_)
     {
-        unsigned pos = Find(replaceThis, nextPos);
+        unsigned pos = Find(replaceThis, nextPos, caseSensitive);
         if (pos == NPOS)
             break;
         Replace(pos, replaceThis.length_, replaceWith);
@@ -225,13 +237,22 @@ void String::Replace(const String& replaceThis, const String& replaceWith)
     }
 }
 
-void String::Replace(unsigned pos, unsigned length, const String& str)
+void String::Replace(unsigned pos, unsigned length, const String& replaceWith)
 {
     // If substring is illegal, do nothing
     if (pos + length > length_)
         return;
     
-    Replace(pos, length, str.buffer_, str.length_);
+    Replace(pos, length, replaceWith.buffer_, replaceWith.length_);
+}
+
+void String::Replace(unsigned pos, unsigned length, const char* replaceWith)
+{
+    // If substring is illegal, do nothing
+    if (pos + length > length_)
+        return;
+    
+    Replace(pos, length, replaceWith, CStringLength(replaceWith));
 }
 
 String::Iterator String::Replace(const String::Iterator& start, const String::Iterator& end, const String& replaceWith)
@@ -245,17 +266,17 @@ String::Iterator String::Replace(const String::Iterator& start, const String::It
     return Begin() + pos;
 }
 
-String String::Replaced(char replaceThis, char replaceWith) const
+String String::Replaced(char replaceThis, char replaceWith, bool caseSensitive) const
 {
     String ret(*this);
-    ret.Replace(replaceThis, replaceWith);
+    ret.Replace(replaceThis, replaceWith, caseSensitive);
     return ret;
 }
 
-String String::Replaced(const String& replaceThis, const String& replaceWith) const
+String String::Replaced(const String& replaceThis, const String& replaceWith,  bool caseSensitive) const
 {
     String ret(*this);
-    ret.Replace(replaceThis, replaceWith);
+    ret.Replace(replaceThis, replaceWith, caseSensitive);
     return ret;
 }
 
@@ -518,36 +539,62 @@ void String::Join(const Vector<String>& subStrings, String glue)
     *this = Joined(subStrings, glue);
 }
 
-unsigned String::Find(char c, unsigned startPos) const
+unsigned String::Find(char c, unsigned startPos, bool caseSensitive) const
 {
-    for (unsigned i = startPos; i < length_; ++i)
+    if (caseSensitive)
+    {
+        for (unsigned i = startPos; i < length_; ++i)
+        {
+            if (buffer_[i] == c)
+                return i;
+        }
+    }
+    else
     {
-        if (buffer_[i] == c)
-            return i;
+        c = tolower(c);
+        for (unsigned i = startPos; i < length_; ++i)
+        {
+            if (tolower(buffer_[i]) == c)
+                return i;
+        }
     }
     
     return NPOS;
 }
 
-unsigned String::Find(const String& str, unsigned startPos) const
+unsigned String::Find(const String& str, unsigned startPos, bool caseSensitive) const
 {
     if (!str.length_ || str.length_ > length_)
         return NPOS;
     
     char first = str.buffer_[0];
-    
+    if (!caseSensitive)
+        first = tolower(first);
+
     for (unsigned i = startPos; i <= length_ - str.length_; ++i)
     {
-        if (buffer_[i] == first)
+        char c = buffer_[i];
+        if (!caseSensitive)
+            c = tolower(c);
+
+        if (c == first)
         {
             unsigned skip = NPOS;
             bool found = true;
             for (unsigned j = 1; j < str.length_; ++j)
             {
-                char c = buffer_[i + j];
+                c = buffer_[i + j];
+                char d = str.buffer_[j];
+                if (!caseSensitive)
+                {
+                    c = tolower(c);
+                    d = tolower(d);
+                }
+
                 if (skip == NPOS && c == first)
                     skip = i + j - 1;
-                if (c != str.buffer_[j])
+
+                if (c != d)
                 {
                     found = false;
                     if (skip != NPOS)
@@ -563,21 +610,33 @@ unsigned String::Find(const String& str, unsigned startPos) const
     return NPOS;
 }
 
-unsigned String::FindLast(char c, unsigned startPos) const
+unsigned String::FindLast(char c, unsigned startPos, bool caseSensitive) const
 {
     if (startPos >= length_)
         startPos = length_ - 1;
     
-    for (unsigned i = startPos; i < length_; --i)
+    if (caseSensitive)
+    {
+        for (unsigned i = startPos; i < length_; --i)
+        {
+            if (buffer_[i] == c)
+                return i;
+        }
+    }
+    else
     {
-        if (buffer_[i] == c)
-            return i;
+        c = tolower(c);
+        for (unsigned i = startPos; i < length_; --i)
+        {
+            if (tolower(buffer_[i]) == c)
+                return i;
+        }
     }
     
     return NPOS;
 }
 
-unsigned String::FindLast(const String& str, unsigned startPos) const
+unsigned String::FindLast(const String& str, unsigned startPos, bool caseSensitive) const
 {
     if (!str.length_ || str.length_ > length_)
         return NPOS;
@@ -585,16 +644,29 @@ unsigned String::FindLast(const String& str, unsigned startPos) const
         startPos = length_ - str.length_;
     
     char first = str.buffer_[0];
-    
+    if (!caseSensitive)
+        first = tolower(first);
+
     for (unsigned i = startPos; i < length_; --i)
     {
-        if (buffer_[i] == first)
+        char c = buffer_[i];
+        if (!caseSensitive)
+            c = tolower(c);
+
+        if (c == first)
         {
             bool found = true;
             for (unsigned j = 1; j < str.length_; ++j)
             {
-                char c = buffer_[i + j];
-                if (c != str.buffer_[j])
+                c = buffer_[i + j];
+                char d = str.buffer_[j];
+                if (!caseSensitive)
+                {
+                    c = tolower(c);
+                    d = tolower(d);
+                }
+
+                if (c != d)
                 {
                     found = false;
                     break;
@@ -608,14 +680,14 @@ unsigned String::FindLast(const String& str, unsigned startPos) const
     return NPOS;
 }
 
-bool String::StartsWith(const String& str) const
+bool String::StartsWith(const String& str, bool caseSensitive) const
 {
-    return Find(str) == 0;
+    return Find(str, 0, caseSensitive) == 0;
 }
 
-bool String::EndsWith(const String& str) const
+bool String::EndsWith(const String& str, bool caseSensitive) const
 {
-    return FindLast(str) == Length() - str.Length();
+    return FindLast(str, Length() - 1, caseSensitive) == Length() - str.Length();
 }
 
 int String::Compare(const String& str, bool caseSensitive) const

+ 12 - 10
Source/Engine/Container/Str.h

@@ -274,17 +274,19 @@ public:
     const char& At(unsigned index) const { assert(index < length_); return buffer_[index]; }
     
     /// Replace all occurrences of a character.
-    void Replace(char replaceThis, char replaceWith);
+    void Replace(char replaceThis, char replaceWith, bool caseSensitive = true);
     /// Replace all occurrences of a string.
-    void Replace(const String& replaceThis, const String& replaceWith);
+    void Replace(const String& replaceThis, const String& replaceWith, bool caseSensitive = true);
     /// Replace a substring.
     void Replace(unsigned pos, unsigned length, const String& replaceWith);
+    /// Replace a substring with a C string.
+    void Replace(unsigned pos, unsigned length, const char* replaceWith);
     /// Replace a substring by iterators.
     Iterator Replace(const Iterator& start, const Iterator& end, const String& replaceWith);
     /// Return a string with all occurrences of a character replaced.
-    String Replaced(char replaceThis, char replaceWith) const;
+    String Replaced(char replaceThis, char replaceWith, bool caseSensitive = true) const;
     /// Return a string with all occurrences of a string replaced.
-    String Replaced(const String& replaceThis, const String& replaceWith) const;
+    String Replaced(const String& replaceThis, const String& replaceWith, bool caseSensitive = true) const;
     /// Append a string.
     String& Append(const String& str);
     /// Append a C string.
@@ -347,17 +349,17 @@ public:
     /// Join substrings with a 'glue' string.
     void Join(const Vector<String>& subStrings, String glue);
     /// Return index to the first occurrence of a string, or NPOS if not found.
-    unsigned Find(const String& str, unsigned startPos = 0) const;
+    unsigned Find(const String& str, unsigned startPos = 0, bool caseSensitive = true) const;
     /// Return index to the first occurrence of a character, or NPOS if not found.
-    unsigned Find(char c, unsigned startPos = 0) const;
+    unsigned Find(char c, unsigned startPos = 0, bool caseSensitive = true) const;
     /// Return index to the last occurrence of a string, or NPOS if not found.
-    unsigned FindLast(const String& str, unsigned startPos = NPOS) const;
+    unsigned FindLast(const String& str, unsigned startPos = NPOS, bool caseSensitive = true) const;
     /// Return index to the last occurrence of a character, or NPOS if not found.
-    unsigned FindLast(char c, unsigned startPos = NPOS) const;
+    unsigned FindLast(char c, unsigned startPos = NPOS, bool caseSensitive = true) const;
     /// Return whether starts with a string.
-    bool StartsWith(const String& str) const;
+    bool StartsWith(const String& str, bool caseSensitive = true) const;
     /// Return whether ends with a string.
-    bool EndsWith(const String& str) const;
+    bool EndsWith(const String& str, bool casSensitive = true) const;
     /// Return the C string.
     const char* CString() const { return buffer_; }
     /// Return length.

+ 3 - 3
Source/Engine/Engine/Console.cpp

@@ -108,10 +108,10 @@ void Console::SetDefaultStyle(XMLFile* style)
 void Console::SetVisible(bool enable)
 {
     // Check if we have script subsystem
-    bool hasScriptSubsystem = GetSubsystem<Script>();
+    bool hasScriptSubsystem = GetSubsystem<Script>() != 0;
     #ifdef ENABLE_LUA
     if (!hasScriptSubsystem)
-        hasScriptSubsystem = GetSubsystem<LuaScript>();
+        hasScriptSubsystem = GetSubsystem<LuaScript>() != 0;
     #endif
 
     lineEdit_->SetVisible(hasScriptSubsystem);
@@ -186,7 +186,7 @@ bool Console::IsVisible() const
 
 unsigned Console::GetNumRows() const
 {
-    rowContainer_->GetNumChildren();
+    return rowContainer_->GetNumChildren();
 }
 
 const String& Console::GetHistoryRow(unsigned index) const

+ 1 - 1
Source/Engine/Graphics/AnimatedModel.cpp

@@ -162,7 +162,7 @@ void AnimatedModel::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQu
             {
                 // Follow with an OBB test if required
                 Matrix3x4 inverse = transform.Inverse();
-                Ray localRay(inverse * query.ray_.origin_, inverse * Vector4(query.ray_.direction_, 0.0f));
+                Ray localRay = query.ray_.Transformed(inverse);
                 distance = localRay.HitDistance(box);
                 if (distance >= query.maxDistance_)
                     continue;

+ 1 - 1
Source/Engine/Graphics/CustomGeometry.cpp

@@ -89,7 +89,7 @@ void CustomGeometry::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQ
     case RAY_OBB:
     case RAY_TRIANGLE:
         Matrix3x4 inverse(node_->GetWorldTransform().Inverse());
-        Ray localRay(inverse * query.ray_.origin_, inverse * Vector4(query.ray_.direction_, 0.0f));
+        Ray localRay = query.ray_.Transformed(inverse);
         float distance = localRay.HitDistance(boundingBox_);
         if (distance < query.maxDistance_)
         {

+ 1 - 1
Source/Engine/Graphics/Light.cpp

@@ -175,7 +175,7 @@ void Light::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryResul
     case RAY_OBB:
         {
             Matrix3x4 inverse(node_->GetWorldTransform().Inverse());
-            Ray localRay(inverse * query.ray_.origin_, inverse * Vector4(query.ray_.direction_, 0.0f));
+            Ray localRay = query.ray_.Transformed(inverse);
             distance = localRay.HitDistance(GetWorldBoundingBox().Transformed(inverse));
             if (distance >= query.maxDistance_)
                 return;

+ 1 - 1
Source/Engine/Graphics/StaticModel.cpp

@@ -84,7 +84,7 @@ void StaticModel::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQuer
     case RAY_OBB:
     case RAY_TRIANGLE:
         Matrix3x4 inverse(node_->GetWorldTransform().Inverse());
-        Ray localRay(inverse * query.ray_.origin_, inverse * Vector4(query.ray_.direction_, 0.0f));
+        Ray localRay = query.ray_.Transformed(inverse);
         float distance = localRay.HitDistance(boundingBox_);
         if (distance < query.maxDistance_)
         {

+ 1 - 1
Source/Engine/Graphics/TerrainPatch.cpp

@@ -85,7 +85,7 @@ void TerrainPatch::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQue
     case RAY_OBB:
     case RAY_TRIANGLE:
         Matrix3x4 inverse(node_->GetWorldTransform().Inverse());
-        Ray localRay(inverse * query.ray_.origin_, inverse * Vector4(query.ray_.direction_, 0.0f));
+        Ray localRay = query.ray_.Transformed(inverse);
         float distance = localRay.HitDistance(boundingBox_);
         if (distance < query.maxDistance_)
         {

+ 8 - 6
Source/Engine/IO/FileSystem.cpp

@@ -586,7 +586,7 @@ void FileSystem::ScanDirInternal(Vector<String>& result, String path, const Stri
     #endif
 }
 
-void SplitPath(const String& fullPath, String& pathName, String& fileName, String& extension)
+void SplitPath(const String& fullPath, String& pathName, String& fileName, String& extension, bool lowercaseExtension)
 {
     String fullPathCopy = GetInternalPath(fullPath);
 
@@ -595,7 +595,9 @@ void SplitPath(const String& fullPath, String& pathName, String& fileName, Strin
 
     if (extPos != String::NPOS && (pathPos == String::NPOS || extPos > pathPos))
     {
-        extension = fullPathCopy.Substring(extPos).ToLower();
+        extension = fullPathCopy.Substring(extPos);
+        if (lowercaseExtension)
+            extension = extension.ToLower();
         fullPathCopy = fullPathCopy.Substring(0, extPos);
     }
     else
@@ -628,17 +630,17 @@ String GetFileName(const String& fullPath)
     return file;
 }
 
-String GetExtension(const String& fullPath)
+String GetExtension(const String& fullPath, bool lowercaseExtension)
 {
     String path, file, extension;
-    SplitPath(fullPath, path, file, extension);
+    SplitPath(fullPath, path, file, extension, lowercaseExtension);
     return extension;
 }
 
-String GetFileNameAndExtension(const String& fileName)
+String GetFileNameAndExtension(const String& fileName, bool lowercaseExtension)
 {
     String path, file, extension;
-    SplitPath(fileName, path, file, extension);
+    SplitPath(fileName, path, file, extension, lowercaseExtension);
     return file + extension;
 }
 

+ 6 - 6
Source/Engine/IO/FileSystem.h

@@ -94,16 +94,16 @@ private:
     mutable String programDir_;
 };
 
-/// Split a full path to path, filename and extension. The extension will be converted to lowercase.
-URHO3D_API void SplitPath(const String& fullPath, String& pathName, String& fileName, String& extension);
+/// Split a full path to path, filename and extension. The extension will be converted to lowercase by default.
+URHO3D_API void SplitPath(const String& fullPath, String& pathName, String& fileName, String& extension, bool lowercaseExtension = true);
 /// Return the path from a full path.
 URHO3D_API String GetPath(const String& fullPath);
 /// Return the filename from a full path.
 URHO3D_API String GetFileName(const String& fullPath);
-/// Return the extension from a full path, converted to lowercase.
-URHO3D_API String GetExtension(const String& fullPath);
-/// Return the filename and extension from a full path. The extension will be converted to lowercase.
-URHO3D_API String GetFileNameAndExtension(const String& fullPath);
+/// Return the extension from a full path, converted to lowercase by default.
+URHO3D_API String GetExtension(const String& fullPath, bool lowercaseExtension = true);
+/// Return the filename and extension from a full path. The case of the extension is preserved by default, so that the file can be opened in case-sensitive operating systems.
+URHO3D_API String GetFileNameAndExtension(const String& fullPath, bool lowercaseExtension = false);
 /// Replace the extension of a file name with another.
 URHO3D_API String ReplaceExtension(const String& fullPath, const String& newExtension);
 /// Add a slash at the end of the path if missing and convert to internal format (use slashes.)

+ 7 - 0
Source/Engine/Math/Ray.cpp

@@ -395,5 +395,12 @@ bool Ray::InsideGeometry(const void* vertexData, unsigned vertexSize, const void
     return false;
 }
 
+Ray Ray::Transformed(const Matrix3x4& transform) const
+{
+    Ray ret;
+    ret.origin_ = transform * origin_;
+    ret.direction_ = transform * Vector4(direction_, 0.0f);
+    return ret;
+}
 
 }

+ 5 - 2
Source/Engine/Math/Ray.h

@@ -23,6 +23,7 @@
 #pragma once
 
 #include "Vector3.h"
+#include "Matrix3x4.h"
 
 namespace Urho3D
 {
@@ -41,7 +42,7 @@ public:
     {
     }
     
-    /// Construct from origin and direction.
+    /// Construct from origin and direction. The direction will be normalized.
     Ray(const Vector3& origin, const Vector3& direction) :
         origin_(origin),
         direction_(direction.Normalized())
@@ -99,7 +100,9 @@ public:
     bool InsideGeometry(const void* vertexData, unsigned vertexSize, unsigned vertexStart, unsigned vertexCount) const;
     /// Return whether ray is inside indexed geometry.
     bool InsideGeometry(const void* vertexData, unsigned vertexSize, const void* indexData, unsigned indexSize, unsigned indexStart, unsigned indexCount) const;
-    
+    /// Return transformed by a 3x4 matrix. This may result in a non-normalized direction.
+    Ray Transformed(const Matrix3x4& transform) const;
+
     /// Ray origin.
     Vector3 origin_;
     /// Ray direction.

+ 22 - 0
Source/Engine/Resource/ResourceCache.cpp

@@ -581,6 +581,28 @@ String ResourceCache::SanitateResourceName(const String& nameIn) const
     String name = GetInternalPath(nameIn);
     name.Replace("../", "");
     name.Replace("./", "");
+
+    // If the path refers to one of the resource directories, normalize the resource name
+    FileSystem* fileSystem = GetSubsystem<FileSystem>();
+    if (fileSystem && resourceDirs_.Size())
+    {
+        String namePath = GetPath(name);
+        String exePath = fileSystem->GetProgramDir();
+        for (unsigned i = 0; i < resourceDirs_.Size(); ++i)
+        {
+            String relativeResourcePath = resourceDirs_[i];
+            if (relativeResourcePath.StartsWith(exePath))
+                relativeResourcePath = relativeResourcePath.Substring(exePath.Length());
+            
+            if (namePath.StartsWith(resourceDirs_[i], false))
+                namePath = namePath.Substring(resourceDirs_[i].Length());
+            else if (namePath.StartsWith(relativeResourcePath, false))
+                namePath = namePath.Substring(relativeResourcePath.Length());
+        }
+
+        name = namePath + GetFileNameAndExtension(name);
+    }
+
     return name;
 }
 

+ 1 - 1
Source/Engine/Resource/ResourceCache.h

@@ -133,7 +133,7 @@ public:
     
     /// Return either the path itself or its parent, based on which of them has recognized resource subdirectories.
     String GetPreferredResourceDir(const String& path) const;
-    /// Remove unsupported constructs from the resource name to prevent ambiguity.
+    /// Remove unsupported constructs from the resource name to prevent ambiguity, and normalize absolute filename to resource path relative if possible.
     String SanitateResourceName(const String& name) const;
     /// Store a hash-to-name mapping.
     void StoreNameHash(const String& name);

+ 10 - 10
Source/Engine/Script/Addons.cpp

@@ -1594,17 +1594,17 @@ void RegisterString(asIScriptEngine *engine)
     engine->RegisterObjectMethod("String", "String opAdd(const String&in) const", asMETHODPR(String, operator +, (const String&) const, String), asCALL_THISCALL);
     engine->RegisterObjectMethod("String", "uint8 &opIndex(uint)", asFUNCTION(StringCharAt), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("String", "const uint8 &opIndex(uint) const", asFUNCTION(StringCharAt), asCALL_CDECL_OBJLAST);
-    engine->RegisterObjectMethod("String", "void Replace(uint8, uint8)", asMETHODPR(String, Replace, (char, char), void), asCALL_THISCALL);
-    engine->RegisterObjectMethod("String", "void Replace(const String&in, const String&in)", asMETHODPR(String, Replace, (const String&, const String&), void), asCALL_THISCALL);
-    engine->RegisterObjectMethod("String", "String Replaced(uint8, uint8) const", asMETHODPR(String, Replaced, (char, char) const, String), asCALL_THISCALL);
-    engine->RegisterObjectMethod("String", "String Replaced(const String&in, const String&in) const", asMETHODPR(String, Replaced, (const String&, const String&) const, String), asCALL_THISCALL);
+    engine->RegisterObjectMethod("String", "void Replace(uint8, uint8, bool caseSensitive = true)", asMETHODPR(String, Replace, (char, char, bool), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("String", "void Replace(const String&in, const String&in, bool caseSensitive = true)", asMETHODPR(String, Replace, (const String&, const String&, bool), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("String", "String Replaced(uint8, uint8, bool caseSensitive = true) const", asMETHODPR(String, Replaced, (char, char, bool) const, String), asCALL_THISCALL);
+    engine->RegisterObjectMethod("String", "String Replaced(const String&in, const String&in, bool caseSensitive = true) const", asMETHODPR(String, Replaced, (const String&, const String&, bool) const, String), asCALL_THISCALL);
     engine->RegisterObjectMethod("String", "void Resize(uint)", asFUNCTION(StringResize), asCALL_CDECL_OBJLAST);
-    engine->RegisterObjectMethod("String", "uint Find(const String&in, uint start = 0) const", asMETHODPR(String, Find, (const String&, unsigned) const, unsigned), asCALL_THISCALL);
-    engine->RegisterObjectMethod("String", "uint Find(uint8, uint start = 0) const", asMETHODPR(String, Find, (char, unsigned) const, unsigned), asCALL_THISCALL);
-    engine->RegisterObjectMethod("String", "uint FindLast(const String&in, uint start = 0xffffffff) const", asMETHODPR(String, FindLast, (const String&, unsigned) const, unsigned), asCALL_THISCALL);
-    engine->RegisterObjectMethod("String", "uint FindLast(uint8, uint start = 0xffffffff) const", asMETHODPR(String, FindLast, (char, unsigned) const, unsigned), asCALL_THISCALL);
-    engine->RegisterObjectMethod("String", "bool StartsWith(const String&in) const", asMETHOD(String, StartsWith), asCALL_THISCALL);
-    engine->RegisterObjectMethod("String", "bool EndsWith(const String&in) const", asMETHOD(String, EndsWith), asCALL_THISCALL);
+    engine->RegisterObjectMethod("String", "uint Find(const String&in, uint start = 0, bool caseSensitive = true) const", asMETHODPR(String, Find, (const String&, unsigned, bool) const, unsigned), asCALL_THISCALL);
+    engine->RegisterObjectMethod("String", "uint Find(uint8, uint start = 0, bool caseSensitive = true) const", asMETHODPR(String, Find, (char, unsigned, bool) const, unsigned), asCALL_THISCALL);
+    engine->RegisterObjectMethod("String", "uint FindLast(const String&in, uint start = 0xffffffff, bool caseSensitive = true) const", asMETHODPR(String, FindLast, (const String&, unsigned, bool) const, unsigned), asCALL_THISCALL);
+    engine->RegisterObjectMethod("String", "uint FindLast(uint8, uint start = 0xffffffff, bool caseSensitive = true) const", asMETHODPR(String, FindLast, (char, unsigned, bool) const, unsigned), asCALL_THISCALL);
+    engine->RegisterObjectMethod("String", "bool StartsWith(const String&in, bool caseSensitive = true) const", asMETHOD(String, StartsWith), asCALL_THISCALL);
+    engine->RegisterObjectMethod("String", "bool EndsWith(const String&in, bool caseSensitive = true) const", asMETHOD(String, EndsWith), asCALL_THISCALL);
     engine->RegisterObjectMethod("String", "String Substring(uint) const", asMETHODPR(String, Substring, (unsigned) const, String), asCALL_THISCALL);
     engine->RegisterObjectMethod("String", "String Substring(uint, uint) const", asMETHODPR(String, Substring, (unsigned, unsigned) const, String), asCALL_THISCALL);
     engine->RegisterObjectMethod("String", "String ToUpper() const", asMETHOD(String, ToUpper), asCALL_THISCALL);

+ 2 - 2
Source/Engine/Script/IOAPI.cpp

@@ -330,8 +330,8 @@ void RegisterFileSystem(asIScriptEngine* engine)
 
     engine->RegisterGlobalFunction("String GetPath(const String&in)", asFUNCTION(GetPath), asCALL_CDECL);
     engine->RegisterGlobalFunction("String GetFileName(const String&in)", asFUNCTION(GetFileName), asCALL_CDECL);
-    engine->RegisterGlobalFunction("String GetExtension(const String&in)", asFUNCTION(GetExtension), asCALL_CDECL);
-    engine->RegisterGlobalFunction("String GetFileNameAndExtension(const String&in)", asFUNCTION(GetFileNameAndExtension), asCALL_CDECL);
+    engine->RegisterGlobalFunction("String GetExtension(const String&in, bool lowercaseExtension = true)", asFUNCTION(GetExtension), asCALL_CDECL);
+    engine->RegisterGlobalFunction("String GetFileNameAndExtension(const String&in, bool lowercaseExtension = false)", asFUNCTION(GetFileNameAndExtension), asCALL_CDECL);
     engine->RegisterGlobalFunction("String ReplaceExtension(const String&in, const String&in)", asFUNCTION(ReplaceExtension), asCALL_CDECL);
     engine->RegisterGlobalFunction("String AddTrailingSlash(const String&in)", asFUNCTION(AddTrailingSlash), asCALL_CDECL);
     engine->RegisterGlobalFunction("String RemoveTrailingSlash(const String&in)", asFUNCTION(RemoveTrailingSlash), asCALL_CDECL);

+ 1 - 0
Source/Engine/Script/MathAPI.cpp

@@ -703,6 +703,7 @@ static void RegisterRay(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Ray", "float HitDistance(const BoundingBox&in) const", asMETHODPR(Ray, HitDistance, (const BoundingBox&) const, float), asCALL_THISCALL);
     engine->RegisterObjectMethod("Ray", "float HitDistance(const Frustum&in, bool solidInside = true) const", asMETHODPR(Ray, HitDistance, (const Frustum&, bool) const, float), asCALL_THISCALL);
     engine->RegisterObjectMethod("Ray", "float HitDistance(const Vector3&in, const Vector3&in, const Vector3&in) const", asMETHODPR(Ray, HitDistance, (const Vector3&, const Vector3&, const Vector3&) const, float), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Ray", "Ray Transformed(const Matrix3x4&in) const", asMETHOD(Ray, Transformed), asCALL_THISCALL);
     engine->RegisterObjectProperty("Ray", "Vector3 origin", offsetof(Ray, origin_));
     engine->RegisterObjectProperty("Ray", "Vector3 direction", offsetof(Ray, direction_));
 }

+ 1 - 1
Source/Engine/UI/Font.cpp

@@ -178,7 +178,7 @@ bool Font::Load(Deserializer& source)
         return false;
     }
 
-    String ext = GetExtension(GetName()).ToLower();
+    String ext = GetExtension(GetName());
     if (ext == ".ttf")
         fontType_ = FONT_TTF;
     else if (ext == ".xml" || ext == ".fnt")

+ 13 - 13
Source/Extras/LuaScript/pkgs/Container/Str.pkg

@@ -15,15 +15,15 @@ class String
     bool operator == (const char* rhs) const;
     tolua_outside bool StringEquals @ Equals(const String& rhs) const;
     tolua_outside bool StringEquals @ Equals(const char*& rhs) const;
-    
-    void Replace(const String& replaceThis, const String& replaceWith);
-    void Replace(const char* replaceThis, const char* replaceWith);
+
+    void Replace(const String& replaceThis, const String& replaceWith, bool caseSensitive = true);
+    void Replace(const char* replaceThis, const char* replaceWith, bool caseSensitive = true);
     
     void Replace(unsigned pos, unsigned length, const String& replaceWith);
     void Replace(unsigned pos, unsigned length, const char* replaceWith);
     
-    String Replaced(const String& replaceThis, const String& replaceWith) const;
-    String Replaced(const char* replaceThis, const char* replaceWith) const;
+    String Replaced(const String& replaceThis, const String& replaceWith, bool caseSensitive = true) const;
+    String Replaced(const char* replaceThis, const char* replaceWith, bool caseSensitive = true) const;
     
     String& Append(const String& str);
     String& Append(const char* str);
@@ -44,16 +44,16 @@ class String
     String ToUpper() const;
     String ToLower() const;
     
-    unsigned Find(const String& str, unsigned startPos = 0) const;
-    unsigned Find(const char* str, unsigned startPos = 0) const;
+    unsigned Find(const String& str, unsigned startPos = 0, bool caseSensitive = true) const;
+    unsigned Find(const char* str, unsigned startPos = 0, bool caseSensitive = true) const;
     
-    unsigned FindLast(const String& str, unsigned startPos = String::NPOS) const;
-    unsigned FindLast(const char* str, unsigned startPos = String::NPOS) const;
+    unsigned FindLast(const String& str, unsigned startPos = String::NPOS, bool caseSensitive = true) const;
+    unsigned FindLast(const char* str, unsigned startPos = String::NPOS, bool caseSensitive = true) const;
     
-    bool StartsWith(const String& str) const;
-    bool StartsWith(const char* str) const;
-    bool EndsWith(const String& str) const;
-    bool EndsWith(const char* str) const;
+    bool StartsWith(const String& str, bool caseSensitive = true) const;
+    bool StartsWith(const char* str, bool caseSensitive = true) const;
+    bool EndsWith(const String& str, bool caseSensitive = true) const;
+    bool EndsWith(const char* str, bool caseSensitive = true) const;
     
     const char* CString() const;
     unsigned Length() const;

+ 3 - 3
Source/Extras/LuaScript/pkgs/IO/FileSystem.pkg

@@ -35,7 +35,7 @@ class FileSystem : public Object
     
     String GetCurrentDir() const;
     bool HasRegisteredPaths() const;
-    
+
     bool CheckAccess(const String& pathName) const;
     bool CheckAccess(const char* pathName) const;
     
@@ -58,8 +58,8 @@ class FileSystem : public Object
 
 String GetPath(const String& fullPath);
 String GetFileName(const String& fullPath);
-String GetExtension(const String& fullPath);
-String GetFileNameAndExtension(const String& fullPath);
+String GetExtension(const String& fullPath, bool lowercaseExtension = true);
+String GetFileNameAndExtension(const String& fullPath, bool lowercaseExtension = false);
 String ReplaceExtension(const String& fullPath, const String& newExtension);
 String AddTrailingSlash(const String& pathName);
 String RemoveTrailingSlash(const String& pathName);

+ 2 - 0
Source/Extras/LuaScript/pkgs/Math/Ray.pkg

@@ -22,6 +22,8 @@ class Ray
     float HitDistance(const Sphere& sphere) const;
     float HitDistance(const Vector3& v0, const Vector3& v1, const Vector3& v2) const;
     
+    Ray Transformed(const Matrix3x4& transform) const;
+    
     Vector3 origin_ @ origin;
     Vector3 direction_ @ direction;
 };

+ 1 - 2
Source/Tools/AssetImporter/AssetImporter.cpp

@@ -320,8 +320,7 @@ void Run(const Vector<String>& arguments)
             // If output file already has the Models/ path (model mode), do not take it into the resource path
             if (command == "model")
             {
-                String resPathLower = resourcePath_.ToLower();
-                if (resPathLower.FindLast("models/") == resPathLower.Length() - 7)
+                if (resourcePath_.EndsWith("Models/", false))
                     resourcePath_ = resourcePath_.Substring(0, resourcePath_.Length() - 7);
             }
             if (resourcePath_.Empty())

+ 1 - 1
Source/Tools/Urho3D/Urho3D.cpp

@@ -136,7 +136,7 @@ void Urho::Setup()
 void Urho::Start()
 {
 #ifdef ENABLE_LUA
-    String extension = GetExtension(scriptFileName_).ToLower();
+    String extension = GetExtension(scriptFileName_);
     if (extension != ".lua")
     {
 #endif