Browse Source

Merge branch 'master' of https://github.com/BearishSun/BansheeEngine.git

marco.bellan 9 years ago
parent
commit
6695c24b5f

+ 4 - 8
Documentation/GitHub/license.md

@@ -1,6 +1,6 @@
 # License
 
-Banshee if offered using a dual-license model: LGPL v3 and Commercial.
+Banshee is offered using a dual-license model: LGPL v3 and Commercial.
 
 ## GNU Lesser General Public License v3
 
@@ -10,8 +10,6 @@ Banshee is offered completely free for personal or commercial use under the GNU
 A commercial license is available for those that are not comfortable with LGPL terms. The commercial license is a paid per-user license that allows you to distribute your application under your own terms.
 
 It is available under the "pay what you want" model, meaning you can purchase it for as little as $0 or as much as $1000. This is completely up to you and we won't judge if you just want a free license.
-
-However if you like Banshee consider spending some money as this ensures we can pay the costs for developing it further. The money will be invested into hiring more developers to help with adding more features faster (see [Roadmap](roadmap.md)), as well as help with testing, polish and documentation.
  
 ## Third party licenses
 Licenses for all third party libraries used by Banshee can be found in License\ThirdParty sub-directory of the source code.
@@ -19,18 +17,16 @@ Licenses for all third party libraries used by Banshee can be found in License\T
 ## License FAQ
 
 *Why have two separate licenses?*
-LGPL is offered because at its core Banshee is an open source project. Commercial license is offered because we wish to fund Banshee development so we can make it bigger and better than other open source engines.
+LGPL is offered because at its core Banshee is an open source project. Commercial license is offered so we can fund Banshee development in order to make it better than other open source engines.
 
 *How do I get a commercial license?*
-Until a website is set up, you must contact me personally at [email protected]
+Commercial licenses will become available after Banshee v1.0 is released.
 
 *What are the differences between the two licenses?*
 Engine features for both license models are identical. The only difference are the license terms.
 
 *What are the limitations of the LGPL license?*
-When modifying the source code of the engine, or linking the engine statically with your application you will be required to release your code/application under LGPL or a compatible license.
-
-This involves providing the source code for your application, as well as giving the rights to use, modify and redistribute your code/application to anyone who acquires it.
+When modifying the source code of the engine, or linking the engine statically with your application you will be required to release your code/application under LGPL or a compatible license. This involves providing the source code for your application, as well as giving the rights to use, modify and redistribute your code/application to anyone who acquires it.
 
 *What are the limitations of the commercial license?*
 You can publish binaries of your product (including all portions of Banshee, modified or original) under any terms you wish. The only limitation being that you are not allowed to publish Banshee's source code (modified or original) under a custom license (source code must be published under the LGPL).

+ 1 - 1
README.md

@@ -5,7 +5,7 @@ On top of the engine Banshee also provides a highly intuitive and customizable *
 
 The scripting system supports C# and comes with an extensive API ensuring you can complete your game without ever touching the C++ engine core. **C# scripting** makes your development easier by giving you access to the entire .NET library and a wide variety of pre-existing managed libraries. Integration of the scripting system with the editor and external tools like Visual Studio, as well as fast compilation times ensures that iteration times between coding and testing are minimized.
 
-Aside from being a fully featured game engine and toolkit, Banshee can also be used as a **low level library**, providing a powerful foundation to build new technologies with or to easily customize the engine for game specific needs. Layered and plugin based design allows developers to use only the functionality they need, and to fully remove or replace major engine systems. Banshee's code is modern, with clean interfaces that make it easy to learn and maintain. Platform specific functionality is kept at a minimum making porting as easy as possible. It is fully documented with an extensive API reference, as well as a set of manuals introducing you to most major systems.
+Aside from being a fully featured game engine and toolkit, Banshee can also be used as a **low level framework**, providing a powerful foundation to build new technologies with or to easily customize the engine for game specific needs. Layered and plugin based design allows developers to use only the functionality they need, and to fully remove or replace major engine systems. Banshee's code is modern, with clean interfaces that make it easy to learn and maintain. Platform specific functionality is kept at a minimum making porting as easy as possible. It is fully documented with an extensive API reference, as well as a set of manuals introducing you to most major systems.
 
 # Features
 * [Features](Documentation/GitHub/features.md) - A list of all currently available features.

+ 3 - 1
Source/BansheeCore/Include/BsCollider.h

@@ -2,6 +2,8 @@
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #pragma once
 
+#include <cfloat>
+
 #include "BsCorePrerequisites.h"
 #include "BsPhysicsCommon.h"
 #include "BsVector3.h"
@@ -155,4 +157,4 @@ namespace BansheeEngine
 	};
 
 	/** @} */
-}
+}

+ 16 - 1
Source/BansheeCore/Include/BsGpuProgram.h

@@ -231,4 +231,19 @@ namespace BansheeEngine
 	};
 
 	/** @} */
-}
+}
+
+namespace std
+{
+/** Hash value generator for GpuProgramProfile. */
+template<>
+struct hash<BansheeEngine::GpuProgramProfile>
+{
+	size_t operator()(const BansheeEngine::GpuProgramProfile& profile) const
+	{
+		size_t hash = 0;
+		BansheeEngine::hash_combine(hash, (int)profile);
+		return hash;
+	}
+};
+}

+ 3 - 1
Source/BansheeCore/Include/BsJoint.h

@@ -2,6 +2,8 @@
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #pragma once
 
+#include <cfloat>
+
 #include "BsCorePrerequisites.h"
 #include "BsPhysicsCommon.h"
 #include "BsFJoint.h"
