Browse Source

Touch input on Android.
Run NinjaSnowWar by default on Android.
Rudimentary touch controls (virtual on-screen joystick) in NinjaSnowWar.
Improved OpenGL ES depth bias.
Support GetUserDocumentsDir() on Android (return the getFilesDir() path.)
Fixed OpenGL ES mode erroneously attempting to use shadowed point lights.
Added .bat file to copy Data & CoreData directories as Android assets.

Lasse Öörni 13 years ago
parent
commit
0706ae397f

+ 1 - 1
Android/AndroidManifest.xml

@@ -3,7 +3,7 @@
     package="org.libsdl.app"
     android:versionCode="1"
     android:versionName="1.0">
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.INTERNET" />
     <uses-feature android:glEsVersion="0x00020000" />
     <uses-sdk android:targetSdkVersion="8" android:minSdkVersion="8" />
     <application android:label="@string/app_name" android:icon="@drawable/icon">

+ 4 - 0
Android/CopyData.bat

@@ -0,0 +1,4 @@
+md assets\Data
+xcopy ..\Bin\Data\*.* assets\Data /S /E /C /Y
+md assets\CoreData
+xcopy ..\Bin\CoreData\*.* assets\CoreData /S /E /C /Y

+ 36 - 28
Android/src/org/libsdl/app/SDLActivity.java

@@ -1,4 +1,4 @@
-// Modified by Lasse Öörni for Urho3D
+// Modified by Lasse Oorni for Urho3D
 
 package org.libsdl.app;
 