@@ -342,4 +344,4 @@ namespace BansheeEngine
 	};
 
 	/** @} */
-}
+}

+ 34 - 18
Source/BansheeEngine/Source/BsGUISliderHandle.cpp

@@ -251,9 +251,9 @@ namespace BansheeEngine
 						INT32 right = left + handleSize;
 
 						INT32 clickPos = ev.getPosition().x;
-						if(clickPos >= left && clickPos < (left + (INT32)RESIZE_HANDLE_SIZE))
+						if (clickPos >= left && clickPos < (left + (INT32)RESIZE_HANDLE_SIZE))
 							mDragState = DragState::LeftResize;
-						else if(clickPos >= (right - (INT32)RESIZE_HANDLE_SIZE) && clickPos < right)
+						else if (clickPos >= (right - (INT32)RESIZE_HANDLE_SIZE) && clickPos < right)
 							mDragState = DragState::RightResize;
 						else
 							mDragState = DragState::Normal;
@@ -295,36 +295,52 @@ namespace BansheeEngine
 		{
 			if (!_isDisabled())
 			{
-				INT32 handlePosPx;
-				if (mFlags.isSet(GUISliderHandleFlag::Horizontal))
-					handlePosPx = ev.getPosition().x - mDragStartPos - mLayoutData.area.x;
-				else
-					handlePosPx = ev.getPosition().y - mDragStartPos - mLayoutData.area.y;
-
 				if (mDragState == DragState::Normal)
 				{
+					INT32 handlePosPx;
+					if (mFlags.isSet(GUISliderHandleFlag::Horizontal))
+						handlePosPx = ev.getPosition().x - mDragStartPos - mLayoutData.area.x;
+					else
+						handlePosPx = ev.getPosition().y - mDragStartPos - mLayoutData.area.y;
+
 					setHandlePosPx(handlePosPx);
 					onHandleMovedOrResized(mPctHandlePos, _getHandleSizePct());
 				}
 				else // Resizing
 				{
+					INT32 clickPosPx;
+					if (mFlags.isSet(GUISliderHandleFlag::Horizontal))
+						clickPosPx = ev.getPosition().x - mLayoutData.area.x;
+					else
+						clickPosPx = ev.getPosition().y - mLayoutData.area.y;
+
+					INT32 left = getHandlePosPx();
+					UINT32 maxSize = getMaxSize();
+
+					INT32 newHandleSize;
+					float newHandlePos;
 					if(mDragState == DragState::LeftResize)
 					{
-						INT32 right = getHandlePosPx() + handleSize;
-						INT32 newHandleSize = right - handlePosPx;
+						INT32 newLeft = clickPosPx - mDragStartPos;
+						INT32 right = left + handleSize;
+						newLeft = Math::clamp(newLeft, 0, right);
 
-						_setHandleSize(newHandleSize / (float)getMaxSize());
-						setHandlePosPx(handlePosPx);
-						onHandleMovedOrResized(mPctHandlePos, _getHandleSizePct());
+						newHandleSize = std::max((INT32)mMinHandleSize, right - newLeft);
+						newLeft = right - newHandleSize;
+						newHandlePos = newLeft / (float)(maxSize - newHandleSize);
 					}
-					else if(mDragState == DragState::RightResize)
+					else // Right resize
 					{
-						INT32 left = getHandlePosPx();
-						INT32 newHandleSize = handlePosPx - left;
+						INT32 newRight = clickPosPx;
+						newHandleSize = std::max((INT32)mMinHandleSize, std::min(newRight, (INT32)maxSize) - left);
 
-						_setHandleSize(newHandleSize / (float)getMaxSize());
-						onHandleMovedOrResized(mPctHandlePos, _getHandleSizePct());
+						newHandlePos = left / (float)(maxSize - newHandleSize);
 					}
+
+					_setHandleSize(newHandleSize / (float)maxSize);
+					_setHandlePos(newHandlePos);
+
+					onHandleMovedOrResized(mPctHandlePos, _getHandleSizePct());
 				}
 
 				_markLayoutAsDirty();

+ 2 - 2
Source/BansheeGLRenderAPI/Include/BsGLPrerequisites.h

@@ -24,7 +24,7 @@
 #   include <GL/glew.h>
 #   include <GL/glu.h>
 #   define GL_GLEXT_PROTOTYPES
-#elif BS_PLATFORM == BS_PLATFORM_APPLE
+#elif BS_PLATFORM == BS_PLATFORM_OSX
 #   include <GL/glew.h>
 #   include <OpenGL/glu.h>
 #endif
@@ -101,4 +101,4 @@ namespace BansheeEngine
 	};
 
 	/** @} */
-}
+}

+ 2 - 2
Source/BansheeOISInput/Source/BsInputHandlerOIS.cpp

@@ -73,7 +73,7 @@ namespace BansheeEngine
 		pl.insert(std::make_pair(std::string("w32_mouse"), std::string("DISCL_NONEXCLUSIVE")));
 		pl.insert(std::make_pair(std::string("w32_keyboard"), std::string("DISCL_FOREGROUND")));
 		pl.insert(std::make_pair(std::string("w32_keyboard"), std::string("DISCL_NONEXCLUSIVE")));
-#elif defined BS_PLATFORM == BS_PLATFORM_LINUX || BS_PLATFORM == BS_PLATFORM_APPLE
+#elif defined BS_PLATFORM == BS_PLATFORM_LINUX || BS_PLATFORM == BS_PLATFORM_OSX
 		pl.insert(std::make_pair(std::string("x11_mouse_grab"), std::string("false")));
 		pl.insert(std::make_pair(std::string("x11_mouse_hide"), std::string("false")));
 		pl.insert(std::make_pair(std::string("x11_keyboard_grab"), std::string("false")));
@@ -334,4 +334,4 @@ namespace BansheeEngine
 
 		return (ButtonCode)(BC_GAMEPAD_BTN1 + (joystickCode - 15));
 	}
-}
+}

+ 2 - 2
Source/BansheeUtility/Include/BsAny.h

@@ -52,7 +52,7 @@ namespace BansheeEngine
 			:mData(bs_new<Data<ValueType>>(value))
 		{ }
 
-		Any(nullptr_t)
+		Any(std::nullptr_t)
 			:mData(nullptr)
 		{ }
 
@@ -186,4 +186,4 @@ namespace BansheeEngine
 	}
 
 	/** @} */
-}
+}

+ 1 - 7
Source/BansheeUtility/Include/BsDynLib.h

@@ -21,18 +21,12 @@ namespace BansheeEngine
 #    define DYNLIB_GETSYM( a, b ) GetProcAddress( a, b )
 #    define DYNLIB_UNLOAD( a ) !FreeLibrary( a )
 
-#elif BS_PLATFORM == BS_PLATFORM_LINUX
+#elif BS_PLATFORM == BS_PLATFORM_LINUX || BS_PLATFORM == BS_PLATFORM_OSX
 #    define DYNLIB_HANDLE void*
 #    define DYNLIB_LOAD( a ) dlopen( a, RTLD_LAZY | RTLD_GLOBAL)
 #    define DYNLIB_GETSYM( a, b ) dlsym( a, b )
 #    define DYNLIB_UNLOAD( a ) dlclose( a )
 
-#elif BS_PLATFORM == BS_PLATFORM_APPLE
-#    define DYNLIB_HANDLE void*
-#    define DYNLIB_LOAD( a ) mac_loadDylib( a )
-#    define DYNLIB_GETSYM( a, b ) dlsym( a, b )
-#    define DYNLIB_UNLOAD( a ) dlclose( a )
-
 #endif
 
     /** Class that holds data about a dynamic library. */

+ 21 - 4
Source/BansheeUtility/Include/BsFrameAlloc.h

@@ -2,6 +2,9 @@
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #pragma once
 
+#include <limits>
+#include <new>                  /* For 'placement new' */
+
 #include "BsPrerequisitesUtil.h"
 
 namespace BansheeEngine
@@ -151,6 +154,12 @@ namespace BansheeEngine
 	{
 	public:
 		typedef T value_type;
+		typedef value_type* pointer;
+		typedef const value_type* const_pointer;
+		typedef value_type& reference;
+		typedef const value_type& const_reference;
+		typedef std::size_t size_type;
+		typedef std::ptrdiff_t difference_type;
 
 		StdFrameAlloc() noexcept 
 			:mFrameAlloc(nullptr)
@@ -160,12 +169,13 @@ namespace BansheeEngine
 			:mFrameAlloc(alloc)
 		{ }
 
-		template<class T> StdFrameAlloc(const StdFrameAlloc<T>& alloc) noexcept
+		template<class U> StdFrameAlloc(const StdFrameAlloc<U>& alloc) noexcept
 			:mFrameAlloc(alloc.mFrameAlloc)
 		{ }
 
-		template<class T> bool operator==(const StdFrameAlloc<T>&) const noexcept { return true; }
-		template<class T> bool operator!=(const StdFrameAlloc<T>&) const noexcept { return false; }
+		template<class U> bool operator==(const StdFrameAlloc<U>&) const noexcept { return true; }
+		template<class U> bool operator!=(const StdFrameAlloc<U>&) const noexcept { return false; }
+		template<class U> class rebind { public: typedef StdFrameAlloc<U> other; };
 
 		/** Allocate but don't initialize number elements of type T.*/
 		T* allocate(const size_t num) const
@@ -190,6 +200,13 @@ namespace BansheeEngine
 		}
 
 		FrameAlloc* mFrameAlloc;
+
+		size_t max_size() const { return std::numeric_limits<size_type>::max() / sizeof(T); }
+		void construct(pointer p, const_reference t) { new (p) T(t); }
+		void destroy(pointer p) { p->~T(); }
+		template<class U, class... Args>
+		void construct(U* p, Args&&... args) { new(p) U(std::forward<Args>(args)...); }
+
 	};
 
 	/** Return that all specializations of this allocator are interchangeable. */
@@ -208,4 +225,4 @@ namespace BansheeEngine
 
 	/** @} */
 	/** @} */
-}
+}

+ 21 - 6
Source/BansheeUtility/Include/BsMemoryAllocator.h

@@ -1,11 +1,12 @@
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #pragma once
-
 #undef min
 #undef max
 
 #include <atomic>
+#include <limits>
+#include <new>                  /* For 'placement new' */
 #include <utility>
 
 #if BS_PLATFORM == BS_PLATFORM_LINUX