@@ -82,8 +82,8 @@ public class SDLActivity extends Activity {
     }
 
     protected void onDestroy() {
-        super.onDestroy();
         Log.v("SDL", "onDestroy()");
+        super.onDestroy();
 
         mFinished = true;
 
@@ -109,6 +109,10 @@ public class SDLActivity extends Activity {
         super.onConfigurationChanged(newConfig);
     }
 
+    public static SDLActivity getSingleton() {
+        return mSingleton;
+    }
+
     // Messages from the SDLMain thread
     static int COMMAND_CHANGE_TITLE = 1;
     static int COMMAND_FINISH = 2;
@@ -137,7 +141,7 @@ public class SDLActivity extends Activity {
     }
 
     // C functions we call
-    public static native void nativeInit();
+    public static native void nativeInit(String filesDir);
     public static native void nativeQuit();
     public static native void nativePause();
     public static native void nativeResume();
@@ -145,7 +149,7 @@ public class SDLActivity extends Activity {
     public static native void onNativeKeyDown(int keycode);
     public static native void onNativeKeyUp(int keycode);
     public static native void onNativeTouch(int touchDevId, int pointerFingerId,
-                                            int action, float x, 
+                                            int action, float x,
                                             float y, float p);
     public static native void onNativeAccel(float x, float y, float z);
     public static native void nativeRunAudioThread();
@@ -165,7 +169,7 @@ public class SDLActivity extends Activity {
         // Called from SDLMain() thread and can't directly affect the view
         mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
     }
-    
+
     public static void finishActivity() {
         mSingleton.sendCommand(COMMAND_FINISH, null);
     }
@@ -310,34 +314,34 @@ public class SDLActivity extends Activity {
 
     // Audio
     private static Object buf;
-    
+
     public static Object audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
         int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
         int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
         int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
-        
+
         Log.v("SDL", "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + ((float)sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
-        
+
         // Let the user pick a larger buffer if they really want -- but ye
         // gods they probably shouldn't, the minimums are horrifyingly high
         // latency already
         desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
-        
+
         mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
                 channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
-        
+
         audioStartThread();
-        
+
         Log.v("SDL", "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + ((float)mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");
-        
+
         if (is16Bit) {
             buf = new short[desiredFrames * (isStereo ? 2 : 1)];
         } else {
-            buf = new byte[desiredFrames * (isStereo ? 2 : 1)]; 
+            buf = new byte[desiredFrames * (isStereo ? 2 : 1)];
         }
         return buf;
     }
-    
+
     public static void audioStartThread() {
         mAudioThread = new Thread(new Runnable() {
             public void run() {
@@ -345,12 +349,12 @@ public class SDLActivity extends Activity {
                 nativeRunAudioThread();
             }
         });
-        
+
         // I'd take REALTIME if I could get it!
         mAudioThread.setPriority(Thread.MAX_PRIORITY);
         mAudioThread.start();
     }
-    
+
     public static void audioWriteShortBuffer(short[] buffer) {
         for (int i = 0; i < buffer.length; ) {
             int result = mAudioTrack.write(buffer, i, buffer.length - i);
@@ -368,7 +372,7 @@ public class SDLActivity extends Activity {
             }
         }
     }
-    
+
     public static void audioWriteByteBuffer(byte[] buffer) {
         for (int i = 0; i < buffer.length; ) {
             int result = mAudioTrack.write(buffer, i, buffer.length - i);
@@ -412,7 +416,7 @@ public class SDLActivity extends Activity {
 class SDLMain implements Runnable {
     public void run() {
         // Runs SDL_main()
-        SDLActivity.nativeInit();
+        SDLActivity.nativeInit(SDLActivity.getSingleton().getFilesDir().getAbsolutePath());
 
         //Log.v("SDL", "SDL thread terminated");
         SDLActivity.finishActivity();
@@ -436,14 +440,14 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
     public SDLSurface(Context context) {
         super(context);
         getHolder().addCallback(this);
-    
+
         setFocusable(true);
         setFocusableInTouchMode(true);
         requestFocus();
-        setOnKeyListener(this); 
-        setOnTouchListener(this);   
+        setOnKeyListener(this);
+        setOnTouchListener(this);
 
-        mSensorManager = (SensorManager)context.getSystemService("sensor");  
+        mSensorManager = (SensorManager)context.getSystemService("sensor");
     }
 
     // Called when we have a valid drawing surface
@@ -525,6 +529,10 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
     // Key events
     public boolean onKey(View  v, int keyCode, KeyEvent event) {
 
+        // Urho3D: let the home & volume keys be handled by the system
+        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_HOME)
+            return false;
+
         if (event.getAction() == KeyEvent.ACTION_DOWN) {
             //Log.v("SDL", "key down: " + keyCode);
             SDLActivity.onNativeKeyDown(keyCode);
@@ -535,7 +543,7 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
             SDLActivity.onNativeKeyUp(keyCode);
             return true;
         }
-        
+
         return false;
     }
 
@@ -568,21 +576,21 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
              }
         }
       return true;
-   } 
+   }
 
     // Sensor events
     public void enableSensor(int sensortype, boolean enabled) {
         // TODO: This uses getDefaultSensor - what if we have >1 accels?
         if (enabled) {
-            mSensorManager.registerListener(this, 
-                            mSensorManager.getDefaultSensor(sensortype), 
+            mSensorManager.registerListener(this,
+                            mSensorManager.getDefaultSensor(sensortype),
                             SensorManager.SENSOR_DELAY_GAME, null);
         } else {
-            mSensorManager.unregisterListener(this, 
+            mSensorManager.unregisterListener(this,
                             mSensorManager.getDefaultSensor(sensortype));
         }
     }
-    
+
     public void onAccuracyChanged(Sensor sensor, int accuracy) {
         // TODO
     }

+ 2 - 0
Bin/Data/Scripts/Editor/EditorUI.as

@@ -41,6 +41,8 @@ void CreateCursor()
     cursor.style = uiStyle;
     cursor.SetPosition(graphics.width / 2, graphics.height / 2);
     ui.cursor = cursor;
+    if (GetPlatform() == "Android")
+        ui.cursor.visible = false;
 }
 
 void CreateMenuBar()

+ 76 - 0
Bin/Data/Scripts/NinjaSnowWar.as

@@ -9,6 +9,7 @@
 #include "Scripts/Utilities/Network.as"
 
 const float mouseSensitivity = 0.125;
+const float touchSensitivity = 2.0;
 const float cameraMinDist = 25;
 const float cameraMaxDist = 500;
 const float cameraSafetyDist = 30;
@@ -29,6 +30,8 @@ Text@ hiscoreText;
 Text@ messageText;
 BorderImage@ healthBar;
 BorderImage@ sight;
+BorderImage@ moveButton;
+BorderImage@ shootButton;
 SoundSource@ musicSource;
 
 Controls playerControls;
@@ -44,6 +47,12 @@ float powerupSpawnTimer = 0;
 uint clientNodeID = 0;
 int clientScore = 0;
 
+bool touchEnabled = false;
+int touchButtonSize = 96;
+int moveTouchID = -1;
+int rotateTouchID = -1;
+int fireTouchID = -1;
+
 Array<Player> players;
 Array<HiscoreEntry> hiscores;
 
@@ -73,6 +82,8 @@ void Start()
     SubscribeToEvent("Kill", "HandleKill");
     SubscribeToEvent("ScreenMode", "HandleScreenMode");
 
+    if (touchEnabled)
+        SubscribeToEvent("TouchEnd", "HandleTouchEnd");
     if (singlePlayer)
         StartGame(null);
 }
@@ -225,6 +236,25 @@ void CreateOverlays()
     healthBar.SetPosition(2, 2);
     healthBar.SetSize(116, 16);
     healthBorder.AddChild(healthBar);
+
+    if (GetPlatform() == "Android")
+    {
+        touchEnabled = true;
+
+        moveButton = BorderImage();
+        moveButton.texture = cache.GetResource("Texture2D", "Textures/TouchInput.png");
+        moveButton.imageRect = IntRect(0, 0, 96, 96);
+        moveButton.SetPosition(0, graphics.height - touchButtonSize);
+        moveButton.SetSize(touchButtonSize, touchButtonSize);
+        ui.root.AddChild(moveButton);
+
+        shootButton = BorderImage();
+        shootButton.texture = cache.GetResource("Texture2D", "Textures/TouchInput.png");
+        shootButton.imageRect = IntRect(96, 0, 192, 96);
+        shootButton.SetPosition(graphics.width - touchButtonSize, graphics.height - touchButtonSize);
+        shootButton.SetSize(touchButtonSize, touchButtonSize);
+        ui.root.AddChild(shootButton);
+    }
 }
 
 void SetMessage(const String&in message)
@@ -374,6 +404,17 @@ void HandlePostRenderUpdate()
         gameScene.octree.DrawDebugGeometry(true);
 }
 
+void HandleTouchEnd(StringHash eventType, VariantMap& eventData)
+{
+    int touchID = eventData["TouchID"].GetInt();
+    if (touchID == moveTouchID)
+        moveTouchID = -1;
+    if (touchID == rotateTouchID)
+        rotateTouchID = -1;
+    if (touchID == fireTouchID)
+        fireTouchID = -1;
+}
+
 void HandleKeyDown(StringHash eventType, VariantMap& eventData)
 {
     int key = eventData["Key"].GetInt();
@@ -724,6 +765,41 @@ void UpdateControls()
         prevPlayerControls = playerControls;
         playerControls.Set(CTRL_ALL, false);
 
+        if (touchEnabled)
+        {
+            for (int i = 0; i < input.numTouches; ++i)
+            {
+                TouchState touch = input.touches[i];
+                if (touch.touchID == rotateTouchID || (touch.position.y < graphics.height - touchButtonSize || 
+                    (touch.position.x >= touchButtonSize && touch.position.x < graphics.width - touchButtonSize)))
+                {
+                    rotateTouchID = touch.touchID;
+                    playerControls.yaw += touchSensitivity * gameCamera.fov / graphics.height * touch.delta.x;
+                    playerControls.pitch += touchSensitivity * gameCamera.fov / graphics.height * touch.delta.y;
+                }
+                else if (touch.position.y >= graphics.height - touchButtonSize && touch.position.x < touchButtonSize)
+                {
+                    moveTouchID = touch.touchID;
+                    int relX = touch.position.x - touchButtonSize / 2;
+                    int relY = touch.position.y - (graphics.height - touchButtonSize / 2);
+                    if (relY < 0 && Abs(relX * 3 / 2) < Abs(relY))
+                        playerControls.Set(CTRL_UP, true);
+                    if (relY > 0 && Abs(relX * 3 / 2) < Abs(relY))
+                        playerControls.Set(CTRL_DOWN, true);
+                    if (relX < 0 && Abs(relY * 3 / 2) < Abs(relX))
+                        playerControls.Set(CTRL_LEFT, true);
+                    if (relX > 0 && Abs(relY * 3 / 2) < Abs(relX))
+                        playerControls.Set(CTRL_RIGHT, true);
+                }
+                else if (touch.position.y >= graphics.height - touchButtonSize && touch.position.x >= graphics.width - 
+                    touchButtonSize)
+                {
+                    fireTouchID = touch.touchID;
+                    playerControls.Set(CTRL_FIRE, true);
+                }
+            }
+        }
+
         // For the triggered actions (fire & jump) check also for press, in case the FPS is low
         // and the key was already released
         if ((console is null) || (!console.visible))

+ 3 - 1
Bin/Data/Scripts/TestScene.as

@@ -83,7 +83,9 @@ void InitUI()
     newCursor.style = uiStyle;
     newCursor.position = IntVector2(graphics.width / 2, graphics.height / 2);
     ui.cursor = newCursor;
-    
+    if (GetPlatform() == "Android")
+        ui.cursor.visible = false;
+
     downloadsText = Text();
     downloadsText.SetAlignment(HA_CENTER, VA_CENTER);
     downloadsText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 20);

+ 2 - 0
Bin/Data/Scripts/TestSceneOld.as

@@ -289,6 +289,8 @@ void InitUI()
     cursor.style = uiStyle;
     cursor.position = IntVector2(graphics.width / 2, graphics.height / 2);
     ui.cursor = cursor;
+    if (GetPlatform() == "Android")
+        ui.cursor.visible = false;
 }
 
 void CreateCamera()

BIN
Bin/Data/Textures/TouchInput.png


+ 1 - 1
Docs/GettingStarted.dox

@@ -54,7 +54,7 @@ Urho3D.exe <scriptfilename> [options]
 
 The scripting language used is AngelScript (http://www.angelcode.com/angelscript); the script files have .as extension and need to be placed under either the Data or CoreData subdirectories so that Urho3D.exe can find them. An application script is required to have the function void Start(), which will be executed before starting the engine main loop. It is this function's responsibility to initialize the application and to hook up to any necessary \ref Events "events", such as the update that happens every frame.
 
-On Android there are no command line options, so the Urho3D activity chooses to load the TestScene example. This can be changed from Urho3D.cpp.
+On Android there are no command line options, so running the NinjaSnowWar example is hardcoded. This can be changed from Urho3D.cpp.
 
 Currently, five example application scripts exist:
 

+ 11 - 0
Docs/ScriptAPI.dox

@@ -2368,6 +2368,15 @@ Properties:<br>
 - uint[] numOccluders (readonly)
 
 
+TouchState
+
+Properties:<br>
+- int touchID
+- IntVector2 position
+- IntVector2 delta
+- int pressure
+
+
 Input
 
 Properties:<br>
@@ -2385,6 +2394,8 @@ Properties:<br>
 - int mouseMoveX (readonly)
 - int mouseMoveY (readonly)
 - int mouseMoveWheel (readonly)
+- uint numTouches (readonly)
+- TouchState[] touches (readonly)
 - bool active (readonly)
 - bool minimized (readonly)
 

+ 14 - 0
Engine/Engine/InputAPI.cpp

@@ -108,8 +108,20 @@ static Input* GetInput()
     return GetScriptContext()->GetSubsystem<Input>();
 }
 
+static void ConstructTouchState(TouchState* ptr)
+{
+    new(ptr) TouchState();
+}
+
 static void RegisterInput(asIScriptEngine* engine)
 {
+    engine->RegisterObjectType("TouchState", sizeof(TouchState), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS);
+    engine->RegisterObjectBehaviour("TouchState", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructTouchState), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectProperty("TouchState", "int touchID", offsetof(TouchState, touchID_));
+    engine->RegisterObjectProperty("TouchState", "IntVector2 position", offsetof(TouchState, position_));
+    engine->RegisterObjectProperty("TouchState", "IntVector2 delta", offsetof(TouchState, delta_));
+    engine->RegisterObjectProperty("TouchState", "int pressure", offsetof(TouchState, pressure_));
+    
     RegisterObject<Input>(engine, "Input");
     engine->RegisterObjectMethod("Input", "void set_toggleFullscreen(bool)", asMETHOD(Input, SetToggleFullscreen), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "bool get_toggleFullscreen() const", asMETHOD(Input, GetToggleFullscreen), asCALL_THISCALL);
@@ -124,6 +136,8 @@ static void RegisterInput(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Input", "int get_mouseMoveX() const", asMETHOD(Input, GetMouseMoveX), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "int get_mouseMoveY() const", asMETHOD(Input, GetMouseMoveY), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "int get_mouseMoveWheel() const", asMETHOD(Input, GetMouseMoveWheel), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Input", "uint get_numTouches() const", asMETHOD(Input, GetNumTouches), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Input", "TouchState get_touches(uint) const", asMETHOD(Input, GetTouch), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "bool get_active() const", asMETHOD(Input, IsActive), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "bool get_minimized() const", asMETHOD(Input, IsMinimized), asCALL_THISCALL);
     engine->RegisterGlobalFunction("Input@+ get_input()", asFUNCTION(GetInput), asCALL_CDECL);

+ 5 - 0
Engine/Graphics/OpenGL/OGLGraphics.cpp

@@ -1414,9 +1414,14 @@ void Graphics::SetDepthBias(float constantBias, float slopeScaledBias)
         {
             // Bring the constant bias from Direct3D9 scale to OpenGL (depends on depth buffer bitdepth)
             // Zero depth bits may be returned if using the packed depth-stencil format. Assume 24bit in that case
+            #ifndef GL_ES_VERSION_2_0
             int depthBits = Min(impl_->depthBits_, 23);
             if (!depthBits)
                 depthBits = 23;
+            #else
+            int depthBits = 25;
+            #endif
+            
             float adjustedConstantBias = constantBias * (float)(1 << (depthBits - 1));
             float adjustedSlopeScaledBias = slopeScaledBias + 1.0f;
             

+ 1 - 0
Engine/Graphics/View.cpp

@@ -26,6 +26,7 @@
 #include "DebugRenderer.h"
 #include "Geometry.h"
 #include "Graphics.h"
+#include "GraphicsImpl.h"
 #include "Light.h"
 #include "Log.h"
 #include "OcclusionBuffer.h"

+ 7 - 1
Engine/IO/FileSystem.cpp

@@ -50,6 +50,10 @@
 #include <mach-o/dyld.h>
 #endif
 
+#ifdef ANDROID
+extern "C" const char* SDL_Android_GetFilesDir();
+#endif
+
 #include "DebugNew.h"
 
 OBJECTTYPESTATIC(FileSystem);
@@ -398,11 +402,13 @@ String FileSystem::GetProgramDir()
 
 String FileSystem::GetUserDocumentsDir()
 {
-    #ifdef WIN32
+    #if defined(WIN32)
     wchar_t pathName[MAX_PATH];
     pathName[0] = 0;
     SHGetSpecialFolderPathW(0, pathName, CSIDL_PERSONAL, 0);
     return AddTrailingSlash(String(pathName));
+    #elif defined(ANDROID)
+    return AddTrailingSlash(String(SDL_Android_GetFilesDir()));
     #else
     char pathName[MAX_PATH];
     pathName[0] = 0;

+ 107 - 0
Engine/Input/Input.cpp

@@ -169,6 +169,13 @@ void Input::Update()
     mouseMove_ = IntVector2::ZERO;
     mouseMoveWheel_ = 0;
     
+    // Reset touch delta movement. Note: last coordinates are stored internally, but the frame delta is returned to user
+    for (Map<int, TouchState>::Iterator i = touches_.Begin(); i != touches_.End(); ++i)
+    {
+        TouchState& state = i->second_;
+        state.delta_ = state.position_;
+    }
+    
     #ifndef USE_OPENGL
     // Pump Win32 events
     MSG msg;
@@ -312,6 +319,25 @@ int Input::GetQualifiers() const
     return ret;
 }
 
+TouchState Input::GetTouch(unsigned index) const
+{
+    unsigned cmpIndex = 0;
+    for (Map<int, TouchState>::ConstIterator i = touches_.Begin(); i != touches_.End(); ++i)
+    {
+        if (cmpIndex == index)
+        {
+            TouchState ret = i->second_;
+            // Convert last position to delta
+            ret.delta_ = ret.position_ - ret.delta_;
+            return ret;
+        }
+        else
+            ++cmpIndex;
+    }
+    
+    return TouchState();
+}
+
 void Input::Initialize()
 {
     Graphics* graphics = GetSubsystem<Graphics>();
@@ -397,6 +423,21 @@ void Input::ResetState()
     keyDown_.Clear();
     keyPress_.Clear();
     
+    // When clearing touch states, send the corresponding touch end events
+    for (Map<int, TouchState>::Iterator i = touches_.Begin(); i != touches_.End(); ++i)
+    {
+        TouchState& state = i->second_;
+        
+        using namespace TouchEnd;
+        
+        VariantMap eventData;
+        
+        eventData[P_TOUCHID] = state.touchID_;
+        eventData[P_X] = state.position_.x_;
+        eventData[P_Y] = state.position_.y_;
+        SendEvent(E_TOUCHEND, eventData);
+    }
+    
     // Use SetMouseButton() to reset the state so that mouse events will be sent properly
     SetMouseButton(MOUSEB_LEFT, false);
     SetMouseButton(MOUSEB_RIGHT, false);
@@ -733,6 +774,72 @@ void Input::HandleSDLEvent(void* sdlEvent)
             input->SetMouseWheel(evt.wheel.y);
         break;
         
+    case SDL_FINGERDOWN:
+        input = GetInputInstance(evt.tfinger.windowID);
+        if (input)
+        {
+            int touchID = (int)evt.tfinger.fingerId;
+            TouchState& state = input->touches_[touchID];
+            state.touchID_ = touchID;
+            state.delta_ = state.position_ = IntVector2(evt.tfinger.x * input->graphics_->GetWidth() / 32768,
+                evt.tfinger.y * input->graphics_->GetHeight() / 32768);
+            state.pressure_ = evt.tfinger.pressure;
+            
+            using namespace TouchBegin;
+            
+            VariantMap eventData;
+            
+            eventData[P_TOUCHID] = touchID;
+            eventData[P_X] = state.position_.x_;
+            eventData[P_Y] = state.position_.y_;
+            eventData[P_PRESSURE] = state.pressure_;
+            input->SendEvent(E_TOUCHBEGIN, eventData);
+        }
+        break;
+        
+    case SDL_FINGERUP:
+        input = GetInputInstance(evt.tfinger.windowID);
+        if (input)
+        {
+            int touchID = (int)evt.tfinger.fingerId;
+            input->touches_.Erase(touchID);
+            
+            using namespace TouchEnd;
+            
+            VariantMap eventData;
+            
+            eventData[P_TOUCHID] = touchID;
+            eventData[P_X] = evt.tfinger.x * input->graphics_->GetWidth() / 32768;
+            eventData[P_Y] = evt.tfinger.y * input->graphics_->GetHeight() / 32768;
+            input->SendEvent(E_TOUCHEND, eventData);
+        }
+        break;
+        
+    case SDL_FINGERMOTION:
+        input = GetInputInstance(evt.tfinger.windowID);
+        if (input)
+        {
+            int touchID = (int)evt.tfinger.fingerId;
+            TouchState& state = input->touches_[touchID];
+            state.touchID_ = touchID;
+            state.position_ = IntVector2(evt.tfinger.x * input->graphics_->GetWidth() / 32768,
+                evt.tfinger.y * input->graphics_->GetHeight() / 32768);
+            state.pressure_ = evt.tfinger.pressure;
+            
+            using namespace TouchMove;
+            
+            VariantMap eventData;
+            
+            eventData[P_TOUCHID] = touchID;
+            eventData[P_X] = state.position_.x_;
+            eventData[P_Y] = state.position_.y_;
+            eventData[P_DX] = evt.tfinger.dx * input->graphics_->GetWidth() / 32768;
+            eventData[P_DY] = evt.tfinger.dy * input->graphics_->GetHeight() / 32768;
+            eventData[P_PRESSURE] = state.pressure_;
+            input->SendEvent(E_TOUCHMOVE, eventData);
+        }
+        break;
+        
     case SDL_WINDOWEVENT:
         if (evt.window.event == SDL_WINDOWEVENT_CLOSE)
         {

+ 20 - 0
Engine/Input/Input.h

@@ -23,12 +23,26 @@
 
 #pragma once
 
+#include "Map.h"
 #include "HashSet.h"
 #include "InputEvents.h"
 #include "Object.h"
 
 class Graphics;
 
+/// Structure for an ongoing finger touch.
+struct TouchState
+{
+    /// Touch (finger) ID.
+    int touchID_;
+    /// Position in screen coordinates.
+    IntVector2 position_;
+    /// Movement since last frame.
+    IntVector2 delta_;
+    /// Finger pressure.
+    int pressure_;
+};
+
 /// %Input subsystem. Converts operating system window messages to input state and events.
 class Input : public Object
 {
@@ -67,6 +81,10 @@ public:
     int GetMouseMoveY() const { return mouseMove_.y_; }
     /// Return mouse wheel movement since last frame.
     int GetMouseMoveWheel() const { return mouseMoveWheel_; }
+    /// Return number of active finger touches.
+    unsigned GetNumTouches() const { return touches_.Size(); }
+    /// Return active finger touch by index.
+    TouchState GetTouch(unsigned index) const;
     /// Return whether fullscreen toggle is enabled.
     bool GetToggleFullscreen() const { return toggleFullscreen_; }
     /// Return whether application window is active.
@@ -117,6 +135,8 @@ private:
     HashSet<int> keyDown_;
     /// Key pressed state.
     HashSet<int> keyPress_;
+    /// Active finger touches.
+    Map<int, TouchState> touches_;
     /// Mouse buttons' down state.
     unsigned mouseButtonDown_;
     /// Mouse buttons' pressed state.

+ 28 - 0
Engine/Input/InputEvents.h

@@ -87,6 +87,34 @@ EVENT(E_CHAR, Char)
     PARAM(P_QUALIFIERS, Qualifiers);        // int
 }
 
+/// Finger pressed on the screen.
+EVENT(E_TOUCHBEGIN, TouchBegin)
+{
+    PARAM(P_TOUCHID, TouchID);              // int
+    PARAM(P_X, X);                          // int
+    PARAM(P_Y, Y);                          // int
+    PARAM(P_PRESSURE, Pressure);            // int
+}
+
+/// Finger released from the screen.
+EVENT(E_TOUCHEND, TouchEnd)
+{
+    PARAM(P_TOUCHID, TouchID);              // int
+    PARAM(P_X, X);                          // int
+    PARAM(P_Y, Y);                          // int
+}
+
+/// Finger moved on the screen.
+EVENT(E_TOUCHMOVE, TouchMove)
+{
+    PARAM(P_TOUCHID, TouchID);              // int
+    PARAM(P_X, X);                          // int
+    PARAM(P_Y, Y);                          // int
+    PARAM(P_DX, DX);                        // int
+    PARAM(P_DY, DY);                        // int
+    PARAM(P_PRESSURE, Pressure);            // int
+}
+
 /// Application activation state changed.
 EVENT(E_ACTIVATION, Activation)
 {

+ 8 - 8
Engine/UI/LineEdit.cpp

@@ -38,7 +38,7 @@ LineEdit::LineEdit(Context* context) :
     lastFont_(0),
     lastFontSize_(0),
     cursorPosition_(0),
-    dragStartCursor_(M_MAX_UNSIGNED),
+    dragBeginCursor_(M_MAX_UNSIGNED),
     cursorBlinkRate_(1.0f),
     cursorBlinkTimer_(0.0f),
     maxLength_(0),
@@ -144,16 +144,16 @@ void LineEdit::OnClick(const IntVector2& position, const IntVector2& screenPosit
     }
 }
 
-void LineEdit::OnDragStart(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
+void LineEdit::OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
 {
-    dragStartCursor_ = GetCharIndex(position);
+    dragBeginCursor_ = GetCharIndex(position);
 }
 
 void LineEdit::OnDragMove(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
 {
     if (cursorMovable_ && textSelectable_)
     {
-        unsigned start = dragStartCursor_;
+        unsigned start = dragBeginCursor_;
         unsigned current = GetCharIndex(position);
         if (start != M_MAX_UNSIGNED && current != M_MAX_UNSIGNED)
         {
@@ -235,7 +235,7 @@ void LineEdit::OnKey(int key, int buttons, int qualifiers)
         if (cursorMovable_ && cursorPosition_ > 0)
         {
             if (textSelectable_ && qualifiers & QUAL_SHIFT && !text_->GetSelectionLength())
-                dragStartCursor_ = cursorPosition_;
+                dragBeginCursor_ = cursorPosition_;
             
             if (qualifiers & QUAL_CTRL)
                 cursorPosition_ = 0;
@@ -245,7 +245,7 @@ void LineEdit::OnKey(int key, int buttons, int qualifiers)
             
             if (textSelectable_ && qualifiers & QUAL_SHIFT)
             {
-                unsigned start = dragStartCursor_;
+                unsigned start = dragBeginCursor_;
                 unsigned current = cursorPosition_;
                 if (start < current)
                     text_->SetSelection(start, current - start);
@@ -261,7 +261,7 @@ void LineEdit::OnKey(int key, int buttons, int qualifiers)
         if (cursorMovable_ && cursorPosition_ < line_.LengthUTF8())
         {
             if (textSelectable_ && qualifiers & QUAL_SHIFT && !text_->GetSelectionLength())
-                dragStartCursor_ = cursorPosition_;
+                dragBeginCursor_ = cursorPosition_;
             
             if (qualifiers & QUAL_CTRL)
                 cursorPosition_ = line_.LengthUTF8();
@@ -271,7 +271,7 @@ void LineEdit::OnKey(int key, int buttons, int qualifiers)
             
             if (textSelectable_ && qualifiers & QUAL_SHIFT)
             {
-                unsigned start = dragStartCursor_;
+                unsigned start = dragBeginCursor_;
                 unsigned current = cursorPosition_;
                 if (start < current)
                     text_->SetSelection(start, current - start);

+ 4 - 4
Engine/UI/LineEdit.h

@@ -50,8 +50,8 @@ public:
     
     /// React to mouse click.
     virtual void OnClick(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
-    /// React to mouse drag start.
-    virtual void OnDragStart(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
+    /// React to mouse drag begin.
+    virtual void OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
     /// React to mouse drag motion.
     virtual void OnDragMove(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
     /// React to drag and drop test. Return true to signal that the drop is acceptable.
@@ -121,8 +121,8 @@ protected:
     int lastFontSize_;
     /// Text edit cursor position.
     unsigned cursorPosition_;
-    /// Drag start edit cursor position.
-    unsigned dragStartCursor_;
+    /// Drag begin cursor position.
+    unsigned dragBeginCursor_;
     /// Cursor blink rate.
     float cursorBlinkRate_;
     /// Cursor blink timer.

+ 6 - 6
Engine/UI/Slider.cpp

@@ -95,10 +95,10 @@ void Slider::OnHover(const IntVector2& position, const IntVector2& screenPositio
     hovering_ = knob_->IsInside(screenPosition, true);
 }
 
-void Slider::OnDragStart(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
+void Slider::OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
 {
-    dragStartCursor_ = position;
-    dragStartPosition_ = knob_->GetPosition();
+    dragBeginCursor_ = position;
+    dragBeginPosition_ = knob_->GetPosition();
     dragSlider_ = knob_->IsInside(screenPosition, true);
 }
 
@@ -108,17 +108,17 @@ void Slider::OnDragMove(const IntVector2& position, const IntVector2& screenPosi
         return;
     
     float newValue = value_;
-    IntVector2 delta = position - dragStartCursor_;
+    IntVector2 delta = position - dragBeginCursor_;
     
     if (orientation_ == O_HORIZONTAL)
     {
-        int newX = Clamp(dragStartPosition_.x_ + delta.x_, 0, GetWidth() - knob_->GetWidth());
+        int newX = Clamp(dragBeginPosition_.x_ + delta.x_, 0, GetWidth() - knob_->GetWidth());
         knob_->SetPosition(newX, 0);
         newValue = (float)newX * range_ / (float)(GetWidth() - knob_->GetWidth());
     }
     else
     {
-        int newY = Clamp(dragStartPosition_.y_ + delta.y_, 0, GetHeight() - knob_->GetHeight());
+        int newY = Clamp(dragBeginPosition_.y_ + delta.y_, 0, GetHeight() - knob_->GetHeight());
         knob_->SetPosition(0, newY);
         newValue = (float)newY * range_ / (float)(GetHeight() - knob_->GetHeight());
     }

+ 6 - 6
Engine/UI/Slider.h

@@ -46,8 +46,8 @@ public:
     virtual void Update(float timeStep);
     /// React to mouse hover.
     virtual void OnHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
-    /// React to mouse drag start.
-    virtual void OnDragStart(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
+    /// React to mouse drag begin.
+    virtual void OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
     /// React to mouse drag motion.
     virtual void OnDragMove(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
     /// React to mouse drag end.
@@ -87,8 +87,8 @@ protected:
     float value_;
     /// Internal flag of whether the slider is being dragged.
     bool dragSlider_;
-    /// Original mouse cursor position at drag start.
-    IntVector2 dragStartCursor_;
-    /// Original slider position at drag start.
-    IntVector2 dragStartPosition_;
+    /// Original mouse cursor position at drag begin.
+    IntVector2 dragBeginCursor_;
+    /// Original slider position at drag begin.
+    IntVector2 dragBeginPosition_;
 };

+ 165 - 39
Engine/UI/UI.cpp

@@ -31,6 +31,7 @@
 #include "Font.h"
 #include "Graphics.h"
 #include "GraphicsEvents.h"
+#include "Input.h"
 #include "InputEvents.h"
 #include "LineEdit.h"
 #include "ListView.h"
@@ -62,10 +63,13 @@ UI::UI(Context* context) :
     initialized_(false)
 {
     SubscribeToEvent(E_SCREENMODE, HANDLER(UI, HandleScreenMode));
-    SubscribeToEvent(E_MOUSEMOVE, HANDLER(UI, HandleMouseMove));
     SubscribeToEvent(E_MOUSEBUTTONDOWN, HANDLER(UI, HandleMouseButtonDown));
     SubscribeToEvent(E_MOUSEBUTTONUP, HANDLER(UI, HandleMouseButtonUp));
+    SubscribeToEvent(E_MOUSEMOVE, HANDLER(UI, HandleMouseMove));
     SubscribeToEvent(E_MOUSEWHEEL, HANDLER(UI, HandleMouseWheel));
+    SubscribeToEvent(E_TOUCHBEGIN, HANDLER(UI, HandleTouchBegin));
+    SubscribeToEvent(E_TOUCHEND, HANDLER(UI, HandleTouchEnd));
+    SubscribeToEvent(E_TOUCHMOVE, HANDLER(UI, HandleTouchMove));
     SubscribeToEvent(E_KEYDOWN, HANDLER(UI, HandleKeyDown));
     SubscribeToEvent(E_CHAR, HANDLER(UI, HandleChar));
     SubscribeToEvent(E_POSTUPDATE, HANDLER(UI, HandlePostUpdate));
@@ -206,6 +210,20 @@ void UI::Update(float timeStep)
             cursor_->SetShape(dragElement_ == element ? CS_ACCEPTDROP : CS_REJECTDROP);
     }
     
+    // Touch hover
+    Input* input = GetSubsystem<Input>();
+    if (input)
+    {
+        unsigned numTouches = input->GetNumTouches();
+        for (unsigned i = 0; i < numTouches; ++i)
+        {
+            TouchState touch = input->GetTouch(i);
+            WeakPtr<UIElement> element(GetElementAt(touch.position_));
+            if (element)
+                element->OnHover(element->ScreenToElement(touch.position_), touch.position_, MOUSEB_LEFT, 0, 0);
+        }
+    }
+    
     Update(timeStep, rootElement_);
 }
 
@@ -665,42 +683,6 @@ void UI::HandleScreenMode(StringHash eventType, VariantMap& eventData)
         rootElement_->SetSize(eventData[P_WIDTH].GetInt(), eventData[P_HEIGHT].GetInt());
 }
 
-void UI::HandleMouseMove(StringHash eventType, VariantMap& eventData)
-{
-    using namespace MouseMove;
-    
-    mouseButtons_ = eventData[P_BUTTONS].GetInt();
-    qualifiers_ = eventData[P_QUALIFIERS].GetInt();
-    
-    if (cursor_)
-    {
-        const IntVector2& rootSize = rootElement_->GetSize();
-        
-        // Move cursor only when visible
-        if (cursor_->IsVisible())
-        {
-            IntVector2 pos = cursor_->GetPosition();
-            pos.x_ += eventData[P_DX].GetInt();
-            pos.y_ += eventData[P_DY].GetInt();
-            pos.x_ = Clamp(pos.x_, 0, rootSize.x_ - 1);
-            pos.y_ = Clamp(pos.y_, 0, rootSize.y_ - 1);
-            cursor_->SetPosition(pos);
-        }
-        
-        if (dragElement_ && mouseButtons_)
-        {
-            IntVector2 pos = cursor_->GetPosition();
-            if (dragElement_->IsActive() && dragElement_->IsVisible())
-                dragElement_->OnDragMove(dragElement_->ScreenToElement(pos), pos, mouseButtons_, qualifiers_, cursor_);
-            else
-            {
-                dragElement_->OnDragEnd(dragElement_->ScreenToElement(pos), pos, cursor_);
-                dragElement_.Reset();
-            }
-        }
-    }
-}
-
 void UI::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
 {
     mouseButtons_ = eventData[MouseButtonDown::P_BUTTONS].GetInt();
@@ -728,7 +710,7 @@ void UI::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
             if (element && !dragElement_ && mouseButtons_ == MOUSEB_LEFT)
             {
                 dragElement_ = element;
-                element->OnDragStart(element->ScreenToElement(pos), pos, mouseButtons_, qualifiers_, cursor_);
+                element->OnDragBegin(element->ScreenToElement(pos), pos, mouseButtons_, qualifiers_, cursor_);
             }
         }
         else
@@ -757,7 +739,7 @@ void UI::HandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
     mouseButtons_ = eventData[P_BUTTONS].GetInt();
     qualifiers_ = eventData[P_QUALIFIERS].GetInt();
     
-    if (cursor_ && (cursor_->IsVisible())|| (dragElement_))
+    if (cursor_ && (cursor_->IsVisible()) || (dragElement_))
     {
         IntVector2 pos = cursor_->GetPosition();
         
@@ -799,6 +781,42 @@ void UI::HandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
     }
 }
 
+void UI::HandleMouseMove(StringHash eventType, VariantMap& eventData)
+{
+    using namespace MouseMove;
+    
+    mouseButtons_ = eventData[P_BUTTONS].GetInt();
+    qualifiers_ = eventData[P_QUALIFIERS].GetInt();
+    
+    if (cursor_)
+    {
+        const IntVector2& rootSize = rootElement_->GetSize();
+        
+        // Move cursor only when visible
+        if (cursor_->IsVisible())
+        {
+            IntVector2 pos = cursor_->GetPosition();
+            pos.x_ += eventData[P_DX].GetInt();
+            pos.y_ += eventData[P_DY].GetInt();
+            pos.x_ = Clamp(pos.x_, 0, rootSize.x_ - 1);
+            pos.y_ = Clamp(pos.y_, 0, rootSize.y_ - 1);
+            cursor_->SetPosition(pos);
+        }
+        
+        if (dragElement_ && mouseButtons_)
+        {
+            IntVector2 pos = cursor_->GetPosition();
+            if (dragElement_->IsActive() && dragElement_->IsVisible())
+                dragElement_->OnDragMove(dragElement_->ScreenToElement(pos), pos, mouseButtons_, qualifiers_, cursor_);
+            else
+            {
+                dragElement_->OnDragEnd(dragElement_->ScreenToElement(pos), pos, cursor_);
+                dragElement_.Reset();
+            }
+        }
+    }
+}
+
 void UI::HandleMouseWheel(StringHash eventType, VariantMap& eventData)
 {
     using namespace MouseWheel;
@@ -825,6 +843,114 @@ void UI::HandleMouseWheel(StringHash eventType, VariantMap& eventData)
     }
 }
 
+
+void UI::HandleTouchBegin(StringHash eventType, VariantMap& eventData)
+{
+    using namespace TouchBegin;
+    
+    IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
+    WeakPtr<UIElement> element(GetElementAt(pos));
+    
+    if (element)
+    {
+        // Handle focusing & bringing to front
+        SetFocusElement(element);
+        element->BringToFront();
+        
+        // Handle click
+        element->OnClick(element->ScreenToElement(pos), pos, MOUSEB_LEFT, 0, 0);
+        
+        // Handle start of drag. OnClick() may have caused destruction of the element, so check the pointer again
+        if (element && !dragElement_ )
+        {
+            dragElement_ = element;
+            element->OnDragBegin(element->ScreenToElement(pos), pos, MOUSEB_LEFT, 0, 0);
+        }
+    }
+    else
+    {
+        // If clicked over no element, or a disabled element, lose focus
+        SetFocusElement(0);
+    }
+    
+    using namespace UIMouseClick;
+    
+    VariantMap clickEventData;
+    clickEventData[UIMouseClick::P_ELEMENT] = (void*)element.Get();
+    clickEventData[UIMouseClick::P_X] = pos.x_;
+    clickEventData[UIMouseClick::P_Y] = pos.y_;
+    clickEventData[UIMouseClick::P_BUTTON] = MOUSEB_LEFT;
+    clickEventData[UIMouseClick::P_BUTTONS] = MOUSEB_LEFT;
+    clickEventData[UIMouseClick::P_QUALIFIERS] = 0;
+    SendEvent(E_UIMOUSECLICK, clickEventData);
+}
+
+void UI::HandleTouchEnd(StringHash eventType, VariantMap& eventData)
+{
+    using namespace TouchEnd;
+    
+    IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
+    
+    // Transmit hover end to the position where the finger was lifted
+    WeakPtr<UIElement> element(GetElementAt(pos));
+    if (element)
+        element->OnHover(element->ScreenToElement(pos), pos, 0, 0, 0);
+    
+    if (dragElement_)
+    {
+        if (dragElement_->IsActive() && dragElement_->IsVisible())
+        {
+            dragElement_->OnDragEnd(dragElement_->ScreenToElement(pos), pos, cursor_);
+            
+            // Drag and drop finish
+            bool dragSource = dragElement_ && (dragElement_->GetDragDropMode() & DD_SOURCE) != 0;
+            if (dragSource)
+            {
+                WeakPtr<UIElement> target(GetElementAt(pos));
+                bool dragTarget = target && (target->GetDragDropMode() & DD_TARGET) != 0;
+                bool dragDropFinish = dragSource && dragTarget && target != dragElement_;
+                
+                if (dragDropFinish)
+                {
+                    bool accept = target->OnDragDropFinish(dragElement_);
+                    
+                    // OnDragDropFinish() may have caused destruction of the elements, so check the pointers again
+                    if (accept && dragElement_ && target)
+                    {
+                        using namespace DragDropFinish;
+                        
+                        VariantMap eventData;
+                        eventData[P_SOURCE] = (void*)dragElement_.Get();
+                        eventData[P_TARGET] = (void*)target.Get();
+                        eventData[P_ACCEPT] = accept;
+                        SendEvent(E_DRAGDROPFINISH, eventData);
+                    }
+                }
+            }
+        }
+        
+        dragElement_.Reset();
+    }
+}
+
+void UI::HandleTouchMove(StringHash eventType, VariantMap& eventData)
+{
+    using namespace TouchMove;
+    
+    IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
+    
+    if (dragElement_)
+    {
+        if (dragElement_->IsActive() && dragElement_->IsVisible())
+            dragElement_->OnDragMove(dragElement_->ScreenToElement(pos), pos, MOUSEB_LEFT, 0, 0);
+        else
+        {
+            dragElement_->OnDragEnd(dragElement_->ScreenToElement(pos), pos, 0);
+            dragElement_.Reset();
+        }
+    }
+}
+
 void UI::HandleKeyDown(StringHash eventType, VariantMap& eventData)
 {
     using namespace KeyDown;

+ 8 - 2
Engine/UI/UI.h

@@ -97,14 +97,20 @@ private:
     void LoadLayout(UIElement* current, const XMLElement& elem, XMLFile* styleFile);
     /// Handle screen mode event.
     void HandleScreenMode(StringHash eventType, VariantMap& eventData);
-    /// Handle mouse move event.
-    void HandleMouseMove(StringHash eventType, VariantMap& eventData);
     /// Handle mouse button down event.
     void HandleMouseButtonDown(StringHash eventType, VariantMap& eventData);
     /// Handle mouse button up event.
     void HandleMouseButtonUp(StringHash eventType, VariantMap& eventData);
+    /// Handle mouse move event.
+    void HandleMouseMove(StringHash eventType, VariantMap& eventData);
     /// Handle mouse wheel event.
     void HandleMouseWheel(StringHash eventType, VariantMap& eventData);
+    /// Handle touch begin event.
+    void HandleTouchBegin(StringHash eventType, VariantMap& eventData);
+    /// Handle touch end event.
+    void HandleTouchEnd(StringHash eventType, VariantMap& eventData);
+    /// Handle touch move event.
+    void HandleTouchMove(StringHash eventType, VariantMap& eventData);
     /// Handle keypress event.
     void HandleKeyDown(StringHash eventType, VariantMap& eventData);
     /// Handle character event.

+ 1 - 1
Engine/UI/UIElement.cpp

@@ -264,7 +264,7 @@ void UIElement::OnClick(const IntVector2& position, const IntVector2& screenPosi
 {
 }
 
-void UIElement::OnDragStart(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
+void UIElement::OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
 {
 }
 

+ 2 - 2
Engine/UI/UIElement.h

@@ -122,8 +122,8 @@ public:
     virtual void OnHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
     /// React to mouse click.
     virtual void OnClick(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
-    /// React to mouse drag start.
-    virtual void OnDragStart(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
+    /// React to mouse drag begin.
+    virtual void OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
     /// React to mouse drag motion.
     virtual void OnDragMove(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
     /// React to mouse drag end.

+ 22 - 19
Engine/UI/Window.cpp

@@ -77,7 +77,7 @@ void Window::OnHover(const IntVector2& position, const IntVector2& screenPositio
         SetCursorShape(dragMode_, cursor);
 }
 
-void Window::OnDragStart(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
+void Window::OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
 {
     if (buttons != MOUSEB_LEFT || !CheckAlignment())
     {
@@ -85,9 +85,9 @@ void Window::OnDragStart(const IntVector2& position, const IntVector2& screenPos
         return;
     }
     
-    dragStartCursor_ = screenPosition;
-    dragStartPosition_ = GetPosition();
-    dragStartSize_ = GetSize();
+    dragBeginCursor_ = screenPosition;
+    dragBeginPosition_ = GetPosition();
+    dragBeginSize_ = GetSize();
     dragMode_ = GetDragMode(position);
     SetCursorShape(dragMode_, cursor);
 }
@@ -97,49 +97,49 @@ void Window::OnDragMove(const IntVector2& position, const IntVector2& screenPosi
     if (dragMode_ == DRAG_NONE)
         return;
     
-    IntVector2 delta = screenPosition - dragStartCursor_;
+    IntVector2 delta = screenPosition - dragBeginCursor_;
     
     switch (dragMode_)
     {
     case DRAG_MOVE:
-        SetPosition(dragStartPosition_ + delta);
+        SetPosition(dragBeginPosition_ + delta);
         break;
         
     case DRAG_RESIZE_TOPLEFT:
-        SetPosition(dragStartPosition_ + delta);
-        SetSize(dragStartSize_ - delta);
+        SetPosition(dragBeginPosition_ + delta);
+        SetSize(dragBeginSize_ - delta);
         break;
         
     case DRAG_RESIZE_TOP:
-        SetPosition(dragStartPosition_.x_, dragStartPosition_.y_ + delta.y_);
-        SetSize(dragStartSize_.x_, dragStartSize_.y_ - delta.y_);
+        SetPosition(dragBeginPosition_.x_, dragBeginPosition_.y_ + delta.y_);
+        SetSize(dragBeginSize_.x_, dragBeginSize_.y_ - delta.y_);
         break;
         
     case DRAG_RESIZE_TOPRIGHT:
-        SetPosition(dragStartPosition_.x_, dragStartPosition_.y_ + delta.y_);
-        SetSize(dragStartSize_.x_ + delta.x_, dragStartSize_.y_ - delta.y_);
+        SetPosition(dragBeginPosition_.x_, dragBeginPosition_.y_ + delta.y_);
+        SetSize(dragBeginSize_.x_ + delta.x_, dragBeginSize_.y_ - delta.y_);
         break;
         
     case DRAG_RESIZE_RIGHT:
-        SetSize(dragStartSize_.x_ + delta.x_, dragStartSize_.y_);
+        SetSize(dragBeginSize_.x_ + delta.x_, dragBeginSize_.y_);
         break;
         
     case DRAG_RESIZE_BOTTOMRIGHT:
-        SetSize(dragStartSize_ + delta);
+        SetSize(dragBeginSize_ + delta);
         break;
         
     case DRAG_RESIZE_BOTTOM:
-        SetSize(dragStartSize_.x_, dragStartSize_.y_ + delta.y_);
+        SetSize(dragBeginSize_.x_, dragBeginSize_.y_ + delta.y_);
         break;
         
     case DRAG_RESIZE_BOTTOMLEFT:
-        SetPosition(dragStartPosition_.x_ + delta.x_, dragStartPosition_.y_);
-        SetSize(dragStartSize_.x_ - delta.x_, dragStartSize_.y_ + delta.y_);
+        SetPosition(dragBeginPosition_.x_ + delta.x_, dragBeginPosition_.y_);
+        SetSize(dragBeginSize_.x_ - delta.x_, dragBeginSize_.y_ + delta.y_);
         break;
         
     case DRAG_RESIZE_LEFT:
-        SetPosition(dragStartPosition_.x_ + delta.x_, dragStartPosition_.y_);
-        SetSize(dragStartSize_.x_ - delta.x_, dragStartSize_.y_);
+        SetPosition(dragBeginPosition_.x_ + delta.x_, dragBeginPosition_.y_);
+        SetSize(dragBeginSize_.x_ - delta.x_, dragBeginSize_.y_);
         break;
 
     default:
@@ -224,6 +224,9 @@ WindowDragMode Window::GetDragMode(const IntVector2& position) const
 
 void Window::SetCursorShape(WindowDragMode mode, Cursor* cursor) const
 {
+    if (!cursor)
+        return;
+    
     switch (mode)
     {
     case DRAG_RESIZE_TOP:

+ 8 - 8
Engine/UI/Window.h

@@ -59,8 +59,8 @@ public:
     virtual void SetStyle(const XMLElement& element);
     /// React to mouse hover.
     virtual void OnHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
-    /// React to mouse drag start.
-    virtual void OnDragStart(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
+    /// React to mouse drag begin.
+    virtual void OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
     /// React to mouse drag motion.
     virtual void OnDragMove(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
     /// React to mouse drag end.
@@ -98,10 +98,10 @@ protected:
     IntRect resizeBorder_;
     /// Current drag mode.
     WindowDragMode dragMode_;
-    /// Mouse position at drag start.
-    IntVector2 dragStartCursor_;
-    /// Original position at drag start.
-    IntVector2 dragStartPosition_;
-    /// Original size at drag start.
-    IntVector2 dragStartSize_;
+    /// Mouse position at drag begin.
+    IntVector2 dragBeginCursor_;
+    /// Original position at drag begin.
+    IntVector2 dragBeginPosition_;
+    /// Original size at drag begin.
+    IntVector2 dragBeginSize_;
 };

+ 1 - 1
SourceAssets/GLSLShaders/Samplers.frag

@@ -17,7 +17,7 @@ uniform sampler2DShadow sShadowMap;
 uniform samplerCube sFaceSelectCubeMap;
 uniform samplerCube sIndirectionCubeMap;
 #else
-uniform sampler2D sShadowMap;
+uniform mediump sampler2D sShadowMap;
 #endif
 
 vec3 DecodeNormal(vec4 normalInput)

+ 71 - 46
ThirdParty/SDL/src/core/android/SDL_android.cpp

@@ -18,6 +18,9 @@
      misrepresented as being the original software.
   3. This notice may not be removed or altered from any source distribution.
 */
+
+// Modified by Lasse Öörni for Urho3D
+
 #include "SDL_config.h"
 #include "SDL_stdinc.h"
 #include "SDL_assert.h"
@@ -73,6 +76,9 @@ static jmethodID midAudioQuit;
 static float fLastAccelerometer[3];
 static bool bHasNewData;
 
+// Application files dir
+static char* mFilesDir = 0;
+
 /*******************************************************************************
                  Functions called by JNI
 *******************************************************************************/
@@ -91,11 +97,30 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
     return JNI_VERSION_1_4;
 }
 
+extern "C" const char* SDL_Android_GetFilesDir()
+{
+    return mFilesDir;
+}
+
 // Called before SDL_main() to initialize JNI bindings
-extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls)
+extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls, jstring filesDir)
 {
     __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
 
+    // Copy the files dir
+    const char *str;
+    str = env->GetStringUTFChars(filesDir, 0);
+    if (str)
+    {
+        if (mFilesDir)
+            free(mFilesDir);
+
+        size_t length = strlen(str) + 1;
+        mFilesDir = (char*)malloc(length);
+        memcpy(mFilesDir, str, length);
+        env->ReleaseStringUTFChars(filesDir, str);
+    }
+
     mEnv = env;
     mActivityClass = (jclass)env->NewGlobalRef(cls);
 
@@ -103,7 +128,7 @@ extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls)
                                 "createGLContext","(II)Z");
     midFlipBuffers = mEnv->GetStaticMethodID(mActivityClass,
                                 "flipBuffers","()V");
-    midAudioInit = mEnv->GetStaticMethodID(mActivityClass, 
+    midAudioInit = mEnv->GetStaticMethodID(mActivityClass,
                                 "audioInit", "(IZZI)Ljava/lang/Object;");
     midAudioWriteShortBuffer = mEnv->GetStaticMethodID(mActivityClass,
                                 "audioWriteShortBuffer", "([S)V");
@@ -165,53 +190,53 @@ extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
 
 // Quit
 extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
-                                    JNIEnv* env, jclass cls)
-{    
-    // Inject a SDL_QUIT event
-    SDL_Event event;
-    event.type=SDL_SYSEVENT_TERMINATE;
-    event.sysevent.data=NULL;
-    if (SDL_SysEventHandler)
-        SDL_SysEventHandler(&event);
-    else SDL_SendQuit();
-}
-
-// Pause
-extern "C" void Java_org_libsdl_app_SDLActivity_nativePause(
-                                    JNIEnv* env, jclass cls)
-{
-    SDL_Event event;
-    event.type=SDL_SYSEVENT_WILL_SUSPEND;
-    event.sysevent.data=NULL;
-    if (SDL_SysEventHandler)
-        SDL_SysEventHandler(&event);
-    event.type=SDL_SYSEVENT_SUSPEND;
-    event.sysevent.data=NULL;
-    if (SDL_SysEventHandler)
-        SDL_SysEventHandler(&event);
-    else if (Android_Window) {
-        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
-        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
-    }
+                                    JNIEnv* env, jclass cls)
+{    
+    // Inject a SDL_QUIT event
+    SDL_Event event;
+    event.type=SDL_SYSEVENT_TERMINATE;
+    event.sysevent.data=NULL;
+    if (SDL_SysEventHandler)
+        SDL_SysEventHandler(&event);
+    else SDL_SendQuit();
+}
+
+// Pause
+extern "C" void Java_org_libsdl_app_SDLActivity_nativePause(
+                                    JNIEnv* env, jclass cls)
+{
+    SDL_Event event;
+    event.type=SDL_SYSEVENT_WILL_SUSPEND;
+    event.sysevent.data=NULL;
+    if (SDL_SysEventHandler)
+        SDL_SysEventHandler(&event);
+    event.type=SDL_SYSEVENT_SUSPEND;
+    event.sysevent.data=NULL;
+    if (SDL_SysEventHandler)
+        SDL_SysEventHandler(&event);
+    else if (Android_Window) {
+        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
+        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
+    }
 }
 
 // Resume
-extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume(
-                                    JNIEnv* env, jclass cls)
-{
-    SDL_Event event;
-    event.type=SDL_SYSEVENT_WILL_RESUME;
-    event.sysevent.data=NULL;
-    if (SDL_SysEventHandler)
-        SDL_SysEventHandler(&event);
-    event.type=SDL_SYSEVENT_RESUME;
-    event.sysevent.data=NULL;
-    if (SDL_SysEventHandler)
-        SDL_SysEventHandler(&event);
-    else if (Android_Window) {
-        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
-        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
-    }
+extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume(
+                                    JNIEnv* env, jclass cls)
+{
+    SDL_Event event;
+    event.type=SDL_SYSEVENT_WILL_RESUME;
+    event.sysevent.data=NULL;
+    if (SDL_SysEventHandler)
+        SDL_SysEventHandler(&event);
+    event.type=SDL_SYSEVENT_RESUME;
+    event.sysevent.data=NULL;
+    if (SDL_SysEventHandler)
+        SDL_SysEventHandler(&event);
+    else if (Android_Window) {
+        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
+        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
+    }
 }
 
 extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(

+ 679 - 670
ThirdParty/SDL/src/events/SDL_gesture.c

@@ -1,670 +1,679 @@
-/*
-  Simple DirectMedia Layer
-  Copyright (C) 1997-2012 Sam Lantinga <[email protected]>
-
-  This software is provided 'as-is', without any express or implied
-  warranty.  In no event will the authors be held liable for any damages
-  arising from the use of this software.
-
-  Permission is granted to anyone to use this software for any purpose,
-  including commercial applications, and to alter it and redistribute it
-  freely, subject to the following restrictions:
-
-  1. The origin of this software must not be misrepresented; you must not
-  claim that you wrote the original software. If you use this software
-  in a product, an acknowledgment in the product documentation would be
-  appreciated but is not required.
-  2. Altered source versions must be plainly marked as such, and must not be
-  misrepresented as being the original software.
-  3. This notice may not be removed or altered from any source distribution.
-*/
-
-// Modified by Lasse Öörni for Urho3D
-
-#include "SDL_config.h"
-
-/* General mouse handling code for SDL */
-
-#include "SDL_events.h"
-#include "SDL_events_c.h"
-#include "SDL_gesture_c.h"
-
-#include <memory.h>
-#include <string.h>
-#include <stdio.h>
-#include <math.h>
-
-//TODO: Replace with malloc
-
-// Urho3D: added definition
-#ifndef M_PI
-static const float M_PI = 3.141592653589793238462643f;
-#endif
-
-#define MAXPATHSIZE 1024
-
-
-
-
-#define DOLLARNPOINTS 64
-#define DOLLARSIZE 256
-
-#define ENABLE_DOLLAR
-
-#define PHI 0.618033989
-
-typedef struct {
-    float x,y;
-} SDL_FloatPoint;
-
-typedef struct {
-    float length;
-
-    int numPoints;
-    SDL_FloatPoint p[MAXPATHSIZE];
-} SDL_DollarPath;
-
-typedef struct {
-    SDL_FloatPoint path[DOLLARNPOINTS];
-    unsigned long hash;
-} SDL_DollarTemplate;
-
-typedef struct {
-    SDL_TouchID id;
-    SDL_FloatPoint res;
-    SDL_FloatPoint centroid;
-    SDL_DollarPath dollarPath;
-    Uint16 numDownFingers;
-
-    int numDollarTemplates;
-    SDL_DollarTemplate *dollarTemplate;
-
-    SDL_bool recording;
-} SDL_GestureTouch;
-
-SDL_GestureTouch *SDL_gestureTouch;
-int SDL_numGestureTouches = 0;
-SDL_bool recordAll;
-
-#if 0
-static void PrintPath(SDL_FloatPoint *path)
-{
-    int i;
-    printf("Path:");
-    for (i=0; i<DOLLARNPOINTS; i++) {
-        printf(" (%f,%f)",path[i].x,path[i].y);
-    }
-    printf("\n");
-}
-#endif
-
-int SDL_RecordGesture(SDL_TouchID touchId)
-{
-    int i;
-    if (touchId < 0) recordAll = SDL_TRUE;
-    for (i = 0; i < SDL_numGestureTouches; i++) {
-        if ((touchId < 0) || (SDL_gestureTouch[i].id == touchId)) {
-            SDL_gestureTouch[i].recording = SDL_TRUE;
-            if (touchId >= 0)
-                return 1;
-        }
-    }
-    return (touchId < 0);
-}
-
-static unsigned long SDL_HashDollar(SDL_FloatPoint* points)
-{
-    unsigned long hash = 5381;
-    int i;
-    for (i = 0; i < DOLLARNPOINTS; i++) {
-        hash = ((hash<<5) + hash) + (unsigned long)points[i].x;
-        hash = ((hash<<5) + hash) + (unsigned long)points[i].y;
-    }
-    return hash;
-}
-
-
-static int SaveTemplate(SDL_DollarTemplate *templ, SDL_RWops * src)
-{
-    if (src == NULL) return 0;
-
-
-    //No Longer storing the Hash, rehash on load
-    //if(SDL_RWops.write(src,&(templ->hash),sizeof(templ->hash),1) != 1) return 0;
-
-    if (SDL_RWwrite(src,templ->path,
-                    sizeof(templ->path[0]),DOLLARNPOINTS) != DOLLARNPOINTS)
-        return 0;
-
-    return 1;
-}
-
-
-int SDL_SaveAllDollarTemplates(SDL_RWops *src)
-{
-    int i,j,rtrn = 0;
-    for (i = 0; i < SDL_numGestureTouches; i++) {
-        SDL_GestureTouch* touch = &SDL_gestureTouch[i];
-        for (j = 0; j < touch->numDollarTemplates; j++) {
-            rtrn += SaveTemplate(&touch->dollarTemplate[i],src);
-        }
-    }
-    return rtrn;
-}
-
-int SDL_SaveDollarTemplate(SDL_GestureID gestureId, SDL_RWops *src)
-{
-    int i,j;
-    for (i = 0; i < SDL_numGestureTouches; i++) {
-        SDL_GestureTouch* touch = &SDL_gestureTouch[i];
-        for (j = 0; j < touch->numDollarTemplates; j++) {
-            if (touch->dollarTemplate[i].hash == gestureId) {
-                return SaveTemplate(&touch->dollarTemplate[i],src);
-            }
-        }
-    }
-    SDL_SetError("Unknown gestureId");
-    return -1;
-}
-
-//path is an already sampled set of points
-//Returns the index of the gesture on success, or -1
-static int SDL_AddDollarGesture_one(SDL_GestureTouch* inTouch, SDL_FloatPoint* path)
-{
-    SDL_DollarTemplate* dollarTemplate;
-    SDL_DollarTemplate *templ;
-    int index;
-
-    index = inTouch->numDollarTemplates;
-    dollarTemplate =
-        (SDL_DollarTemplate *)SDL_realloc(inTouch->dollarTemplate,
-                                          (index + 1) *
-                                          sizeof(SDL_DollarTemplate));
-    if (!dollarTemplate) {
-        SDL_OutOfMemory();
-        return -1;
-    }
-    inTouch->dollarTemplate = dollarTemplate;
-
-    templ = &inTouch->dollarTemplate[index];
-    SDL_memcpy(templ->path, path, DOLLARNPOINTS*sizeof(SDL_FloatPoint));
-    templ->hash = SDL_HashDollar(templ->path);
-    inTouch->numDollarTemplates++;
-
-    return index;
-}
-
-static int SDL_AddDollarGesture(SDL_GestureTouch* inTouch, SDL_FloatPoint* path)
-{
-    int index = -1;
-    int i = 0;
-    if (inTouch == NULL) {
-        if (SDL_numGestureTouches == 0) return -1;
-        for (i = 0; i < SDL_numGestureTouches; i++) {
-            inTouch = &SDL_gestureTouch[i];
-            index = SDL_AddDollarGesture_one(inTouch, path);
-            if (index < 0)
-                return -1;
-        }
-        // Use the index of the last one added.
-        return index;
-    } else {
-        return SDL_AddDollarGesture_one(inTouch, path);
-    }
-    return -1;
-}
-
-int SDL_LoadDollarTemplates(SDL_TouchID touchId, SDL_RWops *src)
-{
-    int i,loaded = 0;
-    SDL_GestureTouch *touch = NULL;
-    if (src == NULL) return 0;
-    if (touchId >= 0) {
-        for (i = 0; i < SDL_numGestureTouches; i++)
-            if (SDL_gestureTouch[i].id == touchId)
-                touch = &SDL_gestureTouch[i];
-        if (touch == NULL) return -1;
-    }
-
-    while (1) {
-        SDL_DollarTemplate templ;
-
-        if (SDL_RWread(src,templ.path,sizeof(templ.path[0]),DOLLARNPOINTS) <
-           DOLLARNPOINTS) break;
-
-        if (touchId >= 0) {
-            //printf("Adding loaded gesture to 1 touch\n");
-            if (SDL_AddDollarGesture(touch, templ.path) >= 0)
-                loaded++;
-        }
-        else {
-            //printf("Adding to: %i touches\n",SDL_numGestureTouches);
-            for (i = 0; i < SDL_numGestureTouches; i++) {
-                touch = &SDL_gestureTouch[i];
-                //printf("Adding loaded gesture to + touches\n");
-                //TODO: What if this fails?
-                SDL_AddDollarGesture(touch,templ.path);
-            }
-            loaded++;
-        }
-    }
-
-    return loaded;
-}
-
-
-static float dollarDifference(SDL_FloatPoint* points,SDL_FloatPoint* templ,float ang)
-{
-    //  SDL_FloatPoint p[DOLLARNPOINTS];
-    float dist = 0;
-    SDL_FloatPoint p;
-    int i;
-    for (i = 0; i < DOLLARNPOINTS; i++) {
-        p.x = (float)(points[i].x * SDL_cos(ang) - points[i].y * SDL_sin(ang));
-        p.y = (float)(points[i].x * SDL_sin(ang) + points[i].y * SDL_cos(ang));
-        dist += (float)(SDL_sqrt((p.x-templ[i].x)*(p.x-templ[i].x)+
-                                 (p.y-templ[i].y)*(p.y-templ[i].y)));
-    }
-    return dist/DOLLARNPOINTS;
-
-}
-
-static float bestDollarDifference(SDL_FloatPoint* points,SDL_FloatPoint* templ)
-{
-    //------------BEGIN DOLLAR BLACKBOX----------------//
-    //-TRANSLATED DIRECTLY FROM PSUDEO-CODE AVAILABLE AT-//
-    //-"http://depts.washington.edu/aimgroup/proj/dollar/"-//
-    double ta = -M_PI/4;
-    double tb = M_PI/4;
-    double dt = M_PI/90;
-    float x1 = (float)(PHI*ta + (1-PHI)*tb);
-    float f1 = dollarDifference(points,templ,x1);
-    float x2 = (float)((1-PHI)*ta + PHI*tb);
-    float f2 = dollarDifference(points,templ,x2);
-    while (SDL_fabs(ta-tb) > dt) {
-        if (f1 < f2) {
-            tb = x2;
-            x2 = x1;
-            f2 = f1;
-            x1 = (float)(PHI*ta + (1-PHI)*tb);
-            f1 = dollarDifference(points,templ,x1);
-        }
-        else {
-            ta = x1;
-            x1 = x2;
-            f1 = f2;
-            x2 = (float)((1-PHI)*ta + PHI*tb);
-            f2 = dollarDifference(points,templ,x2);
-        }
-    }
-    /*
-      if (f1 <= f2)
-          printf("Min angle (x1): %f\n",x1);
-      else if (f1 >  f2)
-          printf("Min angle (x2): %f\n",x2);
-    */
-    return SDL_min(f1,f2);
-}
-
-//DollarPath contains raw points, plus (possibly) the calculated length
-static int dollarNormalize(const SDL_DollarPath *path,SDL_FloatPoint *points)
-{
-    int i;
-    float interval;
-    float dist;
-    int numPoints = 0;
-    SDL_FloatPoint centroid;
-    float xmin,xmax,ymin,ymax;
-    float ang;
-    float w,h;
-    float length = path->length;
-
-    //Calculate length if it hasn't already been done
-    if (length <= 0) {
-        for (i=1;i < path->numPoints; i++) {
-            float dx = path->p[i  ].x - path->p[i-1].x;
-            float dy = path->p[i  ].y - path->p[i-1].y;
-            length += (float)(SDL_sqrt(dx*dx+dy*dy));
-        }
-    }
-
-    //Resample
-    interval = length/(DOLLARNPOINTS - 1);
-    dist = interval;
-
-    centroid.x = 0;centroid.y = 0;
-
-    //printf("(%f,%f)\n",path->p[path->numPoints-1].x,path->p[path->numPoints-1].y);
-    for (i = 1; i < path->numPoints; i++) {
-        float d = (float)(SDL_sqrt((path->p[i-1].x-path->p[i].x)*(path->p[i-1].x-path->p[i].x)+
-                                   (path->p[i-1].y-path->p[i].y)*(path->p[i-1].y-path->p[i].y)));
-        //printf("d = %f dist = %f/%f\n",d,dist,interval);
-        while (dist + d > interval) {
-            points[numPoints].x = path->p[i-1].x +
-                ((interval-dist)/d)*(path->p[i].x-path->p[i-1].x);
-            points[numPoints].y = path->p[i-1].y +
-                ((interval-dist)/d)*(path->p[i].y-path->p[i-1].y);
-            centroid.x += points[numPoints].x;
-            centroid.y += points[numPoints].y;
-            numPoints++;
-
-            dist -= interval;
-        }
-        dist += d;
-    }
-    if (numPoints < DOLLARNPOINTS-1) {
-        SDL_SetError("ERROR: NumPoints = %i\n",numPoints);
-        return 0;
-    }
-    //copy the last point
-    points[DOLLARNPOINTS-1] = path->p[path->numPoints-1];
-    numPoints = DOLLARNPOINTS;
-
-    centroid.x /= numPoints;
-    centroid.y /= numPoints;
-
-    //printf("Centroid (%f,%f)",centroid.x,centroid.y);
-    //Rotate Points so point 0 is left of centroid and solve for the bounding box
-    xmin = centroid.x;
-    xmax = centroid.x;
-    ymin = centroid.y;
-    ymax = centroid.y;
-
-    ang = (float)(SDL_atan2(centroid.y - points[0].y,
-                            centroid.x - points[0].x));
-
-    for (i = 0; i<numPoints; i++) {
-        float px = points[i].x;
-        float py = points[i].y;
-        points[i].x = (float)((px - centroid.x)*SDL_cos(ang) -
-                              (py - centroid.y)*SDL_sin(ang) + centroid.x);
-        points[i].y = (float)((px - centroid.x)*SDL_sin(ang) +
-                              (py - centroid.y)*SDL_cos(ang) + centroid.y);
-
-
-        if (points[i].x < xmin) xmin = points[i].x;
-        if (points[i].x > xmax) xmax = points[i].x;
-        if (points[i].y < ymin) ymin = points[i].y;
-        if (points[i].y > ymax) ymax = points[i].y;
-    }
-
-    //Scale points to DOLLARSIZE, and translate to the origin
-    w = xmax-xmin;
-    h = ymax-ymin;
-
-    for (i=0; i<numPoints; i++) {
-        points[i].x = (points[i].x - centroid.x)*DOLLARSIZE/w;
-        points[i].y = (points[i].y - centroid.y)*DOLLARSIZE/h;
-    }
-    return numPoints;
-}
-
-static float dollarRecognize(const SDL_DollarPath *path,int *bestTempl,SDL_GestureTouch* touch)
-{
-
-    SDL_FloatPoint points[DOLLARNPOINTS];
-    int i;
-    float bestDiff = 10000;
-
-    dollarNormalize(path,points);
-
-    //PrintPath(points);
-    *bestTempl = -1;
-    for (i = 0; i < touch->numDollarTemplates; i++) {
-        float diff = bestDollarDifference(points,touch->dollarTemplate[i].path);
-        if (diff < bestDiff) {bestDiff = diff; *bestTempl = i;}
-    }
-    return bestDiff;
-}
-
-int SDL_GestureAddTouch(SDL_Touch* touch)
-{
-    SDL_GestureTouch *gestureTouch = (SDL_GestureTouch *)SDL_realloc(SDL_gestureTouch,
-                                                                     (SDL_numGestureTouches + 1) *
-                                                                     sizeof(SDL_GestureTouch));
-
-    if (!gestureTouch) {
-        SDL_OutOfMemory();
-        return -1;
-    }
-
-    SDL_gestureTouch = gestureTouch;
-
-    SDL_gestureTouch[SDL_numGestureTouches].res.x = touch->xres;
-    SDL_gestureTouch[SDL_numGestureTouches].res.y = touch->yres;
-    SDL_gestureTouch[SDL_numGestureTouches].numDownFingers = 0;
-
-    SDL_gestureTouch[SDL_numGestureTouches].res.x = touch->xres;
-    SDL_gestureTouch[SDL_numGestureTouches].id = touch->id;
-
-    SDL_gestureTouch[SDL_numGestureTouches].numDollarTemplates = 0;
-
-    SDL_gestureTouch[SDL_numGestureTouches].recording = SDL_FALSE;
-
-    SDL_numGestureTouches++;
-    return 0;
-}
-
-static SDL_GestureTouch * SDL_GetGestureTouch(SDL_TouchID id)
-{
-    int i;
-    for (i = 0; i < SDL_numGestureTouches; i++) {
-        //printf("%i ?= %i\n",SDL_gestureTouch[i].id,id);
-        if (SDL_gestureTouch[i].id == id)
-            return &SDL_gestureTouch[i];
-    }
-    return NULL;
-}
-
-int SDL_SendGestureMulti(SDL_GestureTouch* touch,float dTheta,float dDist)
-{
-    SDL_Event event;
-    event.mgesture.type = SDL_MULTIGESTURE;
-    event.mgesture.touchId = touch->id;
-    event.mgesture.x = touch->centroid.x;
-    event.mgesture.y = touch->centroid.y;
-    event.mgesture.dTheta = dTheta;
-    event.mgesture.dDist = dDist;
-    event.mgesture.numFingers = touch->numDownFingers;
-    return SDL_PushEvent(&event) > 0;
-}
-
-static int SDL_SendGestureDollar(SDL_GestureTouch* touch,
-                          SDL_GestureID gestureId,float error)
-{
-    SDL_Event event;
-    event.dgesture.type = SDL_DOLLARGESTURE;
-    event.dgesture.touchId = touch->id;
-    /*
-    //TODO: Add this to give location of gesture?
-    event.mgesture.x = touch->centroid.x;
-    event.mgesture.y = touch->centroid.y;
-    */
-    event.dgesture.gestureId = gestureId;
-    event.dgesture.error = error;
-    //A finger came up to trigger this event.
-    event.dgesture.numFingers = touch->numDownFingers + 1;
-    return SDL_PushEvent(&event) > 0;
-}
-
-
-static int SDL_SendDollarRecord(SDL_GestureTouch* touch,SDL_GestureID gestureId)
-{
-    SDL_Event event;
-    event.dgesture.type = SDL_DOLLARRECORD;
-    event.dgesture.touchId = touch->id;
-    event.dgesture.gestureId = gestureId;
-    return SDL_PushEvent(&event) > 0;
-}
-
-
-void SDL_GestureProcessEvent(SDL_Event* event)
-{
-    float x,y;
-    SDL_FloatPoint path[DOLLARNPOINTS];
-    int index;
-    int i;
-    float pathDx, pathDy;
-    SDL_FloatPoint lastP;
-    SDL_FloatPoint lastCentroid;
-    float lDist;
-    float Dist;
-    float dtheta;
-    float dDist;
-
-    if (event->type == SDL_FINGERMOTION ||
-        event->type == SDL_FINGERDOWN ||
-        event->type == SDL_FINGERUP) {
-        SDL_GestureTouch* inTouch = SDL_GetGestureTouch(event->tfinger.touchId);
-
-        //Shouldn't be possible
-        if (inTouch == NULL) return;
-
-        //printf("@ (%i,%i) with res: (%i,%i)\n",(int)event->tfinger.x,
-        //           (int)event->tfinger.y,
-        //   (int)inTouch->res.x,(int)inTouch->res.y);
-
-
-        x = ((float)event->tfinger.x)/(float)inTouch->res.x;
-        y = ((float)event->tfinger.y)/(float)inTouch->res.y;
-
-
-        //Finger Up
-        if (event->type == SDL_FINGERUP) {
-            inTouch->numDownFingers--;
-
-#ifdef ENABLE_DOLLAR
-            if (inTouch->recording) {
-                inTouch->recording = SDL_FALSE;
-                dollarNormalize(&inTouch->dollarPath,path);
-                //PrintPath(path);
-                if (recordAll) {
-                    index = SDL_AddDollarGesture(NULL,path);
-                    for (i = 0; i < SDL_numGestureTouches; i++)
-                        SDL_gestureTouch[i].recording = SDL_FALSE;
-                }
-                else {
-                    index = SDL_AddDollarGesture(inTouch,path);
-                }
-
-                if (index >= 0) {
-                    SDL_SendDollarRecord(inTouch,inTouch->dollarTemplate[index].hash);
-                }
-                else {
-                    SDL_SendDollarRecord(inTouch,-1);
-                }
-            }
-            else {
-                int bestTempl;
-                float error;
-                error = dollarRecognize(&inTouch->dollarPath,
-                                        &bestTempl,inTouch);
-                if (bestTempl >= 0){
-                    //Send Event
-                    unsigned long gestureId = inTouch->dollarTemplate[bestTempl].hash;
-                    SDL_SendGestureDollar(inTouch,gestureId,error);
-                    //printf ("%s\n",);("Dollar error: %f\n",error);
-                }
-            }
-#endif
-            //inTouch->gestureLast[j] = inTouch->gestureLast[inTouch->numDownFingers];
-            if (inTouch->numDownFingers > 0) {
-                inTouch->centroid.x = (inTouch->centroid.x*(inTouch->numDownFingers+1)-
-                                       x)/inTouch->numDownFingers;
-                inTouch->centroid.y = (inTouch->centroid.y*(inTouch->numDownFingers+1)-
-                                       y)/inTouch->numDownFingers;
-            }
-        }
-        else if (event->type == SDL_FINGERMOTION) {
-            float dx = ((float)event->tfinger.dx)/(float)inTouch->res.x;
-            float dy = ((float)event->tfinger.dy)/(float)inTouch->res.y;
-            //printf("dx,dy: (%f,%f)\n",dx,dy);
-#ifdef ENABLE_DOLLAR
-            SDL_DollarPath* path = &inTouch->dollarPath;
-            if (path->numPoints < MAXPATHSIZE) {
-                path->p[path->numPoints].x = inTouch->centroid.x;
-                path->p[path->numPoints].y = inTouch->centroid.y;
-                pathDx =
-                    (path->p[path->numPoints].x-path->p[path->numPoints-1].x);
-                pathDy =
-                    (path->p[path->numPoints].y-path->p[path->numPoints-1].y);
-                path->length += (float)SDL_sqrt(pathDx*pathDx + pathDy*pathDy);
-                path->numPoints++;
-            }
-#endif
-            lastP.x = x - dx;
-            lastP.y = y - dy;
-            lastCentroid = inTouch->centroid;
-
-            inTouch->centroid.x += dx/inTouch->numDownFingers;
-            inTouch->centroid.y += dy/inTouch->numDownFingers;
-            //printf("Centrid : (%f,%f)\n",inTouch->centroid.x,inTouch->centroid.y);
-            if (inTouch->numDownFingers > 1) {
-                SDL_FloatPoint lv; //Vector from centroid to last x,y position
-                SDL_FloatPoint v; //Vector from centroid to current x,y position
-                //lv = inTouch->gestureLast[j].cv;
-                lv.x = lastP.x - lastCentroid.x;
-                lv.y = lastP.y - lastCentroid.y;
-                lDist = (float)SDL_sqrt(lv.x*lv.x + lv.y*lv.y);
-                //printf("lDist = %f\n",lDist);
-                v.x = x - inTouch->centroid.x;
-                v.y = y - inTouch->centroid.y;
-                //inTouch->gestureLast[j].cv = v;
-                Dist = (float)SDL_sqrt(v.x*v.x+v.y*v.y);
-                // SDL_cos(dTheta) = (v . lv)/(|v| * |lv|)
-
-                //Normalize Vectors to simplify angle calculation
-                lv.x/=lDist;
-                lv.y/=lDist;
-                v.x/=Dist;
-                v.y/=Dist;
-                dtheta = (float)SDL_atan2(lv.x*v.y - lv.y*v.x,lv.x*v.x + lv.y*v.y);
-
-                dDist = (Dist - lDist);
-                if (lDist == 0) {dDist = 0;dtheta = 0;} //To avoid impossible values
-
-                //inTouch->gestureLast[j].dDist = dDist;
-                //inTouch->gestureLast[j].dtheta = dtheta;
-
-                //printf("dDist = %f, dTheta = %f\n",dDist,dtheta);
-                //gdtheta = gdtheta*.9 + dtheta*.1;
-                //gdDist  =  gdDist*.9 +  dDist*.1
-                //knob.r += dDist/numDownFingers;
-                //knob.ang += dtheta;
-                //printf("thetaSum = %f, distSum = %f\n",gdtheta,gdDist);
-                //printf("id: %i dTheta = %f, dDist = %f\n",j,dtheta,dDist);
-                SDL_SendGestureMulti(inTouch,dtheta,dDist);
-            }
-            else {
-                //inTouch->gestureLast[j].dDist = 0;
-                //inTouch->gestureLast[j].dtheta = 0;
-                //inTouch->gestureLast[j].cv.x = 0;
-                //inTouch->gestureLast[j].cv.y = 0;
-            }
-            //inTouch->gestureLast[j].f.p.x = x;
-            //inTouch->gestureLast[j].f.p.y = y;
-            //break;
-            //pressure?
-        }
-
-        if (event->type == SDL_FINGERDOWN) {
-
-            inTouch->numDownFingers++;
-            inTouch->centroid.x = (inTouch->centroid.x*(inTouch->numDownFingers - 1)+
-                                   x)/inTouch->numDownFingers;
-            inTouch->centroid.y = (inTouch->centroid.y*(inTouch->numDownFingers - 1)+
-                                   y)/inTouch->numDownFingers;
-            //printf("Finger Down: (%f,%f). Centroid: (%f,%f\n",x,y,
-            //     inTouch->centroid.x,inTouch->centroid.y);
-
-#ifdef ENABLE_DOLLAR
-            inTouch->dollarPath.length = 0;
-            inTouch->dollarPath.p[0].x = x;
-            inTouch->dollarPath.p[0].y = y;
-            inTouch->dollarPath.numPoints = 1;
-#endif
-        }
-    }
-}
-
-/* vi: set ts=4 sw=4 expandtab: */
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2012 Sam Lantinga <[email protected]>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+  claim that you wrote the original software. If you use this software
+  in a product, an acknowledgment in the product documentation would be
+  appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+  misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+// Modified by Lasse Öörni for Urho3D
+
+#include "SDL_config.h"
+
+/* General mouse handling code for SDL */
+
+#include "SDL_events.h"
+#include "SDL_events_c.h"
+#include "SDL_gesture_c.h"
+
+#include <memory.h>
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+
+// Urho3D: disabled
+#if 0
+
+//TODO: Replace with malloc
+
+#define MAXPATHSIZE 1024
+
+
+
+
+#define DOLLARNPOINTS 64
+#define DOLLARSIZE 256
+
+#define ENABLE_DOLLAR
+
+#define PHI 0.618033989
+
+typedef struct {
+    float x,y;
+} SDL_FloatPoint;
+
+typedef struct {
+    float length;
+
+    int numPoints;
+    SDL_FloatPoint p[MAXPATHSIZE];
+} SDL_DollarPath;
+
+typedef struct {
+    SDL_FloatPoint path[DOLLARNPOINTS];
+    unsigned long hash;
+} SDL_DollarTemplate;
+
+typedef struct {
+    SDL_TouchID id;
+    SDL_FloatPoint res;
+    SDL_FloatPoint centroid;
+    SDL_DollarPath dollarPath;
+    Uint16 numDownFingers;
+
+    int numDollarTemplates;
+    SDL_DollarTemplate *dollarTemplate;
+
+    SDL_bool recording;
+} SDL_GestureTouch;
+
+SDL_GestureTouch *SDL_gestureTouch;
+int SDL_numGestureTouches = 0;
+SDL_bool recordAll;
+
+#if 0
+static void PrintPath(SDL_FloatPoint *path)
+{
+    int i;
+    printf("Path:");
+    for (i=0; i<DOLLARNPOINTS; i++) {
+        printf(" (%f,%f)",path[i].x,path[i].y);
+    }
+    printf("\n");
+}
+#endif
+
+int SDL_RecordGesture(SDL_TouchID touchId)
+{
+    int i;
+    if (touchId < 0) recordAll = SDL_TRUE;
+    for (i = 0; i < SDL_numGestureTouches; i++) {
+        if ((touchId < 0) || (SDL_gestureTouch[i].id == touchId)) {
+            SDL_gestureTouch[i].recording = SDL_TRUE;
+            if (touchId >= 0)
+                return 1;
+        }
+    }
+    return (touchId < 0);
+}
+
+static unsigned long SDL_HashDollar(SDL_FloatPoint* points)
+{
+    unsigned long hash = 5381;
+    int i;
+    for (i = 0; i < DOLLARNPOINTS; i++) {
+        hash = ((hash<<5) + hash) + (unsigned long)points[i].x;
+        hash = ((hash<<5) + hash) + (unsigned long)points[i].y;
+    }
+    return hash;
+}
+
+
+static int SaveTemplate(SDL_DollarTemplate *templ, SDL_RWops * src)
+{
+    if (src == NULL) return 0;
+
+
+    //No Longer storing the Hash, rehash on load
+    //if(SDL_RWops.write(src,&(templ->hash),sizeof(templ->hash),1) != 1) return 0;
+
+    if (SDL_RWwrite(src,templ->path,
+                    sizeof(templ->path[0]),DOLLARNPOINTS) != DOLLARNPOINTS)
+        return 0;
+
+    return 1;
+}
+
+
+int SDL_SaveAllDollarTemplates(SDL_RWops *src)
+{
+    int i,j,rtrn = 0;
+    for (i = 0; i < SDL_numGestureTouches; i++) {
+        SDL_GestureTouch* touch = &SDL_gestureTouch[i];
+        for (j = 0; j < touch->numDollarTemplates; j++) {
+            rtrn += SaveTemplate(&touch->dollarTemplate[i],src);
+        }
+    }
+    return rtrn;
+}
+
+int SDL_SaveDollarTemplate(SDL_GestureID gestureId, SDL_RWops *src)
+{
+    int i,j;
+    for (i = 0; i < SDL_numGestureTouches; i++) {
+        SDL_GestureTouch* touch = &SDL_gestureTouch[i];
+        for (j = 0; j < touch->numDollarTemplates; j++) {
+            if (touch->dollarTemplate[i].hash == gestureId) {
+                return SaveTemplate(&touch->dollarTemplate[i],src);
+            }
+        }
+    }
+    SDL_SetError("Unknown gestureId");
+    return -1;
+}
+
+//path is an already sampled set of points
+//Returns the index of the gesture on success, or -1
+static int SDL_AddDollarGesture_one(SDL_GestureTouch* inTouch, SDL_FloatPoint* path)
+{
+    SDL_DollarTemplate* dollarTemplate;
+    SDL_DollarTemplate *templ;
+    int index;
+
+    index = inTouch->numDollarTemplates;
+    dollarTemplate =
+        (SDL_DollarTemplate *)SDL_realloc(inTouch->dollarTemplate,
+                                          (index + 1) *
+                                          sizeof(SDL_DollarTemplate));
+    if (!dollarTemplate) {
+        SDL_OutOfMemory();
+        return -1;
+    }
+    inTouch->dollarTemplate = dollarTemplate;
+
+    templ = &inTouch->dollarTemplate[index];
+    SDL_memcpy(templ->path, path, DOLLARNPOINTS*sizeof(SDL_FloatPoint));
+    templ->hash = SDL_HashDollar(templ->path);
+    inTouch->numDollarTemplates++;
+
+    return index;
+}
+
+static int SDL_AddDollarGesture(SDL_GestureTouch* inTouch, SDL_FloatPoint* path)
+{
+    int index = -1;
+    int i = 0;
+    if (inTouch == NULL) {
+        if (SDL_numGestureTouches == 0) return -1;
+        for (i = 0; i < SDL_numGestureTouches; i++) {
+            inTouch = &SDL_gestureTouch[i];
+            index = SDL_AddDollarGesture_one(inTouch, path);
+            if (index < 0)
+                return -1;
+        }
+        // Use the index of the last one added.
+        return index;
+    } else {
+        return SDL_AddDollarGesture_one(inTouch, path);
+    }
+    return -1;
+}
+
+int SDL_LoadDollarTemplates(SDL_TouchID touchId, SDL_RWops *src)
+{
+    int i,loaded = 0;
+    SDL_GestureTouch *touch = NULL;
+    if (src == NULL) return 0;
+    if (touchId >= 0) {
+        for (i = 0; i < SDL_numGestureTouches; i++)
+            if (SDL_gestureTouch[i].id == touchId)
+                touch = &SDL_gestureTouch[i];
+        if (touch == NULL) return -1;
+    }
+
+    while (1) {
+        SDL_DollarTemplate templ;
+
+        if (SDL_RWread(src,templ.path,sizeof(templ.path[0]),DOLLARNPOINTS) <
+           DOLLARNPOINTS) break;
+
+        if (touchId >= 0) {
+            //printf("Adding loaded gesture to 1 touch\n");
+            if (SDL_AddDollarGesture(touch, templ.path) >= 0)
+                loaded++;
+        }
+        else {
+            //printf("Adding to: %i touches\n",SDL_numGestureTouches);
+            for (i = 0; i < SDL_numGestureTouches; i++) {
+                touch = &SDL_gestureTouch[i];
+                //printf("Adding loaded gesture to + touches\n");
+                //TODO: What if this fails?
+                SDL_AddDollarGesture(touch,templ.path);
+            }
+            loaded++;
+        }
+    }
+
+    return loaded;
+}
+
+
+static float dollarDifference(SDL_FloatPoint* points,SDL_FloatPoint* templ,float ang)
+{
+    //  SDL_FloatPoint p[DOLLARNPOINTS];
+    float dist = 0;
+    SDL_FloatPoint p;
+    int i;
+    for (i = 0; i < DOLLARNPOINTS; i++) {
+        p.x = (float)(points[i].x * SDL_cos(ang) - points[i].y * SDL_sin(ang));
+        p.y = (float)(points[i].x * SDL_sin(ang) + points[i].y * SDL_cos(ang));
+        dist += (float)(SDL_sqrt((p.x-templ[i].x)*(p.x-templ[i].x)+
+                                 (p.y-templ[i].y)*(p.y-templ[i].y)));
+    }
+    return dist/DOLLARNPOINTS;
+
+}
+
+static float bestDollarDifference(SDL_FloatPoint* points,SDL_FloatPoint* templ)
+{
+    //------------BEGIN DOLLAR BLACKBOX----------------//
+    //-TRANSLATED DIRECTLY FROM PSUDEO-CODE AVAILABLE AT-//
+    //-"http://depts.washington.edu/aimgroup/proj/dollar/"-//
+    double ta = -M_PI/4;
+    double tb = M_PI/4;
+    double dt = M_PI/90;
+    float x1 = (float)(PHI*ta + (1-PHI)*tb);
+    float f1 = dollarDifference(points,templ,x1);
+    float x2 = (float)((1-PHI)*ta + PHI*tb);
+    float f2 = dollarDifference(points,templ,x2);
+    while (SDL_fabs(ta-tb) > dt) {
+        if (f1 < f2) {
+            tb = x2;
+            x2 = x1;
+            f2 = f1;
+            x1 = (float)(PHI*ta + (1-PHI)*tb);
+            f1 = dollarDifference(points,templ,x1);
+        }
+        else {
+            ta = x1;
+            x1 = x2;
+            f1 = f2;
+            x2 = (float)((1-PHI)*ta + PHI*tb);
+            f2 = dollarDifference(points,templ,x2);
+        }
+    }
+    /*
+      if (f1 <= f2)
+          printf("Min angle (x1): %f\n",x1);
+      else if (f1 >  f2)
+          printf("Min angle (x2): %f\n",x2);
+    */
+    return SDL_min(f1,f2);
+}
+
+//DollarPath contains raw points, plus (possibly) the calculated length
+static int dollarNormalize(const SDL_DollarPath *path,SDL_FloatPoint *points)
+{
+    int i;
+    float interval;
+    float dist;
+    int numPoints = 0;
+    SDL_FloatPoint centroid;
+    float xmin,xmax,ymin,ymax;
+    float ang;
+    float w,h;
+    float length = path->length;
+
+    //Calculate length if it hasn't already been done
+    if (length <= 0) {
+        for (i=1;i < path->numPoints; i++) {
+            float dx = path->p[i  ].x - path->p[i-1].x;
+            float dy = path->p[i  ].y - path->p[i-1].y;
+            length += (float)(SDL_sqrt(dx*dx+dy*dy));
+        }
+    }
+
+    //Resample
+    interval = length/(DOLLARNPOINTS - 1);
+    dist = interval;
+
+    centroid.x = 0;centroid.y = 0;
+
+    //printf("(%f,%f)\n",path->p[path->numPoints-1].x,path->p[path->numPoints-1].y);
+    for (i = 1; i < path->numPoints; i++) {
+        float d = (float)(SDL_sqrt((path->p[i-1].x-path->p[i].x)*(path->p[i-1].x-path->p[i].x)+
+                                   (path->p[i-1].y-path->p[i].y)*(path->p[i-1].y-path->p[i].y)));
+        //printf("d = %f dist = %f/%f\n",d,dist,interval);
+        while (dist + d > interval) {
+            points[numPoints].x = path->p[i-1].x +
+                ((interval-dist)/d)*(path->p[i].x-path->p[i-1].x);
+            points[numPoints].y = path->p[i-1].y +
+                ((interval-dist)/d)*(path->p[i].y-path->p[i-1].y);
+            centroid.x += points[numPoints].x;
+            centroid.y += points[numPoints].y;
+            numPoints++;
+
+            dist -= interval;
+        }
+        dist += d;
+    }
+    if (numPoints < DOLLARNPOINTS-1) {
+        SDL_SetError("ERROR: NumPoints = %i\n",numPoints);
+        return 0;
+    }
+    //copy the last point
+    points[DOLLARNPOINTS-1] = path->p[path->numPoints-1];
+    numPoints = DOLLARNPOINTS;
+
+    centroid.x /= numPoints;
+    centroid.y /= numPoints;
+
+    //printf("Centroid (%f,%f)",centroid.x,centroid.y);
+    //Rotate Points so point 0 is left of centroid and solve for the bounding box
+    xmin = centroid.x;
+    xmax = centroid.x;
+    ymin = centroid.y;
+    ymax = centroid.y;
+
+    ang = (float)(SDL_atan2(centroid.y - points[0].y,
+                            centroid.x - points[0].x));
+
+    for (i = 0; i<numPoints; i++) {
+        float px = points[i].x;
+        float py = points[i].y;
+        points[i].x = (float)((px - centroid.x)*SDL_cos(ang) -
+                              (py - centroid.y)*SDL_sin(ang) + centroid.x);
+        points[i].y = (float)((px - centroid.x)*SDL_sin(ang) +
+                              (py - centroid.y)*SDL_cos(ang) + centroid.y);
+
+
+        if (points[i].x < xmin) xmin = points[i].x;
+        if (points[i].x > xmax) xmax = points[i].x;
+        if (points[i].y < ymin) ymin = points[i].y;
+        if (points[i].y > ymax) ymax = points[i].y;
+    }
+
+    //Scale points to DOLLARSIZE, and translate to the origin
+    w = xmax-xmin;
+    h = ymax-ymin;
+
+    for (i=0; i<numPoints; i++) {
+        points[i].x = (points[i].x - centroid.x)*DOLLARSIZE/w;
+        points[i].y = (points[i].y - centroid.y)*DOLLARSIZE/h;
+    }
+    return numPoints;
+}
+
+static float dollarRecognize(const SDL_DollarPath *path,int *bestTempl,SDL_GestureTouch* touch)
+{
+
+    SDL_FloatPoint points[DOLLARNPOINTS];
+    int i;
+    float bestDiff = 10000;
+
+    dollarNormalize(path,points);
+
+    //PrintPath(points);
+    *bestTempl = -1;
+    for (i = 0; i < touch->numDollarTemplates; i++) {
+        float diff = bestDollarDifference(points,touch->dollarTemplate[i].path);
+        if (diff < bestDiff) {bestDiff = diff; *bestTempl = i;}
+    }
+    return bestDiff;
+}
+
+int SDL_GestureAddTouch(SDL_Touch* touch)
+{
+    SDL_GestureTouch *gestureTouch = (SDL_GestureTouch *)SDL_realloc(SDL_gestureTouch,
+                                                                     (SDL_numGestureTouches + 1) *
+                                                                     sizeof(SDL_GestureTouch));
+
+    if (!gestureTouch) {
+        SDL_OutOfMemory();
+        return -1;
+    }
+
+    SDL_gestureTouch = gestureTouch;
+
+    SDL_gestureTouch[SDL_numGestureTouches].res.x = touch->xres;
+    SDL_gestureTouch[SDL_numGestureTouches].res.y = touch->yres;
+    SDL_gestureTouch[SDL_numGestureTouches].numDownFingers = 0;
+
+    SDL_gestureTouch[SDL_numGestureTouches].res.x = touch->xres;
+    SDL_gestureTouch[SDL_numGestureTouches].id = touch->id;
+
+    SDL_gestureTouch[SDL_numGestureTouches].numDollarTemplates = 0;
+
+    SDL_gestureTouch[SDL_numGestureTouches].recording = SDL_FALSE;
+
+    SDL_numGestureTouches++;
+    return 0;
+}
+
+static SDL_GestureTouch * SDL_GetGestureTouch(SDL_TouchID id)
+{
+    int i;
+    for (i = 0; i < SDL_numGestureTouches; i++) {
+        //printf("%i ?= %i\n",SDL_gestureTouch[i].id,id);
+        if (SDL_gestureTouch[i].id == id)
+            return &SDL_gestureTouch[i];
+    }
+    return NULL;
+}
+
+int SDL_SendGestureMulti(SDL_GestureTouch* touch,float dTheta,float dDist)
+{
+    SDL_Event event;
+    event.mgesture.type = SDL_MULTIGESTURE;
+    event.mgesture.touchId = touch->id;
+    event.mgesture.x = touch->centroid.x;
+    event.mgesture.y = touch->centroid.y;
+    event.mgesture.dTheta = dTheta;
+    event.mgesture.dDist = dDist;
+    event.mgesture.numFingers = touch->numDownFingers;
+    return SDL_PushEvent(&event) > 0;
+}
+
+static int SDL_SendGestureDollar(SDL_GestureTouch* touch,
+                          SDL_GestureID gestureId,float error)
+{
+    SDL_Event event;
+    event.dgesture.type = SDL_DOLLARGESTURE;
+    event.dgesture.touchId = touch->id;
+    /*
+    //TODO: Add this to give location of gesture?
+    event.mgesture.x = touch->centroid.x;
+    event.mgesture.y = touch->centroid.y;
+    */
+    event.dgesture.gestureId = gestureId;
+    event.dgesture.error = error;
+    //A finger came up to trigger this event.
+    event.dgesture.numFingers = touch->numDownFingers + 1;
+    return SDL_PushEvent(&event) > 0;
+}
+
+
+static int SDL_SendDollarRecord(SDL_GestureTouch* touch,SDL_GestureID gestureId)
+{
+    SDL_Event event;
+    event.dgesture.type = SDL_DOLLARRECORD;
+    event.dgesture.touchId = touch->id;
+    event.dgesture.gestureId = gestureId;
+    return SDL_PushEvent(&event) > 0;
+}
+
+
+void SDL_GestureProcessEvent(SDL_Event* event)
+{
+    float x,y;
+    SDL_FloatPoint path[DOLLARNPOINTS];
+    int index;
+    int i;
+    float pathDx, pathDy;
+    SDL_FloatPoint lastP;
+    SDL_FloatPoint lastCentroid;
+    float lDist;
+    float Dist;
+    float dtheta;
+    float dDist;
+
+    if (event->type == SDL_FINGERMOTION ||
+        event->type == SDL_FINGERDOWN ||
+        event->type == SDL_FINGERUP) {
+        SDL_GestureTouch* inTouch = SDL_GetGestureTouch(event->tfinger.touchId);
+
+        //Shouldn't be possible
+        if (inTouch == NULL) return;
+
+        //printf("@ (%i,%i) with res: (%i,%i)\n",(int)event->tfinger.x,
+        //           (int)event->tfinger.y,
+        //   (int)inTouch->res.x,(int)inTouch->res.y);
+
+
+        x = ((float)event->tfinger.x)/(float)inTouch->res.x;
+        y = ((float)event->tfinger.y)/(float)inTouch->res.y;
+
+
+        //Finger Up
+        if (event->type == SDL_FINGERUP) {
+            inTouch->numDownFingers--;
+
+#ifdef ENABLE_DOLLAR
+            if (inTouch->recording) {
+                inTouch->recording = SDL_FALSE;
+                dollarNormalize(&inTouch->dollarPath,path);
+                //PrintPath(path);
+                if (recordAll) {
+                    index = SDL_AddDollarGesture(NULL,path);
+                    for (i = 0; i < SDL_numGestureTouches; i++)
+                        SDL_gestureTouch[i].recording = SDL_FALSE;
+                }
+                else {
+                    index = SDL_AddDollarGesture(inTouch,path);
+                }
+
+                if (index >= 0) {
+                    SDL_SendDollarRecord(inTouch,inTouch->dollarTemplate[index].hash);
+                }
+                else {
+                    SDL_SendDollarRecord(inTouch,-1);
+                }
+            }
+            else {
+                int bestTempl;
+                float error;
+                error = dollarRecognize(&inTouch->dollarPath,
+                                        &bestTempl,inTouch);
+                if (bestTempl >= 0){
+                    //Send Event
+                    unsigned long gestureId = inTouch->dollarTemplate[bestTempl].hash;
+                    SDL_SendGestureDollar(inTouch,gestureId,error);
+                    //printf ("%s\n",);("Dollar error: %f\n",error);
+                }
+            }
+#endif
+            //inTouch->gestureLast[j] = inTouch->gestureLast[inTouch->numDownFingers];
+            if (inTouch->numDownFingers > 0) {
+                inTouch->centroid.x = (inTouch->centroid.x*(inTouch->numDownFingers+1)-
+                                       x)/inTouch->numDownFingers;
+                inTouch->centroid.y = (inTouch->centroid.y*(inTouch->numDownFingers+1)-
+                                       y)/inTouch->numDownFingers;
+            }
+        }
+        else if (event->type == SDL_FINGERMOTION) {
+            float dx = ((float)event->tfinger.dx)/(float)inTouch->res.x;
+            float dy = ((float)event->tfinger.dy)/(float)inTouch->res.y;
+            //printf("dx,dy: (%f,%f)\n",dx,dy);
+#ifdef ENABLE_DOLLAR
+            SDL_DollarPath* path = &inTouch->dollarPath;
+            if (path->numPoints < MAXPATHSIZE) {
+                path->p[path->numPoints].x = inTouch->centroid.x;
+                path->p[path->numPoints].y = inTouch->centroid.y;
+                pathDx =
+                    (path->p[path->numPoints].x-path->p[path->numPoints-1].x);
+                pathDy =
+                    (path->p[path->numPoints].y-path->p[path->numPoints-1].y);
+                path->length += (float)SDL_sqrt(pathDx*pathDx + pathDy*pathDy);
+                path->numPoints++;
+            }
+#endif
+            lastP.x = x - dx;
+            lastP.y = y - dy;
+            lastCentroid = inTouch->centroid;
+
+            inTouch->centroid.x += dx/inTouch->numDownFingers;
+            inTouch->centroid.y += dy/inTouch->numDownFingers;
+            //printf("Centrid : (%f,%f)\n",inTouch->centroid.x,inTouch->centroid.y);
+            if (inTouch->numDownFingers > 1) {
+                SDL_FloatPoint lv; //Vector from centroid to last x,y position
+                SDL_FloatPoint v; //Vector from centroid to current x,y position
+                //lv = inTouch->gestureLast[j].cv;
+                lv.x = lastP.x - lastCentroid.x;
+                lv.y = lastP.y - lastCentroid.y;
+                lDist = (float)SDL_sqrt(lv.x*lv.x + lv.y*lv.y);
+                //printf("lDist = %f\n",lDist);
+                v.x = x - inTouch->centroid.x;
+                v.y = y - inTouch->centroid.y;
+                //inTouch->gestureLast[j].cv = v;
+                Dist = (float)SDL_sqrt(v.x*v.x+v.y*v.y);
+                // SDL_cos(dTheta) = (v . lv)/(|v| * |lv|)
+
+                //Normalize Vectors to simplify angle calculation
+                lv.x/=lDist;
+                lv.y/=lDist;
+                v.x/=Dist;
+                v.y/=Dist;
+                dtheta = (float)SDL_atan2(lv.x*v.y - lv.y*v.x,lv.x*v.x + lv.y*v.y);
+
+                dDist = (Dist - lDist);
+                if (lDist == 0) {dDist = 0;dtheta = 0;} //To avoid impossible values
+
+                //inTouch->gestureLast[j].dDist = dDist;
+                //inTouch->gestureLast[j].dtheta = dtheta;
+
+                //printf("dDist = %f, dTheta = %f\n",dDist,dtheta);
+                //gdtheta = gdtheta*.9 + dtheta*.1;
+                //gdDist  =  gdDist*.9 +  dDist*.1
+                //knob.r += dDist/numDownFingers;
+                //knob.ang += dtheta;
+                //printf("thetaSum = %f, distSum = %f\n",gdtheta,gdDist);
+                //printf("id: %i dTheta = %f, dDist = %f\n",j,dtheta,dDist);
+                SDL_SendGestureMulti(inTouch,dtheta,dDist);
+            }
+            else {
+                //inTouch->gestureLast[j].dDist = 0;
+                //inTouch->gestureLast[j].dtheta = 0;
+                //inTouch->gestureLast[j].cv.x = 0;
+                //inTouch->gestureLast[j].cv.y = 0;
+            }
+            //inTouch->gestureLast[j].f.p.x = x;
+            //inTouch->gestureLast[j].f.p.y = y;
+            //break;
+            //pressure?
+        }
+
+        if (event->type == SDL_FINGERDOWN) {
+
+            inTouch->numDownFingers++;
+            inTouch->centroid.x = (inTouch->centroid.x*(inTouch->numDownFingers - 1)+
+                                   x)/inTouch->numDownFingers;
+            inTouch->centroid.y = (inTouch->centroid.y*(inTouch->numDownFingers - 1)+
+                                   y)/inTouch->numDownFingers;
+            //printf("Finger Down: (%f,%f). Centroid: (%f,%f\n",x,y,
+            //     inTouch->centroid.x,inTouch->centroid.y);
+
+#ifdef ENABLE_DOLLAR
+            inTouch->dollarPath.length = 0;
+            inTouch->dollarPath.p[0].x = x;
+            inTouch->dollarPath.p[0].y = y;
+            inTouch->dollarPath.numPoints = 1;
+#endif
+        }
+    }
+}
+
+#else
+void SDL_GestureProcessEvent(SDL_Event* event)
+{
+}
+
+int SDL_GestureAddTouch(SDL_Touch* touch)
+{
+    return 0;
+}
+#endif
+
+/* vi: set ts=4 sw=4 expandtab: */

+ 4 - 4
ThirdParty/SDL/src/main/android/SDL_android_main.cpp

@@ -13,13 +13,13 @@
 #include <jni.h>
 
 // Called before SDL_main() to initialize JNI bindings in SDL library
-extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls);
-                            
+extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls, jstring filesDir);
+
 // Start up the SDL app
-extern "C" void Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj)
+extern "C" void Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jstring filesDir)
 {
     /* This interface could expand with ABI negotiation, calbacks, etc. */
-    SDL_Android_Init(env, cls);
+    SDL_Android_Init(env, cls, filesDir);
 
     /* Run the application code! */
     int status;

+ 1 - 1
Urho3D/Urho3D.cpp

@@ -96,7 +96,7 @@ void Run()
         
         #ifdef ANDROID
         // Can not pass script name on Android, so choose a hardcoded default
-        scriptFileName = "Scripts/TestScene.as";
+        scriptFileName = "Scripts/NinjaSnowWar.as";
         #endif
         
         // Show usage if not found