@@ -377,16 +378,24 @@ namespace BansheeEngine
 	 *  @{
 	 */
 
-    /** Allocator for the standard library that internally uses Banshee memory allocator. */
-    template <class T, class Alloc = GenAlloc>
+	/** Allocator for the standard library that internally uses Banshee memory allocator. */
+	template <class T, class Alloc = GenAlloc>
 	class StdAlloc 
 	{
 	public:
 		typedef T value_type;
+		typedef T* pointer;
+		typedef const T* const_pointer;
+		typedef T& reference;
+		typedef const T& const_reference;
+		typedef std::size_t size_type;
+		typedef std::ptrdiff_t difference_type;
+
 		StdAlloc() noexcept {}
-		template<class T, class Alloc> StdAlloc(const StdAlloc<T, Alloc>&) noexcept {}
-		template<class T, class Alloc> bool operator==(const StdAlloc<T, Alloc>&) const noexcept { return true; }
-		template<class T, class Alloc> bool operator!=(const StdAlloc<T, Alloc>&) const noexcept { return false; }
+		template<class U, class Alloc2> StdAlloc(const StdAlloc<U, Alloc2>&) noexcept {}
+		template<class U, class Alloc2> bool operator==(const StdAlloc<U, Alloc2>&) const noexcept { return true; }
+		template<class U, class Alloc2> bool operator!=(const StdAlloc<U, Alloc2>&) const noexcept { return false; }
+		template<class U> class rebind { public: typedef StdAlloc<U, Alloc> other; };
 
 		/** Allocate but don't initialize number elements of type T. */
 		T* allocate(const size_t num) const
@@ -409,6 +418,12 @@ namespace BansheeEngine
 		{
 			bs_free<Alloc>((void*)p);
 		}
+
+		size_t max_size() const { return std::numeric_limits<size_type>::max() / sizeof(T); }
+		void construct(pointer p, const_reference t) { new (p) T(t); }
+		void destroy(pointer p) { p->~T(); }
+		template<class U, class... Args>
+		void construct(U* p, Args&&... args) { new(p) U(std::forward<Args>(args)...); }
 	};
 
 	/** @} */

+ 2 - 2
Source/BansheeUtility/Include/BsPlatformDefines.h

@@ -51,7 +51,7 @@
 #if defined( __WIN32__ ) || defined( _WIN32 )
 #   define BS_PLATFORM BS_PLATFORM_WIN32
 #elif defined( __APPLE_CC__)
-#   define BS_PLATFORM BS_PLATFORM_APPLE
+#   define BS_PLATFORM BS_PLATFORM_OSX
 #else
 #   define BS_PLATFORM BS_PLATFORM_LINUX
 #endif
@@ -116,4 +116,4 @@
 #        define BS_THREADLOCAL __thread
 #	endif
 
-#endif
+#endif

+ 2 - 2
Source/BansheeUtility/Include/BsRTTIType.h

@@ -546,7 +546,7 @@ namespace BansheeEngine
 
 			UINT32 typeSize = 0;
 			if(field->hasDynamicSize())
-				typeSize = field->getArrayElemDynamicSize(object, arrIdx);
+				typeSize = field->getArrayElemDynamicSize(object, index);
 			else
 				typeSize = field->getTypeSize();
 
@@ -1238,4 +1238,4 @@ namespace BansheeEngine
 	}
 
 	/** @} */
-}
+}

+ 1 - 1
Source/BansheeUtility/Include/BsVectorNI.h

@@ -52,7 +52,7 @@ namespace BansheeEngine
 		{
 			for (UINT32 i = 0; i < N; i++)
 			{
-				if (v[i] != rhs.v[i])
+				if (v[i] != rhs[i])
 					return false;
 			}
 

+ 1 - 2
Source/BansheeUtility/Source/BsDynLib.cpp

@@ -12,7 +12,6 @@
 #endif
 
 #if BS_PLATFORM == BS_PLATFORM_OSX
-#   include "macUtils.h"
 #   include <dlfcn.h>
 #endif
 
@@ -96,7 +95,7 @@ namespace BansheeEngine
         // Free the buffer.
         LocalFree(lpMsgBuf);
         return ret;
-#elif BS_PLATFORM == BS_PLATFORM_LINUX || BS_PLATFORM == BS_PLATFORM_APPLE
+#elif BS_PLATFORM == BS_PLATFORM_LINUX || BS_PLATFORM == BS_PLATFORM_OSX
         return String(dlerror());
 #else
         return String("");

+ 5 - 5
Source/BansheeUtility/Source/BsPath.cpp

@@ -117,7 +117,7 @@ namespace BansheeEngine
 		default:
 #if BS_PLATFORM == BS_PLATFORM_WIN32
 			parseWindows(pathStr, numChars);
-#elif BS_PLATFORM == BS_PLATFORM_APPLE || BS_PLATFORM == BS_PLATFORM_LINUX
+#elif BS_PLATFORM == BS_PLATFORM_OSX || BS_PLATFORM == BS_PLATFORM_LINUX
 			parseUnix(pathStr, numChars);
 #else
 			static_assert(false, "Unsupported platform for path.");
@@ -139,7 +139,7 @@ namespace BansheeEngine
 		default:
 #if BS_PLATFORM == BS_PLATFORM_WIN32
 			parseWindows(pathStr, numChars);
-#elif BS_PLATFORM == BS_PLATFORM_APPLE || BS_PLATFORM == BS_PLATFORM_LINUX
+#elif BS_PLATFORM == BS_PLATFORM_OSX || BS_PLATFORM == BS_PLATFORM_LINUX
 			parseUnix(pathStr, numChars);
 #else
 			static_assert(false, "Unsupported platform for path.");
@@ -159,7 +159,7 @@ namespace BansheeEngine
 		default:
 #if BS_PLATFORM == BS_PLATFORM_WIN32
 			return buildWindows();
-#elif BS_PLATFORM == BS_PLATFORM_APPLE || BS_PLATFORM == BS_PLATFORM_LINUX
+#elif BS_PLATFORM == BS_PLATFORM_OSX || BS_PLATFORM == BS_PLATFORM_LINUX
 			return buildUnix();
 #else
 			static_assert(false, "Unsupported platform for path.");
@@ -179,7 +179,7 @@ namespace BansheeEngine
 		default:
 #if BS_PLATFORM == BS_PLATFORM_WIN32
 			return BansheeEngine::toString(buildWindows());
-#elif BS_PLATFORM == BS_PLATFORM_APPLE || BS_PLATFORM == BS_PLATFORM_LINUX
+#elif BS_PLATFORM == BS_PLATFORM_OSX || BS_PLATFORM == BS_PLATFORM_LINUX
 			return BansheeEngine::toString(buildUnix());
 #else
 			static_assert(false, "Unsupported platform for path.");
@@ -591,4 +591,4 @@ namespace BansheeEngine
 	{
 		pushDirectory(BansheeEngine::toWString(dir));
 	}
-}
+}

+ 2 - 1
Source/BansheeUtility/Source/BsThreadPool.cpp

@@ -1,6 +1,7 @@
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #include "BsThreadPool.h"
+#include "BsDebug.h"
 
 #if BS_PLATFORM == BS_PLATFORM_WIN32
 #include "windows.h"
@@ -344,4 +345,4 @@ namespace BansheeEngine
 
 		return (UINT32)mThreads.size();
 	}
-}
+}

+ 1 - 0
Source/MBansheeEditor/MBansheeEditor.csproj

@@ -52,6 +52,7 @@
     <Compile Include="Windows\AboutBox.cs" />
     <Compile Include="Windows\AnimationWindow.cs" />
     <Compile Include="Windows\Animation\FieldSelectionWindow.cs" />
+    <Compile Include="Windows\Animation\GUIAnimEvents.cs" />
     <Compile Include="Windows\Animation\GUIAnimFieldDisplay.cs" />
     <Compile Include="Windows\Animation\GUICurveDrawing.cs" />
     <Compile Include="Windows\Animation\GUICurveEditor.cs" />

+ 167 - 0
Source/MBansheeEditor/Windows/Animation/GUIAnimEvents.cs

@@ -0,0 +1,167 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+using System;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+    /** @addtogroup AnimationEditor
+     *  @{
+     */
+
+    public class GUIAnimEvents
+    {
+        private const int EVENT_HALF_WIDTH = 2;
+
+        private float rangeLength = 60.0f;
+        private float rangeOffset = 0.0f;
+        private int fps = 1;
+
+        private GUICanvas canvas;
+        private int width;
+        private int height;
+        private int drawableWidth;
+
+        private AnimationEvent[] events = new AnimationEvent[0];
+        private bool[] selectedEvents = new bool[0];
+
+        public GUIAnimEvents(GUILayout layout, int width, int height)
+        {
+            canvas = new GUICanvas();
+            layout.AddElement(canvas);
+
+            SetSize(width, height);
+        }
+
+        public bool FindEvent(Vector2I windowCoords, out int eventIdx)
+        {
+            Rect2I bounds = canvas.Bounds;
+
+            if (windowCoords.x < (bounds.x + GUIGraphTime.PADDING) || windowCoords.x >= (bounds.x + bounds.width - GUIGraphTime.PADDING) ||
+                windowCoords.y < bounds.y || windowCoords.y >= (bounds.y + bounds.height))
+            {
+                eventIdx = -1;
+                return false;
+            }
+
+            Vector2I relativeCoords = windowCoords - new Vector2I(bounds.x + GUIGraphTime.PADDING, bounds.y);
+            for (int i = 0; i < events.Length; i++)
+            {
+                AnimationEvent evnt = events[i];
+
+                int xPos = (int)(((evnt.Time - rangeOffset) / GetRange()) * drawableWidth) + GUIGraphTime.PADDING;
+
+                if (relativeCoords.x >= (xPos - EVENT_HALF_WIDTH) || relativeCoords.y >= (xPos + EVENT_HALF_WIDTH))
+                {
+                    eventIdx = i;
+                    return true;
+                }
+            }
+
+            eventIdx = -1;
+            return false;
+        }
+
+        public void SetSize(int width, int height)
+        {
+            this.width = width;
+            this.height = height;
+
+            canvas.SetWidth(width);
+            canvas.SetHeight(height);
+
+            drawableWidth = Math.Max(0, width - GUIGraphTime.PADDING * 2);
+        }
+
+        public void SetRange(float length)
+        {
+            rangeLength = Math.Max(0.0f, length);
+        }
+
+        public void SetOffset(float offset)
+        {
+            rangeOffset = offset;
+        }
+
+        public void SetFPS(int fps)
+        {
+            this.fps = Math.Max(1, fps);
+        }
+
+        public void SetEvents(AnimationEvent[] events, bool[] selected)
+        {
+            int numEvents;
+            if (events != null)
+                numEvents = events.Length;
+            else
+                numEvents = 0;
+
+            this.events = new AnimationEvent[numEvents];
+            if(events != null)
+                Array.Copy(events, this.events, numEvents);
+
+            selectedEvents = new bool[numEvents];
+            if(selected != null)
+                Array.Copy(selected, selectedEvents, MathEx.Min(numEvents, selected.Length));
+        }
+
+        private void DrawEventMarker(float t, bool selected)
+        {
+            int xPos = (int)(((t - rangeOffset) / GetRange()) * drawableWidth) + GUIGraphTime.PADDING;
+
+            Vector2I a = new Vector2I(xPos - EVENT_HALF_WIDTH, height - 1);
+            Vector2I b = new Vector2I(xPos, 0);
+            Vector2I c = new Vector2I(xPos + EVENT_HALF_WIDTH, height - 1);
+            Vector2I d = new Vector2I(xPos, 0);
+
+            // Draw square shape
+            Vector2I[] linePoints = { a, b, c, d, a };
+            Vector2I[] trianglePoints = { b, c, a, d };
+
+            Color outerColor = selected ? Color.BansheeOrange : Color.Black;
+            canvas.DrawTriangleStrip(trianglePoints, Color.White, 101);
+            canvas.DrawPolyLine(linePoints, outerColor, 100);
+        }
+
+        private float GetRange(bool padding = false)
+        {
+            float spf = 1.0f / fps;
+
+            float range = rangeLength;
+            if (padding)
+            {
+                float lengthPerPixel = rangeLength / drawableWidth;
+                range += lengthPerPixel * GUIGraphTime.PADDING;
+            }
+
+            return ((int)range / spf) * spf;
+        }
+
+        /// <summary>
+        /// Rebuilds the internal GUI elements. Should be called whenever timeline properties change.
+        /// </summary>
+        public void Rebuild()
+        {
+            canvas.Clear();
+
+            float range = GetRange();
+
+            float lengthPerPixel = rangeLength / drawableWidth;
+            float eventHalfWidth = lengthPerPixel * EVENT_HALF_WIDTH;
+            for (int i = 0; i < events.Length; i++)
+            {
+                float t = events[i].Time;
+
+                float min = t - eventHalfWidth;
+                float max = t + eventHalfWidth;
+
+                if (max < rangeOffset || min > (rangeOffset + range))
+                    continue;
+
+                DrawEventMarker(t, selectedEvents[i]);
+            }
+        }
+    }
+
+    /** @} */
+}

+ 2 - 2
Source/MBansheeEditor/Windows/Animation/GUICurveDrawing.cs

@@ -275,7 +275,7 @@ namespace BansheeEditor
         /// <param name="pixelCoords">Coordinates relative to this GUI element, in pixels.</param>
         /// <param name="curveCoords">Curve coordinates within the range as specified by <see cref="SetRange"/>. Only
         ///                           valid when function returns true.</param>
-        /// <returns>True if the window coordinates were within the curve area, false otherwise.</returns>
+        /// <returns>True if the coordinates are within the curve area, false otherwise.</returns>
         public bool PixelToCurveSpace(Vector2I pixelCoords, out Vector2 curveCoords)
         {
             Rect2I bounds = canvas.Bounds;
@@ -501,7 +501,7 @@ namespace BansheeEditor
             if (curves == null)
                 return;
 
-            tickHandler.SetRange(offset.x, GetRange(true), drawableWidth + GUIGraphTime.PADDING);
+            tickHandler.SetRange(offset.x, offset.x + GetRange(true), drawableWidth + GUIGraphTime.PADDING);
 
             // Draw vertical frame markers
             int numTickLevels = tickHandler.NumLevels;

+ 52 - 8
Source/MBansheeEditor/Windows/Animation/GUICurveEditor.cs

@@ -37,14 +37,16 @@ namespace BansheeEditor
         }
 
         private const int TIMELINE_HEIGHT = 20;
+        private const int EVENTS_HEIGHT = 10;
         private const int SIDEBAR_WIDTH = 30;
         private const int DRAG_START_DISTANCE = 3;
 
         private EditorWindow window;
         private GUILayout gui;
         private GUIPanel drawingPanel;
-        private GUIPanel sidebarPanel;
+
         private GUIGraphTime guiTimeline;
+        private GUIAnimEvents guiEvents;
         private GUICurveDrawing guiCurveDrawing;
         private GUIGraphValues guiSidebar;
 
@@ -90,6 +92,7 @@ namespace BansheeEditor
                 yRange = value.y;
 
                 guiTimeline.SetRange(xRange);
+                guiEvents.SetRange(xRange);
                 guiCurveDrawing.SetRange(xRange, yRange * 2.0f);
                 guiSidebar.SetRange(offset.y - yRange, offset.y + yRange);
 
@@ -108,8 +111,11 @@ namespace BansheeEditor
                 offset = value;
 
                 guiTimeline.SetOffset(offset.x);
+                guiEvents.SetOffset(offset.x);
                 guiCurveDrawing.SetOffset(offset);
                 guiSidebar.SetRange(offset.y - yRange, offset.y + yRange);
+
+                Redraw();
             }
         }
 
@@ -155,19 +161,46 @@ namespace BansheeEditor
 
             guiTimeline = new GUIGraphTime(gui, width, TIMELINE_HEIGHT);
 
+            GUIPanel eventsPanel = gui.AddPanel();
+            eventsPanel.SetPosition(0, TIMELINE_HEIGHT);
+            guiEvents = new GUIAnimEvents(eventsPanel, width, EVENTS_HEIGHT);
+            
             drawingPanel = gui.AddPanel();
-            drawingPanel.SetPosition(0, TIMELINE_HEIGHT);
+            drawingPanel.SetPosition(0, TIMELINE_HEIGHT + EVENTS_HEIGHT);
 
-            guiCurveDrawing = new GUICurveDrawing(drawingPanel, width, height - TIMELINE_HEIGHT, curves);
+            guiCurveDrawing = new GUICurveDrawing(drawingPanel, width, height - TIMELINE_HEIGHT - EVENTS_HEIGHT, curves);
             guiCurveDrawing.SetRange(60.0f, 20.0f);
 
-            sidebarPanel = gui.AddPanel(-10);
-            sidebarPanel.SetPosition(0, TIMELINE_HEIGHT);
+            GUIPanel sidebarPanel = gui.AddPanel(-10);
+            sidebarPanel.SetPosition(0, TIMELINE_HEIGHT + EVENTS_HEIGHT);
 
-            guiSidebar = new GUIGraphValues(sidebarPanel, SIDEBAR_WIDTH, height - TIMELINE_HEIGHT);
+            guiSidebar = new GUIGraphValues(sidebarPanel, SIDEBAR_WIDTH, height - TIMELINE_HEIGHT - EVENTS_HEIGHT);
             guiSidebar.SetRange(-10.0f, 10.0f);
         }
 
+        /// <summary>
+        /// Converts pixel coordinates relative to the curve drawing area into coordinates in curve space.
+        /// </summary>
+        /// <param name="pixelCoords">Coordinates relative to this GUI element, in pixels.</param>
+        /// <param name="curveCoords">Curve coordinates within the range as specified by <see cref="Range"/>. Only
+        ///                           valid when function returns true.</param>
+        /// <returns>True if the coordinates are within the curve area, false otherwise.</returns>
+        public bool PixelToCurveSpace(Vector2I pixelCoords, out Vector2 curveCoords)
+        {
+            return guiCurveDrawing.PixelToCurveSpace(pixelCoords, out curveCoords);
+        }
+
+        /// <summary>
+        /// Converts coordinate in curve space (time, value) into pixel coordinates relative to the curve drawing area
+        /// origin.
+        /// </summary>
+        /// <param name="curveCoords">Time and value of the location to convert.</param>
+        /// <returns>Coordinates relative to curve drawing area's origin, in pixels.</returns>
+        public Vector2I CurveToPixelSpace(Vector2 curveCoords)
+        {
+            return guiCurveDrawing.CurveToPixelSpace(curveCoords);
+        }
+
         public bool WindowToCurveSpace(Vector2I windowPos, out Vector2 curveCoord)
         {
             Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
@@ -232,6 +265,14 @@ namespace BansheeEditor
 
                     if (frameIdx != -1)
                         SetMarkedFrame(frameIdx);
+                    else
+                    {
+                        int eventIdx;
+                        if (guiEvents.FindEvent(pointerPos, out eventIdx))
+                        {
+                            // TODO - Select event
+                        }
+                    }
 
                     OnFrameSelected?.Invoke(frameIdx);
                 }
@@ -448,8 +489,9 @@ namespace BansheeEditor
             this.height = height;
 
             guiTimeline.SetSize(width, TIMELINE_HEIGHT);
-            guiCurveDrawing.SetSize(width, height - TIMELINE_HEIGHT);
-            guiSidebar.SetSize(SIDEBAR_WIDTH, height - TIMELINE_HEIGHT);
+            guiEvents.SetSize(height, EVENTS_HEIGHT);
+            guiCurveDrawing.SetSize(width, height - TIMELINE_HEIGHT - EVENTS_HEIGHT);
+            guiSidebar.SetSize(SIDEBAR_WIDTH, height - TIMELINE_HEIGHT - EVENTS_HEIGHT);
 
             Redraw();
         }
@@ -461,6 +503,7 @@ namespace BansheeEditor
         public void SetFPS(int fps)
         {
             guiTimeline.SetFPS(fps);
+            guiEvents.SetFPS(fps);
             guiCurveDrawing.SetFPS(fps);
 
             Redraw();
@@ -512,6 +555,7 @@ namespace BansheeEditor
         {
             guiCurveDrawing.Rebuild();
             guiTimeline.Rebuild();
+            guiEvents.Rebuild();
             guiSidebar.Rebuild();
         }
         

+ 6 - 6
Source/MBansheeEditor/Windows/Animation/GUIGraphTime.cs

@@ -96,7 +96,7 @@ namespace BansheeEditor
             tickHeight = (int)(height * TICK_HEIGHT_PCT);
             drawableWidth = Math.Max(0, width - PADDING * 2);
 
-            tickHandler.SetRange(rangeOffset, GetRange(true), drawableWidth + PADDING);
+            tickHandler.SetRange(rangeOffset, rangeOffset + GetRange(true), drawableWidth + PADDING);
         }
 
         /// <summary>
@@ -107,7 +107,7 @@ namespace BansheeEditor
         {
             rangeLength = Math.Max(0.0f, length);
 
-            tickHandler.SetRange(rangeOffset, GetRange(true), drawableWidth + PADDING);
+            tickHandler.SetRange(rangeOffset, rangeOffset + GetRange(true), drawableWidth + PADDING);
         }
 
         /// <summary>
@@ -118,7 +118,7 @@ namespace BansheeEditor
         {
             rangeOffset = offset;
 
-            tickHandler.SetRange(rangeOffset, GetRange(true), drawableWidth + PADDING);
+            tickHandler.SetRange(rangeOffset, rangeOffset + GetRange(true), drawableWidth + PADDING);
         }
 
         /// <summary>
@@ -129,7 +129,7 @@ namespace BansheeEditor
         {
             this.fps = Math.Max(1, fps);
 
-            tickHandler.SetRange(rangeOffset, GetRange(true), drawableWidth + PADDING);
+            tickHandler.SetRange(rangeOffset, rangeOffset + GetRange(true), drawableWidth + PADDING);
         }
         
         /// <summary>
@@ -206,12 +206,12 @@ namespace BansheeEditor
         /// <returns>Time range rounded to a multiple of FPS.</returns>
         private float GetRange(bool padding = false)
         {
-            float spf = 1.0f/fps;
+            float spf = 1.0f / fps;
 
             float range = rangeLength;
             if (padding)
             {
-                float lengthPerPixel = rangeLength/drawableWidth;
+                float lengthPerPixel = rangeLength / drawableWidth;
                 range += lengthPerPixel * PADDING;
             }
 

+ 39 - 28
Source/MBansheeEditor/Windows/AnimationWindow.cs

@@ -19,7 +19,7 @@ namespace BansheeEditor
         private const int FIELD_DISPLAY_WIDTH = 200;
         private const int DRAG_START_DISTANCE = 3;
         private const float DRAG_SCALE = 10.0f;
-        private const float ZOOM_SCALE = 15.0f;
+        private const float ZOOM_SCALE = 0.1f/120.0f; // One scroll step is usually 120 units, we want 1/10 of that
 
         private bool isInitialized;
         private SceneObject selectedSO;
@@ -114,7 +114,12 @@ namespace BansheeEditor
             curves.Clear();
             isInitialized = false;
 
-            selectedSO = Selection.SceneObject;
+            if (selectedSO != Selection.SceneObject)
+            {
+                zoomAmount = 0.0f;
+                selectedSO = Selection.SceneObject;
+            }
+
             if (selectedSO == null)
             {
                 GUILabel warningLbl = new GUILabel(new LocEdString("Select an object to animate in the Hierarchy or Scene windows."));
@@ -361,7 +366,7 @@ namespace BansheeEditor
             float scrollableRange = totalRange.y - visibleRange.y;
 
             Vector2 offset = guiCurveEditor.Offset;
-            offset.y = scrollableRange * (position * 2.0f - 1.0f);
+            offset.y = -scrollableRange * (position * 2.0f - 1.0f);
 
             guiCurveEditor.Offset = offset;
         }
@@ -398,12 +403,22 @@ namespace BansheeEditor
             Vector2 scrollableRange = totalRange - visibleRange;
 
             Vector2 offset = guiCurveEditor.Offset;
-            // Transform Y from [-x, +x] range to [0, x]
-            offset.y += visibleRange.y;
-            offset.y /= 2.0f;
+            if (scrollableRange.x > 0.0f)
+                horzScrollBar.Position = offset.x / scrollableRange.x;
+            else
+                horzScrollBar.Position = 0.0f;
 
-            horzScrollBar.Position = offset.x / scrollableRange.x;
-            vertScrollBar.Position = offset.y / scrollableRange.y;
+            if (scrollableRange.y > 0.0f)
+            {
+                float pos = offset.y/scrollableRange.y;
+                float sign = MathEx.Sign(pos);
+                pos = sign*MathEx.Clamp01(MathEx.Abs(pos));
+                pos = (1.0f - pos) /2.0f;
+
+                vertScrollBar.Position = pos;
+            }
+            else
+                vertScrollBar.Position = 0.0f;
         }
 
         private Vector2 GetZoomedRange()
@@ -416,41 +431,37 @@ namespace BansheeEditor
 
         private Vector2 GetTotalRange()
         {
-            Vector2 visibleRange = guiCurveEditor.Range;
-            Vector2 totalRange = guiCurveEditor.Offset;
-            totalRange.x += visibleRange.x;
-            totalRange.y = Math.Abs(totalRange.y) + visibleRange.y;
-
+            // Return optimal range (that covers the visible curve)
             Vector2 optimalRange = GetOptimalRange();
-            return Vector2.Max(totalRange, optimalRange);
+
+            // Increase range in case user zoomed out
+            Vector2 zoomedRange = GetZoomedRange();
+            return Vector2.Max(optimalRange, zoomedRange);
         }
 
         private void Zoom(Vector2 curvePos, float amount)
         {
+            // Increase or decrease the visible range depending on zoom level
             Vector2 oldZoomedRange = GetZoomedRange();
-            zoomAmount += amount;
+            zoomAmount = MathEx.Clamp(zoomAmount + amount, -10.0f, 10.0f);
             Vector2 zoomedRange = GetZoomedRange();
 
             Vector2 zoomedDiff = zoomedRange - oldZoomedRange;
-            zoomedDiff.y *= 0.5f;
 
             Vector2 currentRange = guiCurveEditor.Range;
             Vector2 newRange = currentRange + zoomedDiff;
+            guiCurveEditor.Range = newRange;
 
-            Vector2 offset = guiCurveEditor.Offset;
-            Vector2 relativePos = curvePos - offset;
-
-            relativePos.x /= currentRange.x;
-            relativePos.y /= currentRange.y;
-
-            relativePos.x = relativePos.x * 2.0f - 1.0f;
-            relativePos.y = relativePos.y * 2.0f - 1.0f;
+            // When zooming, make sure to focus on the point provided, so adjust the offset
+            Vector2 rangeScale = newRange;
+            rangeScale.x /= currentRange.x;
+            rangeScale.y /= currentRange.y;
 
-            offset.x += relativePos.x * zoomedDiff.x;
-            offset.y += relativePos.y * zoomedDiff.y * 2.0f;
+            Vector2 relativeCurvePos = curvePos - guiCurveEditor.Offset;
+            Vector2 newCurvePos = relativeCurvePos * rangeScale;
+            Vector2 diff = newCurvePos - relativeCurvePos;
 
-            guiCurveEditor.Offset = offset;
-            guiCurveEditor.Range = newRange;
+            guiCurveEditor.Offset -= diff;
 
             UpdateScrollBarSize();
             UpdateScrollBarPosition();