Ver código fonte

Merge remote-tracking branch 'origin/3.6-beta' into 3.6-beta

NathanSweet 8 anos atrás
pai
commit
5ae5a2af76
100 arquivos alterados com 12391 adições e 1002 exclusões
  1. 33 11
      CHANGELOG.md
  2. BIN
      spine-as3/spine-as3-example/lib/spine-as3.swc
  3. 7 3
      spine-as3/spine-as3/src/spine/animation/AnimationState.as
  4. 1 0
      spine-c/spine-c-unit-tests/main.cpp
  5. 11 12
      spine-c/spine-c/include/spine/AnimationState.h
  6. 0 1
      spine-c/spine-c/include/spine/Array.h
  7. 1 0
      spine-c/spine-c/include/spine/extension.h
  8. 149 155
      spine-c/spine-c/src/spine/AnimationState.c
  9. 7 1
      spine-c/spine-c/src/spine/extension.c
  10. 8 4
      spine-csharp/src/AnimationState.cs
  11. 128 158
      spine-lua/AnimationState.lua
  12. 0 1
      spine-sfml/example/main.cpp
  13. BIN
      spine-starling/spine-starling-example/lib/spine-as3.swc
  14. BIN
      spine-starling/spine-starling/lib/spine-as3.swc
  15. 6 3
      spine-ts/build/spine-all.js
  16. 0 0
      spine-ts/build/spine-all.js.map
  17. 6 3
      spine-ts/build/spine-canvas.js
  18. 0 0
      spine-ts/build/spine-canvas.js.map
  19. 6 3
      spine-ts/build/spine-core.js
  20. 0 0
      spine-ts/build/spine-core.js.map
  21. 6 3
      spine-ts/build/spine-threejs.js
  22. 0 0
      spine-ts/build/spine-threejs.js.map
  23. 6 3
      spine-ts/build/spine-webgl.js
  24. 0 0
      spine-ts/build/spine-webgl.js.map
  25. 6 3
      spine-ts/build/spine-widget.js
  26. 0 0
      spine-ts/build/spine-widget.js.map
  27. 7 3
      spine-ts/core/src/AnimationState.ts
  28. BIN
      spine-ue4/Content/Test/Assets/TestMaterial.uasset
  29. BIN
      spine-ue4/Content/Test/Assets/Textures/TwoColorTest.uasset
  30. 13 0
      spine-ue4/Content/Test/Assets/TwoColorTest.atlas
  31. 150 0
      spine-ue4/Content/Test/Assets/TwoColorTest.json
  32. BIN
      spine-ue4/Content/Test/Assets/TwoColorTest.png
  33. BIN
      spine-ue4/Content/Test/Assets/TwoColorTest.uasset
  34. BIN
      spine-ue4/Content/Test/Blueprints/TwoColor_Blueprint.uasset
  35. BIN
      spine-ue4/Content/Test/Test.umap
  36. 58 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/.gitignore
  37. 21 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/LICENSE
  38. 50 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/README.md
  39. BIN
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Resources/Icon128.png
  40. 29 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/RuntimeMeshComponent.uplugin
  41. 2134 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Private/RuntimeMeshComponent.cpp
  42. 38 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Private/RuntimeMeshComponentPlugin.cpp
  43. 14 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Private/RuntimeMeshComponentPluginPrivatePCH.h
  44. 51 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Private/RuntimeMeshCore.cpp
  45. 32 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Private/RuntimeMeshGenericVertex.cpp
  46. 561 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Private/RuntimeMeshLibrary.cpp
  47. 156 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Private/TessellationUtilities.cpp
  48. 178 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Private/TessellationUtilities.h
  49. 1215 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshAsync.h
  50. 983 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshBuilder.h
  51. 1308 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshComponent.h
  52. 38 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshComponentPlugin.h
  53. 422 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshCore.h
  54. 1273 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshGenericVertex.h
  55. 129 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshLibrary.h
  56. 74 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshProfiling.h
  57. 187 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshRendering.h
  58. 812 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshSection.h
  59. 228 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshSectionProxy.h
  60. 257 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshUpdateCommands.h
  61. 27 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshVersion.h
  62. 24 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/RuntimeMeshComponent.Build.cs
  63. 265 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponentEditor/Private/RuntimeMeshComponentDetails.cpp
  64. 27 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponentEditor/Private/RuntimeMeshComponentDetails.h
  65. 31 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponentEditor/Private/RuntimeMeshComponentEditorPlugin.cpp
  66. 15 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponentEditor/Private/RuntimeMeshComponentEditorPrivatePCH.h
  67. 36 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponentEditor/Public/IRuntimeMeshComponentEditorPlugin.h
  68. 33 0
      spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponentEditor/RuntimeMeshComponentEditor.Build.cs
  69. BIN
      spine-ue4/Plugins/SpinePlugin/Content/TestMaterial.uasset
  70. 16 7
      spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonRendererComponent.cpp
  71. 2 2
      spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonAnimationComponent.h
  72. 3 3
      spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonRendererComponent.h
  73. 1 1
      spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/SpinePlugin.Build.cs
  74. 0 12
      spine-unity/Assets/Examples/Getting Started/1 The Spine GameObject.unity
  75. 101 49
      spine-unity/Assets/Examples/Getting Started/2 Controlling Animation.unity
  76. 2 2
      spine-unity/Assets/Examples/Getting Started/4 Object Oriented Sample.unity
  77. 2 2
      spine-unity/Assets/Examples/Getting Started/5 Basic Platformer.unity
  78. 2 2
      spine-unity/Assets/Examples/Getting Started/6 SkeletonGraphic.unity
  79. 0 14
      spine-unity/Assets/Examples/Other Examples/AtlasRegionAttacher.unity
  80. 114 0
      spine-unity/Assets/Examples/Other Examples/Dragon.unity
  81. 168 91
      spine-unity/Assets/Examples/Other Examples/Goblins.unity
  82. 61 16
      spine-unity/Assets/Examples/Other Examples/Mix and Match.unity
  83. 14 16
      spine-unity/Assets/Examples/Other Examples/SkeletonRenderSeparator.unity
  84. 352 205
      spine-unity/Assets/Examples/Other Examples/SkeletonUtility Eyes.unity
  85. 103 68
      spine-unity/Assets/Examples/Other Examples/SpineGauge.unity
  86. 59 41
      spine-unity/Assets/Examples/Scripts/MixAndMatch.cs
  87. 12 2
      spine-unity/Assets/Examples/Scripts/Sample Components/BoneLocalOverride.cs
  88. 33 0
      spine-unity/Assets/Examples/Scripts/Sample Components/CombinedSkin.cs
  89. 12 0
      spine-unity/Assets/Examples/Scripts/Sample Components/CombinedSkin.cs.meta
  90. 2 2
      spine-unity/Assets/Examples/Scripts/Sample Components/Legacy.meta
  91. 19 57
      spine-unity/Assets/Examples/Scripts/Sample Components/Legacy/AtlasRegionAttacher.cs
  92. 0 0
      spine-unity/Assets/Examples/Scripts/Sample Components/Legacy/AtlasRegionAttacher.cs.meta
  93. 0 0
      spine-unity/Assets/Examples/Scripts/Sample Components/Legacy/CustomSkin.cs
  94. 0 0
      spine-unity/Assets/Examples/Scripts/Sample Components/Legacy/CustomSkin.cs.meta
  95. 0 0
      spine-unity/Assets/Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs
  96. 0 0
      spine-unity/Assets/Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs.meta
  97. 7 4
      spine-unity/Assets/Examples/Scripts/Sample Components/SkeletonColorInitialize.cs
  98. 1 2
      spine-unity/Assets/Examples/Scripts/Sample Components/SlotTintBlackFollower.cs
  99. 36 37
      spine-unity/Assets/Examples/Scripts/Sample Components/SpineEventUnityHandler.cs
  100. 12 0
      spine-unity/Assets/Examples/Scripts/Sample Components/SpineEventUnityHandler.cs.meta

+ 33 - 11
CHANGELOG.md

@@ -65,22 +65,44 @@
  * Fixed renderer to work with 3.6 changes
  * Added new UPROPERTY to SpineSkeletonRendererComponent called `Color`. This allows to set the tint color of the skeleton in the editor, C++ and Blueprints. Under the hood, the `spSkeleton->color` will be set on every tick of the renderer component.
  * Added support for clipping.
+ * Switched from built-in ProceduralMeshComponent to RuntimeMeshComponent by Koderz (https://github.com/Koderz/UE4RuntimeMeshComponent, MIT). Needed for more flexibility regarding vertex format, should not have an impact on existing code/assets.
 
 ## C#
  * **Breaking changes**
-  *  `MeshAttachment.parentMesh` is now a private field to enforce using the `.ParentMesh` setter property in external code. The `MeshAttachment.ParentMesh` property is an appropriate replacement wherever `.parentMesh` was used.
+   *  `MeshAttachment.parentMesh` is now a private field to enforce using the `.ParentMesh` setter property in external code. The `MeshAttachment.ParentMesh` property is an appropriate replacement wherever `.parentMesh` was used.
 
 ### Unity
- * Fixed renderer to work with 3.6 changes.
- * Two color tinting is currently supported via extra UV2 and UV3 mesh vertex streams. To use Two color tinting, you need to:
-  * switch on "Tint Black" under "Advanced...",
-  * use the new `Spine/Skeleton Tint Black` shader, or your own shader that treats the UV2 and UV3 streams similarly.
- * `SkeletonAnimator` now has autoreset set to true by default. Old prefabs and scene values will have been serialized to whatever value it was previously. This change only applies to new instances of SkeletonAnimator.
- * Old triangle-winding code has been removed from `SkeletonRenderer`. Please use shaders that have backface culling off.
- * The code in the example scripts have been switched over to using properties instead of fields. This is in anticipation of both users who want to move the Spine folders to the Unity Plugins folder (compiled as a different assembly), and of Unity 2017's ability to manually define different assemblies.
- * Warnings and conditionals checking for specific Unity 5.2-and-below incompatibility have been removed.
- * `AtasRegionAttacher` and `SpriteAttacher` are now part of `Example Modules`, to reflect that they are meant to be used as sample code rather than production.
- * In the unitypackage, the "spine-csharp" and "spine-unity" folders are now inside a "Spine" folder. This change will only affect fresh imports. Importing the unitypackage to update Spine-Unity in your project will update the appropriate files wherever you have moved them.
+ * Refactored renderer to work with new 3.6 features.
+   * **Two color tinting** is currently supported via extra UV2 and UV3 mesh vertex streams. To use Two color tinting, you need to:
+     * switch on "Tint Black" under "Advanced...",
+     * use the new `Spine/Skeleton Tint Black` shader, or your own shader that treats the UV2 and UV3 streams similarly.
+   * **Clipping** is now supported. The SkeletonAnimation switches to slightly slower mesh generation code when clipping so limit your use of `ClippingAttachment`s when using on large numbers of skeletons.
+ * **[SpineAttribute] Improvements** 
+   * **Icons have been added to SpineAttributeDrawers**. This should make your default inspectors easier to understand at a glance.
+   * **Added Constraint Attributes** You can now use `[SpineIkConstraint]` `[SpineTransformConstraint]` `[SpinePathConstraint]`
+   * **SpineAttribute dataField** parameter can also now detect sibling fields within arrays and serializable structs/classes.
+   * **[SpineAttribute(includeNone:false)]** SpineAttributes now have an `includeNone` optional parameter to specify if you want to include or exclude a none ("") value option in the dropdown menu. Default is `includeNone:true`.
+   * **[SpineAttachment(skinField:"mySkin")]** The SpineAttachment attribute now has a skinField optional parameter to limit the dropdown items to attachments in a specific skin instead of the just default skin or all the skins in SkeletonData.
+ * **SkeletonDebugWindow**. Debugging tools have been moved from the SkeletonAnimation and SkeletonUtility component inspectors into its own utility window. You can access "Skeleton Debug" under the `Advanced...` foldout in the SkeletonAnimation inspector, or in SkeletonAnimation's right-click/context menu.
+   * **Skeleton Baking Window** The old Skeleton Baking feature is also now accessible through the SkeletonDataAsset's right-click/context menu.
+ * **AttachmentTools source material**. `AttachmentTools` methods can now accept a `sourceMaterial` argument to copy material properties from.
+ * **AttachmentTools Skin Extensions**. Using AttachmentTools, you can now add entries by slot name by also providing a skeleton argument. Also `Append(Skin)`, `RemoveAttachment` and `Clear` have been added.
+ * **BoneFollower and SkeletonUtilityBone Add RigidBody Button**. The BoneFollower and SkeletonUtilityBone component inspectors will now offer to add a `Rigidbody` or `Rigidbody2D` if it detects a collider of the appropriate type. Having a rigidbody on a moving transform with a collider fits better with the Unity physics systems and prevents excess calculations. It will not detect colliders on child objects so you have to add Rigidbody components manually accordingly.
+ * **Examples**
+   * **Examples now use properties**. The code in the example scripts have been switched over to using properties instead of fields to encourage their use for consistency. This is in anticipation of both users who want to move the Spine folders to the Unity Plugins folder (compiled as a different assembly), and of Unity 2017's ability to manually define different assemblies for shorter compilation times.
+   * **Mix And Match**. The mix-and-match example scene, code and data have been updated to reflect the current recommended setup for animation-compatible custom equip systems The underlying API has changed since 3.5 and the new API calls in MixAndMatch.cs is recommended. Documentation is in progress.  
+   * **Sample Components**. `AtasRegionAttacher` and `SpriteAttacher` are now part of `Sample Components`, to reflect that they are meant to be used as sample code rather than production. A few other sample components have also been added. New imports of the unitypackage Examples folder will see a "Legacy" folder comprised of old sample components that no longer contain the most up-to-date and recommended workflows, but are kept in case old setups used them for production.
+ * **Spine folder**. In the unitypackage, the "spine-csharp" and "spine-unity" folders are now inside a "Spine" folder. This change will only affect fresh imports. Importing the unitypackage to update Spine-Unity in your existing project will update the appropriate files however you chose to arrange them, as long as the meta files are intact.
+ * **Breaking changes**
+   * The Sprite shaders module was updated to the latest version from the [source](https://github.com/traggett/UnitySpriteShaders/commits/master). Some changes were made to the underlying keyword structure. You may need to review the settings of your lit materials. Particularly, your Fixed Normals settings.
+   * The old MeshGenerator classes, interfaces and code in `Spine.Unity.MeshGeneration` are now deprecated. All mesh-generating components now share the class `Spine.Unity.MeshGenerator` defined in `SpineMesh.cs`. MeshGenerator is a serializable class.
+     * The `SkeletonRenderer.renderMeshes` optimization is currently non-functional.
+     * Old triangle-winding code has been removed from `SkeletonRenderer`. Please use shaders that have backface culling off.
+     * Render settings in `SkeletonGraphic` can now be accessed under `SkeletonGraphic.MeshGenerator.settings`. This is visible in the SkeletonGraphic inspector as `Advanced...`
+     * We will continue to bundle the unitypackage with the empty .cs files of deprecated classes until Spine 3.7 to ensure the upgrade process does not break.
+   * The [SpineAttachment(slotField:)] optional parameter found property value now acts as a Find(slotName) argument rather than Contains(slotName).
+   * `SkeletonAnimator` now has autoreset set to true by default. Old prefabs and scene values will have been serialized to whatever value it was previously. This change only applies to new instances of SkeletonAnimator.
+   * Warnings and conditionals checking for specific Unity 5.2-and-below incompatibility have been removed. 
 
 ## Java
  * **Breaking changes**

BIN
spine-as3/spine-as3-example/lib/spine-as3.swc


+ 7 - 3
spine-as3/spine-as3/src/spine/animation/AnimationState.as

@@ -131,9 +131,13 @@ package spine.animation {
 
 			// Require mixTime > 0 to ensure the mixing from entry was applied at least once.
 			if (entry.mixTime > 0 && (entry.mixTime >= entry.mixDuration || entry.timeScale == 0)) {
-				if (animationCount > 6 && from.mixingFrom == null) { // Limit the mixing from linked list.
-					entry.mixingFrom = null;
-					queue.end(from);
+				if (animationCount > 5 && from.mixingFrom == null) {
+					// Limit linked list by speeding up and removing old entries.
+					entry.interruptAlpha = Math.max(0, entry.interruptAlpha - delta * 0.66);
+					if (entry.interruptAlpha <= 0) {
+						entry.mixingFrom = null;
+						queue.end(from);
+					}
 				}
 				return finished;
 			}

+ 1 - 0
spine-c/spine-c-unit-tests/main.cpp

@@ -25,6 +25,7 @@ void RegisterMemoryLeakDetector()
 	_setDebugMalloc(_kanjimalloc);
 	#endif
 	_setMalloc(_kanjimalloc);
+	_setRealloc(_kanjirealloc);
 	_setFree(_kanjifree);
 }
 

+ 11 - 12
spine-c/spine-c/include/spine/AnimationState.h

@@ -34,6 +34,7 @@
 #include <spine/Animation.h>
 #include <spine/AnimationStateData.h>
 #include <spine/Event.h>
+#include <spine/Array.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -48,6 +49,8 @@ typedef struct spTrackEntry spTrackEntry;
 
 typedef void (*spAnimationStateListener) (spAnimationState* state, spEventType type, spTrackEntry* entry, spEvent* event);
 
+_SP_ARRAY_DECLARE_TYPE(spTrackEntryArray, spTrackEntry*)
+
 struct spTrackEntry {
 	spAnimation* animation;
 	spTrackEntry* next;
@@ -58,11 +61,9 @@ struct spTrackEntry {
 	float eventThreshold, attachmentThreshold, drawOrderThreshold;
 	float animationStart, animationEnd, animationLast, nextAnimationLast;
 	float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale;
-	float alpha, mixTime, mixDuration, mixAlpha;
-	int* /*boolean*/ timelinesFirst;
-	int timelinesFirstCount;
-	int* /*boolean*/ timelinesLast;
-	int timelinesLastCount;
+	float alpha, mixTime, mixDuration, interruptAlpha;
+	spIntArray* timelineData;
+	spTrackEntryArray* timelineDipMix;
 	float* timelinesRotation;
 	int timelinesRotationCount;
 	void* rendererObject;
@@ -78,11 +79,9 @@ struct spTrackEntry {
 		eventThreshold(0), attachmentThreshold(0), drawOrderThreshold(0),
 		animationStart(0), animationEnd(0), animationLast(0), nextAnimationLast(0),
 		delay(0), trackTime(0), trackLast(0), nextTrackLast(0), trackEnd(0), timeScale(0),
-		alpha(0), mixTime(0), mixDuration(0), mixAlpha(0),
-		timelinesFirst(0),
-		timelinesFirstCount(0),
-		timelinesLast(0),
-		timelinesLastCount(0),
+		alpha(0), mixTime(0), mixDuration(0), interruptAlpha(0),
+		timelineData(0),
+		timelineDipMix(0),
 		timelinesRotation(0),
 		timelinesRotationCount(0) {
 	}
@@ -99,7 +98,7 @@ struct spAnimationState {
 
 	float timeScale;
 
-	int /*boolean*/ multipleMixing;
+	spTrackEntryArray* mixingTo;
 
 	void* rendererObject;
 
@@ -110,7 +109,7 @@ struct spAnimationState {
 		tracks(0),
 		listener(0),
 		timeScale(0),
-		multipleMixing(0),
+		mixingTo(0),
 		rendererObject(0) {
 	}
 #endif

+ 0 - 1
spine-c/spine-c/include/spine/Array.h

@@ -126,7 +126,6 @@ _SP_ARRAY_DECLARE_TYPE(spUnsignedShortArray, unsigned short)
 _SP_ARRAY_DECLARE_TYPE(spArrayFloatArray, spFloatArray*)
 _SP_ARRAY_DECLARE_TYPE(spArrayShortArray, spShortArray*)
 
-
 #ifdef __cplusplus
 }
 #endif

+ 1 - 0
spine-c/spine-c/include/spine/extension.h

@@ -169,6 +169,7 @@ void _free (void* ptr);
 
 void _setMalloc (void* (*_malloc) (size_t size));
 void _setDebugMalloc (void* (*_malloc) (size_t size, const char* file, int line));
+void _setRealloc(void* (*_realloc) (void* ptr, size_t size));
 void _setFree (void (*_free) (void* ptr));
 
 char* _readFile (const char* path, int* length);

+ 149 - 155
spine-c/spine-c/src/spine/AnimationState.c

@@ -32,6 +32,12 @@
 #include <spine/extension.h>
 #include <limits.h>
 
+#define SUBSEQUENT 0
+#define FIRST 1
+#define DIP 2
+
+_SP_ARRAY_IMPLEMENT_TYPE(spTrackEntryArray, spTrackEntry*)
+
 static spAnimation* SP_EMPTY_ANIMATION = 0;
 void spAnimationState_disposeStatics () {
 	if (SP_EMPTY_ANIMATION) spAnimation_dispose(SP_EMPTY_ANIMATION);
@@ -42,7 +48,7 @@ void spAnimationState_disposeStatics () {
    the same function order in C as we have method order in Java */
 void _spAnimationState_disposeTrackEntry (spTrackEntry* entry);
 void _spAnimationState_disposeTrackEntries (spAnimationState* state, spTrackEntry* entry);
-void _spAnimationState_updateMixingFrom (spAnimationState* self, spTrackEntry* entry, float delta);
+int /*boolean*/ _spAnimationState_updateMixingFrom (spAnimationState* self, spTrackEntry* entry, float delta, int animationCount);
 float _spAnimationState_applyMixingFrom (spAnimationState* self, spTrackEntry* entry, spSkeleton* skeleton);
 void _spAnimationState_applyRotateTimeline (spAnimationState* self, spTimeline* timeline, spSkeleton* skeleton, float time, float alpha, int /*boolean*/ setupPose, float* timelinesRotation, int i, int /*boolean*/ firstFrame);
 void _spAnimationState_queueEvents (spAnimationState* self, spTrackEntry* entry, float animationTime);
@@ -55,9 +61,8 @@ float* _spAnimationState_resizeTimelinesRotation(spTrackEntry* entry, int newSiz
 int* _spAnimationState_resizeTimelinesFirst(spTrackEntry* entry, int newSize);
 void _spAnimationState_ensureCapacityPropertyIDs(spAnimationState* self, int capacity);
 int _spAnimationState_addPropertyID(spAnimationState* self, int id);
-void _spAnimationState_setTimelinesFirst (spAnimationState* self, spTrackEntry* entry);
-void _spAnimationState_checkTimelinesFirst (spAnimationState* self, spTrackEntry* entry);
-void _spAnimationState_checkTimelinesUsage (spAnimationState* self, spTrackEntry* entry, int /*boolean*/ useTimelinesFirst);
+spTrackEntry* _spTrackEntry_setTimelineData(spTrackEntry* self, spTrackEntry* to, spTrackEntryArray* mixingToArray, spAnimationState* state);
+
 
 _spEventQueue* _spEventQueue_create (_spAnimationState* state) {
 	_spEventQueue *self = CALLOC(_spEventQueue, 1);
@@ -175,8 +180,8 @@ void _spEventQueue_drain (_spEventQueue* self) {
 }
 
 void _spAnimationState_disposeTrackEntry (spTrackEntry* entry) {
-	FREE(entry->timelinesFirst);
-	FREE(entry->timelinesLast);
+	spIntArray_dispose(entry->timelineData);
+	spTrackEntryArray_dispose(entry->timelineDipMix);
 	FREE(entry->timelinesRotation);
 	FREE(entry);
 }
@@ -216,6 +221,8 @@ spAnimationState* spAnimationState_create (spAnimationStateData* data) {
 	internal->propertyIDs = CALLOC(int, 128);
 	internal->propertyIDsCapacity = 128;
 
+	self->mixingTo = spTrackEntryArray_create(16);
+
 	return self;
 }
 
@@ -229,6 +236,7 @@ void spAnimationState_dispose (spAnimationState* self) {
 	FREE(internal->events);
 	FREE(internal->propertyIDs);
     FREE(internal);
+	spTrackEntryArray_dispose(self->mixingTo);
 }
 
 void spAnimationState_update (spAnimationState* self, float delta) {
@@ -277,7 +285,15 @@ void spAnimationState_update (spAnimationState* self, float delta) {
 				continue;
 			}
 		}
-        _spAnimationState_updateMixingFrom(self, current, delta);
+		if (current->mixingFrom != 0 && _spAnimationState_updateMixingFrom(self, current, delta, 2)) {
+			/* End mixing from entries once all have completed. */
+			spTrackEntry* from = current->mixingFrom;
+			current->mixingFrom = 0;
+			while (from != 0) {
+				_spEventQueue_end(internal->queue, from);
+				from = from->mixingFrom;
+			}
+		}
 
 		current->trackTime += currentDelta;
 	}
@@ -285,23 +301,32 @@ void spAnimationState_update (spAnimationState* self, float delta) {
 	_spEventQueue_drain(internal->queue);
 }
 
-void _spAnimationState_updateMixingFrom (spAnimationState* self, spTrackEntry* entry, float delta) {
-	spTrackEntry* from = entry->mixingFrom;		
+int /*boolean*/ _spAnimationState_updateMixingFrom (spAnimationState* self, spTrackEntry* entry, float delta, int animationCount) {
+	spTrackEntry* from = entry->mixingFrom;
+	int finished;
 	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
-	if (!from) return;
-    
-    _spAnimationState_updateMixingFrom(self, from, delta);
-
-	if (entry->mixTime >= entry->mixDuration && from->mixingFrom == 0 && entry->mixTime > 0) {
-        entry->mixingFrom = 0;
-		_spEventQueue_end(internal->queue, from);
-        return;
+	if (!from) return -1;
+
+	finished = _spAnimationState_updateMixingFrom(self, from, delta, animationCount + 1);
+
+	/* Require mixTime > 0 to ensure the mixing from entry was applied at least once. */
+	if (entry->mixTime > 0 && (entry->mixTime >= entry->mixDuration || entry->timeScale == 0)) {
+		if (animationCount > 5 && from->mixingFrom == 0) {
+			/* Limit linked list by speeding up and removing old entries. */
+			entry->interruptAlpha = MAX(0, entry->interruptAlpha - delta * 0.66f);
+			if (entry->interruptAlpha <= 0) {
+				entry->mixingFrom = 0;
+				_spEventQueue_end(internal->queue, from);
+			}
+		}
+		return finished;
 	}
 
 	from->animationLast = from->nextAnimationLast;
 	from->trackLast = from->nextTrackLast;
 	from->trackTime += delta * from->timeScale;
 	entry->mixTime += delta * entry->timeScale;
+	return 0;
 }
 
 void spAnimationState_apply (spAnimationState* self, spSkeleton* skeleton) {
@@ -313,7 +338,6 @@ void spAnimationState_apply (spAnimationState* self, spSkeleton* skeleton) {
 	spTimeline** timelines;
 	int /*boolean*/ firstFrame;
 	float* timelinesRotation;
-	int* timelinesFirst;
 	spTimeline* timeline;
 
 	if (internal->animationsChanged) _spAnimationState_animationsChanged(self);
@@ -338,17 +362,18 @@ void spAnimationState_apply (spAnimationState* self, spSkeleton* skeleton) {
 			for (ii = 0; ii < timelineCount; ii++)
 				spTimeline_apply(timelines[ii], skeleton, animationLast, animationTime, internal->events, &internal->eventsCount, 1, 1, 0);
 		} else {
+			spIntArray* timelineData = current->timelineData;
+
 			firstFrame = current->timelinesRotationCount == 0;
 			if (firstFrame) _spAnimationState_resizeTimelinesRotation(current, timelineCount << 1);
 			timelinesRotation = current->timelinesRotation;
 
-			timelinesFirst = current->timelinesFirst;
 			for (ii = 0; ii < timelineCount; ii++) {
 				timeline = timelines[ii];
 				if (timeline->type == SP_TIMELINE_ROTATE)
-					_spAnimationState_applyRotateTimeline(self, timeline, skeleton, animationTime, mix, timelinesFirst[ii], timelinesRotation, ii << 1, firstFrame);
+					_spAnimationState_applyRotateTimeline(self, timeline, skeleton, animationTime, mix, timelineData->items[ii] > 0, timelinesRotation, ii << 1, firstFrame);
 				else
-					spTimeline_apply(timeline, skeleton, animationLast, animationTime, internal->events, &internal->eventsCount, mix, timelinesFirst[ii], 0);
+					spTimeline_apply(timeline, skeleton, animationLast, animationTime, internal->events, &internal->eventsCount, mix, timelineData->items[ii] > 0, 0);
 			}
 		}
 		_spAnimationState_queueEvents(self, current, animationTime);
@@ -360,7 +385,7 @@ void spAnimationState_apply (spAnimationState* self, spSkeleton* skeleton) {
 	_spEventQueue_drain(internal->queue);
 }
 
-float _spAnimationState_applyMixingFrom (spAnimationState* self, spTrackEntry* entry, spSkeleton* skeleton) {
+float _spAnimationState_applyMixingFrom (spAnimationState* self, spTrackEntry* to, spSkeleton* skeleton) {
 	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
 	float mix;
 	spEvent** events;
@@ -370,24 +395,24 @@ float _spAnimationState_applyMixingFrom (spAnimationState* self, spTrackEntry* e
 	float animationTime;
 	int timelineCount;
 	spTimeline** timelines;
-	int* timelinesFirst;
-	int* timelinesLast;
-	float alphaBase;
+	spIntArray* timelineData;
+	spTrackEntryArray* timelineDipMix;
+	float alphaDip;
 	float alphaMix;
 	float alpha;
 	int /*boolean*/ firstFrame;
 	float* timelinesRotation;
-	spTimeline* timeline;
-	int /*boolean*/ setupPose;
+	int /*boolean*/ first;
 	int i;
+	spTrackEntry* dipMix;
 
-	spTrackEntry* from = entry->mixingFrom;
+	spTrackEntry* from = to->mixingFrom;
 	if (from->mixingFrom) _spAnimationState_applyMixingFrom(self, from, skeleton);
 
-	if (entry->mixDuration == 0) /* Single frame mix to undo mixingFrom changes. */
+	if (to->mixDuration == 0) /* Single frame mix to undo mixingFrom changes. */
 		mix = 1;
 	else {
-		mix = entry->mixTime / entry->mixDuration;
+		mix = to->mixTime / to->mixDuration;
 		if (mix > 1) mix = 1;
 	}
 
@@ -398,31 +423,46 @@ float _spAnimationState_applyMixingFrom (spAnimationState* self, spTrackEntry* e
 	animationTime = spTrackEntry_getAnimationTime(from);
 	timelineCount = from->animation->timelinesCount;
 	timelines = from->animation->timelines;
-	timelinesFirst = from->timelinesFirst;
-	timelinesLast = self->multipleMixing ? 0 : from->timelinesLast;
-	alphaBase = from->alpha * entry->mixAlpha;
-	alphaMix = alphaBase * (1 - mix);
+	timelineData = from->timelineData;
+	timelineDipMix = from->timelineDipMix;
 
 	firstFrame = from->timelinesRotationCount == 0;
 	if (firstFrame) _spAnimationState_resizeTimelinesRotation(from, timelineCount << 1);
 	timelinesRotation = from->timelinesRotation;
 
+	first = 0;
+	alphaDip = from->alpha * to->interruptAlpha; alphaMix = alphaDip * (1 - mix);
 	for (i = 0; i < timelineCount; i++) {
-		timeline = timelines[i];
-		setupPose = timelinesFirst[i];
-		alpha = timelinesLast != 0 && setupPose && !timelinesLast[i] ? alphaBase : alphaMix;
+		spTimeline* timeline = timelines[i];
+		switch (timelineData->items[i]) {
+			case SUBSEQUENT:
+				first = 0;
+				alpha = alphaMix;
+				break;
+			case FIRST:
+				first = 1;
+				alpha = alphaMix;
+				break;
+			default:
+				first = 1;
+				alpha = alphaDip;
+				dipMix = timelineDipMix->items[i];
+				if (dipMix != 0) alpha *= MAX(0, 1 - dipMix->mixTime / dipMix->mixDuration);
+				break;
+		}
 		if (timeline->type == SP_TIMELINE_ROTATE)
-			_spAnimationState_applyRotateTimeline(self, timeline, skeleton, animationTime, alpha, setupPose, timelinesRotation, i << 1, firstFrame);
+			_spAnimationState_applyRotateTimeline(self, timeline, skeleton, animationTime, alpha, first, timelinesRotation, i << 1, firstFrame);
 		else {
-			if (!setupPose) {
+			if (!first) {
 				if (!attachments && timeline->type == SP_TIMELINE_ATTACHMENT) continue;
 				if (!drawOrder && timeline->type == SP_TIMELINE_DRAWORDER) continue;
 			}
-			spTimeline_apply(timeline, skeleton, animationLast, animationTime, events, &internal->eventsCount, alpha, setupPose, 1);
+			spTimeline_apply(timeline, skeleton, animationLast, animationTime, events, &internal->eventsCount, alpha, first, 1);
 		}
 	}
 
-	if (entry->mixDuration > 0) _spAnimationState_queueEvents(self, from, animationTime);
+
+	if (to->mixDuration > 0) _spAnimationState_queueEvents(self, from, animationTime);
 	internal->eventsCount = 0;
 	from->nextAnimationLast = animationTime;
 	from->nextTrackLast = from->trackTime;
@@ -581,7 +621,6 @@ void spAnimationState_clearTrack (spAnimationState* self, int trackIndex) {
 void _spAnimationState_setCurrent (spAnimationState* self, int index, spTrackEntry* current, int /*boolean*/ interrupt) {
 	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
 	spTrackEntry* from = _spAnimationState_expandToIndex(self, index);
-	spTrackEntry* mixingFrom = 0;
 	self->tracks[index] = current;
 
 	if (from) {
@@ -589,25 +628,9 @@ void _spAnimationState_setCurrent (spAnimationState* self, int index, spTrackEnt
 		current->mixingFrom = from;
 		current->mixTime = 0;
 
-		mixingFrom = from->mixingFrom;
-		if (mixingFrom != 0 && from->mixDuration > 0) {
-			if (self->multipleMixing) {
-				current->mixAlpha *= MIN(from->mixTime / from->mixDuration, 1);
-			} else {
-				if (from->mixTime / from->mixDuration < 0.5 && mixingFrom->animation != SP_EMPTY_ANIMATION) {
-					current->mixingFrom = mixingFrom;
-					mixingFrom->mixingFrom = from;
-					mixingFrom->mixTime = from->mixDuration - from->mixTime;
-					mixingFrom->mixDuration = from->mixDuration;
-					from->mixingFrom = 0;
-					from = mixingFrom;
-				}
-				
-				from->mixAlpha = 0;
-				from->mixTime = 0;
-				from->mixDuration = 0;
-			}
-		}
+		/* Store the interrupted mix percentage. */
+		if (from->mixingFrom != 0 && from->mixDuration > 0)
+			current->interruptAlpha *= MIN(1, from->mixTime / from->mixDuration);
 
 		from->timelinesRotationCount = 0;
 	}
@@ -747,9 +770,12 @@ spTrackEntry* _spAnimationState_trackEntry (spAnimationState* self, int trackInd
 	entry->timeScale = 1;
 
 	entry->alpha = 1;
-	entry->mixAlpha = 1;
+	entry->interruptAlpha = 1;
 	entry->mixTime = 0;
 	entry->mixDuration = !last ? 0 : spAnimationStateData_getMix(self->data, last->animation, animation);
+
+	entry->timelineData = spIntArray_create(16);
+	entry->timelineDipMix = spTrackEntryArray_create(16);
 	return entry;
 }
 
@@ -765,48 +791,22 @@ void _spAnimationState_disposeNext (spAnimationState* self, spTrackEntry* entry)
 
 void _spAnimationState_animationsChanged (spAnimationState* self) {
 	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
-	int i, n, ii, nn, lowestMixingFrom;
+	int i, n;
 	spTrackEntry* entry;
-	spTimeline** timelines;
+	spTrackEntry* lastEntry = 0;
+	spTrackEntryArray* mixingTo;
 	internal->animationsChanged = 0;
 
-	i = 0; n = self->tracksCount;
 	internal->propertyIDsCount = 0;
+	i = 0; n = self->tracksCount;
 
-	for (; i < n; i++) {
-		entry = self->tracks[i];
-		if (!entry) continue;
-		_spAnimationState_setTimelinesFirst(self, entry);
-		i++;
-		break;
-	}
-	for (; i < n; i++) {
-		entry = self->tracks[i];
-		if (entry) _spAnimationState_checkTimelinesFirst(self, entry);
-	}
-
-	if (self->multipleMixing) return;
+	mixingTo = self->mixingTo;
 
-	internal->propertyIDsCount = 0;
-	lowestMixingFrom = n;
-	for (i = 0; i < n; i++) {
-		entry = self->tracks[i];
-		if (entry == 0 || entry->mixingFrom == 0) continue;
-		lowestMixingFrom = i;
-		break;
-	}
-	for (i = n - 1; i >= lowestMixingFrom; i--) {
+	for (;i < n; i++) {
 		entry = self->tracks[i];
-		if (entry == 0) continue;
-
-		timelines = entry->animation->timelines;
-		for (ii = 0, nn = entry->animation->timelinesCount; ii < nn; ii++)
-			_spAnimationState_addPropertyID(self, spTimeline_getPropertyId(timelines[ii]));
-
-		entry = entry->mixingFrom;
-		while (entry != 0) {
-			_spAnimationState_checkTimelinesUsage(self, entry, 0);
-			entry = entry->mixingFrom;
+		if (entry != 0) {
+			_spTrackEntry_setTimelineData(entry, lastEntry, mixingTo, self);
+			lastEntry = entry;
 		}
 	}
 }
@@ -821,28 +821,6 @@ float* _spAnimationState_resizeTimelinesRotation(spTrackEntry* entry, int newSiz
 	return entry->timelinesRotation;
 }
 
-int* _spAnimationState_resizeTimelinesFirst(spTrackEntry* entry, int newSize) {
-	if (entry->timelinesFirstCount != newSize) {
-		int* newTimelinesFirst = CALLOC(int, newSize);
-		FREE(entry->timelinesFirst);
-		entry->timelinesFirst = newTimelinesFirst;
-		entry->timelinesFirstCount = newSize;
-	}
-
-	return entry->timelinesFirst;
-}
-
-int* _spAnimationState_resizeTimelinesLast(spTrackEntry* entry, int newSize) {
-	if (entry->timelinesLastCount != newSize) {
-		int* newTimelinesLast = CALLOC(int, newSize);
-		FREE(entry->timelinesLast);
-		entry->timelinesLast = newTimelinesLast;
-		entry->timelinesLastCount = newSize;
-	}
-
-	return entry->timelinesLast;
-}
-
 void _spAnimationState_ensureCapacityPropertyIDs(spAnimationState* self, int capacity) {
 	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
 	if (internal->propertyIDsCapacity < capacity) {
@@ -868,42 +846,6 @@ int _spAnimationState_addPropertyID(spAnimationState* self, int id) {
 	return 1;
 }
 
-void _spAnimationState_setTimelinesFirst (spAnimationState* self, spTrackEntry* entry) {
-	int i, n;
-	int* usage;
-	spTimeline** timelines;
-
-	if (entry->mixingFrom) {
-		_spAnimationState_setTimelinesFirst(self, entry->mixingFrom);
-		_spAnimationState_checkTimelinesUsage(self, entry, -1);
-		return;
-	}
-
-	n = entry->animation->timelinesCount;
-	timelines = entry->animation->timelines;
-	usage = _spAnimationState_resizeTimelinesFirst(entry, n);
-	for (i = 0; i < n; i++) {
-		_spAnimationState_addPropertyID(self, spTimeline_getPropertyId(timelines[i]));
-		usage[i] = 1;
-	}
-}
-
-void _spAnimationState_checkTimelinesFirst (spAnimationState* self, spTrackEntry* entry) {
-	if (entry->mixingFrom) _spAnimationState_checkTimelinesFirst(self, entry->mixingFrom);
-	_spAnimationState_checkTimelinesUsage(self, entry, -1);
-}
-
-void _spAnimationState_checkTimelinesUsage (spAnimationState* self, spTrackEntry* entry, int /*boolean*/ useTimelinesFirst) {
-	int i, n;
-	int* usage;
-	spTimeline** timelines;
-	n = entry->animation->timelinesCount;
-	timelines = entry->animation->timelines;
-	usage = useTimelinesFirst ? _spAnimationState_resizeTimelinesFirst(entry, n) : _spAnimationState_resizeTimelinesLast(entry, n);
-	for (i = 0; i < n; i++)
-		usage[i] = _spAnimationState_addPropertyID(self, spTimeline_getPropertyId(timelines[i]));
-}
-
 spTrackEntry* spAnimationState_getCurrent (spAnimationState* self, int trackIndex) {
 	if (trackIndex >= self->tracksCount) return 0;
 	return self->tracks[trackIndex];
@@ -922,3 +864,55 @@ float spTrackEntry_getAnimationTime (spTrackEntry* entry) {
 	}
 	return MIN(entry->trackTime + entry->animationStart, entry->animationEnd);
 }
+
+int /*boolean*/ _spTrackEntry_hasTimeline(spTrackEntry* self, int id) {
+	spTimeline** timelines = self->animation->timelines;
+	int i, n;
+	for (i = 0, n = self->animation->timelinesCount; i < n; i++)
+		if (spTimeline_getPropertyId(timelines[i]) == id) return 1;
+	return 0;
+}
+
+spTrackEntry* _spTrackEntry_setTimelineData(spTrackEntry* self, spTrackEntry* to, spTrackEntryArray* mixingToArray, spAnimationState* state) {
+	spTrackEntry* lastEntry;
+	spTrackEntry** mixingTo;
+	int mixingToLast;
+	spTimeline** timelines;
+	int timelinesCount;
+	int* timelineData;
+	spTrackEntry** timelineDipMix;
+	int i, ii;
+
+	if (to != 0) spTrackEntryArray_add(mixingToArray, to);
+	lastEntry = self->mixingFrom != 0 ? _spTrackEntry_setTimelineData(self->mixingFrom, self, mixingToArray, state) : self;
+	if (to != 0) spTrackEntryArray_pop(mixingToArray);
+
+	mixingTo = mixingToArray->items;
+	mixingToLast = mixingToArray->size - 1;
+	timelines = self->animation->timelines;
+	timelinesCount = self->animation->timelinesCount;
+	timelineData = spIntArray_setSize(self->timelineData, timelinesCount)->items;
+	timelineDipMix = spTrackEntryArray_setSize(self->timelineDipMix, timelinesCount)->items;
+
+	outer:
+	for (i = 0; i < timelinesCount; i++) {
+		int id = spTimeline_getPropertyId(timelines[i]);
+		if (!_spAnimationState_addPropertyID(state, id))
+			timelineData[i] = SUBSEQUENT;
+		else if (to == 0 || !_spTrackEntry_hasTimeline(to, id))
+			timelineData[i] = FIRST;
+		else {
+			timelineData[i] = DIP;
+			for (ii = mixingToLast; ii >= 0; ii--) {
+				spTrackEntry* entry = mixingTo[ii];
+				if (!_spTrackEntry_hasTimeline(entry, id)) {
+					if (entry->mixDuration > 0) timelineDipMix[i] = entry;
+					i++;
+					goto outer;
+				}
+			}
+			timelineDipMix[i] = 0;
+		}
+	}
+	return lastEntry;
+}

+ 7 - 1
spine-c/spine-c/src/spine/extension.c

@@ -32,6 +32,7 @@
 #include <stdio.h>
 
 static void* (*mallocFunc) (size_t size) = malloc;
+static void* (*reallocFunc) (void* ptr, size_t size) = realloc;
 static void* (*debugMallocFunc) (size_t size, const char* file, int line) = NULL;
 static void (*freeFunc) (void* ptr) = free;
 
@@ -47,7 +48,7 @@ void* _calloc (size_t num, size_t size, const char* file, int line) {
 	return ptr;
 }
 void* _realloc(void* ptr, size_t size) {
-	return realloc(ptr, size);
+	return reallocFunc(ptr, size);
 }
 void _free (void* ptr) {
 	freeFunc(ptr);
@@ -60,6 +61,11 @@ void _setDebugMalloc(void* (*malloc) (size_t size, const char* file, int line))
 void _setMalloc (void* (*malloc) (size_t size)) {
 	mallocFunc = malloc;
 }
+
+void _setRealloc (void* (*realloc) (void* ptr, size_t size)) {
+	reallocFunc = realloc;
+}
+
 void _setFree (void (*free) (void* ptr)) {
 	freeFunc = free;
 }

+ 8 - 4
spine-csharp/src/AnimationState.cs

@@ -140,9 +140,13 @@ namespace Spine {
 
 			// Require mixTime > 0 to ensure the mixing from entry was applied at least once.
 			if (entry.mixTime > 0 && (entry.mixTime >= entry.mixDuration || entry.timeScale == 0)) {
-				if (animationCount > 6 && from.mixingFrom == null) { // Limit the mixing from linked list.
-					entry.mixingFrom = null;
-					queue.End(from);
+				if (animationCount > 5 && from.mixingFrom == null) {
+					// Limit linked list by speeding up and removing old entries.
+					entry.interruptAlpha = Math.Max(0, entry.interruptAlpha - delta * 0.66f);
+					if (entry.interruptAlpha <= 0) {
+						entry.mixingFrom = null;
+						queue.End(from);
+					}
 				}
 				return finished;
 			}
@@ -702,7 +706,7 @@ namespace Spine {
 					for (int ii = mixingToLast; ii >= 0; ii--) {
 						var entry = mixingTo[ii];
 						if (!entry.HasTimeline(id)) {
-							timelineDipMixItems[i] = entry;
+							if (entry.mixDuration > 0) timelineDipMixItems[i] = entry;
 							goto outer; // continue outer;
 						}
 					}

+ 128 - 158
spine-lua/AnimationState.lua

@@ -34,6 +34,7 @@ local utils = require "spine-lua.utils"
 local Animation = require "spine-lua.Animation"
 local AnimationStateData = require "spine-lua.AnimationStateData"
 local math_min = math.min
+local math_max = math.max
 local math_abs = math.abs
 local math_signum = utils.signum
 local math_floor = math.floor
@@ -45,6 +46,9 @@ local function zlen(array)
 end
 
 local EMPTY_ANIMATION = Animation.new("<empty>", {}, 0)
+local SUBSEQUENT = 0
+local FIRST = 1
+local DIP = 2
 
 local EventType = {
 	start = 0,
@@ -167,15 +171,63 @@ function TrackEntry.new ()
 		eventThreshold = 0, attachmentThreshold = 0, drawOrderThreshold = 0,
 		animationStart = 0, animationEnd = 0, animationLast = 0, nextAnimationLast = 0,
 		delay = 0, trackTime = 0, trackLast = 0, nextTrackLast = 0, trackEnd = 0, timeScale = 0,
-		alpha = 0, mixTime = 0, mixDuration = 0, mixAlpha = 0,
-		timelinesFirst = {},
-		timelinesLast = {},
+		alpha = 0, mixTime = 0, mixDuration = 0, interruptAlpha = 0,
+		timelineData = {},
+		timelineDipMix = {},
 		timelinesRotation = {}
 	}
 	setmetatable(self, TrackEntry)
 	return self
 end
 
+function TrackEntry:setTimelineData(to, mixingToArray, propertyIDs)
+	if to then table_insert(mixingToArray, to) end
+	local lastEntry = self
+	if self.mixingFrom then lastEntry = self.mixingFrom:setTimelineData(self, mixingToArray, propertyIDs) end
+	if to then mixingToArray[#mixingToArray] = nil end
+
+	local mixingTo = mixingToArray
+	local mixingToLast = #mixingToArray
+	local timelines = self.animation.timelines
+	local timelinesCount = #self.animation.timelines
+	local timelineData = self.timelineData
+	local timelineDipMix = self.timelineDipMix
+
+	local i = 1
+	while i <= timelinesCount do
+		local id = "" .. timelines[i]:getPropertyId()
+		if not (propertyIDs[id] == nil) then
+			timelineData[i] = SUBSEQUENT
+		elseif (to == nil or not to:hasTimeline(id)) then
+			timelineData[i] = FIRST
+		else
+			timelineData[i] = DIP
+			local ii = mixingToLast
+			while ii > 0 do
+				local entry = mixingTo[ii]
+				local skip = false
+				if not entry:hasTimeline(id) then
+					if entry.mixDuration > 0 then timelineDipMix[i] = entry end
+					skip = true
+					break
+				end
+				ii = ii - 1
+			end
+			if not skip then timelineDipMix[i] = nil end
+		end
+		i = i + 1
+	end
+	return lastEntry
+end
+
+function TrackEntry:hasTimeline(id)
+	local timelines = self.animation.timelines
+	for i,timeline in ipairs(timelines) do
+		if timeline:getPropertyId() == id then return true end
+	end
+	return false
+end
+
 function TrackEntry:getAnimationTime ()
 	if self.loop then
 		local duration = self.animationEnd - self.animationStart
@@ -204,7 +256,7 @@ function AnimationState.new (data)
 		propertyIDs = {},
 		animationsChanged = false,
 		timeScale = 1,
-		mixingMultiple = false
+		mixingTo = {}
 	}
 	self.queue = EventQueue.new(self)
 	setmetatable(self, AnimationState)
@@ -260,8 +312,17 @@ function AnimationState:update (delta)
 					end
 				end
 
-				if not skip then 
- 					self:updateMixingFrom(current, delta)
+				if not skip then
+					if current.mixingFrom and self:updateMixingFrom(current, delta, 2) then
+						-- End mixing from entries once all have completed.
+						local from = current.mixingFrom
+						current.mixingFrom = nil
+						while from do
+							queue:_end(from)
+							from = from.mixingFrom
+						end
+					end
+					
           current.trackTime = current.trackTime + currentDelta
         end
 			end
@@ -271,23 +332,30 @@ function AnimationState:update (delta)
 	queue:drain()
 end
 
-function AnimationState:updateMixingFrom (entry, delta)
+function AnimationState:updateMixingFrom (entry, delta, animationCount)
 	local from = entry.mixingFrom
-	if from == nil then return end
+	if from == nil then return true end
 
- 	self:updateMixingFrom(from, delta)
-
-	local queue = self.queue
-	if entry.mixTime >= entry.mixDuration and from.mixingFrom == nil and entry.mixTime > 0 then
- 		entry.mixingFrom = null
-		queue:_end(from)
-    return
+ 	local finished = self:updateMixingFrom(from, delta, animationCount + 1)
+	
+	-- Require mixTime > 0 to ensure the mixing from entry was applied at least once.
+	if (entry.mixTime > 0 and (entry.mixTime >= entry.mixDuration or entry.timeScale == 0)) then
+		if (animationCount > 5 and from.mixingFrom == nil) then
+			-- Limit linked list by speeding up and removing old entries.
+			entry.interruptAlpha = math_max(0, entry.interruptAlpha - delta * 0.66)
+			if entry.interruptAlpha <= 0 then
+				entry.mixingFrom = nil
+				queue._end(from)
+			end
+		end
+		return finished
 	end
 
 	from.animationLast = from.nextAnimationLast
 	from.trackLast = from.nextTrackLast
-	from.trackTime = from.trackTime + delta * from.timeScale;
-	entry.mixTime = entry.mixTime + delta * entry.timeScale;
+	from.trackTime = from.trackTime + delta * from.timeScale
+	entry.mixTime = entry.mixTime + delta * entry.timeScale
+	return false;
 end
 
 function AnimationState:apply (skeleton)
@@ -317,15 +385,16 @@ function AnimationState:apply (skeleton)
 					timeline:apply(skeleton, animationLast, animationTime, events, 1, true, false)
 				end
 			else
+				local timelineData = current.timelineData
 				local firstFrame = #current.timelinesRotation == 0
-				local timelinesRotation = current.timelinesRotation;
-				local timelinesFirst = current.timelinesFirst
+				local timelinesRotation = current.timelinesRotation
+
 				for i,timeline in ipairs(timelines) do
 					if timeline.type == Animation.TimelineType.rotate then
-						self:applyRotateTimeline(timeline, skeleton, animationTime, mix, timelinesFirst[i], timelinesRotation, i * 2,
+						self:applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineData[i] > 0, timelinesRotation, i * 2,
 							firstFrame) -- FIXME passing ii * 2, indexing correct?
 					else
-						timeline:apply(skeleton, animationLast, animationTime, events, mix, timelinesFirst[i], false)
+						timeline:apply(skeleton, animationLast, animationTime, events, mix, timelineData[i] > 0, false)
 					end
 				end
 			end
@@ -339,15 +408,15 @@ function AnimationState:apply (skeleton)
 	queue:drain()
 end
 
-function AnimationState:applyMixingFrom (entry, skeleton)
-	local from = entry.mixingFrom
+function AnimationState:applyMixingFrom (to, skeleton)
+	local from = to.mixingFrom
 	if from.mixingFrom then self:applyMixingFrom(from, skeleton) end
 
 	local mix = 0
-	if entry.mixDuration == 0 then -- Single frame mix to undo mixingFrom changes.
+	if to.mixDuration == 0 then -- Single frame mix to undo mixingFrom changes.
 		mix = 1
 	else
-		mix = entry.mixTime / entry.mixDuration
+		mix = to.mixTime / to.mixDuration
 		if mix > 1 then mix = 1 end
 	end
 
@@ -358,36 +427,45 @@ function AnimationState:applyMixingFrom (entry, skeleton)
 	local animationLast = from.animationLast
 	local animationTime = from:getAnimationTime()
 	local timelines = from.animation.timelines
-	local timelinesFirst = from.timelinesFirst
-	local timelinesLast = nil
-	if (self.multipleMixing == false) then timelinesLast = from.timelinesLast end
-	local alphaBase = from.alpha * entry.mixAlpha
-	local alphaMix = alphaBase * (1 - mix)
+	local timelineData = from.timelineData
+	local timelineDipMix = from.timelineDipMix
 
 	local firstFrame = #from.timelinesRotation == 0
 	local timelinesRotation = from.timelinesRotation
 
+	local first = false
+	local alphaDip = from.alpha * to.interruptAlpha
+	local alphaMix = alphaDip * (1 - mix)
+	local alpha = 0
+	
 	local skip = false
 	for i,timeline in ipairs(timelines) do
-		local setupPose = timelinesFirst[i]
-		local alpha = 1;
-		if (timelinesLast ~= nil and setupPose and not timelinesLast[i]) then
-			alpha = alphaBase
-		else
+		
+		if timelineData[i] == SUBSEQUENT then
+			first = false
 			alpha = alphaMix
+		elseif timelineData[i] == FIRST then
+			first = true
+			alpha = alphaMix
+		else
+			first = true
+			alpha = alphaDip
+			local dipMix = timelineDipMix[i]
+			if dipMix then alpha = alpha * math_max(0, 1 - dipMix.mixtime / dipMix.mixDuration) end
 		end
+	
 		if timeline.type == Animation.TimelineType.rotate then
-			self:applyRotateTimeline(timeline, skeleton, animationTime, alpha, setupPose, timelinesRotation, i * 2, firstFrame) -- FIXME passing i * 2, correct indexing?
+			self:applyRotateTimeline(timeline, skeleton, animationTime, alpha, first, timelinesRotation, i * 2, firstFrame)
 		else
-			if not setupPose then
+			if not first then
 				if not attachments and timeline.type == Animation.TimelineType.attackment then skip = true end
 				if not drawOrder and timeline.type == Animation.TimelineType.drawOrder then skip = true end
 			end
-			if not skip then timeline:apply(skeleton, animationLast, animationTime, events, alpha, setupPose, true) end
+			if not skip then timeline:apply(skeleton, animationLast, animationTime, events, alpha, first, true) end
 		end
 	end
 
-	if (entry.mixDuration > 0) then 	self:queueEvents(from, animationTime) end
+	if (to.mixDuration > 0) then 	self:queueEvents(from, animationTime) end
 	self.events = {};
 	from.nextAnimationLast = animationTime
 	from.nextTrackLast = from.trackTime
@@ -555,24 +633,8 @@ function AnimationState:setCurrent (index, current, interrupt)
     current.mixingFrom = from
     current.mixTime = 0
 		
-		local mixingFrom = from.mixingFrom
-		if (mixingFrom ~= nil and from.mixDuration > 0) then
-			if (self.multipleMixing) then
-				current.mixAlpha = current.mixAlpha * math_min(from.mixTime / from.mixDuration, 1)
-			else
-				if (from.mixTime / from.mixDuration < 0.5 and mixingFrom.animation ~= EMPTY_ANIMATION) then
-					current.mixingFrom = mixingFrom
-					mixingFrom.mixingFrom = from
-					mixingFrom.mixTime = from.mixDuration - from.mixTime
-					mixingFrom.mixDuration = from.mixDuration
-					from.mixingFrom = nil
-					from = mixingFrom
-				end
-
-				from.mixAlpha = 0;
-				from.mixTime = 0;
-				from.mixDuration = 0;
-			end
+		if from.mixingFrom and from.mixDuration > 0 then
+			current.interruptAlpha = current.interruptAlpha * math_min(1, from.mixTime / from.mixDuration)
 		end
 		
 		from.timelinesRotation = {};
@@ -705,7 +767,7 @@ function AnimationState:trackEntry (trackIndex, animation, loop, last)
   entry.timeScale = 1
 
   entry.alpha = 1
-  entry.mixAlpha = 1
+  entry.interruptAlpha = 1
   entry.mixTime = 0
   if not last then
     entry.mixDuration = 0
@@ -726,111 +788,19 @@ function AnimationState:disposeNext (entry)
 end
 
 function AnimationState:_animationsChanged ()
-  self.animationsChanged = false;
+  self.animationsChanged = false
 
   self.propertyIDs = {}
-  local propertyIDs = self.propertyIDs;
-
-  -- need to get the highest index cause Lua is funny
-  local highest = -1
-  local tracks = self.tracks
-  for i,entry in pairs(tracks) do
-    if i > highest then highest = i end
-  end
-
-  -- Set timelinesFirst for all entries, from lowest track to highest.
-  local i = 0
-  local n = highest + 1
-  while i < n do
-    local entry = tracks[i]
-    if entry then
-      self:setTimelinesFirst(entry);
-      i = i + 1
-      break;
-    end
-    i = i + 1
-  end
-  while i < n do
-    local entry = tracks[i]
-    if entry then self:checkTimelinesFirst(entry) end
-    i = i + 1
-  end
+  local propertyIDs = self.propertyIDs
+	local mixingTo = self.mixingTo
 	
-	if (self.multipleMixing) then return end
-
-	self.propertyIDs = {}
-	local lowestMixingFrom = n
-	i = 0;
-	while i < n do
-		entry = self.tracks[i]
-		if not (entry == nil or entry.mixingFrom == nil) then
-			lowestMixingFrom = i
-			i = n + 1 -- break
+  local lastEntry = nil
+	for i, entry in pairs(self.tracks) do
+		if entry then
+			entry:setTimelineData(lastEntry, mixingTo, propertyIDs)
+			lastEntry = entry
 		end
-		i = i + 1
 	end
-	i = n - 1
-	while i >= lowestMixingFrom do
-		local entry = self.tracks[i]
-		if (entry) then
-			local propertyIDs = self.propertyIDs
-			local timelines = entry.animation.timelines
-			local ii = 1
-			local nn = #entry.animation.timelines;
-			while ii <= nn do
-				local id = "" .. timelines[ii]:getPropertyId()
-				propertyIDs[id] = id
-				ii = ii + 1
-			end
-
-			entry = entry.mixingFrom
-			while (entry) do
-				self:checkTimelinesUsage(entry, entry.timelinesLast)
-				entry = entry.mixingFrom;
-			end
-		end
-		i = i - 1
-	end
-end
-
-function AnimationState:setTimelinesFirst (entry)
-  if entry.mixingFrom then
-    self:setTimelinesFirst(entry.mixingFrom)
-    self:checkTimelinesUsage(entry, entry.timelinesFirst)
-    return
-  end
-  local propertyIDs = self.propertyIDs
-  local n = #entry.animation.timelines
-  local timelines = entry.animation.timelines
-  entry.timelinesFirst = {}
-  local usage = entry.timelinesFirst;
-  local i = 1
-  while i <= n do
-    local id = "" .. timelines[i]:getPropertyId()
-    propertyIDs[id] = id
-    usage[i] = true;
-    i = i + 1
-  end
-end
-
-function AnimationState:checkTimelinesFirst (entry)
-  if entry.mixingFrom then self:checkTimelinesFirst(entry.mixingFrom) end
-  self:checkTimelinesUsage(entry, entry.timelinesFirst)
-end
-
-function AnimationState:checkTimelinesUsage (entry, usageArray)
-  local propertyIDs = self.propertyIDs
-  local n = #entry.animation.timelines
-  local timelines = entry.animation.timelines
-  local usage = usageArray
-	local i = 1
-  while i <= n do
-    local id = "" .. timelines[i]:getPropertyId()
-    local contained = propertyIDs[id] == id
-    propertyIDs[id] = id
-    usage[i] = not contained
-    i = i + 1
-  end
 end
 
 function AnimationState:getCurrent (trackIndex)

+ 0 - 1
spine-sfml/example/main.cpp

@@ -116,7 +116,6 @@ void spineboy (SkeletonData* skeletonData, Atlas* atlas) {
 
 	SkeletonDrawable* drawable = new SkeletonDrawable(skeletonData, stateData);
 	drawable->timeScale = 1;
-	drawable->state->multipleMixing = -1;
 
 	Skeleton* skeleton = drawable->skeleton;
 	skeleton->flipX = false;

BIN
spine-starling/spine-starling-example/lib/spine-as3.swc


BIN
spine-starling/spine-starling/lib/spine-as3.swc


+ 6 - 3
spine-ts/build/spine-all.js

@@ -1118,9 +1118,12 @@ var spine;
 				return true;
 			var finished = this.updateMixingFrom(from, delta, animationCount + 1);
 			if (entry.mixTime > 0 && (entry.mixTime >= entry.mixDuration || entry.timeScale == 0)) {
-				if (animationCount > 6 && from.mixingFrom == null) {
-					entry.mixingFrom = null;
-					this.queue.end(from);
+				if (animationCount > 5 && from.mixingFrom == null) {
+					entry.interruptAlpha = Math.max(0, entry.interruptAlpha - delta * 0.66);
+					if (entry.interruptAlpha <= 0) {
+						entry.mixingFrom = null;
+						this.queue.end(from);
+					}
 				}
 				return finished;
 			}

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
spine-ts/build/spine-all.js.map


+ 6 - 3
spine-ts/build/spine-canvas.js

@@ -1118,9 +1118,12 @@ var spine;
 				return true;
 			var finished = this.updateMixingFrom(from, delta, animationCount + 1);
 			if (entry.mixTime > 0 && (entry.mixTime >= entry.mixDuration || entry.timeScale == 0)) {
-				if (animationCount > 6 && from.mixingFrom == null) {
-					entry.mixingFrom = null;
-					this.queue.end(from);
+				if (animationCount > 5 && from.mixingFrom == null) {
+					entry.interruptAlpha = Math.max(0, entry.interruptAlpha - delta * 0.66);
+					if (entry.interruptAlpha <= 0) {
+						entry.mixingFrom = null;
+						this.queue.end(from);
+					}
 				}
 				return finished;
 			}

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
spine-ts/build/spine-canvas.js.map


+ 6 - 3
spine-ts/build/spine-core.js

@@ -1118,9 +1118,12 @@ var spine;
 				return true;
 			var finished = this.updateMixingFrom(from, delta, animationCount + 1);
 			if (entry.mixTime > 0 && (entry.mixTime >= entry.mixDuration || entry.timeScale == 0)) {
-				if (animationCount > 6 && from.mixingFrom == null) {
-					entry.mixingFrom = null;
-					this.queue.end(from);
+				if (animationCount > 5 && from.mixingFrom == null) {
+					entry.interruptAlpha = Math.max(0, entry.interruptAlpha - delta * 0.66);
+					if (entry.interruptAlpha <= 0) {
+						entry.mixingFrom = null;
+						this.queue.end(from);
+					}
 				}
 				return finished;
 			}

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
spine-ts/build/spine-core.js.map


+ 6 - 3
spine-ts/build/spine-threejs.js

@@ -1118,9 +1118,12 @@ var spine;
 				return true;
 			var finished = this.updateMixingFrom(from, delta, animationCount + 1);
 			if (entry.mixTime > 0 && (entry.mixTime >= entry.mixDuration || entry.timeScale == 0)) {
-				if (animationCount > 6 && from.mixingFrom == null) {
-					entry.mixingFrom = null;
-					this.queue.end(from);
+				if (animationCount > 5 && from.mixingFrom == null) {
+					entry.interruptAlpha = Math.max(0, entry.interruptAlpha - delta * 0.66);
+					if (entry.interruptAlpha <= 0) {
+						entry.mixingFrom = null;
+						this.queue.end(from);
+					}
 				}
 				return finished;
 			}

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
spine-ts/build/spine-threejs.js.map


+ 6 - 3
spine-ts/build/spine-webgl.js

@@ -1118,9 +1118,12 @@ var spine;
 				return true;
 			var finished = this.updateMixingFrom(from, delta, animationCount + 1);
 			if (entry.mixTime > 0 && (entry.mixTime >= entry.mixDuration || entry.timeScale == 0)) {
-				if (animationCount > 6 && from.mixingFrom == null) {
-					entry.mixingFrom = null;
-					this.queue.end(from);
+				if (animationCount > 5 && from.mixingFrom == null) {
+					entry.interruptAlpha = Math.max(0, entry.interruptAlpha - delta * 0.66);
+					if (entry.interruptAlpha <= 0) {
+						entry.mixingFrom = null;
+						this.queue.end(from);
+					}
 				}
 				return finished;
 			}

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
spine-ts/build/spine-webgl.js.map


+ 6 - 3
spine-ts/build/spine-widget.js

@@ -1118,9 +1118,12 @@ var spine;
 				return true;
 			var finished = this.updateMixingFrom(from, delta, animationCount + 1);
 			if (entry.mixTime > 0 && (entry.mixTime >= entry.mixDuration || entry.timeScale == 0)) {
-				if (animationCount > 6 && from.mixingFrom == null) {
-					entry.mixingFrom = null;
-					this.queue.end(from);
+				if (animationCount > 5 && from.mixingFrom == null) {
+					entry.interruptAlpha = Math.max(0, entry.interruptAlpha - delta * 0.66);
+					if (entry.interruptAlpha <= 0) {
+						entry.mixingFrom = null;
+						this.queue.end(from);
+					}
 				}
 				return finished;
 			}

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
spine-ts/build/spine-widget.js.map


+ 7 - 3
spine-ts/core/src/AnimationState.ts

@@ -115,9 +115,13 @@ module spine {
 
 			// Require mixTime > 0 to ensure the mixing from entry was applied at least once.
 			if (entry.mixTime > 0 && (entry.mixTime >= entry.mixDuration || entry.timeScale == 0)) {
-				if (animationCount > 6 && from.mixingFrom == null) { // Limit the mixing from linked list.
-					entry.mixingFrom = null;
-					this.queue.end(from);
+				if (animationCount > 5 && from.mixingFrom == null) {
+					// Limit linked list by speeding up and removing old entries.
+					entry.interruptAlpha = Math.max(0, entry.interruptAlpha - delta * 0.66);
+					if (entry.interruptAlpha <= 0) {
+						entry.mixingFrom = null;
+						this.queue.end(from);
+					}
 				}
 				return finished;
 			}

BIN
spine-ue4/Content/Test/Assets/TestMaterial.uasset


BIN
spine-ue4/Content/Test/Assets/Textures/TwoColorTest.uasset


+ 13 - 0
spine-ue4/Content/Test/Assets/TwoColorTest.atlas

@@ -0,0 +1,13 @@
+
+TwoColorTest.png
+size: 512,512
+format: RGBA8888
+filter: Linear,Linear
+repeat: none
+squareWithBorder
+  rotate: false
+  xy: 2, 2
+  size: 300, 300
+  orig: 300, 300
+  offset: 0, 0
+  index: -1

+ 150 - 0
spine-ue4/Content/Test/Assets/TwoColorTest.json

@@ -0,0 +1,150 @@
+{
+"skeleton": { "hash": "5Oji/z9A5lQ/crlH60repeTNBg8", "spine": "3.6.07-beta", "width": 1588, "height": 732, "images": "" },
+"bones": [
+	{ "name": "root" },
+	{ "name": "singleColorTint", "parent": "root", "x": -400 },
+	{ "name": "singleColorTint2", "parent": "root", "x": -400, "y": -383 },
+	{ "name": "twoColorTint", "parent": "root", "x": 800 },
+	{ "name": "twoColorTint2", "parent": "root", "x": 800, "y": -382 },
+	{ "name": "twoColorTint (blackOnly)", "parent": "root" },
+	{ "name": "twoColorTint (blackOnly)2", "parent": "root", "y": -391 },
+	{ "name": "twoColorTint (colorOnly)", "parent": "root", "x": 400 },
+	{ "name": "twoColorTint (colorOnly)2", "parent": "root", "x": 400, "y": -382 }
+],
+"slots": [
+	{ "name": "squareWithBorder", "bone": "singleColorTint", "attachment": "squareWithBorder" },
+	{ "name": "squareWithBorder7", "bone": "singleColorTint2", "attachment": "squareWithBorder" },
+	{ "name": "squareWithBorder2", "bone": "twoColorTint (blackOnly)", "dark": "000000", "attachment": "squareWithBorder" },
+	{ "name": "squareWithBorder8", "bone": "twoColorTint (blackOnly)2", "dark": "000000", "attachment": "squareWithBorder" },
+	{ "name": "squareWithBorder4", "bone": "twoColorTint (colorOnly)", "dark": "000000", "attachment": "squareWithBorder" },
+	{ "name": "squareWithBorder5", "bone": "twoColorTint (colorOnly)2", "dark": "000000", "attachment": "squareWithBorder" },
+	{ "name": "squareWithBorder3", "bone": "twoColorTint", "dark": "000000", "attachment": "squareWithBorder" },
+	{ "name": "squareWithBorder6", "bone": "twoColorTint2", "dark": "000000", "attachment": "squareWithBorder" }
+],
+"skins": {
+	"default": {
+		"squareWithBorder": {
+			"squareWithBorder": { "width": 300, "height": 300 }
+		},
+		"squareWithBorder2": {
+			"squareWithBorder": { "width": 300, "height": 300 }
+		},
+		"squareWithBorder3": {
+			"squareWithBorder": { "width": 300, "height": 300 }
+		},
+		"squareWithBorder4": {
+			"squareWithBorder": { "width": 300, "height": 300 }
+		},
+		"squareWithBorder5": {
+			"squareWithBorder": {
+				"type": "mesh",
+				"uvs": [ 1, 1, 0, 1, 0, 0, 1, 0 ],
+				"triangles": [ 1, 2, 3, 1, 3, 0 ],
+				"vertices": [ 150, -150, -150, -150, -197, 99, 183, 155 ],
+				"hull": 4,
+				"edges": [ 0, 2, 2, 4, 4, 6, 0, 6 ],
+				"width": 300,
+				"height": 300
+			}
+		},
+		"squareWithBorder6": {
+			"squareWithBorder": {
+				"type": "mesh",
+				"uvs": [ 1, 1, 0, 1, 0, 0, 1, 0 ],
+				"triangles": [ 1, 2, 3, 1, 3, 0 ],
+				"vertices": [ 238, -200, -191, -60, -150, 150, 119, 111 ],
+				"hull": 4,
+				"edges": [ 0, 2, 2, 4, 4, 6, 0, 6 ],
+				"width": 300,
+				"height": 300
+			}
+		},
+		"squareWithBorder7": {
+			"squareWithBorder": {
+				"type": "mesh",
+				"uvs": [ 1, 1, 0, 1, 0, 0, 1, 0 ],
+				"triangles": [ 1, 2, 3, 1, 3, 0 ],
+				"vertices": [ 210, -132, -150, -150, -150, 150, 124, 119 ],
+				"hull": 4,
+				"edges": [ 0, 2, 2, 4, 4, 6, 0, 6 ],
+				"width": 300,
+				"height": 300
+			}
+		},
+		"squareWithBorder8": {
+			"squareWithBorder": {
+				"type": "mesh",
+				"uvs": [ 1, 1, 0, 1, 0, 0, 1, 0 ],
+				"triangles": [ 1, 2, 3, 1, 3, 0 ],
+				"vertices": [ 150, -150, -150, -150, -97, 58, 86, 62 ],
+				"hull": 4,
+				"edges": [ 0, 2, 2, 4, 4, 6, 0, 6 ],
+				"width": 300,
+				"height": 300
+			}
+		}
+	}
+},
+"animations": {
+	"animation": {
+		"slots": {
+			"squareWithBorder": {
+				"color": [
+					{ "time": 0, "color": "fffffffe" },
+					{ "time": 1, "color": "9e17b3fe" },
+					{ "time": 2, "color": "fffffffe" }
+				]
+			},
+			"squareWithBorder2": {
+				"twoColor": [
+					{ "time": 0, "light": "fffffffe", "dark": "000000" },
+					{ "time": 1, "light": "fffffffe", "dark": "ff0000" },
+					{ "time": 2, "light": "fffffffe", "dark": "000000" }
+				]
+			},
+			"squareWithBorder3": {
+				"twoColor": [
+					{ "time": 0, "light": "fffffffe", "dark": "000000" },
+					{ "time": 1, "light": "80ff00fe", "dark": "001cff" },
+					{ "time": 2, "light": "fffffffe", "dark": "000000" }
+				]
+			},
+			"squareWithBorder4": {
+				"twoColor": [
+					{ "time": 0, "light": "fffffffe", "dark": "000000" },
+					{ "time": 1, "light": "ffd300fe", "dark": "000000" },
+					{ "time": 2, "light": "fffffffe", "dark": "000000" }
+				]
+			},
+			"squareWithBorder5": {
+				"twoColor": [
+					{ "time": 0, "light": "fffffffe", "dark": "000000" },
+					{ "time": 1, "light": "ffd300fe", "dark": "000000" },
+					{ "time": 2, "light": "fffffffe", "dark": "000000" }
+				]
+			},
+			"squareWithBorder6": {
+				"twoColor": [
+					{ "time": 0, "light": "fffffffe", "dark": "000000" },
+					{ "time": 1, "light": "80ff00fe", "dark": "001cff" },
+					{ "time": 2, "light": "fffffffe", "dark": "000000" }
+				]
+			},
+			"squareWithBorder7": {
+				"color": [
+					{ "time": 0, "color": "fffffffe" },
+					{ "time": 1, "color": "9e17b3fe" },
+					{ "time": 2, "color": "fffffffe" }
+				]
+			},
+			"squareWithBorder8": {
+				"twoColor": [
+					{ "time": 0, "light": "fffffffe", "dark": "000000" },
+					{ "time": 1, "light": "fffffffe", "dark": "ff0000" },
+					{ "time": 2, "light": "fffffffe", "dark": "000000" }
+				]
+			}
+		}
+	}
+}
+}

BIN
spine-ue4/Content/Test/Assets/TwoColorTest.png


BIN
spine-ue4/Content/Test/Assets/TwoColorTest.uasset


BIN
spine-ue4/Content/Test/Blueprints/TwoColor_Blueprint.uasset


BIN
spine-ue4/Content/Test/Test.umap


+ 58 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/.gitignore

@@ -0,0 +1,58 @@
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+*.ipa
+
+# These project files can be generated by the engine
+*.xcodeproj
+*.sln
+*.suo
+*.opensdf
+*.sdf
+
+# Precompiled Assets
+SourceArt/**/*.png
+SourceArt/**/*.tga
+
+# Binary Files
+Binaries/*
+
+# Builds
+Build/*
+
+# Don't ignore icon files in Build
+!Build/**/*.ico
+
+# Configuration files generated by the Editor
+Saved/*
+
+# Compiled source files for the engine to use
+Intermediate/*
+
+# Cache files for the editor to use
+DerivedDataCache/*

+ 21 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Chris Conway (Koderz)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 50 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/README.md

@@ -0,0 +1,50 @@
+# UE4 Runtime Mesh Component
+
+**Branch with Slicer Support can be found here https://github.com/Koderz/UnrealEngine/tree/RMC_With_Slicer**
+**Examples project can be found here https://github.com/Koderz/UE4RuntimeMeshComponentExamples**
+
+
+**The RuntimeMeshComponent, or RMC for short, is a component designed specifically to support rendering and collision on meshes generated at runtime. This could be anything from voxel engines like Minecraft, to custom model viewers, or just supporting loading user models for things like modding. It has numerous different features to support most of the normal rendering needs of a game, as well as ability to support both static collision for things such as terrain, as well as dynamic collision for things that need to be able to move and bounce around!**
+
+**Now, the RMC is very similar in purpose to the ProceduralMeshComponent or CustomMeshComponent currently found in UE4, but it far surpasses both in features, and efficiency! It on average uses half the memory of the ProceduralMeshComponent, while also being more efficient to render, and far faster to update mesh data. This is shown by the ability to update a 600k+ vertex mesh in real time! The RMC is also nearly 100% compatible with the ProceduralMeshComponent, while adding many features above what the PMC offers.**
+
+
+*Current list of features in the RMC*
+* Slicer Support!!  You can now use
+* Collision cooking speed improvements.** (new)
+* High precision normals support (new)
+* Tessellation support (new)
+* Navigation mesh support (new)
+* Fully configurable vertex structure (new)
+* Ability to save individual sections or the entire RMC to disk (new)
+* RMC <-> StaticMesHComponent conversions.  SMC -> RMC at runtime or in editor.  RMC -> SMC in editor.  (new)
+* Normal/Tangent calculation. (new) (will be getting speed improvements soon)
+* Multiple UV channel support (up to 8 channels) 
+* Fast path updates for meshes that need to update as fast as frame-by-frame
+* Static render path for meshes that don't update frequently, this provides a slightly faster rendering performance.
+* Collision only mesh sections.
+* 50%+ memory reduction over the ProceduralMeshComponent and CustomMeshComponent
+* Visibility and shadowing are configurable per section.
+* Separate vertex positions for cases of only needing to update the position.
+* Collision has lower overhead compared to ProceduralMeshComponent
+
+**The RMC has picked up the collision cooking improvements done in UE4.14. This means that by default you'll see far faster collision updates, but at the cost of a little lower performance collision. You do however have the option to prioritize quality, which will slow down updates, but make actual collision detection a little faster**
+
+**As a part of V2, there has also been some preliminary work done on threaded cooking. This can help to unblock the game thread from collision with large meshes. This is still a very new part, and not heavily tested or complete. To use this you'll have to use a source build of the engine. More information to come.**
+
+For information on installation, usage and everything else, [please read the Wiki](https://github.com/Koderz/UE4RuntimeMeshComponent/wiki/)
+
+**Some requested features that I'm looking into: (These aren't guaranteed to be added)**
+* LOD (Potentially with dithering support)
+* Dithered transitions for mesh updates.
+* Mesh sharing, to allow multiple RMCs to have the same copy of the mesh to reduce memory overhead. This is much like how the StaticMeshComponent works.
+* Instancing support.
+* Multiple vertex buffer (In Addition to the current separate position vertex buffer)
+* Mesh replication
+
+**Supported Engine Versions:**
+v1.2 supports engine versions 4.10+
+v2.0 supports engine versions 4.12+
+
+*The Runtime Mesh Component should support all UE4 platforms.*
+*Collision MAY NOT be available on some platforms (HTML5, Mobile)*

BIN
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Resources/Icon128.png


+ 29 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/RuntimeMeshComponent.uplugin

@@ -0,0 +1,29 @@
+{
+  "FileVersion": 3,
+  "Version": 2,
+  "VersionName": "2.0",
+  "FriendlyName": "Runtime Mesh Component",
+  "Description": "A renderable component with utilities for creating and modifying mesh geometry procedurally at runtime.",
+  "Category": "Rendering",
+	"CreatedBy" : "Chris Conway (Koderz)",
+	"CreatedByURL" : "https://forums.unrealengine.com/member.php?141752-Koderz",
+  "DocsURL": "https://github.com/Koderz/UE4RuntimeMeshComponent/wiki",
+  "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/content/ad0b45a6a31e462aba7cf7c790a9c125",
+  "SupportURL": "https://github.com/Koderz/UE4RuntimeMeshComponent/issues",
+  "EnabledByDefault": true,
+  "CanContainContent": false,
+  "IsBetaVersion": false,
+  "Installed": false,
+  "Modules": [
+    {
+      "Name": "RuntimeMeshComponent",
+      "Type": "Runtime",
+      "LoadingPhase": "Default"
+    },
+		{
+			"Name" : "RuntimeMeshComponentEditor",
+			"Type" : "Editor",
+			"LoadingPhase" : "Default"
+		}
+  ]
+}

+ 2134 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Private/RuntimeMeshComponent.cpp

@@ -0,0 +1,2134 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#include "RuntimeMeshComponentPluginPrivatePCH.h"
+#include "RuntimeMeshComponent.h"
+#include "RuntimeMeshCore.h"
+#include "RuntimeMeshGenericVertex.h"
+#include "RuntimeMeshVersion.h"
+#include "PhysicsEngine/PhysicsSettings.h"
+
+
+/** Runtime mesh scene proxy */
+class FRuntimeMeshSceneProxy : public FPrimitiveSceneProxy
+{
+private:
+	// Temporarily holds all section creation data until this proxy is passsed to the RT.
+	// After this data is applied this array is cleared.
+	TArray<FRuntimeMeshSectionCreateDataInterface*> SectionCreationData;
+
+public:
+
+	FRuntimeMeshSceneProxy(URuntimeMeshComponent* Component)
+		: FPrimitiveSceneProxy(Component)
+		, BodySetup(Component->GetBodySetup())
+	{
+		bStaticElementsAlwaysUseProxyPrimitiveUniformBuffer = true;
+
+
+		// Get the proxy for all mesh sections
+
+		const int32 NumSections = Component->MeshSections.Num();
+		Sections.AddDefaulted(NumSections);
+
+		for (int32 SectionIdx = 0; SectionIdx < NumSections; SectionIdx++)
+		{
+			RuntimeMeshSectionPtr& SourceSection = Component->MeshSections[SectionIdx];
+			if (SourceSection.IsValid())
+			{
+				UMaterialInterface* Material = Component->GetMaterial(SectionIdx);
+				if (Material == nullptr)
+				{
+					Material = UMaterial::GetDefaultMaterial(MD_Surface);
+				}
+
+
+				// Get the section creation data
+				auto* SectionData = SourceSection->GetSectionCreationData(&GetScene(), Material);
+				SectionData->SetTargetSection(SectionIdx);
+				SectionCreationData.Add(SectionData);
+
+			}
+		}
+
+		// Update material relevancy information needed to control the rendering.
+		UpdateMaterialRelevance();
+	}
+
+	virtual ~FRuntimeMeshSceneProxy()
+	{
+		for (FRuntimeMeshSectionProxyInterface* Section : Sections)
+		{
+			if (Section)
+			{
+				delete Section;
+			}
+		}
+	}
+
+	void CreateRenderThreadResources() override
+	{
+		FPrimitiveSceneProxy::CreateRenderThreadResources();
+
+		for (auto Section : SectionCreationData)
+		{
+			CreateSection_RenderThread(Section);
+		}
+		// The individual items are deleted by CreateSection_RenderThread so just clear the array.
+		SectionCreationData.Empty();
+	}
+
+	/** Called on render thread to create a new dynamic section. (Static sections are handled differently) */
+	void CreateSection_RenderThread(FRuntimeMeshSectionCreateDataInterface* SectionData)
+	{
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_CreateSection_RenderThread);
+
+		check(IsInRenderingThread());
+		check(SectionData);
+
+		int32 SectionIndex = SectionData->GetTargetSection();
+
+		// Make sure the array is big enough
+		if (SectionIndex >= Sections.Num())
+		{
+			Sections.SetNum(SectionIndex + 1, false);
+		}
+		
+		// If a section already exists... destroy it!
+		if (FRuntimeMeshSectionProxyInterface* Section = Sections[SectionIndex])
+		{			
+			delete Section;
+		}
+		
+		// Get the proxy and finish the creation here on the render thread.
+		FRuntimeMeshSectionProxyInterface* Section = SectionData->NewProxy;
+		Section->FinishCreate_RenderThread(SectionData);		
+
+		// Save ref to new section
+		Sections[SectionIndex] = Section;
+		
+		delete SectionData;
+		
+		// Update material relevancy information needed to control the rendering.
+		UpdateMaterialRelevance();
+	}
+
+	/** Called on render thread to assign new dynamic data */
+  	void UpdateSection_RenderThread(FRuntimeMeshRenderThreadCommandInterface* SectionData)
+  	{
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateSection_RenderThread);
+
+  		check(IsInRenderingThread());
+		check(SectionData);
+
+		if (SectionData->GetTargetSection() < Sections.Num() && Sections[SectionData->GetTargetSection()] != nullptr)
+		{
+			Sections[SectionData->GetTargetSection()]->FinishUpdate_RenderThread(SectionData);
+		}
+
+		delete SectionData;
+ 	}
+
+	void UpdateSectionPositionOnly_RenderThread(FRuntimeMeshRenderThreadCommandInterface* SectionData)
+	{
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateSectionPositionOnly_RenderThread);
+
+		check(IsInRenderingThread());
+		check(SectionData);
+
+		if (SectionData->GetTargetSection() < Sections.Num() && Sections[SectionData->GetTargetSection()] != nullptr)
+		{
+			Sections[SectionData->GetTargetSection()]->FinishPositionUpdate_RenderThread(SectionData);
+		}
+
+		delete SectionData;
+	}
+
+	void UpdateSectionProperties_RenderThread(FRuntimeMeshRenderThreadCommandInterface* SectionData)
+	{
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateSectionProperties_RenderThread);
+
+		check(IsInRenderingThread());
+		check(SectionData);
+
+		int32 SectionIndex = SectionData->GetTargetSection();
+
+		if (SectionIndex < Sections.Num() && Sections[SectionIndex] != nullptr)
+		{
+			Sections[SectionIndex]->FinishPropertyUpdate_RenderThread(SectionData);
+		}
+	}
+
+
+	void DestroySection_RenderThread(int32 SectionIndex)
+	{
+		check(IsInRenderingThread());
+
+		if (SectionIndex < Sections.Num() && Sections[SectionIndex] != nullptr)
+		{
+			delete Sections[SectionIndex];
+			Sections[SectionIndex] = nullptr;
+		}
+		
+		// Update material relevancy information needed to control the rendering.
+		UpdateMaterialRelevance();
+	}
+
+	void ApplyBatchUpdate_RenderThread(FRuntimeMeshBatchUpdateData* BatchUpdateData)
+	{
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_ApplyBatchUpdate_RenderThread);
+
+		check(IsInRenderingThread());
+		check(BatchUpdateData);
+
+		// Destroy flagged sections
+		for (auto& SectionIndex : BatchUpdateData->DestroySections)
+		{
+			DestroySection_RenderThread(SectionIndex);
+		}
+
+		// Create new sections
+		for (auto& SectionToCreate : BatchUpdateData->CreateSections)
+		{
+			CreateSection_RenderThread(static_cast<FRuntimeMeshSectionCreateDataInterface*>(SectionToCreate));
+		}
+
+		// Update sections
+		for (auto& SectionToUpdate : BatchUpdateData->UpdateSections)
+		{
+			UpdateSection_RenderThread(SectionToUpdate);
+		}		
+
+		// Apply section property updates
+		for (auto& SectionToUpdate : BatchUpdateData->PropertyUpdateSections)
+		{
+			UpdateSectionProperties_RenderThread(SectionToUpdate);
+		}
+
+		delete BatchUpdateData;
+
+	}
+
+
+	bool HasDynamicSections() const
+	{
+		for (FRuntimeMeshSectionProxyInterface* Section : Sections)
+		{
+			if (Section && !Section->WantsToRenderInStaticPath())
+			{
+				return true;
+			}
+		}
+		return false;
+	}
+
+	bool HasStaticSections() const 
+	{
+		for (FRuntimeMeshSectionProxyInterface* Section : Sections)
+		{
+			if (Section && Section->WantsToRenderInStaticPath())
+			{
+				return true;
+			}
+		}
+		return false;
+	}
+
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 11
+	virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override
+#else
+	virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) override
+#endif
+	{
+		FPrimitiveViewRelevance Result;
+		Result.bDrawRelevance = IsShown(View);
+		Result.bShadowRelevance = IsShadowCast(View);
+
+		bool bForceDynamicPath = IsRichView(*View->Family) || View->Family->EngineShowFlags.Wireframe || IsSelected() || !IsStaticPathAvailable();
+		Result.bStaticRelevance = !bForceDynamicPath && HasStaticSections();
+		Result.bDynamicRelevance =  bForceDynamicPath || HasDynamicSections();
+		
+		Result.bRenderInMainPass = ShouldRenderInMainPass();
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 11
+		Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask();
+#endif
+		Result.bRenderCustomDepth = ShouldRenderCustomDepth();
+		MaterialRelevance.SetPrimitiveViewRelevance(Result);
+		return Result;
+	}
+
+	void CreateMeshBatch(FMeshBatch& MeshBatch, FRuntimeMeshSectionProxyInterface* Section, FMaterialRenderProxy* WireframeMaterial) const
+	{
+		Section->CreateMeshBatch(MeshBatch, WireframeMaterial, IsSelected());
+
+		MeshBatch.ReverseCulling = IsLocalToWorldDeterminantNegative();
+		MeshBatch.bCanApplyViewModeOverrides = true;
+		
+		FMeshBatchElement& BatchElement = MeshBatch.Elements[0];
+		BatchElement.PrimitiveUniformBufferResource = &GetUniformBuffer();
+	}
+	
+	virtual void DrawStaticElements(FStaticPrimitiveDrawInterface* PDI) override
+	{
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_DrawStaticElements);
+
+		for (FRuntimeMeshSectionProxyInterface* Section : Sections)
+		{
+			if (Section && Section->ShouldRender() && Section->WantsToRenderInStaticPath())
+			{
+				FMeshBatch MeshBatch;
+				CreateMeshBatch(MeshBatch, Section, nullptr);
+				PDI->DrawMesh(MeshBatch, FLT_MAX);
+			}
+		}
+	}
+
+	virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
+	{
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_GetDynamicMeshElements);
+
+		// Set up wireframe material (if needed)
+		const bool bWireframe = AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe;
+
+		FColoredMaterialRenderProxy* WireframeMaterialInstance = nullptr;
+		if (bWireframe)
+		{
+			WireframeMaterialInstance = new FColoredMaterialRenderProxy(
+				GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy(IsSelected()) : nullptr,
+				FLinearColor(0, 0.5f, 1.f)
+				);
+
+			Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance);
+		}
+
+		// Iterate over sections
+		for (FRuntimeMeshSectionProxyInterface* Section : Sections)
+		{
+			if (Section && Section->ShouldRender())
+			{
+				// Add the mesh batch to every view it's visible in
+				for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
+				{
+					if (VisibilityMap & (1 << ViewIndex))
+					{
+						bool bForceDynamicPath = IsRichView(*Views[ViewIndex]->Family) || Views[ViewIndex]->Family->EngineShowFlags.Wireframe || IsSelected() || !IsStaticPathAvailable();
+
+						if (bForceDynamicPath || !Section->WantsToRenderInStaticPath())
+						{
+							FMeshBatch& MeshBatch = Collector.AllocateMesh();
+							CreateMeshBatch(MeshBatch, Section, WireframeMaterialInstance);
+
+							Collector.AddMesh(ViewIndex, MeshBatch);
+						}
+					}
+				}
+			}			
+		}
+
+		// Draw bounds
+#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
+		for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
+		{
+			if (VisibilityMap & (1 << ViewIndex))
+			{				
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 13
+				// Draw simple collision as wireframe if 'show collision', and collision is enabled, and we are not using the complex as the simple
+				if (ViewFamily.EngineShowFlags.Collision && IsCollisionEnabled() && BodySetup->GetCollisionTraceFlag() != ECollisionTraceFlag::CTF_UseComplexAsSimple)
+				{
+					FTransform GeomTransform(GetLocalToWorld());
+					BodySetup->AggGeom.GetAggGeom(GeomTransform, GetSelectionColor(FColor(157, 149, 223, 255), IsSelected(), IsHovered()).ToFColor(true), NULL, false, false, UseEditorDepthTest(), ViewIndex, Collector);
+				}
+#endif
+
+				// Render bounds
+				RenderBounds(Collector.GetPDI(ViewIndex), ViewFamily.EngineShowFlags, GetBounds(), IsSelected());
+			}
+		}
+#endif
+	}
+
+
+	virtual bool CanBeOccluded() const override
+	{
+		return !MaterialRelevance.bDisableDepthTest;
+	}
+
+	virtual uint32 GetMemoryFootprint(void) const
+	{
+		return(sizeof(*this) + GetAllocatedSize());
+	}
+
+	uint32 GetAllocatedSize(void) const
+	{
+		return(FPrimitiveSceneProxy::GetAllocatedSize());
+	}
+
+	void UpdateMaterialRelevance()
+	{
+		FMaterialRelevance NewMaterialRelevance;
+		for (FRuntimeMeshSectionProxyInterface* Section : Sections)
+		{
+			if (Section)
+			{
+				NewMaterialRelevance |= Section->GetMaterialRelevance();
+			}
+		}
+		MaterialRelevance = NewMaterialRelevance;
+	}
+
+private:
+	/** Array of sections */
+	TArray<FRuntimeMeshSectionProxyInterface*> Sections;
+	UBodySetup* BodySetup;
+	FMaterialRelevance MaterialRelevance;
+};
+
+
+
+
+void FRuntimeMeshComponentPrePhysicsTickFunction::ExecuteTick( float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
+{
+	/* Ensure target still exists */
+
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 11
+	bool bIsValid = Target && !Target->IsPendingKillOrUnreachable();
+#else
+	bool bIsValid = Target && !Target->HasAnyFlags(RF_PendingKill | RF_Unreachable);
+#endif
+
+	if (bIsValid)
+	{
+		FScopeCycleCounterUObject ActorScope(Target);
+		Target->BakeCollision();
+	}
+}
+
+FString FRuntimeMeshComponentPrePhysicsTickFunction::DiagnosticMessage()
+{
+	return Target->GetFullName() + TEXT("[PrePhysicsTick]");
+}
+
+
+
+/* Helper for converting an array of FLinearColor to an array of FColors*/
+void ConvertLinearColorToFColor(const TArray<FLinearColor>& LinearColors, TArray<FColor>& Colors)
+{
+	Colors.SetNumUninitialized(LinearColors.Num());
+	for (int32 Index = 0; Index < LinearColors.Num(); Index++)
+	{
+		Colors[Index] = LinearColors[Index].ToFColor(false);
+	}
+}
+
+
+
+
+URuntimeMeshComponent::URuntimeMeshComponent(const FObjectInitializer& ObjectInitializer)
+	: Super(ObjectInitializer)
+	, bUseComplexAsSimpleCollision(true)
+	, bShouldSerializeMeshData(true)
+	, bCollisionDirty(true)
+	, CollisionMode(ERuntimeMeshCollisionCookingMode::CookingPerformance)
+{
+	// Setup the collision update ticker
+	PrePhysicsTick.TickGroup = TG_PrePhysics;
+	PrePhysicsTick.bCanEverTick = true;
+	PrePhysicsTick.bStartWithTickEnabled = true;
+
+	// Reset the batch state
+	BatchState.ResetBatch();
+
+	SetNetAddressable();
+}
+
+TSharedPtr<FRuntimeMeshSectionInterface> URuntimeMeshComponent::CreateOrResetSectionLegacyType(int32 SectionIndex, int32 NumUVChannels)
+{
+	if (NumUVChannels == 1)
+	{
+		return CreateOrResetSection<FRuntimeMeshSection<FRuntimeMeshVertexSimple>>(SectionIndex, false, true);
+	}
+	else if (NumUVChannels == 2)
+	{
+		return CreateOrResetSection<FRuntimeMeshSection<FRuntimeMeshVertexDualUV>>(SectionIndex, false, true);
+	}
+	else
+	{
+		check(false && "Legacy sections only support standard vertex formats wit 1 or 2 uv channels");
+		return nullptr;
+	}
+}
+
+
+void URuntimeMeshComponent::CreateSectionInternal(int32 SectionIndex, ESectionUpdateFlags UpdateFlags)
+{
+	RuntimeMeshSectionPtr Section = MeshSections[SectionIndex];
+	check(Section.IsValid());
+
+	// Update normal/tangents if requested...
+	if (!!(UpdateFlags & ESectionUpdateFlags::CalculateNormalTangent))
+	{
+		Section->GenerateNormalTangent();
+	}
+
+	// calculate tessellation if requested...
+	if (!!(UpdateFlags & ESectionUpdateFlags::CalculateTessellationIndices))
+	{
+		Section->GenerateTessellationIndices();
+	}
+
+	// Use the batch update if one is running
+	if (BatchState.IsBatchPending())
+	{
+		// Mark section created
+		BatchState.MarkSectionCreated(SectionIndex, Section->UpdateFrequency == EUpdateFrequency::Infrequent);
+
+		// Flag collision if this section affects it
+		if (Section->CollisionEnabled)
+		{
+			BatchState.MarkCollisionDirty();
+		}
+		
+		// Flag bounds update
+		BatchState.MarkBoundsDirty();
+
+		// bail since we don't update directly in this case.
+		return;
+	}
+
+	// Enqueue the RT command if we already have a SceneProxy
+	if (SceneProxy && Section->UpdateFrequency != EUpdateFrequency::Infrequent)
+	{
+		// Gather all needed update info
+		auto* SectionData = Section->GetSectionCreationData(GetScene(), GetSectionMaterial(SectionIndex));
+		SectionData->SetTargetSection(SectionIndex);
+
+		// Enqueue update on RT
+		ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
+			FRuntimeMeshSectionCreate,
+			FRuntimeMeshSceneProxy*, RuntimeMeshSceneProxy, (FRuntimeMeshSceneProxy*)SceneProxy,
+			FRuntimeMeshSectionCreateDataInterface*, SectionData, SectionData,
+			{
+				RuntimeMeshSceneProxy->CreateSection_RenderThread(SectionData);
+			}
+		);
+	}
+	else
+	{
+		// Mark the render state dirty so it's recreated when necessary.
+		MarkRenderStateDirty();
+	}
+
+	// Mark collision dirty so it's re-baked at the end of this frame
+	if (Section->CollisionEnabled)
+	{
+		MarkCollisionDirty();
+	}
+
+	// Update overall bounds
+	UpdateLocalBounds();
+
+}
+
+void URuntimeMeshComponent::UpdateSectionInternal(int32 SectionIndex, bool bHadVertexPositionsUpdate, bool bHadVertexUpdates, bool bHadIndexUpdates, bool bNeedsBoundsUpdate, ESectionUpdateFlags UpdateFlags)
+{
+	// Ensure that something was updated
+	check(bHadVertexPositionsUpdate || bHadVertexUpdates || bHadIndexUpdates || bNeedsBoundsUpdate);
+
+	check(SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid());	
+	RuntimeMeshSectionPtr Section = MeshSections[SectionIndex];
+	
+	// Update normal/tangents if requested...
+	if (!!(UpdateFlags & ESectionUpdateFlags::CalculateNormalTangent))
+	{
+		Section->GenerateNormalTangent();
+	}
+
+	// calculate tessellation if requested...
+	if (!!(UpdateFlags & ESectionUpdateFlags::CalculateTessellationIndices))
+	{
+		Section->GenerateTessellationIndices();
+	}
+
+	/* Make sure this is only flagged if the section is dual buffer */
+	bHadVertexPositionsUpdate = Section->IsDualBufferSection() && bHadVertexPositionsUpdate;
+	bool bNeedsCollisionUpdate = Section->CollisionEnabled && (bHadVertexPositionsUpdate || (!Section->IsDualBufferSection() && bHadVertexUpdates));
+	
+	// Use the batch update if one is running
+	if (BatchState.IsBatchPending())
+	{
+		// Mark update for section or promote to proxy recreate if static section
+		if (Section->UpdateFrequency == EUpdateFrequency::Infrequent)
+		{
+			BatchState.MarkRenderStateDirty();
+		}
+		else
+		{
+			ERuntimeMeshSectionBatchUpdateType UpdateType = ERuntimeMeshSectionBatchUpdateType::None;
+			UpdateType |= bHadVertexPositionsUpdate ? ERuntimeMeshSectionBatchUpdateType::PositionsUpdate : ERuntimeMeshSectionBatchUpdateType::None;
+			UpdateType |= bHadVertexUpdates ? ERuntimeMeshSectionBatchUpdateType::VerticesUpdate : ERuntimeMeshSectionBatchUpdateType::None;
+			UpdateType |= bHadIndexUpdates ? ERuntimeMeshSectionBatchUpdateType::IndicesUpdate : ERuntimeMeshSectionBatchUpdateType::None;
+
+			BatchState.MarkUpdateForSection(SectionIndex, UpdateType);
+		}
+
+		// Flag collision if this section affects it
+		if (bNeedsCollisionUpdate)
+		{
+			BatchState.MarkCollisionDirty();
+		}
+
+		// Flag bounds update if needed.
+		if (bNeedsBoundsUpdate)
+		{
+			BatchState.MarkBoundsDirty();
+		}
+
+		// bail since we don't update directly in this case.
+		return;
+	}
+
+
+	// Send the update to the render thread if the scene proxy exists
+	if (SceneProxy && Section->UpdateFrequency != EUpdateFrequency::Infrequent)
+	{
+		auto* SectionData = Section->GetSectionUpdateData(bHadVertexPositionsUpdate, bHadVertexUpdates, bHadIndexUpdates);
+		SectionData->SetTargetSection(SectionIndex);
+
+		// Enqueue update on RT
+		ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
+			FRuntimeMeshSectionUpdate,
+			FRuntimeMeshSceneProxy*, RuntimeMeshSceneProxy, (FRuntimeMeshSceneProxy*)SceneProxy,
+			FRuntimeMeshRenderThreadCommandInterface*, SectionData, SectionData,
+			{
+				RuntimeMeshSceneProxy->UpdateSection_RenderThread(SectionData);
+			}
+		);
+	}
+	else
+	{
+		// Mark the renderstate dirty so it's recreated when necessary.
+		MarkRenderStateDirty();
+	}
+
+	// Mark collision dirty so it's re-baked at the end of this frame
+	if (bNeedsCollisionUpdate)
+	{
+		MarkCollisionDirty();
+	}
+
+	// Update overall bounds if needed
+	if (bNeedsBoundsUpdate)
+	{
+		UpdateLocalBounds();
+	}
+}
+
+void URuntimeMeshComponent::UpdateSectionVertexPositionsInternal(int32 SectionIndex, bool bNeedsBoundsUpdate)
+{
+	check(SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid());
+	RuntimeMeshSectionPtr Section = MeshSections[SectionIndex];
+
+	if (SceneProxy)
+	{
+		auto SectionData = Section->GetSectionPositionUpdateData();
+		SectionData->SetTargetSection(SectionIndex);
+
+		// Enqueue command to modify render thread info
+		ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
+			FRuntimeMeshSectionPositionUpdate,
+			FRuntimeMeshSceneProxy*, RuntimeMeshSceneProxy, (FRuntimeMeshSceneProxy*)SceneProxy,
+			FRuntimeMeshRenderThreadCommandInterface*, SectionData, SectionData,
+			{
+				RuntimeMeshSceneProxy->UpdateSectionPositionOnly_RenderThread(SectionData);
+			}
+		);
+	}
+	else
+	{
+		MarkRenderStateDirty();
+	}
+
+	if (bNeedsBoundsUpdate)
+	{
+		UpdateLocalBounds();
+	}
+}
+
+void URuntimeMeshComponent::UpdateSectionPropertiesInternal(int32 SectionIndex, bool bUpdateRequiresProxyRecreateIfStatic)
+{
+	check(SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid());
+	RuntimeMeshSectionPtr Section = MeshSections[SectionIndex];
+
+	bool bRequiresRecreate = bUpdateRequiresProxyRecreateIfStatic && Section->UpdateFrequency == EUpdateFrequency::Infrequent;
+
+	// Use the batch update if one is running
+	if (BatchState.IsBatchPending())
+	{
+		if (bRequiresRecreate)
+		{
+			BatchState.MarkRenderStateDirty();
+		}
+		else
+		{
+			BatchState.MarkUpdateForSection(SectionIndex, ERuntimeMeshSectionBatchUpdateType::PropertyUpdate);
+		}
+
+		// bail since we don't update directly in this case.
+		return;
+	}
+
+
+
+	if (SceneProxy && !bRequiresRecreate)
+	{
+		auto SectionData = new FRuntimeMeshSectionPropertyUpdateData();
+		SectionData->SetTargetSection(SectionIndex);
+		SectionData->bIsVisible = Section->bIsVisible;
+		SectionData->bCastsShadow = Section->bCastsShadow;
+
+
+		// Enqueue command to modify render thread info
+		ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
+			FRuntimeMeshSectionPropertyUpdate,
+			FRuntimeMeshSceneProxy*, RuntimeMeshSceneProxy, (FRuntimeMeshSceneProxy*)SceneProxy,
+			FRuntimeMeshRenderThreadCommandInterface*, SectionData, SectionData,
+			{
+				RuntimeMeshSceneProxy->UpdateSectionProperties_RenderThread(SectionData);
+			}
+		);
+	}
+	else
+	{
+		MarkRenderStateDirty();
+	}
+}
+
+
+void URuntimeMeshComponent::UpdateMeshSectionPositionsImmediate(int32 SectionIndex, TArray<FVector>& VertexPositions, ESectionUpdateFlags UpdateFlags)
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateMeshSectionPositionsImmediate);
+
+	// Validate all update parameters
+	RMC_VALIDATE_UPDATEPARAMETERS_DUALBUFFER(SectionIndex, /*VoidReturn*/);
+
+	// Get section
+	RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
+
+	// Check dual buffer section status
+	if (VertexPositions.Num() != Section->PositionVertexBuffer.Num())
+	{
+		Log(TEXT("UpdateMeshSection() - Positions cannot change length unless the vertexdata is updated as well."), true);
+		return;
+	}
+
+	bool bShouldUseMove = (UpdateFlags & ESectionUpdateFlags::MoveArrays) != ESectionUpdateFlags::None;
+	bool bNeedsBoundsUpdate = false;
+
+	// Update vertex positions if supplied
+	bool bUpdatedVertexPositions = false;
+	if (VertexPositions.Num() > 0)
+	{
+		bNeedsBoundsUpdate = Section->UpdateVertexPositionBuffer(VertexPositions, nullptr, bShouldUseMove);
+		bUpdatedVertexPositions = true;
+	}
+	else
+	{
+		Log(TEXT("UpdatemeshSection() - Vertex positions empty. They will not be updated."));
+	}
+
+	// Finalize section update if we have anything to apply
+	if (bUpdatedVertexPositions)
+	{
+		UpdateSectionVertexPositionsInternal(SectionIndex, bNeedsBoundsUpdate);
+	}
+}
+
+void URuntimeMeshComponent::UpdateMeshSectionPositionsImmediate(int32 SectionIndex, TArray<FVector>& VertexPositions, const FBox& BoundingBox, ESectionUpdateFlags UpdateFlags)
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateMeshSectionPositionsImmediate_WithBoundinBox);
+
+	// Validate all update parameters
+	RMC_VALIDATE_UPDATEPARAMETERS_DUALBUFFER(SectionIndex, /*VoidReturn*/);
+
+	// Get section
+	RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
+
+	// Check dual buffer section status
+	if (VertexPositions.Num() != Section->PositionVertexBuffer.Num())
+	{
+		Log(TEXT("UpdateMeshSection() - Positions cannot change length unless the vertexdata is updated as well."), true);
+		return;
+	}
+
+	bool bShouldUseMove = (UpdateFlags & ESectionUpdateFlags::MoveArrays) != ESectionUpdateFlags::None;
+	bool bNeedsBoundsUpdate = false;
+
+	// Update vertex positions if supplied
+	bool bUpdatedVertexPositions = false;
+	if (VertexPositions.Num() > 0)
+	{
+		bNeedsBoundsUpdate = Section->UpdateVertexPositionBuffer(VertexPositions, &BoundingBox, bShouldUseMove);
+		bUpdatedVertexPositions = true;
+	}
+	else
+	{
+		Log(TEXT("UpdatemeshSection() - Vertex positions empty. They will not be updated."));
+	}
+
+	// Finalize section update if we have anything to apply
+	if (bUpdatedVertexPositions)
+	{
+		UpdateSectionVertexPositionsInternal(SectionIndex, bNeedsBoundsUpdate);
+	}
+}
+
+
+TArray<FVector>* URuntimeMeshComponent::BeginMeshSectionPositionUpdate(int32 SectionIndex)
+{
+	// Validate all update parameters
+	RMC_VALIDATE_UPDATEPARAMETERS_DUALBUFFER(SectionIndex, nullptr);
+
+	// Get section
+	RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
+	
+	return &Section->PositionVertexBuffer;
+}
+
+void URuntimeMeshComponent::EndMeshSectionPositionUpdate(int32 SectionIndex)
+{
+	// Validate all update parameters
+	RMC_VALIDATE_UPDATEPARAMETERS_DUALBUFFER(SectionIndex, /*VoidReturn*/);
+	
+	// TODO: Validate that the position buffer is still the same length
+
+	UpdateSectionVertexPositionsInternal(SectionIndex, true);
+}
+
+void URuntimeMeshComponent::EndMeshSectionPositionUpdate(int32 SectionIndex, const FBox& BoundingBox)
+{
+	// Validate all update parameters
+	RMC_VALIDATE_UPDATEPARAMETERS_DUALBUFFER(SectionIndex, /*VoidReturn*/);
+
+	RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
+	
+	bool bNeedsBoundingBoxUpdate = !(Section->LocalBoundingBox == BoundingBox);
+	if (bNeedsBoundingBoxUpdate)
+	{
+		Section->LocalBoundingBox = BoundingBox;
+	}
+	
+	// TODO: Validate that the position buffer is still the same length
+
+	UpdateSectionVertexPositionsInternal(SectionIndex, bNeedsBoundingBoxUpdate);
+}
+
+
+
+
+void URuntimeMeshComponent::EndMeshSectionUpdate(int32 SectionIndex, ERuntimeMeshBuffer UpdatedBuffers, ESectionUpdateFlags UpdateFlags)
+{
+	// Bail if we have no buffers to update.
+	if (UpdatedBuffers == ERuntimeMeshBuffer::None)
+	{
+		return;
+	}
+
+	// Validate all update parameters
+	RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, /*VoidReturn*/);
+
+	// Get section and update bounding box
+	RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
+	Section->RecalculateBoundingBox();
+
+	// Finalize section update
+	UpdateSectionInternal(SectionIndex, 
+		!!(UpdatedBuffers & ERuntimeMeshBuffer::Positions), 
+		!!(UpdatedBuffers & ERuntimeMeshBuffer::Vertices), 
+		!!(UpdatedBuffers & ERuntimeMeshBuffer::Triangles), true, UpdateFlags);
+}
+
+
+void URuntimeMeshComponent::EndMeshSectionUpdate(int32 SectionIndex, ERuntimeMeshBuffer UpdatedBuffers, const FBox& BoundingBox, ESectionUpdateFlags UpdateFlags)
+{
+	// Bail if we have no buffers to update.
+	if (UpdatedBuffers == ERuntimeMeshBuffer::None)
+	{
+		return;
+	}
+
+	// Validate all update parameters
+	RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, /*VoidReturn*/);
+
+	// Get section and update bounding box
+	RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
+	Section->LocalBoundingBox = BoundingBox;
+
+	// Finalize section update
+	UpdateSectionInternal(SectionIndex,
+		!!(UpdatedBuffers & ERuntimeMeshBuffer::Positions),
+		!!(UpdatedBuffers & ERuntimeMeshBuffer::Vertices),
+		!!(UpdatedBuffers & ERuntimeMeshBuffer::Triangles), true, UpdateFlags);
+}
+
+
+
+void URuntimeMeshComponent::CreateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals,
+	const TArray<FVector2D>& UV0, const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents, bool bCreateCollision,	EUpdateFrequency UpdateFrequency, 
+	ESectionUpdateFlags UpdateFlags)
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_CreateMeshSection);
+
+	// Validate all creation parameters
+	RMC_VALIDATE_CREATIONPARAMETERS(SectionIndex, Vertices, Triangles, /*VoidReturn*/);
+
+	// Create the section
+	auto NewSection = CreateOrResetSectionLegacyType(SectionIndex, 1);
+
+	// Update the mesh data in the section
+	NewSection->UpdateVertexBufferInternal(Vertices, Normals, Tangents, UV0, TArray<FVector2D>(), Colors);
+
+	TArray<int32>& TrianglesRef = const_cast<TArray<int32>&>(Triangles);
+	NewSection->UpdateIndexBuffer(TrianglesRef, false);
+
+	// Track collision status and update collision information if necessary
+	NewSection->CollisionEnabled = bCreateCollision;
+	NewSection->UpdateFrequency = UpdateFrequency;
+
+	// Finalize section.
+	CreateSectionInternal(SectionIndex, UpdateFlags);
+}
+
+void URuntimeMeshComponent::CreateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals,
+	const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents,
+	bool bCreateCollision, EUpdateFrequency UpdateFrequency, ESectionUpdateFlags UpdateFlags)
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_CreateMeshSection_DualUV);
+
+	// Validate all creation parameters
+	RMC_VALIDATE_CREATIONPARAMETERS(SectionIndex, Vertices, Triangles, /*VoidReturn*/);
+
+	// Create the section
+	auto NewSection = CreateOrResetSectionLegacyType(SectionIndex, 2);
+
+	// Update the mesh data in the section
+	NewSection->UpdateVertexBufferInternal(Vertices, Normals, Tangents, UV0, UV1, Colors);
+
+	TArray<int32>& TrianglesRef = const_cast<TArray<int32>&>(Triangles);
+	NewSection->UpdateIndexBuffer(TrianglesRef, false);
+
+	// Track collision status and update collision information if necessary
+	NewSection->CollisionEnabled = bCreateCollision;
+	NewSection->UpdateFrequency = UpdateFrequency;
+
+	// Finalize section.
+	CreateSectionInternal(SectionIndex, UpdateFlags);
+}
+
+
+
+void URuntimeMeshComponent::UpdateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<FVector>& Normals, const TArray<FVector2D>& UV0,
+	const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents, ESectionUpdateFlags UpdateFlags)
+{
+	UpdateMeshSection(SectionIndex, Vertices, TArray<int32>(), Normals, UV0, Colors, Tangents, UpdateFlags);
+}
+
+void URuntimeMeshComponent::UpdateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<FVector>& Normals, const TArray<FVector2D>& UV0,
+	const TArray<FVector2D>& UV1, const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents, ESectionUpdateFlags UpdateFlags)
+{
+	UpdateMeshSection(SectionIndex, Vertices, TArray<int32>(), Normals, UV0, UV1, Colors, Tangents, UpdateFlags);
+}
+
+void URuntimeMeshComponent::UpdateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals,
+	const TArray<FVector2D>& UV0, const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents, ESectionUpdateFlags UpdateFlags)
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateMeshSection);
+
+	// Validate all update parameters
+	RMC_VALIDATE_UPDATEPARAMETERS_INTERNALSECTION(SectionIndex, /*VoidReturn*/);
+
+	// Validate section type
+	MeshSections[SectionIndex]->GetVertexType()->EnsureEquals<FRuntimeMeshVertexSimple>();
+
+	// Get section
+	RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
+	
+	// Tell the section to update the vertex buffer
+	bool bHadVertexUpdates = Section->UpdateVertexBufferInternal(Vertices, Normals, Tangents, UV0, TArray<FVector2D>(), Colors);
+
+	bool bHadTriangleUpdates = Triangles.Num() > 0;
+	if (bHadTriangleUpdates)
+	{
+		TArray<int32>& TrianglesRef = const_cast<TArray<int32>&>(Triangles);
+
+		Section->UpdateIndexBuffer(TrianglesRef, false);
+	}
+
+	UpdateSectionInternal(SectionIndex, false, bHadVertexUpdates, bHadTriangleUpdates, true, UpdateFlags);
+}
+
+void URuntimeMeshComponent::UpdateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals,
+	const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents, ESectionUpdateFlags UpdateFlags)
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateMeshSection_DualUV);
+
+	// Validate all update parameters
+	RMC_VALIDATE_UPDATEPARAMETERS_INTERNALSECTION(SectionIndex, /*VoidReturn*/);
+
+	// Validate section type
+	MeshSections[SectionIndex]->GetVertexType()->EnsureEquals<FRuntimeMeshVertexDualUV>();
+
+	// Get section
+	RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
+	
+	// Tell the section to update the vertex buffer
+	bool bHadVertexUpdates = Section->UpdateVertexBufferInternal(Vertices, Normals, Tangents, UV0, UV1, Colors);
+
+	bool bHadTriangleUpdates = Triangles.Num() > 0;
+	if (bHadTriangleUpdates)
+	{
+		TArray<int32>& TrianglesRef = const_cast<TArray<int32>&>(Triangles);
+
+		Section->UpdateIndexBuffer(TrianglesRef, false);
+	}
+
+	UpdateSectionInternal(SectionIndex, false, bHadVertexUpdates, bHadTriangleUpdates, true, UpdateFlags);
+}
+
+
+void URuntimeMeshComponent::CreateMeshSection_Blueprint(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals, const TArray<FRuntimeMeshTangent>& Tangents,
+	const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, const TArray<FLinearColor>& VertexColors, bool bCreateCollision, bool bCalculateNormalTangent, bool bGenerateTessellationTriangles, EUpdateFrequency UpdateFrequency)
+{	
+	// Convert vertex colors to FColor
+	TArray<FColor> Colors;
+	ConvertLinearColorToFColor(VertexColors, Colors);
+
+	ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None;
+	UpdateFlags |= bCalculateNormalTangent ? ESectionUpdateFlags::CalculateNormalTangent : ESectionUpdateFlags::None;
+	UpdateFlags |= bGenerateTessellationTriangles ? ESectionUpdateFlags::CalculateTessellationIndices : ESectionUpdateFlags::None;
+
+	// Create section
+	CreateMeshSection(SectionIndex, Vertices, Triangles, Normals, UV0, UV1, Colors, Tangents, bCreateCollision, UpdateFrequency, UpdateFlags);
+}
+
+void URuntimeMeshComponent::UpdateMeshSection_Blueprint(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals, const TArray<FRuntimeMeshTangent>& Tangents,
+	const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, const TArray<FLinearColor>& VertexColors, bool bCalculateNormalTangent, bool bGenerateTessellationTriangles)
+{
+	// Convert vertex colors to FColor
+	TArray<FColor> Colors;
+	ConvertLinearColorToFColor(VertexColors, Colors);
+
+	ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None;
+	UpdateFlags |= bCalculateNormalTangent ? ESectionUpdateFlags::CalculateNormalTangent : ESectionUpdateFlags::None;
+	UpdateFlags |= bGenerateTessellationTriangles ? ESectionUpdateFlags::CalculateTessellationIndices : ESectionUpdateFlags::None;
+
+	// Update section
+	UpdateMeshSection(SectionIndex, Vertices, Triangles, Normals, UV0, UV1, Colors, Tangents, UpdateFlags);
+}
+
+
+
+void URuntimeMeshComponent::CreateMeshSection(int32 SectionIndex, IRuntimeMeshVerticesBuilder& Vertices, FRuntimeMeshIndicesBuilder& Triangles, bool bCreateCollision,
+	EUpdateFrequency UpdateFrequency, ESectionUpdateFlags UpdateFlags)
+{
+	RMC_CHECKINGAME_LOGINEDITOR((SectionIndex >= 0), "SectionIndex cannot be negative.", /**/);
+	RMC_CHECKINGAME_LOGINEDITOR((Vertices.Length() > 0), "Vertices length must not be 0.", /**/);
+	RMC_CHECKINGAME_LOGINEDITOR((Triangles.Length() > 0), "Triangles length must not be 0", /**/);
+
+	// First we need to create the new section
+	TSharedPtr<FRuntimeMeshSectionInterface> Section = MakeShareable(Vertices.GetVertexType()->CreateSection(Vertices.WantsSeparatePositionBuffer()));
+
+	// Ensure sections array is long enough
+	if (SectionIndex >= MeshSections.Num())
+	{
+		MeshSections.SetNum(SectionIndex + 1, false);
+	}
+
+	// Set the new section
+	MeshSections[SectionIndex] = Section;
+
+	// Set vertex/index buffers
+	Section->UpdateVertexBuffer(Vertices, nullptr, !!(UpdateFlags & ESectionUpdateFlags::MoveArrays));
+	Section->UpdateIndexBuffer(Triangles, !!(UpdateFlags & ESectionUpdateFlags::MoveArrays));
+	
+
+	// Track collision status and update collision information if necessary
+	Section->CollisionEnabled = bCreateCollision;
+	Section->UpdateFrequency = UpdateFrequency;
+
+	// Finalize section.
+	CreateSectionInternal(SectionIndex, UpdateFlags);
+}
+
+void URuntimeMeshComponent::UpdateMeshSection(int32 SectionIndex, IRuntimeMeshVerticesBuilder& Vertices, FRuntimeMeshIndicesBuilder& Triangles, ESectionUpdateFlags UpdateFlags)
+{	
+	// Validate all update parameters
+	RMC_VALIDATE_UPDATEPARAMETERS_INTERNALSECTION(SectionIndex, /*VoidReturn*/);
+
+	// Get section
+	RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
+
+	// Set vertex/index buffers
+	Section->UpdateVertexBuffer(Vertices, nullptr, !!(UpdateFlags & ESectionUpdateFlags::MoveArrays));
+	Section->UpdateIndexBuffer(Triangles, !!(UpdateFlags & ESectionUpdateFlags::MoveArrays));
+
+	UpdateSectionInternal(SectionIndex, Vertices.WantsSeparatePositionBuffer(), true, true, true, UpdateFlags);
+}
+
+
+
+void URuntimeMeshComponent::ClearMeshSection(int32 SectionIndex)
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_ClearMeshSection);
+
+ 	if (SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid())
+ 	{
+		// Did this section have collision
+		bool HadCollision = MeshSections[SectionIndex]->CollisionEnabled;
+		bool bWasStaticSection = MeshSections[SectionIndex]->UpdateFrequency == EUpdateFrequency::Infrequent;
+
+		// Clear the section
+		MeshSections[SectionIndex].Reset();
+		
+		// Use the batch update if one is running
+		if (BatchState.IsBatchPending())
+		{
+			// Mark section created
+			BatchState.MarkSectionDestroyed(SectionIndex, bWasStaticSection);
+
+			// Flag collision if this section affects it
+			if (HadCollision)
+			{
+				BatchState.MarkCollisionDirty();
+			}
+
+			// Flag bounds update
+			BatchState.MarkBoundsDirty();
+			
+			// bail since we don't update directly in this case.
+			return;
+		}
+
+
+		if (SceneProxy && !bWasStaticSection)
+		{			
+			// Enqueue update on RT
+			ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
+				FRuntimeMeshSectionUpdate,
+				FRuntimeMeshSceneProxy*, RuntimeMeshSceneProxy, (FRuntimeMeshSceneProxy*)SceneProxy,
+				int32, SectionIndex, SectionIndex,
+				{
+					RuntimeMeshSceneProxy->DestroySection_RenderThread(SectionIndex);
+				}
+			);
+		}
+		else
+		{
+			MarkRenderStateDirty();
+		}
+
+		// Update our collision info only if this section had any influence on it
+		if (HadCollision)
+		{
+			MarkCollisionDirty();
+		}
+		
+		UpdateLocalBounds();
+
+ 	}
+}
+
+void URuntimeMeshComponent::ClearAllMeshSections()
+{
+ 	MeshSections.Empty();
+
+	// Use the batch update if one is running
+	if (BatchState.IsBatchPending())
+	{
+		// Mark render state dirty
+		BatchState.MarkRenderStateDirty();
+
+		// Flag collision
+		BatchState.MarkCollisionDirty();
+
+		// Flag bounds update
+		BatchState.MarkBoundsDirty();
+
+		// bail since we don't update directly in this case.
+		return;
+	}
+	
+ 	MarkRenderStateDirty();
+	MarkCollisionDirty();
+	UpdateLocalBounds();
+}
+
+void URuntimeMeshComponent::SetSectionTessellationTriangles(int32 SectionIndex, const TArray<int32>& TessellationTriangles, bool bShouldMoveArray)
+{
+	// Validate all update parameters
+	RMC_VALIDATE_UPDATEPARAMETERS_INTERNALSECTION(SectionIndex, /*VoidReturn*/);
+	
+	// Get section
+	RuntimeMeshSectionPtr& Section = MeshSections[SectionIndex];
+
+	// Tell the section to update the tessellation index buffer
+	Section->UpdateTessellationIndexBuffer(const_cast<TArray<int32>&>(TessellationTriangles), bShouldMoveArray);
+
+	UpdateSectionInternal(SectionIndex, false, false, true, false, ESectionUpdateFlags::None);
+}
+
+
+
+
+bool URuntimeMeshComponent::GetSectionBoundingBox(int32 SectionIndex, FBox& OutBoundingBox)
+{
+	if (SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid())
+	{
+		OutBoundingBox = MeshSections[SectionIndex]->LocalBoundingBox;
+		return true;
+	}
+	return false;
+}
+
+void URuntimeMeshComponent::SetMeshSectionVisible(int32 SectionIndex, bool bNewVisibility)
+{
+ 	if (SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid())
+ 	{
+ 		// Set game thread state
+ 		MeshSections[SectionIndex]->bIsVisible = bNewVisibility;
+
+		// Finish the update
+		UpdateSectionPropertiesInternal(SectionIndex, false);
+ 	}
+}
+
+bool URuntimeMeshComponent::IsMeshSectionVisible(int32 SectionIndex) const
+{
+	return SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid() && MeshSections[SectionIndex]->bIsVisible;
+}
+
+void URuntimeMeshComponent::SetMeshSectionCastsShadow(int32 SectionIndex, bool bNewCastsShadow)
+{
+	if (SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid())
+	{
+		// Set game thread state
+		MeshSections[SectionIndex]->bCastsShadow = bNewCastsShadow;
+
+		// Finish the update
+		UpdateSectionPropertiesInternal(SectionIndex, true);
+	}
+}
+
+bool URuntimeMeshComponent::IsMeshSectionCastingShadows(int32 SectionIndex) const
+{
+	return SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid() && MeshSections[SectionIndex]->bCastsShadow;
+}
+
+void URuntimeMeshComponent::SetMeshSectionCollisionEnabled(int32 SectionIndex, bool bNewCollisionEnabled)
+{
+	if (SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid())
+	{
+		auto& Section = MeshSections[SectionIndex];
+		if (Section->CollisionEnabled != bNewCollisionEnabled)
+		{
+			Section->CollisionEnabled = bNewCollisionEnabled;
+			
+			// Use the batch update if one is running
+			if (BatchState.IsBatchPending())
+			{
+				// Mark render state dirty
+				BatchState.MarkCollisionDirty();
+			}
+			else
+			{
+				MarkCollisionDirty();
+			}
+		}
+	}
+}
+
+bool URuntimeMeshComponent::IsMeshSectionCollisionEnabled(int32 SectionIndex)
+{
+	return SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid() && MeshSections[SectionIndex]->CollisionEnabled;
+}
+
+
+
+int32 URuntimeMeshComponent::GetNumSections() const
+{
+	int32 SectionCount = 0;
+	for (int32 Index = 0; Index < MeshSections.Num(); Index++)
+	{
+		if (MeshSections[Index].IsValid())
+		{
+			SectionCount++;
+		}
+	}
+
+	return SectionCount;
+}
+
+bool URuntimeMeshComponent::DoesSectionExist(int32 SectionIndex) const
+{
+	return SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid();
+}
+
+int32 URuntimeMeshComponent::FirstAvailableMeshSectionIndex() const
+{
+	for (int32 Index = 0; Index < MeshSections.Num(); Index++)
+	{
+		if (!MeshSections[Index].IsValid())
+		{
+			return Index;
+		}
+	}
+	return MeshSections.Num();
+}
+
+int32 URuntimeMeshComponent::GetLastSectionIndex() const
+{
+	for (int32 Index = MeshSections.Num() - 1; Index >= 0; Index--)
+	{
+		if (MeshSections[Index].IsValid())
+		{
+			return Index;
+		}
+	}
+
+	return -1;
+}
+
+
+void URuntimeMeshComponent::SetMeshCollisionSection(int32 CollisionSectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles)
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_SetMeshCollisionSection);
+
+	if (MeshCollisionSections.Num() <= CollisionSectionIndex)
+	{
+		MeshCollisionSections.SetNum(CollisionSectionIndex + 1, false);
+	}
+
+	auto& Section = MeshCollisionSections[CollisionSectionIndex];
+	Section.VertexBuffer = Vertices;
+	Section.IndexBuffer = Triangles;
+
+	// Use the batch update if one is running
+	if (BatchState.IsBatchPending())
+	{
+		// Mark render state dirty
+		BatchState.MarkCollisionDirty();
+	}
+	else
+	{
+		MarkCollisionDirty();
+	}
+}
+
+void URuntimeMeshComponent::ClearMeshCollisionSection(int32 CollisionSectionIndex)
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_ClearMeshCollisionSection);
+
+	if (MeshCollisionSections.Num() <= CollisionSectionIndex)
+		return;
+
+	MeshCollisionSections[CollisionSectionIndex].Reset();
+
+	// Use the batch update if one is running
+	if (BatchState.IsBatchPending())
+	{
+		// Mark render state dirty
+		BatchState.MarkCollisionDirty();
+	}
+	else
+	{
+		MarkCollisionDirty();
+	}
+}
+
+void URuntimeMeshComponent::ClearAllMeshCollisionSections()
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_ClearAllMeshCollisionSections);
+
+	MeshCollisionSections.Empty();
+
+	// Use the batch update if one is running
+	if (BatchState.IsBatchPending())
+	{
+		// Mark render state dirty
+		BatchState.MarkCollisionDirty();
+	}
+	else
+	{
+		MarkCollisionDirty();
+	}
+}
+
+
+void URuntimeMeshComponent::AddCollisionConvexMesh(TArray<FVector> ConvexVerts)
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_AddCollisionConvexMesh);
+
+	if (ConvexVerts.Num() >= 4)
+	{
+		FRuntimeConvexCollisionSection ConvexSection;
+		ConvexSection.VertexBuffer = ConvexVerts;
+		ConvexSection.BoundingBox = FBox(ConvexVerts);
+		ConvexCollisionSections.Add(ConvexSection);
+		
+
+		// Use the batch update if one is running
+		if (BatchState.IsBatchPending())
+		{
+			// Mark render state dirty
+			BatchState.MarkCollisionDirty();
+		}
+		else
+		{
+			MarkCollisionDirty();
+		}
+	}
+}
+
+void URuntimeMeshComponent::ClearCollisionConvexMeshes()
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_ClearCollisionConvexMeshes);
+
+	// Empty simple collision info
+	ConvexCollisionSections.Empty();
+
+
+	// Use the batch update if one is running
+	if (BatchState.IsBatchPending())
+	{
+		// Mark render state dirty
+		BatchState.MarkCollisionDirty();
+	}
+	else
+	{
+		MarkCollisionDirty();
+	}
+}
+
+void URuntimeMeshComponent::SetCollisionConvexMeshes(const TArray< TArray<FVector> >& ConvexMeshes)
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_SetCollisionConvexMeshes);
+
+	ConvexCollisionSections.Empty(ConvexMeshes.Num());
+
+	// Create element for each convex mesh
+	for (int32 ConvexIndex = 0; ConvexIndex < ConvexMeshes.Num(); ConvexIndex++)
+	{
+		FRuntimeConvexCollisionSection ConvexSection;
+		ConvexSection.VertexBuffer = ConvexMeshes[ConvexIndex];
+		ConvexSection.BoundingBox = FBox(ConvexSection.VertexBuffer);
+		ConvexCollisionSections.Add(ConvexSection);
+	}
+
+
+	// Use the batch update if one is running
+	if (BatchState.IsBatchPending())
+	{
+		// Mark render state dirty
+		BatchState.MarkCollisionDirty();
+	}
+	else
+	{
+		MarkCollisionDirty();
+	}
+}
+
+
+void URuntimeMeshComponent::UpdateLocalBounds(bool bMarkRenderTransform)
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateLocalBounds);
+	
+	FBox LocalBox(EForceInit::ForceInitToZero);
+
+	for (const RuntimeMeshSectionPtr& Section : MeshSections)
+	{
+		if (Section.IsValid() && Section->bIsVisible)
+		{
+			LocalBox += Section->LocalBoundingBox;
+		}
+	}
+
+	LocalBounds = LocalBox.IsValid ? FBoxSphereBounds(LocalBox) : FBoxSphereBounds(FVector(0, 0, 0), FVector(0, 0, 0), 0); // fallback to reset box sphere bounds
+
+	// Update global bounds
+	UpdateBounds();
+
+	if (bMarkRenderTransform)
+	{
+		// Need to send to render thread
+		MarkRenderTransformDirty();
+	}
+}
+
+FPrimitiveSceneProxy* URuntimeMeshComponent::CreateSceneProxy()
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_CreateSceneProxy);
+
+	return new FRuntimeMeshSceneProxy(this);
+}
+
+int32 URuntimeMeshComponent::GetNumMaterials() const
+{
+	return MeshSections.Num();
+}
+
+FBoxSphereBounds URuntimeMeshComponent::CalcBounds(const FTransform& LocalToWorld) const
+{
+	return LocalBounds.TransformBy(LocalToWorld);
+}
+
+
+
+
+
+void URuntimeMeshComponent::EndBatchUpdates()
+{
+	// Bail if we have no pending updates
+	if (!BatchState.IsBatchPending())
+		return;
+
+	// Handle all pending rendering updates..
+	if (BatchState.RequiresSceneProxyRecreate())
+	{
+		MarkRenderStateDirty();
+	}
+	else
+	{
+		auto* BatchUpdateData = new FRuntimeMeshBatchUpdateData;
+
+		for (int32 Index = 0; Index <= BatchState.GetMaxSection(); Index++)
+		{
+			// Skip this section if it has no updates.
+			if (!BatchState.HasAnyFlagSet(Index))
+			{
+				continue;
+			}
+
+			// Check that we don't have both create and destroy flagged
+			check(!(BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::Create) && BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::Destroy)));
+
+			// Handle section created
+			if (BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::Create))
+			{
+				// Validate section exists
+				check(MeshSections.Num() >= Index && MeshSections[Index].IsValid());
+				
+				UMaterialInterface* Material = GetMaterial(Index);
+				if (Material == nullptr)
+				{
+					Material = UMaterial::GetDefaultMaterial(MD_Surface);
+				}
+
+				// Get the section create data and add it to the list
+				auto SectionCreateData = MeshSections[Index]->GetSectionCreationData(GetScene(), Material);
+				SectionCreateData->SetTargetSection(Index);
+
+				BatchUpdateData->CreateSections.Add(SectionCreateData);
+			}
+			// Handle destroy
+			else if (BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::Destroy))
+			{
+				BatchUpdateData->DestroySections.Add(Index);
+			}
+			// Handle vertex/index updates
+			else if (BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::VerticesUpdate) || BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::IndicesUpdate))
+			{
+				// Validate section exists
+				check(MeshSections.Num() >= Index && MeshSections[Index].IsValid());
+
+				// Get the section update data and add it to the list.
+				bool bHadPositionUpdates = BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::PositionsUpdate);
+				bool bHadVertexUpdates = BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::VerticesUpdate);
+				bool bHadIndexUpdates = BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::IndicesUpdate);
+				auto SectionUpdateData = MeshSections[Index]->GetSectionUpdateData(bHadPositionUpdates, bHadVertexUpdates, bHadIndexUpdates);
+				SectionUpdateData->SetTargetSection(Index);
+
+				BatchUpdateData->UpdateSections.Add(SectionUpdateData);
+			}
+			// Handle property updates
+			else if (BatchState.HasFlagSet(Index, ERuntimeMeshSectionBatchUpdateType::PropertyUpdate))
+			{
+				// Validate section exists
+				check(MeshSections.Num() >= Index && MeshSections[Index].IsValid());
+
+				auto SectionProperties = new(BatchUpdateData->PropertyUpdateSections) FRuntimeMeshSectionPropertyUpdateData;
+
+				auto& Section = MeshSections[Index];
+
+				SectionProperties->SetTargetSection(Index);
+				SectionProperties->bIsVisible = Section->bIsVisible;
+				SectionProperties->bCastsShadow = Section->bCastsShadow;
+			}
+			else
+			{
+				// Unknown update type.
+				checkNoEntry();
+			}
+		}
+
+
+
+		// Enqueue update on RT
+		ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
+			FRuntimeMeshBatchUpdateCommand,
+			FRuntimeMeshSceneProxy*, RuntimeMeshSceneProxy, (FRuntimeMeshSceneProxy*)SceneProxy,
+			FRuntimeMeshBatchUpdateData*, BatchUpdateData, BatchUpdateData,
+			{
+				RuntimeMeshSceneProxy->ApplyBatchUpdate_RenderThread(BatchUpdateData);
+			}
+		);
+
+
+	}
+
+	// Update collision if necessary
+	if (BatchState.RequiresCollisionUpdate())
+	{
+		MarkCollisionDirty();
+	}
+
+	// Update local bounds if necessary
+	if (BatchState.RequiresBoundsUpdate())
+	{
+		UpdateLocalBounds(!BatchState.RequiresSceneProxyRecreate());
+	}
+
+	// Clear batch info
+	BatchState.ResetBatch();
+}
+
+
+
+
+void URuntimeMeshComponent::GetSectionMesh(int32 SectionIndex, const IRuntimeMeshVerticesBuilder*& Vertices, const FRuntimeMeshIndicesBuilder*& Indices)
+{
+	RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, /*VoidReturn*/);
+
+	IRuntimeMeshVerticesBuilder* TempVertices;
+	FRuntimeMeshIndicesBuilder* TempIndices;
+
+	MeshSections[SectionIndex]->GetSectionMesh(TempVertices, TempIndices);
+
+	Vertices = TempVertices;
+	Indices = TempIndices;
+}
+
+void URuntimeMeshComponent::BeginMeshSectionUpdate(int32 SectionIndex, IRuntimeMeshVerticesBuilder*& Vertices, FRuntimeMeshIndicesBuilder*& Indices)
+{
+	RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, /*VoidReturn*/);
+
+	// Get mesh
+	MeshSections[SectionIndex]->GetSectionMesh(Vertices, Indices);
+}
+
+bool URuntimeMeshComponent::GetPhysicsTriMeshData(struct FTriMeshCollisionData* CollisionData, bool InUseAllTriData)
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_GetPhysicsTriMeshData);
+ 	int32 VertexBase = 0; // Base vertex index for current section
+ 
+	bool HadCollision = false;
+
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 13
+	// See if we should copy UVs
+	bool bCopyUVs = UPhysicsSettings::Get()->bSupportUVFromHitResults;
+	if (bCopyUVs)
+	{
+		CollisionData->UVs.AddZeroed(1); // only one UV channel
+	}
+#endif
+
+	// For each section..
+	for (int32 SectionIdx = 0; SectionIdx < MeshSections.Num(); SectionIdx++)
+	{ 
+		const RuntimeMeshSectionPtr& Section = MeshSections[SectionIdx];
+
+		if (Section.IsValid() && Section->CollisionEnabled)
+		{
+			// Copy vertex data
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 13
+			Section->GetCollisionInformation(CollisionData->Vertices, CollisionData->UVs, bCopyUVs);
+#else
+			Section->GetCollisionInformation(CollisionData->Vertices);
+#endif
+
+			// Copy indices
+			const int32 NumTriangles = Section->IndexBuffer.Num() / 3;
+			for (int32 TriIdx = 0; TriIdx < NumTriangles; TriIdx++)
+			{
+				// Add the triangle
+				FTriIndices& Triangle = *new (CollisionData->Indices) FTriIndices;
+				Triangle.v0 = Section->IndexBuffer[(TriIdx * 3) + 0] + VertexBase;
+				Triangle.v1 = Section->IndexBuffer[(TriIdx * 3) + 1] + VertexBase;
+				Triangle.v2 = Section->IndexBuffer[(TriIdx * 3) + 2] + VertexBase;
+
+				// Add material info
+				CollisionData->MaterialIndices.Add(SectionIdx);
+			}
+
+			// Update the vertex base index
+			VertexBase = CollisionData->Vertices.Num();
+			HadCollision = true;
+		}
+	}
+
+	for (int32 SectionIdx = 0; SectionIdx < MeshCollisionSections.Num(); SectionIdx++)
+	{
+		auto& Section = MeshCollisionSections[SectionIdx];
+		if (Section.VertexBuffer.Num() > 0 && Section.IndexBuffer.Num() > 0)
+		{
+			CollisionData->Vertices.Append(Section.VertexBuffer);
+
+			const int32 NumTriangles = Section.IndexBuffer.Num() / 3;
+			for (int32 TriIdx = 0; TriIdx < NumTriangles; TriIdx++)
+			{
+				// Add the triangle
+				FTriIndices& Triangle = *new (CollisionData->Indices) FTriIndices;
+				Triangle.v0 = Section.IndexBuffer[(TriIdx * 3) + 0] + VertexBase;
+				Triangle.v1 = Section.IndexBuffer[(TriIdx * 3) + 1] + VertexBase;
+				Triangle.v2 = Section.IndexBuffer[(TriIdx * 3) + 2] + VertexBase;
+
+				// Add material info
+				CollisionData->MaterialIndices.Add(SectionIdx);
+			}
+
+
+			VertexBase = CollisionData->Vertices.Num();
+			HadCollision = true;
+		}
+	}
+ 
+ 	CollisionData->bFlipNormals = true;
+
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 14
+	if (CollisionMode == ERuntimeMeshCollisionCookingMode::CookingPerformance)
+	{
+		CollisionData->bFlipNormals = true;
+	}
+#endif
+ 
+ 	return HadCollision;
+ }
+
+ bool URuntimeMeshComponent::ContainsPhysicsTriMeshData(bool InUseAllTriData) const
+ {
+ 	for (const RuntimeMeshSectionPtr& Section : MeshSections)
+ 	{
+ 		if (Section.IsValid() && Section->IndexBuffer.Num() >= 3 && Section->CollisionEnabled)
+ 		{
+ 			return true;
+ 		}
+ 	}
+
+	for (const auto& Section : MeshCollisionSections)
+	{
+		if (Section.VertexBuffer.Num() > 0 && Section.IndexBuffer.Num() > 0)
+		{
+			return true;
+		}
+	}
+ 
+ 	return false;
+ }
+
+
+void URuntimeMeshComponent::EnsureBodySetupCreated()
+{
+	if (BodySetup == nullptr)
+	{
+		BodySetup = NewObject<UBodySetup>(this, NAME_None, (IsTemplate() ? RF_Public : RF_NoFlags));
+		BodySetup->BodySetupGuid = FGuid::NewGuid();
+
+		BodySetup->bGenerateMirroredCollision = false;
+		BodySetup->bDoubleSidedGeometry = true;
+	}
+}
+
+void URuntimeMeshComponent::UpdateCollision()
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateCollision);
+
+	bool NeedsNewPhysicsState = false;
+
+	// Destroy physics state if it exists
+	if (bPhysicsStateCreated)
+	{
+		DestroyPhysicsState();
+		NeedsNewPhysicsState = true;
+	}
+
+	// Ensure we have a BodySetup
+	EnsureBodySetupCreated();
+
+	// Fill in simple collision convex elements
+	BodySetup->AggGeom.ConvexElems.SetNum(ConvexCollisionSections.Num());
+	for (int32 Index = 0; Index < ConvexCollisionSections.Num(); Index++)
+	{
+		FKConvexElem& NewConvexElem = BodySetup->AggGeom.ConvexElems[Index];
+
+		NewConvexElem.VertexData = ConvexCollisionSections[Index].VertexBuffer;
+		NewConvexElem.ElemBox = FBox(NewConvexElem.VertexData);
+	} 
+
+	// Set trace flag
+	BodySetup->CollisionTraceFlag = bUseComplexAsSimpleCollision ? CTF_UseComplexAsSimple : CTF_UseDefault;
+
+	// New GUID as collision has changed
+	BodySetup->BodySetupGuid = FGuid::NewGuid();
+
+
+#if WITH_RUNTIME_PHYSICS_COOKING || WITH_EDITOR
+	// Clear current mesh data
+	BodySetup->InvalidatePhysicsData();
+	// Create new mesh data
+	BodySetup->CreatePhysicsMeshes();
+#endif // WITH_RUNTIME_PHYSICS_COOKING || WITH_EDITOR
+
+	// Recreate physics state if necessary
+	if (NeedsNewPhysicsState)
+	{
+		CreatePhysicsState();
+	}
+
+	UpdateNavigation();
+}
+
+UBodySetup* URuntimeMeshComponent::GetBodySetup()
+{
+	EnsureBodySetupCreated();
+	return BodySetup;
+}
+
+void URuntimeMeshComponent::MarkCollisionDirty()
+{
+	if (!bCollisionDirty)
+	{
+		bCollisionDirty = true;
+		PrePhysicsTick.SetTickFunctionEnable(true);
+	}
+}
+
+void URuntimeMeshComponent::CookCollisionNow()
+{
+	if (bCollisionDirty)
+	{
+		BakeCollision();
+	}
+}
+
+
+void URuntimeMeshComponent::BakeCollision()
+{
+	// Bake the collision
+	UpdateCollision();
+
+	bCollisionDirty = false;
+	PrePhysicsTick.SetTickFunctionEnable(false);
+}
+
+
+void URuntimeMeshComponent::UpdateNavigation()
+{
+	if (UNavigationSystem::ShouldUpdateNavOctreeOnComponentChange() && IsRegistered())
+	{
+		UWorld* MyWorld = GetWorld();
+
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 13
+		if (MyWorld != nullptr && MyWorld->GetNavigationSystem() != nullptr &&
+			(MyWorld->GetNavigationSystem()->ShouldAllowClientSideNavigation() || !MyWorld->IsNetMode(ENetMode::NM_Client)))
+#else
+
+		if (MyWorld != nullptr && MyWorld->IsGameWorld() && MyWorld->GetNetMode() < ENetMode::NM_Client)
+#endif
+		{
+			UNavigationSystem::UpdateComponentInNavOctree(*this);
+		}
+	}
+}
+
+void URuntimeMeshComponent::RegisterComponentTickFunctions(bool bRegister)
+{
+	Super::RegisterComponentTickFunctions(bRegister);
+
+	if (bRegister)
+	{
+		if (SetupActorComponentTickFunction(&PrePhysicsTick))
+		{
+			PrePhysicsTick.Target = this;
+			PrePhysicsTick.SetTickFunctionEnable(bCollisionDirty);
+		}
+	}
+	else
+	{
+		if (PrePhysicsTick.IsTickFunctionRegistered())
+		{
+			PrePhysicsTick.UnRegisterTickFunction();
+		}
+	}
+}
+
+void URuntimeMeshComponent::SerializeLegacy(FArchive& Ar)
+{
+	if (Ar.CustomVer(FRuntimeMeshVersion::GUID) >= FRuntimeMeshVersion::Initial)
+	{
+		int32 SectionsCount = bShouldSerializeMeshData ? MeshSections.Num() : 0;
+		Ar << SectionsCount;
+		if (Ar.IsLoading() && MeshSections.Num() < SectionsCount)
+		{
+			MeshSections.SetNum(SectionsCount);
+		}
+
+		for (int32 Index = 0; Index < SectionsCount; Index++)
+		{
+			bool IsSectionValid = MeshSections[Index].IsValid();
+
+			// WE can only load/save internal types (we don't know how to serialize arbitrary vertex types.
+			if (Ar.IsSaving() && (IsSectionValid && !MeshSections[Index]->bIsLegacySectionType))
+			{
+				IsSectionValid = false;
+			}
+
+			Ar << IsSectionValid;
+
+			if (IsSectionValid)
+			{
+				if (Ar.CustomVer(FRuntimeMeshVersion::GUID) >= FRuntimeMeshVersion::TemplatedVertexFix)
+				{
+					int32 NumUVChannels;
+					bool WantsHalfPrecisionUVs;
+
+					if (Ar.IsSaving())
+					{
+						MeshSections[Index]->GetInternalVertexComponents(NumUVChannels, WantsHalfPrecisionUVs);
+					}
+
+					Ar << NumUVChannels;
+					Ar << WantsHalfPrecisionUVs;
+
+					if (Ar.IsLoading())
+					{
+						CreateOrResetSectionLegacyType(Index, NumUVChannels);
+					}
+
+				}
+				else
+				{
+					bool bWantsNormal;
+					bool bWantsTangent;
+					bool bWantsColor;
+					int32 TextureChannels;
+
+					Ar << bWantsNormal;
+					Ar << bWantsTangent;
+					Ar << bWantsColor;
+					Ar << TextureChannels;
+
+					if (Ar.IsLoading())
+					{
+						CreateOrResetSectionLegacyType(Index, TextureChannels);
+					}
+				}
+
+				FRuntimeMeshSectionInterface& SectionPtr = *MeshSections[Index].Get();
+				SectionPtr.Serialize(Ar);
+
+			}
+		}
+	}
+
+	if (Ar.CustomVer(FRuntimeMeshVersion::GUID) >= FRuntimeMeshVersion::SerializationOptional)
+	{
+
+		if (bShouldSerializeMeshData || Ar.IsLoading())
+		{
+			// Serialize the real data if we want it, also use this path for loading to get anything that was in the last save
+
+			// Serialize the collision data
+			Ar << MeshCollisionSections;
+			Ar << ConvexCollisionSections;
+		}
+		else
+		{
+			// serialize empty arrays if we don't want serialization
+			TArray<FRuntimeMeshCollisionSection> NullCollisionSections;
+			Ar << NullCollisionSections;
+			TArray<FRuntimeConvexCollisionSection> NullConvexBodies;
+			Ar << NullConvexBodies;
+		}
+	}
+}
+
+void URuntimeMeshComponent::Serialize(FArchive& Ar)
+{	
+	Super::Serialize(Ar);
+
+	SerializeInternal(Ar);
+}	
+
+void URuntimeMeshComponent::SerializeInternal(FArchive& Ar, bool bForceSaveAll)
+{
+	SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_Serialize);
+
+	Ar.UsingCustomVersion(FRuntimeMeshVersion::GUID);
+
+	// Handle old serialization
+	if (Ar.CustomVer(FRuntimeMeshVersion::GUID) < FRuntimeMeshVersion::SerializationV2)
+	{
+		SerializeLegacy(Ar);
+		return;
+	}
+
+	bool bSerializeMeshData = bShouldSerializeMeshData || bForceSaveAll;
+
+	// Serialize basic settings
+	Ar << bSerializeMeshData;
+	Ar << bUseComplexAsSimpleCollision;
+
+	// Serialize the number of sections...
+	int32 NumSections = bSerializeMeshData ? MeshSections.Num() : 0;
+	Ar << NumSections;
+
+	// Resize the section array if we're loading.
+	if (Ar.IsLoading())
+	{
+		MeshSections.Reset(NumSections);
+		MeshSections.SetNum(NumSections);
+	}
+
+	// Next serialize all the sections...
+	for (int32 Index = 0; Index < NumSections; Index++)
+	{
+		SerializeRMCSection(Ar, Index);
+	}
+
+	if (bSerializeMeshData || Ar.IsLoading())
+	{
+		// Serialize the real data if we want it, also use this path for loading to get anything that was in the last save
+
+		// Serialize the collision data
+		Ar << MeshCollisionSections;
+		Ar << ConvexCollisionSections;
+	}
+	else
+	{
+		// serialize empty arrays if we don't want serialization
+		TArray<FRuntimeMeshCollisionSection> NullCollisionSections;
+		Ar << NullCollisionSections;
+		TArray<FRuntimeConvexCollisionSection> NullConvexBodies;
+		Ar << NullConvexBodies;
+	}
+}
+
+
+void URuntimeMeshComponent::SerializeRMC(FArchive& Ar)
+{
+	SerializeInternal(Ar, true);
+}
+
+void URuntimeMeshComponent::SerializeRMCSection(FArchive& Ar, int32 SectionIndex)
+{
+	if (Ar.IsLoading() && MeshSections.Num() <= SectionIndex)
+	{
+		MeshSections.SetNum(SectionIndex + 1);
+	}
+
+	// Serialize the section validity (default it to section valid + type known for saving reasons)
+	bool bSectionIsValid = MeshSections[SectionIndex].IsValid();
+	bool bSectionTypeFound = bSectionIsValid ? FRuntimeMeshVertexTypeRegistrationContainer::GetInstance().GetVertexType(MeshSections[SectionIndex]->GetVertexType()->TypeGuid) != nullptr : true;
+	bSectionIsValid = bSectionIsValid && bSectionTypeFound;
+	Ar << bSectionIsValid;
+
+	// If section is invalid, skip
+	if (!bSectionIsValid)
+	{
+		if (!bSectionTypeFound)
+		{
+			UE_LOG(RuntimeMeshLog, Error, TEXT("Attempted to serialize a vertex of unknown type %s"), *MeshSections[SectionIndex]->GetVertexType()->TypeGuid.ToString());
+		}
+		return;
+	}
+
+	// Serialize section type info
+	FGuid TypeGuid;
+	bool bHasSeparatePositionBuffer;
+
+	if (Ar.IsSaving())
+	{
+		TypeGuid = MeshSections[SectionIndex]->GetVertexType()->TypeGuid;
+		bHasSeparatePositionBuffer = MeshSections[SectionIndex]->IsDualBufferSection();
+	}
+
+	Ar << TypeGuid;
+	Ar << bHasSeparatePositionBuffer;
+
+	if (Ar.IsLoading())
+	{
+		auto VertexTypeRegistration = FRuntimeMeshVertexTypeRegistrationContainer::GetInstance().GetVertexType(TypeGuid);
+
+		if (VertexTypeRegistration == nullptr)
+		{
+			UE_LOG(RuntimeMeshLog, Error, TEXT("Attempted to serialize a vertex of unknown type %s"), *MeshSections[SectionIndex]->GetVertexType()->TypeGuid.ToString());
+			bSectionIsValid = false;
+		}
+		else
+		{
+			auto NewSection = VertexTypeRegistration->CreateSection(bHasSeparatePositionBuffer);
+			MeshSections[SectionIndex] = MakeShareable(NewSection);
+		}
+	}
+
+	// Now we save the section data to a separate archive and then write in into the main. 
+	// This way we can recover from unknown types or mismatch sizes
+
+
+	TArray<uint8> SectionData;
+
+	if (Ar.IsSaving())
+	{
+		FMemoryWriter SectionAr(SectionData, true);
+		SectionAr.UsingCustomVersion(FRuntimeMeshVersion::GUID);
+
+		MeshSections[SectionIndex]->Serialize(SectionAr);
+	}
+
+	Ar << SectionData;
+
+	if (Ar.IsLoading() && bSectionIsValid)
+	{
+		FMemoryReader SectionAr(SectionData, true);
+		SectionAr.Seek(0);
+		SectionAr.UsingCustomVersion(FRuntimeMeshVersion::GUID);
+
+		MeshSections[SectionIndex]->Serialize(SectionAr);
+
+		// Was this section loaded correctly?
+		if (SectionAr.IsError())
+		{
+			MeshSections[SectionIndex].Reset();
+			UE_LOG(RuntimeMeshLog, Log, TEXT("Unable to load section %d of type %s. This is most likely caused by a reconfigured vertex type."),
+				SectionIndex, *MeshSections[SectionIndex]->GetVertexType()->TypeName);
+		}
+	}
+}
+
+
+
+
+
+
+
+
+void URuntimeMeshComponent::PostLoad()
+{
+	Super::PostLoad();
+
+	// Rebuild collision and local bounds.
+	MarkCollisionDirty();
+	UpdateLocalBounds();
+
+	if (BodySetup && IsTemplate())
+	{
+		BodySetup->SetFlags(RF_Public);
+	}
+}

+ 38 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Private/RuntimeMeshComponentPlugin.cpp

@@ -0,0 +1,38 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#include "RuntimeMeshComponentPluginPrivatePCH.h"
+#include "RuntimeMeshVersion.h"
+#include "RuntimeMeshComponentPlugin.h"
+
+
+// Register the custom version with core
+FCustomVersionRegistration GRegisterRuntimeMeshCustomVersion(FRuntimeMeshVersion::GUID, FRuntimeMeshVersion::LatestVersion, TEXT("RuntimeMesh"));
+
+
+
+
+class FRuntimeMeshComponentPlugin : public IRuntimeMeshComponentPlugin
+{
+	/** IModuleInterface implementation */
+	virtual void StartupModule() override;
+	virtual void ShutdownModule() override;
+};
+
+IMPLEMENT_MODULE(FRuntimeMeshComponentPlugin, RuntimeMeshComponent)
+
+
+
+void FRuntimeMeshComponentPlugin::StartupModule()
+{
+
+}
+
+
+void FRuntimeMeshComponentPlugin::ShutdownModule()
+{
+
+}
+
+
+
+DEFINE_LOG_CATEGORY(RuntimeMeshLog);

+ 14 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Private/RuntimeMeshComponentPluginPrivatePCH.h

@@ -0,0 +1,14 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+
+#pragma once
+
+#include "CoreUObject.h"
+#include "Engine.h"
+
+#include "RuntimeMeshComponentPlugin.h"
+
+
+#include "RuntimeMeshComponent.h"
+#include "PhysicsEngine/BodySetup.h"
+

+ 51 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Private/RuntimeMeshCore.cpp

@@ -0,0 +1,51 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#include "RuntimeMeshComponentPluginPrivatePCH.h"
+#include "RuntimeMeshCore.h"
+
+
+FRuntimeMeshVertexTypeRegistrationContainer& FRuntimeMeshVertexTypeRegistrationContainer::GetInstance()
+{
+	static FRuntimeMeshVertexTypeRegistrationContainer Instance;
+	return Instance;
+}
+
+void FRuntimeMeshVertexTypeRegistrationContainer::Register(const FRuntimeMeshVertexTypeInfo* InType)
+{
+	if (auto ExistingRegistration = Registrations.Find(InType->TypeGuid))
+	{
+		// This path only exists to support hotreload
+
+		// If you hit this then you've probably either:
+		// * Changed registration details during hotreload.
+		// * Accidentally copy-and-pasted an FRuntimeMeshVertexTypeRegistration object.
+		ensureMsgf(ExistingRegistration->TypeInfo->TypeName == InType->TypeName,
+			TEXT("Runtime mesh vertex registrations cannot change between hotreloads - \"%s\" is being reregistered as \"%s\""),
+			*ExistingRegistration->TypeInfo->TypeName, *InType->TypeName);
+
+		ExistingRegistration->ReferenceCount++;
+	}
+	else
+	{
+		Registrations.Add(InType->TypeGuid, VertexRegistration(InType));
+	}
+}
+
+void FRuntimeMeshVertexTypeRegistrationContainer::UnRegister(const FRuntimeMeshVertexTypeInfo* InType)
+{
+	auto ExistingRegistration = Registrations.Find(InType->TypeGuid);
+
+	check(ExistingRegistration);
+
+	ExistingRegistration->ReferenceCount--;
+	if (ExistingRegistration->ReferenceCount == 0)
+	{
+		Registrations.Remove(InType->TypeGuid);
+	}
+}
+
+const FRuntimeMeshVertexTypeInfo* FRuntimeMeshVertexTypeRegistrationContainer::GetVertexType(FGuid Key) const
+{
+	auto Registration = Registrations.Find(Key);
+	return (Registration ? Registration->TypeInfo : nullptr);
+}

+ 32 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Private/RuntimeMeshGenericVertex.cpp

@@ -0,0 +1,32 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#include "RuntimeMeshComponentPluginPrivatePCH.h"
+#include "RuntimeMeshGenericVertex.h"
+#include "RuntimeMeshBuilder.h"
+
+// Finish all the built in vertex types.
+DEFINE_RUNTIME_MESH_VERTEX(FRuntimeMeshVertexSimple);
+DEFINE_RUNTIME_MESH_VERTEX(FRuntimeMeshVertexDualUV);
+DEFINE_RUNTIME_MESH_VERTEX(FRuntimeMeshVertexTripleUV);
+DEFINE_RUNTIME_MESH_VERTEX(FRuntimeMeshVertexNoPosition);
+DEFINE_RUNTIME_MESH_VERTEX(FRuntimeMeshVertexNoPositionDualUV);
+DEFINE_RUNTIME_MESH_VERTEX(FRuntimeMeshVertexHiPrecisionNormals);
+DEFINE_RUNTIME_MESH_VERTEX(FRuntimeMeshVertexDualUVHiPrecisionNormals);
+DEFINE_RUNTIME_MESH_VERTEX(FRuntimeMeshVertexNoPositionHiPrecisionNormals);
+DEFINE_RUNTIME_MESH_VERTEX(FRuntimeMeshVertexNoPositionDualUVHiPrecisionNormals);
+
+
+
+
+const FRuntimeMeshVertexTypeInfo* FRuntimeMeshComponentVerticesBuilder::GetVertexType() const
+{
+	if (HasUVComponent(1))
+	{
+		if (HasUVComponent(2)) return &FRuntimeMeshVertexTripleUV::TypeInfo;
+		else return &FRuntimeMeshVertexDualUV::TypeInfo;
+	}
+	else
+	{
+		return &FRuntimeMeshVertexSimple::TypeInfo;
+	}
+}

+ 561 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Private/RuntimeMeshLibrary.cpp

@@ -0,0 +1,561 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#include "RuntimeMeshComponentPluginPrivatePCH.h"
+#include "RuntimeMeshLibrary.h"
+#include "MessageLog.h"
+#include "UObjectToken.h"
+#include "StaticMeshResources.h"
+#include "TessellationUtilities.h"
+#include "RuntimeMeshBuilder.h"
+#include "RuntimeMeshComponent.h"
+
+#define LOCTEXT_NAMESPACE "RuntimeMeshLibrary"
+
+
+URuntimeMeshLibrary::URuntimeMeshLibrary(const FObjectInitializer& ObjectInitializer)
+	: Super(ObjectInitializer)
+{
+
+}
+
+void URuntimeMeshLibrary::ConvertQuadToTriangles(TArray<int32>& Triangles, int32 Vert0, int32 Vert1, int32 Vert2, int32 Vert3)
+{
+	Triangles.Add(Vert0);
+	Triangles.Add(Vert1);
+	Triangles.Add(Vert3);
+
+	Triangles.Add(Vert1);
+	Triangles.Add(Vert2);
+	Triangles.Add(Vert3);
+}
+
+void URuntimeMeshLibrary::CreateGridMeshTriangles(int32 NumX, int32 NumY, bool bWinding, TArray<int32>& Triangles)
+{
+	Triangles.Reset();
+
+	if (NumX >= 2 && NumY >= 2)
+	{
+		// Build Quads
+		for (int XIdx = 0; XIdx < NumX - 1; XIdx++)
+		{
+			for (int YIdx = 0; YIdx < NumY - 1; YIdx++)
+			{
+				const int32 I0 = (XIdx + 0)*NumY + (YIdx + 0);
+				const int32 I1 = (XIdx + 1)*NumY + (YIdx + 0);
+				const int32 I2 = (XIdx + 1)*NumY + (YIdx + 1);
+				const int32 I3 = (XIdx + 0)*NumY + (YIdx + 1);
+
+				if (bWinding)
+				{
+					ConvertQuadToTriangles(Triangles, I0, I1, I2, I3);
+				}
+				else
+				{
+					ConvertQuadToTriangles(Triangles, I0, I3, I2, I1);
+				}
+			}
+		}
+	}
+}
+
+void URuntimeMeshLibrary::CreateBoxMesh(FVector BoxRadius, TArray<FVector>& Vertices, TArray<int32>& Triangles, TArray<FVector>& Normals, TArray<FVector2D>& UVs, TArray<FRuntimeMeshTangent>& Tangents)
+{
+	// Generate verts
+	FVector BoxVerts[8];
+	BoxVerts[0] = FVector(-BoxRadius.X, BoxRadius.Y, BoxRadius.Z);
+	BoxVerts[1] = FVector(BoxRadius.X, BoxRadius.Y, BoxRadius.Z);
+	BoxVerts[2] = FVector(BoxRadius.X, -BoxRadius.Y, BoxRadius.Z);
+	BoxVerts[3] = FVector(-BoxRadius.X, -BoxRadius.Y, BoxRadius.Z);
+
+	BoxVerts[4] = FVector(-BoxRadius.X, BoxRadius.Y, -BoxRadius.Z);
+	BoxVerts[5] = FVector(BoxRadius.X, BoxRadius.Y, -BoxRadius.Z);
+	BoxVerts[6] = FVector(BoxRadius.X, -BoxRadius.Y, -BoxRadius.Z);
+	BoxVerts[7] = FVector(-BoxRadius.X, -BoxRadius.Y, -BoxRadius.Z);
+
+	// Generate triangles (from quads)
+	Triangles.Reset();
+
+	const int32 NumVerts = 24; // 6 faces x 4 verts per face
+
+	Vertices.Reset();
+	Vertices.AddUninitialized(NumVerts);
+
+	Normals.Reset();
+	Normals.AddUninitialized(NumVerts);
+
+	Tangents.Reset();
+	Tangents.AddUninitialized(NumVerts);
+
+	Vertices[0] = BoxVerts[0];
+	Vertices[1] = BoxVerts[1];
+	Vertices[2] = BoxVerts[2];
+	Vertices[3] = BoxVerts[3];
+	ConvertQuadToTriangles(Triangles, 0, 1, 2, 3);
+	Normals[0] = Normals[1] = Normals[2] = Normals[3] = FVector(0, 0, 1);
+	Tangents[0] = Tangents[1] = Tangents[2] = Tangents[3] = FRuntimeMeshTangent(0.f, -1.f, 0.f);
+
+	Vertices[4] = BoxVerts[4];
+	Vertices[5] = BoxVerts[0];
+	Vertices[6] = BoxVerts[3];
+	Vertices[7] = BoxVerts[7];
+	ConvertQuadToTriangles(Triangles, 4, 5, 6, 7);
+	Normals[4] = Normals[5] = Normals[6] = Normals[7] = FVector(-1, 0, 0);
+	Tangents[4] = Tangents[5] = Tangents[6] = Tangents[7] = FRuntimeMeshTangent(0.f, -1.f, 0.f);
+
+	Vertices[8] = BoxVerts[5];
+	Vertices[9] = BoxVerts[1];
+	Vertices[10] = BoxVerts[0];
+	Vertices[11] = BoxVerts[4];
+	ConvertQuadToTriangles(Triangles, 8, 9, 10, 11);
+	Normals[8] = Normals[9] = Normals[10] = Normals[11] = FVector(0, 1, 0);
+	Tangents[8] = Tangents[9] = Tangents[10] = Tangents[11] = FRuntimeMeshTangent(-1.f, 0.f, 0.f);
+
+	Vertices[12] = BoxVerts[6];
+	Vertices[13] = BoxVerts[2];
+	Vertices[14] = BoxVerts[1];
+	Vertices[15] = BoxVerts[5];
+	ConvertQuadToTriangles(Triangles, 12, 13, 14, 15);
+	Normals[12] = Normals[13] = Normals[14] = Normals[15] = FVector(1, 0, 0);
+	Tangents[12] = Tangents[13] = Tangents[14] = Tangents[15] = FRuntimeMeshTangent(0.f, 1.f, 0.f);
+
+	Vertices[16] = BoxVerts[7];
+	Vertices[17] = BoxVerts[3];
+	Vertices[18] = BoxVerts[2];
+	Vertices[19] = BoxVerts[6];
+	ConvertQuadToTriangles(Triangles, 16, 17, 18, 19);
+	Normals[16] = Normals[17] = Normals[18] = Normals[19] = FVector(0, -1, 0);
+	Tangents[16] = Tangents[17] = Tangents[18] = Tangents[19] = FRuntimeMeshTangent(1.f, 0.f, 0.f);
+
+	Vertices[20] = BoxVerts[7];
+	Vertices[21] = BoxVerts[6];
+	Vertices[22] = BoxVerts[5];
+	Vertices[23] = BoxVerts[4];
+	ConvertQuadToTriangles(Triangles, 20, 21, 22, 23);
+	Normals[20] = Normals[21] = Normals[22] = Normals[23] = FVector(0, 0, -1);
+	Tangents[20] = Tangents[21] = Tangents[22] = Tangents[23] = FRuntimeMeshTangent(0.f, 1.f, 0.f);
+
+	// UVs
+	UVs.Reset();
+	UVs.AddUninitialized(NumVerts);
+
+	UVs[0] = UVs[4] = UVs[8] = UVs[12] = UVs[16] = UVs[20] = FVector2D(0.f, 0.f);
+	UVs[1] = UVs[5] = UVs[9] = UVs[13] = UVs[17] = UVs[21] = FVector2D(0.f, 1.f);
+	UVs[2] = UVs[6] = UVs[10] = UVs[14] = UVs[18] = UVs[22] = FVector2D(1.f, 1.f);
+	UVs[3] = UVs[7] = UVs[11] = UVs[15] = UVs[19] = UVs[23] = FVector2D(1.f, 0.f);
+}
+
+
+
+
+
+void FindVertOverlaps(int32 TestVertIndex, const IRuntimeMeshVerticesBuilder* Vertices, TArray<int32>& VertOverlaps)
+{
+	// Check if Verts is empty or test is outside range
+	if (TestVertIndex < Vertices->Length())
+	{
+		const FVector TestVert = Vertices->GetPosition(TestVertIndex);
+
+		for (int32 VertIdx = 0; VertIdx < Vertices->Length(); VertIdx++)
+		{
+			// First see if we overlap, and smoothing groups are the same
+			if (TestVert.Equals(Vertices->GetPosition(VertIdx)))
+			{
+				// If it, so we are at least considered an 'overlap' for normal gen
+				VertOverlaps.Add(VertIdx);
+			}
+		}
+	}
+}
+
+void URuntimeMeshLibrary::CalculateTangentsForMesh(IRuntimeMeshVerticesBuilder* Vertices, const FRuntimeMeshIndicesBuilder* Triangles)
+{
+	if (Vertices->Length() == 0) return;
+
+	// Number of triangles
+	const int32 NumTris = Triangles->TriangleLength();
+	// Number of verts
+	const int32 NumVerts = Vertices->Length();
+
+	// Map of vertex to triangles in Triangles array
+	TMultiMap<int32, int32> VertToTriMap;
+	// Map of vertex to triangles to consider for normal calculation
+	TMultiMap<int32, int32> VertToTriSmoothMap;
+
+	// Normal/tangents for each face
+	TArray<FVector> FaceTangentX, FaceTangentY, FaceTangentZ;
+	FaceTangentX.AddUninitialized(NumTris);
+	FaceTangentY.AddUninitialized(NumTris);
+	FaceTangentZ.AddUninitialized(NumTris);
+
+	// Iterate over triangles
+	for (int TriIdx = 0; TriIdx < NumTris; TriIdx++)
+	{
+		int32 CornerIndex[3];
+		FVector P[3];
+
+		for (int32 CornerIdx = 0; CornerIdx < 3; CornerIdx++)
+		{
+			// Find vert index (clamped within range)
+			int32 VertIndex = FMath::Min(Triangles->GetIndex((TriIdx * 3) + CornerIdx), NumVerts - 1);
+
+			CornerIndex[CornerIdx] = VertIndex;
+			P[CornerIdx] = Vertices->GetPosition(VertIndex);
+
+			// Find/add this vert to index buffer
+			TArray<int32> VertOverlaps;
+			FindVertOverlaps(VertIndex, Vertices, VertOverlaps);
+
+			// Remember which triangles map to this vert
+			VertToTriMap.AddUnique(VertIndex, TriIdx);
+			VertToTriSmoothMap.AddUnique(VertIndex, TriIdx);
+
+			// Also update map of triangles that 'overlap' this vert (ie don't match UV, but do match smoothing) and should be considered when calculating normal
+			for (int32 OverlapIdx = 0; OverlapIdx < VertOverlaps.Num(); OverlapIdx++)
+			{
+				// For each vert we overlap..
+				int32 OverlapVertIdx = VertOverlaps[OverlapIdx];
+
+				// Add this triangle to that vert
+				VertToTriSmoothMap.AddUnique(OverlapVertIdx, TriIdx);
+
+				// And add all of its triangles to us
+				TArray<int32> OverlapTris;
+				VertToTriMap.MultiFind(OverlapVertIdx, OverlapTris);
+				for (int32 OverlapTriIdx = 0; OverlapTriIdx < OverlapTris.Num(); OverlapTriIdx++)
+				{
+					VertToTriSmoothMap.AddUnique(VertIndex, OverlapTris[OverlapTriIdx]);
+				}
+			}
+		}
+
+		// Calculate triangle edge vectors and normal
+		const FVector Edge21 = P[1] - P[2];
+		const FVector Edge20 = P[0] - P[2];
+		const FVector TriNormal = (Edge21 ^ Edge20).GetSafeNormal();
+
+		// If we have UVs, use those to calc 
+		if (Vertices->HasUVComponent(0))
+		{
+			const FVector2D T1 = Vertices->GetUV(CornerIndex[0]);
+			const FVector2D T2 = Vertices->GetUV(CornerIndex[1]);
+			const FVector2D T3 = Vertices->GetUV(CornerIndex[2]);
+
+			FMatrix	ParameterToLocal(
+				FPlane(P[1].X - P[0].X, P[1].Y - P[0].Y, P[1].Z - P[0].Z, 0),
+				FPlane(P[2].X - P[0].X, P[2].Y - P[0].Y, P[2].Z - P[0].Z, 0),
+				FPlane(P[0].X, P[0].Y, P[0].Z, 0),
+				FPlane(0, 0, 0, 1)
+			);
+
+			FMatrix ParameterToTexture(
+				FPlane(T2.X - T1.X, T2.Y - T1.Y, 0, 0),
+				FPlane(T3.X - T1.X, T3.Y - T1.Y, 0, 0),
+				FPlane(T1.X, T1.Y, 1, 0),
+				FPlane(0, 0, 0, 1)
+			);
+
+			// Use InverseSlow to catch singular matrices.  Inverse can miss this sometimes.
+			const FMatrix TextureToLocal = ParameterToTexture.Inverse() * ParameterToLocal;
+
+			FaceTangentX[TriIdx] = TextureToLocal.TransformVector(FVector(1, 0, 0)).GetSafeNormal();
+			FaceTangentY[TriIdx] = TextureToLocal.TransformVector(FVector(0, 1, 0)).GetSafeNormal();
+		}
+		else
+		{
+			FaceTangentX[TriIdx] = Edge20.GetSafeNormal();
+			FaceTangentY[TriIdx] = (FaceTangentX[TriIdx] ^ TriNormal).GetSafeNormal();
+		}
+
+		FaceTangentZ[TriIdx] = TriNormal;
+	}
+
+
+	// Arrays to accumulate tangents into
+	TArray<FVector> VertexTangentXSum, VertexTangentYSum, VertexTangentZSum;
+	VertexTangentXSum.AddZeroed(NumVerts);
+	VertexTangentYSum.AddZeroed(NumVerts);
+	VertexTangentZSum.AddZeroed(NumVerts);
+
+	// For each vertex..
+	for (int VertxIdx = 0; VertxIdx < Vertices->Length(); VertxIdx++)
+	{
+		// Find relevant triangles for normal
+		TArray<int32> SmoothTris;
+		VertToTriSmoothMap.MultiFind(VertxIdx, SmoothTris);
+
+		for (int i = 0; i < SmoothTris.Num(); i++)
+		{
+			int32 TriIdx = SmoothTris[i];
+			VertexTangentZSum[VertxIdx] += FaceTangentZ[TriIdx];
+		}
+
+		// Find relevant triangles for tangents
+		TArray<int32> TangentTris;
+		VertToTriMap.MultiFind(VertxIdx, TangentTris);
+
+		for (int i = 0; i < TangentTris.Num(); i++)
+		{
+			int32 TriIdx = TangentTris[i];
+			VertexTangentXSum[VertxIdx] += FaceTangentX[TriIdx];
+			VertexTangentYSum[VertxIdx] += FaceTangentY[TriIdx];
+		}
+	}
+
+	// Finally, normalize tangents and build output arrays
+	
+	for (int VertxIdx = 0; VertxIdx < NumVerts; VertxIdx++)
+	{
+		FVector& TangentX = VertexTangentXSum[VertxIdx];
+		FVector& TangentY = VertexTangentYSum[VertxIdx];
+		FVector& TangentZ = VertexTangentZSum[VertxIdx];
+
+		TangentX.Normalize();
+		TangentZ.Normalize();
+
+		// Use Gram-Schmidt orthogonalization to make sure X is orth with Z
+		TangentX -= TangentZ * (TangentZ | TangentX);
+		TangentX.Normalize();
+
+		Vertices->SetTangents(VertxIdx, TangentX, TangentY, TangentZ);
+	}
+}
+
+void URuntimeMeshLibrary::CalculateTangentsForMesh(const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector2D>& UVs, TArray<FVector>& Normals, TArray<FRuntimeMeshTangent>& Tangents)
+{
+	FRuntimeMeshComponentVerticesBuilder VerticesBuilder(const_cast<TArray<FVector>*>(&Vertices), &Normals, &Tangents, nullptr, const_cast<TArray<FVector2D>*>(&UVs));
+	FRuntimeMeshIndicesBuilder IndicesBuilder(const_cast<TArray<int32>*>(&Triangles));
+
+	CalculateTangentsForMesh(&VerticesBuilder, &IndicesBuilder);
+}
+
+
+
+
+
+void URuntimeMeshLibrary::GenerateTessellationIndexBuffer(const IRuntimeMeshVerticesBuilder* Vertices, const FRuntimeMeshIndicesBuilder* Indices, FRuntimeMeshIndicesBuilder* OutTessellationIndices)
+{
+	TessellationUtilities::CalculateTessellationIndices(Vertices, Indices, OutTessellationIndices);
+}
+
+void URuntimeMeshLibrary::GenerateTessellationIndexBuffer(const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector2D>& UVs, TArray<FVector>& Normals, TArray<FRuntimeMeshTangent>& Tangents, TArray<int32>& OutTessTriangles)
+{
+	FRuntimeMeshComponentVerticesBuilder VerticesBuilder(const_cast<TArray<FVector>*>(&Vertices), &Normals, &Tangents, nullptr, const_cast<TArray<FVector2D>*>(&UVs));
+	FRuntimeMeshIndicesBuilder IndicesBuilder(const_cast<TArray<int32>*>(&Triangles));
+	FRuntimeMeshIndicesBuilder OutIndicesBuilder(&OutTessTriangles);
+
+	GenerateTessellationIndexBuffer(&VerticesBuilder, &IndicesBuilder, &OutIndicesBuilder);
+}
+
+
+
+
+
+static int32 GetNewIndexForOldVertIndex(int32 MeshVertIndex, TMap<int32, int32>& MeshToSectionVertMap, const FPositionVertexBuffer* PosBuffer, const FStaticMeshVertexBuffer* VertBuffer, const FColorVertexBuffer* ColorBuffer, IRuntimeMeshVerticesBuilder* Vertices)
+{
+	int32* NewIndexPtr = MeshToSectionVertMap.Find(MeshVertIndex);
+	if (NewIndexPtr != nullptr)
+	{
+		return *NewIndexPtr;
+	}
+	else
+	{
+		// Copy position
+		int32 SectionVertIndex = Vertices->MoveNextOrAdd();
+
+		Vertices->SetPosition(PosBuffer->VertexPosition(MeshVertIndex));
+		Vertices->SetNormal(VertBuffer->VertexTangentZ(MeshVertIndex));
+		Vertices->SetTangent(VertBuffer->VertexTangentX(MeshVertIndex));
+		if (ColorBuffer && ColorBuffer->GetNumVertices())
+		{
+			Vertices->SetColor(ColorBuffer->VertexColor(MeshVertIndex));
+		}
+
+		// copy all uv channels
+		for (uint32 Index = 0; Index < VertBuffer->GetNumTexCoords(); Index++)
+		{
+			Vertices->SetUV(Index, VertBuffer->GetVertexUV(MeshVertIndex, Index));
+		}
+
+		MeshToSectionVertMap.Add(MeshVertIndex, SectionVertIndex);
+
+		return SectionVertIndex;
+	}
+}
+
+void URuntimeMeshLibrary::GetSectionFromStaticMesh(UStaticMesh* InMesh, int32 LODIndex, int32 SectionIndex,
+	IRuntimeMeshVerticesBuilder* Vertices, FRuntimeMeshIndicesBuilder* Triangles, FRuntimeMeshIndicesBuilder* AdjacencyTriangles)
+{
+	if (InMesh)
+	{
+#if !WITH_EDITOR && (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 13)
+		if (!InMesh->bAllowCPUAccess)
+		{
+			FMessageLog("PIE").Warning()
+				->AddToken(FTextToken::Create(LOCTEXT("GetSectionFromStaticMeshStart", "Calling GetSectionFromStaticMesh on")))
+				->AddToken(FUObjectToken::Create(InMesh))
+				->AddToken(FTextToken::Create(LOCTEXT("GetSectionFromStaticMeshEnd", "but 'Allow CPU Access' is not enabled. This is required for converting StaticMesh to RuntimeMeshComponent in cooked builds.")));
+		}
+		else 
+#endif
+#if WITH_EDITOR || (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 13)
+		if (InMesh->RenderData != nullptr && InMesh->RenderData->LODResources.IsValidIndex(LODIndex))
+		{
+			const FStaticMeshLODResources& LOD = InMesh->RenderData->LODResources[LODIndex];
+			if (LOD.Sections.IsValidIndex(SectionIndex))
+			{
+				// Empty output buffers
+				Vertices->Reset();
+				Triangles->Reset();
+
+				// Map from vert buffer for whole mesh to vert buffer for section of interest
+				TMap<int32, int32> MeshToSectionVertMap;
+
+				const FStaticMeshSection& Section = LOD.Sections[SectionIndex];
+				const uint32 OnePastLastIndex = Section.FirstIndex + Section.NumTriangles * 3;
+				FIndexArrayView Indices = LOD.IndexBuffer.GetArrayView();
+
+
+				// Iterate over section index buffer, copying verts as needed
+				for (uint32 i = Section.FirstIndex; i < OnePastLastIndex; i++)
+				{
+					uint32 MeshVertIndex = Indices[i];
+
+					// See if we have this vert already in our section vert buffer, and copy vert in if not 
+					int32 SectionVertIndex = GetNewIndexForOldVertIndex(MeshVertIndex, MeshToSectionVertMap, &LOD.PositionVertexBuffer, &LOD.VertexBuffer, &LOD.ColorVertexBuffer, Vertices);
+
+					// Add to index buffer
+					Triangles->AddIndex(SectionVertIndex);
+				}
+
+				if (AdjacencyTriangles != nullptr)
+				{
+					AdjacencyTriangles->Reset();
+
+					// Adjacency indices use 12 per triangle instead of 3. So start position and length both need to be multiplied by 4
+					const uint32 SectionAdjacencyFirstIndex = Section.FirstIndex * 4;
+					const uint32 SectionAdjacencyOnePastLastIndex = SectionAdjacencyFirstIndex + Section.NumTriangles * (3 * 4);
+					FIndexArrayView AdjacencyIndices = LOD.AdjacencyIndexBuffer.GetArrayView();
+
+					// Iterate over section adjacency index buffer, copying any new verts as needed
+					for (uint32 i = SectionAdjacencyFirstIndex; i < SectionAdjacencyOnePastLastIndex; i++)
+					{
+						uint32 MeshVertIndex = AdjacencyIndices[i];
+
+						// See if we have this vert already in our section vert buffer, and copy vert in if not 
+						int32 SectionVertIndex = GetNewIndexForOldVertIndex(MeshVertIndex, MeshToSectionVertMap, &LOD.PositionVertexBuffer, &LOD.VertexBuffer, &LOD.ColorVertexBuffer, Vertices);
+
+						// Add to index buffer
+						AdjacencyTriangles->AddIndex(SectionVertIndex);
+					}
+				}
+			}
+		}
+#endif
+	}
+}
+
+void URuntimeMeshLibrary::GetSectionFromStaticMesh(UStaticMesh* InMesh, int32 LODIndex, int32 SectionIndex, TArray<FVector>& Vertices,
+	TArray<int32>& Triangles, TArray<FVector>& Normals, TArray<FVector2D>& UVs, TArray<FRuntimeMeshTangent>& Tangents)
+{
+	FRuntimeMeshComponentVerticesBuilder VerticesBuilder(&Vertices, &Normals, &Tangents, nullptr, &UVs);
+	FRuntimeMeshIndicesBuilder IndicesBuilder(&Triangles);
+
+	GetSectionFromStaticMesh(InMesh, LODIndex, SectionIndex, &VerticesBuilder, &IndicesBuilder, nullptr);
+}
+
+void URuntimeMeshLibrary::CopyRuntimeMeshFromStaticMeshComponent(UStaticMeshComponent* StaticMeshComp, int32 LODIndex,
+	URuntimeMeshComponent* RuntimeMeshComp, bool bShouldCreateCollision)
+{
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 14
+	UStaticMesh* StaticMesh = StaticMeshComp->GetStaticMesh();
+#else
+	UStaticMesh* StaticMesh = StaticMeshComp->StaticMesh;
+#endif
+
+	if (StaticMeshComp != nullptr && StaticMesh != nullptr && RuntimeMeshComp != nullptr)
+	{
+		//// MESH DATA
+
+		// Make sure LOD index is valid
+		if (StaticMesh->RenderData == nullptr || !StaticMesh->RenderData->LODResources.IsValidIndex(LODIndex))
+		{
+			return;
+		}
+
+		// Get specified LOD
+		const FStaticMeshLODResources& LOD = StaticMesh->RenderData->LODResources[LODIndex];
+
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 13
+		int32 NumSections = StaticMesh->GetNumSections(LODIndex);
+#else
+		int32 NumSections = LOD.Sections.Num();
+#endif
+		for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++)
+		{
+			// Buffers for copying geom data
+			if (LOD.Sections.IsValidIndex(SectionIndex))
+			{
+				int32 NumUVChannels = LOD.GetNumTexCoords();
+
+				FRuntimeMeshIndicesBuilder AdjacencyTriangles;
+
+				if (NumUVChannels <= 1)
+				{
+					FRuntimeMeshPackedVerticesBuilder<FRuntimeMeshVertexSimple> Vertices;
+					FRuntimeMeshIndicesBuilder Triangles;
+
+					// Get geom data from static mesh
+					GetSectionFromStaticMesh(StaticMesh, LODIndex, SectionIndex, &Vertices, &Triangles, &AdjacencyTriangles);
+
+					// Create section using data
+					RuntimeMeshComp->CreateMeshSection(SectionIndex, *Vertices.GetVertices(), *Triangles.GetIndices(),
+						bShouldCreateCollision, EUpdateFrequency::Infrequent, ESectionUpdateFlags::MoveArrays);
+				}
+				else
+				{
+					FRuntimeMeshPackedVerticesBuilder<FRuntimeMeshVertexDualUV> Vertices;
+					FRuntimeMeshIndicesBuilder Triangles;
+
+					// Get geom data from static mesh
+					GetSectionFromStaticMesh(StaticMesh, LODIndex, SectionIndex, &Vertices, &Triangles, &AdjacencyTriangles);
+
+					// Create section using data
+					RuntimeMeshComp->CreateMeshSection(SectionIndex, *Vertices.GetVertices(), *Triangles.GetIndices(),
+						bShouldCreateCollision, EUpdateFrequency::Infrequent, ESectionUpdateFlags::MoveArrays);
+
+				}
+
+			}
+		}
+
+		//// SIMPLE COLLISION
+
+		// Clear any existing collision hulls
+		RuntimeMeshComp->ClearCollisionConvexMeshes();
+
+		if (StaticMesh->BodySetup != nullptr)
+		{
+			// Iterate over all convex hulls on static mesh..
+			const int32 NumConvex = StaticMesh->BodySetup->AggGeom.ConvexElems.Num();
+			for (int ConvexIndex = 0; ConvexIndex < NumConvex; ConvexIndex++)
+			{
+				// Copy convex verts to ProcMesh
+				FKConvexElem& MeshConvex = StaticMesh->BodySetup->AggGeom.ConvexElems[ConvexIndex];
+				RuntimeMeshComp->AddCollisionConvexMesh(MeshConvex.VertexData);
+			}
+		}
+
+		//// MATERIALS
+
+		for (int32 MatIndex = 0; MatIndex < StaticMeshComp->GetNumMaterials(); MatIndex++)
+		{
+			RuntimeMeshComp->SetMaterial(MatIndex, StaticMeshComp->GetMaterial(MatIndex));
+		}
+	}
+}
+
+
+
+
+
+#undef LOCTEXT_NAMESPACE

+ 156 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Private/TessellationUtilities.cpp

@@ -0,0 +1,156 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#include "RuntimeMeshComponentPluginPrivatePCH.h"
+#include "TessellationUtilities.h"
+
+const uint32 EdgesPerTriangle = 3;
+const uint32 IndicesPerTriangle = 3;
+const uint32 VerticesPerTriangle = 3;
+const uint32 DuplicateIndexCount = 3;
+
+const uint32 PnAenDomCorner_IndicesPerPatch = 12;
+
+
+void TessellationUtilities::AddIfLeastUV(PositionDictionary& PosDict, const Vertex& Vert, uint32 Index)
+{
+	auto* Pos = PosDict.Find(Vert.Position);
+	if (Pos == nullptr)
+	{
+		PosDict.Add(Vert.Position, Corner(Index, Vert.TexCoord));
+	}
+	else if (Vert.TexCoord < Pos->TexCoord)
+	{
+		PosDict[Vert.Position] = Corner(Index, Vert.TexCoord);
+	}
+}
+
+
+void TessellationUtilities::CalculateTessellationIndices(const IRuntimeMeshVerticesBuilder* Vertices, const FRuntimeMeshIndicesBuilder* Indices, FRuntimeMeshIndicesBuilder* TessellationIndices)
+{
+	EdgeDictionary EdgeDict;
+	EdgeDict.Reserve(Indices->Length());
+	PositionDictionary PosDict;
+	PosDict.Reserve(Indices->Length());
+
+	TessellationIndices->Reset(PnAenDomCorner_IndicesPerPatch * Indices->Length() / IndicesPerTriangle);
+	
+	ExpandIB(Vertices, Indices, EdgeDict, PosDict, TessellationIndices);
+
+	ReplacePlaceholderIndices(Vertices, Indices, EdgeDict, PosDict, TessellationIndices);
+}
+
+
+void TessellationUtilities::ExpandIB(const IRuntimeMeshVerticesBuilder* Vertices, const FRuntimeMeshIndicesBuilder* Indices,
+	EdgeDictionary& OutEdgeDict, PositionDictionary& OutPosDict, FRuntimeMeshIndicesBuilder* OutIndices)
+{
+	const uint32 TriangleCount = Indices->Length() / IndicesPerTriangle;
+
+	for (uint32 U = 0; U < TriangleCount; U++)
+	{
+		const uint32 StartInIndex = U * IndicesPerTriangle;
+		const uint32 StartOutIndex = U * PnAenDomCorner_IndicesPerPatch;
+
+		Indices->Seek(StartInIndex);
+		const uint32 Index0 = Indices->ReadOne();
+		const uint32 Index1 = Indices->ReadOne();
+		const uint32 Index2 = Indices->ReadOne();
+
+		Vertices->Seek(Index0);
+		const Vertex Vertex0(Vertices->GetPosition(), Vertices->GetUV(0));
+		Vertices->Seek(Index1);
+		const Vertex Vertex1(Vertices->GetPosition(), Vertices->GetUV(0));
+		Vertices->Seek(Index2);
+		const Vertex Vertex2(Vertices->GetPosition(), Vertices->GetUV(0));
+
+		Triangle Tri(Index0, Index1, Index2, Vertex0, Vertex1, Vertex2);
+
+		OutIndices->Seek(StartOutIndex);
+		OutIndices->AddTriangle(Tri.GetIndex(0), Tri.GetIndex(1), Tri.GetIndex(2));
+
+		OutIndices->AddIndex(Tri.GetIndex(0));
+		OutIndices->AddIndex(Tri.GetIndex(1));
+
+		OutIndices->AddIndex(Tri.GetIndex(1));
+		OutIndices->AddIndex(Tri.GetIndex(2));
+
+		OutIndices->AddIndex(Tri.GetIndex(2));
+		OutIndices->AddIndex(Tri.GetIndex(0));
+
+		OutIndices->AddTriangle(Tri.GetIndex(0), Tri.GetIndex(1), Tri.GetIndex(2));
+
+		
+		Edge Rev0 = Tri.GetEdge(0).GetReverse();
+		Edge Rev1 = Tri.GetEdge(1).GetReverse();
+		Edge Rev2 = Tri.GetEdge(2).GetReverse();
+
+		OutEdgeDict.Add(Rev0, Rev0);
+		OutEdgeDict.Add(Rev1, Rev1);
+		OutEdgeDict.Add(Rev2, Rev2);
+
+		AddIfLeastUV(OutPosDict, Vertex0, Index0);
+		AddIfLeastUV(OutPosDict, Vertex1, Index1);
+		AddIfLeastUV(OutPosDict, Vertex2, Index2);
+	}
+}
+
+
+
+void TessellationUtilities::ReplacePlaceholderIndices(const IRuntimeMeshVerticesBuilder* Vertices, const FRuntimeMeshIndicesBuilder* Indices,
+	EdgeDictionary& EdgeDict, PositionDictionary& PosDict, FRuntimeMeshIndicesBuilder* OutIndices)
+{
+	const uint32 TriangleCount = Indices->Length() / PnAenDomCorner_IndicesPerPatch;
+
+	for (uint32 U = 0; U < TriangleCount; U++)
+	{
+		const uint32 StartOutIndex = U * PnAenDomCorner_IndicesPerPatch;
+
+		OutIndices->Seek(StartOutIndex);
+		const uint32 Index0 = OutIndices->ReadOne();
+		const uint32 Index1 = OutIndices->ReadOne();
+		const uint32 Index2 = OutIndices->ReadOne();
+
+		Vertices->Seek(Index0);
+		const Vertex Vertex0(Vertices->GetPosition(), Vertices->GetUV(0));
+		Vertices->Seek(Index1);
+		const Vertex Vertex1(Vertices->GetPosition(), Vertices->GetUV(0));
+		Vertices->Seek(Index2);
+		const Vertex Vertex2(Vertices->GetPosition(), Vertices->GetUV(0));
+
+		Triangle Tri(Index0, Index1, Index2, Vertex0, Vertex1, Vertex2);
+
+		Edge* Ed = EdgeDict.Find(Tri.GetEdge(0));
+		if (Ed != nullptr)
+		{
+			OutIndices->Seek(StartOutIndex + 3);
+			OutIndices->AddIndex(Ed->GetIndex(0));
+			OutIndices->AddIndex(Ed->GetIndex(1));
+		}
+
+		Ed = EdgeDict.Find(Tri.GetEdge(1));
+		if (Ed != nullptr)
+		{
+			OutIndices->Seek(StartOutIndex + 5);
+			OutIndices->AddIndex(Ed->GetIndex(0));
+			OutIndices->AddIndex(Ed->GetIndex(1));
+		}
+
+		Ed = EdgeDict.Find(Tri.GetEdge(2));
+		if (Ed != nullptr)
+		{
+			OutIndices->Seek(StartOutIndex + 7);
+			OutIndices->AddIndex(Ed->GetIndex(0));
+			OutIndices->AddIndex(Ed->GetIndex(1));
+		}
+		
+		// Deal with dominant positions.
+		for (uint32 V = 0; V < VerticesPerTriangle; V++)
+		{
+			Corner* Corn = PosDict.Find(Tri.GetEdge(V).GetVertex(0).Position);
+			if (Corn != nullptr)
+			{
+				OutIndices->Seek(StartOutIndex + 9 + V);
+				OutIndices->AddIndex(Corn->Index);
+			}
+		}
+	}
+}

+ 178 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Private/TessellationUtilities.h

@@ -0,0 +1,178 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#pragma once
+#include "RuntimeMeshBuilder.h"
+
+
+
+/**
+ * 
+ */
+class TessellationUtilities
+{
+public:
+	static void CalculateTessellationIndices(const IRuntimeMeshVerticesBuilder* Vertices, const FRuntimeMeshIndicesBuilder* Indices, FRuntimeMeshIndicesBuilder* TessellationIndices);
+
+
+
+
+private:
+	struct Vertex
+	{
+		FVector Position;
+		FVector2D TexCoord;
+
+		Vertex() { }
+		Vertex(const FVector& InPosition, const FVector2D& InTexCoord)
+			: Position(InPosition), TexCoord(InTexCoord)
+		{ }
+
+		FORCEINLINE bool operator==(const Vertex& Other) const
+		{
+			return Position == Other.Position;
+		}
+		FORCEINLINE bool operator<(const Vertex& Other) const
+		{
+			return Position.X < Other.Position.X
+				|| Position.Y < Other.Position.Y
+				|| Position.Z < Other.Position.Z;
+		}
+	};
+
+	static FORCEINLINE uint32 HashValue(const FVector& Vec)
+	{
+		return 31337 * GetTypeHash(Vec.X) + 13 * GetTypeHash(Vec.Y) + 3 * GetTypeHash(Vec.Z);
+	}
+
+	static FORCEINLINE uint32 HashValue(const Vertex& Vert)
+	{
+		return HashValue(Vert.Position);
+	}
+
+	struct Edge
+	{
+	private:
+		uint32 IndexFrom;
+		uint32 IndexTo;
+
+		Vertex VertexFrom;
+		Vertex VertexTo;
+
+		uint32 CachedHash;
+
+	public:
+		Edge() : CachedHash(0) { }
+		Edge(uint32 InIndexFrom, uint32 InIndexTo, const Vertex& InVertexFrom, const Vertex& InVertexTo)
+			: IndexFrom(InIndexFrom), IndexTo(InIndexTo), VertexFrom(InVertexFrom), VertexTo(InVertexTo)
+		{
+			// Hash should only consider position, not index. 
+			// We want values with different indices to compare true.
+			CachedHash = 7 * HashValue(VertexFrom) + 2 * HashValue(VertexTo);
+		}
+
+		Vertex GetVertex(uint32 I) const
+		{
+			switch (I)
+			{
+			case 0:
+				return VertexFrom;
+			case 1:
+				return VertexTo;
+			default:
+				checkNoEntry();
+				return Vertex();
+			}
+		}
+
+		uint32 GetIndex(uint32 I) const
+		{
+			switch (I)
+			{
+			case 0:
+				return IndexFrom;
+			case 1:
+				return IndexTo;
+			default:
+				checkNoEntry();
+				return 0;
+			}
+		}
+
+		Edge GetReverse() const
+		{
+			return Edge(IndexTo, IndexFrom, VertexTo, VertexFrom);
+		}
+
+		FORCEINLINE bool operator<(const Edge& Other) const
+		{
+			// Quick out, otherwise we have to compare vertices
+			if (IndexFrom == Other.IndexFrom && IndexTo == Other.IndexTo)
+			{
+				return false;
+			}
+
+			return VertexFrom < Other.VertexFrom || VertexTo < Other.VertexTo;
+		}
+
+		FORCEINLINE bool operator==(const Edge& Other) const
+		{
+			return (IndexFrom == Other.IndexFrom && IndexTo == Other.IndexTo) ||
+				(VertexFrom == Other.VertexFrom && VertexTo == Other.VertexTo);
+		}
+
+		friend FORCEINLINE uint32 GetTypeHash(const Edge& E)
+		{
+			return E.CachedHash;
+		}
+	};
+
+	struct Corner
+	{
+		uint32 Index;
+		FVector2D TexCoord;
+
+		Corner() : Index(0) { }
+		Corner(uint32 InIndex, FVector2D InTexCoord)
+			: Index(InIndex), TexCoord(InTexCoord)
+		{ }
+	};
+
+	class Triangle
+	{
+		Edge Edge0;
+		Edge Edge1;
+		Edge Edge2;
+
+	public:
+		Triangle(uint32 Index0, uint32 Index1, uint32 Index2, const Vertex& Vertex0, const Vertex& Vertex1, const Vertex& Vertex2)
+			: Edge0(Index0, Index1, Vertex0, Vertex1)
+			, Edge1(Index1, Index2, Vertex1, Vertex2)
+			, Edge2(Index2, Index0, Vertex2, Vertex0)
+		{ }
+
+		FORCEINLINE bool operator<(const Triangle& Other) const
+		{
+			return Edge0 < Other.Edge0 || Edge1 < Other.Edge1 || Edge2 < Other.Edge2;
+		}
+
+		FORCEINLINE const Edge& GetEdge(uint32 I)
+		{
+			return ((Edge*)&Edge0)[I];
+		}
+		FORCEINLINE uint32 GetIndex(uint32 I)
+		{
+			return GetEdge(I).GetIndex(0);
+		}
+
+	};
+	using EdgeDictionary = TMap<Edge, Edge>;
+	using PositionDictionary = TMap<FVector, Corner>;
+
+	static void AddIfLeastUV(PositionDictionary& PosDict, const Vertex& Vert, uint32 Index);
+
+	static void ReplacePlaceholderIndices(const IRuntimeMeshVerticesBuilder* Vertices, const FRuntimeMeshIndicesBuilder* Indices,
+		EdgeDictionary& EdgeDict, PositionDictionary& PosDict, FRuntimeMeshIndicesBuilder* OutIndices);
+
+	static void ExpandIB(const IRuntimeMeshVerticesBuilder* Vertices, const FRuntimeMeshIndicesBuilder* Indices,
+		EdgeDictionary& OutEdgeDict, PositionDictionary& OutPosDict, FRuntimeMeshIndicesBuilder* OutIndices);
+};

+ 1215 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshAsync.h

@@ -0,0 +1,1215 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#pragma once
+
+#include "RuntimeMeshCore.h"
+#include "RuntimeMeshComponent.h"
+
+#define RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(TaskType, DataType, DataPtr, RuntimeMesh, Code)														\
+	class FParallelRuntimeMeshComponentTask_##TaskType																							\
+	{																																			\
+		TWeakObjectPtr<URuntimeMeshComponent> RuntimeMeshComponent;																				\
+		DataType* RMCCallData;																															\
+	public:																																		\
+		FParallelRuntimeMeshComponentTask_##TaskType(TWeakObjectPtr<URuntimeMeshComponent> InRMC, DataType* InData)			\
+			: RuntimeMeshComponent(InRMC), RMCCallData(InData)																		\
+		{																																		\
+		}																																		\
+																																				\
+		~FParallelRuntimeMeshComponentTask_##TaskType()																							\
+		{																																		\
+			if (RMCCallData != nullptr)																											\
+			{																																	\
+				delete RMCCallData;																												\
+			}																																	\
+		}																																		\
+																																				\
+		FORCEINLINE TStatId GetStatId() const																									\
+		{																																		\
+			RETURN_QUICK_DECLARE_CYCLE_STAT(FParallelRuntimeMeshComponentTask_##TaskType, STATGROUP_TaskGraphTasks);							\
+		}																																		\
+																																				\
+		static ENamedThreads::Type GetDesiredThread()																							\
+		{																																		\
+			return ENamedThreads::GameThread;																									\
+		}																																		\
+																																				\
+		static ESubsequentsMode::Type GetSubsequentsMode()																						\
+		{																																		\
+			return ESubsequentsMode::FireAndForget;																								\
+		}																																		\
+																																				\
+		void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)											\
+		{																																		\
+			DataType* Data = RMCCallData;																										\
+			if (URuntimeMeshComponent* Mesh = RuntimeMeshComponent.Get())																		\
+			{																																	\
+				Code																															\
+			}																																	\
+		}																																		\
+	};																																			\
+	TGraphTask<FParallelRuntimeMeshComponentTask_##TaskType>::CreateTask().ConstructAndDispatchWhenReady(RuntimeMesh, DataPtr);
+
+
+
+
+class RUNTIMEMESHCOMPONENT_API FRuntimeMeshAsync
+{
+
+
+
+public:
+
+	/**
+	*	Create/replace a section.
+	*	@param	SectionIndex		Index of the section to create or replace.
+	*	@param	Vertices			Vertex buffer all vertex data for this section.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	bCreateCollision	Indicates whether collision should be created for this section. This adds significant cost.
+	*	@param	UpdateFrequency		Indicates how frequently the section will be updated. Allows the RMC to optimize itself to a particular use.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	static void CreateMeshSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, TArray<VertexType>& Vertices, TArray<int32>& Triangles, 
+		bool bCreateCollision = false, EUpdateFrequency UpdateFrequency = EUpdateFrequency::Average, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<VertexType> Vertices;
+			TArray<int32> Triangles;
+			bool bCreateCollision;
+			EUpdateFrequency UpdateFrequency;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->Vertices = MoveTemp(Vertices);
+			CallData->Triangles = MoveTemp(Triangles);
+		}
+		else
+		{
+			CallData->Vertices = Vertices;
+			CallData->Triangles = Triangles;
+		}
+		CallData->bCreateCollision = bCreateCollision;
+		CallData->UpdateFrequency = UpdateFrequency;
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(CreateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->CreateMeshSection(Data->SectionIndex, Data->Vertices, Data->Triangles, Data->bCreateCollision, Data->UpdateFrequency, Data->UpdateFlags);
+		});
+	}
+
+
+	/**
+	*	Create/replace a section.
+	*	@param	SectionIndex		Index of the section to create or replace.
+	*	@param	Vertices			Vertex buffer all vertex data for this section.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	BoundingBox			The bounds of this section. Faster than the RMC automatically calculating it.
+	*	@param	bCreateCollision	Indicates whether collision should be created for this section. This adds significant cost.
+	*	@param	UpdateFrequency		Indicates how frequently the section will be updated. Allows the RMC to optimize itself to a particular use.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	static void CreateMeshSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, TArray<VertexType>& Vertices, TArray<int32>& Triangles,
+		const FBox& BoundingBox, bool bCreateCollision = false, EUpdateFrequency UpdateFrequency = EUpdateFrequency::Average, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<VertexType> Vertices;
+			TArray<int32> Triangles;
+			FBox BoundingBox;
+			bool bCreateCollision;
+			EUpdateFrequency UpdateFrequency;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->Vertices = MoveTemp(Vertices);
+			CallData->Triangles = MoveTemp(Triangles);
+		}
+		else
+		{
+			CallData->Vertices = Vertices;
+			CallData->Triangles = Triangles;
+		}
+		CallData->BoundingBox = BoundingBox;
+		CallData->bCreateCollision = bCreateCollision;
+		CallData->UpdateFrequency = UpdateFrequency;
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(CreateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->CreateMeshSection(Data->SectionIndex, Data->Vertices, Data->Triangles, Data->BoundingBox, Data->bCreateCollision, Data->UpdateFrequency, Data->UpdateFlags);
+		});
+	}
+
+	/**
+	*	Create/replace a section using 2 vertex buffers. One contains positions only, the other contains all other data. This allows for very efficient updates of the positions of a mesh.
+	*	@param	SectionIndex		Index of the section to create or replace.
+	*	@param	VertexPositions		Vertex buffer containing only the position information for each vertex.
+	*	@param	VertexData			Vertex buffer containing everything except position for each vertex.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	bCreateCollision	Indicates whether collision should be created for this section. This adds significant cost.
+	*	@param	UpdateFrequency		Indicates how frequently the section will be updated. Allows the RMC to optimize itself to a particular use.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	static void CreateMeshSectionDualBuffer(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, TArray<FVector>& VertexPositions,
+		TArray<VertexType>& VertexData, TArray<int32>& Triangles, bool bCreateCollision = false,
+		EUpdateFrequency UpdateFrequency = EUpdateFrequency::Average, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<FVector> VertexPositions;
+			TArray<VertexType> Vertices;
+			TArray<int32> Triangles;
+			bool bCreateCollision;
+			EUpdateFrequency UpdateFrequency;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->VertexPositions = MoveTemp(VertexPositions);
+			CallData->Vertices = MoveTemp(VertexData);
+			CallData->Triangles = MoveTemp(Triangles);
+		}
+		else
+		{
+			CallData->VertexPositions = VertexPositions;
+			CallData->Vertices = VertexData;
+			CallData->Triangles = Triangles;
+		}
+		CallData->bCreateCollision = bCreateCollision;
+		CallData->UpdateFrequency = UpdateFrequency;
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(CreateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->CreateMeshSection(Data->SectionIndex, Data->VertexPositions, Data->Vertices, Data->Triangles, Data->bCreateCollision, Data->UpdateFrequency, Data->UpdateFlags);
+		});
+	}
+
+	/**
+	*	Create/replace a section using 2 vertex buffers. One contains positions only, the other contains all other data. This allows for very efficient updates of the positions of a mesh.
+	*	@param	SectionIndex		Index of the section to create or replace.
+	*	@param	VertexPositions		Vertex buffer containing only the position information for each vertex.
+	*	@param	VertexData			Vertex buffer containing everything except position for each vertex.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	BoundingBox			The bounds of this section. Faster than the RMC automatically calculating it.
+	*	@param	bCreateCollision	Indicates whether collision should be created for this section. This adds significant cost.
+	*	@param	UpdateFrequency		Indicates how frequently the section will be updated. Allows the RMC to optimize itself to a particular use.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	static void CreateMeshSectionDualBuffer(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, TArray<FVector>& VertexPositions,
+		TArray<VertexType>& VertexData, TArray<int32>& Triangles, const FBox& BoundingBox,
+		bool bCreateCollision = false, EUpdateFrequency UpdateFrequency = EUpdateFrequency::Average, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<FVector> VertexPositions;
+			TArray<VertexType> Vertices;
+			TArray<int32> Triangles;
+			FBox BoundingBox;
+			bool bCreateCollision;
+			EUpdateFrequency UpdateFrequency;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->VertexPositions = MoveTemp(VertexPositions);
+			CallData->Vertices = MoveTemp(VertexData);
+			CallData->Triangles = MoveTemp(Triangles);
+		}
+		else
+		{
+			CallData->VertexPositions = VertexPositions;
+			CallData->Vertices = VertexData;
+			CallData->Triangles = Triangles;
+		}
+		CallData->BoundingBox = BoundingBox;
+		CallData->bCreateCollision = bCreateCollision;
+		CallData->UpdateFrequency = UpdateFrequency;
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(CreateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->CreateMeshSection(Data->SectionIndex, Data->VertexPositions, Data->Vertices, Data->Triangles, Data->BoundingBox, Data->bCreateCollision, Data->UpdateFrequency, Data->UpdateFlags);
+		});
+	}
+
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. If this is a dual buffer section, you cannot change the length of the vertices.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer all vertex data for this section, or in the case of dual buffer section it contains everything but position.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	static void UpdateMeshSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, TArray<VertexType>& Vertices,
+		ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<VertexType> Vertices;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->Vertices = MoveTemp(Vertices);
+		}
+		else
+		{
+			CallData->Vertices = Vertices;
+		}
+
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(UpdateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->UpdateMeshSection(Data->SectionIndex, Data->Vertices, Data->UpdateFlags);
+		});
+	}
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. If this is a dual buffer section, you cannot change the length of the vertices.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer all vertex data for this section, or in the case of dual buffer section it contains everything but position.
+	*	@param	BoundingBox			The bounds of this section. Faster than the RMC automatically calculating it.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	static void UpdateMeshSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, TArray<VertexType>& Vertices,
+		const FBox& BoundingBox, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<VertexType> Vertices;
+			FBox BoundingBox;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->Vertices = MoveTemp(Vertices);
+		}
+		else
+		{
+			CallData->Vertices = Vertices;
+		}
+		
+		CallData->BoundingBox = BoundingBox;
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(UpdateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->UpdateMeshSection(Data->SectionIndex, Data->Vertices, Data->BoundingBox, Data->UpdateFlags);
+		});
+	}
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. If this is a dual buffer section, you cannot change the length of the vertices.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer all vertex data for this section, or in the case of dual buffer section it contains everything but position.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	static void UpdateMeshSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, TArray<VertexType>& Vertices,
+		TArray<int32>& Triangles, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<VertexType> Vertices;
+			TArray<int32> Triangles;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->Vertices = MoveTemp(Vertices);
+			CallData->Triangles = MoveTemp(Triangles);
+		}
+		else
+		{
+			CallData->Vertices = Vertices;
+			CallData->Triangles = Triangles;
+		}
+
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(UpdateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->UpdateMeshSection(Data->SectionIndex, Data->Vertices, Data->Triangles, Data->UpdateFlags);
+		});
+	}
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. If this is a dual buffer section, you cannot change the length of the vertices.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer all vertex data for this section, or in the case of dual buffer section it contains everything but position.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	BoundingBox			The bounds of this section. Faster than the RMC automatically calculating it.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	static void UpdateMeshSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, TArray<VertexType>& Vertices,
+		TArray<int32>& Triangles, const FBox& BoundingBox, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<VertexType> Vertices;
+			TArray<int32> Triangles;
+			FBox BoundingBox;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->Vertices = MoveTemp(Vertices);
+			CallData->Triangles = MoveTemp(Triangles);
+		}
+		else
+		{
+			CallData->Vertices = Vertices;
+			CallData->Triangles = Triangles;
+		}
+
+		CallData->BoundingBox = BoundingBox;
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(UpdateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->UpdateMeshSection(Data->SectionIndex, Data->Vertices, Data->Triangles, Data->BoundingBox, Data->UpdateFlags);
+		});
+	}
+
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. This is only for dual buffer sections. You cannot change the length of positions or vertex data unless you specify both together.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	VertexPositions		Vertex buffer containing only the position information for each vertex.
+	*	@param	Vertices			Vertex buffer containing everything except position for each vertex.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	static void UpdateMeshSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, TArray<FVector>& VertexPositions,
+		TArray<VertexType>& Vertices, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<FVector> VertexPositions;
+			TArray<VertexType> Vertices;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->VertexPositions = MoveTemp(VertexPositions);
+			CallData->Vertices = MoveTemp(Vertices);
+		}
+		else
+		{
+			CallData->VertexPositions = VertexPositions;
+			CallData->Vertices = Vertices;
+		}
+
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(UpdateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->UpdateMeshSection(Data->SectionIndex, Data->VertexPositions, Data->Vertices, Data->UpdateFlags);
+		});
+	}
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. This is only for dual buffer sections. You cannot change the length of positions or vertex data unless you specify both together.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	VertexPositions		Vertex buffer containing only the position information for each vertex.
+	*	@param	Vertices			Vertex buffer containing everything except position for each vertex.
+	*	@param	BoundingBox			The bounds of this section. Faster than the RMC automatically calculating it.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	static void UpdateMeshSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, TArray<FVector>& VertexPositions,
+		TArray<VertexType>& Vertices, const FBox& BoundingBox, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<FVector> VertexPositions;
+			TArray<VertexType> Vertices;
+			FBox BoundingBox;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->VertexPositions = MoveTemp(VertexPositions);
+			CallData->Vertices = MoveTemp(Vertices);
+		}
+		else
+		{
+			CallData->VertexPositions = VertexPositions;
+			CallData->Vertices = Vertices;
+		}
+
+		CallData->BoundingBox = BoundingBox;
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(UpdateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->UpdateMeshSection(Data->SectionIndex, Data->VertexPositions, Data->Vertices, Data->BoundingBox, Data->UpdateFlags);
+		});
+	}
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. This is only for dual buffer sections. You cannot change the length of positions or vertex data unless you specify both together.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	VertexPositions		Vertex buffer containing only the position information for each vertex.
+	*	@param	Vertices			Vertex buffer containing everything except position for each vertex.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	static void UpdateMeshSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, TArray<FVector>& VertexPositions,
+		TArray<VertexType>& Vertices, TArray<int32>& Triangles, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<FVector> VertexPositions;
+			TArray<VertexType> Vertices;
+			TArray<int32> Triangles;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->VertexPositions = MoveTemp(VertexPositions);
+			CallData->Vertices = MoveTemp(Vertices);
+			CallData->Triangles = MoveTemp(Triangles);
+		}
+		else
+		{
+			CallData->VertexPositions = VertexPositions;
+			CallData->Vertices = Vertices;
+			CallData->Triangles = Triangles;
+		}
+
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(UpdateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->UpdateMeshSection(Data->SectionIndex, Data->VertexPositions, Data->Vertices, Data->Triangles, Data->UpdateFlags);
+		});
+	}
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. This is only for dual buffer sections. You cannot change the length of positions or vertex data unless you specify both together.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	VertexPositions		Vertex buffer containing only the position information for each vertex.
+	*	@param	Vertices			Vertex buffer containing everything except position for each vertex.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	BoundingBox			The bounds of this section. Faster than the RMC automatically calculating it.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	static void UpdateMeshSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, TArray<FVector>& VertexPositions,
+		TArray<VertexType>& Vertices, TArray<int32>& Triangles, const FBox& BoundingBox, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<FVector> VertexPositions;
+			TArray<VertexType> Vertices;
+			TArray<int32> Triangles;
+			FBox BoundingBox;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->VertexPositions = MoveTemp(VertexPositions);
+			CallData->Vertices = MoveTemp(Vertices);
+			CallData->Triangles = MoveTemp(Triangles);
+		}
+		else
+		{
+			CallData->VertexPositions = VertexPositions;
+			CallData->Vertices = Vertices;
+			CallData->Triangles = Triangles;
+		}
+
+		CallData->BoundingBox = BoundingBox;
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(UpdateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->UpdateMeshSection(Data->SectionIndex, Data->VertexPositions, Data->Vertices, Data->Triangles, Data->BoundingBox, Data->UpdateFlags);
+		});
+	}
+
+
+	/**
+	*	Updates a sections position buffer only. This cannot be used on a non-dual buffer section. You cannot change the length of the vertex position buffer with this function.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	VertexPositions		Vertex buffer containing only the position information for each vertex.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	static void UpdateMeshSectionPositionsImmediate(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex,
+		TArray<FVector>& VertexPositions, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<FVector> VertexPositions;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->VertexPositions = MoveTemp(VertexPositions);
+		}
+		else
+		{
+			CallData->VertexPositions = VertexPositions;
+		}
+
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(UpdateMeshSectionPositionsImmediate, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->UpdateMeshSectionPositionsImmediate(Data->SectionIndex, Data->VertexPositions, Data->UpdateFlags);
+		});
+	}
+
+	/**
+	*	Updates a sections position buffer only. This cannot be used on a non-dual buffer section. You cannot change the length of the vertex position buffer with this function.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	VertexPositions		Vertex buffer containing only the position information for each vertex.
+	*	@param	BoundingBox			The bounds of this section. Faster than the RMC automatically calculating it.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	static void UpdateMeshSectionPositionsImmediate(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex,
+		TArray<FVector>& VertexPositions, const FBox& BoundingBox, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<FVector> VertexPositions;
+			FBox BoundingBox;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->VertexPositions = MoveTemp(VertexPositions);
+		}
+		else
+		{
+			CallData->VertexPositions = VertexPositions;
+		}
+		
+		CallData->BoundingBox = BoundingBox;
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(UpdateMeshSectionPositionsImmediate, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->UpdateMeshSectionPositionsImmediate(Data->SectionIndex, Data->VertexPositions, Data->BoundingBox, Data->UpdateFlags);
+		});
+	}
+
+
+
+
+
+	/**
+	*	Create/replace a section.
+	*	@param	SectionIndex		Index of the section to create or replace.
+	*	@param	Vertices			Vertex buffer of all vertex positions to use for this mesh section.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	Normals				Optional array of normal vectors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	UV0					Optional array of texture co-ordinates for each vertex (UV Channel 0). If supplied, must be same length as Vertices array.
+	*	@param	Colors				Optional array of colors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	Tangents			Optional array of tangent vector for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	bCreateCollision	Indicates whether collision should be created for this section. This adds significant cost.
+	*	@param	UpdateFrequency		Indicates how frequently the section will be updated. Allows the RMC to optimize itself to a particular use.
+	*/
+	static void CreateMeshSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, const TArray<FVector>& Vertices,
+		const TArray<int32>& Triangles, const TArray<FVector>& Normals, const TArray<FVector2D>& UV0, const TArray<FColor>& Colors,
+		const TArray<FRuntimeMeshTangent>& Tangents, bool bCreateCollision = false,
+		EUpdateFrequency UpdateFrequency = EUpdateFrequency::Average, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<FVector> Vertices;
+			TArray<int32> Triangles;
+			TArray<FVector> Normals;
+			TArray<FVector2D> UV0;
+			TArray<FColor> Colors;
+			TArray<FRuntimeMeshTangent> Tangents;
+			bool bCreateCollision = false;
+			EUpdateFrequency UpdateFrequency;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->Vertices = MoveTemp(Vertices);
+			CallData->Triangles = MoveTemp(Triangles);
+			CallData->Normals = MoveTemp(Normals);
+			CallData->UV0 = MoveTemp(UV0);
+			CallData->Colors = MoveTemp(Colors);
+			CallData->Tangents = MoveTemp(Tangents);
+		}
+		else
+		{
+			CallData->Vertices = Vertices;
+			CallData->Triangles = Triangles;
+			CallData->Normals = Normals;
+			CallData->UV0 = UV0;
+			CallData->Colors = Colors;
+			CallData->Tangents = Tangents;
+		}
+
+		CallData->bCreateCollision = bCreateCollision;
+		CallData->UpdateFrequency = UpdateFrequency;
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(CreateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->CreateMeshSection(Data->SectionIndex, Data->Vertices, Data->Triangles, Data->Normals, Data->UV0, Data->Colors, 
+			Data->Tangents, Data->bCreateCollision, Data->UpdateFrequency, Data->UpdateFlags);
+		});
+	}
+
+	/**
+	*	Create/replace a section.
+	*	@param	SectionIndex		Index of the section to create or replace.
+	*	@param	Vertices			Vertex buffer of all vertex positions to use for this mesh section.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	Normals				Optional array of normal vectors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	UV0					Optional array of texture co-ordinates for each vertex (UV Channel 0). If supplied, must be same length as Vertices array.
+	*	@param	UV1					Optional array of texture co-ordinates for each vertex (UV Channel 1). If supplied, must be same length as Vertices array.
+	*	@param	Colors				Optional array of colors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	Tangents			Optional array of tangent vector for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	bCreateCollision	Indicates whether collision should be created for this section. This adds significant cost.
+	*	@param	UpdateFrequency		Indicates how frequently the section will be updated. Allows the RMC to optimize itself to a particular use.
+	*/
+	static void CreateMeshSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, const TArray<FVector>& Vertices,
+		const TArray<int32>& Triangles, const TArray<FVector>& Normals,	const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, 
+		const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents,
+		bool bCreateCollision = false, EUpdateFrequency UpdateFrequency = EUpdateFrequency::Average, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<FVector> Vertices;
+			TArray<int32> Triangles;
+			TArray<FVector> Normals;
+			TArray<FVector2D> UV0;
+			TArray<FVector2D> UV1;
+			TArray<FColor> Colors;
+			TArray<FRuntimeMeshTangent> Tangents;
+			bool bCreateCollision = false;
+			EUpdateFrequency UpdateFrequency;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->Vertices = MoveTemp(Vertices);
+			CallData->Triangles = MoveTemp(Triangles);
+			CallData->Normals = MoveTemp(Normals);
+			CallData->UV0 = MoveTemp(UV0);
+			CallData->UV1 = MoveTemp(UV1);
+			CallData->Colors = MoveTemp(Colors);
+			CallData->Tangents = MoveTemp(Tangents);
+		}
+		else
+		{
+			CallData->Vertices = Vertices;
+			CallData->Triangles = Triangles;
+			CallData->Normals = Normals;
+			CallData->UV0 = UV0;
+			CallData->UV1 = UV1;
+			CallData->Colors = Colors;
+			CallData->Tangents = Tangents;
+		}
+
+		CallData->bCreateCollision = bCreateCollision;
+		CallData->UpdateFrequency = UpdateFrequency;
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(CreateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->CreateMeshSection(Data->SectionIndex, Data->Vertices, Data->Triangles, Data->Normals, Data->UV0, Data->UV1, Data->Colors,
+			Data->Tangents, Data->bCreateCollision, Data->UpdateFrequency, Data->UpdateFlags);
+		});
+	}
+
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer of all vertex positions to use for this mesh section.
+	*	@param	Normals				Optional array of normal vectors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	UV1					Optional array of texture co-ordinates for each vertex (UV Channel 1). If supplied, must be same length as Vertices array.
+	*	@param	Colors				Optional array of colors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	Tangents			Optional array of tangent vector for each vertex. If supplied, must be same length as Vertices array.
+	*/
+	static void UpdateMeshSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, const TArray<FVector>& Vertices,
+		const TArray<FVector>& Normals, const TArray<FVector2D>& UV0, const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents, 
+		ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<FVector> Vertices;
+			TArray<FVector> Normals;
+			TArray<FVector2D> UV0;
+			TArray<FVector2D> UV1;
+			TArray<FColor> Colors;
+			TArray<FRuntimeMeshTangent> Tangents;
+			bool bCreateCollision = false;
+			EUpdateFrequency UpdateFrequency;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->Vertices = MoveTemp(Vertices);
+			CallData->Normals = MoveTemp(Normals);
+			CallData->UV0 = MoveTemp(UV0);
+			CallData->Colors = MoveTemp(Colors);
+			CallData->Tangents = MoveTemp(Tangents);
+		}
+		else
+		{
+			CallData->Vertices = Vertices;
+			CallData->Normals = Normals;
+			CallData->UV0 = UV0;
+			CallData->Colors = Colors;
+			CallData->Tangents = Tangents;
+		}
+
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(UpdateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->UpdateMeshSection(Data->SectionIndex, Data->Vertices, Data->Normals, Data->UV0, Data->Colors,	Data->Tangents, Data->UpdateFlags);
+		});
+	}
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer of all vertex positions to use for this mesh section.
+	*	@param	Normals				Optional array of normal vectors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	UV0					Optional array of texture co-ordinates for each vertex (UV Channel 0). If supplied, must be same length as Vertices array.
+	*	@param	UV1					Optional array of texture co-ordinates for each vertex (UV Channel 1). If supplied, must be same length as Vertices array.
+	*	@param	Colors				Optional array of colors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	Tangents			Optional array of tangent vector for each vertex. If supplied, must be same length as Vertices array.
+	*/
+	static void UpdateMeshSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, const TArray<FVector>& Vertices,
+		const TArray<FVector>& Normals, const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, const TArray<FColor>& Colors, 
+		const TArray<FRuntimeMeshTangent>& Tangents, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<FVector> Vertices;
+			TArray<FVector> Normals;
+			TArray<FVector2D> UV0;
+			TArray<FVector2D> UV1;
+			TArray<FColor> Colors;
+			TArray<FRuntimeMeshTangent> Tangents;
+			bool bCreateCollision = false;
+			EUpdateFrequency UpdateFrequency;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->Vertices = MoveTemp(Vertices);
+			CallData->Normals = MoveTemp(Normals);
+			CallData->UV0 = MoveTemp(UV0);
+			CallData->UV1 = MoveTemp(UV1);
+			CallData->Colors = MoveTemp(Colors);
+			CallData->Tangents = MoveTemp(Tangents);
+		}
+		else
+		{
+			CallData->Vertices = Vertices;
+			CallData->Normals = Normals;
+			CallData->UV0 = UV0;
+			CallData->UV1 = UV1;
+			CallData->Colors = Colors;
+			CallData->Tangents = Tangents;
+		}
+
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(UpdateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->UpdateMeshSection(Data->SectionIndex, Data->Vertices, Data->Normals, Data->UV0, Data->UV1, Data->Colors, Data->Tangents, Data->UpdateFlags);
+		});
+	}
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer of all vertex positions to use for this mesh section.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	Normals				Optional array of normal vectors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	UV0					Optional array of texture co-ordinates for each vertex (UV Channel 0). If supplied, must be same length as Vertices array.
+	*	@param	Colors				Optional array of colors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	Tangents			Optional array of tangent vector for each vertex. If supplied, must be same length as Vertices array.
+	*/
+	static void UpdateMeshSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, const TArray<FVector>& Vertices,
+		const TArray<int32>& Triangles, const TArray<FVector>& Normals,	const TArray<FVector2D>& UV0, const TArray<FColor>& Colors, 
+		const TArray<FRuntimeMeshTangent>& Tangents, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<FVector> Vertices;
+			TArray<int32> Triangles;
+			TArray<FVector> Normals;
+			TArray<FVector2D> UV0;
+			TArray<FColor> Colors;
+			TArray<FRuntimeMeshTangent> Tangents;
+			bool bCreateCollision = false;
+			EUpdateFrequency UpdateFrequency;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->Vertices = MoveTemp(Vertices);
+			CallData->Triangles = MoveTemp(Triangles);
+			CallData->Normals = MoveTemp(Normals);
+			CallData->UV0 = MoveTemp(UV0);
+			CallData->Colors = MoveTemp(Colors);
+			CallData->Tangents = MoveTemp(Tangents);
+		}
+		else
+		{
+			CallData->Vertices = Vertices;
+			CallData->Triangles = Triangles;
+			CallData->Normals = Normals;
+			CallData->UV0 = UV0;
+			CallData->Colors = Colors;
+			CallData->Tangents = Tangents;
+		}
+
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(UpdateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->UpdateMeshSection(Data->SectionIndex, Data->Vertices, Data->Triangles, Data->Normals, Data->UV0, Data->Colors, Data->Tangents, Data->UpdateFlags);
+		});
+	}
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer of all vertex positions to use for this mesh section.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	Normals				Optional array of normal vectors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	UV0					Optional array of texture co-ordinates for each vertex (UV Channel 0). If supplied, must be same length as Vertices array.
+	*	@param	UV1					Optional array of texture co-ordinates for each vertex (UV Channel 1). If supplied, must be same length as Vertices array.
+	*	@param	Colors				Optional array of colors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	Tangents			Optional array of tangent vector for each vertex. If supplied, must be same length as Vertices array.
+	*/
+	static void UpdateMeshSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex, const TArray<FVector>& Vertices,
+		const TArray<int32>& Triangles, const TArray<FVector>& Normals,	const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, 
+		const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<FVector> Vertices;
+			TArray<int32> Triangles;
+			TArray<FVector> Normals;
+			TArray<FVector2D> UV0;
+			TArray<FVector2D> UV1;
+			TArray<FColor> Colors;
+			TArray<FRuntimeMeshTangent> Tangents;
+			bool bCreateCollision = false;
+			EUpdateFrequency UpdateFrequency;
+			ESectionUpdateFlags UpdateFlags;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (!!(UpdateFlags & ESectionUpdateFlags::MoveArrays))
+		{
+			CallData->Vertices = MoveTemp(Vertices);
+			CallData->Triangles = MoveTemp(Triangles);
+			CallData->Normals = MoveTemp(Normals);
+			CallData->UV0 = MoveTemp(UV0);
+			CallData->UV1 = MoveTemp(UV1);
+			CallData->Colors = MoveTemp(Colors);
+			CallData->Tangents = MoveTemp(Tangents);
+		}
+		else
+		{
+			CallData->Vertices = Vertices;
+			CallData->Triangles = Triangles;
+			CallData->Normals = Normals;
+			CallData->UV0 = UV0;
+			CallData->UV1 = UV1;
+			CallData->Colors = Colors;
+			CallData->Tangents = Tangents;
+		}
+
+		CallData->UpdateFlags = UpdateFlags | ESectionUpdateFlags::MoveArrays; // We can always use move arrays here since we either just copied it, or moved it from the original
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(UpdateMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->UpdateMeshSection(Data->SectionIndex, Data->Vertices, Data->Triangles, Data->Normals, Data->UV0, Data->UV1, Data->Colors,	Data->Tangents, Data->UpdateFlags);
+		});
+	}
+
+
+
+	/** Clear a section of the procedural mesh. */
+	static void ClearMeshSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+		};
+
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(ClearMeshSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->ClearMeshSection(Data->SectionIndex);
+		});
+	}
+
+	/** Clear all mesh sections and reset to empty state */
+	static void ClearAllMeshSections(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+		};
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(ClearAllMeshSections, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->ClearAllMeshSections();
+		});
+	}
+
+
+	/** Sets the tessellation triangles needed to correctly support tessellation on a section. */
+	static void SetSectionTessellationTriangles(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 SectionIndex,
+		const TArray<int32>& TessellationTriangles, bool bShouldMoveArray = false)
+	{
+		struct FRMCAsyncData
+		{
+			int32 SectionIndex;
+			TArray<int32> TessellationTriangles;
+		};
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->SectionIndex = SectionIndex;
+
+		if (bShouldMoveArray)
+		{
+			CallData->TessellationTriangles = MoveTemp(TessellationTriangles);
+		}
+		else
+		{
+			CallData->TessellationTriangles = TessellationTriangles;
+		}
+
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(SetSectionTessellationTriangles, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->SetSectionTessellationTriangles(Data->SectionIndex, Data->TessellationTriangles, true);
+		});
+	}
+
+
+	/** Sets the geometry for a collision only section */
+	static void SetMeshCollisionSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 CollisionSectionIndex,
+		const TArray<FVector>& Vertices, const TArray<int32>& Triangles)
+	{
+		struct FRMCAsyncData
+		{
+			int32 CollisionSectionIndex;
+			TArray<FVector> Vertices;
+			TArray<int32> Triangles;
+		};
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->CollisionSectionIndex = CollisionSectionIndex;
+		CallData->Vertices = Vertices;
+		CallData->Triangles = Triangles;
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(SetMeshCollisionSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->SetMeshCollisionSection(Data->CollisionSectionIndex, Data->Vertices, Data->Triangles);
+		});
+	}
+
+	/** Clears the geometry for a collision only section */
+	static void ClearMeshCollisionSection(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, int32 CollisionSectionIndex)
+	{
+		struct FRMCAsyncData
+		{
+			int32 CollisionSectionIndex;
+		};
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->CollisionSectionIndex = CollisionSectionIndex;
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(ClearMeshCollisionSection, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->ClearMeshCollisionSection(Data->CollisionSectionIndex);
+		});
+	}
+
+	/** Clears the geometry for ALL collision only sections */
+	static void ClearAllMeshCollisionSections(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent)
+	{
+		struct FRMCAsyncData
+		{
+			int32 CollisionSectionIndex;
+		};
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(ClearAllMeshCollisionSections, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->ClearAllMeshCollisionSections();
+		});
+	}
+
+
+	/** Add simple collision convex to this component */
+	static void AddCollisionConvexMesh(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, TArray<FVector> ConvexVerts)
+	{
+		struct FRMCAsyncData
+		{
+			TArray<FVector> ConvexVerts;
+		};
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->ConvexVerts = ConvexVerts;
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(AddCollisionConvexMesh, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->AddCollisionConvexMesh(Data->ConvexVerts);
+		});
+	}
+
+	/** Add simple collision convex to this component */
+	static void ClearCollisionConvexMeshes(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent)
+	{
+		struct FRMCAsyncData
+		{
+			int32 CollisionSectionIndex;
+		};
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(ClearCollisionConvexMeshes, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->ClearCollisionConvexMeshes();
+		});
+	}
+
+	/** Function to replace _all_ simple collision in one go */
+	static void SetCollisionConvexMeshes(TWeakObjectPtr<URuntimeMeshComponent> InRuntimeMeshComponent, const TArray< TArray<FVector> >& ConvexMeshes)
+	{
+		struct FRMCAsyncData
+		{
+			TArray<TArray<FVector>> ConvexMeshes;
+		};
+		FRMCAsyncData* CallData = new FRMCAsyncData;
+		CallData->ConvexMeshes = ConvexMeshes;
+
+		RUNTIMEMESHCOMPONENTASYNC_ENQUEUETASK(SetCollisionConvexMeshes, FRMCAsyncData, CallData, InRuntimeMeshComponent,
+		{
+			Mesh->SetCollisionConvexMeshes(Data->ConvexMeshes);
+		});
+	}
+};

+ 983 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshBuilder.h

@@ -0,0 +1,983 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#pragma once
+
+#include "RuntimeMeshCore.h"
+#include "RuntimeMeshGenericVertex.h"
+
+//////////////////////////////////////////////////////////////////////////
+//	This is a work in progress, it's functional, but could use some improvement
+//////////////////////////////////////////////////////////////////////////
+
+enum class ERuntimeMeshVerticesBuilderType : uint8
+{
+	Component,
+	Packed
+};
+
+
+class IRuntimeMeshVerticesBuilder
+{
+public:
+	IRuntimeMeshVerticesBuilder() { }
+	IRuntimeMeshVerticesBuilder(const IRuntimeMeshVerticesBuilder& Other) = delete;
+	IRuntimeMeshVerticesBuilder& operator=(const IRuntimeMeshVerticesBuilder& Other) = delete;
+	virtual ~IRuntimeMeshVerticesBuilder() { }
+
+	virtual ERuntimeMeshVerticesBuilderType GetBuilderType() const = 0;
+	virtual const FRuntimeMeshVertexTypeInfo* GetVertexType() const = 0;
+
+	virtual bool HasPositionComponent() const = 0;
+	virtual bool HasNormalComponent() const = 0;
+	virtual bool HasTangentComponent() const = 0;
+	virtual bool HasColorComponent() const = 0;
+	virtual bool HasUVComponent(int32 Index) const = 0;
+	virtual bool HasHighPrecisionNormals() const = 0;
+	virtual bool HasHighPrecisionUVs() const = 0;
+
+	virtual void SetPosition(const FVector& InPosition) = 0;
+	virtual void SetNormal(const FVector4& InNormal) = 0;
+	virtual void SetTangent(const FVector& InTangent) = 0;
+	virtual void SetColor(const FColor& InColor) = 0;
+	virtual void SetUV(int32 Index, const FVector2D& InUV) = 0;
+
+	virtual void SetPosition(int32 VertexIndex, const FVector& InPosition) = 0;
+	virtual void SetNormal(int32 VertexIndex, const FVector4& InNormal) = 0;
+	virtual void SetTangent(int32 VertexIndex, const FVector& InTangent) = 0;
+	virtual void SetColor(int32 VertexIndex, const FColor& InColor) = 0;
+	virtual void SetUV(int32 VertexIndex, int32 Index, const FVector2D& InUV) = 0;
+	
+	void SetTangents(const FVector& InTangentX, const FVector& InTangentY, const FVector& InTangentZ)
+	{
+		SetNormal(FVector4(InTangentZ, GetBasisDeterminantSign(InTangentX, InTangentY, InTangentZ)));
+		SetTangent(InTangentX);
+	}
+	void SetTangents(int32 VertexIndex, const FVector& InTangentX, const FVector& InTangentY, const FVector& InTangentZ)
+	{
+		Seek(VertexIndex);
+		SetNormal(FVector4(InTangentZ, GetBasisDeterminantSign(InTangentX, InTangentY, InTangentZ)));
+		SetTangent(InTangentX);
+	}
+
+	virtual FVector GetPosition() const = 0;
+	virtual FVector4 GetNormal() const = 0;
+	virtual FVector GetTangent() const = 0;
+	virtual FColor GetColor() const = 0;
+	virtual FVector2D GetUV(int32 Index) const = 0;
+
+	virtual FVector GetPosition(int32 VertexIndex) const = 0;
+	virtual FVector4 GetNormal(int32 VertexIndex) const = 0;
+	virtual FVector GetTangent(int32 VertexIndex) const = 0;
+	virtual FColor GetColor(int32 VertexIndex) const = 0;
+	virtual FVector2D GetUV(int32 VertexIndex, int32 Index) const = 0;
+
+	virtual int32 Length() const = 0;
+	virtual void Seek(int32 Position) const = 0;
+	void SeekEnd() const
+	{
+		Seek(Length() - 1);
+	}
+	virtual int32 MoveNext() const = 0;
+	virtual int32 MoveNextOrAdd() = 0;
+
+	virtual void Reset() = 0;
+
+	virtual IRuntimeMeshVerticesBuilder* Clone(bool bIncludeData = true) const = 0;
+
+	virtual bool WantsSeparatePositionBuffer() const
+	{
+		return false;
+	}
+};
+
+
+template<typename VertexType>
+class FRuntimeMeshPackedVerticesBuilder : public IRuntimeMeshVerticesBuilder
+{
+private:
+	TArray<VertexType>* Vertices;
+	TArray<FVector>* Positions;
+	int32 CurrentPosition;
+	bool bOwnsVertexArray;
+public:
+
+	FRuntimeMeshPackedVerticesBuilder(bool bWantsSeparatePositions = false)
+		: Vertices(new TArray<VertexType>())
+		, Positions(bWantsSeparatePositions? new TArray<FVector>() : nullptr)
+		, CurrentPosition(-1)
+		, bOwnsVertexArray(true)
+	{ }
+	FRuntimeMeshPackedVerticesBuilder(TArray<VertexType>* InVertices, TArray<FVector>* InPositions = nullptr)
+		: Vertices(InVertices)
+		, Positions(InPositions)
+		, CurrentPosition(-1)
+		, bOwnsVertexArray(false)
+	{ }
+	FRuntimeMeshPackedVerticesBuilder(const FRuntimeMeshPackedVerticesBuilder& Other) = delete;
+	FRuntimeMeshPackedVerticesBuilder& operator=(const FRuntimeMeshPackedVerticesBuilder& Other) = delete;
+	virtual ~FRuntimeMeshPackedVerticesBuilder() override
+	{
+		if (bOwnsVertexArray)
+		{
+			delete Vertices;
+
+			if (Positions)
+			{
+				delete Positions;
+			}
+		}
+	}
+
+
+	virtual ERuntimeMeshVerticesBuilderType GetBuilderType() const
+	{
+		return ERuntimeMeshVerticesBuilderType::Packed;
+	}
+	virtual const FRuntimeMeshVertexTypeInfo* GetVertexType() const
+	{
+		return &VertexType::TypeInfo;
+	}
+
+
+	virtual bool HasPositionComponent() const override { return Positions != nullptr || FRuntimeMeshVertexTraits<VertexType>::HasPosition; }
+	virtual bool HasNormalComponent() const override { return FRuntimeMeshVertexTraits<VertexType>::HasNormal; }
+	virtual bool HasTangentComponent() const override { return FRuntimeMeshVertexTraits<VertexType>::HasTangent; }
+	virtual bool HasColorComponent() const override { return FRuntimeMeshVertexTraits<VertexType>::HasColor; }
+	virtual bool HasUVComponent(int32 Index) const override
+	{
+		switch (Index)
+		{
+		case 0:
+			return FRuntimeMeshVertexTraits<VertexType>::HasUV0;
+		case 1:
+			return FRuntimeMeshVertexTraits<VertexType>::HasUV1;
+		case 2:
+			return FRuntimeMeshVertexTraits<VertexType>::HasUV2;
+		case 3:
+			return FRuntimeMeshVertexTraits<VertexType>::HasUV3;
+		case 4:
+			return FRuntimeMeshVertexTraits<VertexType>::HasUV4;
+		case 5:
+			return FRuntimeMeshVertexTraits<VertexType>::HasUV5;
+		case 6:
+			return FRuntimeMeshVertexTraits<VertexType>::HasUV6;
+		case 7:
+			return FRuntimeMeshVertexTraits<VertexType>::HasUV7;
+		}
+		return false;
+	}
+	virtual bool HasHighPrecisionNormals() const override { return FRuntimeMeshVertexTraits<VertexType>::HasHighPrecisionNormals; }
+	virtual bool HasHighPrecisionUVs() const override { return FRuntimeMeshVertexTraits<VertexType>::HasHighPrecisionUVs; }
+
+	virtual void SetPosition(const FVector& InPosition) override 
+	{ 
+		if (Positions)
+		{
+			(*Positions)[CurrentPosition] = InPosition;
+		}
+		else
+		{
+			SetPositionInternal<VertexType>((*Vertices)[CurrentPosition], InPosition);
+		}		
+	}
+	virtual void SetNormal(const FVector4& InNormal) override { SetNormalInternal<VertexType>((*Vertices)[CurrentPosition], InNormal); }
+	virtual void SetTangent(const FVector& InTangent) override { SetTangentInternal<VertexType>((*Vertices)[CurrentPosition], InTangent); }
+	virtual void SetColor(const FColor& InColor) override { SetColorInternal<VertexType>((*Vertices)[CurrentPosition], InColor); }
+	virtual void SetUV(int32 Index, const FVector2D& InUV)
+	{
+		switch (Index)
+		{
+		case 0:
+			SetUV0Internal<VertexType>((*Vertices)[CurrentPosition], InUV);
+			return;
+		case 1:
+			SetUV1Internal<VertexType>((*Vertices)[CurrentPosition], InUV);
+			return;
+		case 2:
+			SetUV2Internal<VertexType>((*Vertices)[CurrentPosition], InUV);
+			return;
+		case 3:
+			SetUV3Internal<VertexType>((*Vertices)[CurrentPosition], InUV);
+			return;
+		case 4:
+			SetUV4Internal<VertexType>((*Vertices)[CurrentPosition], InUV);
+			return;
+		case 5:
+			SetUV5Internal<VertexType>((*Vertices)[CurrentPosition], InUV);
+			return;
+		case 6:
+			SetUV6Internal<VertexType>((*Vertices)[CurrentPosition], InUV);
+			return;
+		case 7:
+			SetUV7Internal<VertexType>((*Vertices)[CurrentPosition], InUV);
+			return;
+		}
+	}
+
+
+	virtual void SetPosition(int32 VertexIndex, const FVector& InPosition) override
+	{
+		Seek(VertexIndex);
+		if (Positions)
+		{
+			(*Positions)[CurrentPosition] = InPosition;
+		}
+		else
+		{
+			SetPositionInternal<VertexType>((*Vertices)[CurrentPosition], InPosition);
+		}
+	}
+	virtual void SetNormal(int32 VertexIndex, const FVector4& InNormal) override { Seek(VertexIndex); SetNormalInternal<VertexType>((*Vertices)[CurrentPosition], InNormal); }
+	virtual void SetTangent(int32 VertexIndex, const FVector& InTangent) override { Seek(VertexIndex); SetTangentInternal<VertexType>((*Vertices)[CurrentPosition], InTangent); }
+	virtual void SetColor(int32 VertexIndex, const FColor& InColor) override { Seek(VertexIndex); SetColorInternal<VertexType>((*Vertices)[CurrentPosition], InColor); }
+	virtual void SetUV(int32 VertexIndex, int32 Index, const FVector2D& InUV)
+	{
+		Seek(VertexIndex);
+		switch (Index)
+		{
+		case 0:
+			SetUV0Internal<VertexType>((*Vertices)[CurrentPosition], InUV);
+			return;
+		case 1:
+			SetUV1Internal<VertexType>((*Vertices)[CurrentPosition], InUV);
+			return;
+		case 2:
+			SetUV2Internal<VertexType>((*Vertices)[CurrentPosition], InUV);
+			return;
+		case 3:
+			SetUV3Internal<VertexType>((*Vertices)[CurrentPosition], InUV);
+			return;
+		case 4:
+			SetUV4Internal<VertexType>((*Vertices)[CurrentPosition], InUV);
+			return;
+		case 5:
+			SetUV5Internal<VertexType>((*Vertices)[CurrentPosition], InUV);
+			return;
+		case 6:
+			SetUV6Internal<VertexType>((*Vertices)[CurrentPosition], InUV);
+			return;
+		case 7:
+			SetUV7Internal<VertexType>((*Vertices)[CurrentPosition], InUV);
+			return;
+		}
+	}
+
+
+	virtual FVector GetPosition() const override 
+	{ 
+		if (Positions)
+		{
+			return (*Positions)[CurrentPosition];
+		}
+		else
+		{
+			return GetPositionInternal<VertexType>((*Vertices)[CurrentPosition]);
+		}
+	}
+	virtual FVector4 GetNormal() const override { return GetNormalInternal<VertexType>((*Vertices)[CurrentPosition]); }
+	virtual FVector GetTangent() const override { return GetTangentInternal<VertexType>((*Vertices)[CurrentPosition]); }
+	virtual FColor GetColor() const override { return GetColorInternal<VertexType>((*Vertices)[CurrentPosition]); }
+	virtual FVector2D GetUV(int32 Index) const override
+	{
+		switch (Index)
+		{
+		case 0:
+			return GetUV0Internal<VertexType>((*Vertices)[CurrentPosition]);
+		case 1:
+			return GetUV1Internal<VertexType>((*Vertices)[CurrentPosition]);
+		case 2:
+			return GetUV2Internal<VertexType>((*Vertices)[CurrentPosition]);
+		case 3:
+			return GetUV3Internal<VertexType>((*Vertices)[CurrentPosition]);
+		case 4:
+			return GetUV4Internal<VertexType>((*Vertices)[CurrentPosition]);
+		case 5:
+			return GetUV5Internal<VertexType>((*Vertices)[CurrentPosition]);
+		case 6:
+			return GetUV6Internal<VertexType>((*Vertices)[CurrentPosition]);
+		case 7:
+			return GetUV7Internal<VertexType>((*Vertices)[CurrentPosition]);
+		}
+		return FVector2D::ZeroVector;
+	}
+
+
+	virtual FVector GetPosition(int32 VertexIndex) const override
+	{
+		Seek(VertexIndex);
+		if (Positions)
+		{
+			return (*Positions)[CurrentPosition];
+		}
+		else
+		{
+			return GetPositionInternal<VertexType>((*Vertices)[CurrentPosition]);
+		}
+	}
+	virtual FVector4 GetNormal(int32 VertexIndex) const override { Seek(VertexIndex); return GetNormalInternal<VertexType>((*Vertices)[CurrentPosition]); }
+	virtual FVector GetTangent(int32 VertexIndex) const override { Seek(VertexIndex); return GetTangentInternal<VertexType>((*Vertices)[CurrentPosition]); }
+	virtual FColor GetColor(int32 VertexIndex) const override { Seek(VertexIndex); return GetColorInternal<VertexType>((*Vertices)[CurrentPosition]); }
+	virtual FVector2D GetUV(int32 VertexIndex, int32 Index) const override
+	{
+		Seek(VertexIndex);
+		switch (Index)
+		{
+		case 0:
+			return GetUV0Internal<VertexType>((*Vertices)[CurrentPosition]);
+		case 1:
+			return GetUV1Internal<VertexType>((*Vertices)[CurrentPosition]);
+		case 2:
+			return GetUV2Internal<VertexType>((*Vertices)[CurrentPosition]);
+		case 3:
+			return GetUV3Internal<VertexType>((*Vertices)[CurrentPosition]);
+		case 4:
+			return GetUV4Internal<VertexType>((*Vertices)[CurrentPosition]);
+		case 5:
+			return GetUV5Internal<VertexType>((*Vertices)[CurrentPosition]);
+		case 6:
+			return GetUV6Internal<VertexType>((*Vertices)[CurrentPosition]);
+		case 7:
+			return GetUV7Internal<VertexType>((*Vertices)[CurrentPosition]);
+		}
+		return FVector2D::ZeroVector;
+	}
+
+	virtual int32 Length() const override { return Vertices->Num(); }
+	virtual void Seek(int32 Position) const override 
+	{ 
+		const_cast<FRuntimeMeshPackedVerticesBuilder<VertexType>*>(this)->CurrentPosition = Position;
+	}
+	virtual int32 MoveNext() const override
+	{
+		return ++const_cast<FRuntimeMeshPackedVerticesBuilder<VertexType>*>(this)->CurrentPosition;
+	}
+	virtual int32 MoveNextOrAdd() override
+	{
+		CurrentPosition++;
+		if (CurrentPosition >= Vertices->Num())
+		{
+			Vertices->SetNumZeroed(CurrentPosition + 1, false);
+			if (Positions)
+			{
+				Positions->SetNumZeroed(CurrentPosition + 1, false);
+			}
+		}
+		return CurrentPosition;
+	}
+
+	virtual void Reset() override
+	{
+		Vertices->Reset();
+		if (Positions)
+		{
+			Positions->Reset();
+		}
+		CurrentPosition = -1;
+	}
+
+	virtual IRuntimeMeshVerticesBuilder* Clone(bool bIncludeData = true) const override
+	{
+		FRuntimeMeshPackedVerticesBuilder<VertexType>* NewBuilder = new FRuntimeMeshPackedVerticesBuilder(Positions != nullptr);
+
+		if (bIncludeData)
+		{
+			*NewBuilder->Vertices = *Vertices;
+			*NewBuilder->Positions = *Positions;
+			NewBuilder->Seek(0);
+		}
+
+		return NewBuilder;
+	}
+
+	TArray<VertexType>* GetVertices()
+	{
+		return Vertices;
+	}
+
+	TArray<FVector>* GetPositions()
+	{
+		return Positions;
+	}
+
+
+	virtual bool WantsSeparatePositionBuffer() const
+	{
+		return Positions != nullptr;
+	}
+
+private:
+	template<typename Type>
+	FORCEINLINE static typename TEnableIf<FRuntimeMeshVertexTraits<Type>::HasPosition>::Type SetPositionInternal(Type& Vertex, const FVector& Position)
+	{
+		Vertex.Position = Position;
+	}
+	template<typename Type>
+	FORCEINLINE static typename TEnableIf<!FRuntimeMeshVertexTraits<Type>::HasPosition>::Type SetPositionInternal(Type& Vertex, const FVector& Position)
+	{
+
+	}	
+	template<typename Type>
+	FORCEINLINE static typename TEnableIf<FRuntimeMeshVertexTraits<Type>::HasPosition, FVector>::Type GetPositionInternal(const Type& Vertex)
+	{
+		return Vertex.Position;
+	}
+	template<typename Type>
+	FORCEINLINE static typename TEnableIf<!FRuntimeMeshVertexTraits<Type>::HasPosition, FVector>::Type GetPositionInternal(const Type& Vertex)
+	{
+		return FVector::ZeroVector;
+	}
+
+
+	template<typename Type>
+	FORCEINLINE static typename TEnableIf<FRuntimeMeshVertexTraits<Type>::HasNormal>::Type SetNormalInternal(Type& Vertex, const FVector4& Normal)
+	{
+		Vertex.Normal = Normal;
+	}
+	template<typename Type>
+	FORCEINLINE static typename TEnableIf<!FRuntimeMeshVertexTraits<Type>::HasNormal>::Type SetNormalInternal(Type& Vertex, const FVector4& Normal)
+	{
+
+	}
+	template<typename Type>
+	FORCEINLINE static typename TEnableIf<FRuntimeMeshVertexTraits<Type>::HasNormal, FVector4>::Type GetNormalInternal(const Type& Vertex)
+	{
+		return Vertex.Normal;
+	}
+	template<typename Type>
+	FORCEINLINE static typename TEnableIf<!FRuntimeMeshVertexTraits<Type>::HasNormal, FVector4>::Type GetNormalInternal(const Type& Vertex)
+	{
+		return FVector::ZeroVector;
+	}
+
+
+	template<typename Type>
+	FORCEINLINE static typename TEnableIf<FRuntimeMeshVertexTraits<Type>::HasTangent>::Type SetTangentInternal(Type& Vertex, const FVector4& Tangent)
+	{
+		Vertex.Tangent = Tangent;
+	}
+	template<typename Type>
+	FORCEINLINE static typename TEnableIf<!FRuntimeMeshVertexTraits<Type>::HasTangent>::Type SetTangentInternal(Type& Vertex, const FVector4& Tangent)
+	{
+
+	}
+	template<typename Type>
+	FORCEINLINE static typename TEnableIf<FRuntimeMeshVertexTraits<Type>::HasTangent, FVector4>::Type GetTangentInternal(const Type& Vertex)
+	{
+		return Vertex.Tangent;
+	}
+	template<typename Type>
+	FORCEINLINE static typename TEnableIf<!FRuntimeMeshVertexTraits<Type>::HasTangent, FVector4>::Type GetTangentInternal(const Type& Vertex)
+	{
+		return FVector::ZeroVector;
+	}
+
+
+	template<typename Type>
+	FORCEINLINE static typename TEnableIf<FRuntimeMeshVertexTraits<Type>::HasColor>::Type SetColorInternal(Type& Vertex, const FColor& Color)
+	{
+		Vertex.Color = Color;
+	}
+	template<typename Type>
+	FORCEINLINE static typename TEnableIf<!FRuntimeMeshVertexTraits<Type>::HasColor>::Type SetColorInternal(Type& Vertex, const FColor& Color)
+	{
+
+	}
+	template<typename Type>
+	FORCEINLINE static typename TEnableIf<FRuntimeMeshVertexTraits<Type>::HasColor, FColor>::Type GetColorInternal(const Type& Vertex)
+	{
+		return Vertex.Color;
+	}
+	template<typename Type>
+	FORCEINLINE static typename TEnableIf<!FRuntimeMeshVertexTraits<Type>::HasColor, FColor>::Type GetColorInternal(const Type& Vertex)
+	{
+		return FColor::Transparent;
+	}
+
+
+
+#define CreateUVChannelGetSetPair(Index)																											\
+	template<typename Type>																															\
+	FORCEINLINE static typename TEnableIf<FRuntimeMeshVertexTraits<Type>::HasUV##Index>::Type SetUV##Index##Internal(Type& Vertex, const FVector2D& UV##Index)			\
+	{																																				\
+		Vertex.UV##Index = UV##Index;																												\
+	}																																				\
+	template<typename Type>																															\
+	FORCEINLINE static typename TEnableIf<!FRuntimeMeshVertexTraits<Type>::HasUV##Index>::Type SetUV##Index##Internal(Type& Vertex, const FVector2D& UV##Index)			\
+	{																																				\
+	}																																				\
+	template<typename Type>																															\
+	FORCEINLINE static typename TEnableIf<FRuntimeMeshVertexTraits<Type>::HasUV##Index, FVector2D>::Type GetUV##Index##Internal(const Type& Vertex)						\
+	{																																				\
+		return Vertex.UV##Index;																													\
+	}																																				\
+	template<typename Type>																															\
+	FORCEINLINE static typename TEnableIf<!FRuntimeMeshVertexTraits<Type>::HasUV##Index, FVector2D>::Type GetUV##Index##Internal(const Type& Vertex)					\
+	{																																				\
+		return FVector2D::ZeroVector;																												\
+	}																																				
+
+
+CreateUVChannelGetSetPair(0);
+CreateUVChannelGetSetPair(1);
+CreateUVChannelGetSetPair(2);
+CreateUVChannelGetSetPair(3);
+CreateUVChannelGetSetPair(4);
+CreateUVChannelGetSetPair(5);
+CreateUVChannelGetSetPair(6);
+CreateUVChannelGetSetPair(7);
+
+
+#undef CreateUVChannelGetSetPair
+};
+
+class RUNTIMEMESHCOMPONENT_API FRuntimeMeshComponentVerticesBuilder : public IRuntimeMeshVerticesBuilder
+{
+private:
+	TArray<FVector>* Positions;
+	TArray<FVector>* Normals;
+	TArray<FRuntimeMeshTangent>* Tangents;
+	TArray<FColor>* Colors;
+	TArray<FVector2D>* UV0s;
+	TArray<FVector2D>* UV1s;
+	int32 CurrentPosition;
+	bool bOwnsBuffers;
+public:
+
+	FRuntimeMeshComponentVerticesBuilder(bool bInWantsNormal, bool bInWantsTangent, bool bInWantsColor, bool bInWantsUV0, bool bInWantsUV1)
+		: Positions(new TArray<FVector>())
+		, Normals(bInWantsNormal ? new TArray<FVector>() : nullptr)
+		, Tangents(bInWantsTangent ? new TArray<FRuntimeMeshTangent>() : nullptr)
+		, Colors(bInWantsColor ? new TArray<FColor>() : nullptr)
+		, UV0s(bInWantsUV0 ? new TArray<FVector2D>() : nullptr)
+		, UV1s(bInWantsUV1 ? new TArray<FVector2D>() : nullptr)
+		, CurrentPosition(-1)
+		, bOwnsBuffers(true)		
+	{ }
+	FRuntimeMeshComponentVerticesBuilder(TArray<FVector>* InPositions, TArray<FVector>* InNormals, TArray<FRuntimeMeshTangent>* InTangents,
+		TArray<FColor>* InColors, TArray<FVector2D>* InUV0s, TArray<FVector2D>* InUV1s = nullptr)
+		: Positions(InPositions)
+		, Normals(InNormals)
+		, Tangents(InTangents)
+		, Colors(InColors)
+		, UV0s(InUV0s)
+		, UV1s(InUV1s)
+		, CurrentPosition(-1)
+		, bOwnsBuffers(false)
+	{ }
+	FRuntimeMeshComponentVerticesBuilder(const FRuntimeMeshComponentVerticesBuilder& Other) = delete;
+	FRuntimeMeshComponentVerticesBuilder& operator=(const FRuntimeMeshComponentVerticesBuilder& Other) = delete;
+	virtual ~FRuntimeMeshComponentVerticesBuilder() override
+	{
+		if (bOwnsBuffers)
+		{			
+			if (Positions) delete Positions;
+			if (Normals) delete Normals;
+			if (Tangents) delete Tangents;
+			if (Colors) delete Colors;
+			if (UV0s) delete UV0s;
+			if (UV1s) delete UV1s;
+		}
+	}
+
+
+	virtual ERuntimeMeshVerticesBuilderType GetBuilderType() const { return ERuntimeMeshVerticesBuilderType::Packed; }
+	virtual const FRuntimeMeshVertexTypeInfo* GetVertexType() const;
+
+
+	virtual bool HasPositionComponent() const override { return Positions != nullptr; }
+	virtual bool HasNormalComponent() const override { return Normals != nullptr; }
+	virtual bool HasTangentComponent() const override { return Tangents != nullptr; }
+	virtual bool HasColorComponent() const override { return Colors != nullptr; }
+	virtual bool HasUVComponent(int32 Index) const override
+	{
+		switch (Index)
+		{
+		case 0:
+			return UV0s != nullptr;
+		case 1:
+			return UV1s != nullptr;
+		default:
+			return false;
+		}
+	}
+	virtual bool HasHighPrecisionNormals() const override { return false; }
+	virtual bool HasHighPrecisionUVs() const override { return true; }
+
+	virtual void SetPosition(const FVector& InPosition) override 
+	{
+		if (CurrentPosition >= Positions->Num())
+		{
+			Positions->SetNumZeroed(CurrentPosition + 1, false);
+		}
+		(*Positions)[CurrentPosition] = InPosition;
+	}
+	virtual void SetNormal(const FVector4& InNormal) override
+	{
+		if (Normals)
+		{
+			if (CurrentPosition >= Normals->Num())
+			{
+				Normals->SetNumZeroed(CurrentPosition + 1, false);
+			}
+			(*Normals)[CurrentPosition] = InNormal;
+			(*Tangents)[CurrentPosition].bFlipTangentY = InNormal.W < 0.0f;
+		}
+	}
+	virtual void SetTangent(const FVector& InTangent) override
+	{
+		if (Tangents)
+		{
+			if (CurrentPosition >= Tangents->Num())
+			{
+				Tangents->SetNumZeroed(CurrentPosition + 1, false);
+			}
+			(*Tangents)[CurrentPosition].TangentX = InTangent;
+		}
+	}
+	virtual void SetColor(const FColor& InColor) override
+	{
+		if (Colors)
+		{
+			if (CurrentPosition >= Colors->Num())
+			{
+				Colors->SetNumZeroed(CurrentPosition + 1, false);
+			}
+			(*Colors)[CurrentPosition] = InColor;
+		}
+	}
+	virtual void SetUV(int32 Index, const FVector2D& InUV)
+	{
+		switch (Index)
+		{
+		case 0:
+		{
+			if (UV0s)
+			{
+				if (CurrentPosition >= UV0s->Num())
+				{
+					UV0s->SetNumZeroed(CurrentPosition + 1, false);
+				}
+				(*UV0s)[CurrentPosition] = InUV;
+			}
+		}
+		case 1:
+		{
+			if (UV1s)
+			{
+				if (CurrentPosition >= UV1s->Num())
+				{
+					UV1s->SetNumZeroed(CurrentPosition + 1, false);
+				}
+				(*UV1s)[CurrentPosition] = InUV;
+			}
+		}
+		default:
+			return;
+		}
+	}
+
+	virtual void SetPosition(int32 VertexIndex, const FVector& InPosition) override
+	{
+		Seek(VertexIndex);
+		if (CurrentPosition >= Positions->Num())
+		{
+			Positions->SetNumZeroed(CurrentPosition + 1, false);
+		}
+		(*Positions)[CurrentPosition] = InPosition;
+	}
+	virtual void SetNormal(int32 VertexIndex, const FVector4& InNormal) override
+	{
+		Seek(VertexIndex);
+		if (Normals)
+		{
+			if (CurrentPosition >= Normals->Num())
+			{
+				Normals->SetNumZeroed(CurrentPosition + 1, false);
+			}
+			(*Normals)[CurrentPosition] = InNormal;
+			(*Tangents)[CurrentPosition].bFlipTangentY = InNormal.W < 0.0f;
+		}
+	}
+	virtual void SetTangent(int32 VertexIndex, const FVector& InTangent) override
+	{
+		Seek(VertexIndex);
+		if (Tangents)
+		{
+			if (CurrentPosition >= Tangents->Num())
+			{
+				Tangents->SetNumZeroed(CurrentPosition + 1, false);
+			}
+			(*Tangents)[CurrentPosition].TangentX = InTangent;
+		}
+	}
+	virtual void SetColor(int32 VertexIndex, const FColor& InColor) override
+	{
+		Seek(VertexIndex);
+		if (Colors)
+		{
+			if (CurrentPosition >= Colors->Num())
+			{
+				Colors->SetNumZeroed(CurrentPosition + 1, false);
+			}
+			(*Colors)[CurrentPosition] = InColor;
+		}
+	}
+	virtual void SetUV(int32 VertexIndex, int32 Index, const FVector2D& InUV)
+	{
+		Seek(VertexIndex);
+		switch (Index)
+		{
+		case 0:
+		{
+			if (UV0s)
+			{
+				if (CurrentPosition >= UV0s->Num())
+				{
+					UV0s->SetNumZeroed(CurrentPosition + 1, false);
+				}
+				(*UV0s)[CurrentPosition] = InUV;
+			}
+		}
+		case 1:
+		{
+			if (UV1s)
+			{
+				if (CurrentPosition >= UV1s->Num())
+				{
+					UV1s->SetNumZeroed(CurrentPosition + 1, false);
+				}
+				(*UV1s)[CurrentPosition] = InUV;
+			}
+		}
+		default:
+			return;
+		}
+	}
+
+	virtual FVector GetPosition() const override
+	{
+		check(Positions && Positions->Num() > CurrentPosition);
+		return (*Positions)[CurrentPosition];
+	}
+	virtual FVector4 GetNormal() const override
+	{
+		check(Normals && Normals->Num() > CurrentPosition);
+		float W = (Tangents && Tangents->Num() > CurrentPosition) ? ((*Tangents)[CurrentPosition].bFlipTangentY ? -1.0f : 1.0f) : 1.0f;
+		return FVector4((*Normals)[CurrentPosition], W);
+	}
+	virtual FVector GetTangent() const override
+	{
+		check(Tangents && Tangents->Num() > CurrentPosition);
+		return (*Tangents)[CurrentPosition].TangentX;
+	}
+	virtual FColor GetColor() const override
+	{
+		check(Colors && Colors->Num() > CurrentPosition);
+		return (*Colors)[CurrentPosition];
+	}
+	virtual FVector2D GetUV(int32 Index) const override
+	{
+		switch (Index)
+		{
+		case 0:
+			check(UV0s && UV0s->Num() > CurrentPosition);
+			return (*UV0s)[CurrentPosition];
+		case 1:
+			check(UV1s && UV1s->Num() > CurrentPosition);
+			return (*UV1s)[CurrentPosition];
+		}
+		return FVector2D::ZeroVector;
+	}
+
+	virtual FVector GetPosition(int32 VertexIndex) const override
+	{
+		Seek(VertexIndex);
+		check(Positions && Positions->Num() > CurrentPosition);
+		return (*Positions)[CurrentPosition];
+	}
+	virtual FVector4 GetNormal(int32 VertexIndex) const override
+	{
+		Seek(VertexIndex);
+		check(Normals && Normals->Num() > CurrentPosition);
+		float W = (Tangents && Tangents->Num() > CurrentPosition) ? ((*Tangents)[CurrentPosition].bFlipTangentY ? -1.0f : 1.0f) : 1.0f;
+		return FVector4((*Normals)[CurrentPosition], W);
+	}
+	virtual FVector GetTangent(int32 VertexIndex) const override
+	{
+		Seek(VertexIndex);
+		check(Tangents && Tangents->Num() > CurrentPosition);
+		return (*Tangents)[CurrentPosition].TangentX;
+	}
+	virtual FColor GetColor(int32 VertexIndex) const override
+	{
+		Seek(VertexIndex);
+		check(Colors && Colors->Num() > CurrentPosition);
+		return (*Colors)[CurrentPosition];
+	}
+	virtual FVector2D GetUV(int32 VertexIndex, int32 Index) const override
+	{
+		Seek(VertexIndex);
+		switch (Index)
+		{
+		case 0:
+			check(UV0s && UV0s->Num() > CurrentPosition);
+			return (*UV0s)[CurrentPosition];
+		case 1:
+			check(UV1s && UV1s->Num() > CurrentPosition);
+			return (*UV1s)[CurrentPosition];
+		}
+		return FVector2D::ZeroVector;
+	}
+
+
+	virtual int32 Length() const override { return Positions->Num(); }
+	virtual void Seek(int32 Position) const override
+	{
+		const_cast<FRuntimeMeshComponentVerticesBuilder*>(this)->CurrentPosition = Position;
+	}
+	virtual int32 MoveNext() const override
+	{
+		return ++const_cast<FRuntimeMeshComponentVerticesBuilder*>(this)->CurrentPosition;
+	}
+	virtual int32 MoveNextOrAdd() override
+	{
+		return ++CurrentPosition;
+	}
+
+	
+	TArray<FVector>* GetPositions() const { return Positions; }
+	TArray<FVector>* GetNormals() const { return Normals; }
+	TArray<FRuntimeMeshTangent>* GetTangents() const { return Tangents; }
+	TArray<FColor>* GetColors() const { return Colors; }
+	TArray<FVector2D>* GetUV0s() const { return UV0s; }
+	TArray<FVector2D>* GetUV1s() const { return UV1s; }
+
+	virtual void Reset() override
+	{
+		Positions->Reset();
+		Normals->Reset();
+		Tangents->Reset();
+		Colors->Reset();
+		UV0s->Reset();
+		UV1s->Reset();
+		CurrentPosition = -1;
+	}
+
+	virtual IRuntimeMeshVerticesBuilder* Clone(bool bIncludeData = true) const override
+	{
+		FRuntimeMeshComponentVerticesBuilder* NewBuilder = new FRuntimeMeshComponentVerticesBuilder(Normals != nullptr, Tangents != nullptr, Colors != nullptr, UV0s != nullptr, UV1s != nullptr);
+
+		if (bIncludeData)
+		{
+			*NewBuilder->Positions = *Positions;
+			*NewBuilder->Normals = *Normals;
+			*NewBuilder->Tangents = *Tangents;
+			*NewBuilder->Colors = *Colors;
+			*NewBuilder->UV0s = *UV0s;
+			*NewBuilder->UV1s = *UV1s;
+			NewBuilder->Seek(0);
+		}
+
+		return NewBuilder;
+	}
+};
+
+
+
+class FRuntimeMeshIndicesBuilder
+{
+	bool bOwnsIndexArray;
+	TArray<int32>* Indices;
+	int32 CurrentPosition;
+public:
+
+	FRuntimeMeshIndicesBuilder()
+		: bOwnsIndexArray(true), Indices(new TArray<int32>()), CurrentPosition(0)
+	{ }
+	FRuntimeMeshIndicesBuilder(TArray<int32>* InVertices)
+		: bOwnsIndexArray(false), Indices(InVertices), CurrentPosition(0)
+	{ }
+	FRuntimeMeshIndicesBuilder(const FRuntimeMeshIndicesBuilder& Other) = delete;
+	FRuntimeMeshIndicesBuilder& operator=(const FRuntimeMeshIndicesBuilder& Other) = delete;
+	virtual ~FRuntimeMeshIndicesBuilder()
+	{
+		if (bOwnsIndexArray)
+		{
+			delete Indices;
+		}
+	}
+
+	virtual ERuntimeMeshVerticesBuilderType GetBuilderType() const { return ERuntimeMeshVerticesBuilderType::Component; }
+
+
+	void AddTriangle(int32 Index0, int32 Index1, int32 Index2)
+	{
+		if ((CurrentPosition + 3) >= Indices->Num())
+		{
+			Indices->SetNum(CurrentPosition + 3);
+		}
+
+		(*Indices)[CurrentPosition++] = Index0;
+		(*Indices)[CurrentPosition++] = Index1;
+		(*Indices)[CurrentPosition++] = Index2;
+	}
+
+	void AddIndex(int32 Index)
+	{
+		if ((CurrentPosition + 1) >= Indices->Num())
+		{
+			Indices->SetNum(CurrentPosition + 1);
+		}
+		(*Indices)[CurrentPosition++] = Index;
+	}
+	int32 ReadOne() const
+	{
+		return (*Indices)[const_cast<FRuntimeMeshIndicesBuilder*>(this)->CurrentPosition++];
+	}
+
+	int32 GetIndex(int32 Position) const
+	{
+		const_cast<FRuntimeMeshIndicesBuilder*>(this)->CurrentPosition = Position;
+		return (*Indices)[CurrentPosition];
+	}
+
+	int32 TriangleLength() const { return Length() / 3; }
+	int32 Length() const { return Indices->Num(); }
+
+	bool HasRemaining() const { return CurrentPosition < Indices->Num(); }
+	
+	void Seek(int32 Position) const
+	{
+		const_cast<FRuntimeMeshIndicesBuilder*>(this)->CurrentPosition = Position;
+	}
+	void SeekEnd() const
+	{
+		Seek(Length());
+	}
+	void Reset(int32 NewSize = 0)
+	{
+		Indices->Reset(NewSize);
+		CurrentPosition = 0;
+	}
+	void SetNum(int32 NewSize)
+	{
+		Indices->SetNum(NewSize);
+	}
+	
+	FRuntimeMeshIndicesBuilder* Clone(bool bIncludeData = true) const
+	{
+		FRuntimeMeshIndicesBuilder* NewBuilder = new FRuntimeMeshIndicesBuilder();
+
+		if (bIncludeData)
+		{
+			*NewBuilder->Indices = *Indices;
+		}
+		
+		return NewBuilder;
+	}
+
+	TArray<int32>* GetIndices()
+	{
+		return Indices;
+	}
+};

+ 1308 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshComponent.h

@@ -0,0 +1,1308 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#pragma once
+
+#include "Components/MeshComponent.h"
+#include "RuntimeMeshCore.h"
+#include "RuntimeMeshSection.h"
+#include "RuntimeMeshGenericVertex.h"
+#include "RuntimeMeshBuilder.h"
+#include "PhysicsEngine/ConvexElem.h"
+#include "RuntimeMeshComponent.generated.h"
+
+// This set of macros is only meant for argument validation as it will return out of whatever scope.
+#if WITH_EDITOR
+#define RMC_CHECKINGAME_LOGINEDITOR(Condition, Message, RetVal) \
+	{ if (!(Condition)) \
+	{ \
+		Log(TEXT(Message), true); \
+		return RetVal; \
+	} }
+#else
+#define RMC_CHECKINGAME_LOGINEDITOR(Condition, Message, RetVal) \
+	check(Condition && Message);
+#endif
+
+
+#define RMC_VALIDATE_CREATIONPARAMETERS(SectionIndex, Vertices, Triangles, RetVal) \
+		RMC_CHECKINGAME_LOGINEDITOR((SectionIndex >= 0), "SectionIndex cannot be negative.", RetVal); \
+		RMC_CHECKINGAME_LOGINEDITOR((Vertices.Num() > 0), "Vertices length must not be 0.", RetVal); \
+		RMC_CHECKINGAME_LOGINEDITOR((Triangles.Num() > 0), "Triangles length must not be 0", RetVal);
+
+#define RMC_VALIDATE_CREATIONPARAMETERS_DUALBUFFER(SectionIndex, Vertices, Triangles, Positions, RetVal) \
+		RMC_VALIDATE_CREATIONPARAMETERS(SectionIndex, Vertices, Triangles, RetVal) \
+		RMC_CHECKINGAME_LOGINEDITOR((Positions.Num() == Vertices.Num()), "Positions must be the same length as Vertices", RetVal);
+
+#define RMC_VALIDATE_BOUNDINGBOX(BoundingBox, RetVal) \
+		RMC_CHECKINGAME_LOGINEDITOR(BoundingBox.IsValid, "BoundingBox must be valid.", RetVal);
+
+#define RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, RetVal) \
+		RMC_CHECKINGAME_LOGINEDITOR((SectionIndex >= 0), "SectionIndex cannot be negative.", RetVal); \
+		RMC_CHECKINGAME_LOGINEDITOR((SectionIndex < MeshSections.Num() && MeshSections[SectionIndex].IsValid()), "Invalid SectionIndex.", RetVal);
+
+#define RMC_VALIDATE_UPDATEPARAMETERS_INTERNALSECTION(SectionIndex, RetVal) \
+		RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, RetVal) \
+		RMC_CHECKINGAME_LOGINEDITOR((MeshSections[SectionIndex]->bIsLegacySectionType), "Section is not of legacy type.", RetVal);
+		
+#define RMC_VALIDATE_UPDATEPARAMETERS_DUALBUFFER(SectionIndex, RetVal) \
+		RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, RetVal) \
+		RMC_CHECKINGAME_LOGINEDITOR((MeshSections[SectionIndex]->IsDualBufferSection()), "Section is not dual buffer.", RetVal);
+
+
+
+/* 
+*	This tick function is used to drive the collision cooker. It is enabled for one frame when we need to update collision. 
+*	This keeps from cooking on each individual create/update section as the original PMC did
+*/
+USTRUCT()
+struct RUNTIMEMESHCOMPONENT_API FRuntimeMeshComponentPrePhysicsTickFunction : public FTickFunction
+{
+	GENERATED_USTRUCT_BODY()
+
+	/* Target RMC to tick */
+	class URuntimeMeshComponent* Target;
+
+	virtual void ExecuteTick(float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread,
+		const FGraphEventRef& MyCompletionGraphEvent) override;
+
+	virtual FString DiagnosticMessage() override;
+};
+
+template<>
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION <= 15
+struct TStructOpsTypeTraits<FRuntimeMeshComponentPrePhysicsTickFunction> : public TStructOpsTypeTraitsBase
+#else
+struct TStructOpsTypeTraits<FRuntimeMeshComponentPrePhysicsTickFunction> : public TStructOpsTypeTraitsBase2<FRuntimeMeshComponentPrePhysicsTickFunction>
+#endif
+{
+	enum
+	{
+		WithCopy = false
+	};
+};
+
+/**
+*	Component that allows you to specify custom triangle mesh geometry for rendering and collision.
+*/
+UCLASS(HideCategories = (Object, LOD), Meta = (BlueprintSpawnableComponent))
+class RUNTIMEMESHCOMPONENT_API URuntimeMeshComponent : public UMeshComponent, public IInterface_CollisionDataProvider
+{
+	GENERATED_BODY()
+
+private:
+	
+	/* Creates an mesh section of a specified type at the specified index. */
+	template<typename SectionType>
+	TSharedPtr<SectionType> CreateOrResetSection(int32 SectionIndex, bool bWantsSeparatePositionBuffer, bool bInIsLegacySectionType = false)
+	{
+		// Ensure sections array is long enough
+		if (SectionIndex >= MeshSections.Num())
+		{
+			MeshSections.SetNum(SectionIndex + 1, false);
+		}
+
+		// Create new section
+		TSharedPtr<SectionType> NewSection = MakeShareable(new SectionType(bWantsSeparatePositionBuffer));
+		NewSection->bIsLegacySectionType = bInIsLegacySectionType;
+
+		// Store section at index
+		MeshSections[SectionIndex] = NewSection;
+
+		return NewSection;
+	}
+		
+	/* Creates a mesh section of an internal type meant for the generic vertex and the old PMC style API */
+	TSharedPtr<FRuntimeMeshSectionInterface> CreateOrResetSectionLegacyType(int32 SectionIndex, int32 NumUVChannels);
+
+	/* Gets the material for a section or the default material if one's not provided. */
+	UMaterialInterface* GetSectionMaterial(int32 Index)
+	{
+		auto Material = GetMaterial(Index);
+		return Material ? Material : UMaterial::GetDefaultMaterial(MD_Surface);
+	}
+
+
+	/* Finishes creating a section, including entering it for batch updating, or updating the RT directly */
+	void CreateSectionInternal(int32 SectionIndex, ESectionUpdateFlags UpdateFlags);
+
+	/* Finishes updating a section, including entering it for batch updating, or updating the RT directly */
+	void UpdateSectionInternal(int32 SectionIndex, bool bHadVertexPositionsUpdate, bool bHadVertexUpdates, bool bHadIndexUpdates, bool bNeedsBoundsUpdate, ESectionUpdateFlags UpdateFlags);
+
+	/* Finishes updating a sections positions (Only used if section is dual vertex buffer), including entering it for batch updating, or updating the RT directly */
+	void UpdateSectionVertexPositionsInternal(int32 SectionIndex, bool bNeedsBoundsUpdate);
+
+	/* Finishes updating a sections properties, like visible/casts shadow, a*/
+	void UpdateSectionPropertiesInternal(int32 SectionIndex, bool bUpdateRequiresProxyRecreateIfStatic);
+	
+	/* Internal log helper for the templates to be able to use the internal logger */
+	static void Log(FString Text, bool bIsError = false)
+	{
+		if (bIsError)
+		{
+			UE_LOG(RuntimeMeshLog, Error, TEXT("%s"), *Text);
+		}
+		else
+		{
+			UE_LOG(RuntimeMeshLog, Warning, TEXT("%s"), *Text);
+		}
+	}
+
+public:
+	URuntimeMeshComponent(const FObjectInitializer& ObjectInitializer);
+
+	/**
+	*	Create/replace a section.
+	*	@param	SectionIndex		Index of the section to create or replace.
+	*	@param	Vertices			Vertex buffer all vertex data for this section.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	bCreateCollision	Indicates whether collision should be created for this section. This adds significant cost.
+	*	@param	UpdateFrequency		Indicates how frequently the section will be updated. Allows the RMC to optimize itself to a particular use.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	void CreateMeshSection(int32 SectionIndex, TArray<VertexType>& Vertices, TArray<int32>& Triangles, bool bCreateCollision = false, 
+		EUpdateFrequency UpdateFrequency = EUpdateFrequency::Average, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		// It is only safe to call these functions from the game thread.
+		check(IsInGameThread());
+
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_CreateMeshSection_VertexType);
+
+		// Validate all creation parameters
+		RMC_VALIDATE_CREATIONPARAMETERS(SectionIndex, Vertices, Triangles, /*VoidReturn*/);
+
+		// Create the section
+		TSharedPtr<FRuntimeMeshSection<VertexType>> Section = CreateOrResetSection<FRuntimeMeshSection<VertexType>>(SectionIndex, false);
+		
+		// Set the vertex and index buffers
+		bool bShouldUseMove = (UpdateFlags & ESectionUpdateFlags::MoveArrays) != ESectionUpdateFlags::None;
+		Section->UpdateVertexBuffer(Vertices, nullptr, bShouldUseMove);
+		Section->UpdateIndexBuffer(Triangles, bShouldUseMove);
+
+		// Track collision status and update collision information if necessary
+		Section->CollisionEnabled = bCreateCollision;
+		Section->UpdateFrequency = UpdateFrequency;
+
+		// Finalize section.
+		CreateSectionInternal(SectionIndex, UpdateFlags);
+	}
+
+	/**
+	*	Create/replace a section.
+	*	@param	SectionIndex		Index of the section to create or replace.
+	*	@param	Vertices			Vertex buffer all vertex data for this section.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	BoundingBox			The bounds of this section. Faster than the RMC automatically calculating it.
+	*	@param	bCreateCollision	Indicates whether collision should be created for this section. This adds significant cost.
+	*	@param	UpdateFrequency		Indicates how frequently the section will be updated. Allows the RMC to optimize itself to a particular use.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	void CreateMeshSection(int32 SectionIndex, TArray<VertexType>& Vertices, TArray<int32>& Triangles, const FBox& BoundingBox, bool bCreateCollision = false,
+		EUpdateFrequency UpdateFrequency = EUpdateFrequency::Average, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		// It is only safe to call these functions from the game thread.
+		check(IsInGameThread());
+
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_CreateMeshSection_VertexType_WithBoundingBox);
+
+		// Validate all creation parameters
+		RMC_VALIDATE_CREATIONPARAMETERS(SectionIndex, Vertices, Triangles, /*VoidReturn*/);
+		RMC_VALIDATE_BOUNDINGBOX(BoundingBox, /*VoidReturn*/);
+
+		// Create the section
+		TSharedPtr<FRuntimeMeshSection<VertexType>> Section = CreateOrResetSection<FRuntimeMeshSection<VertexType>>(SectionIndex, false);
+
+		// Set the vertex and index buffers
+		bool bShouldUseMove = (UpdateFlags & ESectionUpdateFlags::MoveArrays) != ESectionUpdateFlags::None;
+		Section->UpdateVertexBuffer(Vertices, &BoundingBox, bShouldUseMove);
+		Section->UpdateIndexBuffer(Triangles, bShouldUseMove);
+
+		// Track collision status and update collision information if necessary
+		Section->CollisionEnabled = bCreateCollision;
+		Section->UpdateFrequency = UpdateFrequency;
+
+		// Finalize section.
+		CreateSectionInternal(SectionIndex, UpdateFlags);
+	}
+
+	/**
+	*	Create/replace a section using 2 vertex buffers. One contains positions only, the other contains all other data. This allows for very efficient updates of the positions of a mesh.
+	*	@param	SectionIndex		Index of the section to create or replace.
+	*	@param	VertexPositions		Vertex buffer containing only the position information for each vertex.
+	*	@param	VertexData			Vertex buffer containing everything except position for each vertex.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	bCreateCollision	Indicates whether collision should be created for this section. This adds significant cost.
+	*	@param	UpdateFrequency		Indicates how frequently the section will be updated. Allows the RMC to optimize itself to a particular use.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	void CreateMeshSectionDualBuffer(int32 SectionIndex, TArray<FVector>& VertexPositions, TArray<VertexType>& VertexData, TArray<int32>& Triangles, bool bCreateCollision = false,
+		EUpdateFrequency UpdateFrequency = EUpdateFrequency::Average, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_CreateMeshSectionDualBuffer_VertexType);
+
+		// Validate all creation parameters
+		RMC_VALIDATE_CREATIONPARAMETERS_DUALBUFFER(SectionIndex, VertexData, Triangles, VertexPositions, /*VoidReturn*/);
+
+		TSharedPtr<FRuntimeMeshSection<VertexType>> Section = CreateOrResetSection<FRuntimeMeshSection<VertexType>>(SectionIndex, true);
+
+		bool bShouldUseMove = (UpdateFlags & ESectionUpdateFlags::MoveArrays) != ESectionUpdateFlags::None;
+		Section->UpdateVertexPositionBuffer(VertexPositions, nullptr, bShouldUseMove);
+		Section->UpdateVertexBuffer(VertexData, nullptr, bShouldUseMove);
+		Section->UpdateIndexBuffer(Triangles, bShouldUseMove);
+
+		// Track collision status and update collision information if necessary
+		Section->CollisionEnabled = bCreateCollision;
+		Section->UpdateFrequency = UpdateFrequency;
+
+		// Finalize section.
+		CreateSectionInternal(SectionIndex, UpdateFlags);
+	}
+
+	/**
+	*	Create/replace a section using 2 vertex buffers. One contains positions only, the other contains all other data. This allows for very efficient updates of the positions of a mesh.
+	*	@param	SectionIndex		Index of the section to create or replace.
+	*	@param	VertexPositions		Vertex buffer containing only the position information for each vertex.
+	*	@param	VertexData			Vertex buffer containing everything except position for each vertex.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	BoundingBox			The bounds of this section. Faster than the RMC automatically calculating it.
+	*	@param	bCreateCollision	Indicates whether collision should be created for this section. This adds significant cost.
+	*	@param	UpdateFrequency		Indicates how frequently the section will be updated. Allows the RMC to optimize itself to a particular use.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	void CreateMeshSectionDualBuffer(int32 SectionIndex, TArray<FVector>& VertexPositions, TArray<VertexType>& VertexData, TArray<int32>& Triangles, const FBox& BoundingBox,
+		bool bCreateCollision = false, EUpdateFrequency UpdateFrequency = EUpdateFrequency::Average, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_CreateMeshSectionDualBuffer_VertexType_WithBoundingBox);
+
+		// Validate all creation parameters
+		RMC_VALIDATE_CREATIONPARAMETERS_DUALBUFFER(SectionIndex, VertexData, Triangles, VertexPositions, /*VoidReturn*/);
+		RMC_VALIDATE_BOUNDINGBOX(BoundingBox, /*VoidReturn*/);
+
+		TSharedPtr<FRuntimeMeshSection<VertexType>> Section = CreateOrResetSection<FRuntimeMeshSection<VertexType>>(SectionIndex, true);
+
+		bool bShouldUseMove = (UpdateFlags & ESectionUpdateFlags::MoveArrays) != ESectionUpdateFlags::None;
+		Section->UpdateVertexPositionBuffer(VertexPositions, &BoundingBox, bShouldUseMove);
+		Section->UpdateVertexBuffer(VertexData, nullptr, bShouldUseMove);
+		Section->UpdateIndexBuffer(Triangles, bShouldUseMove);
+
+		// Track collision status and update collision information if necessary
+		Section->CollisionEnabled = bCreateCollision;
+		Section->UpdateFrequency = UpdateFrequency;
+
+		// Finalize section.
+		CreateSectionInternal(SectionIndex, UpdateFlags);
+	}
+	
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. If this is a dual buffer section, you cannot change the length of the vertices.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer all vertex data for this section, or in the case of dual buffer section it contains everything but position.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	void UpdateMeshSection(int32 SectionIndex, TArray<VertexType>& Vertices, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateMeshSection_VertexType);
+
+		// Validate all update parameters
+		RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, /*VoidReturn*/);
+		
+		// Validate section type
+		MeshSections[SectionIndex]->GetVertexType()->EnsureEquals<VertexType>();
+
+		// Cast section to correct type
+		TSharedPtr<FRuntimeMeshSection<VertexType>> Section = StaticCastSharedPtr<FRuntimeMeshSection<VertexType>>(MeshSections[SectionIndex]);
+		
+		// Check dual buffer section status
+		if (Section->IsDualBufferSection() && Vertices.Num() != Section->VertexBuffer.Num())
+		{
+			Log(TEXT("UpdateMeshSection() - Vertices cannot change length unless the positions are updated as well."), true);
+			return;
+		}
+
+		bool bShouldUseMove = (UpdateFlags & ESectionUpdateFlags::MoveArrays) != ESectionUpdateFlags::None;
+		bool bNeedsBoundsUpdate = false;
+
+		// Update vertices if supplied
+		bool bUpdatedVertices = false;
+		if (Vertices.Num() > 0)
+		{
+			bNeedsBoundsUpdate = Section->UpdateVertexBuffer(Vertices, nullptr, bShouldUseMove);
+			bUpdatedVertices = true;
+		}
+		else
+		{
+			Log(TEXT("UpdateMeshSection() - Vertices empty. They will not be updated."));
+		}
+		
+		// Finalize section update if we have anything to apply
+		if (bUpdatedVertices)
+		{
+			UpdateSectionInternal(SectionIndex, false, bUpdatedVertices, false, bNeedsBoundsUpdate, UpdateFlags);
+		}
+	}
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. If this is a dual buffer section, you cannot change the length of the vertices.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer all vertex data for this section, or in the case of dual buffer section it contains everything but position.
+	*	@param	BoundingBox			The bounds of this section. Faster than the RMC automatically calculating it.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	void UpdateMeshSection(int32 SectionIndex, TArray<VertexType>& Vertices, const FBox& BoundingBox, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateMeshSection_VertexType_WithBoundingBox);
+
+		// Validate all update parameters
+		RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, /*VoidReturn*/);
+		RMC_VALIDATE_BOUNDINGBOX(BoundingBox, /*VoidReturn*/);
+
+		// Validate section type
+		MeshSections[SectionIndex]->GetVertexType()->EnsureEquals<VertexType>();
+
+		// Cast section to correct type
+		TSharedPtr<FRuntimeMeshSection<VertexType>> Section = StaticCastSharedPtr<FRuntimeMeshSection<VertexType>>(MeshSections[SectionIndex]);
+
+		// Check dual buffer section status
+		if (Section->IsDualBufferSection() && Vertices.Num() != Section->VertexBuffer.Num())
+		{
+			Log(TEXT("UpdateMeshSection() - Vertices cannot change length unless the positions are updated as well."), true);
+			return;
+		}
+
+		bool bShouldUseMove = (UpdateFlags & ESectionUpdateFlags::MoveArrays) != ESectionUpdateFlags::None;
+		bool bNeedsBoundsUpdate = false;
+
+		// Update vertices if supplied
+		bool bUpdatedVertices = false;
+		if (Vertices.Num() > 0)
+		{
+			bNeedsBoundsUpdate = Section->UpdateVertexBuffer(Vertices, &BoundingBox, bShouldUseMove);
+			bUpdatedVertices = true;
+		}
+		else
+		{
+			Log(TEXT("UpdateMeshSection() - Vertices empty. They will not be updated."));
+		}
+
+		// Finalize section update if we have anything to apply
+		if (bUpdatedVertices)
+		{
+			UpdateSectionInternal(SectionIndex, false, bUpdatedVertices, false, bNeedsBoundsUpdate, UpdateFlags);
+		}
+	}
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. If this is a dual buffer section, you cannot change the length of the vertices.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer all vertex data for this section, or in the case of dual buffer section it contains everything but position.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	void UpdateMeshSection(int32 SectionIndex, TArray<VertexType>& Vertices, TArray<int32>& Triangles, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateMeshSection_VertexType_WithTriangles);
+
+		// Validate all update parameters
+		RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, /*VoidReturn*/);
+
+		// Validate section type
+		MeshSections[SectionIndex]->GetVertexType()->EnsureEquals<VertexType>();
+
+		// Cast section to correct type
+		TSharedPtr<FRuntimeMeshSection<VertexType>> Section = StaticCastSharedPtr<FRuntimeMeshSection<VertexType>>(MeshSections[SectionIndex]);
+
+		// Check dual buffer section status
+		if (Section->IsDualBufferSection() && Vertices.Num() != Section->VertexBuffer.Num())
+		{
+			Log(TEXT("UpdateMeshSection() - Vertices cannot change length unless the positions are updated as well."), true);
+			return;
+		}
+
+		bool bShouldUseMove = (UpdateFlags & ESectionUpdateFlags::MoveArrays) != ESectionUpdateFlags::None;
+		bool bNeedsBoundsUpdate = false;
+
+		// Update vertices if supplied
+		bool bUpdatedVertices = false;
+		if (Vertices.Num() > 0)
+		{
+			bNeedsBoundsUpdate = Section->UpdateVertexBuffer(Vertices, nullptr, bShouldUseMove);
+			bUpdatedVertices = true;
+		}
+		else
+		{
+			Log(TEXT("UpdateMeshSection() - Vertices empty. They will not be updated."));
+		}
+
+		// Update triangles if supplied
+		bool bUpdatedIndices = false;
+		if (Triangles.Num() > 0)
+		{
+			Section->UpdateIndexBuffer(Triangles, bShouldUseMove);
+			bUpdatedIndices = true;
+		}
+		else
+		{
+			Log(TEXT("UpdateMeshSection() - Triangles empty. They will not be updated."));
+		}
+
+		// Finalize section update if we have anything to apply
+		if (bUpdatedVertices || bUpdatedIndices)
+		{
+			UpdateSectionInternal(SectionIndex, false, bUpdatedVertices, bUpdatedIndices, bNeedsBoundsUpdate, UpdateFlags);
+		}
+	}
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. If this is a dual buffer section, you cannot change the length of the vertices.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer all vertex data for this section, or in the case of dual buffer section it contains everything but position.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	BoundingBox			The bounds of this section. Faster than the RMC automatically calculating it.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	void UpdateMeshSection(int32 SectionIndex, TArray<VertexType>& Vertices, TArray<int32>& Triangles, const FBox& BoundingBox, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateMeshSection_VertexType_WithTrianglesAndBoundinBox);
+
+		// Validate all update parameters
+		RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, /*VoidReturn*/);
+		RMC_VALIDATE_BOUNDINGBOX(BoundingBox, /*VoidReturn*/);
+
+		// Validate section type
+		MeshSections[SectionIndex]->GetVertexType()->EnsureEquals<VertexType>();
+
+		// Cast section to correct type
+		TSharedPtr<FRuntimeMeshSection<VertexType>> Section = StaticCastSharedPtr<FRuntimeMeshSection<VertexType>>(MeshSections[SectionIndex]);
+
+		// Check dual buffer section status
+		if (Section->IsDualBufferSection() && Vertices.Num() != Section->VertexBuffer.Num())
+		{
+			Log(TEXT("UpdateMeshSection() - Vertices cannot change length unless the positions are updated as well."), true);
+			return;
+		}
+
+		bool bShouldUseMove = (UpdateFlags & ESectionUpdateFlags::MoveArrays) != ESectionUpdateFlags::None;
+		bool bNeedsBoundsUpdate = false;
+
+		// Update vertices if supplied
+		bool bUpdatedVertices = false;
+		if (Vertices.Num() > 0)
+		{
+			bNeedsBoundsUpdate = Section->UpdateVertexBuffer(Vertices, &BoundingBox, bShouldUseMove);
+			bUpdatedVertices = true;
+		}
+		else
+		{
+			Log(TEXT("UpdateMeshSection() - Vertices empty. They will not be updated."));
+		}
+
+		// Update indices if supplied
+		bool bUpdatedIndices = false;
+		if (Triangles.Num() > 0)
+		{
+			Section->UpdateIndexBuffer(Triangles, bShouldUseMove);
+			bUpdatedIndices = true;
+		}
+		else
+		{
+			Log(TEXT("UpdateMeshSection() - Triangles empty. They will not be updated."));
+		}
+
+		// Finalize section update if we have anything to apply
+		if (bUpdatedVertices || bUpdatedIndices)
+		{
+			UpdateSectionInternal(SectionIndex, false, bUpdatedVertices, bUpdatedIndices, bNeedsBoundsUpdate, UpdateFlags);
+		}
+	}
+
+	
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. This is only for dual buffer sections. You cannot change the length of positions or vertex data unless you specify both together.
+	*	@param	SectionIndex		Index of the section to update.	
+	*	@param	VertexPositions		Vertex buffer containing only the position information for each vertex.
+	*	@param	VertexData			Vertex buffer containing everything except position for each vertex.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	void UpdateMeshSection(int32 SectionIndex, TArray<FVector>& VertexPositions, TArray<VertexType>& VertexData, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateMeshSection_Dual_VertexType);
+
+		// Validate all update parameters
+		RMC_VALIDATE_UPDATEPARAMETERS_DUALBUFFER(SectionIndex, /*VoidReturn*/);
+
+		// Validate section type
+		MeshSections[SectionIndex]->GetVertexType()->EnsureEquals<VertexType>();
+
+		// Cast section to correct type
+		TSharedPtr<FRuntimeMeshSection<VertexType>> Section = StaticCastSharedPtr<FRuntimeMeshSection<VertexType>>(MeshSections[SectionIndex]);
+
+		// Check dual buffer section status
+		if (Section->IsDualBufferSection() && 
+			VertexData.Num() != Section->VertexBuffer.Num() &&
+			VertexPositions.Num() != VertexData.Num())
+		{
+			Log(TEXT("UpdateMeshSection() - Vertices cannot change length unless the positions are updated as well."), true);
+			return;
+		}
+
+		bool bShouldUseMove = (UpdateFlags & ESectionUpdateFlags::MoveArrays) != ESectionUpdateFlags::None;
+		bool bNeedsBoundsUpdate = false;
+
+		// Update vertex positions if supplied
+		bool bUpdatedVertexPositions = false;
+		if (VertexPositions.Num() > 0)
+		{
+			bNeedsBoundsUpdate = Section->UpdateVertexPositionBuffer(VertexPositions, nullptr, bShouldUseMove);
+			bUpdatedVertexPositions = true;
+		}
+		else
+		{
+			Log(TEXT("UpdatemeshSection() - Vertex positions empty. They will not be updated."));
+		}
+
+		// Update vertices if supplied
+		bool bUpdatedVertices = false;
+		if (VertexData.Num() > 0)
+		{
+			Section->UpdateVertexBuffer(VertexData, nullptr, bShouldUseMove);
+			bUpdatedVertices = true;
+		}
+		else
+		{
+			Log(TEXT("UpdateMeshSection() - Vertices empty. They will not be updated."));
+		}
+
+		// Finalize section update if we have anything to apply
+		if (bUpdatedVertexPositions || bUpdatedVertices)
+		{
+			UpdateSectionInternal(SectionIndex, bUpdatedVertexPositions, bUpdatedVertices, false, bNeedsBoundsUpdate, UpdateFlags);
+		}
+	}
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. This is only for dual buffer sections. You cannot change the length of positions or vertex data unless you specify both together.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	VertexPositions		Vertex buffer containing only the position information for each vertex.
+	*	@param	VertexData			Vertex buffer containing everything except position for each vertex.
+	*	@param	BoundingBox			The bounds of this section. Faster than the RMC automatically calculating it.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	void UpdateMeshSection(int32 SectionIndex, TArray<FVector>& VertexPositions, TArray<VertexType>& VertexData, const FBox& BoundingBox, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateMeshSection_Dual_VertexType_WithBoundingBox);
+
+		// Validate all update parameters
+		RMC_VALIDATE_UPDATEPARAMETERS_DUALBUFFER(SectionIndex, /*VoidReturn*/);
+		RMC_VALIDATE_BOUNDINGBOX(BoundingBox, /*VoidReturn*/);
+
+		// Validate section type
+		MeshSections[SectionIndex]->GetVertexType()->EnsureEquals<VertexType>();
+
+		// Cast section to correct type
+		TSharedPtr<FRuntimeMeshSection<VertexType>> Section = StaticCastSharedPtr<FRuntimeMeshSection<VertexType>>(MeshSections[SectionIndex]);
+
+		// Check dual buffer section status
+		if (Section->IsDualBufferSection() &&
+			VertexData.Num() != Section->VertexBuffer.Num() &&
+			VertexPositions.Num() != VertexData.Num())
+		{
+			Log(TEXT("UpdateMeshSection() - Vertices cannot change length unless the positions are updated as well."), true);
+			return;
+		}
+
+		bool bShouldUseMove = (UpdateFlags & ESectionUpdateFlags::MoveArrays) != ESectionUpdateFlags::None;
+		bool bNeedsBoundsUpdate = false;
+
+		// Update vertex positions if supplied
+		bool bUpdatedVertexPositions = false;
+		if (VertexPositions.Num() > 0)
+		{
+			bNeedsBoundsUpdate = Section->UpdateVertexPositionBuffer(VertexPositions, &BoundingBox, bShouldUseMove);
+			bUpdatedVertexPositions = true;
+		}
+		else
+		{
+			Log(TEXT("UpdatemeshSection() - Vertex positions empty. They will not be updated."));
+		}
+
+		// Update vertices if supplied
+		bool bUpdatedVertices = false;
+		if (VertexData.Num() > 0)
+		{
+			Section->UpdateVertexBuffer(VertexData, nullptr, bShouldUseMove);
+			bUpdatedVertices = true;
+		}
+		else
+		{
+			Log(TEXT("UpdateMeshSection() - Vertices empty. They will not be updated."));
+		}
+
+		// Finalize section update if we have anything to apply
+		if (bUpdatedVertexPositions || bUpdatedVertices)
+		{
+			UpdateSectionInternal(SectionIndex, bUpdatedVertexPositions, bUpdatedVertices, false, bNeedsBoundsUpdate, UpdateFlags);
+		}
+	}
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. This is only for dual buffer sections. You cannot change the length of positions or vertex data unless you specify both together.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	VertexPositions		Vertex buffer containing only the position information for each vertex.
+	*	@param	VertexData			Vertex buffer containing everything except position for each vertex.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	void UpdateMeshSection(int32 SectionIndex, TArray<FVector>& VertexPositions, TArray<VertexType>& VertexData, TArray<int32>& Triangles, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateMeshSection_Dual_VertexType_WithTriangles);
+
+		// Validate all update parameters
+		RMC_VALIDATE_UPDATEPARAMETERS_DUALBUFFER(SectionIndex, /*VoidReturn*/);
+
+		// Validate section type
+		MeshSections[SectionIndex]->GetVertexType()->EnsureEquals<VertexType>();
+
+		// Cast section to correct type
+		TSharedPtr<FRuntimeMeshSection<VertexType>> Section = StaticCastSharedPtr<FRuntimeMeshSection<VertexType>>(MeshSections[SectionIndex]);
+
+		// Check dual buffer section status
+		if (Section->IsDualBufferSection() &&
+			VertexData.Num() != Section->VertexBuffer.Num() &&
+			VertexPositions.Num() != VertexData.Num())
+		{
+			Log(TEXT("UpdateMeshSection() - Vertices cannot change length unless the positions are updated as well."), true);
+			return;
+		}
+
+		bool bShouldUseMove = (UpdateFlags & ESectionUpdateFlags::MoveArrays) != ESectionUpdateFlags::None;
+		bool bNeedsBoundsUpdate = false;
+
+		// Update vertex positions if supplied
+		bool bUpdatedVertexPositions = false;
+		if (VertexPositions.Num() > 0)
+		{
+			bNeedsBoundsUpdate = Section->UpdateVertexPositionBuffer(VertexPositions, nullptr, bShouldUseMove);
+			bUpdatedVertexPositions = true;
+		}
+		else
+		{
+			Log(TEXT("UpdatemeshSection() - Vertex positions empty. They will not be updated."));
+		}
+
+		// Update vertices if supplied
+		bool bUpdatedVertices = false;
+		if (VertexData.Num() > 0)
+		{
+			Section->UpdateVertexBuffer(VertexData, nullptr, bShouldUseMove);
+			bUpdatedVertices = true;
+		}
+		else
+		{
+			Log(TEXT("UpdateMeshSection() - Vertices empty. They will not be updated."));
+		}
+
+		// Update triangles if supplied
+		bool bUpdatedIndices = false;
+		if (Triangles.Num() > 0)
+		{
+			Section->UpdateIndexBuffer(Triangles, bShouldUseMove);
+			bUpdatedIndices = true;
+		}
+		else
+		{
+			Log(TEXT("UpdateMeshSection() - Triangles empty. They will not be updated."));
+		}
+
+		// Finalize section update if we have anything to apply
+		if (bUpdatedVertexPositions || bUpdatedVertices || bUpdatedIndices)
+		{
+			UpdateSectionInternal(SectionIndex, bUpdatedVertexPositions, bUpdatedVertices, bUpdatedIndices, bNeedsBoundsUpdate, UpdateFlags);
+		}
+	}
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. This is only for dual buffer sections. You cannot change the length of positions or vertex data unless you specify both together.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	VertexPositions		Vertex buffer containing only the position information for each vertex.
+	*	@param	VertexData			Vertex buffer containing everything except position for each vertex.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	BoundingBox			The bounds of this section. Faster than the RMC automatically calculating it.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	template<typename VertexType>
+	void UpdateMeshSection(int32 SectionIndex, TArray<FVector>& VertexPositions, TArray<VertexType>& VertexData, TArray<int32>& Triangles, const FBox& BoundingBox, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None)
+	{
+		SCOPE_CYCLE_COUNTER(STAT_RuntimeMesh_UpdateMeshSection_Dual_VertexType_WithTrianglesAndBoundinBox);
+
+		// Validate all update parameters
+		RMC_VALIDATE_UPDATEPARAMETERS_DUALBUFFER(SectionIndex, /*VoidReturn*/);
+		RMC_VALIDATE_BOUNDINGBOX(BoundingBox, /*VoidReturn*/);
+
+		// Validate section type
+		MeshSections[SectionIndex]->GetVertexType()->EnsureEquals<VertexType>();
+
+		// Cast section to correct type
+		TSharedPtr<FRuntimeMeshSection<VertexType>> Section = StaticCastSharedPtr<FRuntimeMeshSection<VertexType>>(MeshSections[SectionIndex]);
+
+		// Check dual buffer section status
+		if (Section->IsDualBufferSection() &&
+			VertexData.Num() != Section->VertexBuffer.Num() &&
+			VertexPositions.Num() != VertexData.Num())
+		{
+			Log(TEXT("UpdateMeshSection() - Vertices cannot change length unless the positions are updated as well."), true);
+			return;
+		}
+
+		bool bShouldUseMove = (UpdateFlags & ESectionUpdateFlags::MoveArrays) != ESectionUpdateFlags::None;
+		bool bNeedsBoundsUpdate = false;
+
+		// Update vertex positions if supplied
+		bool bUpdatedVertexPositions = false;
+		if (VertexPositions.Num() > 0)
+		{
+			bNeedsBoundsUpdate = Section->UpdateVertexPositionBuffer(VertexPositions, &BoundingBox, bShouldUseMove);
+			bUpdatedVertexPositions = true;
+		}
+		else
+		{
+			Log(TEXT("UpdatemeshSection() - Vertex positions empty. They will not be updated."));
+		}
+
+		// Update vertices if supplied
+		bool bUpdatedVertices = false;
+		if (VertexData.Num() > 0)
+		{
+			Section->UpdateVertexBuffer(VertexData, nullptr, bShouldUseMove);
+			bUpdatedVertices = true;
+		}
+		else
+		{
+			Log(TEXT("UpdateMeshSection() - Vertices empty. They will not be updated."));
+		}
+
+		// Update indices if supplied
+		bool bUpdatedIndices = false;
+		if (Triangles.Num() > 0)
+		{
+			Section->UpdateIndexBuffer(Triangles, bShouldUseMove);
+			bUpdatedIndices = true;
+		}
+		else
+		{
+			Log(TEXT("UpdateMeshSection() - Triangles empty. They will not be updated."));
+		}
+
+		// Finalize section update if we have anything to apply
+		if (bUpdatedVertexPositions || bUpdatedVertices || bUpdatedIndices)
+		{
+			UpdateSectionInternal(SectionIndex, bUpdatedVertexPositions, bUpdatedVertices, bUpdatedIndices, bNeedsBoundsUpdate, UpdateFlags);
+		}
+	}
+
+	
+	/**
+	*	Updates a sections position buffer only. This cannot be used on a non-dual buffer section. You cannot change the length of the vertex position buffer with this function.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	VertexPositions		Vertex buffer containing only the position information for each vertex.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	void UpdateMeshSectionPositionsImmediate(int32 SectionIndex, TArray<FVector>& VertexPositions, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None);
+
+	/**
+	*	Updates a sections position buffer only. This cannot be used on a non-dual buffer section. You cannot change the length of the vertex position buffer with this function.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	VertexPositions		Vertex buffer containing only the position information for each vertex.
+	*	@param	BoundingBox			The bounds of this section. Faster than the RMC automatically calculating it.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	void UpdateMeshSectionPositionsImmediate(int32 SectionIndex, TArray<FVector>& VertexPositions, const FBox& BoundingBox, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None);
+	
+
+	/**
+	*	Starts an in place update of vertex positions. 
+	*	@param	SectionIndex		Index of the section to update.
+	*/
+	TArray<FVector>* BeginMeshSectionPositionUpdate(int32 SectionIndex);
+
+	/**
+	*	Finishes an in place update of vertex positions.
+	*	This will push the update to the GPU and calculate the new Bounding Box
+	*	@param	SectionIndex		Index of the section to update.
+	*/
+	void EndMeshSectionPositionUpdate(int32 SectionIndex);
+
+	/**
+	*	Finishes an in place update of vertex positions.
+	*	This will push the update to the GPU
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	BoundingBox			The bounds of this section. Faster than the RMC automatically calculating it.
+	*/
+	void EndMeshSectionPositionUpdate(int32 SectionIndex, const FBox& BoundingBox);
+
+
+	template<typename VertexType>
+	void BeginMeshSectionUpdate(int32 SectionIndex, TArray<VertexType>*& Vertices)
+	{
+		RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, /*VoidReturn*/);
+
+		// Validate section type
+		MeshSections[SectionIndex]->GetVertexType()->EnsureEquals<VertexType>();
+
+		// Cast section to correct type
+		TSharedPtr<FRuntimeMeshSection<VertexType>> Section = StaticCastSharedPtr<FRuntimeMeshSection<VertexType>>(MeshSections[SectionIndex]);
+
+		Vertices = &Section->VertexBuffer;
+	}
+
+	template<typename VertexType>
+	void BeginMeshSectionUpdate(int32 SectionIndex, TArray<VertexType>*& Vertices, TArray<int32>*& Triangles)
+	{
+		RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, /*VoidReturn*/);
+
+		// Validate section type
+		MeshSections[SectionIndex]->GetVertexType()->EnsureEquals<VertexType>();
+
+		// Cast section to correct type
+		TSharedPtr<FRuntimeMeshSection<VertexType>> Section = StaticCastSharedPtr<FRuntimeMeshSection<VertexType>>(MeshSections[SectionIndex]);
+
+		Vertices = &Section->VertexBuffer;
+		Triangles = &Section->IndexBuffer;
+	}
+
+	template<typename VertexType>
+	void BeginMeshSectionUpdate(int32 SectionIndex, TArray<FVector>*& Positions, TArray<VertexType>*& Vertices)
+	{
+		RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, /*VoidReturn*/);
+
+		// Validate section type
+		MeshSections[SectionIndex]->GetVertexType()->EnsureEquals<VertexType>();
+
+		// Cast section to correct type
+		TSharedPtr<FRuntimeMeshSection<VertexType>> Section = StaticCastSharedPtr<FRuntimeMeshSection<VertexType>>(MeshSections[SectionIndex]);
+
+		Positions = &Section->PositionVertexBuffer;
+		Vertices = &Section->VertexBuffer;
+	}
+
+	template<typename VertexType>
+	void BeginMeshSectionUpdate(int32 SectionIndex, TArray<FVector>*& Positions, TArray<VertexType>*& Vertices, TArray<int32>*& Triangles)
+	{
+		RMC_VALIDATE_UPDATEPARAMETERS(SectionIndex, /*VoidReturn*/);
+
+		// Validate section type
+		MeshSections[SectionIndex]->GetVertexType()->EnsureEquals<VertexType>();
+
+		// Cast section to correct type
+		TSharedPtr<FRuntimeMeshSection<VertexType>> Section = StaticCastSharedPtr<FRuntimeMeshSection<VertexType>>(MeshSections[SectionIndex]);
+
+		Positions = &Section->PositionVertexBuffer;
+		Vertices = &Section->VertexBuffer;
+		Triangles = &Section->IndexBuffer;
+	}
+
+
+	void BeginMeshSectionUpdate(int32 SectionIndex, IRuntimeMeshVerticesBuilder*& Vertices, FRuntimeMeshIndicesBuilder*& Indices);
+
+	void EndMeshSectionUpdate(int32 SectionIndex, ERuntimeMeshBuffer UpdatedBuffers, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None);
+
+	void EndMeshSectionUpdate(int32 SectionIndex, ERuntimeMeshBuffer UpdatedBuffers, const FBox& BoundingBox, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None);
+	
+
+	/*
+	*	Gets a readonly pointer to the sections mesh data.
+	*	To be able to edit the section data use BegineMeshSectionUpdate()
+	*/
+	void GetSectionMesh(int32 SectionIndex, const IRuntimeMeshVerticesBuilder*& Vertices, const FRuntimeMeshIndicesBuilder*& Indices);
+
+
+
+
+
+	/**
+	*	Create/replace a section.
+	*	@param	SectionIndex		Index of the section to create or replace.
+	*	@param	Vertices			Vertex buffer of all vertex positions to use for this mesh section.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	Normals				Optional array of normal vectors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	UV0					Optional array of texture co-ordinates for each vertex (UV Channel 0). If supplied, must be same length as Vertices array.
+	*	@param	Colors				Optional array of colors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	Tangents			Optional array of tangent vector for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	bCreateCollision	Indicates whether collision should be created for this section. This adds significant cost.
+	*	@param	UpdateFrequency		Indicates how frequently the section will be updated. Allows the RMC to optimize itself to a particular use.
+	*/
+	void CreateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals,
+		const TArray<FVector2D>& UV0, const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents, bool bCreateCollision = false,
+		EUpdateFrequency UpdateFrequency = EUpdateFrequency::Average, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None);
+
+	/**
+	*	Create/replace a section.
+	*	@param	SectionIndex		Index of the section to create or replace.
+	*	@param	Vertices			Vertex buffer of all vertex positions to use for this mesh section.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	Normals				Optional array of normal vectors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	UV0					Optional array of texture co-ordinates for each vertex (UV Channel 0). If supplied, must be same length as Vertices array.
+	*	@param	UV1					Optional array of texture co-ordinates for each vertex (UV Channel 1). If supplied, must be same length as Vertices array.
+	*	@param	Colors				Optional array of colors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	Tangents			Optional array of tangent vector for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	bCreateCollision	Indicates whether collision should be created for this section. This adds significant cost.
+	*	@param	UpdateFrequency		Indicates how frequently the section will be updated. Allows the RMC to optimize itself to a particular use.
+	*/
+	void CreateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals,
+		const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents,
+		bool bCreateCollision = false, EUpdateFrequency UpdateFrequency = EUpdateFrequency::Average, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None);
+
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. 
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer of all vertex positions to use for this mesh section.
+	*	@param	Normals				Optional array of normal vectors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	UV1					Optional array of texture co-ordinates for each vertex (UV Channel 1). If supplied, must be same length as Vertices array.
+	*	@param	Colors				Optional array of colors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	Tangents			Optional array of tangent vector for each vertex. If supplied, must be same length as Vertices array.
+	*/
+	void UpdateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<FVector>& Normals, const TArray<FVector2D>& UV0, 
+		const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None);
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer of all vertex positions to use for this mesh section.
+	*	@param	Normals				Optional array of normal vectors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	UV0					Optional array of texture co-ordinates for each vertex (UV Channel 0). If supplied, must be same length as Vertices array.
+	*	@param	UV1					Optional array of texture co-ordinates for each vertex (UV Channel 1). If supplied, must be same length as Vertices array.
+	*	@param	Colors				Optional array of colors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	Tangents			Optional array of tangent vector for each vertex. If supplied, must be same length as Vertices array.
+	*/
+	void UpdateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<FVector>& Normals, const TArray<FVector2D>& UV0, 
+		const TArray<FVector2D>& UV1, const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None);
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer of all vertex positions to use for this mesh section.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	Normals				Optional array of normal vectors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	UV0					Optional array of texture co-ordinates for each vertex (UV Channel 0). If supplied, must be same length as Vertices array.
+	*	@param	Colors				Optional array of colors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	Tangents			Optional array of tangent vector for each vertex. If supplied, must be same length as Vertices array.
+	*/
+	void UpdateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals, 
+		const TArray<FVector2D>& UV0, const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None);
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer of all vertex positions to use for this mesh section.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	Normals				Optional array of normal vectors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	UV0					Optional array of texture co-ordinates for each vertex (UV Channel 0). If supplied, must be same length as Vertices array.
+	*	@param	UV1					Optional array of texture co-ordinates for each vertex (UV Channel 1). If supplied, must be same length as Vertices array.
+	*	@param	Colors				Optional array of colors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	Tangents			Optional array of tangent vector for each vertex. If supplied, must be same length as Vertices array.
+	*/
+	void UpdateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals,
+		const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, const TArray<FColor>& Colors, const TArray<FRuntimeMeshTangent>& Tangents, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None);
+
+	
+
+	/**
+	*	Create/replace a section.
+	*	@param	SectionIndex		Index of the section to create or replace.
+	*	@param	Vertices			Vertex buffer of all vertex positions to use for this mesh section.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	Normals				Optional array of normal vectors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	Tangents			Optional array of tangent vector for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	UV0					Optional array of texture co-ordinates for each vertex (UV Channel 0). If supplied, must be same length as Vertices array.
+	*	@param	UV1					Optional array of texture co-ordinates for each vertex (UV Channel 1). If supplied, must be same length as Vertices array.
+	*	@param	Colors				Optional array of colors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	bCreateCollision	Indicates whether collision should be created for this section. This adds significant cost.
+	*	@param	bCalculateNormalTangent	Indicates whether normal/tangent information should be calculated automatically. This can add significant cost.
+	*	@param	bGenerateTessellationTriangles	Indicates whether tessellation supporting triangles should be calculated. This can add significant cost.
+	*	@param	UpdateFrequency		Indicates how frequently the section will be updated. Allows the RMC to optimize itself to a particular use.
+	*/
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh", meta = (DisplayName = "Create Mesh Section", AutoCreateRefTerm = "Normals,Tangents,UV0,UV1,Colors"))
+	void CreateMeshSection_Blueprint(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals, 
+		const TArray<FRuntimeMeshTangent>& Tangents, const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, const TArray<FLinearColor>& Colors, 
+		bool bCreateCollision, bool bCalculateNormalTangent, bool bGenerateTessellationTriangles, EUpdateFrequency UpdateFrequency = EUpdateFrequency::Average);
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. If you change the vertices count, you must update the other components.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer of all vertex positions to use for this mesh section.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	Normals				Optional array of normal vectors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	Tangents			Optional array of tangent vector for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	UV0					Optional array of texture co-ordinates for each vertex (UV Channel 0). If supplied, must be same length as Vertices array.
+	*	@param	UV1					Optional array of texture co-ordinates for each vertex (UV Channel 1). If supplied, must be same length as Vertices array.
+	*	@param	Colors		Optional array of colors for each vertex. If supplied, must be same length as Vertices array.
+	*	@param	bCalculateNormalTangent	Indicates whether normal/tangent information should be calculated automatically. This can add significant cost.
+	*	@param	bGenerateTessellationTriangles	Indicates whether tessellation supporting triangles should be calculated. This can add significant cost.
+	*/
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh", meta = (DisplayName = "Update Mesh Section", AutoCreateRefTerm = "Triangles,Normals,Tangents,UV0,UV1,Colors"))
+	void UpdateMeshSection_Blueprint(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals, 
+		const TArray<FRuntimeMeshTangent>& Tangents, const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, const TArray<FLinearColor>& Colors, bool bCalculateNormalTangent, bool bGenerateTessellationTriangles);
+	
+
+
+	/**
+	*	Create/replace a section.
+	*	@param	SectionIndex		Index of the section to create or replace.
+	*	@param	Vertices			Vertex buffer all vertex data for this section.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	bCreateCollision	Indicates whether collision should be created for this section. This adds significant cost.
+	*	@param	UpdateFrequency		Indicates how frequently the section will be updated. Allows the RMC to optimize itself to a particular use.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	void CreateMeshSection(int32 SectionIndex, IRuntimeMeshVerticesBuilder& Vertices, FRuntimeMeshIndicesBuilder& Indices, bool bCreateCollision = false,
+		EUpdateFrequency UpdateFrequency = EUpdateFrequency::Average, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None);
+
+
+	/**
+	*	Updates a section. This is faster than CreateMeshSection. If this is a dual buffer section, you cannot change the length of the vertices.
+	*	@param	SectionIndex		Index of the section to update.
+	*	@param	Vertices			Vertex buffer all vertex data for this section, or in the case of dual buffer section it contains everything but position.
+	*	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
+	*	@param	UpdateFlags			Flags pertaining to this particular update.
+	*/
+	void UpdateMeshSection(int32 SectionIndex, IRuntimeMeshVerticesBuilder& Vertices, FRuntimeMeshIndicesBuilder& Indices, ESectionUpdateFlags UpdateFlags = ESectionUpdateFlags::None);
+
+
+
+
+	/** Clear a section of the procedural mesh. */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	void ClearMeshSection(int32 SectionIndex);
+
+	/** Clear all mesh sections and reset to empty state */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	void ClearAllMeshSections();
+
+
+	/** Sets the tessellation triangles needed to correctly support tessellation on a section. */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	void SetSectionTessellationTriangles(int32 SectionIndex, const TArray<int32>& TessellationTriangles, bool bShouldMoveArray = false);
+
+
+
+
+
+
+	/** Gets the bounding box of a specific section */
+	bool GetSectionBoundingBox(int32 SectionIndex, FBox& OutBoundingBox);
+
+
+	/** Control visibility of a particular section */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	void SetMeshSectionVisible(int32 SectionIndex, bool bNewVisibility);
+
+	/** Returns whether a particular section is currently visible */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	bool IsMeshSectionVisible(int32 SectionIndex) const;
+
+
+	/** Control whether a particular section casts a shadow */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	void SetMeshSectionCastsShadow(int32 SectionIndex, bool bNewCastsShadow);
+
+	/** Returns whether a particular section is currently casting shadows */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	bool IsMeshSectionCastingShadows(int32 SectionIndex) const;
+
+
+	/** Control whether a particular section has collision */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	void SetMeshSectionCollisionEnabled(int32 SectionIndex, bool bNewCollisionEnabled);
+
+	/** Returns whether a particular section has collision */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	bool IsMeshSectionCollisionEnabled(int32 SectionIndex);
+
+
+	/** Returns number of sections currently created for this component */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	int32 GetNumSections() const;
+
+	/** Returns whether a particular section currently exists */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	bool DoesSectionExist(int32 SectionIndex) const;
+
+	/** Returns first available section index */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	int32 FirstAvailableMeshSectionIndex() const;
+	
+	/** Returns the last in use section index */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	int32 GetLastSectionIndex() const;
+
+
+	/** Sets the geometry for a collision only section */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	void SetMeshCollisionSection(int32 CollisionSectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles);
+
+	/** Clears the geometry for a collision only section */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	void ClearMeshCollisionSection(int32 CollisionSectionIndex);
+
+	/** Clears the geometry for ALL collision only sections */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	void ClearAllMeshCollisionSections();
+
+
+
+	/** Add simple collision convex to this component */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	void AddCollisionConvexMesh(TArray<FVector> ConvexVerts);
+
+	/** Add simple collision convex to this component */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	void ClearCollisionConvexMeshes();
+
+	/** Function to replace _all_ simple collision in one go */
+	void SetCollisionConvexMeshes(const TArray< TArray<FVector> >& ConvexMeshes);
+
+
+	/** Begins a batch of updates, delays updates until you call EndBatchUpdates() */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	void BeginBatchUpdates()
+	{
+		BatchState.StartBatch();
+	}
+
+	/** Ends a batch of updates started with BeginBatchUpdates() */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	void EndBatchUpdates();
+
+	/** Runs any pending collision cook (Not required to call this. This is only if you need to make sure all changes are cooked before doing something)*/
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	void CookCollisionNow();
+
+
+
+	/**
+	*	Controls whether the complex (Per poly) geometry should be treated as 'simple' collision.
+	*	Should be set to false if this component is going to be given simple collision and simulated.
+	*/
+	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "RuntimeMesh")
+	bool bUseComplexAsSimpleCollision;
+
+	/**
+	*	Controls whether the mesh data should be serialized with the component.
+	*/
+	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "RuntimeMesh")
+	bool bShouldSerializeMeshData;
+	
+	/* 
+	*	The current mode of the collision cooker 
+	*	WARNING: This feature will only work in engine version 4.14 or above!
+	*/
+	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RuntimeMesh")
+	ERuntimeMeshCollisionCookingMode CollisionMode;
+	
+	/** Collision data */
+	UPROPERTY(Instanced)
+	class UBodySetup* BodySetup;
+
+	/* Serialize the entire RMC to the supplied archive. */
+	void SerializeRMC(FArchive& Ar);
+
+	/* Serialize the designated section into the supplied archive. */
+	void SerializeRMCSection(FArchive& Ar, int32 SectionIndex);
+
+private:
+
+
+	//~ Begin Interface_CollisionDataProvider Interface
+	virtual bool GetPhysicsTriMeshData(struct FTriMeshCollisionData* CollisionData, bool InUseAllTriData) override;
+	virtual bool ContainsPhysicsTriMeshData(bool InUseAllTriData) const override;
+	virtual bool WantsNegXTriMesh() override { return false; }
+	//~ End Interface_CollisionDataProvider Interface
+
+
+	//~ Begin USceneComponent Interface.
+	virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override;
+	virtual bool IsSupportedForNetworking() const override
+	{
+		return true;
+	}
+	//~ Begin USceneComponent Interface.
+
+	//~ Begin UPrimitiveComponent Interface.
+	virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
+	virtual class UBodySetup* GetBodySetup() override;
+	//~ End UPrimitiveComponent Interface.
+
+	//~ Begin UMeshComponent Interface.
+	virtual int32 GetNumMaterials() const override;
+	//~ End UMeshComponent Interface.
+
+
+
+	/** Update LocalBounds member from the local box of each section */
+	void UpdateLocalBounds(bool bMarkRenderTransform = true);
+	/** Ensure ProcMeshBodySetup is allocated and configured */
+	void EnsureBodySetupCreated();
+	/** Mark collision data as dirty, and re-create on instance if necessary */
+	void UpdateCollision();
+
+	/* Marks the collision for an end of frame update */
+	void MarkCollisionDirty();
+
+	/* Cooks the new collision mesh updating the body */
+	void BakeCollision();
+
+	void UpdateNavigation();
+
+
+	/* Serializes this component */
+	virtual void Serialize(FArchive& Ar) override;
+	void SerializeInternal(FArchive& Ar, bool bForceSaveAll = false);
+	void SerializeLegacy(FArchive& Ar);
+
+	/* Does post load fixups */
+	virtual void PostLoad() override;
+
+	/* Registers the pre-physics tick function used to cook new meshes when necessary */
+	virtual void RegisterComponentTickFunctions(bool bRegister) override;
+
+
+	/* Current state of a batch update. */
+	FRuntimeMeshBatchUpdateState BatchState;
+
+	/* Is the collision in need of a recook? */
+	bool bCollisionDirty;
+
+	/** Array of sections of mesh */	
+	TArray<RuntimeMeshSectionPtr> MeshSections;
+
+	/* Array of collision only mesh sections*/
+	UPROPERTY(Transient)
+	TArray<FRuntimeMeshCollisionSection> MeshCollisionSections;
+
+	/** Convex shapes used for simple collision */
+	UPROPERTY(Transient)
+	TArray<FRuntimeConvexCollisionSection> ConvexCollisionSections;
+
+	/** Local space bounds of mesh */
+	UPROPERTY(Transient)
+	FBoxSphereBounds LocalBounds;
+
+	/* Tick function used to cook collision when needed*/
+	UPROPERTY(Transient)
+	FRuntimeMeshComponentPrePhysicsTickFunction PrePhysicsTick;
+
+
+	friend class FRuntimeMeshSceneProxy;
+	friend struct FRuntimeMeshComponentPrePhysicsTickFunction;
+};

+ 38 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshComponentPlugin.h

@@ -0,0 +1,38 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#pragma once
+
+#include "ModuleManager.h"
+
+/**
+* The public interface to this module
+*/
+class IRuntimeMeshComponentPlugin : public IModuleInterface
+{
+
+public:
+
+	/**
+	* Singleton-like access to this module's interface.  This is just for convenience!
+	* Beware of calling this during the shutdown phase, though.  Your module might have been unloaded already.
+	*
+	* @return Returns singleton instance, loading the module on demand if needed
+	*/
+	static inline IRuntimeMeshComponentPlugin& Get()
+	{
+		return FModuleManager::LoadModuleChecked< IRuntimeMeshComponentPlugin >("RuntimeMeshComponentPlugin");
+	}
+
+	/**
+	* Checks to see if this module is loaded and ready.  It is only valid to call Get() if IsAvailable() returns true.
+	*
+	* @return True if the module is loaded and ready to use
+	*/
+	static inline bool IsAvailable()
+	{
+		return FModuleManager::Get().IsModuleLoaded("RuntimeMeshComponentPlugin");
+	}
+};
+
+
+DECLARE_LOG_CATEGORY_EXTERN(RuntimeMeshLog, Log, All);

+ 422 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshCore.h

@@ -0,0 +1,422 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#pragma once
+
+#include "Engine.h"
+#include "Components/MeshComponent.h"
+#include "RuntimeMeshProfiling.h"
+#include "RuntimeMeshVersion.h"
+#include "Runtime/Launch/Resources/Version.h"
+#include "RuntimeMeshCore.generated.h"
+
+class FRuntimeMeshVertexFactory;
+
+
+template<typename T>
+struct FRuntimeMeshVertexTraits
+{
+private:
+	template<typename C, C> struct ChT;
+
+	struct FallbackPosition { FVector Position; };
+	struct DerivedPosition : T, FallbackPosition { };
+	template<typename C> static char(&PositionCheck(ChT<FVector FallbackPosition::*, &C::Position>*))[1];
+	template<typename C> static char(&PositionCheck(...))[2];
+
+	struct FallbackNormal { FPackedRGBA16N Normal; };
+	struct DerivedNormal : T, FallbackNormal { };
+	template<typename C> static char(&NormalCheck(ChT<FPackedRGBA16N FallbackNormal::*, &C::Normal>*))[1];
+	template<typename C> static char(&NormalCheck(...))[2];
+
+	struct FallbackTangent { FPackedRGBA16N Tangent; };
+	struct DerivedTangent : T, FallbackTangent { };
+	template<typename C> static char(&TangentCheck(ChT<FPackedRGBA16N FallbackTangent::*, &C::Tangent>*))[1];
+	template<typename C> static char(&TangentCheck(...))[2];
+
+	struct FallbackColor { FColor Color; };
+	struct DerivedColor : T, FallbackColor { };
+	template<typename C> static char(&ColorCheck(ChT<FColor FallbackColor::*, &C::Color>*))[1];
+	template<typename C> static char(&ColorCheck(...))[2];
+
+	struct FallbackUV0 { FVector2D UV0; };
+	struct DerivedUV0 : T, FallbackUV0 { };
+	template<typename C> static char(&UV0Check(ChT<FVector2D FallbackUV0::*, &C::UV0>*))[1];
+	template<typename C> static char(&UV0Check(...))[2];
+
+	struct FallbackUV1 { FVector2D UV1; };
+	struct DerivedUV1 : T, FallbackUV1 { };
+	template<typename C> static char(&UV1Check(ChT<FVector2D FallbackUV1::*, &C::UV1>*))[1];
+	template<typename C> static char(&UV1Check(...))[2];
+
+	struct FallbackUV2 { FVector2D UV2; };
+	struct DerivedUV2 : T, FallbackUV2 { };
+	template<typename C> static char(&UV2Check(ChT<FVector2D FallbackUV2::*, &C::UV2>*))[1];
+	template<typename C> static char(&UV2Check(...))[2];
+
+	struct FallbackUV3 { FVector2D UV3; };
+	struct DerivedUV3 : T, FallbackUV3 { };
+	template<typename C> static char(&UV3Check(ChT<FVector2D FallbackUV3::*, &C::UV3>*))[1];
+	template<typename C> static char(&UV3Check(...))[2];
+
+	struct FallbackUV4 { FVector2D UV4; };
+	struct DerivedUV4 : T, FallbackUV4 { };
+	template<typename C> static char(&UV4Check(ChT<FVector2D FallbackUV4::*, &C::UV4>*))[1];
+	template<typename C> static char(&UV4Check(...))[2];
+
+	struct FallbackUV5 { FVector2D UV5; };
+	struct DerivedUV5 : T, FallbackUV5 { };
+	template<typename C> static char(&UV5Check(ChT<FVector2D FallbackUV5::*, &C::UV5>*))[1];
+	template<typename C> static char(&UV5Check(...))[2];
+
+	struct FallbackUV6 { FVector2D UV6; };
+	struct DerivedUV6 : T, FallbackUV6 { };
+	template<typename C> static char(&UV6Check(ChT<FVector2D FallbackUV6::*, &C::UV6>*))[1];
+	template<typename C> static char(&UV6Check(...))[2];
+
+	struct FallbackUV7 { FVector2D UV7; };
+	struct DerivedUV7 : T, FallbackUV7 { };
+	template<typename C> static char(&UV7Check(ChT<FVector2D FallbackUV7::*, &C::UV7>*))[1];
+	template<typename C> static char(&UV7Check(...))[2];
+
+	template<typename A, typename B>
+	struct IsSameType
+	{
+		static const bool Value = false;
+	};
+
+	template<typename A>
+	struct IsSameType<A, A>
+	{
+		static const bool Value = true;
+	};
+
+	template<bool HasNormal, bool HasTangent, typename Type>
+	struct TangentBasisHighPrecisionDetector
+	{
+		static const bool Value = false;
+	};
+
+	template<typename Type>
+	struct TangentBasisHighPrecisionDetector<true, false, Type>
+	{
+		static const bool Value = IsSameType<decltype(DeclVal<T>().Normal), FPackedRGBA16N>::Value;
+	};
+
+	template<bool HasNormal, typename Type>
+	struct TangentBasisHighPrecisionDetector<HasNormal, true, Type>
+	{
+		static const bool Value = IsSameType<decltype(DeclVal<T>().Tangent), FPackedRGBA16N>::Value;
+	};
+
+	template<bool HasUV0, typename Type>
+	struct UVChannelHighPrecisionDetector
+	{
+		static const bool Value = false;
+	};
+
+	template<typename Type>
+	struct UVChannelHighPrecisionDetector<true, Type>
+	{
+		static const bool Value = IsSameType<decltype(DeclVal<T>().UV0), FVector2D>::Value;
+	};
+	
+
+public:
+	static const bool HasPosition = sizeof(PositionCheck<DerivedPosition>(0)) == 2;
+	static const bool HasNormal = sizeof(NormalCheck<DerivedNormal>(0)) == 2;
+	static const bool HasTangent = sizeof(TangentCheck<DerivedTangent>(0)) == 2;
+	static const bool HasColor = sizeof(ColorCheck<DerivedColor>(0)) == 2;
+	static const bool HasUV0 = sizeof(UV0Check<DerivedUV0>(0)) == 2;
+	static const bool HasUV1 = sizeof(UV1Check<DerivedUV1>(0)) == 2;
+	static const bool HasUV2 = sizeof(UV2Check<DerivedUV2>(0)) == 2;
+	static const bool HasUV3 = sizeof(UV3Check<DerivedUV3>(0)) == 2;
+	static const bool HasUV4 = sizeof(UV4Check<DerivedUV4>(0)) == 2;
+	static const bool HasUV5 = sizeof(UV5Check<DerivedUV5>(0)) == 2;
+	static const bool HasUV6 = sizeof(UV6Check<DerivedUV6>(0)) == 2;
+	static const bool HasUV7 = sizeof(UV7Check<DerivedUV7>(0)) == 2;
+	static const int32 NumUVChannels = 
+		(HasUV0 ? 1 : 0) +
+		(HasUV1 ? 1 : 0) +
+		(HasUV2 ? 1 : 0) +
+		(HasUV3 ? 1 : 0) +
+		(HasUV4 ? 1 : 0) +
+		(HasUV5 ? 1 : 0) +
+		(HasUV6 ? 1 : 0) +
+		(HasUV7 ? 1 : 0);
+
+
+	
+	static const bool HasHighPrecisionNormals = TangentBasisHighPrecisionDetector<HasNormal, HasTangent, T>::Value;
+	static const bool HasHighPrecisionUVs = UVChannelHighPrecisionDetector<HasUV0, T>::Value;
+};
+
+
+
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 12
+/** Structure definition of a vertex */
+using RuntimeMeshVertexStructure = FLocalVertexFactory::FDataType;
+#else
+/** Structure definition of a vertex */
+using RuntimeMeshVertexStructure = FLocalVertexFactory::DataType;
+#endif
+
+#define RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, VertexType, Member, MemberType) \
+	STRUCTMEMBER_VERTEXSTREAMCOMPONENT(&VertexBuffer, VertexType, Member, MemberType)
+
+/* Update frequency for a section. Used to optimize for update or render speed*/
+UENUM(BlueprintType)
+enum class EUpdateFrequency : uint8
+{
+	/* Tries to skip recreating the scene proxy if possible. */
+	Average UMETA(DisplayName = "Average"),
+	/* Tries to skip recreating the scene proxy if possible and optimizes the buffers for frequent updates. */
+	Frequent UMETA(DisplayName = "Frequent"),
+	/* If the component is static it will try to use the static rendering path (this will force a recreate of the scene proxy) */
+	Infrequent UMETA(DisplayName = "Infrequent")
+};
+
+/* Control flags for update actions */
+enum class ESectionUpdateFlags
+{
+	None = 0x0,
+
+	/** 
+	*	This will use move-assignment when copying the supplied vertices/triangles into the section.
+	*	This is faster as it doesn't require copying the data.
+	*
+	*	CAUTION: This means that your copy of the arrays will be cleared!
+	*/
+	MoveArrays = 0x1,
+
+	/**
+	*	Should the normals and tangents be calculated automatically?
+	*	To do this manually see RuntimeMeshLibrary::CalculateTangentsForMesh()
+	*/
+	CalculateNormalTangent = 0x2,
+
+	/**
+	*	Should the tessellation indices be calculated to support tessellation?
+	*	To do this manually see RuntimeMeshLibrary::GenerateTessellationIndexBuffer()
+	*/
+	CalculateTessellationIndices = 0x4,
+	
+};
+ENUM_CLASS_FLAGS(ESectionUpdateFlags)
+
+/**
+*	Struct used to specify a tangent vector for a vertex
+*	The Y tangent is computed from the cross product of the vertex normal (Tangent Z) and the TangentX member.
+*/
+USTRUCT(BlueprintType)
+struct FRuntimeMeshTangent
+{
+	GENERATED_USTRUCT_BODY()
+
+	/** Direction of X tangent for this vertex */
+	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Tangent)
+	FVector TangentX;
+
+	/** Bool that indicates whether we should flip the Y tangent when we compute it using cross product */
+	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Tangent)
+	bool bFlipTangentY;
+
+	FRuntimeMeshTangent()
+		: TangentX(1.f, 0.f, 0.f)
+		, bFlipTangentY(false)
+	{}
+
+	FRuntimeMeshTangent(float X, float Y, float Z, bool bInFlipTangentY = false)
+		: TangentX(X, Y, Z)
+		, bFlipTangentY(bInFlipTangentY)
+	{}
+
+	FRuntimeMeshTangent(FVector InTangentX, bool bInFlipTangentY = false)
+		: TangentX(InTangentX)
+		, bFlipTangentY(bInFlipTangentY)
+	{}
+
+	void AdjustNormal(FPackedNormal& Normal) const
+	{
+		Normal.Vector.W = bFlipTangentY ? 0 : 255;
+	}
+
+	void AdjustNormal(FPackedRGBA16N& Normal) const
+	{
+		Normal.W = bFlipTangentY ? 0 : 65535;
+	}
+};
+
+/*
+*	Configuration flag for the collision cooking to prioritize cooking speed or collision performance.
+*/
+UENUM(BlueprintType)
+enum class ERuntimeMeshCollisionCookingMode : uint8
+{
+	/*
+	*	Favors runtime collision performance of cooking speed. 
+	*	This means that cooking a new mesh will be slower, but collision will be faster.
+	*/
+	CollisionPerformance UMETA(DisplayName = "Collision Performance"),
+
+	/*
+	*	Favors cooking speed over collision performance.
+	*	This means that cooking a new mesh will be faster, but collision will be slower.
+	*/
+	CookingPerformance UMETA(DisplayName = "Cooking Performance"),
+};
+
+/* The different buffers within the Runtime Mesh Component */
+enum class ERuntimeMeshBuffer
+{
+	None = 0x0,
+	Vertices = 0x1,
+	Triangles = 0x2,
+	Positions = 0x4
+};
+ENUM_CLASS_FLAGS(ERuntimeMeshBuffer)
+
+
+USTRUCT()
+struct FRuntimeMeshCollisionSection
+{
+	GENERATED_BODY()
+
+	UPROPERTY()
+	TArray<FVector> VertexBuffer;
+
+	UPROPERTY()
+	TArray<int32> IndexBuffer;
+
+	void Reset()
+	{
+		VertexBuffer.Empty();
+		IndexBuffer.Empty();
+	}
+
+	friend FArchive& operator <<(FArchive& Ar, FRuntimeMeshCollisionSection& Section)
+	{
+		Ar << Section.VertexBuffer;
+		Ar << Section.IndexBuffer;
+		return Ar;
+	}
+}; 
+
+USTRUCT()
+struct FRuntimeConvexCollisionSection
+{
+	GENERATED_BODY()
+
+	UPROPERTY()
+	TArray<FVector> VertexBuffer;
+
+	UPROPERTY()
+	FBox BoundingBox;
+
+	void Reset()
+	{
+		VertexBuffer.Empty();
+		BoundingBox.Init();
+	}
+
+	friend FArchive& operator <<(FArchive& Ar, FRuntimeConvexCollisionSection& Section)
+	{
+		Ar << Section.VertexBuffer;
+		Ar << Section.BoundingBox;
+		return Ar;
+	}
+};
+
+
+
+
+
+
+struct RUNTIMEMESHCOMPONENT_API FRuntimeMeshVertexTypeInfo
+{
+	const FString TypeName;
+	const FGuid TypeGuid;
+
+	FRuntimeMeshVertexTypeInfo(FString Name, FGuid Guid) : TypeName(Name), TypeGuid(Guid) { }
+
+	virtual bool Equals(const FRuntimeMeshVertexTypeInfo* Other) const
+	{
+		return TypeGuid == Other->TypeGuid;
+	}
+
+	template<typename Type>
+	void EnsureEquals() const
+	{
+		if (!Equals(&Type::TypeInfo))
+		{
+			ThrowMismatchException(Type::TypeInfo.TypeName);
+		}
+	}
+
+	virtual class FRuntimeMeshSectionInterface* CreateSection(bool bInNeedsPositionOnlyBuffer) const = 0;
+protected:
+
+	void ThrowMismatchException(const FString& OtherName) const
+	{
+		UE_LOG(RuntimeMeshLog, Fatal, TEXT("Vertex Type Mismatch: %s  and  %s"), *TypeName, *OtherName);
+	}
+};
+
+
+/*
+*  Internal container used to track known vertex types, for serialization and other purposes.
+*/
+class RUNTIMEMESHCOMPONENT_API FRuntimeMeshVertexTypeRegistrationContainer
+{
+	struct VertexRegistration
+	{
+		const FRuntimeMeshVertexTypeInfo* const TypeInfo;
+		uint32 ReferenceCount;
+		
+		VertexRegistration(const FRuntimeMeshVertexTypeInfo* const InTypeInfo)
+			: TypeInfo(InTypeInfo), ReferenceCount(1) { }
+	};
+	TMap<FGuid, VertexRegistration> Registrations;
+
+public:
+
+	static FRuntimeMeshVertexTypeRegistrationContainer& GetInstance();
+
+	void Register(const FRuntimeMeshVertexTypeInfo* InType);
+
+	void UnRegister(const FRuntimeMeshVertexTypeInfo* InType);
+
+	const FRuntimeMeshVertexTypeInfo* GetVertexType(FGuid Key) const;
+
+};
+
+
+template<typename VertexType>
+class FRuntimeMeshVertexTypeRegistration : FNoncopyable
+{
+public:
+	FRuntimeMeshVertexTypeRegistration()
+	{ 
+		FRuntimeMeshVertexTypeRegistrationContainer::GetInstance().Register(&VertexType::TypeInfo);
+	}
+
+	~FRuntimeMeshVertexTypeRegistration()
+	{
+		FRuntimeMeshVertexTypeRegistrationContainer::GetInstance().UnRegister(&VertexType::TypeInfo);
+	}
+};
+
+
+
+
+#define DECLARE_RUNTIMEMESH_CUSTOMVERTEX_TYPEINFO(TypeName, Guid) \
+	struct FRuntimeMeshVertexTypeInfo_##TypeName : public FRuntimeMeshVertexTypeInfo \
+	{ \
+		FRuntimeMeshVertexTypeInfo_##TypeName() : FRuntimeMeshVertexTypeInfo(TEXT(#TypeName), Guid) { } \
+	}; \
+	static const FRuntimeMeshVertexTypeInfo_##TypeName TypeInfo;
+
+#define DEFINE_RUNTIMEMESH_CUSTOMVERTEX_TYPEINFO(TypeName) \
+	const  TypeName::FRuntimeMeshVertexTypeInfo_##TypeName TypeName::TypeInfo = TypeName::FRuntimeMeshVertexTypeInfo_##TypeName(); \
+    FRuntimeMeshVertexTypeRegistration<##TypeName> FRuntimeMeshVertexTypeInfoRegistration_##TypeName(&##TypeName::TypeInfo);
+
+

+ 1273 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshGenericVertex.h

@@ -0,0 +1,1273 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#pragma once
+
+#include "RuntimeMeshCore.h"
+
+//////////////////////////////////////////////////////////////////////////
+//	
+//	This file contains a generic vertex structure capable of efficiently representing a vertex 
+//	with any combination of position, normal, tangent, color, and 0-8 uv channels.
+//
+//	To get around an issue with MSVC and potentially other compilers not performing
+//	empty base class optimizations (EBO) to children in multiple inheritance, 
+//	this vertex is built via a tree of inheritance using partial specializations.
+//
+//	At each tier of this tree partial specialization will choose which components 
+//	we need to add in, thereby removing entire inherited classes when we don't need them.
+//
+//  Structure:
+//											  FRuntimeMeshVertex
+//											/					\
+//	FRuntimeMeshPositionNormalTangentComponentCombiner			FRuntimeMeshColorUVComponentCombiner
+//					/					\								/					\
+//	FRuntimeMeshPositionComponent		 \					FRuntimeMeshColorComponent		 \
+//										  \													  \
+//						FRuntimeMeshNormalTangentComponents							FRuntimeMeshUVComponents
+//
+//
+//
+//
+//
+//	Example use: (This defines a vertex with all components and 1 UV with default precision for normal/tangent and UV)
+//
+//	using MyVertex = FRuntimeMeshVertex<true, true, true, true, 1, ERuntimeMeshVertexTangentBasisType::Default, ERuntimeMeshVertexUVType::Default>;
+//
+//	MyVertex Vertex;
+//	Vertex.Position = FVector(0,0,0);
+//	Vertex.Normal = FVector(0,0,0);
+//	Vertex.UV0 = FVector2D(0,0);
+//
+//
+//////////////////////////////////////////////////////////////////////////
+
+
+template<int32 TextureChannels, bool HalfPrecisionUVs, bool HasPositionComponent>
+RuntimeMeshVertexStructure CreateVertexStructure(const FVertexBuffer& VertexBuffer);
+
+
+//////////////////////////////////////////////////////////////////////////
+// Texture Component Type Selector
+//////////////////////////////////////////////////////////////////////////
+
+enum class ERuntimeMeshVertexUVType
+{
+	Default = 1,
+	HighPrecision = 2,
+};
+
+template<ERuntimeMeshVertexUVType UVType>
+struct FRuntimeMeshVertexUVsTypeSelector;
+
+template<>
+struct FRuntimeMeshVertexUVsTypeSelector<ERuntimeMeshVertexUVType::Default>
+{
+	typedef FVector2DHalf UVsType;
+	static const EVertexElementType VertexElementType1Channel = VET_Half2;
+	static const EVertexElementType VertexElementType2Channel = VET_Half4;
+
+};
+
+template<>
+struct FRuntimeMeshVertexUVsTypeSelector<ERuntimeMeshVertexUVType::HighPrecision>
+{
+	typedef FVector2D UVsType;
+	static const EVertexElementType VertexElementType1Channel = VET_Float2;
+	static const EVertexElementType VertexElementType2Channel = VET_Float4;
+};
+
+//////////////////////////////////////////////////////////////////////////
+// Texture Component
+//////////////////////////////////////////////////////////////////////////
+
+/* Defines the UV coordinates for a vertex (Defaulted to 0 channels) */
+template<int32 TextureChannels, typename UVType> struct FRuntimeMeshUVComponents
+{
+	static_assert(TextureChannels >= 0 && TextureChannels <= 8, "You must have between 0 and 8 (inclusive) UV channels");
+};
+
+/* Defines the UV coordinates for a vertex (Specialized to 0 channels) */
+template<typename UVType> struct FRuntimeMeshUVComponents<0, UVType>
+{
+	FRuntimeMeshUVComponents() { }
+	FRuntimeMeshUVComponents(EForceInit) { }
+};
+
+/* Defines the UV coordinates for a vertex (Specialized to 1 channels) */
+template<typename UVType> struct FRuntimeMeshUVComponents<1, UVType>
+{
+	UVType UV0;
+
+	FRuntimeMeshUVComponents() { }
+	FRuntimeMeshUVComponents(EForceInit) : UV0(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0) : UV0(InUV0) { }
+};
+
+/* Defines the UV coordinates for a vertex (Specialized to 2 channels) */
+template<typename UVType> struct FRuntimeMeshUVComponents<2, UVType>
+{
+	UVType UV0;
+	UVType UV1;
+
+	FRuntimeMeshUVComponents() { }
+	FRuntimeMeshUVComponents(EForceInit) : UV0(0, 0), UV1(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0) : UV0(InUV0), UV1(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1) : UV0(InUV0), UV1(InUV1) { }
+};
+
+/* Defines the UV coordinates for a vertex (Specialized to 3 channels) */
+template<typename UVType> struct FRuntimeMeshUVComponents<3, UVType>
+{
+	UVType UV0;
+	UVType UV1;
+	UVType UV2;
+
+	FRuntimeMeshUVComponents() { }
+	FRuntimeMeshUVComponents(EForceInit) :
+		UV0(0, 0), UV1(0, 0), UV2(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0) :
+		UV0(InUV0), UV1(0, 0), UV2(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1) :
+		UV0(InUV0), UV1(InUV1), UV2(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2) { }
+};
+
+/* Defines the UV coordinates for a vertex (Specialized to 4 channels) */
+template<typename UVType> struct FRuntimeMeshUVComponents<4, UVType>
+{
+	UVType UV0;
+	UVType UV1;
+	UVType UV2;
+	UVType UV3;
+
+	FRuntimeMeshUVComponents() { }
+	FRuntimeMeshUVComponents(EForceInit) :
+		UV0(0, 0), UV1(0, 0), UV2(0, 0), UV3(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0) :
+		UV0(InUV0), UV1(0, 0), UV2(0, 0), UV3(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1) :
+		UV0(InUV0), UV1(InUV1), UV2(0, 0), UV3(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2, const FVector2D& InUV3) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(InUV3) { }
+};
+
+/* Defines the UV coordinates for a vertex (Specialized to 5 channels) */
+template<typename UVType> struct FRuntimeMeshUVComponents<5, UVType>
+{
+	UVType UV0;
+	UVType UV1;
+	UVType UV2;
+	UVType UV3;
+	UVType UV4;
+
+	FRuntimeMeshUVComponents() { }
+	FRuntimeMeshUVComponents(EForceInit) :
+		UV0(0, 0), UV1(0, 0), UV2(0, 0), UV3(0, 0), UV4(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0) :
+		UV0(InUV0), UV1(0, 0), UV2(0, 0), UV3(0, 0), UV4(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1) :
+		UV0(InUV0), UV1(InUV1), UV2(0, 0), UV3(0, 0), UV4(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(0, 0), UV4(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2, const FVector2D& InUV3) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(InUV3), UV4(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2, const FVector2D& InUV3,
+		const FVector2D& InUV4) : UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(InUV3), UV4(InUV4) { }
+};
+
+/* Defines the UV coordinates for a vertex (Specialized to 6 channels, Half Precision) */
+template<typename UVType> struct FRuntimeMeshUVComponents<6, UVType>
+{
+	UVType UV0;
+	UVType UV1;
+	UVType UV2;
+	UVType UV3;
+	UVType UV4;
+	UVType UV5;
+
+	FRuntimeMeshUVComponents() { }
+	FRuntimeMeshUVComponents(EForceInit) :
+		UV0(0, 0), UV1(0, 0), UV2(0, 0), UV3(0, 0), UV4(0, 0), UV5(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0) :
+		UV0(InUV0), UV1(0, 0), UV2(0, 0), UV3(0, 0), UV4(0, 0), UV5(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1) :
+		UV0(InUV0), UV1(InUV1), UV2(0, 0), UV3(0, 0), UV4(0, 0), UV5(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(0, 0), UV4(0, 0), UV5(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2, const FVector2D& InUV3) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(InUV3), UV4(0, 0), UV5(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2, const FVector2D& InUV3,
+		const FVector2D& InUV4) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(InUV3), UV4(InUV4), UV5(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2, const FVector2D& InUV3,
+		const FVector2D& InUV4, const FVector2D& InUV5) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(InUV3), UV4(InUV4), UV5(InUV5) { }
+};
+
+/* Defines the UV coordinates for a vertex (Specialized to 7 channels) */
+template<typename UVType> struct FRuntimeMeshUVComponents<7, UVType>
+{
+	UVType UV0;
+	UVType UV1;
+	UVType UV2;
+	UVType UV3;
+	UVType UV4;
+	UVType UV5;
+	UVType UV6;
+
+	FRuntimeMeshUVComponents() { }
+	FRuntimeMeshUVComponents(EForceInit) :
+		UV0(0, 0), UV1(0, 0), UV2(0, 0), UV3(0, 0), UV4(0, 0), UV5(0, 0), UV6(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0) :
+		UV0(InUV0), UV1(0, 0), UV2(0, 0), UV3(0, 0), UV4(0, 0), UV5(0, 0), UV6(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1) :
+		UV0(InUV0), UV1(InUV1), UV2(0, 0), UV3(0, 0), UV4(0, 0), UV5(0, 0), UV6(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(0, 0), UV4(0, 0), UV5(0, 0), UV6(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2, const FVector2D& InUV3) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(InUV3), UV4(0, 0), UV5(0, 0), UV6(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2, const FVector2D& InUV3,
+		const FVector2D& InUV4) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(InUV3), UV4(InUV4), UV5(0, 0), UV6(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2, const FVector2D& InUV3,
+		const FVector2D& InUV4, const FVector2D& InUV5) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(InUV3), UV4(InUV4), UV5(InUV5), UV6(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2, const FVector2D& InUV3,
+		const FVector2D& InUV4, const FVector2D& InUV5, const FVector2D& InUV6) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(InUV3), UV4(InUV4), UV5(InUV5), UV6(InUV6) { }
+};
+
+/* Defines the UV coordinates for a vertex (Specialized to 8 channels) */
+template<typename UVType> struct FRuntimeMeshUVComponents<8, UVType>
+{
+	UVType UV0;
+	UVType UV1;
+	UVType UV2;
+	UVType UV3;
+	UVType UV4;
+	UVType UV5;
+	UVType UV6;
+	UVType UV7;
+
+	FRuntimeMeshUVComponents() { }
+	FRuntimeMeshUVComponents(EForceInit) :
+		UV0(0, 0), UV1(0, 0), UV2(0, 0), UV3(0, 0), UV4(0, 0), UV5(0, 0), UV6(0, 0), UV7(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0) :
+		UV0(InUV0), UV1(0, 0), UV2(0, 0), UV3(0, 0), UV4(0, 0), UV5(0, 0), UV6(0, 0), UV7(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1) :
+		UV0(InUV0), UV1(InUV1), UV2(0, 0), UV3(0, 0), UV4(0, 0), UV5(0, 0), UV6(0, 0), UV7(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(0, 0), UV4(0, 0), UV5(0, 0), UV6(0, 0), UV7(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2, const FVector2D& InUV3) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(InUV3), UV4(0, 0), UV5(0, 0), UV6(0, 0), UV7(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2, const FVector2D& InUV3,
+		const FVector2D& InUV4) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(InUV3), UV4(InUV4), UV5(0, 0), UV6(0, 0), UV7(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2, const FVector2D& InUV3,
+		const FVector2D& InUV4, const FVector2D& InUV5) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(InUV3), UV4(InUV4), UV5(InUV5), UV6(0, 0), UV7(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2, const FVector2D& InUV3,
+		const FVector2D& InUV4, const FVector2D& InUV5, const FVector2D& InUV6) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(InUV3), UV4(InUV4), UV5(InUV5), UV6(InUV6), UV7(0, 0) { }
+	FRuntimeMeshUVComponents(const FVector2D& InUV0, const FVector2D& InUV1, const FVector2D& InUV2, const FVector2D& InUV3,
+		const FVector2D& InUV4, const FVector2D& InUV5, const FVector2D& InUV6, const FVector2D& InUV7) :
+		UV0(InUV0), UV1(InUV1), UV2(InUV2), UV3(InUV3), UV4(InUV4), UV5(InUV5), UV6(InUV6), UV7(InUV7) { }
+};
+
+
+//////////////////////////////////////////////////////////////////////////
+// Tangent Basis Component Type Selector
+//////////////////////////////////////////////////////////////////////////
+
+enum class ERuntimeMeshVertexTangentBasisType
+{
+	Default = 1,
+	HighPrecision = 2,
+};
+
+template<ERuntimeMeshVertexTangentBasisType TangentBasisType>
+struct FRuntimeMeshVertexTangentTypeSelector;
+
+template<>
+struct FRuntimeMeshVertexTangentTypeSelector<ERuntimeMeshVertexTangentBasisType::Default>
+{
+	typedef FPackedNormal TangentType;
+	static const EVertexElementType VertexElementType = VET_PackedNormal;
+};
+
+template<>
+struct FRuntimeMeshVertexTangentTypeSelector<ERuntimeMeshVertexTangentBasisType::HighPrecision>
+{
+	typedef FPackedRGBA16N TangentType;
+	static const EVertexElementType VertexElementType = VET_UShort4N;
+};
+
+
+//////////////////////////////////////////////////////////////////////////
+// Tangent Basis Components
+//////////////////////////////////////////////////////////////////////////
+
+template<bool WantsNormal, bool WantsTangent, typename TangentType>
+struct FRuntimeMeshNormalTangentComponents;
+
+struct RuntimeMeshNormalUtil
+{
+	static void SetNormalW(FPackedNormal& Target, float Determinant)
+	{
+		Target.Vector.W = Determinant < 0.0f ? 0 : 255;
+	}
+
+	static void SetNormalW(FPackedRGBA16N& Target, float Determinant)
+	{
+		Target.W = Determinant < 0.0f ? 0 : 65535;
+	}
+};
+
+template<typename TangentType>
+struct FRuntimeMeshNormalTangentComponents<true, false, TangentType>
+{
+	TangentType Normal;
+
+	FRuntimeMeshNormalTangentComponents() { }
+	FRuntimeMeshNormalTangentComponents(EForceInit) : Normal(FVector4(0.0f, 0.0f, 1.0f, 1.0f)) { }
+
+	void SetNormalAndTangent(const FVector& InNormal, const FRuntimeMeshTangent& InTangent)
+	{
+		Normal = InNormal;
+
+		InTangent.AdjustNormal(Normal);
+	}
+
+	void SetNormalAndTangent(const FVector& InTangentX, const FVector& InTangentY, const FVector& InTangentZ)
+	{
+		Normal = InTangentZ;
+
+		// store determinant of basis in w component of normal vector
+		RuntimeMeshNormalUtil::SetNormalW(Normal, GetBasisDeterminantSign(InTangentX, InTangentY, InTangentZ));
+	}
+};
+
+template<typename TangentType>
+struct FRuntimeMeshNormalTangentComponents<false, true, TangentType>
+{
+	TangentType Tangent;
+
+	FRuntimeMeshNormalTangentComponents() { }
+	FRuntimeMeshNormalTangentComponents(EForceInit) : Tangent(FVector(1.0f, 0.0f, 0.0f)) { }
+
+	void SetNormalAndTangent(const FVector& InNormal, const FRuntimeMeshTangent& InTangent)
+	{
+		Tangent = InTangent.TangentX;
+	}
+
+	void SetNormalAndTangent(const FVector& InTangentX, const FVector& InTangentY, const FVector& InTangentZ)
+	{
+		Tangent = InTangentX;
+	}
+};
+
+template<typename TangentType>
+struct FRuntimeMeshNormalTangentComponents<true, true, TangentType>
+{
+	TangentType Normal;
+	TangentType Tangent;
+
+	FRuntimeMeshNormalTangentComponents() { }
+	FRuntimeMeshNormalTangentComponents(EForceInit) : Normal(FVector4(0.0f, 0.0f, 1.0f, 1.0f)), Tangent(FVector(1.0f, 0.0f, 0.0f)) { }
+
+	void SetNormalAndTangent(const FVector& InNormal, const FRuntimeMeshTangent& InTangent)
+	{
+		Normal = InNormal;
+		Tangent = InTangent.TangentX;
+
+		InTangent.AdjustNormal(Normal);
+	}
+	
+	void SetNormalAndTangent(const FVector& InTangentX, const FVector& InTangentY, const FVector& InTangentZ)
+	{
+		Normal = InTangentZ;
+		Tangent = InTangentX;
+
+		// store determinant of basis in w component of normal vector
+		RuntimeMeshNormalUtil::SetNormalW(Normal, GetBasisDeterminantSign(InTangentX, InTangentY, InTangentZ));
+	}
+};
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// Position Component
+//////////////////////////////////////////////////////////////////////////
+
+template<bool WantsPosition>
+struct FRuntimeMeshPositionComponent;
+
+template<>
+struct FRuntimeMeshPositionComponent<true>
+{
+	FVector Position;
+
+	FRuntimeMeshPositionComponent() { }
+	FRuntimeMeshPositionComponent(EForceInit) : Position(0.0f, 0.0f, 0.0f) { }
+};
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// Color Component
+//////////////////////////////////////////////////////////////////////////
+
+template<bool WantsColor>
+struct FRuntimeMeshColorComponent;
+
+template<>
+struct FRuntimeMeshColorComponent<true>
+{
+	FColor Color;
+
+	FRuntimeMeshColorComponent() { }
+	FRuntimeMeshColorComponent(EForceInit) : Color(FColor::White) { }
+};
+
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// Position Normal Tangent Combiner
+//////////////////////////////////////////////////////////////////////////
+
+template<bool WantsPosition, bool WantsNormal, bool WantsTangent, typename TangentBasisType>
+struct FRuntimeMeshPositionNormalTangentComponentCombiner :
+	public FRuntimeMeshPositionComponent<WantsPosition>,
+	public FRuntimeMeshNormalTangentComponents<WantsNormal, WantsTangent, TangentBasisType>
+{
+	FRuntimeMeshPositionNormalTangentComponentCombiner() { }
+	FRuntimeMeshPositionNormalTangentComponentCombiner(EForceInit)
+		: FRuntimeMeshPositionComponent<WantsPosition>(EForceInit::ForceInit)
+		, FRuntimeMeshNormalTangentComponents<WantsNormal, WantsTangent, TangentBasisType>(EForceInit::ForceInit)
+	{ }
+};
+
+template<bool WantsPosition, typename TangentBasisType>
+struct FRuntimeMeshPositionNormalTangentComponentCombiner<WantsPosition, false, false, TangentBasisType> :
+	public FRuntimeMeshPositionComponent<WantsPosition>
+{
+	FRuntimeMeshPositionNormalTangentComponentCombiner() { }
+	FRuntimeMeshPositionNormalTangentComponentCombiner(EForceInit)
+		: FRuntimeMeshPositionComponent<WantsPosition>(EForceInit::ForceInit)
+	{ }
+};
+
+template<bool WantsNormal, bool WantsTangent, typename TangentBasisType>
+struct FRuntimeMeshPositionNormalTangentComponentCombiner<false, WantsNormal, WantsTangent, TangentBasisType> :
+	public FRuntimeMeshNormalTangentComponents<WantsNormal, WantsTangent, TangentBasisType>
+{
+	FRuntimeMeshPositionNormalTangentComponentCombiner() { }
+	FRuntimeMeshPositionNormalTangentComponentCombiner(EForceInit)
+		: FRuntimeMeshNormalTangentComponents<WantsNormal, WantsTangent, TangentBasisType>(EForceInit::ForceInit)
+	{ }
+};
+
+template<typename TangentBasisType>
+struct FRuntimeMeshPositionNormalTangentComponentCombiner<false, false, false, TangentBasisType>;
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// Color UV Combiner
+//////////////////////////////////////////////////////////////////////////
+
+template<bool WantsColor, int32 NumWantedUVChannels, typename UVType>
+struct FRuntimeMeshColorUVComponentCombiner :
+	public FRuntimeMeshColorComponent<WantsColor>,
+	public FRuntimeMeshUVComponents<NumWantedUVChannels, UVType>
+{
+	FRuntimeMeshColorUVComponentCombiner() { }
+	FRuntimeMeshColorUVComponentCombiner(EForceInit)
+		: FRuntimeMeshColorComponent<WantsColor>(EForceInit::ForceInit)
+		, FRuntimeMeshUVComponents<NumWantedUVChannels, UVType>(EForceInit::ForceInit)
+	{ }
+};
+
+template<int32 NumWantedUVChannels, typename UVType>
+struct FRuntimeMeshColorUVComponentCombiner<false, NumWantedUVChannels, UVType> :
+	public FRuntimeMeshUVComponents<NumWantedUVChannels, UVType>
+{
+	FRuntimeMeshColorUVComponentCombiner() { }
+	FRuntimeMeshColorUVComponentCombiner(EForceInit)
+		: FRuntimeMeshUVComponents<NumWantedUVChannels, UVType>(EForceInit::ForceInit)
+	{ }
+};
+
+template<bool WantsColor, typename UVType>
+struct FRuntimeMeshColorUVComponentCombiner<WantsColor, 0, UVType> :
+	public FRuntimeMeshColorComponent<WantsColor>
+{
+	FRuntimeMeshColorUVComponentCombiner() { }
+	FRuntimeMeshColorUVComponentCombiner(EForceInit)
+		: FRuntimeMeshColorComponent<WantsColor>(EForceInit::ForceInit)
+	{ }
+};
+
+template<typename UVType>
+struct FRuntimeMeshColorUVComponentCombiner<false, 0, UVType>;
+
+
+
+
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// Template Vertex Type Info Structure
+//////////////////////////////////////////////////////////////////////////
+
+template<bool WantsPosition, bool WantsNormal, bool WantsTangent, bool WantsColor, int32 NumWantedUVChannels,
+	ERuntimeMeshVertexTangentBasisType NormalTangentType, ERuntimeMeshVertexUVType UVType>
+struct FRuntimeMeshVertexTypeInfo_GenericVertex : public FRuntimeMeshVertexTypeInfo
+{
+	FRuntimeMeshVertexTypeInfo_GenericVertex(FString VertexName) :
+		FRuntimeMeshVertexTypeInfo(
+			FString::Printf(TEXT("RuntimeMeshVertex<%d, %d, %d, %d, %d, %d, %d>"), WantsPosition, WantsNormal, WantsTangent, WantsColor, NumWantedUVChannels, (int32)NormalTangentType, (int32)UVType),
+			GetVertexGuid(VertexName)) { }
+
+	static FGuid GetVertexGuid(FString VertexName)
+	{
+		uint32 TypeID = 0;
+		TypeID = (TypeID << 1) | (WantsPosition ? 1 : 0);
+		TypeID = (TypeID << 1) | (WantsNormal ? 1 : 0);
+		TypeID = (TypeID << 1) | (WantsTangent ? 1 : 0);
+		TypeID = (TypeID << 3) | (uint32)NormalTangentType;
+		TypeID = (TypeID << 1) | (WantsColor ? 1 : 0);
+		TypeID = (TypeID << 6) | (NumWantedUVChannels & 0xFF);
+		TypeID = (TypeID << 3) | (uint32)UVType;
+
+		FGuid Guid = FGuid(0x00FFEB44, 0x31094597, /*0x93918032*/  GetTypeHash(VertexName), (0x78C3 << 16) | TypeID);
+		return Guid;
+	}
+};
+
+//////////////////////////////////////////////////////////////////////////
+// Macros to create a custom vertex type based on the generic vertex and implement some common constructors
+//////////////////////////////////////////////////////////////////////////
+
+
+
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_POSITION_true Position = FVector(0.0f, 0.0f, 0.0f);
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_POSITION_false 
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_POSITION(HasPosition) RUNTIMEMESH_VERTEX_DEFAULTINIT_POSITION_##HasPosition
+
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_NORMAL_true Normal = FVector4(0.0f, 0.0f, 1.0f, 1.0f);
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_NORMAL_false 
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_NORMAL(HasNormal) RUNTIMEMESH_VERTEX_DEFAULTINIT_NORMAL_##HasNormal
+
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_TANGENT_true Tangent = FVector(1.0f, 0.0f, 0.0f);
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_TANGENT_false 
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_TANGENT(HasTangent) RUNTIMEMESH_VERTEX_DEFAULTINIT_TANGENT_##HasTangent
+
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_COLOR_true Color = FColor::White;
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_COLOR_false 
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_COLOR(HasColor) RUNTIMEMESH_VERTEX_DEFAULTINIT_COLOR_##HasColor
+
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNEL_0
+
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNEL_1 \
+	UV0 = FVector2D(0.0f, 0.0f);
+
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNEL_2 \
+	RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNEL_1 \
+	UV1 = FVector2D(0.0f, 0.0f);
+
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNEL_3 \
+	RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNEL_2 \
+	UV2 = FVector2D(0.0f, 0.0f);
+
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNEL_4 \
+	RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNEL_3 \
+	UV3 = FVector2D(0.0f, 0.0f);
+
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNEL_5 \
+	RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNEL_4 \
+	UV4 = FVector2D(0.0f, 0.0f);
+
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNEL_6 \
+	RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNEL_5 \
+	UV5 = FVector2D(0.0f, 0.0f);
+
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNEL_7 \
+	RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNEL_6 \
+	UV6 = FVector2D(0.0f, 0.0f);
+
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNEL_8 \
+	RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNEL_7 \
+	UV7 = FVector2D(0.0f, 0.0f);
+
+#define RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNELS(NumChannels) RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNEL_##NumChannels
+
+
+
+
+#define RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNEL_0
+#define RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNEL_1 , const FVector2D& InUV0 = FVector2D::ZeroVector
+#define RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNEL_2 RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNEL_1 , const FVector2D& InUV1 = FVector2D::ZeroVector
+#define RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNEL_3 RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNEL_2 , const FVector2D& InUV2 = FVector2D::ZeroVector
+#define RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNEL_4 RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNEL_3 , const FVector2D& InUV3 = FVector2D::ZeroVector
+#define RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNEL_5 RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNEL_4 , const FVector2D& InUV4 = FVector2D::ZeroVector
+#define RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNEL_6 RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNEL_5 , const FVector2D& InUV5 = FVector2D::ZeroVector
+#define RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNEL_7 RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNEL_6 , const FVector2D& InUV6 = FVector2D::ZeroVector
+#define RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNEL_8 RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNEL_7 , const FVector2D& InUV7 = FVector2D::ZeroVector
+
+#define RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNELS(NumChannels) RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNEL_##NumChannels
+
+
+
+
+#define RUNTIMEMESH_VERTEX_INIT_UVCHANNEL_0
+
+#define RUNTIMEMESH_VERTEX_INIT_UVCHANNEL_1 \
+	UV0 = InUV0;
+
+#define RUNTIMEMESH_VERTEX_INIT_UVCHANNEL_2 \
+	RUNTIMEMESH_VERTEX_INIT_UVCHANNEL_1 \
+	UV1 = InUV1;
+
+#define RUNTIMEMESH_VERTEX_INIT_UVCHANNEL_3 \
+	RUNTIMEMESH_VERTEX_INIT_UVCHANNEL_2 \
+	UV2 = InUV2;
+
+#define RUNTIMEMESH_VERTEX_INIT_UVCHANNEL_4 \
+	RUNTIMEMESH_VERTEX_INIT_UVCHANNEL_3 \
+	UV3 = InUV3;
+
+#define RUNTIMEMESH_VERTEX_INIT_UVCHANNEL_5 \
+	RUNTIMEMESH_VERTEX_INIT_UVCHANNEL_4 \
+	UV4 = InUV4;
+
+#define RUNTIMEMESH_VERTEX_INIT_UVCHANNEL_6 \
+	RUNTIMEMESH_VERTEX_INIT_UVCHANNEL_5 \
+	UV5 = InUV5;
+
+#define RUNTIMEMESH_VERTEX_INIT_UVCHANNEL_7 \
+	RUNTIMEMESH_VERTEX_INIT_UVCHANNEL_6 \
+	UV6 = InUV6;
+
+#define RUNTIMEMESH_VERTEX_INIT_UVCHANNEL_8 \
+	RUNTIMEMESH_VERTEX_INIT_UVCHANNEL_7 \
+	UV7 = InUV7;
+
+#define RUNTIMEMESH_VERTEX_INIT_UVCHANNELS(NumChannels) RUNTIMEMESH_VERTEX_INIT_UVCHANNEL_##NumChannels
+
+
+
+#define RUNTIMEMESH_VERTEX_PARAMETER_POSITION_true const FVector& InPosition, 
+#define RUNTIMEMESH_VERTEX_PARAMETER_POSITION_false
+#define RUNTIMEMESH_VERTEX_PARAMETER_POSITION(NeedsPosition) RUNTIMEMESH_VERTEX_PARAMETER_POSITION_##NeedsPosition
+
+#define RUNTIMEMESH_VERTEX_INIT_POSITION_true Position = InPosition;
+#define RUNTIMEMESH_VERTEX_INIT_POSITION_false
+#define RUNTIMEMESH_VERTEX_INIT_POSITION(NeedsPosition) RUNTIMEMESH_VERTEX_INIT_POSITION_##NeedsPosition
+
+
+// PreProcessor IF with pass through for all the constructor arguments
+#define RUNTIMEMESH_VERTEX_CONSTRUCTOR_DEFINITION_IF(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, Condition, IfTrue) \
+	RUNTIMEMESH_VERTEX_CONSTRUCTOR_DEFINITION_IF_##Condition(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, IfTrue)
+#define RUNTIMEMESH_VERTEX_CONSTRUCTOR_DEFINITION_IF_false(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, IfTrue)
+#define RUNTIMEMESH_VERTEX_CONSTRUCTOR_DEFINITION_IF_true(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, IfTrue) IfTrue
+
+
+// Implementation of Position only Constructor
+#define RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_IMPLEMENTATION(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType) \
+	VertexName(const FVector& InPosition)							\
+	{																\
+		RUNTIMEMESH_VERTEX_INIT_POSITION(NeedsPosition)				\
+		RUNTIMEMESH_VERTEX_DEFAULTINIT_NORMAL(NeedsNormal)			\
+		RUNTIMEMESH_VERTEX_DEFAULTINIT_TANGENT(NeedsTangent)		\
+		RUNTIMEMESH_VERTEX_DEFAULTINIT_COLOR(NeedsColor)			\
+		RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNELS(UVChannelCount)	\
+	}
+
+// Defines the Position Constuctor if it's wanted
+#define RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)								\
+	RUNTIMEMESH_VERTEX_CONSTRUCTOR_DEFINITION_IF(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, NeedsPosition,				\
+		RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_IMPLEMENTATION(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)				\
+	)
+
+// Implementation of Position/Normal Constructor
+#define RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_NORMAL_IMPLEMENTATION(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType) \
+	VertexName(RUNTIMEMESH_VERTEX_PARAMETER_POSITION(NeedsPosition) const FVector& InNormal)	\
+	{																\
+		RUNTIMEMESH_VERTEX_INIT_POSITION(NeedsPosition)				\
+		Normal = InNormal;											\
+		RUNTIMEMESH_VERTEX_DEFAULTINIT_TANGENT(NeedsTangent)		\
+		RUNTIMEMESH_VERTEX_DEFAULTINIT_COLOR(NeedsColor)			\
+		RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNELS(UVChannelCount)	\
+	}
+
+// Defines the Position/Normal Constuctor if it's wanted
+#define RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_NORMAL(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)						\
+	RUNTIMEMESH_VERTEX_CONSTRUCTOR_DEFINITION_IF(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, NeedsNormal,				\
+		RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_NORMAL_IMPLEMENTATION(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)		\
+	)
+
+// Implementation of Position/Color Constructor
+#define RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_COLOR_IMPLEMENTATION(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType) \
+	VertexName(RUNTIMEMESH_VERTEX_PARAMETER_POSITION(NeedsPosition) const FColor& InColor)	\
+	{																\
+		RUNTIMEMESH_VERTEX_INIT_POSITION(NeedsPosition)				\
+		RUNTIMEMESH_VERTEX_DEFAULTINIT_NORMAL(NeedsNormal)			\
+		RUNTIMEMESH_VERTEX_DEFAULTINIT_TANGENT(NeedsTangent)		\
+		Color = InColor;											\
+		RUNTIMEMESH_VERTEX_DEFAULTINIT_UVCHANNELS(UVChannelCount)	\
+	}
+
+// Defines the Position/Color Constructor if it's wanted
+#define RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_COLOR(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)						\
+	RUNTIMEMESH_VERTEX_CONSTRUCTOR_DEFINITION_IF(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, NeedsColor,				\
+		RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_COLOR_IMPLEMENTATION(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)		\
+	)
+
+
+
+
+
+
+
+
+// Implementation of Position/Normal/Tangent Constructor
+#define RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_NORMAL_TANGENT_IMPLEMENTATION(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType) \
+	VertexName(RUNTIMEMESH_VERTEX_PARAMETER_POSITION(NeedsPosition) const FVector& InNormal, const FRuntimeMeshTangent& InTangent RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNELS(UVChannelCount))	\
+	{																\
+		RUNTIMEMESH_VERTEX_INIT_POSITION(NeedsPosition)				\
+		Normal = InNormal;											\
+		Tangent = InTangent.TangentX;								\
+		InTangent.AdjustNormal(Normal);								\
+		RUNTIMEMESH_VERTEX_DEFAULTINIT_COLOR(NeedsColor)			\
+		RUNTIMEMESH_VERTEX_INIT_UVCHANNELS(UVChannelCount)			\
+	}
+
+// Defines the Position/Normal/Tangent Constructor if it's wanted
+#define RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_NORMAL_TANGENT(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)						\
+	RUNTIMEMESH_VERTEX_CONSTRUCTOR_DEFINITION_IF(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, NeedsNormal,						\
+		RUNTIMEMESH_VERTEX_CONSTRUCTOR_DEFINITION_IF(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, NeedsTangent,					\
+			RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_NORMAL_TANGENT_IMPLEMENTATION(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)	\
+		)																																															\
+	)
+
+
+// Implementation of Position/TangentX/TangentY/TangentZ Constructor
+#define RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_TANGENTX_TANGENTY_TANGENTZ_IMPLEMENTATION(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType) \
+	VertexName(RUNTIMEMESH_VERTEX_PARAMETER_POSITION(NeedsPosition) const FVector& InTangentX, const FVector& InTangentY, const FVector& InTangentZ RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNELS(UVChannelCount))	\
+	{																\
+		RUNTIMEMESH_VERTEX_INIT_POSITION(NeedsPosition)				\
+		SetNormalAndTangent(InTangentX, InTangentY, InTangentZ);	\
+		RUNTIMEMESH_VERTEX_DEFAULTINIT_COLOR(NeedsColor)			\
+		RUNTIMEMESH_VERTEX_INIT_UVCHANNELS(UVChannelCount)			\
+	}
+
+// Defines the Position/TangentX/TangentY/TangentZ Constructor if it's wanted
+#define RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_TANGENTX_TANGENTY_TANGENTZ(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)						\
+	RUNTIMEMESH_VERTEX_CONSTRUCTOR_DEFINITION_IF(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, NeedsNormal,									\
+		RUNTIMEMESH_VERTEX_CONSTRUCTOR_DEFINITION_IF(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, NeedsTangent,								\
+			RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_TANGENTX_TANGENTY_TANGENTZ_IMPLEMENTATION(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)	\
+		)																																																		\
+	)
+
+
+
+
+
+
+
+// Implementation of Position/Normal/Tangent Constructor
+#define RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_NORMAL_TANGENT_COLOR_IMPLEMENTATION(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType) \
+	VertexName(RUNTIMEMESH_VERTEX_PARAMETER_POSITION(NeedsPosition) const FVector& InNormal, const FRuntimeMeshTangent& InTangent, const FColor& InColor RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNELS(UVChannelCount))	\
+	{																\
+		RUNTIMEMESH_VERTEX_INIT_POSITION(NeedsPosition)				\
+		Normal = InNormal;											\
+		Tangent = InTangent.TangentX;								\
+		InTangent.AdjustNormal(Normal);								\
+		Color = InColor;											\
+		RUNTIMEMESH_VERTEX_INIT_UVCHANNELS(UVChannelCount)			\
+	}
+
+// Defines the Position/Normal/Tangent Constructor if it's wanted
+#define RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_NORMAL_TANGENT_COLOR(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)									\
+	RUNTIMEMESH_VERTEX_CONSTRUCTOR_DEFINITION_IF(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, NeedsNormal,									\
+		RUNTIMEMESH_VERTEX_CONSTRUCTOR_DEFINITION_IF(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, NeedsTangent,								\
+			RUNTIMEMESH_VERTEX_CONSTRUCTOR_DEFINITION_IF(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, NeedsColor,								\
+				RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_NORMAL_TANGENT_COLOR_IMPLEMENTATION(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)		\
+			)																																																		\
+		)																																																			\
+	)
+
+
+// Implementation of Position/TangentX/TangentY/TangentZ Constructor
+#define RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_TANGENTX_TANGENTY_TANGENTZ_COLOR_IMPLEMENTATION(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType) \
+	VertexName(RUNTIMEMESH_VERTEX_PARAMETER_POSITION(NeedsPosition) const FVector& InTangentX, const FVector& InTangentY, const FVector& InTangentZ, const FColor& InColor RUNTIMEMESH_VERTEX_PARAMETER_UVCHANNELS(UVChannelCount))	\
+	{																\
+		RUNTIMEMESH_VERTEX_INIT_POSITION(NeedsPosition)				\
+		SetNormalAndTangent(InTangentX, InTangentY, InTangentZ);	\
+		Color = InColor;											\
+		RUNTIMEMESH_VERTEX_INIT_UVCHANNELS(UVChannelCount)			\
+	}
+
+// Defines the Position/TangentX/TangentY/TangentZ Constructor if it's wanted
+#define RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_TANGENTX_TANGENTY_TANGENTZ_COLOR(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)								\
+	RUNTIMEMESH_VERTEX_CONSTRUCTOR_DEFINITION_IF(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, NeedsNormal,											\
+		RUNTIMEMESH_VERTEX_CONSTRUCTOR_DEFINITION_IF(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, NeedsTangent,										\
+			RUNTIMEMESH_VERTEX_CONSTRUCTOR_DEFINITION_IF(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, NeedsColor,										\
+				RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_TANGENTX_TANGENTY_TANGENTZ_COLOR_IMPLEMENTATION(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)	\
+			)																																																				\
+		)																																																					\
+	)
+
+
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_POSITION_true Ar << V.Position;
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_POSITION_false
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_POSITION(NeedsPosition) RUNTIMEMESH_VERTEX_SERIALIZATION_POSITION_##NeedsPosition
+
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_NORMAL_true Ar << V.Normal;
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_NORMAL_false
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_NORMAL(NeedsNormal) RUNTIMEMESH_VERTEX_SERIALIZATION_NORMAL_##NeedsNormal
+
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_TANGENT_true Ar << V.Tangent;
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_TANGENT_false
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_TANGENT(NeedsTangent) RUNTIMEMESH_VERTEX_SERIALIZATION_TANGENT_##NeedsTangent
+
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_COLOR_true Ar << V.Color;
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_COLOR_false
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_COLOR(NeedsColor) RUNTIMEMESH_VERTEX_SERIALIZATION_COLOR_##NeedsColor
+
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNEL_0
+
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNEL_1 \
+	Ar << V.UV0;
+
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNEL_2 \
+	RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNEL_1 \
+	Ar << V.UV1;
+
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNEL_3 \
+	RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNEL_2 \
+	Ar << V.UV2;
+
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNEL_4 \
+	RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNEL_3 \
+	Ar << V.UV3;
+
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNEL_5 \
+	RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNEL_4 \
+	Ar << V.UV4;
+
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNEL_6 \
+	RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNEL_5 \
+	Ar << V.UV5;
+
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNEL_7 \
+	RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNEL_6 \
+	Ar << V.UV6;
+
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNEL_8 \
+	RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNEL_7 \
+	Ar << V.UV7;
+
+#define RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNELS(NumChannels) RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNEL_##NumChannels
+
+#define RUNTIMEMESH_VERTEX_SERIALIZER(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount)	\
+	friend FArchive& operator<<(FArchive& Ar, VertexName & V)		\
+	{																\
+		RUNTIMEMESH_VERTEX_SERIALIZATION_POSITION(NeedsPosition)	\
+		RUNTIMEMESH_VERTEX_SERIALIZATION_NORMAL(NeedsNormal)		\
+		RUNTIMEMESH_VERTEX_SERIALIZATION_TANGENT(NeedsTangent)		\
+		RUNTIMEMESH_VERTEX_SERIALIZATION_COLOR(NeedsColor)			\
+		RUNTIMEMESH_VERTEX_SERIALIZATION_UVCHANNELS(UVChannelCount)	\
+		return Ar;													\
+	}
+
+
+
+#define DECLARE_RUNTIME_MESH_VERTEXINTERNAL(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, APIQUALIFIER)							\
+	struct APIQUALIFIER FRuntimeMeshVertexTypeInfo_##VertexName																															\
+		: public FRuntimeMeshVertexTypeInfo_GenericVertex<NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType>										\
+	{																																																\
+		FRuntimeMeshVertexTypeInfo_##VertexName()																																					\
+			: FRuntimeMeshVertexTypeInfo_GenericVertex<NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType>(TEXT("")) { }								\
+																																																	\
+		virtual class FRuntimeMeshSectionInterface* CreateSection(bool bInNeedsPositionOnlyBuffer) const override;																					\
+	};																																																\
+	struct APIQUALIFIER VertexName : public FRuntimeMeshVertex<NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType>						\
+	{																																																\
+		static const FRuntimeMeshVertexTypeInfo_##VertexName TypeInfo;																																\
+																																																	\
+		typedef FRuntimeMeshVertex<NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType> Super;														\
+																																																	\
+		VertexName() { }																																											\
+																																																	\
+		VertexName(EForceInit) : Super(EForceInit::ForceInit) { }																																	\
+																																																	\
+		RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)										\
+																																																	\
+		RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_NORMAL(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)								\
+																																																	\
+		RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_COLOR(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)								\
+																																																	\
+		RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_NORMAL_TANGENT(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)						\
+																																																	\
+		RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_TANGENTX_TANGENTY_TANGENTZ(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)			\
+																																																	\
+		RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_NORMAL_TANGENT_COLOR(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)					\
+																																																	\
+		RUNTIMEMESH_VERTEX_CONSTRUCTOR_POSITION_TANGENTX_TANGENTY_TANGENTZ_COLOR(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)		\
+																																																	\
+		RUNTIMEMESH_VERTEX_SERIALIZER(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount)																				\
+	};		
+
+#define DECLARE_RUNTIME_MESH_VERTEX(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType)		\
+	DECLARE_RUNTIME_MESH_VERTEXINTERNAL(VertexName, NeedsPosition, NeedsNormal, NeedsTangent, NeedsColor, UVChannelCount, TangentsType, UVChannelType, /**/)
+
+/* Used only for the generic vertex to create the type registration */
+#define DEFINE_RUNTIME_MESH_VERTEX(VertexName)																																						\
+	const FRuntimeMeshVertexTypeInfo_##VertexName VertexName::TypeInfo;																																\
+	FRuntimeMeshVertexTypeRegistration< VertexName > FRuntimeMeshVertexTypeInfoRegistration_##VertexName;																											\
+	FRuntimeMeshSectionInterface* FRuntimeMeshVertexTypeInfo_##VertexName::CreateSection(bool bInNeedsPositionOnlyBuffer) const																		\
+	{																																																\
+		return new FRuntimeMeshSection< VertexName >(bInNeedsPositionOnlyBuffer);																													\
+	}
+
+//////////////////////////////////////////////////////////////////////////
+// Template Vertex
+//////////////////////////////////////////////////////////////////////////
+
+// This version uses both sub combiners since there's at least 1 thing we need from both.
+template<bool WantsPosition, bool WantsNormal, bool WantsTangent, bool WantsColor, int32 NumWantedUVChannels,
+ERuntimeMeshVertexTangentBasisType NormalTangentType = ERuntimeMeshVertexTangentBasisType::Default, ERuntimeMeshVertexUVType UVType = ERuntimeMeshVertexUVType::Default>
+struct FRuntimeMeshVertex :
+	public FRuntimeMeshPositionNormalTangentComponentCombiner<WantsPosition, WantsNormal, WantsTangent, typename FRuntimeMeshVertexTangentTypeSelector<NormalTangentType>::TangentType>,
+	public FRuntimeMeshColorUVComponentCombiner<WantsColor, NumWantedUVChannels, typename FRuntimeMeshVertexUVsTypeSelector<UVType>::UVsType>
+{
+    // Make sure something is enabled
+    static_assert((WantsPosition || WantsNormal || WantsTangent || WantsColor || NumWantedUVChannels > 0), "Invalid configuration... You must have at least 1 component enabled.");
+        
+    // Get vertex structure
+    static RuntimeMeshVertexStructure GetVertexStructure(const FVertexBuffer& VertexBuffer);
+    
+    FRuntimeMeshVertex() { }
+    FRuntimeMeshVertex(EForceInit)
+    : FRuntimeMeshPositionNormalTangentComponentCombiner<WantsPosition, WantsNormal, WantsTangent, typename FRuntimeMeshVertexTangentTypeSelector<NormalTangentType>::TangentType>(EForceInit::ForceInit)
+    , FRuntimeMeshColorUVComponentCombiner<WantsColor, NumWantedUVChannels, typename FRuntimeMeshVertexUVsTypeSelector<UVType>::UVsType>(EForceInit::ForceInit)
+    { }
+};
+
+// This version only uses the position/normal/tangent combiner as we don't need anything from the other
+template<bool WantsPosition, bool WantsNormal, bool WantsTangent, ERuntimeMeshVertexTangentBasisType NormalTangentType, ERuntimeMeshVertexUVType UVType>
+struct FRuntimeMeshVertex<WantsPosition, WantsNormal, WantsTangent, false, 0, NormalTangentType, UVType> :
+	public FRuntimeMeshPositionNormalTangentComponentCombiner<WantsPosition, WantsNormal, WantsTangent, typename FRuntimeMeshVertexTangentTypeSelector<NormalTangentType>::TangentType>
+{
+    // Get vertex structure
+    static RuntimeMeshVertexStructure GetVertexStructure(const FVertexBuffer& VertexBuffer);
+    
+    FRuntimeMeshVertex() { }
+    FRuntimeMeshVertex(EForceInit)
+    : FRuntimeMeshPositionNormalTangentComponentCombiner<WantsPosition, WantsNormal, WantsTangent, typename FRuntimeMeshVertexTangentTypeSelector<NormalTangentType>::TangentType>(EForceInit::ForceInit)
+    { }
+};
+
+// This version only uses the color/uv combiner as we don't need anything from the other
+template<bool WantsColor, int32 NumWantedUVChannels, ERuntimeMeshVertexTangentBasisType NormalTangentType, ERuntimeMeshVertexUVType UVType>
+struct FRuntimeMeshVertex<false, false, false, WantsColor, NumWantedUVChannels, NormalTangentType, UVType> :
+	public FRuntimeMeshColorUVComponentCombiner<WantsColor, NumWantedUVChannels, typename FRuntimeMeshVertexUVsTypeSelector<UVType>::UVsType>
+{
+    // Get vertex structure
+    static RuntimeMeshVertexStructure GetVertexStructure(const FVertexBuffer& VertexBuffer);
+    
+    FRuntimeMeshVertex() { }
+    FRuntimeMeshVertex(EForceInit)
+    : FRuntimeMeshColorUVComponentCombiner<WantsColor, NumWantedUVChannels, typename FRuntimeMeshVertexUVsTypeSelector<UVType>::UVsType>(EForceInit::ForceInit)
+    { }
+};
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// Vertex Structure Generator
+//////////////////////////////////////////////////////////////////////////
+
+struct FRuntimeMeshVertexUtilities
+{
+    //////////////////////////////////////////////////////////////////////////
+    // Position Component
+    //////////////////////////////////////////////////////////////////////////
+    template<typename RuntimeVertexType, bool WantsPosition>
+    struct FRuntimeMeshPositionComponentUtilities
+    {
+        static void AddComponent(const FVertexBuffer& VertexBuffer, RuntimeMeshVertexStructure& VertexStructure)
+        {
+            VertexStructure.PositionComponent = RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, Position, VET_Float3);
+        }
+
+    };
+    
+    template<typename RuntimeVertexType>
+    struct FRuntimeMeshPositionComponentUtilities<RuntimeVertexType, false>
+    {
+        static void AddComponent(const FVertexBuffer& VertexBuffer, RuntimeMeshVertexStructure& VertexStructure)
+        {
+        }
+    };
+    
+    //////////////////////////////////////////////////////////////////////////
+    // Normal/Tangent Components
+    //////////////////////////////////////////////////////////////////////////
+    template<typename RuntimeVertexType, bool WantsNormal, bool WantsTangent, ERuntimeMeshVertexTangentBasisType NormalTangentType>
+    struct FRuntimeMeshNormalTangentComponentVertexStructure
+    {
+        static void AddComponent(const FVertexBuffer& VertexBuffer, RuntimeMeshVertexStructure& VertexStructure)
+        {
+            VertexStructure.TangentBasisComponents[1] = RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, Normal,
+                                                                                    FRuntimeMeshVertexTangentTypeSelector<NormalTangentType>::VertexElementType);
+            VertexStructure.TangentBasisComponents[0] = RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, Tangent,
+                                                                                    FRuntimeMeshVertexTangentTypeSelector<NormalTangentType>::VertexElementType);
+        }
+    };
+    
+    template<typename RuntimeVertexType, ERuntimeMeshVertexTangentBasisType NormalTangentType>
+    struct FRuntimeMeshNormalTangentComponentVertexStructure<RuntimeVertexType, true, false, NormalTangentType>
+    {
+        static void AddComponent(const FVertexBuffer& VertexBuffer, RuntimeMeshVertexStructure& VertexStructure)
+        {
+            VertexStructure.TangentBasisComponents[1] = RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, Normal,
+                                                                                    FRuntimeMeshVertexTangentTypeSelector<NormalTangentType>::VertexElementType);
+        }
+    };
+    
+    template<typename RuntimeVertexType, ERuntimeMeshVertexTangentBasisType NormalTangentType>
+    struct FRuntimeMeshNormalTangentComponentVertexStructure<RuntimeVertexType, false, true, NormalTangentType>
+    {
+        static void AddComponent(const FVertexBuffer& VertexBuffer, RuntimeMeshVertexStructure& VertexStructure)
+        {
+            VertexStructure.TangentBasisComponents[0] = RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, Tangent,
+                                                                                    FRuntimeMeshVertexTangentTypeSelector<NormalTangentType>::VertexElementType);
+        }
+    };
+    
+    template<typename RuntimeVertexType, ERuntimeMeshVertexTangentBasisType NormalTangentType>
+    struct FRuntimeMeshNormalTangentComponentVertexStructure<RuntimeVertexType, false, false, NormalTangentType>
+    {
+        static void AddComponent(const FVertexBuffer& VertexBuffer, RuntimeMeshVertexStructure& VertexStructure)
+        {
+        }
+    };
+    
+    //////////////////////////////////////////////////////////////////////////
+    // Color Component
+    //////////////////////////////////////////////////////////////////////////
+    template<typename RuntimeVertexType, bool WantsColor>
+    struct FRuntimeMeshColorComponentVertexStructure
+    {
+        static void AddComponent(const FVertexBuffer& VertexBuffer, RuntimeMeshVertexStructure& VertexStructure)
+        {
+            VertexStructure.ColorComponent = RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, Color, VET_Color);
+        }
+    };
+    
+    template<typename RuntimeVertexType>
+    struct FRuntimeMeshColorComponentVertexStructure<RuntimeVertexType, false>
+    {
+        static void AddComponent(const FVertexBuffer& VertexBuffer, RuntimeMeshVertexStructure& VertexStructure)
+        {
+        }
+    };
+    
+    
+    //////////////////////////////////////////////////////////////////////////
+    // UV Components
+    //////////////////////////////////////////////////////////////////////////
+    template<typename RuntimeVertexType, int32 NumWantedUVChannels, ERuntimeMeshVertexUVType UVType>
+    struct FRuntimeMeshTextureChannelsVertexStructure
+    {
+        static void AddChannels(const FVertexBuffer& VertexBuffer, RuntimeMeshVertexStructure& VertexStructure)
+        {
+        }
+    };
+    
+    template<typename RuntimeVertexType, ERuntimeMeshVertexUVType UVType>
+    struct FRuntimeMeshTextureChannelsVertexStructure<RuntimeVertexType, 1, UVType>
+    {
+        static void AddChannels(const FVertexBuffer& VertexBuffer, RuntimeMeshVertexStructure& VertexStructure)
+        {
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV0, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType1Channel));
+        }
+    };
+    
+    template<typename RuntimeVertexType, ERuntimeMeshVertexUVType UVType>
+    struct FRuntimeMeshTextureChannelsVertexStructure<RuntimeVertexType, 2, UVType>
+    {
+        static void AddChannels(const FVertexBuffer& VertexBuffer, RuntimeMeshVertexStructure& VertexStructure)
+        {
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV0, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType2Channel));
+        }
+    };
+    
+    template<typename RuntimeVertexType, ERuntimeMeshVertexUVType UVType>
+    struct FRuntimeMeshTextureChannelsVertexStructure<RuntimeVertexType, 3, UVType>
+    {
+        static void AddChannels(const FVertexBuffer& VertexBuffer, RuntimeMeshVertexStructure& VertexStructure)
+        {
+            
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV0, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType2Channel));
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV2, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType1Channel));
+        }
+    };
+    
+    template<typename RuntimeVertexType, ERuntimeMeshVertexUVType UVType>
+    struct FRuntimeMeshTextureChannelsVertexStructure<RuntimeVertexType, 4, UVType>
+    {
+        static void AddChannels(const FVertexBuffer& VertexBuffer, RuntimeMeshVertexStructure& VertexStructure)
+        {
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV0, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType2Channel));
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV2, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType2Channel));
+        }
+    };
+    
+    template<typename RuntimeVertexType, ERuntimeMeshVertexUVType UVType>
+    struct FRuntimeMeshTextureChannelsVertexStructure<RuntimeVertexType, 5, UVType>
+    {
+        static void AddChannels(const FVertexBuffer& VertexBuffer, RuntimeMeshVertexStructure& VertexStructure)
+        {
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV0, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType2Channel));
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV2, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType2Channel));
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV4, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType1Channel));
+        }
+    };
+    
+    template<typename RuntimeVertexType, ERuntimeMeshVertexUVType UVType>
+    struct FRuntimeMeshTextureChannelsVertexStructure<RuntimeVertexType, 6, UVType>
+    {
+        static void AddChannels(const FVertexBuffer& VertexBuffer, RuntimeMeshVertexStructure& VertexStructure)
+        {
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV0, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType2Channel));
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV2, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType2Channel));
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV4, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType2Channel));
+        }
+    };
+    
+    template<typename RuntimeVertexType, ERuntimeMeshVertexUVType UVType>
+    struct FRuntimeMeshTextureChannelsVertexStructure<RuntimeVertexType, 7, UVType>
+    {
+        static void AddChannels(const FVertexBuffer& VertexBuffer, RuntimeMeshVertexStructure& VertexStructure)
+        {
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV0, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType2Channel));
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV2, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType2Channel));
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV4, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType2Channel));
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV6, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType1Channel));
+        }
+    };
+    
+    template<typename RuntimeVertexType, ERuntimeMeshVertexUVType UVType>
+    struct FRuntimeMeshTextureChannelsVertexStructure<RuntimeVertexType, 8, UVType>
+    {
+        static void AddChannels(const FVertexBuffer& VertexBuffer, RuntimeMeshVertexStructure& VertexStructure)
+        {
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV0, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType2Channel));
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV2, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType2Channel));
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV4, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType2Channel));
+            VertexStructure.TextureCoordinates.Add(RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, RuntimeVertexType, UV6, FRuntimeMeshVertexUVsTypeSelector<UVType>::VertexElementType2Channel));
+        }
+    };
+    
+    
+    
+    //////////////////////////////////////////////////////////////////////////
+    // Vertex Structure Helper
+    //////////////////////////////////////////////////////////////////////////
+    template<bool WantsPosition, bool WantsNormal, bool WantsTangent, bool WantsColor, int32 NumWantedUVChannels,
+    ERuntimeMeshVertexTangentBasisType NormalTangentType, ERuntimeMeshVertexUVType UVType>
+    static RuntimeMeshVertexStructure CreateVertexStructure(const FVertexBuffer& VertexBuffer)
+    {
+        typedef FRuntimeMeshVertex<WantsPosition, WantsNormal, WantsTangent, WantsColor, NumWantedUVChannels, NormalTangentType, UVType> RuntimeVertexType;
+        
+        RuntimeMeshVertexStructure VertexStructure;
+        
+        // Add Position component if necessary
+        FRuntimeMeshPositionComponentUtilities<RuntimeVertexType, WantsPosition>::AddComponent(VertexBuffer, VertexStructure);
+        
+        // Add normal and tangent components if necessary
+        FRuntimeMeshNormalTangentComponentVertexStructure<RuntimeVertexType, WantsNormal, WantsTangent, NormalTangentType>::AddComponent(VertexBuffer, VertexStructure);
+        
+        // Add color component if necessary
+        FRuntimeMeshColorComponentVertexStructure<RuntimeVertexType, WantsColor>::AddComponent(VertexBuffer, VertexStructure);
+        
+        // Add all texture channels
+        FRuntimeMeshTextureChannelsVertexStructure<RuntimeVertexType, NumWantedUVChannels, UVType>::AddChannels(VertexBuffer, VertexStructure);
+        
+        return VertexStructure;
+    }
+};
+
+
+
+
+
+
+
+
+
+
+
+
+// These need to be declared after FRuntimemeshVertexStructureHelper and RuntimeMeshVertexStructure to fix circular dependencies between the two
+template<bool WantsPosition, bool WantsNormal, bool WantsTangent, bool WantsColor, int32 NumWantedUVChannels, ERuntimeMeshVertexTangentBasisType NormalTangentType, ERuntimeMeshVertexUVType UVType>
+RuntimeMeshVertexStructure FRuntimeMeshVertex<WantsPosition, WantsNormal, WantsTangent, WantsColor, NumWantedUVChannels, NormalTangentType, UVType>::GetVertexStructure(const FVertexBuffer& VertexBuffer)
+{
+    return FRuntimeMeshVertexUtilities::CreateVertexStructure<WantsPosition, WantsNormal, WantsTangent, WantsColor, NumWantedUVChannels, NormalTangentType, UVType>(VertexBuffer);
+}
+
+template<bool WantsPosition, bool WantsNormal, bool WantsTangent, ERuntimeMeshVertexTangentBasisType NormalTangentType, ERuntimeMeshVertexUVType UVType>
+RuntimeMeshVertexStructure FRuntimeMeshVertex<WantsPosition, WantsNormal, WantsTangent, false, 0, NormalTangentType, UVType>::GetVertexStructure(const FVertexBuffer& VertexBuffer)
+{
+    return FRuntimeMeshVertexUtilities::CreateVertexStructure<WantsPosition, WantsNormal, WantsTangent, false, 0, NormalTangentType, UVType>(VertexBuffer);
+}
+
+template<bool WantsColor, int32 NumWantedUVChannels, ERuntimeMeshVertexTangentBasisType NormalTangentType, ERuntimeMeshVertexUVType UVType>
+RuntimeMeshVertexStructure FRuntimeMeshVertex<false, false, false, WantsColor, NumWantedUVChannels, NormalTangentType, UVType>::GetVertexStructure(const FVertexBuffer& VertexBuffer)
+{
+    return FRuntimeMeshVertexUtilities::CreateVertexStructure<false, false, false, WantsColor, NumWantedUVChannels, NormalTangentType, UVType>(VertexBuffer);
+}
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// Name Vertex Configurations
+//////////////////////////////////////////////////////////////////////////
+
+/** Simple vertex with 1 UV channel */
+DECLARE_RUNTIME_MESH_VERTEXINTERNAL(FRuntimeMeshVertexSimple, true, true, true, true, 1, ERuntimeMeshVertexTangentBasisType::Default, ERuntimeMeshVertexUVType::HighPrecision, RUNTIMEMESHCOMPONENT_API)
+
+/** Simple vertex with 2 UV channels */
+DECLARE_RUNTIME_MESH_VERTEXINTERNAL(FRuntimeMeshVertexDualUV, true, true, true, true, 2, ERuntimeMeshVertexTangentBasisType::Default, ERuntimeMeshVertexUVType::HighPrecision, RUNTIMEMESHCOMPONENT_API)
+
+/** Simple vertex with 3 UV channels */
+DECLARE_RUNTIME_MESH_VERTEXINTERNAL(FRuntimeMeshVertexTripleUV, true, true, true, true, 3, ERuntimeMeshVertexTangentBasisType::Default, ERuntimeMeshVertexUVType::HighPrecision, RUNTIMEMESHCOMPONENT_API)
+
+/** Simple vertex with 1 UV channel and NO position component (Meant to be used with separate position buffer) */
+DECLARE_RUNTIME_MESH_VERTEXINTERNAL(FRuntimeMeshVertexNoPosition, false, true, true, true, 1, ERuntimeMeshVertexTangentBasisType::Default, ERuntimeMeshVertexUVType::HighPrecision, RUNTIMEMESHCOMPONENT_API)
+
+/** Simple vertex with 2 UV channels and NO position component (Meant to be used with separate position buffer) */
+DECLARE_RUNTIME_MESH_VERTEXINTERNAL(FRuntimeMeshVertexNoPositionDualUV, false, true, true, true, 2, ERuntimeMeshVertexTangentBasisType::Default, ERuntimeMeshVertexUVType::HighPrecision, RUNTIMEMESHCOMPONENT_API)
+
+/** Simple vertex with 1 UV channel */
+DECLARE_RUNTIME_MESH_VERTEXINTERNAL(FRuntimeMeshVertexHiPrecisionNormals, true, true, true, true, 1, ERuntimeMeshVertexTangentBasisType::HighPrecision, ERuntimeMeshVertexUVType::HighPrecision, RUNTIMEMESHCOMPONENT_API)
+
+/** Simple vertex with 2 UV channels */
+DECLARE_RUNTIME_MESH_VERTEXINTERNAL(FRuntimeMeshVertexDualUVHiPrecisionNormals, true, true, true, true, 2, ERuntimeMeshVertexTangentBasisType::HighPrecision, ERuntimeMeshVertexUVType::HighPrecision, RUNTIMEMESHCOMPONENT_API)
+
+/** Simple vertex with 1 UV channel and NO position component (Meant to be used with separate position buffer) */
+DECLARE_RUNTIME_MESH_VERTEXINTERNAL(FRuntimeMeshVertexNoPositionHiPrecisionNormals, false, true, true, true, 1, ERuntimeMeshVertexTangentBasisType::HighPrecision, ERuntimeMeshVertexUVType::HighPrecision, RUNTIMEMESHCOMPONENT_API)
+
+/** Simple vertex with 2 UV channels and NO position component (Meant to be used with separate position buffer) */
+DECLARE_RUNTIME_MESH_VERTEXINTERNAL(FRuntimeMeshVertexNoPositionDualUVHiPrecisionNormals, false, true, true, true, 2, ERuntimeMeshVertexTangentBasisType::HighPrecision, ERuntimeMeshVertexUVType::HighPrecision, RUNTIMEMESHCOMPONENT_API)
+
+

+ 129 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshLibrary.h

@@ -0,0 +1,129 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#pragma once
+
+#include "Kismet/BlueprintFunctionLibrary.h"
+#include "RuntimeMeshComponent.h"
+#include "RuntimeMeshLibrary.generated.h"
+
+class RuntimeMeshComponent;
+
+UCLASS()
+class RUNTIMEMESHCOMPONENT_API URuntimeMeshLibrary : public UBlueprintFunctionLibrary
+{
+	GENERATED_UCLASS_BODY()
+
+	/** Add a quad, specified by four indices, to a triangle index buffer as two triangles. */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	static void ConvertQuadToTriangles(UPARAM(ref) TArray<int32>& Triangles, int32 Vert0, int32 Vert1, int32 Vert2, int32 Vert3);
+
+	/**
+	*	Generate an index buffer for a grid of quads.
+	*	@param	NumX			Number of vertices in X direction (must be >= 2)
+	*	@param	NumY			Number of vertices in y direction (must be >= 2)
+	*	@param	bWinding		Reverses winding of indices generated for each quad
+	*	@out	Triangles		Output index buffer
+	*/
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	static void CreateGridMeshTriangles(int32 NumX, int32 NumY, bool bWinding, TArray<int32>& Triangles);
+
+	/** Generate vertex and index buffer for a simple box, given the supplied dimensions. Normals, UVs and tangents are also generated for each vertex. */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	static void CreateBoxMesh(FVector BoxRadius, TArray<FVector>& Vertices, TArray<int32>& Triangles, TArray<FVector>& Normals, TArray<FVector2D>& UVs, TArray<FRuntimeMeshTangent>& Tangents);
+
+
+
+	/**
+	*	Automatically generate normals and tangent vectors for a mesh
+	*	UVs are required for correct tangent generation.
+	*/
+	static void CalculateTangentsForMesh(IRuntimeMeshVerticesBuilder* Vertices, const FRuntimeMeshIndicesBuilder* Triangles);
+
+	/**
+	*	Automatically generate normals and tangent vectors for a mesh
+	*	UVs are required for correct tangent generation.
+	*/
+	template <typename VertexType>
+	static void CalculateTangentsForMesh(TArray<VertexType>& Vertices, const TArray<int32>& Triangles)
+	{
+		FRuntimeMeshPackedVerticesBuilder<VertexType> VerticesBuilder(&Vertices);
+		FRuntimeMeshIndicesBuilder IndicesBuilder(const_cast<TArray<int32>*>(&Triangles));
+
+		CalculateTangentsForMesh(&VerticesBuilder, &IndicesBuilder);
+	}
+
+	/**
+	*	Automatically generate normals and tangent vectors for a mesh
+	*	UVs are required for correct tangent generation.
+	*/
+	template <typename VertexType>
+	static void CalculateTangentsForMesh(TArray<FVector>& Positions, TArray<VertexType>& Vertices, const TArray<int32>& Triangles)
+	{
+		FRuntimeMeshPackedVerticesBuilder<VertexType> VerticesBuilder(&Vertices, &Positions);
+		FRuntimeMeshIndicesBuilder IndicesBuilder(const_cast<TArray<int32>*>(&Triangles));
+
+		CalculateTangentsForMesh(&VerticesBuilder, &IndicesBuilder);
+	}
+
+	/**
+	*	Automatically generate normals and tangent vectors for a mesh
+	*	UVs are required for correct tangent generation.
+	*/
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh", meta = (AutoCreateRefTerm = "UVs"))
+	static void CalculateTangentsForMesh(const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector2D>& UVs, TArray<FVector>& Normals, TArray<FRuntimeMeshTangent>& Tangents);
+
+
+
+	/**
+	*	Generates the tessellation indices needed to support tessellation in materials
+	*/
+	static void GenerateTessellationIndexBuffer(const IRuntimeMeshVerticesBuilder* Vertices, const FRuntimeMeshIndicesBuilder* Indices, FRuntimeMeshIndicesBuilder* OutTessellationIndices);
+
+	/**
+	*	Generates the tessellation indices needed to support tessellation in materials
+	*/
+	template <typename VertexType>
+	static void GenerateTessellationIndexBuffer(TArray<VertexType>& Vertices, const TArray<int32>& Triangles, TArray<int32>& OutTessTriangles)
+	{
+		FRuntimeMeshPackedVerticesBuilder<VertexType> VerticesBuilder(&Vertices);
+		FRuntimeMeshIndicesBuilder IndicesBuilder(const_cast<TArray<int32>*>(&Triangles));
+		FRuntimeMeshIndicesBuilder OutIndicesBuilder(&OutTessTriangles);
+
+		GenerateTessellationIndexBuffer(&VerticesBuilder, &IndicesBuilder, &OutIndicesBuilder);
+	}
+
+	/**
+	*	Generates the tessellation indices needed to support tessellation in materials
+	*/
+	template <typename VertexType>
+	static void GenerateTessellationIndexBuffer(TArray<FVector>& Positions, TArray<VertexType>& Vertices, const TArray<int32>& Triangles, TArray<int32>& OutTessTriangles)
+	{
+		FRuntimeMeshPackedVerticesBuilder<VertexType> VerticesBuilder(&Vertices, &Positions);
+		FRuntimeMeshIndicesBuilder IndicesBuilder(const_cast<TArray<int32>*>(&Triangles));
+		FRuntimeMeshIndicesBuilder OutIndicesBuilder(&OutTessTriangles);
+
+		GenerateTessellationIndexBuffer(&VerticesBuilder, &IndicesBuilder, &OutIndicesBuilder);
+	}
+
+	/**
+	*	Generates the tessellation indices needed to support tessellation in materials
+	*/
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh", meta = (AutoCreateRefTerm = "UVs"))
+	static void GenerateTessellationIndexBuffer(const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector2D>& UVs, TArray<FVector>& Normals, TArray<FRuntimeMeshTangent>& Tangents, TArray<int32>& OutTessTriangles);
+
+	
+
+	/** Grab geometry data from a StaticMesh asset. */
+	static void GetSectionFromStaticMesh(UStaticMesh* InMesh, int32 LODIndex, int32 SectionIndex,
+		IRuntimeMeshVerticesBuilder* Vertices, FRuntimeMeshIndicesBuilder* Triangles, FRuntimeMeshIndicesBuilder* AdjacencyTriangles);
+
+	/** Grab geometry data from a StaticMesh asset. */
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	static void GetSectionFromStaticMesh(UStaticMesh* InMesh, int32 LODIndex, int32 SectionIndex, TArray<FVector>& Vertices, TArray<int32>& Triangles, TArray<FVector>& Normals, TArray<FVector2D>& UVs, TArray<FRuntimeMeshTangent>& Tangents);
+	
+	/* Copies an entire Static Mesh to a Runtime Mesh. Includes all materials, and sections.*/
+	UFUNCTION(BlueprintCallable, Category = "Components|RuntimeMesh")
+	static void CopyRuntimeMeshFromStaticMeshComponent(UStaticMeshComponent* StaticMeshComp, int32 LODIndex, URuntimeMeshComponent* RuntimeMeshComp, bool bShouldCreateCollision);
+	
+
+};

+ 74 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshProfiling.h

@@ -0,0 +1,74 @@
+// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "RuntimeMeshComponent.h"
+
+DECLARE_STATS_GROUP(TEXT("RuntimeMesh"), STATGROUP_RuntimeMesh, STATCAT_Advanced);
+
+// Scene Proxy Profiling
+DECLARE_CYCLE_STAT(TEXT("Create Section (RT)"), STAT_RuntimeMesh_CreateSection_RenderThread, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("Update Section (RT)"), STAT_RuntimeMesh_UpdateSection_RenderThread, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("Update Section - Position Only (RT)"), STAT_RuntimeMesh_UpdateSectionPositionOnly_RenderThread, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("Update Section Properties (RT)"), STAT_RuntimeMesh_UpdateSectionProperties_RenderThread, STATGROUP_RuntimeMesh);
+
+DECLARE_CYCLE_STAT(TEXT("Apply Batch Update (RT)"), STAT_RuntimeMesh_ApplyBatchUpdate_RenderThread, STATGROUP_RuntimeMesh);
+
+DECLARE_CYCLE_STAT(TEXT("On Transform Changed (RT)"), STAT_RuntimeMesh_OnTransformChanged, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("Draw Static Elements (RT)"), STAT_RuntimeMesh_DrawStaticElements, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("Get Dynamic Mesh Elements (RT)"), STAT_RuntimeMesh_GetDynamicMeshElements, STATGROUP_RuntimeMesh);
+
+// RuntimeMeshComponent Profiling
+
+DECLARE_CYCLE_STAT(TEXT("CreateMeshSection<VertexType> (GT)"), STAT_RuntimeMesh_CreateMeshSection_VertexType, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("CreateMeshSection<VertexType> (With Bounding Box) (GT)"), STAT_RuntimeMesh_CreateMeshSection_VertexType_WithBoundingBox, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("CreateMeshSectionDualBuffer<VertexType> (GT)"), STAT_RuntimeMesh_CreateMeshSectionDualBuffer_VertexType, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("CreateMeshSectionDualBuffer<VertexType> (With Bounding Box) (GT)"), STAT_RuntimeMesh_CreateMeshSectionDualBuffer_VertexType_WithBoundingBox, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("CreateMeshSection<VertexType> (From Mesh Builder) (GT)"), STAT_RuntimeMesh_CreateMeshSection_VertexType_FromMeshBuilder, STATGROUP_RuntimeMesh);
+
+
+
+DECLARE_CYCLE_STAT(TEXT("CreateMeshSection (GT)"), STAT_RuntimeMesh_CreateMeshSection, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("CreateMeshSection (GT)"), STAT_RuntimeMesh_CreateMeshSection_DualUV, STATGROUP_RuntimeMesh);
+
+DECLARE_CYCLE_STAT(TEXT("UpdateMeshSection<VertexType> (GT)"), STAT_RuntimeMesh_UpdateMeshSection_VertexType, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("UpdateMeshSection<VertexType> (With Bounding Box) (GT)"), STAT_RuntimeMesh_UpdateMeshSection_VertexType_WithBoundingBox, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("UpdateMeshSection<VertexType> (With Triangles) (GT)"), STAT_RuntimeMesh_UpdateMeshSection_VertexType_WithTriangles, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("UpdateMeshSection<VertexType> (With Triangles and Bounding Box) (GT)"), STAT_RuntimeMesh_UpdateMeshSection_VertexType_WithTrianglesAndBoundinBox, STATGROUP_RuntimeMesh);
+
+DECLARE_CYCLE_STAT(TEXT("UpdateMeshSection<VertexType> (Dual) (GT)"), STAT_RuntimeMesh_UpdateMeshSection_Dual_VertexType, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("UpdateMeshSection<VertexType> (Dual) (With Bounding Box) (GT)"), STAT_RuntimeMesh_UpdateMeshSection_Dual_VertexType_WithBoundingBox, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("UpdateMeshSection<VertexType> (Dual) (With Triangles) (GT)"), STAT_RuntimeMesh_UpdateMeshSection_Dual_VertexType_WithTriangles, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("UpdateMeshSection<VertexType> (Dual) (With Triangles and Bounding Box) (GT)"), STAT_RuntimeMesh_UpdateMeshSection_Dual_VertexType_WithTrianglesAndBoundinBox, STATGROUP_RuntimeMesh);
+
+DECLARE_CYCLE_STAT(TEXT("UpdateMeshSection (GT)"), STAT_RuntimeMesh_UpdateMeshSection, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("UpdateMeshSection (GT)"), STAT_RuntimeMesh_UpdateMeshSection_DualUV, STATGROUP_RuntimeMesh);
+
+
+
+
+
+
+
+DECLARE_CYCLE_STAT(TEXT("UpdateMeshSectionPositionsImmediate (GT)"), STAT_RuntimeMesh_UpdateMeshSectionPositionsImmediate, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("UpdateMeshSectionPositionsImmediate (With Bounding Box) (GT)"), STAT_RuntimeMesh_UpdateMeshSectionPositionsImmediate_WithBoundinBox, STATGROUP_RuntimeMesh);
+
+DECLARE_CYCLE_STAT(TEXT("Finish Create Section (GT)"), STAT_RuntimeMesh_FinishCreateSectionInternal, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("Finish Update Section (GT)"), STAT_RuntimeMesh_FinishUpdateSectionInternal, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("Clear Mesh Section (GT)"), STAT_RuntimeMesh_ClearMeshSection, STATGROUP_RuntimeMesh);
+
+
+DECLARE_CYCLE_STAT(TEXT("Set Mesh Collision Section (GT)"), STAT_RuntimeMesh_SetMeshCollisionSection, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("Clear Mesh Collision Section (GT)"), STAT_RuntimeMesh_ClearMeshCollisionSection, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("Clear All Mesh Collision Sections (GT)"), STAT_RuntimeMesh_ClearAllMeshCollisionSections, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("Add Collision Convex Mesh (GT)"), STAT_RuntimeMesh_AddCollisionConvexMesh, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("Clear Collision Convex Mesh (GT)"), STAT_RuntimeMesh_ClearCollisionConvexMeshes, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("Set Collision Convex Meshes (GT)"), STAT_RuntimeMesh_SetCollisionConvexMeshes, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("Create Scene Proxy (GT)"), STAT_RuntimeMesh_CreateSceneProxy, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("Get Physics TriMesh Data (GT)"), STAT_RuntimeMesh_GetPhysicsTriMeshData, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("Update Collision (GT)"), STAT_RuntimeMesh_UpdateCollision, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("Update Local Bounds (GT)"), STAT_RuntimeMesh_UpdateLocalBounds, STATGROUP_RuntimeMesh);
+DECLARE_CYCLE_STAT(TEXT("Serialize"), STAT_RuntimeMesh_Serialize, STATGROUP_RuntimeMesh);
+
+
+

+ 187 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshRendering.h

@@ -0,0 +1,187 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#pragma once
+
+#include "Engine.h"
+#include "RuntimeMeshCore.h"
+
+
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 12
+/** Structure definition of a vertex */
+using RuntimeMeshVertexStructure = FLocalVertexFactory::FDataType;
+#else
+/** Structure definition of a vertex */
+using RuntimeMeshVertexStructure = FLocalVertexFactory::DataType;
+#endif
+
+#define RUNTIMEMESH_VERTEXCOMPONENT(VertexBuffer, VertexType, Member, MemberType) \
+	STRUCTMEMBER_VERTEXSTREAMCOMPONENT(&VertexBuffer, VertexType, Member, MemberType)
+
+
+/* 
+ *	Defines an interface for the proxy sections so that 
+ *	the vertex factory can query for section visibility 
+ */
+class FRuntimeMeshVisibilityInterface
+{
+public:
+	virtual bool ShouldRender() = 0;
+};
+
+
+/** Vertex Buffer for one section. Templated to support different vertex types */
+template<typename VertexType>
+class FRuntimeMeshVertexBuffer : public FVertexBuffer
+{
+public:
+
+	FRuntimeMeshVertexBuffer(EUpdateFrequency SectionUpdateFrequency) : VertexCount(0)
+	{
+		UsageFlags = SectionUpdateFrequency == EUpdateFrequency::Frequent ? BUF_Dynamic : BUF_Static;
+	}
+
+	virtual void InitRHI() override
+	{
+		// Create the vertex buffer
+		FRHIResourceCreateInfo CreateInfo;
+		VertexBufferRHI = RHICreateVertexBuffer(sizeof(VertexType) * VertexCount, UsageFlags, CreateInfo);
+	}
+
+	/* Get the size of the vertex buffer */
+	int32 Num() { return VertexCount; }
+	
+	/* Set the size of the vertex buffer */
+	void SetNum(int32 NewVertexCount)
+	{
+		check(NewVertexCount != 0);
+
+		// Make sure we're not already the right size
+		if (NewVertexCount != VertexCount)
+		{
+			VertexCount = NewVertexCount;
+			
+			// Rebuild resource
+			ReleaseResource();
+			InitResource();
+		}
+	}
+
+	/* Set the data for the vertex buffer */
+	void SetData(const TArray<VertexType>& Data)
+	{
+		check(Data.Num() == VertexCount);
+
+		// Lock the vertex buffer
+ 		void* Buffer = RHILockVertexBuffer(VertexBufferRHI, 0, Data.Num() * sizeof(VertexType), RLM_WriteOnly);
+ 		 
+ 		// Write the vertices to the vertex buffer
+ 		FMemory::Memcpy(Buffer, Data.GetData(), Data.Num() * sizeof(VertexType));
+
+		// Unlock the vertex buffer
+ 		RHIUnlockVertexBuffer(VertexBufferRHI);
+	}
+
+private:
+
+	/* The number of vertices this buffer is currently allocated to hold */
+	int32 VertexCount;
+	/* The buffer configuration to use */
+	EBufferUsageFlags UsageFlags;
+};
+
+/** Index Buffer */
+class FRuntimeMeshIndexBuffer : public FIndexBuffer
+{
+public:
+
+	FRuntimeMeshIndexBuffer(EUpdateFrequency SectionUpdateFrequency) : IndexCount(0)
+	{
+		UsageFlags = SectionUpdateFrequency == EUpdateFrequency::Frequent ? BUF_Dynamic : BUF_Static;
+	}
+
+	virtual void InitRHI() override
+	{
+		// Create the index buffer
+		FRHIResourceCreateInfo CreateInfo;
+		IndexBufferRHI = RHICreateIndexBuffer(sizeof(int32), IndexCount * sizeof(int32), BUF_Dynamic, CreateInfo);
+	}
+
+	/* Get the size of the index buffer */
+	int32 Num() { return IndexCount; }
+
+	/* Set the size of the index buffer */
+	void SetNum(int32 NewIndexCount)
+	{
+		check(NewIndexCount != 0);
+
+		// Make sure we're not already the right size
+		if (NewIndexCount != IndexCount)
+		{
+			IndexCount = NewIndexCount;
+
+			// Rebuild resource
+			ReleaseResource();
+			InitResource();
+		}
+	}
+
+	/* Set the data for the index buffer */
+	void SetData(const TArray<int32>& Data)
+	{
+		check(Data.Num() == IndexCount);
+
+		// Lock the index buffer
+		void* Buffer = RHILockIndexBuffer(IndexBufferRHI, 0, IndexCount * sizeof(int32), RLM_WriteOnly);
+
+		// Write the indices to the vertex buffer	
+		FMemory::Memcpy(Buffer, Data.GetData(), Data.Num() * sizeof(int32));
+
+		// Unlock the index buffer
+		RHIUnlockIndexBuffer(IndexBufferRHI);
+	}
+
+private:
+
+	/* The number of indices this buffer is currently allocated to hold */
+	int32 IndexCount;
+	/* The buffer configuration to use */
+	EBufferUsageFlags UsageFlags;
+};
+
+/** Vertex Factory */
+class FRuntimeMeshVertexFactory : public FLocalVertexFactory
+{
+public:
+
+	FRuntimeMeshVertexFactory(FRuntimeMeshVisibilityInterface* InSectionParent) : SectionParent(InSectionParent) { }
+		
+	/** Init function that can be called on any thread, and will do the right thing (enqueue command if called on main thread) */
+	void Init(const RuntimeMeshVertexStructure VertexStructure)
+	{
+		if (IsInRenderingThread())
+		{
+			SetData(VertexStructure);
+		}
+		else
+		{
+			// Send the command to the render thread
+			ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
+				InitRuntimeMeshVertexFactory,
+				FRuntimeMeshVertexFactory*, VertexFactory, this,
+				const RuntimeMeshVertexStructure, VertexStructure, VertexStructure,
+				{
+					VertexFactory->Init(VertexStructure);
+				});
+		}
+	}
+
+	/* Gets the section visibility for static sections */
+	virtual uint64 GetStaticBatchElementVisibility(const class FSceneView& View, const struct FMeshBatch* Batch) const override
+	{
+		return SectionParent->ShouldRender();
+	}
+
+private:
+	/* Interface to the parent section for checking visibility.*/
+	FRuntimeMeshVisibilityInterface* SectionParent;
+};

+ 812 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshSection.h

@@ -0,0 +1,812 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#pragma once
+
+#include "Engine.h"
+#include "Components/MeshComponent.h"
+#include "RuntimeMeshProfiling.h"
+#include "RuntimeMeshVersion.h"
+#include "RuntimeMeshSectionProxy.h"
+#include "RuntimeMeshBuilder.h"
+#include "RuntimeMeshLibrary.h"
+
+/** Interface class for a single mesh section */
+class FRuntimeMeshSectionInterface
+{
+protected:
+	const bool bNeedsPositionOnlyBuffer;
+
+public:
+	/** Position only vertex buffer for this section */
+	TArray<FVector> PositionVertexBuffer;
+
+	/** Index buffer for this section */
+	TArray<int32> IndexBuffer;
+
+	/** Index buffer used for tessellation containing the needed adjacency info */
+	TArray<int32> TessellationIndexBuffer;
+
+	/** Local bounding box of section */
+	FBox LocalBoundingBox;
+
+	/** Should we build collision data for triangles in this section */
+	bool CollisionEnabled;
+
+	/** Should we display this section */
+	bool bIsVisible;
+
+	/** Should this section cast a shadow */
+	bool bCastsShadow;
+
+	/** If this section is currently using an adjacency index buffer */
+	bool bShouldUseAdjacencyIndexBuffer;
+
+	/** Update frequency of this section */
+	EUpdateFrequency UpdateFrequency;
+
+	FRuntimeMeshSectionInterface(bool bInNeedsPositionOnlyBuffer) : 
+		bNeedsPositionOnlyBuffer(bInNeedsPositionOnlyBuffer),
+		LocalBoundingBox(EForceInit::ForceInitToZero),
+		CollisionEnabled(false),
+		bIsVisible(true),
+		bCastsShadow(true),
+		bIsLegacySectionType(false)
+	{}
+
+	virtual ~FRuntimeMeshSectionInterface() { }
+
+protected:
+
+	/** Is this an internal section type. */
+	bool bIsLegacySectionType;
+
+	bool IsDualBufferSection() const { return bNeedsPositionOnlyBuffer; }
+
+	/* Updates the vertex position buffer,   returns whether we have a new bounding box */
+	bool UpdateVertexPositionBuffer(TArray<FVector>& Positions, const FBox* BoundingBox, bool bShouldMoveArray)
+	{
+		// Holds the new bounding box after this update.
+		FBox NewBoundingBox(EForceInit::ForceInitToZero);
+
+		if (bShouldMoveArray)
+		{
+			// Move buffer data
+			PositionVertexBuffer = MoveTemp(Positions);
+
+			// Calculate the bounding box if one doesn't exist.
+			if (BoundingBox == nullptr)
+			{
+				for (int32 VertexIdx = 0; VertexIdx < PositionVertexBuffer.Num(); VertexIdx++)
+				{
+					NewBoundingBox += PositionVertexBuffer[VertexIdx];
+				}
+			}
+			else
+			{
+				// Copy the supplied bounding box instead of calculating it.
+				NewBoundingBox = *BoundingBox;
+			}
+		}
+		else
+		{
+			if (BoundingBox == nullptr)
+			{
+				// Copy the buffer and calculate the bounding box at the same time
+				int32 NumVertices = Positions.Num();
+				PositionVertexBuffer.SetNumUninitialized(NumVertices);
+				for (int32 VertexIdx = 0; VertexIdx < NumVertices; VertexIdx++)
+				{
+					NewBoundingBox += Positions[VertexIdx];
+					PositionVertexBuffer[VertexIdx] = Positions[VertexIdx];
+				}
+			}
+			else
+			{
+				// Copy the buffer
+				PositionVertexBuffer = Positions;
+
+				// Copy the supplied bounding box instead of calculating it.
+				NewBoundingBox = *BoundingBox;
+			}
+		}
+
+		// Update the bounding box if necessary and alert our caller if we did
+		if (!(LocalBoundingBox == NewBoundingBox))
+		{
+			LocalBoundingBox = NewBoundingBox;
+			return true;
+		}
+
+		return false;
+	}
+
+	virtual void UpdateVertexBuffer(IRuntimeMeshVerticesBuilder& Vertices, const FBox* BoundingBox, bool bShouldMoveArray) = 0;
+
+	void UpdateIndexBuffer(TArray<int32>& Triangles, bool bShouldMoveArray)
+	{
+		if (bShouldMoveArray)
+		{
+			IndexBuffer = MoveTemp(Triangles);
+		}
+		else
+		{
+			IndexBuffer = Triangles;
+		}
+	}
+
+	void UpdateIndexBuffer(FRuntimeMeshIndicesBuilder& Triangles, bool bShouldMoveArray)
+	{
+		if (bShouldMoveArray)
+		{
+			IndexBuffer = MoveTemp(*Triangles.GetIndices());
+			Triangles.Reset();
+		}
+		else
+		{
+			IndexBuffer = *Triangles.GetIndices();
+		}
+	}
+
+	void UpdateTessellationIndexBuffer(TArray<int32>& Triangles, bool bShouldMoveArray)
+	{
+		if (bShouldMoveArray)
+		{
+			TessellationIndexBuffer = MoveTemp(Triangles);
+		}
+		else
+		{
+			TessellationIndexBuffer = Triangles;
+		}
+	}
+
+	virtual FRuntimeMeshSectionCreateDataInterface* GetSectionCreationData(FSceneInterface* InScene, UMaterialInterface* InMaterial) const = 0;
+
+	virtual FRuntimeMeshRenderThreadCommandInterface* GetSectionUpdateData(bool bIncludePositionVertices, bool bIncludeVertices, bool bIncludeIndices) const = 0;
+
+	virtual FRuntimeMeshRenderThreadCommandInterface* GetSectionPositionUpdateData() const = 0;
+
+	virtual void RecalculateBoundingBox() = 0;
+
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 13
+	virtual int32 GetCollisionInformation(TArray<FVector>& Positions, TArray<TArray<FVector2D>>& UVs, bool bIncludeUVs) = 0;
+#else
+	virtual int32 GetCollisionInformation(TArray<FVector>& Positions) = 0;
+#endif
+
+	virtual void GetInternalVertexComponents(int32& NumUVChannels, bool& WantsHalfPrecisionUVs) { }
+
+	// This is only meant for internal use for supporting the old style create/update sections
+	virtual bool UpdateVertexBufferInternal(const TArray<FVector>& Positions, const TArray<FVector>& Normals, const TArray<FRuntimeMeshTangent>& Tangents, const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, const TArray<FColor>& Colors) { return false; }
+	
+	virtual void GetSectionMesh(IRuntimeMeshVerticesBuilder*& Vertices, FRuntimeMeshIndicesBuilder*& Indices) = 0;
+
+	virtual const FRuntimeMeshVertexTypeInfo* GetVertexType() const = 0;
+
+	virtual void GenerateNormalTangent() = 0;
+
+	virtual void GenerateTessellationIndices() = 0;
+
+
+	virtual void Serialize(FArchive& Ar)
+	{
+		if (Ar.CustomVer(FRuntimeMeshVersion::GUID) >= FRuntimeMeshVersion::SerializationV2)
+		{
+			if (bNeedsPositionOnlyBuffer)
+			{
+				Ar << PositionVertexBuffer;
+			}
+			Ar << IndexBuffer;
+			Ar << TessellationIndexBuffer;
+			Ar << LocalBoundingBox;
+			Ar << CollisionEnabled;
+			Ar << bIsVisible;
+			Ar << bCastsShadow;
+			Ar << bShouldUseAdjacencyIndexBuffer;
+
+			// Serialize the update frequency as an int32
+			int32 UpdateFreq = (int32)UpdateFrequency;
+			Ar << UpdateFreq;
+			UpdateFrequency = (EUpdateFrequency)UpdateFreq;
+
+			Ar << bIsLegacySectionType;
+		}
+		else
+		{
+			if (Ar.CustomVer(FRuntimeMeshVersion::GUID) >= FRuntimeMeshVersion::DualVertexBuffer)
+			{
+				Ar << PositionVertexBuffer;
+			}
+			Ar << IndexBuffer;
+			Ar << LocalBoundingBox;
+			Ar << CollisionEnabled;
+			Ar << bIsVisible;
+			int32 UpdateFreq = (int32)UpdateFrequency;
+			Ar << UpdateFreq;
+			UpdateFrequency = (EUpdateFrequency)UpdateFreq;
+		}
+	}
+
+	
+
+	friend class FRuntimeMeshSceneProxy;
+	friend class URuntimeMeshComponent;
+};
+
+namespace RuntimeMeshSectionInternal
+{
+	template<typename Type>
+	static typename TEnableIf<FRuntimeMeshVertexTraits<Type>::HasPosition, int32>::Type
+		GetAllVertexPositions(const TArray<Type>& VertexBuffer, const TArray<FVector>& PositionVertexBuffer, TArray<FVector>& Positions)
+	{
+		int32 VertexCount = VertexBuffer.Num();
+		for (int32 VertIdx = 0; VertIdx < VertexCount; VertIdx++)
+		{
+			Positions.Add(VertexBuffer[VertIdx].Position);
+		}
+		return VertexCount;
+	}
+
+	template<typename Type>
+	static typename TEnableIf<!FRuntimeMeshVertexTraits<Type>::HasPosition, int32>::Type
+		GetAllVertexPositions(const TArray<Type>& VertexBuffer, const TArray<FVector>& PositionVertexBuffer, TArray<FVector>& Positions)
+	{
+		Positions.Append(PositionVertexBuffer);
+		return PositionVertexBuffer.Num();
+	}
+
+
+
+	template<typename Type>
+	static typename TEnableIf<FRuntimeMeshVertexTraits<Type>::HasPosition, bool>::Type
+		UpdateVertexBufferInternal(TArray<Type>& VertexBuffer, FBox& LocalBoundingBox, TArray<Type>& Vertices, const FBox* BoundingBox, bool bShouldMoveArray)
+	{
+		// Holds the new bounding box after this update.
+		FBox NewBoundingBox(EForceInit::ForceInitToZero);
+
+		if (bShouldMoveArray)
+		{
+			// Move buffer data
+			VertexBuffer = MoveTemp(Vertices);
+
+			// Calculate the bounding box if one doesn't exist.
+			if (BoundingBox == nullptr)
+			{
+				for (int32 VertexIdx = 0; VertexIdx < VertexBuffer.Num(); VertexIdx++)
+				{
+					NewBoundingBox += VertexBuffer[VertexIdx].Position;
+				}
+			}
+			else
+			{
+				// Copy the supplied bounding box instead of calculating it.
+				NewBoundingBox = *BoundingBox;
+			}
+		}
+		else
+		{
+			if (BoundingBox == nullptr)
+			{
+				// Copy the buffer and calculate the bounding box at the same time
+				int32 NumVertices = Vertices.Num();
+				VertexBuffer.SetNumUninitialized(NumVertices);
+				for (int32 VertexIdx = 0; VertexIdx < NumVertices; VertexIdx++)
+				{
+					NewBoundingBox += Vertices[VertexIdx].Position;
+					VertexBuffer[VertexIdx] = Vertices[VertexIdx];
+				}
+			}
+			else
+			{
+				// Copy the buffer
+				VertexBuffer = Vertices;
+
+				// Copy the supplied bounding box instead of calculating it.
+				NewBoundingBox = *BoundingBox;
+			}
+		}
+
+		// Update the bounding box if necessary and alert our caller if we did
+		if (!(LocalBoundingBox == NewBoundingBox))
+		{
+			LocalBoundingBox = NewBoundingBox;
+			return true;
+		}
+
+		return false;
+	}
+
+	template<typename Type>
+	static typename TEnableIf<!FRuntimeMeshVertexTraits<Type>::HasPosition, bool>::Type
+		UpdateVertexBufferInternal(TArray<Type>& VertexBuffer, FBox& LocalBoundingBox, TArray<Type>& Vertices, const FBox* BoundingBox, bool bShouldMoveArray)
+	{
+		if (bShouldMoveArray)
+		{
+			VertexBuffer = MoveTemp(Vertices);
+		}
+		else
+		{
+			VertexBuffer = Vertices;
+		}
+		return false;
+	}
+
+
+	template<typename Type>
+	static typename TEnableIf<FRuntimeMeshVertexTraits<Type>::HasPosition>::Type RecalculateBoundingBox(TArray<Type>& VertexBuffer, FBox& BoundingBox)
+	{
+		for (int32 Index = 0; Index < VertexBuffer.Num(); Index++)
+		{
+			BoundingBox += VertexBuffer[Index].Position;
+		}
+	}
+
+	template<typename Type>
+	static typename TEnableIf<!FRuntimeMeshVertexTraits<Type>::HasPosition>::Type RecalculateBoundingBox(TArray<Type>& VertexBuffer, FBox& BoundingBox)
+	{
+	}
+
+}
+
+/** Templated class for a single mesh section */
+template<typename VertexType>
+class FRuntimeMeshSection : public FRuntimeMeshSectionInterface
+{
+
+public:
+	/** Vertex buffer for this section */
+	TArray<VertexType> VertexBuffer;
+
+	FRuntimeMeshSection(bool bInNeedsPositionOnlyBuffer) : FRuntimeMeshSectionInterface(bInNeedsPositionOnlyBuffer) { }
+	virtual ~FRuntimeMeshSection() override { }
+
+
+protected:
+	bool UpdateVertexBuffer(TArray<VertexType>& Vertices, const FBox* BoundingBox, bool bShouldMoveArray)
+	{
+		return RuntimeMeshSectionInternal::UpdateVertexBufferInternal<VertexType>(VertexBuffer, LocalBoundingBox, Vertices, BoundingBox, bShouldMoveArray);
+	}
+
+	virtual void UpdateVertexBuffer(IRuntimeMeshVerticesBuilder& Vertices, const FBox* BoundingBox, bool bShouldMoveArray) override
+	{
+		if (Vertices.GetBuilderType() == ERuntimeMeshVerticesBuilderType::Component)
+		{
+			FRuntimeMeshComponentVerticesBuilder* VerticesBuilder = static_cast<FRuntimeMeshComponentVerticesBuilder*>(&Vertices);
+
+			TArray<FVector>* Positions = VerticesBuilder->GetPositions();
+			TArray<FVector>* Normals = VerticesBuilder->GetNormals();
+			TArray<FRuntimeMeshTangent>* Tangents = VerticesBuilder->GetTangents();
+			TArray<FColor>* Colors = VerticesBuilder->GetColors();
+			TArray<FVector2D>* UV0s = VerticesBuilder->GetUV0s();
+			TArray<FVector2D>* UV1s = VerticesBuilder->GetUV1s();
+					
+
+			UpdateVertexBufferInternal(
+				Positions ? *Positions : TArray<FVector>(),
+				Normals ? *Normals : TArray<FVector>(),
+				Tangents ? *Tangents : TArray<FRuntimeMeshTangent>(),
+				UV0s ? *UV0s : TArray<FVector2D>(),
+				UV1s ? *UV1s : TArray<FVector2D>(),
+				Colors ? *Colors : TArray<FColor>());
+
+			if (BoundingBox)
+			{
+				LocalBoundingBox = *BoundingBox;
+			}
+			else
+			{
+				LocalBoundingBox = FBox(*Positions);
+			}
+
+			if (bShouldMoveArray)
+			{
+				// This is just to keep similar behavior to the packed vertices builder.
+				Vertices.Reset();
+			}
+		}
+		else
+		{
+			// Make sure section type is the same
+			Vertices.GetVertexType()->EnsureEquals<VertexType>();
+
+			FRuntimeMeshPackedVerticesBuilder<VertexType>* VerticesBuilder = static_cast<FRuntimeMeshPackedVerticesBuilder<VertexType>*>(&Vertices);
+
+			RuntimeMeshSectionInternal::UpdateVertexBufferInternal<VertexType>(VertexBuffer, LocalBoundingBox, *VerticesBuilder->GetVertices(), BoundingBox, bShouldMoveArray);
+
+			if (BoundingBox == nullptr && VerticesBuilder->WantsSeparatePositionBuffer())
+			{
+				LocalBoundingBox = FBox(*VerticesBuilder->GetPositions());
+			}
+		}	
+	}
+
+	virtual FRuntimeMeshSectionCreateDataInterface* GetSectionCreationData(FSceneInterface* InScene, UMaterialInterface* InMaterial) const override
+	{
+		auto UpdateData = new FRuntimeMeshSectionCreateData<VertexType>();
+
+		FMaterialRelevance MaterialRelevance = (InMaterial != nullptr) 
+			? InMaterial->GetRelevance(InScene->GetFeatureLevel()) 
+			: UMaterial::GetDefaultMaterial(MD_Surface)->GetRelevance(InScene->GetFeatureLevel());
+
+		// Create new section proxy based on whether we need separate position buffer
+		if (IsDualBufferSection())
+		{
+			UpdateData->NewProxy = new FRuntimeMeshSectionProxy<VertexType, true>(InScene, UpdateFrequency, bIsVisible, bCastsShadow, InMaterial, MaterialRelevance);
+			UpdateData->PositionVertexBuffer = PositionVertexBuffer;
+		}
+		else
+		{
+			UpdateData->NewProxy = new FRuntimeMeshSectionProxy<VertexType, false>(InScene, UpdateFrequency, bIsVisible, bCastsShadow, InMaterial, MaterialRelevance);
+		}
+		const_cast<FRuntimeMeshSection*>(this)->bShouldUseAdjacencyIndexBuffer = UpdateData->NewProxy->ShouldUseAdjacencyIndexBuffer();
+
+		UpdateData->VertexBuffer = VertexBuffer;
+
+		// Switch between normal/tessellation indices
+
+		if (bShouldUseAdjacencyIndexBuffer && TessellationIndexBuffer.Num() > 0)
+		{
+			UpdateData->IndexBuffer = TessellationIndexBuffer;
+			UpdateData->bIsAdjacencyIndexBuffer = true;
+		}
+		else
+		{
+			UpdateData->IndexBuffer = IndexBuffer;
+			UpdateData->bIsAdjacencyIndexBuffer = false;
+		}
+
+		return UpdateData;
+	}
+
+	virtual FRuntimeMeshRenderThreadCommandInterface* GetSectionUpdateData(bool bIncludePositionVertices, bool bIncludeVertices, bool bIncludeIndices) const override
+	{
+		auto UpdateData = new FRuntimeMeshSectionUpdateData<VertexType>();
+		UpdateData->bIncludeVertexBuffer = bIncludeVertices;
+		UpdateData->bIncludePositionBuffer = bIncludePositionVertices;
+		UpdateData->bIncludeIndices = bIncludeIndices;
+
+		if (bIncludePositionVertices)
+		{
+			UpdateData->PositionVertexBuffer = PositionVertexBuffer;
+		}
+
+		if (bIncludeVertices)
+		{
+			UpdateData->VertexBuffer = VertexBuffer;
+		}
+
+		if (bIncludeIndices)
+		{
+			if (bShouldUseAdjacencyIndexBuffer && TessellationIndexBuffer.Num() > 0)
+			{
+				UpdateData->IndexBuffer = TessellationIndexBuffer;
+				UpdateData->bIsAdjacencyIndexBuffer = true;
+			}
+			else
+			{
+				UpdateData->IndexBuffer = IndexBuffer;
+				UpdateData->bIsAdjacencyIndexBuffer = false;
+			}
+		}
+
+		return UpdateData;
+	}
+
+	virtual FRuntimeMeshRenderThreadCommandInterface* GetSectionPositionUpdateData() const override
+	{
+		auto UpdateData = new FRuntimeMeshSectionPositionOnlyUpdateData<VertexType>();
+
+		UpdateData->PositionVertexBuffer = PositionVertexBuffer;
+
+		return UpdateData;
+	}
+
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 13
+	virtual int32 GetCollisionInformation(TArray<FVector>& Positions, TArray<TArray<FVector2D>>& UVs, bool bIncludeUVs) override
+#else
+	virtual int32 GetCollisionInformation(TArray<FVector>& Positions) override
+#endif
+	{
+		FRuntimeMeshPackedVerticesBuilder<VertexType> VerticesBuilder(&VertexBuffer, bNeedsPositionOnlyBuffer ? &PositionVertexBuffer : nullptr);
+
+		int32 PositionStart = Positions.Num();
+		Positions.SetNum(PositionStart + VerticesBuilder.Length());
+
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 13
+		if (bIncludeUVs)
+		{
+			UVs[0].SetNumZeroed(PositionStart + VerticesBuilder.Length());
+		}
+#endif
+
+		for (int VertexIdx = 0; VertexIdx < VerticesBuilder.Length(); VertexIdx++)
+		{
+			Positions[PositionStart + VertexIdx] = VerticesBuilder.GetPosition(VertexIdx);
+
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 13
+			if (bIncludeUVs && VerticesBuilder.HasUVComponent(0))
+			{
+				UVs[0][PositionStart + VertexIdx] = VerticesBuilder.GetUV(0);
+			}
+#endif
+		}
+
+		return VerticesBuilder.Length();
+	}
+
+	virtual void GetSectionMesh(IRuntimeMeshVerticesBuilder*& Vertices, FRuntimeMeshIndicesBuilder*& Indices) override
+	{
+		Vertices = new FRuntimeMeshPackedVerticesBuilder<VertexType>(&VertexBuffer);
+		Indices = new FRuntimeMeshIndicesBuilder(&IndexBuffer);
+	}
+
+	virtual const FRuntimeMeshVertexTypeInfo* GetVertexType() const { return &VertexType::TypeInfo; }
+
+	virtual void GenerateNormalTangent()
+	{
+		if (IsDualBufferSection())
+		{
+			URuntimeMeshLibrary::CalculateTangentsForMesh<VertexType>(PositionVertexBuffer, VertexBuffer, IndexBuffer);
+		}
+		else
+		{
+			URuntimeMeshLibrary::CalculateTangentsForMesh<VertexType>(VertexBuffer, IndexBuffer);
+		}
+	}
+
+	virtual void GenerateTessellationIndices()
+	{
+		TArray<int32> TessellationIndices;
+		if (IsDualBufferSection())
+		{
+			URuntimeMeshLibrary::GenerateTessellationIndexBuffer<VertexType>(PositionVertexBuffer, VertexBuffer, IndexBuffer, TessellationIndices);
+		}
+		else
+		{
+			URuntimeMeshLibrary::GenerateTessellationIndexBuffer<VertexType>(VertexBuffer, IndexBuffer, TessellationIndices);
+		}
+		UpdateTessellationIndexBuffer(TessellationIndices, true);
+	}
+
+	virtual void RecalculateBoundingBox() override
+	{
+		LocalBoundingBox.Init();
+
+		if (IsDualBufferSection())
+		{
+			for (int32 Index = 0; Index < PositionVertexBuffer.Num(); Index++)
+			{
+				LocalBoundingBox += PositionVertexBuffer[Index];
+			}
+		}
+		else
+		{
+			RuntimeMeshSectionInternal::RecalculateBoundingBox<VertexType>(VertexBuffer, LocalBoundingBox);
+		}
+	}
+
+	virtual void GetInternalVertexComponents(int32& NumUVChannels, bool& WantsHalfPrecisionUVs) override
+	{
+		NumUVChannels = FRuntimeMeshVertexTraits<VertexType>::NumUVChannels;
+		WantsHalfPrecisionUVs = !FRuntimeMeshVertexTraits<VertexType>::HasHighPrecisionUVs;
+	}
+
+	virtual bool UpdateVertexBufferInternal(const TArray<FVector>& Positions, const TArray<FVector>& Normals, const TArray<FRuntimeMeshTangent>& Tangents, const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, const TArray<FColor>& Colors) override
+	{
+		// Check existence of data components
+		const bool HasPositions = Positions.Num() > 0;
+
+		int32 NewVertexCount = HasPositions ? Positions.Num() : VertexBuffer.Num();
+		int32 OldVertexCount = FMath::Min(VertexBuffer.Num(), NewVertexCount);
+
+		// Size the vertex buffer correctly
+		if (NewVertexCount != VertexBuffer.Num())
+		{
+			VertexBuffer.SetNumZeroed(NewVertexCount);
+		}
+
+		// Clear the bounding box if we have new positions
+		if (HasPositions)
+		{
+			LocalBoundingBox.Init();
+		}
+
+		FRuntimeMeshPackedVerticesBuilder<VertexType> VerticesBuilder(&VertexBuffer);
+				
+		// Loop through existing range to update data
+		for (int32 VertexIdx = 0; VertexIdx < OldVertexCount; VertexIdx++)
+		{
+			VerticesBuilder.Seek(VertexIdx);
+
+			// Update position and bounding box
+			if (HasPositions)
+			{
+				VerticesBuilder.SetPosition(Positions[VertexIdx]);
+				LocalBoundingBox += Positions[VertexIdx];
+			}
+			
+			// see if we have a new normal and/or tangent
+			bool HasNormal = Normals.Num() > VertexIdx;
+			bool HasTangent = Tangents.Num() > VertexIdx;
+
+			// Update normal and tangent together
+			if (HasNormal && HasTangent)
+			{
+				FVector4 NewNormal(Normals[VertexIdx], Tangents[VertexIdx].bFlipTangentY ? -1.0f : 1.0f);
+				VerticesBuilder.SetNormal(NewNormal);
+				VerticesBuilder.SetTangent(Tangents[VertexIdx].TangentX);
+			}
+			// Else update only normal keeping the W component 
+			else if (HasNormal)
+			{
+				float W = VerticesBuilder.GetNormal().W;
+				VerticesBuilder.SetNormal(FVector4(Normals[VertexIdx], W));
+			}
+			// Else update tangent updating the normals W component
+			else if (HasTangent)
+			{
+				FVector4 Normal = VerticesBuilder.GetNormal();
+				Normal.W = Tangents[VertexIdx].bFlipTangentY ? -1.0f : 1.0f;
+				VerticesBuilder.SetNormal(Normal);
+				VerticesBuilder.SetTangent(Tangents[VertexIdx].TangentX);
+			}
+
+			// Update color
+			if (Colors.Num() > VertexIdx)
+			{
+				VerticesBuilder.SetColor(Colors[VertexIdx]);
+			}
+
+			// Update UV0
+			if (UV0.Num() > VertexIdx)
+			{
+				VerticesBuilder.SetUV(0, UV0[VertexIdx]);
+			}
+
+			// Update UV1 if needed
+			if (UV1.Num() > VertexIdx && VerticesBuilder.HasUVComponent(1))
+			{
+				VerticesBuilder.SetUV(1, UV1[VertexIdx]);
+			}
+		}
+
+		// Loop through additional range to add new data
+		for (int32 VertexIdx = OldVertexCount; VertexIdx < NewVertexCount; VertexIdx++)
+		{
+			VerticesBuilder.Seek(VertexIdx);
+
+			// Set position
+			VerticesBuilder.SetPosition(Positions[VertexIdx]);
+
+			// Update bounding box
+			LocalBoundingBox += Positions[VertexIdx];
+
+			// see if we have a new normal and/or tangent
+			bool HasNormal = Normals.Num() > VertexIdx;
+			bool HasTangent = Tangents.Num() > VertexIdx;
+
+			// Set normal and tangent both
+			if (HasNormal && HasTangent)
+			{
+				FVector4 NewNormal(Normals[VertexIdx], Tangents[VertexIdx].bFlipTangentY ? -1.0f : 1.0f);
+				VerticesBuilder.SetNormal(NewNormal);
+				VerticesBuilder.SetTangent(Tangents[VertexIdx].TangentX);
+			}
+			// Set normal and default tangent
+			else if (HasNormal)
+			{
+				VerticesBuilder.SetNormal(FVector4(Normals[VertexIdx], 1.0f));
+				VerticesBuilder.SetTangent(FVector(1.0f, 0.0f, 0.0f));
+			}
+			// Default normal and set tangent
+			else if (HasTangent)
+			{
+				VerticesBuilder.SetNormal(FVector4(0.0f, 0.0f, 1.0f, Tangents[VertexIdx].bFlipTangentY ? -1.0f : 1.0f));
+				VerticesBuilder.SetTangent(Tangents[VertexIdx].TangentX);
+			}
+			// Default normal and tangent
+			else
+			{
+				VerticesBuilder.SetNormal(FVector4(0.0f, 0.0f, 1.0f, 1.0f));
+				VerticesBuilder.SetTangent(FVector(1.0f, 0.0f, 0.0f));
+			}
+
+			// Set color or default 
+			VerticesBuilder.SetColor(Colors.Num() > VertexIdx ? Colors[VertexIdx] : FColor::White);
+
+			// Update UV0
+			VerticesBuilder.SetUV(0, UV0.Num() > VertexIdx ? UV0[VertexIdx] : FVector2D::ZeroVector);
+
+			// Update UV1 if needed
+			if (VerticesBuilder.HasUVComponent(1))
+			{
+				VerticesBuilder.SetUV(1, UV1.Num() > VertexIdx ? UV1[VertexIdx] : FVector2D::ZeroVector);
+			}
+		}
+
+		return true;
+	}
+
+private:
+	void SerializeLegacy(FArchive& Ar)
+	{
+		int32 VertexBufferLength = VertexBuffer.Num();
+		Ar << VertexBufferLength;
+
+		if (Ar.IsLoading())
+		{
+			VertexBuffer.SetNum(VertexBufferLength);
+			FRuntimeMeshPackedVerticesBuilder<VertexType> VerticesBuilder(&VertexBuffer);
+
+			for (int32 Index = 0; Index < VertexBufferLength; Index++)
+			{
+				VerticesBuilder.Seek(Index);
+
+				FVector TempPosition;
+				Ar << TempPosition;
+				VerticesBuilder.SetPosition(TempPosition);
+
+				FPackedNormal TempNormal;
+				Ar << TempNormal;
+				VerticesBuilder.SetNormal(TempNormal);
+
+				Ar << TempNormal;
+				VerticesBuilder.SetTangent(TempNormal);
+
+				FColor TempColor;
+				Ar << TempColor;
+				VerticesBuilder.SetColor(TempColor);
+
+				if (FRuntimeMeshVertexTraits<VertexType>::HasHighPrecisionUVs)
+				{
+					FVector2D TempUV;
+					Ar << TempUV;
+					VerticesBuilder.SetUV(0, TempUV);
+
+					if (FRuntimeMeshVertexTraits<VertexType>::NumUVChannels > 1)
+					{
+						Ar << TempUV;
+						VerticesBuilder.SetUV(1, TempUV);
+					}
+				}
+				else
+				{
+					FVector2DHalf TempUV;
+					Ar << TempUV;
+					VerticesBuilder.SetUV(0, TempUV);
+
+					if (FRuntimeMeshVertexTraits<VertexType>::NumUVChannels > 1)
+					{
+						Ar << TempUV;
+						VerticesBuilder.SetUV(1, TempUV);
+					}
+				}
+			}
+		}
+		else
+		{
+			check(false && "Cannot use legacy save.");
+		}
+	}
+
+public:
+	virtual void Serialize(FArchive& Ar) override
+	{
+
+		if (Ar.CustomVer(FRuntimeMeshVersion::GUID) >= FRuntimeMeshVersion::SerializationV2)
+		{
+			Ar << VertexBuffer;
+			FRuntimeMeshSectionInterface::Serialize(Ar);
+		}
+		else
+		{
+			FRuntimeMeshSectionInterface::Serialize(Ar);
+			SerializeLegacy(Ar);
+		}
+	}
+
+	friend class URuntimeMeshComponent;
+};
+
+
+/** Smart pointer to a Runtime Mesh Section */
+using RuntimeMeshSectionPtr = TSharedPtr<FRuntimeMeshSectionInterface>;

+ 228 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshSectionProxy.h

@@ -0,0 +1,228 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#pragma once
+
+#include "Engine.h"
+#include "Components/MeshComponent.h"
+#include "RuntimeMeshProfiling.h"
+#include "RuntimeMeshVersion.h"
+#include "RuntimeMeshCore.h"
+#include "RuntimeMeshRendering.h"
+#include "RuntimeMeshUpdateCommands.h"
+
+
+/** Interface class for the RT proxy of a single mesh section */
+class FRuntimeMeshSectionProxyInterface : public FRuntimeMeshVisibilityInterface
+{
+public:
+
+	FRuntimeMeshSectionProxyInterface() {}
+	virtual ~FRuntimeMeshSectionProxyInterface() {}
+
+	virtual bool ShouldRender() = 0;
+	virtual bool WantsToRenderInStaticPath() const = 0;
+
+	virtual bool ShouldUseAdjacencyIndexBuffer() const = 0;
+
+	virtual FMaterialRelevance GetMaterialRelevance() const = 0;
+
+	virtual void CreateMeshBatch(FMeshBatch& MeshBatch, FMaterialRenderProxy* WireframeMaterial, bool bIsSelected) = 0;
+
+
+	virtual void FinishCreate_RenderThread(FRuntimeMeshSectionCreateDataInterface* UpdateData) = 0;
+	virtual void FinishUpdate_RenderThread(FRuntimeMeshRenderThreadCommandInterface* UpdateData) = 0;
+	virtual void FinishPositionUpdate_RenderThread(FRuntimeMeshRenderThreadCommandInterface* UpdateData) = 0;
+	virtual void FinishPropertyUpdate_RenderThread(FRuntimeMeshRenderThreadCommandInterface* UpdateData) = 0;
+
+};
+
+/** Templated class for the RT proxy of a single mesh section */
+template <typename VertexType, bool NeedsPositionOnlyBuffer>
+class FRuntimeMeshSectionProxy : public FRuntimeMeshSectionProxyInterface
+{
+protected:
+	/** Whether this section is currently visible */
+	bool bIsVisible;
+
+	/** Should this section cast a shadow */
+	bool bCastsShadow;
+
+	/** Whether this section should be using an adjacency index buffer */
+	bool bShouldUseAdjacency;
+
+	/** Whether this section is using a tessellation adjacency index buffer */
+	bool bIsUsingAdjacency;
+
+	/** Update frequency of this section */
+	const EUpdateFrequency UpdateFrequency;
+
+	/** Material applied to this section */
+	UMaterialInterface* Material;
+
+	FMaterialRelevance MaterialRelevance;
+
+	FRuntimeMeshVertexBuffer<FVector>* PositionVertexBuffer;
+
+	/** Vertex buffer for this section */
+	FRuntimeMeshVertexBuffer<VertexType> VertexBuffer;
+
+	/** Index buffer for this section */
+	FRuntimeMeshIndexBuffer IndexBuffer;
+
+	/** Vertex factory for this section */
+	FRuntimeMeshVertexFactory VertexFactory;
+
+public:
+	FRuntimeMeshSectionProxy(FSceneInterface* InScene, EUpdateFrequency InUpdateFrequency, bool bInIsVisible, bool bInCastsShadow, UMaterialInterface* InMaterial, FMaterialRelevance InMaterialRelevance) :
+		bIsVisible(bInIsVisible), bCastsShadow(bInCastsShadow), UpdateFrequency(InUpdateFrequency), Material(InMaterial), MaterialRelevance(InMaterialRelevance),
+		PositionVertexBuffer(nullptr), VertexBuffer(InUpdateFrequency), IndexBuffer(InUpdateFrequency), VertexFactory(this) 
+	{ 
+		bShouldUseAdjacency = RequiresAdjacencyInformation(InMaterial, VertexFactory.GetType(), InScene->GetFeatureLevel());
+	}
+
+	virtual ~FRuntimeMeshSectionProxy() override
+	{
+		VertexBuffer.ReleaseResource();
+		IndexBuffer.ReleaseResource();
+		VertexFactory.ReleaseResource();
+
+		if (PositionVertexBuffer)
+		{
+			PositionVertexBuffer->ReleaseResource();
+			delete PositionVertexBuffer;
+		}
+	}
+
+
+	virtual bool ShouldRender() override { return bIsVisible && VertexBuffer.Num() > 0 && IndexBuffer.Num() > 0; }
+
+	virtual bool WantsToRenderInStaticPath() const override { return UpdateFrequency == EUpdateFrequency::Infrequent; }
+	
+	virtual bool ShouldUseAdjacencyIndexBuffer() const override { return bShouldUseAdjacency; }
+
+	virtual FMaterialRelevance GetMaterialRelevance() const { return MaterialRelevance; }
+	
+	virtual void CreateMeshBatch(FMeshBatch& MeshBatch, FMaterialRenderProxy* WireframeMaterial, bool bIsSelected) override
+	{
+		MeshBatch.VertexFactory = &VertexFactory;
+		MeshBatch.bWireframe = WireframeMaterial != nullptr;
+		MeshBatch.MaterialRenderProxy = MeshBatch.bWireframe ? WireframeMaterial : Material->GetRenderProxy(bIsSelected);
+		
+		if (bIsUsingAdjacency && WireframeMaterial == nullptr)
+		{
+			MeshBatch.Type = PT_12_ControlPointPatchList;
+		}
+		else
+		{
+			MeshBatch.Type = PT_TriangleList;
+		}
+
+		MeshBatch.DepthPriorityGroup = SDPG_World;
+		MeshBatch.CastShadow = bCastsShadow;
+
+		FMeshBatchElement& BatchElement = MeshBatch.Elements[0];
+		BatchElement.IndexBuffer = &IndexBuffer;
+		BatchElement.FirstIndex = 0;
+		BatchElement.NumPrimitives = bIsUsingAdjacency? IndexBuffer.Num() / 12 : IndexBuffer.Num() / 3;
+		BatchElement.MinVertexIndex = 0;
+		BatchElement.MaxVertexIndex = VertexBuffer.Num() - 1;
+	}
+
+
+	virtual void FinishCreate_RenderThread(FRuntimeMeshSectionCreateDataInterface* UpdateData) override
+	{
+ 		check(IsInRenderingThread());
+
+		auto* SectionUpdateData = UpdateData->As<FRuntimeMeshSectionCreateData<VertexType>>();
+		check(SectionUpdateData);
+		
+		if (NeedsPositionOnlyBuffer)
+		{
+			// Initialize the position buffer
+			PositionVertexBuffer = new FRuntimeMeshVertexBuffer<FVector>(UpdateFrequency);
+
+			// Get and adjust the vertex structure
+			auto VertexStructure = VertexType::GetVertexStructure(VertexBuffer);
+			VertexStructure.PositionComponent = FVertexStreamComponent(PositionVertexBuffer, 0, sizeof(FVector), VET_Float3);
+			VertexFactory.Init(VertexStructure);
+		}
+		else
+		{
+			// Get and submit the vertex structure
+			auto VertexStructure = VertexType::GetVertexStructure(VertexBuffer);
+			VertexFactory.Init(VertexStructure);
+		}
+		
+		// Initialize the vertex factory
+		VertexFactory.InitResource();
+
+		auto& Vertices = SectionUpdateData->VertexBuffer;
+		VertexBuffer.SetNum(Vertices.Num());
+		VertexBuffer.SetData(Vertices);
+
+		if (NeedsPositionOnlyBuffer)
+		{
+			auto& PositionVertices = SectionUpdateData->PositionVertexBuffer;
+			PositionVertexBuffer->SetNum(PositionVertices.Num());
+			PositionVertexBuffer->SetData(PositionVertices);
+		}
+		
+		auto& Indices = SectionUpdateData->IndexBuffer;
+		IndexBuffer.SetNum(Indices.Num());
+		IndexBuffer.SetData(Indices);
+		bIsUsingAdjacency = SectionUpdateData->bIsAdjacencyIndexBuffer;
+	}
+	
+	virtual void FinishUpdate_RenderThread(FRuntimeMeshRenderThreadCommandInterface* UpdateData) override
+	{
+		check(IsInRenderingThread());
+
+		auto* SectionUpdateData = UpdateData->As<FRuntimeMeshSectionUpdateData<VertexType>>();
+		check(SectionUpdateData);
+
+		if (SectionUpdateData->bIncludeVertexBuffer)
+		{
+			auto& VertexBufferData = SectionUpdateData->VertexBuffer;
+			VertexBuffer.SetNum(VertexBufferData.Num());
+			VertexBuffer.SetData(VertexBufferData);
+		}
+
+		if (NeedsPositionOnlyBuffer && SectionUpdateData->bIncludePositionBuffer)
+		{
+			auto& PositionVertices = SectionUpdateData->PositionVertexBuffer;
+			PositionVertexBuffer->SetNum(PositionVertices.Num());
+			PositionVertexBuffer->SetData(PositionVertices);
+		}
+
+		if (SectionUpdateData->bIncludeIndices)
+		{
+			auto& IndexBufferData = SectionUpdateData->IndexBuffer;
+			IndexBuffer.SetNum(IndexBufferData.Num());
+			IndexBuffer.SetData(IndexBufferData);
+			bIsUsingAdjacency = SectionUpdateData->bIsAdjacencyIndexBuffer;
+		}
+	}
+
+	virtual void FinishPositionUpdate_RenderThread(FRuntimeMeshRenderThreadCommandInterface* UpdateData) override 
+	{
+		check(IsInRenderingThread());
+
+		// Get the Position Only update data
+		auto* SectionUpdateData = UpdateData->As<FRuntimeMeshSectionPositionOnlyUpdateData<VertexType>>();
+		check(SectionUpdateData);
+		
+		// Copy the new data to the gpu
+		PositionVertexBuffer->SetData(SectionUpdateData->PositionVertexBuffer);
+	}
+
+	virtual void FinishPropertyUpdate_RenderThread(FRuntimeMeshRenderThreadCommandInterface* UpdateData) override
+	{
+		auto* SectionUpdateData = UpdateData->As<FRuntimeMeshSectionPropertyUpdateData>();
+		check(SectionUpdateData);
+
+		// Copy visibility/shadow
+		bIsVisible = SectionUpdateData->bIsVisible;
+		bCastsShadow = SectionUpdateData->bCastsShadow;
+	}
+
+};

+ 257 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshUpdateCommands.h

@@ -0,0 +1,257 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#pragma once
+
+#include "Engine.h"
+#include "Components/MeshComponent.h"
+#include "RuntimeMeshProfiling.h"
+#include "RuntimeMeshVersion.h"
+
+
+
+
+/* Base class for all render thread command information */
+class FRuntimeMeshRenderThreadCommandInterface
+{
+public:
+
+	FRuntimeMeshRenderThreadCommandInterface() { }
+	virtual ~FRuntimeMeshRenderThreadCommandInterface() { }
+	
+	virtual void SetTargetSection(int32 InTargetSection) { TargetSection = InTargetSection; }
+	virtual int32 GetTargetSection() { return TargetSection; }
+	
+	/* Cast the update data to the specific type of update data */
+	template <typename Type>
+	Type* As()
+	{
+		return static_cast<Type*>(this);
+	}
+
+private:
+	/* Section index that this creation data applies to */
+	int32 TargetSection;
+};
+
+/* Base class for section creation data. Allows the non templated SceneProxy to get the section proxy;*/
+class FRuntimeMeshSectionCreateDataInterface : public FRuntimeMeshRenderThreadCommandInterface
+{
+public:
+	/* The new proxy to be used for section creation */
+	class FRuntimeMeshSectionProxyInterface* NewProxy;
+
+
+	FRuntimeMeshSectionCreateDataInterface() { }
+	virtual ~FRuntimeMeshSectionCreateDataInterface() override { }
+
+};
+
+/** Templated class for update data sent to the RT for updating a single mesh section */
+template<typename VertexType>
+class FRuntimeMeshSectionCreateData : public FRuntimeMeshSectionCreateDataInterface
+{
+public:
+	/* Updated position vertex buffer for the section */
+	TArray<FVector> PositionVertexBuffer;
+
+	/* Updated vertex buffer for the section */
+	TArray<VertexType> VertexBuffer;
+
+	/* Whether the supplied index buffer contains adjacency info */
+	bool bIsAdjacencyIndexBuffer;
+
+	/* Updated index buffer for the section */
+	TArray<int32> IndexBuffer;
+
+
+	FRuntimeMeshSectionCreateData() {}
+	virtual ~FRuntimeMeshSectionCreateData() override { }
+
+};
+
+/** Templated class for update data sent to the RT for updating a single mesh section */
+template<typename VertexType>
+class FRuntimeMeshSectionUpdateData : public FRuntimeMeshRenderThreadCommandInterface
+{
+public:
+	/* Updated position vertex buffer for the section */
+	TArray<FVector> PositionVertexBuffer;
+
+	/* Updated vertex buffer for the section */
+	TArray<VertexType> VertexBuffer;
+
+	/* Updated index buffer for the section */
+	TArray<int32> IndexBuffer;
+
+	/* Should we apply the position buffer */
+	bool bIncludePositionBuffer;
+
+	/* Should we apply the vertex buffer */
+	bool bIncludeVertexBuffer;
+
+	/* Should we apply the indices as an update */
+	bool bIncludeIndices;
+
+	/* Whether the supplied index buffer contains adjacency info */
+	bool bIsAdjacencyIndexBuffer;
+
+	FRuntimeMeshSectionUpdateData() {}
+	virtual ~FRuntimeMeshSectionUpdateData() override { }
+};
+
+/** Templated class for update data sent to the RT for updating a single mesh section */
+template<typename VertexType>
+class FRuntimeMeshSectionPositionOnlyUpdateData : public FRuntimeMeshRenderThreadCommandInterface
+{
+public:
+	/* Updated position vertex buffer for the section */
+	TArray<FVector> PositionVertexBuffer;
+
+	FRuntimeMeshSectionPositionOnlyUpdateData() {}
+	virtual ~FRuntimeMeshSectionPositionOnlyUpdateData() override { }
+};
+
+/** Property update for a single section */
+class FRuntimeMeshSectionPropertyUpdateData : public FRuntimeMeshRenderThreadCommandInterface
+{
+public:
+	/* Is this section visible */
+	bool bIsVisible;
+
+	/* Is this section casting shadows */
+	bool bCastsShadow;
+
+	FRuntimeMeshSectionPropertyUpdateData() {}
+	virtual ~FRuntimeMeshSectionPropertyUpdateData() override { }
+};
+
+enum class ERuntimeMeshSectionBatchUpdateType
+{
+	None = 0x0,
+	Create = 0x1,
+	Destroy = 0x2,
+	PositionsUpdate = 0x4,
+	VerticesUpdate = 0x8,
+	IndicesUpdate = 0x10,
+	PropertyUpdate = 0x20,
+};
+
+ENUM_CLASS_FLAGS(ERuntimeMeshSectionBatchUpdateType)
+
+
+/* Struct carrying all update data for a batch update sent to the render thread */
+struct FRuntimeMeshBatchUpdateData
+{
+	TArray<FRuntimeMeshSectionCreateDataInterface*> CreateSections;
+	TArray<int32> DestroySections;
+	TArray<FRuntimeMeshRenderThreadCommandInterface*> UpdateSections;
+	TArray<FRuntimeMeshSectionPropertyUpdateData*> PropertyUpdateSections;
+};
+
+
+
+struct FRuntimeMeshBatchUpdateState
+{
+	void StartBatch() { bIsPending = true; }
+
+	void ResetBatch() 
+	{
+		bIsPending = false;
+		bRequiresSceneProxyReCreate = false;
+		bRequiresBoundsUpdate = false;
+		bRequiresCollisionUpdate = false;
+
+		SectionUpdates.Empty();
+	}
+
+	
+
+
+	bool IsBatchPending() { return bIsPending; }
+
+	void MarkSectionCreated(int32 SectionIndex, bool bPromoteToProxyRecreate)
+	{
+		// Flag recreate instead of individual section
+		if (bPromoteToProxyRecreate)
+		{
+			bRequiresSceneProxyReCreate = true;
+			return;
+		}
+
+		EnsureUpdateLength(SectionIndex);
+
+		// Clear destroyed flag and set created
+		SectionUpdates[SectionIndex] &= ~ERuntimeMeshSectionBatchUpdateType::Destroy;
+		SectionUpdates[SectionIndex] |= ERuntimeMeshSectionBatchUpdateType::Create;
+	}
+
+	void MarkUpdateForSection(int32 SectionIndex, ERuntimeMeshSectionBatchUpdateType UpdateType)
+	{
+		EnsureUpdateLength(SectionIndex);
+		
+		// Add update type
+		SectionUpdates[SectionIndex] |= UpdateType;
+	}
+
+	void MarkSectionDestroyed(int32 SectionIndex, bool bPromoteToProxyRecreate)
+	{
+		// Flag recreate instead of individual section
+		if (bPromoteToProxyRecreate)
+		{
+			bRequiresSceneProxyReCreate = true;
+			return;
+		}
+
+		EnsureUpdateLength(SectionIndex);
+
+		// Clear destroyed flag and set created
+		SectionUpdates[SectionIndex] &= ~ERuntimeMeshSectionBatchUpdateType::Create;
+		SectionUpdates[SectionIndex] |= ERuntimeMeshSectionBatchUpdateType::Destroy;
+	}
+
+	void MarkRenderStateDirty() { bRequiresSceneProxyReCreate = true; }
+
+	void MarkCollisionDirty() { bRequiresCollisionUpdate = true; }
+
+	void MarkBoundsDirty() { bRequiresBoundsUpdate = true; }
+
+
+
+	bool HasAnyFlagSet(int32 SectionIndex) { return SectionUpdates[SectionIndex] != ERuntimeMeshSectionBatchUpdateType::None; }
+
+	bool HasFlagSet(int32 SectionIndex, ERuntimeMeshSectionBatchUpdateType UpdateType)
+	{
+		return (SectionUpdates[SectionIndex] & UpdateType) == UpdateType;
+	}
+
+	bool RequiresSceneProxyRecreate() { return bRequiresSceneProxyReCreate; }
+
+	bool RequiresBoundsUpdate() { return bRequiresBoundsUpdate; }
+
+	bool RequiresCollisionUpdate() { return bRequiresCollisionUpdate; }
+
+	int32 GetMaxSection() { return SectionUpdates.Num() - 1; }
+
+private:
+
+	void EnsureUpdateLength(int32 SectionIndex)
+	{
+		if (SectionIndex < SectionUpdates.Num())
+		{
+			return;
+		}
+
+		SectionUpdates.AddZeroed((SectionIndex + 1) - SectionUpdates.Num());
+	}
+
+
+	bool bIsPending;
+	bool bRequiresSceneProxyReCreate;
+	bool bRequiresBoundsUpdate;
+	bool bRequiresCollisionUpdate;
+	TArray<ERuntimeMeshSectionBatchUpdateType> SectionUpdates;
+	
+
+
+
+};

+ 27 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/Public/RuntimeMeshVersion.h

@@ -0,0 +1,27 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#pragma once
+
+#include "RuntimeMeshComponentPlugin.h"
+
+
+// Custom version for runtime mesh serialization
+namespace FRuntimeMeshVersion
+{
+	enum Type
+	{
+		Initial = 0,
+		TemplatedVertexFix = 1,
+		SerializationOptional = 2,
+		DualVertexBuffer = 3,
+
+		SerializationV2 = 4,
+
+		// -----<new versions can be added above this line>-------------------------------------------------
+		VersionPlusOne,
+		LatestVersion = VersionPlusOne - 1
+	};
+
+	// The GUID for this custom version
+	const static FGuid GUID = FGuid(0xEE48714B, 0x8A2C4652, 0x98BE40E6, 0xCB7EF0E6);
+};

+ 24 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponent/RuntimeMeshComponent.Build.cs

@@ -0,0 +1,24 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+using UnrealBuildTool;
+
+public class RuntimeMeshComponent : ModuleRules
+{
+	public RuntimeMeshComponent(TargetInfo Target)
+	{
+        PrivateIncludePaths.Add("RuntimeMeshComponent/Private");
+        PublicIncludePaths.Add("RuntimeMeshComponent/Public");
+
+        PublicDependencyModuleNames.AddRange(
+                new string[]
+                {
+                        "Core",
+                        "CoreUObject",
+                        "Engine",
+                        "RenderCore",
+                        "ShaderCore",
+                        "RHI"
+                }
+            );
+    }
+}

+ 265 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponentEditor/Private/RuntimeMeshComponentDetails.cpp

@@ -0,0 +1,265 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#include "RuntimeMeshComponentEditorPrivatePCH.h"
+#include "RuntimeMeshComponentDetails.h"
+#include "RuntimeMeshComponent.h"
+
+#include "DlgPickAssetPath.h"
+#include "IAssetTools.h"
+#include "AssetToolsModule.h"
+#include "AssetRegistryModule.h"
+#include "PhysicsEngine/PhysicsSettings.h"
+#include "PhysicsEngine/BodySetup.h"
+
+#define LOCTEXT_NAMESPACE "RuntimeMeshComponentDetails"
+
+TSharedRef<IDetailCustomization> FRuntimeMeshComponentDetails::MakeInstance()
+{
+	return MakeShareable(new FRuntimeMeshComponentDetails);
+}
+
+void FRuntimeMeshComponentDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder )
+{
+	IDetailCategoryBuilder& RuntimeMeshCategory = DetailBuilder.EditCategory("RuntimeMesh");
+
+	const FText ConvertToStaticMeshText = LOCTEXT("ConvertToStaticMesh", "Create StaticMesh");
+
+	// Cache set of selected things
+	SelectedObjectsList = DetailBuilder.GetDetailsView().GetSelectedObjects();
+
+	RuntimeMeshCategory.AddCustomRow(ConvertToStaticMeshText, false)
+	.NameContent()
+	[
+		SNullWidget::NullWidget
+	]
+	.ValueContent()
+	.VAlign(VAlign_Center)
+	.MaxDesiredWidth(250)
+	[
+		SNew(SButton)
+		.VAlign(VAlign_Center)
+		.ToolTipText(LOCTEXT("ConvertToStaticMeshTooltip", "Create a new StaticMesh asset using current geometry from this RuntimeMeshComponent. Does not modify instance."))
+		.OnClicked(this, &FRuntimeMeshComponentDetails::ClickedOnConvertToStaticMesh)
+		.IsEnabled(this, &FRuntimeMeshComponentDetails::ConvertToStaticMeshEnabled)
+		.Content()
+		[
+			SNew(STextBlock)
+			.Text(ConvertToStaticMeshText)
+		]
+	];
+}
+
+URuntimeMeshComponent* FRuntimeMeshComponentDetails::GetFirstSelectedRuntimeMeshComp() const
+{
+	// Find first selected valid RuntimeMeshComp
+	URuntimeMeshComponent* RuntimeMeshComp = nullptr;
+	for (const TWeakObjectPtr<UObject>& Object : SelectedObjectsList)
+	{
+		URuntimeMeshComponent* TestRuntimeComp = Cast<URuntimeMeshComponent>(Object.Get());
+		// See if this one is good
+		if (TestRuntimeComp != nullptr && !TestRuntimeComp->IsTemplate())
+		{
+			RuntimeMeshComp = TestRuntimeComp;
+			break;
+		}
+	}
+
+	return RuntimeMeshComp;
+}
+
+
+bool FRuntimeMeshComponentDetails::ConvertToStaticMeshEnabled() const
+{
+	return GetFirstSelectedRuntimeMeshComp() != nullptr;
+}
+
+
+FReply FRuntimeMeshComponentDetails::ClickedOnConvertToStaticMesh()
+{
+ 	// Find first selected RuntimeMeshComp
+ 	URuntimeMeshComponent* RuntimeMeshComp = GetFirstSelectedRuntimeMeshComp();
+ 	if (RuntimeMeshComp != nullptr)
+ 	{
+ 		FString NewNameSuggestion = FString(TEXT("RuntimeMeshComp"));
+ 		FString PackageName = FString(TEXT("/Game/Meshes/")) + NewNameSuggestion;
+ 		FString Name;
+ 		FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
+ 		AssetToolsModule.Get().CreateUniqueAssetName(PackageName, TEXT(""), PackageName, Name);
+ 
+ 		TSharedPtr<SDlgPickAssetPath> PickAssetPathWidget =
+ 			SNew(SDlgPickAssetPath)
+ 			.Title(LOCTEXT("ConvertToStaticMeshPickName", "Choose New StaticMesh Location"))
+ 			.DefaultAssetPath(FText::FromString(PackageName));
+ 
+ 		if (PickAssetPathWidget->ShowModal() == EAppReturnType::Ok)
+ 		{
+ 			// Get the full name of where we want to create the physics asset.
+ 			FString UserPackageName = PickAssetPathWidget->GetFullAssetPath().ToString();
+ 			FName MeshName(*FPackageName::GetLongPackageAssetName(UserPackageName));
+ 
+ 			// Check if the user inputed a valid asset name, if they did not, give it the generated default name
+ 			if (MeshName == NAME_None)
+ 			{
+ 				// Use the defaults that were already generated.
+ 				UserPackageName = PackageName;
+ 				MeshName = *Name;
+ 			}
+ 
+ 			// Raw mesh data we are filling in
+ 			FRawMesh RawMesh;
+ 					
+			// Unique Materials to apply to new mesh
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 14
+			TArray<FStaticMaterial> Materials;
+#else
+			TArray<UMaterialInterface*> Materials;
+#endif
+ 
+			bool bUseHighPrecisionTangents = false;
+			bool bUseFullPrecisionUVs = false;
+
+ 			const int32 NumSections = RuntimeMeshComp->GetNumSections();
+ 			int32 VertexBase = 0;
+ 			for (int32 SectionIdx = 0; SectionIdx < NumSections; SectionIdx++)
+ 			{
+				const IRuntimeMeshVerticesBuilder* Vertices;
+				const FRuntimeMeshIndicesBuilder* Indices;
+				RuntimeMeshComp->GetSectionMesh(SectionIdx, Vertices, Indices);
+
+				if (Vertices->HasHighPrecisionNormals())
+				{
+					bUseHighPrecisionTangents = true;
+				}
+				if (Vertices->HasHighPrecisionUVs())
+				{
+					bUseFullPrecisionUVs = true;
+				}
+ 
+ 				// Copy verts
+				Vertices->Seek(-1);
+				while (Vertices->MoveNext() < Vertices->Length())
+				{
+					RawMesh.VertexPositions.Add(Vertices->GetPosition());
+				}
+ 
+ 				// Copy 'wedge' info
+				Indices->Seek(0);
+				while (Indices->HasRemaining())
+ 				{
+					int32 Index = Indices->ReadOne();
+ 
+ 					RawMesh.WedgeIndices.Add(Index + VertexBase);
+ 
+
+					Vertices->Seek(Index);
+ 
+					FVector TangentX = Vertices->GetTangent();
+					FVector TangentZ = Vertices->GetNormal();
+ 					FVector TangentY = (TangentX ^ TangentZ).GetSafeNormal() * Vertices->GetNormal().W;
+ 
+ 					RawMesh.WedgeTangentX.Add(TangentX);
+ 					RawMesh.WedgeTangentY.Add(TangentY);
+ 					RawMesh.WedgeTangentZ.Add(TangentZ);
+ 
+					for (int UVIndex = 0; UVIndex < 8; UVIndex++)
+					{
+						if (!Vertices->HasUVComponent(UVIndex))
+						{
+							break;
+						}
+						RawMesh.WedgeTexCoords[UVIndex].Add(Vertices->GetUV(UVIndex));
+					}
+
+ 					RawMesh.WedgeColors.Add(Vertices->GetColor());
+ 				}
+ 
+				// Find a material index for this section.
+				UMaterialInterface* Material = RuntimeMeshComp->GetMaterial(SectionIdx);
+
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 14
+				int32 MaterialIndex = Materials.AddUnique(FStaticMaterial(Material));
+#else
+				int32 MaterialIndex = Materials.AddUnique(Material);
+#endif
+				
+
+ 				// copy face info
+ 				int32 NumTris = Indices->Length() / 3;
+ 				for (int32 TriIdx=0; TriIdx < NumTris; TriIdx++)
+ 				{
+					// Set the face material
+					RawMesh.FaceMaterialIndices.Add(MaterialIndex);
+
+ 					RawMesh.FaceSmoothingMasks.Add(0); // Assume this is ignored as bRecomputeNormals is false
+ 				}
+ 
+ 				// Update offset for creating one big index/vertex buffer
+				VertexBase += Vertices->Length();
+ 			}
+ 
+ 			// If we got some valid data.
+ 			if (RawMesh.VertexPositions.Num() >= 3 && RawMesh.WedgeIndices.Num() >= 3)
+ 			{
+ 				// Then find/create it.
+ 				UPackage* Package = CreatePackage(NULL, *UserPackageName);
+ 				check(Package);
+ 
+ 				// Create StaticMesh object
+ 				UStaticMesh* StaticMesh = NewObject<UStaticMesh>(Package, MeshName, RF_Public | RF_Standalone);
+ 				StaticMesh->InitResources();
+ 
+ 				StaticMesh->LightingGuid = FGuid::NewGuid();
+ 
+ 				// Add source to new StaticMesh
+ 				FStaticMeshSourceModel* SrcModel = new (StaticMesh->SourceModels) FStaticMeshSourceModel();
+ 				SrcModel->BuildSettings.bRecomputeNormals = false;
+ 				SrcModel->BuildSettings.bRecomputeTangents = false;
+ 				SrcModel->BuildSettings.bRemoveDegenerates = false;
+ 				SrcModel->BuildSettings.bUseHighPrecisionTangentBasis = bUseHighPrecisionTangents;
+				SrcModel->BuildSettings.bUseFullPrecisionUVs = bUseFullPrecisionUVs;
+ 				SrcModel->BuildSettings.bGenerateLightmapUVs = true;
+ 				SrcModel->BuildSettings.SrcLightmapIndex = 0;
+ 				SrcModel->BuildSettings.DstLightmapIndex = 1;
+ 				SrcModel->RawMeshBulkData->SaveRawMesh(RawMesh);
+ 
+				// Set the materials used for this static mesh
+#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 14
+				StaticMesh->StaticMaterials = Materials;
+				int32 NumMaterials = StaticMesh->StaticMaterials.Num();
+#else
+				StaticMesh->Materials = Materials;
+				int32 NumMaterials = StaticMesh->Materials.Num();
+#endif
+
+				// Set up the SectionInfoMap to enable collision
+				for (int32 SectionIdx = 0; SectionIdx < NumMaterials; SectionIdx++)
+				{
+					FMeshSectionInfo Info = StaticMesh->SectionInfoMap.Get(0, SectionIdx);
+					Info.MaterialIndex = SectionIdx;
+					Info.bEnableCollision = true;
+					StaticMesh->SectionInfoMap.Set(0, SectionIdx, Info);
+				}
+
+				// Configure body setup for working collision.
+				StaticMesh->CreateBodySetup();
+				StaticMesh->BodySetup->CollisionTraceFlag = CTF_UseComplexAsSimple;
+
+ 				// Build mesh from source
+ 				StaticMesh->Build(false);
+
+				// Make package dirty.
+				StaticMesh->MarkPackageDirty();
+
+ 				StaticMesh->PostEditChange();
+ 
+ 				// Notify asset registry of new asset
+ 				FAssetRegistryModule::AssetCreated(StaticMesh);
+ 			}
+ 		}
+ 	}
+
+	return FReply::Handled();
+}
+
+
+#undef LOCTEXT_NAMESPACE

+ 27 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponentEditor/Private/RuntimeMeshComponentDetails.h

@@ -0,0 +1,27 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#pragma once
+#include "IDetailCustomization.h"
+#include "DetailLayoutBuilder.h"
+
+class FRuntimeMeshComponentDetails : public IDetailCustomization
+{
+public:
+	/** Makes a new instance of this detail layout class for a specific detail view requesting it */
+	static TSharedRef<IDetailCustomization> MakeInstance();
+
+	/** IDetailCustomization interface */
+	virtual void CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) override;
+
+	/** Handle clicking the convert button */
+	FReply ClickedOnConvertToStaticMesh();
+
+	/** Is the convert button enabled */
+	bool ConvertToStaticMeshEnabled() const;
+
+	/** Util to get the RuntimeMeshComponent we want to convert */
+	class URuntimeMeshComponent* GetFirstSelectedRuntimeMeshComp() const;
+
+	/** Cached array of selected objects */
+	TArray< TWeakObjectPtr<UObject> > SelectedObjectsList;
+};

+ 31 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponentEditor/Private/RuntimeMeshComponentEditorPlugin.cpp

@@ -0,0 +1,31 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#include "RuntimeMeshComponentEditorPrivatePCH.h"
+
+
+
+
+class FRuntimeMeshComponentEditorPlugin : public IRuntimeMeshComponentEditorPlugin
+{
+	/** IModuleInterface implementation */
+	virtual void StartupModule() override;
+	virtual void ShutdownModule() override;
+};
+
+IMPLEMENT_MODULE( FRuntimeMeshComponentEditorPlugin, RuntimeMeshComponentEditor )
+
+
+
+void FRuntimeMeshComponentEditorPlugin::StartupModule()
+{
+	{
+		FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
+		PropertyModule.RegisterCustomClassLayout(URuntimeMeshComponent::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FRuntimeMeshComponentDetails::MakeInstance));
+	}
+}
+
+
+void FRuntimeMeshComponentEditorPlugin::ShutdownModule()
+{
+	
+}

+ 15 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponentEditor/Private/RuntimeMeshComponentEditorPrivatePCH.h

@@ -0,0 +1,15 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#include "UnrealEd.h"
+#include "PropertyEditorModule.h"
+#include "DetailLayoutBuilder.h"
+#include "DetailCategoryBuilder.h"
+#include "DetailWidgetRow.h"
+#include "IDetailsView.h"
+#include "PhysicsEngine/ConvexElem.h"
+#include "RuntimeMeshComponent.h"
+#include "Engine/StaticMesh.h"
+#include "RawMesh.h"
+
+#include "IRuntimeMeshComponentEditorPlugin.h"
+#include "RuntimeMeshComponentDetails.h"

+ 36 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponentEditor/Public/IRuntimeMeshComponentEditorPlugin.h

@@ -0,0 +1,36 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+#pragma once
+
+#include "ModuleManager.h"
+
+
+/**
+* The public interface to this module
+*/
+class IRuntimeMeshComponentEditorPlugin : public IModuleInterface
+{
+
+public:
+
+	/**
+	* Singleton-like access to this module's interface.  This is just for convenience!
+	* Beware of calling this during the shutdown phase, though.  Your module might have been unloaded already.
+	*
+	* @return Returns singleton instance, loading the module on demand if needed
+	*/
+	static inline IRuntimeMeshComponentEditorPlugin& Get()
+	{
+		return FModuleManager::LoadModuleChecked< IRuntimeMeshComponentEditorPlugin >("RuntimeMeshComponentEditorPlugin");
+	}
+
+	/**
+	* Checks to see if this module is loaded and ready.  It is only valid to call Get() if IsAvailable() returns true.
+	*
+	* @return True if the module is loaded and ready to use
+	*/
+	static inline bool IsAvailable()
+	{
+		return FModuleManager::Get().IsModuleLoaded("RuntimeMeshComponentEditorPlugin");
+	}
+};

+ 33 - 0
spine-ue4/Plugins/RuntimeMeshComponentPlugin/Source/RuntimeMeshComponentEditor/RuntimeMeshComponentEditor.Build.cs

@@ -0,0 +1,33 @@
+// Copyright 2016 Chris Conway (Koderz). All Rights Reserved.
+
+namespace UnrealBuildTool.Rules
+{
+	public class RuntimeMeshComponentEditor : ModuleRules
+	{
+        public RuntimeMeshComponentEditor(TargetInfo Target)
+		{
+			PrivateIncludePaths.Add("RuntimeMeshComponentEditor/Private");
+            PublicIncludePaths.Add("RuntimeMeshComponentEditor/Public");
+
+			PublicDependencyModuleNames.AddRange(
+				new string[]
+				{
+					"Core",
+					"CoreUObject",
+                    "Slate",
+                    "SlateCore",
+                    "Engine",
+                    "UnrealEd",
+                    "PropertyEditor",
+                    "RenderCore",
+                    "ShaderCore",
+                    "RHI",
+                    "RuntimeMeshComponent",
+                    "RawMesh",
+                    "AssetTools",
+                    "AssetRegistry"
+                }
+				);
+		}
+	}
+}

BIN
spine-ue4/Plugins/SpinePlugin/Content/TestMaterial.uasset


+ 16 - 7
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonRendererComponent.cpp

@@ -36,7 +36,7 @@
 #define LOCTEXT_NAMESPACE "Spine"
 
 USpineSkeletonRendererComponent::USpineSkeletonRendererComponent (const FObjectInitializer& ObjectInitializer) 
-: UProceduralMeshComponent(ObjectInitializer) {
+: URuntimeMeshComponent(ObjectInitializer) {
 	bWantsBeginPlay = true;
 	PrimaryComponentTick.bCanEverTick = true;
 	bTickInEditor = true;
@@ -169,10 +169,19 @@ void USpineSkeletonRendererComponent::TickComponent (float DeltaTime, ELevelTick
 	}
 }
 
-void USpineSkeletonRendererComponent::Flush (int &Idx, TArray<FVector> &Vertices, TArray<int32> &Indices, TArray<FVector2D> &Uvs, TArray<FColor> &Colors, TArray<FProcMeshTangent>& Colors2, UMaterialInstanceDynamic* Material) {
+
+void USpineSkeletonRendererComponent::Flush (int &Idx, TArray<FVector> &Vertices, TArray<int32> &Indices, TArray<FVector2D> &Uvs, TArray<FColor> &Colors, TArray<FVector>& Colors2, UMaterialInstanceDynamic* Material) {
 	if (Vertices.Num() == 0) return;
 	SetMaterial(Idx, Material);
-	CreateMeshSection(Idx, Vertices, Indices, TArray<FVector>(), Uvs, Colors, TArray<FProcMeshTangent>(), false);
+
+	TArray<FRuntimeMeshVertexTripleUV> verts;
+	for (int32 i = 0; i < Vertices.Num(); i++) {
+		verts.Add(FRuntimeMeshVertexTripleUV(Vertices[i], FVector(), FVector(), Colors[i], Uvs[i], FVector2D(Colors2[i].X, Colors2[i].Y), FVector2D(Colors2[i].Z, 0)));
+	}
+
+	CreateMeshSection(Idx, verts, Indices);
+
+	// CreateMeshSection(Idx, Vertices, Indices, TArray<FVector>(), Uvs, darkRG, Colors, TArray<FRuntimeMeshTangent>(), false);
 	Vertices.SetNum(0);
 	Indices.SetNum(0);
 	Uvs.SetNum(0);
@@ -186,7 +195,7 @@ void USpineSkeletonRendererComponent::UpdateMesh(spSkeleton* Skeleton) {
 	TArray<int32> indices;
 	TArray<FVector2D> uvs;
 	TArray<FColor> colors;
-	TArray<FProcMeshTangent> darkColors;
+	TArray<FVector> darkColors;
 	
 	int idx = 0;
 	int meshSection = 0;
@@ -288,11 +297,11 @@ void USpineSkeletonRendererComponent::UpdateMesh(spSkeleton* Skeleton) {
 
 		float dr = slot->darkColor ? slot->darkColor->r : 0.0f;
 		float dg = slot->darkColor ? slot->darkColor->g : 0.0f;
-		float db = slot->darkColor ? slot->darkColor->b : 0.0f;
+		float db = slot->darkColor ? slot->darkColor->b : 0.0f;		
 
 		for (int j = 0; j < numVertices << 1; j += 2) {
 			colors.Add(FColor(r, g, b, a));
-			darkColors.Add(FProcMeshTangent(dr, dg, db));
+			darkColors.Add(FVector(dr, dg, db));
 			vertices.Add(FVector(attachmentVertices[j], depthOffset, attachmentVertices[j + 1]));
 			uvs.Add(FVector2D(attachmentUvs[j], attachmentUvs[j + 1]));
 		}
@@ -307,7 +316,7 @@ void USpineSkeletonRendererComponent::UpdateMesh(spSkeleton* Skeleton) {
 		spSkeletonClipping_clipEnd(clipper, slot);			
 	}
 	
-	Flush(meshSection, vertices, indices, uvs, colors,darkColors, lastMaterial);
+	Flush(meshSection, vertices, indices, uvs, colors, darkColors, lastMaterial);
 	spSkeletonClipping_clipEnd2(clipper);
 }
 

+ 2 - 2
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonAnimationComponent.h

@@ -173,9 +173,9 @@ public:
 	void SetMixDuration(float mixDuration) { if (entry) entry->mixDuration = mixDuration; }
 
 	UFUNCTION(BlueprintCallable, Category="Components|Spine|TrackEntry")
-	float GetMixAlpha() { return entry ? entry->mixAlpha : 0; }
+	float GetInterruptAlpha() { return entry ? entry->interruptAlpha : 0; }
 	UFUNCTION(BlueprintCallable, Category="Components|Spine|TrackEntry")
-	void SetMixAlpha(float mixAlpha) { if (entry) entry->mixAlpha = mixAlpha; }	
+	void SetInterruptAlpha(float interruptAlpha) { if (entry) entry->interruptAlpha = interruptAlpha; }
 
 	UPROPERTY(BlueprintAssignable, Category = "Components|Spine|TrackEntry")
 	FSpineAnimationStartDelegate AnimationStart;

+ 3 - 3
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonRendererComponent.h

@@ -31,13 +31,13 @@
 #pragma once
 
 #include "Components/ActorComponent.h"
-#include "ProceduralMeshComponent.h"
+#include "RuntimeMeshComponent.h"
 #include "SpineSkeletonAnimationComponent.h"
 #include "SpineSkeletonRendererComponent.generated.h"
 
 
 UCLASS(ClassGroup=(Spine), meta=(BlueprintSpawnableComponent))
-class SPINEPLUGIN_API USpineSkeletonRendererComponent: public UProceduralMeshComponent {
+class SPINEPLUGIN_API USpineSkeletonRendererComponent: public URuntimeMeshComponent {
 	GENERATED_BODY()
 
 public: 
@@ -73,7 +73,7 @@ public:
 protected:
 	void UpdateMesh (spSkeleton* Skeleton);
 
-	void Flush (int &Idx, TArray<FVector> &Vertices, TArray<int32> &Indices, TArray<FVector2D> &Uvs, TArray<FColor> &Colors, TArray<FProcMeshTangent> &Colors2, UMaterialInstanceDynamic* Material);
+	void Flush (int &Idx, TArray<FVector> &Vertices, TArray<int32> &Indices, TArray<FVector2D> &Uvs, TArray<FColor> &Colors, TArray<FVector> &Colors2, UMaterialInstanceDynamic* Material);
 	
 	// Need to hold on to the dynamic instances, or the GC will kill us while updating them
 	UPROPERTY()

+ 1 - 1
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/SpinePlugin.Build.cs

@@ -8,7 +8,7 @@ namespace UnrealBuildTool.Rules
 		{
 			PublicIncludePaths.AddRange(new string[] { "SpinePlugin/Public", "SpinePlugin/Public/spine-c/include" });
             PrivateIncludePaths.AddRange(new string[] { "SpinePlugin/Private", "SpinePlugin/Public/spine-c/include" });
-            PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "RHI", "RenderCore", "ShaderCore", "ProceduralMeshComponent" });
+            PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "RHI", "RenderCore", "ShaderCore", "ProceduralMeshComponent", "RuntimeMeshComponent" });
             OptimizeCode = CodeOptimization.Never;
 		}
 	}

+ 0 - 12
spine-unity/Assets/Examples/Getting Started/1 The Spine GameObject.unity

@@ -120,7 +120,6 @@ GameObject:
   - component: {fileID: 139638500}
   - component: {fileID: 139638499}
   - component: {fileID: 139638503}
-  - component: {fileID: 139638504}
   m_Layer: 0
   m_Name: Spine GameObject (SpineUnityGirl)
   m_TagString: Untagged
@@ -224,17 +223,6 @@ MonoBehaviour:
   blinkAnimation: blink
   minimumDelay: 0.15
   maximumDelay: 3
---- !u!114 &139638504
-MonoBehaviour:
-  m_ObjectHideFlags: 0
-  m_PrefabParentObject: {fileID: 0}
-  m_PrefabInternal: {fileID: 0}
-  m_GameObject: {fileID: 139638498}
-  m_Enabled: 1
-  m_EditorHideFlags: 0
-  m_Script: {fileID: 11500000, guid: 7b337e07bc016684dabf04793dd00ea3, type: 3}
-  m_Name: 
-  m_EditorClassIdentifier: 
 --- !u!1 &199409354
 GameObject:
   m_ObjectHideFlags: 0

+ 101 - 49
spine-unity/Assets/Examples/Getting Started/2 Controlling Animation.unity

@@ -1,19 +1,19 @@
 %YAML 1.1
 %TAG !u! tag:unity3d.com,2011:
 --- !u!29 &1
-SceneSettings:
+OcclusionCullingSettings:
   m_ObjectHideFlags: 0
-  m_PVSData: 
-  m_PVSObjectsArray: []
-  m_PVSPortalsArray: []
+  serializedVersion: 2
   m_OcclusionBakeSettings:
     smallestOccluder: 5
     smallestHole: 0.25
     backfaceThreshold: 100
+  m_SceneGUID: 00000000000000000000000000000000
+  m_OcclusionCullingData: {fileID: 0}
 --- !u!104 &2
 RenderSettings:
   m_ObjectHideFlags: 0
-  serializedVersion: 6
+  serializedVersion: 8
   m_Fog: 0
   m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
   m_FogMode: 3
@@ -25,6 +25,7 @@ RenderSettings:
   m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
   m_AmbientIntensity: 1
   m_AmbientMode: 3
+  m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
   m_SkyboxMaterial: {fileID: 0}
   m_HaloStrength: 0.5
   m_FlareStrength: 1
@@ -37,12 +38,12 @@ RenderSettings:
   m_ReflectionIntensity: 1
   m_CustomReflection: {fileID: 0}
   m_Sun: {fileID: 0}
+  m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
 --- !u!157 &3
 LightmapSettings:
   m_ObjectHideFlags: 0
-  serializedVersion: 6
+  serializedVersion: 9
   m_GIWorkflowMode: 1
-  m_LightmapsMode: 1
   m_GISettings:
     serializedVersion: 2
     m_BounceScale: 1
@@ -53,48 +54,70 @@ LightmapSettings:
     m_EnableBakedLightmaps: 0
     m_EnableRealtimeLightmaps: 0
   m_LightmapEditorSettings:
-    serializedVersion: 3
+    serializedVersion: 8
     m_Resolution: 2
     m_BakeResolution: 40
     m_TextureWidth: 1024
     m_TextureHeight: 1024
+    m_AO: 0
     m_AOMaxDistance: 1
-    m_Padding: 2
     m_CompAOExponent: 0
+    m_CompAOExponentDirect: 0
+    m_Padding: 2
     m_LightmapParameters: {fileID: 0}
+    m_LightmapsBakeMode: 1
     m_TextureCompression: 1
     m_FinalGather: 0
+    m_FinalGatherFiltering: 1
     m_FinalGatherRayCount: 1024
     m_ReflectionCompression: 2
+    m_MixedBakeMode: 1
+    m_BakeBackend: 0
+    m_PVRSampling: 1
+    m_PVRDirectSampleCount: 32
+    m_PVRSampleCount: 500
+    m_PVRBounces: 2
+    m_PVRFiltering: 0
+    m_PVRFilteringMode: 1
+    m_PVRCulling: 1
+    m_PVRFilteringGaussRadiusDirect: 1
+    m_PVRFilteringGaussRadiusIndirect: 5
+    m_PVRFilteringGaussRadiusAO: 2
+    m_PVRFilteringAtrousColorSigma: 1
+    m_PVRFilteringAtrousNormalSigma: 1
+    m_PVRFilteringAtrousPositionSigma: 1
   m_LightingDataAsset: {fileID: 0}
-  m_RuntimeCPUUsage: 25
+  m_ShadowMaskMode: 2
 --- !u!196 &4
 NavMeshSettings:
   serializedVersion: 2
   m_ObjectHideFlags: 0
   m_BuildSettings:
     serializedVersion: 2
+    agentTypeID: 0
     agentRadius: 0.5
     agentHeight: 2
     agentSlope: 45
     agentClimb: 0.4
     ledgeDropHeight: 0
     maxJumpAcrossDistance: 0
-    accuratePlacement: 0
     minRegionArea: 2
-    cellSize: 0.16666667
     manualCellSize: 0
+    cellSize: 0.16666667
+    manualTileSize: 0
+    tileSize: 256
+    accuratePlacement: 0
   m_NavMeshData: {fileID: 0}
 --- !u!1 &199409354
 GameObject:
   m_ObjectHideFlags: 0
   m_PrefabParentObject: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
-  serializedVersion: 4
+  serializedVersion: 5
   m_Component:
-  - 4: {fileID: 199409355}
-  - 23: {fileID: 199409357}
-  - 102: {fileID: 199409356}
+  - component: {fileID: 199409355}
+  - component: {fileID: 199409357}
+  - component: {fileID: 199409356}
   m_Layer: 0
   m_Name: Description
   m_TagString: Untagged
@@ -114,6 +137,7 @@ Transform:
   m_Children: []
   m_Father: {fileID: 926099112}
   m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
 --- !u!102 &199409356
 TextMesh:
   serializedVersion: 3
@@ -151,35 +175,41 @@ MeshRenderer:
   m_Enabled: 1
   m_CastShadows: 1
   m_ReceiveShadows: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
   m_Materials:
   - {fileID: 10100, guid: 0000000000000000e000000000000000, type: 0}
-  m_SubsetIndices: 
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
   m_StaticBatchRoot: {fileID: 0}
-  m_UseLightProbes: 1
-  m_ReflectionProbeUsage: 1
   m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
   m_ScaleInLightmap: 1
   m_PreserveUVs: 0
   m_IgnoreNormalsForChartDetection: 0
   m_ImportantGI: 0
+  m_SelectedEditorRenderState: 3
   m_MinimumChartSize: 4
   m_AutoUVMaxDistance: 0.5
   m_AutoUVMaxAngle: 89
   m_LightmapParameters: {fileID: 0}
   m_SortingLayerID: 0
+  m_SortingLayer: 0
   m_SortingOrder: 0
 --- !u!1 &611702901
 GameObject:
   m_ObjectHideFlags: 0
   m_PrefabParentObject: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
-  serializedVersion: 4
+  serializedVersion: 5
   m_Component:
-  - 4: {fileID: 611702906}
-  - 20: {fileID: 611702905}
-  - 92: {fileID: 611702904}
-  - 124: {fileID: 611702903}
-  - 81: {fileID: 611702902}
+  - component: {fileID: 611702906}
+  - component: {fileID: 611702905}
+  - component: {fileID: 611702904}
+  - component: {fileID: 611702903}
+  - component: {fileID: 611702902}
   m_Layer: 0
   m_Name: Main Camera
   m_TagString: MainCamera
@@ -238,6 +268,8 @@ Camera:
   m_TargetDisplay: 0
   m_TargetEye: 3
   m_HDR: 0
+  m_AllowMSAA: 1
+  m_ForceIntoRT: 0
   m_OcclusionCulling: 1
   m_StereoConvergence: 10
   m_StereoSeparation: 0.022
@@ -254,18 +286,19 @@ Transform:
   m_Children: []
   m_Father: {fileID: 0}
   m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
 --- !u!1 &816033309
 GameObject:
   m_ObjectHideFlags: 0
   m_PrefabParentObject: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
-  serializedVersion: 4
+  serializedVersion: 5
   m_Component:
-  - 4: {fileID: 816033313}
-  - 33: {fileID: 816033312}
-  - 23: {fileID: 816033311}
-  - 114: {fileID: 816033310}
-  - 114: {fileID: 816033314}
+  - component: {fileID: 816033313}
+  - component: {fileID: 816033312}
+  - component: {fileID: 816033311}
+  - component: {fileID: 816033310}
+  - component: {fileID: 816033314}
   m_Layer: 0
   m_Name: spineboy
   m_TagString: Untagged
@@ -284,16 +317,20 @@ MonoBehaviour:
   m_Script: {fileID: 11500000, guid: d247ba06193faa74d9335f5481b2b56c, type: 3}
   m_Name: 
   m_EditorClassIdentifier: 
-  skeletonDataAsset: {fileID: 11400000, guid: 44691b56ed7d1f04da0cbc2a52a91b8d, type: 2}
-  initialSkinName: default
+  skeletonDataAsset: {fileID: 11400000, guid: a467507a4ffb1d542a558739b2fede77, type: 2}
+  initialSkinName: base
+  initialFlipX: 0
+  initialFlipY: 0
   separatorSlotNames: []
   zSpacing: 0
-  renderMeshes: 1
+  useClipping: 1
   immutableTriangles: 0
   pmaVertexColors: 1
-  calculateNormals: 0
+  clearStateOnDisable: 0
+  tintBlack: 0
+  singleSubmesh: 0
+  addNormals: 0
   calculateTangents: 0
-  frontFacing: 0
   logErrors: 0
   disableRenderingOnOverride: 1
   _animationName: idle
@@ -308,22 +345,28 @@ MeshRenderer:
   m_Enabled: 1
   m_CastShadows: 1
   m_ReceiveShadows: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
   m_Materials:
-  - {fileID: 2100000, guid: 4083cd422558e2540a62bbafb94f57b5, type: 2}
-  m_SubsetIndices: 
+  - {fileID: 2100000, guid: 1455e88fdb81ccc45bdeaedd657bad4d, type: 2}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
   m_StaticBatchRoot: {fileID: 0}
-  m_UseLightProbes: 1
-  m_ReflectionProbeUsage: 1
   m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
   m_ScaleInLightmap: 1
   m_PreserveUVs: 0
   m_IgnoreNormalsForChartDetection: 0
   m_ImportantGI: 0
+  m_SelectedEditorRenderState: 3
   m_MinimumChartSize: 4
   m_AutoUVMaxDistance: 0.5
   m_AutoUVMaxAngle: 89
   m_LightmapParameters: {fileID: 0}
   m_SortingLayerID: 0
+  m_SortingLayer: 0
   m_SortingOrder: 0
 --- !u!33 &816033312
 MeshFilter:
@@ -344,6 +387,7 @@ Transform:
   m_Children: []
   m_Father: {fileID: 0}
   m_RootOrder: 1
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
 --- !u!114 &816033314
 MonoBehaviour:
   m_ObjectHideFlags: 0
@@ -364,9 +408,9 @@ GameObject:
   m_ObjectHideFlags: 0
   m_PrefabParentObject: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
-  serializedVersion: 4
+  serializedVersion: 5
   m_Component:
-  - 4: {fileID: 926099112}
+  - component: {fileID: 926099112}
   m_Layer: 0
   m_Name: LABELS
   m_TagString: Untagged
@@ -388,16 +432,17 @@ Transform:
   - {fileID: 1945923979}
   m_Father: {fileID: 0}
   m_RootOrder: 2
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
 --- !u!1 &1945923976
 GameObject:
   m_ObjectHideFlags: 0
   m_PrefabParentObject: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
-  serializedVersion: 4
+  serializedVersion: 5
   m_Component:
-  - 4: {fileID: 1945923979}
-  - 23: {fileID: 1945923978}
-  - 102: {fileID: 1945923977}
+  - component: {fileID: 1945923979}
+  - component: {fileID: 1945923978}
+  - component: {fileID: 1945923977}
   m_Layer: 0
   m_Name: play mode
   m_TagString: Untagged
@@ -435,22 +480,28 @@ MeshRenderer:
   m_Enabled: 1
   m_CastShadows: 1
   m_ReceiveShadows: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
   m_Materials:
   - {fileID: 10100, guid: 0000000000000000e000000000000000, type: 0}
-  m_SubsetIndices: 
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
   m_StaticBatchRoot: {fileID: 0}
-  m_UseLightProbes: 1
-  m_ReflectionProbeUsage: 1
   m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
   m_ScaleInLightmap: 1
   m_PreserveUVs: 0
   m_IgnoreNormalsForChartDetection: 0
   m_ImportantGI: 0
+  m_SelectedEditorRenderState: 3
   m_MinimumChartSize: 4
   m_AutoUVMaxDistance: 0.5
   m_AutoUVMaxAngle: 89
   m_LightmapParameters: {fileID: 0}
   m_SortingLayerID: 0
+  m_SortingLayer: 0
   m_SortingOrder: 0
 --- !u!4 &1945923979
 Transform:
@@ -464,3 +515,4 @@ Transform:
   m_Children: []
   m_Father: {fileID: 926099112}
   m_RootOrder: 1
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

+ 2 - 2
spine-unity/Assets/Examples/Getting Started/4 Object Oriented Sample.unity

@@ -427,8 +427,8 @@ MonoBehaviour:
   m_Script: {fileID: 11500000, guid: d247ba06193faa74d9335f5481b2b56c, type: 3}
   m_Name: 
   m_EditorClassIdentifier: 
-  skeletonDataAsset: {fileID: 11400000, guid: 44691b56ed7d1f04da0cbc2a52a91b8d, type: 2}
-  initialSkinName: default
+  skeletonDataAsset: {fileID: 11400000, guid: a467507a4ffb1d542a558739b2fede77, type: 2}
+  initialSkinName: base
   initialFlipX: 0
   initialFlipY: 0
   separatorSlotNames: []

+ 2 - 2
spine-unity/Assets/Examples/Getting Started/5 Basic Platformer.unity

@@ -651,7 +651,7 @@ MonoBehaviour:
   m_EditorClassIdentifier: 
   groundMask:
     serializedVersion: 2
-    m_Bits: 1024
+    m_Bits: 1025
   use2D: 0
   useRadius: 1
   castRadius: 0.22
@@ -1833,7 +1833,7 @@ MonoBehaviour:
   m_EditorClassIdentifier: 
   groundMask:
     serializedVersion: 2
-    m_Bits: 1024
+    m_Bits: 1025
   use2D: 0
   useRadius: 1
   castRadius: 0.22

+ 2 - 2
spine-unity/Assets/Examples/Getting Started/6 SkeletonGraphic.unity

@@ -938,8 +938,8 @@ MonoBehaviour:
       m_Calls: []
     m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
       Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
-  skeletonDataAsset: {fileID: 11400000, guid: 44691b56ed7d1f04da0cbc2a52a91b8d, type: 2}
-  initialSkinName: default
+  skeletonDataAsset: {fileID: 11400000, guid: a467507a4ffb1d542a558739b2fede77, type: 2}
+  initialSkinName: base
   startingAnimation: run
   startingLoop: 1
   timeScale: 1

+ 0 - 14
spine-unity/Assets/Examples/Other Examples/AtlasRegionAttacher.unity

@@ -210,7 +210,6 @@ GameObject:
   - component: {fileID: 1263080511}
   - component: {fileID: 1263080510}
   - component: {fileID: 1263080509}
-  - component: {fileID: 1263080515}
   m_Layer: 0
   m_Name: FootSoldier
   m_TagString: Untagged
@@ -340,16 +339,3 @@ Transform:
   m_Father: {fileID: 0}
   m_RootOrder: 1
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
---- !u!114 &1263080515
-MonoBehaviour:
-  m_ObjectHideFlags: 0
-  m_PrefabParentObject: {fileID: 0}
-  m_PrefabInternal: {fileID: 0}
-  m_GameObject: {fileID: 1263080508}
-  m_Enabled: 1
-  m_EditorHideFlags: 0
-  m_Script: {fileID: 11500000, guid: 7f726fb798ad621458c431cb9966d91d, type: 3}
-  m_Name: 
-  m_EditorClassIdentifier: 
-  boneRoot: {fileID: 0}
-  skeletonRenderer: {fileID: 1263080511}

+ 114 - 0
spine-unity/Assets/Examples/Other Examples/Dragon.unity

@@ -242,6 +242,120 @@ Transform:
   m_Father: {fileID: 0}
   m_RootOrder: 1
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &757780186
+GameObject:
+  m_ObjectHideFlags: 0
+  m_PrefabParentObject: {fileID: 100000, guid: d51ed5943e10bcb4394b5eec480293f8, type: 2}
+  m_PrefabInternal: {fileID: 0}
+  serializedVersion: 5
+  m_Component:
+  - component: {fileID: 757780190}
+  - component: {fileID: 757780189}
+  - component: {fileID: 757780188}
+  - component: {fileID: 757780187}
+  m_Layer: 0
+  m_Name: dragon (1)
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!114 &757780187
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_PrefabParentObject: {fileID: 11400000, guid: d51ed5943e10bcb4394b5eec480293f8,
+    type: 2}
+  m_PrefabInternal: {fileID: 0}
+  m_GameObject: {fileID: 757780186}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: d247ba06193faa74d9335f5481b2b56c, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  skeletonDataAsset: {fileID: 11400000, guid: 76506fa7fbeed084ab2dfb084648c628, type: 2}
+  initialSkinName: default
+  initialFlipX: 0
+  initialFlipY: 0
+  separatorSlotNames: []
+  zSpacing: 0
+  useClipping: 1
+  immutableTriangles: 0
+  pmaVertexColors: 1
+  clearStateOnDisable: 0
+  tintBlack: 0
+  singleSubmesh: 0
+  addNormals: 1
+  calculateTangents: 0
+  logErrors: 0
+  disableRenderingOnOverride: 1
+  _animationName: flying
+  loop: 1
+  timeScale: 1
+--- !u!23 &757780188
+MeshRenderer:
+  m_ObjectHideFlags: 0
+  m_PrefabParentObject: {fileID: 2300000, guid: d51ed5943e10bcb4394b5eec480293f8,
+    type: 2}
+  m_PrefabInternal: {fileID: 0}
+  m_GameObject: {fileID: 757780186}
+  m_Enabled: 1
+  m_CastShadows: 1
+  m_ReceiveShadows: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 0
+  m_ReflectionProbeUsage: 1
+  m_Materials:
+  - {fileID: 2100000, guid: d58543c96f991934ca874395eb40222c, type: 2}
+  - {fileID: 2100000, guid: 3277fd5561d95724e83c6ca4a1dd28a4, type: 2}
+  - {fileID: 2100000, guid: d58543c96f991934ca874395eb40222c, type: 2}
+  - {fileID: 2100000, guid: 3277fd5561d95724e83c6ca4a1dd28a4, type: 2}
+  - {fileID: 2100000, guid: d58543c96f991934ca874395eb40222c, type: 2}
+  - {fileID: 2100000, guid: 3277fd5561d95724e83c6ca4a1dd28a4, type: 2}
+  - {fileID: 2100000, guid: d58543c96f991934ca874395eb40222c, type: 2}
+  - {fileID: 2100000, guid: 3277fd5561d95724e83c6ca4a1dd28a4, type: 2}
+  - {fileID: 2100000, guid: d58543c96f991934ca874395eb40222c, type: 2}
+  - {fileID: 2100000, guid: 3277fd5561d95724e83c6ca4a1dd28a4, type: 2}
+  - {fileID: 2100000, guid: d58543c96f991934ca874395eb40222c, type: 2}
+  - {fileID: 2100000, guid: 3277fd5561d95724e83c6ca4a1dd28a4, type: 2}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_SelectedEditorRenderState: 3
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+--- !u!33 &757780189
+MeshFilter:
+  m_ObjectHideFlags: 0
+  m_PrefabParentObject: {fileID: 3300000, guid: d51ed5943e10bcb4394b5eec480293f8,
+    type: 2}
+  m_PrefabInternal: {fileID: 0}
+  m_GameObject: {fileID: 757780186}
+  m_Mesh: {fileID: 0}
+--- !u!4 &757780190
+Transform:
+  m_ObjectHideFlags: 0
+  m_PrefabParentObject: {fileID: 400000, guid: d51ed5943e10bcb4394b5eec480293f8, type: 2}
+  m_PrefabInternal: {fileID: 0}
+  m_GameObject: {fileID: 757780186}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 0}
+  m_RootOrder: 4
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
 --- !u!1 &1604340971
 GameObject:
   m_ObjectHideFlags: 0

+ 168 - 91
spine-unity/Assets/Examples/Other Examples/Goblins.unity

@@ -1,19 +1,19 @@
 %YAML 1.1
 %TAG !u! tag:unity3d.com,2011:
 --- !u!29 &1
-SceneSettings:
+OcclusionCullingSettings:
   m_ObjectHideFlags: 0
-  m_PVSData: 
-  m_PVSObjectsArray: []
-  m_PVSPortalsArray: []
+  serializedVersion: 2
   m_OcclusionBakeSettings:
     smallestOccluder: 5
     smallestHole: 0.25
     backfaceThreshold: 100
+  m_SceneGUID: 00000000000000000000000000000000
+  m_OcclusionCullingData: {fileID: 0}
 --- !u!104 &2
 RenderSettings:
   m_ObjectHideFlags: 0
-  serializedVersion: 6
+  serializedVersion: 8
   m_Fog: 0
   m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
   m_FogMode: 3
@@ -25,6 +25,7 @@ RenderSettings:
   m_AmbientGroundColor: {r: 0.2, g: 0.2, b: 0.2, a: 1}
   m_AmbientIntensity: 1
   m_AmbientMode: 3
+  m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
   m_SkyboxMaterial: {fileID: 0}
   m_HaloStrength: 0.5
   m_FlareStrength: 1
@@ -37,12 +38,12 @@ RenderSettings:
   m_ReflectionIntensity: 1
   m_CustomReflection: {fileID: 0}
   m_Sun: {fileID: 0}
+  m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
 --- !u!157 &4
 LightmapSettings:
   m_ObjectHideFlags: 0
-  serializedVersion: 6
+  serializedVersion: 9
   m_GIWorkflowMode: 1
-  m_LightmapsMode: 1
   m_GISettings:
     serializedVersion: 2
     m_BounceScale: 1
@@ -53,51 +54,72 @@ LightmapSettings:
     m_EnableBakedLightmaps: 1
     m_EnableRealtimeLightmaps: 0
   m_LightmapEditorSettings:
-    serializedVersion: 3
+    serializedVersion: 8
     m_Resolution: 1
     m_BakeResolution: 50
     m_TextureWidth: 1024
     m_TextureHeight: 1024
+    m_AO: 0
     m_AOMaxDistance: 1
-    m_Padding: 2
     m_CompAOExponent: 0
+    m_CompAOExponentDirect: 0
+    m_Padding: 2
     m_LightmapParameters: {fileID: 0}
+    m_LightmapsBakeMode: 1
     m_TextureCompression: 0
     m_FinalGather: 0
+    m_FinalGatherFiltering: 1
     m_FinalGatherRayCount: 1024
     m_ReflectionCompression: 2
+    m_MixedBakeMode: 1
+    m_BakeBackend: 0
+    m_PVRSampling: 1
+    m_PVRDirectSampleCount: 32
+    m_PVRSampleCount: 500
+    m_PVRBounces: 2
+    m_PVRFiltering: 0
+    m_PVRFilteringMode: 1
+    m_PVRCulling: 1
+    m_PVRFilteringGaussRadiusDirect: 1
+    m_PVRFilteringGaussRadiusIndirect: 5
+    m_PVRFilteringGaussRadiusAO: 2
+    m_PVRFilteringAtrousColorSigma: 1
+    m_PVRFilteringAtrousNormalSigma: 1
+    m_PVRFilteringAtrousPositionSigma: 1
   m_LightingDataAsset: {fileID: 0}
-  m_RuntimeCPUUsage: 25
+  m_ShadowMaskMode: 2
 --- !u!196 &5
 NavMeshSettings:
   serializedVersion: 2
   m_ObjectHideFlags: 0
   m_BuildSettings:
     serializedVersion: 2
+    agentTypeID: 0
     agentRadius: 0.5
     agentHeight: 2
     agentSlope: 45
     agentClimb: 0.4
     ledgeDropHeight: 0
     maxJumpAcrossDistance: 0
-    accuratePlacement: 0
     minRegionArea: 2
-    cellSize: 0.16666666
     manualCellSize: 0
+    cellSize: 0.16666666
+    manualTileSize: 0
+    tileSize: 256
+    accuratePlacement: 0
   m_NavMeshData: {fileID: 0}
 --- !u!1 &371702429
 GameObject:
   m_ObjectHideFlags: 0
   m_PrefabParentObject: {fileID: 100000, guid: 700a0a62306a8954b8933538790e20a5, type: 2}
   m_PrefabInternal: {fileID: 0}
-  serializedVersion: 4
+  serializedVersion: 5
   m_Component:
-  - 4: {fileID: 371702434}
-  - 33: {fileID: 371702433}
-  - 23: {fileID: 371702432}
-  - 114: {fileID: 371702431}
-  - 114: {fileID: 371702430}
-  - 61: {fileID: 371702435}
+  - component: {fileID: 371702434}
+  - component: {fileID: 371702433}
+  - component: {fileID: 371702432}
+  - component: {fileID: 371702431}
+  - component: {fileID: 371702430}
   m_Layer: 0
   m_Name: goblins-mesh
   m_TagString: Untagged
@@ -117,6 +139,7 @@ MonoBehaviour:
   m_Script: {fileID: 11500000, guid: 40a82af6554a7594f9ffa9ac8dde212f, type: 3}
   m_Name: 
   m_EditorClassIdentifier: 
+  extraRotation: 0
 --- !u!114 &371702431
 MonoBehaviour:
   m_ObjectHideFlags: 0
@@ -131,12 +154,17 @@ MonoBehaviour:
   m_EditorClassIdentifier: 
   skeletonDataAsset: {fileID: 11400000, guid: 2e4e11d4dd87d844a876d18c4d76a612, type: 2}
   initialSkinName: goblin
+  initialFlipX: 0
+  initialFlipY: 0
   separatorSlotNames: []
   zSpacing: 0
-  renderMeshes: 1
+  useClipping: 1
   immutableTriangles: 0
   pmaVertexColors: 1
-  calculateNormals: 1
+  clearStateOnDisable: 0
+  tintBlack: 0
+  singleSubmesh: 0
+  addNormals: 1
   calculateTangents: 0
   logErrors: 0
   disableRenderingOnOverride: 1
@@ -153,22 +181,28 @@ MeshRenderer:
   m_Enabled: 1
   m_CastShadows: 1
   m_ReceiveShadows: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 0
+  m_ReflectionProbeUsage: 1
   m_Materials:
   - {fileID: 2100000, guid: 54091ef934c41eb4192f72bfd8e3bcc9, type: 2}
-  m_SubsetIndices: 
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
   m_StaticBatchRoot: {fileID: 0}
-  m_UseLightProbes: 0
-  m_ReflectionProbeUsage: 1
   m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
   m_ScaleInLightmap: 1
   m_PreserveUVs: 0
   m_IgnoreNormalsForChartDetection: 0
   m_ImportantGI: 0
+  m_SelectedEditorRenderState: 3
   m_MinimumChartSize: 4
   m_AutoUVMaxDistance: 0.5
   m_AutoUVMaxAngle: 89
   m_LightmapParameters: {fileID: 0}
   m_SortingLayerID: 0
+  m_SortingLayer: 0
   m_SortingOrder: 0
 --- !u!33 &371702433
 MeshFilter:
@@ -185,100 +219,141 @@ Transform:
   m_PrefabInternal: {fileID: 0}
   m_GameObject: {fileID: 371702429}
   m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
-  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalPosition: {x: -2, y: 0, z: 0}
   m_LocalScale: {x: 1, y: 1, z: 1}
-  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
   m_Children: []
   m_Father: {fileID: 0}
-  m_RootOrder: 0
---- !u!61 &371702435
-BoxCollider2D:
-  m_ObjectHideFlags: 0
-  m_PrefabParentObject: {fileID: 0}
-  m_PrefabInternal: {fileID: 0}
-  m_GameObject: {fileID: 371702429}
-  m_Enabled: 1
-  m_Density: 1
-  m_Material: {fileID: 0}
-  m_IsTrigger: 0
-  m_UsedByEffector: 0
-  m_Offset: {x: -0.055532753, y: 1.7475463}
-  serializedVersion: 2
-  m_Size: {x: 2.5330653, y: 3.6126883}
---- !u!1 &829885015
+  m_RootOrder: 1
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1248674550
 GameObject:
   m_ObjectHideFlags: 0
-  m_PrefabParentObject: {fileID: 0}
+  m_PrefabParentObject: {fileID: 100000, guid: 700a0a62306a8954b8933538790e20a5, type: 2}
   m_PrefabInternal: {fileID: 0}
-  serializedVersion: 4
+  serializedVersion: 5
   m_Component:
-  - 4: {fileID: 829885017}
-  - 108: {fileID: 829885016}
+  - component: {fileID: 1248674555}
+  - component: {fileID: 1248674554}
+  - component: {fileID: 1248674553}
+  - component: {fileID: 1248674552}
+  - component: {fileID: 1248674551}
   m_Layer: 0
-  m_Name: Point light
+  m_Name: goblins-mesh (1)
   m_TagString: Untagged
   m_Icon: {fileID: 0}
   m_NavMeshLayer: 0
   m_StaticEditorFlags: 0
   m_IsActive: 1
---- !u!108 &829885016
-Light:
+--- !u!114 &1248674551
+MonoBehaviour:
   m_ObjectHideFlags: 0
-  m_PrefabParentObject: {fileID: 0}
+  m_PrefabParentObject: {fileID: 11400002, guid: 700a0a62306a8954b8933538790e20a5,
+    type: 2}
   m_PrefabInternal: {fileID: 0}
-  m_GameObject: {fileID: 829885015}
+  m_GameObject: {fileID: 1248674550}
   m_Enabled: 1
-  serializedVersion: 6
-  m_Type: 2
-  m_Color: {r: 1, g: 1, b: 1, a: 1}
-  m_Intensity: 2
-  m_Range: 10
-  m_SpotAngle: 30
-  m_CookieSize: 10
-  m_Shadows:
-    m_Type: 0
-    m_Resolution: -1
-    m_Strength: 1
-    m_Bias: 0.05
-    m_NormalBias: 0.4
-    m_NearPlane: 0.2
-  m_Cookie: {fileID: 0}
-  m_DrawHalo: 0
-  m_Flare: {fileID: 0}
-  m_RenderMode: 0
-  m_CullingMask:
-    serializedVersion: 2
-    m_Bits: 4294967295
-  m_Lightmapping: 1
-  m_BounceIntensity: 1
-  m_ShadowRadius: 0
-  m_ShadowAngle: 0
-  m_AreaSize: {x: 1, y: 1}
---- !u!4 &829885017
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 40a82af6554a7594f9ffa9ac8dde212f, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  extraRotation: 0
+--- !u!114 &1248674552
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_PrefabParentObject: {fileID: 11400000, guid: 700a0a62306a8954b8933538790e20a5,
+    type: 2}
+  m_PrefabInternal: {fileID: 0}
+  m_GameObject: {fileID: 1248674550}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: d247ba06193faa74d9335f5481b2b56c, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  skeletonDataAsset: {fileID: 11400000, guid: 2e4e11d4dd87d844a876d18c4d76a612, type: 2}
+  initialSkinName: goblingirl
+  initialFlipX: 1
+  initialFlipY: 0
+  separatorSlotNames: []
+  zSpacing: 0
+  useClipping: 1
+  immutableTriangles: 0
+  pmaVertexColors: 1
+  clearStateOnDisable: 0
+  tintBlack: 0
+  singleSubmesh: 0
+  addNormals: 1
+  calculateTangents: 0
+  logErrors: 0
+  disableRenderingOnOverride: 1
+  _animationName: walk
+  loop: 1
+  timeScale: 1
+--- !u!23 &1248674553
+MeshRenderer:
+  m_ObjectHideFlags: 0
+  m_PrefabParentObject: {fileID: 2300000, guid: 700a0a62306a8954b8933538790e20a5,
+    type: 2}
+  m_PrefabInternal: {fileID: 0}
+  m_GameObject: {fileID: 1248674550}
+  m_Enabled: 1
+  m_CastShadows: 1
+  m_ReceiveShadows: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 0
+  m_ReflectionProbeUsage: 1
+  m_Materials:
+  - {fileID: 2100000, guid: 54091ef934c41eb4192f72bfd8e3bcc9, type: 2}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_SelectedEditorRenderState: 3
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+--- !u!33 &1248674554
+MeshFilter:
+  m_ObjectHideFlags: 0
+  m_PrefabParentObject: {fileID: 3300000, guid: 700a0a62306a8954b8933538790e20a5,
+    type: 2}
+  m_PrefabInternal: {fileID: 0}
+  m_GameObject: {fileID: 1248674550}
+  m_Mesh: {fileID: 0}
+--- !u!4 &1248674555
 Transform:
   m_ObjectHideFlags: 0
-  m_PrefabParentObject: {fileID: 0}
+  m_PrefabParentObject: {fileID: 400000, guid: 700a0a62306a8954b8933538790e20a5, type: 2}
   m_PrefabInternal: {fileID: 0}
-  m_GameObject: {fileID: 829885015}
+  m_GameObject: {fileID: 1248674550}
   m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
-  m_LocalPosition: {x: 1.7437632, y: 3.0306358, z: -1.5414181}
+  m_LocalPosition: {x: 2, y: 0, z: 0}
   m_LocalScale: {x: 1, y: 1, z: 1}
-  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
   m_Children: []
   m_Father: {fileID: 0}
   m_RootOrder: 2
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
 --- !u!1 &1457374310
 GameObject:
   m_ObjectHideFlags: 0
   m_PrefabParentObject: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
-  serializedVersion: 4
+  serializedVersion: 5
   m_Component:
-  - 4: {fileID: 1457374315}
-  - 20: {fileID: 1457374314}
-  - 92: {fileID: 1457374313}
-  - 124: {fileID: 1457374312}
-  - 81: {fileID: 1457374311}
+  - component: {fileID: 1457374315}
+  - component: {fileID: 1457374314}
+  - component: {fileID: 1457374313}
+  - component: {fileID: 1457374312}
+  - component: {fileID: 1457374311}
   m_Layer: 0
   m_Name: Main Camera
   m_TagString: MainCamera
@@ -316,7 +391,7 @@ Camera:
   m_Enabled: 1
   serializedVersion: 2
   m_ClearFlags: 1
-  m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0.019607844}
+  m_BackGroundColor: {r: 0.039984792, g: 0.1397059, b: 0.10890967, a: 0.019607844}
   m_NormalizedViewPortRect:
     serializedVersion: 2
     x: 0
@@ -337,6 +412,8 @@ Camera:
   m_TargetDisplay: 0
   m_TargetEye: 3
   m_HDR: 0
+  m_AllowMSAA: 1
+  m_ForceIntoRT: 0
   m_OcclusionCulling: 1
   m_StereoConvergence: 10
   m_StereoSeparation: 0.022
@@ -350,7 +427,7 @@ Transform:
   m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
   m_LocalPosition: {x: 0, y: 2.0984764, z: -5.8611603}
   m_LocalScale: {x: 1, y: 1, z: 1}
-  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
   m_Children: []
   m_Father: {fileID: 0}
-  m_RootOrder: 1
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

Diferenças do arquivo suprimidas por serem muito extensas
+ 61 - 16
spine-unity/Assets/Examples/Other Examples/Mix and Match.unity


+ 14 - 16
spine-unity/Assets/Examples/Other Examples/SkeletonRenderSeparator.unity

@@ -233,8 +233,8 @@ MonoBehaviour:
   m_Name: 
   m_EditorClassIdentifier: 
   skeletonRenderer: {fileID: 93048076}
-  copyPropertyBlock: 0
-  copyMeshRendererFlags: 0
+  copyPropertyBlock: 1
+  copyMeshRendererFlags: 1
   partsRenderers:
   - {fileID: 1769987560}
   - {fileID: 1619823301}
@@ -283,7 +283,6 @@ MeshRenderer:
   m_ReflectionProbeUsage: 1
   m_Materials:
   - {fileID: 0}
-  - {fileID: 2100000, guid: 1455e88fdb81ccc45bdeaedd657bad4d, type: 2}
   m_StaticBatchInfo:
     firstSubMesh: 0
     subMeshCount: 0
@@ -362,9 +361,9 @@ MeshRenderer:
   m_GameObject: {fileID: 122287539}
   m_Enabled: 1
   m_CastShadows: 1
-  m_ReceiveShadows: 1
+  m_ReceiveShadows: 0
   m_MotionVectors: 1
-  m_LightProbeUsage: 1
+  m_LightProbeUsage: 0
   m_ReflectionProbeUsage: 1
   m_Materials:
   - {fileID: 2100000, guid: 1455e88fdb81ccc45bdeaedd657bad4d, type: 2}
@@ -463,7 +462,7 @@ ParticleSystemRenderer:
   m_PreserveUVs: 0
   m_IgnoreNormalsForChartDetection: 0
   m_ImportantGI: 0
-  m_SelectedEditorRenderState: 3
+  m_SelectedEditorRenderState: 0
   m_MinimumChartSize: 4
   m_AutoUVMaxDistance: 0.5
   m_AutoUVMaxAngle: 89
@@ -561,7 +560,7 @@ ParticleSystem:
   moveWithTransform: 1
   moveWithCustomTransform: {fileID: 0}
   scalingMode: 1
-  randomSeed: 0
+  randomSeed: -1037588642
   InitialModule:
     serializedVersion: 3
     enabled: 1
@@ -4022,9 +4021,9 @@ MeshRenderer:
   m_GameObject: {fileID: 1619823300}
   m_Enabled: 1
   m_CastShadows: 1
-  m_ReceiveShadows: 1
+  m_ReceiveShadows: 0
   m_MotionVectors: 1
-  m_LightProbeUsage: 1
+  m_LightProbeUsage: 0
   m_ReflectionProbeUsage: 1
   m_Materials:
   - {fileID: 2100000, guid: 1455e88fdb81ccc45bdeaedd657bad4d, type: 2}
@@ -4134,9 +4133,9 @@ MeshRenderer:
   m_GameObject: {fileID: 1698487791}
   m_Enabled: 1
   m_CastShadows: 1
-  m_ReceiveShadows: 1
+  m_ReceiveShadows: 0
   m_MotionVectors: 1
-  m_LightProbeUsage: 1
+  m_LightProbeUsage: 0
   m_ReflectionProbeUsage: 1
   m_Materials:
   - {fileID: 2100000, guid: 1455e88fdb81ccc45bdeaedd657bad4d, type: 2}
@@ -4215,9 +4214,9 @@ MeshRenderer:
   m_GameObject: {fileID: 1769987559}
   m_Enabled: 1
   m_CastShadows: 1
-  m_ReceiveShadows: 1
+  m_ReceiveShadows: 0
   m_MotionVectors: 1
-  m_LightProbeUsage: 1
+  m_LightProbeUsage: 0
   m_ReflectionProbeUsage: 1
   m_Materials:
   - {fileID: 2100000, guid: 1455e88fdb81ccc45bdeaedd657bad4d, type: 2}
@@ -4291,8 +4290,8 @@ MonoBehaviour:
   m_Name: 
   m_EditorClassIdentifier: 
   skeletonRenderer: {fileID: 1918225116}
-  copyPropertyBlock: 0
-  copyMeshRendererFlags: 0
+  copyPropertyBlock: 1
+  copyMeshRendererFlags: 1
   partsRenderers:
   - {fileID: 122287540}
   - {fileID: 1698487792}
@@ -4341,7 +4340,6 @@ MeshRenderer:
   m_ReflectionProbeUsage: 1
   m_Materials:
   - {fileID: 0}
-  - {fileID: 2100000, guid: 1455e88fdb81ccc45bdeaedd657bad4d, type: 2}
   m_StaticBatchInfo:
     firstSubMesh: 0
     subMeshCount: 0

Diferenças do arquivo suprimidas por serem muito extensas
+ 352 - 205
spine-unity/Assets/Examples/Other Examples/SkeletonUtility Eyes.unity


+ 103 - 68
spine-unity/Assets/Examples/Other Examples/SpineGauge.unity

@@ -1,19 +1,19 @@
 %YAML 1.1
 %TAG !u! tag:unity3d.com,2011:
 --- !u!29 &1
-SceneSettings:
+OcclusionCullingSettings:
   m_ObjectHideFlags: 0
-  m_PVSData: 
-  m_PVSObjectsArray: []
-  m_PVSPortalsArray: []
+  serializedVersion: 2
   m_OcclusionBakeSettings:
     smallestOccluder: 5
     smallestHole: 0.25
     backfaceThreshold: 100
+  m_SceneGUID: 00000000000000000000000000000000
+  m_OcclusionCullingData: {fileID: 0}
 --- !u!104 &2
 RenderSettings:
   m_ObjectHideFlags: 0
-  serializedVersion: 7
+  serializedVersion: 8
   m_Fog: 0
   m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
   m_FogMode: 3
@@ -25,6 +25,7 @@ RenderSettings:
   m_AmbientGroundColor: {r: 0.2, g: 0.2, b: 0.2, a: 1}
   m_AmbientIntensity: 1
   m_AmbientMode: 3
+  m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
   m_SkyboxMaterial: {fileID: 0}
   m_HaloStrength: 0.5
   m_FlareStrength: 1
@@ -41,7 +42,7 @@ RenderSettings:
 --- !u!157 &4
 LightmapSettings:
   m_ObjectHideFlags: 0
-  serializedVersion: 7
+  serializedVersion: 9
   m_GIWorkflowMode: 1
   m_GISettings:
     serializedVersion: 2
@@ -53,7 +54,7 @@ LightmapSettings:
     m_EnableBakedLightmaps: 1
     m_EnableRealtimeLightmaps: 0
   m_LightmapEditorSettings:
-    serializedVersion: 4
+    serializedVersion: 8
     m_Resolution: 1
     m_BakeResolution: 50
     m_TextureWidth: 1024
@@ -66,41 +67,58 @@ LightmapSettings:
     m_LightmapParameters: {fileID: 0}
     m_LightmapsBakeMode: 1
     m_TextureCompression: 0
-    m_DirectLightInLightProbes: 1
     m_FinalGather: 0
     m_FinalGatherFiltering: 1
     m_FinalGatherRayCount: 1024
     m_ReflectionCompression: 2
+    m_MixedBakeMode: 1
+    m_BakeBackend: 0
+    m_PVRSampling: 1
+    m_PVRDirectSampleCount: 32
+    m_PVRSampleCount: 500
+    m_PVRBounces: 2
+    m_PVRFiltering: 0
+    m_PVRFilteringMode: 1
+    m_PVRCulling: 1
+    m_PVRFilteringGaussRadiusDirect: 1
+    m_PVRFilteringGaussRadiusIndirect: 5
+    m_PVRFilteringGaussRadiusAO: 2
+    m_PVRFilteringAtrousColorSigma: 1
+    m_PVRFilteringAtrousNormalSigma: 1
+    m_PVRFilteringAtrousPositionSigma: 1
   m_LightingDataAsset: {fileID: 0}
-  m_RuntimeCPUUsage: 25
+  m_ShadowMaskMode: 2
 --- !u!196 &5
 NavMeshSettings:
   serializedVersion: 2
   m_ObjectHideFlags: 0
   m_BuildSettings:
     serializedVersion: 2
+    agentTypeID: 0
     agentRadius: 0.5
     agentHeight: 2
     agentSlope: 45
     agentClimb: 0.4
     ledgeDropHeight: 0
     maxJumpAcrossDistance: 0
-    accuratePlacement: 0
     minRegionArea: 2
-    cellSize: 0.16666666
     manualCellSize: 0
+    cellSize: 0.16666666
+    manualTileSize: 0
+    tileSize: 256
+    accuratePlacement: 0
   m_NavMeshData: {fileID: 0}
 --- !u!1 &351144566
 GameObject:
   m_ObjectHideFlags: 0
   m_PrefabParentObject: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
-  serializedVersion: 4
+  serializedVersion: 5
   m_Component:
-  - 4: {fileID: 351144570}
-  - 33: {fileID: 351144569}
-  - 23: {fileID: 351144568}
-  - 114: {fileID: 351144567}
+  - component: {fileID: 351144570}
+  - component: {fileID: 351144569}
+  - component: {fileID: 351144568}
+  - component: {fileID: 351144567}
   m_Layer: 0
   m_Name: Spine GameObject (spineboy)
   m_TagString: Untagged
@@ -121,13 +139,17 @@ MonoBehaviour:
   m_EditorClassIdentifier: 
   skeletonDataAsset: {fileID: 11400000, guid: 44691b56ed7d1f04da0cbc2a52a91b8d, type: 2}
   initialSkinName: default
+  initialFlipX: 0
+  initialFlipY: 0
   separatorSlotNames: []
   zSpacing: 0
-  renderMeshes: 1
+  useClipping: 1
   immutableTriangles: 0
   pmaVertexColors: 1
   clearStateOnDisable: 0
-  calculateNormals: 0
+  tintBlack: 0
+  singleSubmesh: 0
+  addNormals: 0
   calculateTangents: 0
   logErrors: 0
   disableRenderingOnOverride: 1
@@ -148,7 +170,9 @@ MeshRenderer:
   m_ReflectionProbeUsage: 1
   m_Materials:
   - {fileID: 2100000, guid: 1455e88fdb81ccc45bdeaedd657bad4d, type: 2}
-  m_SubsetIndices: 
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
   m_StaticBatchRoot: {fileID: 0}
   m_ProbeAnchor: {fileID: 0}
   m_LightProbeVolumeOverride: {fileID: 0}
@@ -156,12 +180,13 @@ MeshRenderer:
   m_PreserveUVs: 0
   m_IgnoreNormalsForChartDetection: 0
   m_ImportantGI: 0
-  m_SelectedWireframeHidden: 0
+  m_SelectedEditorRenderState: 3
   m_MinimumChartSize: 4
   m_AutoUVMaxDistance: 0.5
   m_AutoUVMaxAngle: 89
   m_LightmapParameters: {fileID: 0}
   m_SortingLayerID: 0
+  m_SortingLayer: 0
   m_SortingOrder: 0
 --- !u!33 &351144569
 MeshFilter:
@@ -179,22 +204,23 @@ Transform:
   m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
   m_LocalPosition: {x: -0.3, y: -3.48, z: 0}
   m_LocalScale: {x: 1, y: 1, z: 1}
-  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
-  m_Children: []
+  m_Children:
+  - {fileID: 795271517}
   m_Father: {fileID: 0}
   m_RootOrder: 2
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
 --- !u!1 &795271513
 GameObject:
   m_ObjectHideFlags: 0
   m_PrefabParentObject: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
-  serializedVersion: 4
+  serializedVersion: 5
   m_Component:
-  - 4: {fileID: 795271517}
-  - 33: {fileID: 795271518}
-  - 23: {fileID: 795271516}
-  - 114: {fileID: 795271515}
-  - 114: {fileID: 795271514}
+  - component: {fileID: 795271517}
+  - component: {fileID: 795271518}
+  - component: {fileID: 795271516}
+  - component: {fileID: 795271515}
+  - component: {fileID: 795271514}
   m_Layer: 0
   m_Name: Spine GameObject (gauge)
   m_TagString: Untagged
@@ -228,13 +254,17 @@ MonoBehaviour:
   m_EditorClassIdentifier: 
   skeletonDataAsset: {fileID: 11400000, guid: 22b19a38b21c15a48854f0db86b0b7d3, type: 2}
   initialSkinName: default
+  initialFlipX: 0
+  initialFlipY: 0
   separatorSlotNames: []
   zSpacing: 0
-  renderMeshes: 1
+  useClipping: 1
   immutableTriangles: 0
   pmaVertexColors: 1
   clearStateOnDisable: 0
-  calculateNormals: 0
+  tintBlack: 0
+  singleSubmesh: 0
+  addNormals: 0
   calculateTangents: 0
   logErrors: 0
   disableRenderingOnOverride: 1
@@ -252,7 +282,9 @@ MeshRenderer:
   m_ReflectionProbeUsage: 1
   m_Materials:
   - {fileID: 2100000, guid: 9ab9bdbda020b3e46b5a3b0558ef591d, type: 2}
-  m_SubsetIndices: 
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
   m_StaticBatchRoot: {fileID: 0}
   m_ProbeAnchor: {fileID: 0}
   m_LightProbeVolumeOverride: {fileID: 0}
@@ -260,12 +292,13 @@ MeshRenderer:
   m_PreserveUVs: 0
   m_IgnoreNormalsForChartDetection: 0
   m_ImportantGI: 0
-  m_SelectedWireframeHidden: 0
+  m_SelectedEditorRenderState: 3
   m_MinimumChartSize: 4
   m_AutoUVMaxDistance: 0.5
   m_AutoUVMaxAngle: 89
   m_LightmapParameters: {fileID: 0}
   m_SortingLayerID: 0
+  m_SortingLayer: 0
   m_SortingOrder: 0
 --- !u!4 &795271517
 Transform:
@@ -273,14 +306,13 @@ Transform:
   m_PrefabParentObject: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
   m_GameObject: {fileID: 795271513}
-  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
-  m_LocalPosition: {x: 0, y: 4.09, z: 0}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 0.15800005, y: 7.619125, z: 0}
   m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 351144570}
+  m_RootOrder: 0
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
-  m_Children:
-  - {fileID: 1025516230}
-  m_Father: {fileID: 0}
-  m_RootOrder: 1
 --- !u!33 &795271518
 MeshFilter:
   m_ObjectHideFlags: 0
@@ -293,10 +325,10 @@ GameObject:
   m_ObjectHideFlags: 0
   m_PrefabParentObject: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
-  serializedVersion: 4
+  serializedVersion: 5
   m_Component:
-  - 4: {fileID: 1025516230}
-  - 114: {fileID: 1025516231}
+  - component: {fileID: 1025516230}
+  - component: {fileID: 1025516231}
   m_Layer: 0
   m_Name: Attack Spineboy
   m_TagString: Untagged
@@ -311,13 +343,13 @@ Transform:
   m_PrefabInternal: {fileID: 0}
   m_GameObject: {fileID: 1025516229}
   m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
-  m_LocalPosition: {x: -2.672, y: -1.3108752, z: 0}
+  m_LocalPosition: {x: -2.672, y: 2.779125, z: 0}
   m_LocalScale: {x: 1, y: 1, z: 1}
-  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
   m_Children:
   - {fileID: 1695530253}
-  m_Father: {fileID: 795271517}
-  m_RootOrder: 0
+  m_Father: {fileID: 0}
+  m_RootOrder: 1
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
 --- !u!114 &1025516231
 MonoBehaviour:
   m_ObjectHideFlags: 0
@@ -337,11 +369,11 @@ GameObject:
   m_ObjectHideFlags: 0
   m_PrefabParentObject: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
-  serializedVersion: 4
+  serializedVersion: 5
   m_Component:
-  - 224: {fileID: 1053578424}
-  - 222: {fileID: 1053578426}
-  - 114: {fileID: 1053578425}
+  - component: {fileID: 1053578424}
+  - component: {fileID: 1053578426}
+  - component: {fileID: 1053578425}
   m_Layer: 5
   m_Name: Text
   m_TagString: Untagged
@@ -358,10 +390,10 @@ RectTransform:
   m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
   m_LocalPosition: {x: 0, y: 0, z: 0}
   m_LocalScale: {x: 1, y: 1, z: 1}
-  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
   m_Children: []
   m_Father: {fileID: 1695530253}
   m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
   m_AnchorMin: {x: 0.5, y: 0.5}
   m_AnchorMax: {x: 0.5, y: 0.5}
   m_AnchoredPosition: {x: 436.7, y: 225.4}
@@ -414,13 +446,13 @@ GameObject:
   m_ObjectHideFlags: 0
   m_PrefabParentObject: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
-  serializedVersion: 4
+  serializedVersion: 5
   m_Component:
-  - 4: {fileID: 1611520407}
-  - 20: {fileID: 1611520406}
-  - 92: {fileID: 1611520405}
-  - 124: {fileID: 1611520404}
-  - 81: {fileID: 1611520403}
+  - component: {fileID: 1611520407}
+  - component: {fileID: 1611520406}
+  - component: {fileID: 1611520405}
+  - component: {fileID: 1611520404}
+  - component: {fileID: 1611520403}
   m_Layer: 0
   m_Name: Main Camera
   m_TagString: MainCamera
@@ -479,6 +511,8 @@ Camera:
   m_TargetDisplay: 0
   m_TargetEye: 3
   m_HDR: 0
+  m_AllowMSAA: 1
+  m_ForceIntoRT: 0
   m_OcclusionCulling: 1
   m_StereoConvergence: 10
   m_StereoSeparation: 0.022
@@ -492,20 +526,20 @@ Transform:
   m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
   m_LocalPosition: {x: 0, y: 1, z: -10}
   m_LocalScale: {x: 1, y: 1, z: 1}
-  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
   m_Children: []
   m_Father: {fileID: 0}
   m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
 --- !u!1 &1695530249
 GameObject:
   m_ObjectHideFlags: 0
   m_PrefabParentObject: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
-  serializedVersion: 4
+  serializedVersion: 5
   m_Component:
-  - 224: {fileID: 1695530253}
-  - 223: {fileID: 1695530252}
-  - 114: {fileID: 1695530251}
+  - component: {fileID: 1695530253}
+  - component: {fileID: 1695530252}
+  - component: {fileID: 1695530251}
   m_Layer: 5
   m_Name: Canvas
   m_TagString: Untagged
@@ -541,7 +575,7 @@ Canvas:
   m_PrefabInternal: {fileID: 0}
   m_GameObject: {fileID: 1695530249}
   m_Enabled: 1
-  serializedVersion: 2
+  serializedVersion: 3
   m_RenderMode: 2
   m_Camera: {fileID: 0}
   m_PlaneDistance: 100
@@ -550,6 +584,7 @@ Canvas:
   m_OverrideSorting: 0
   m_OverridePixelPerfect: 0
   m_SortingBucketNormalizedSize: 0
+  m_AdditionalShaderChannelsFlag: 25
   m_SortingLayerID: 0
   m_SortingOrder: 0
   m_TargetDisplay: 0
@@ -562,12 +597,12 @@ RectTransform:
   m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
   m_LocalPosition: {x: 0, y: 0, z: 0}
   m_LocalScale: {x: 0.01, y: 0.01, z: 0.01}
-  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
   m_Children:
   - {fileID: 1053578424}
   - {fileID: 1847717248}
   m_Father: {fileID: 1025516230}
   m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
   m_AnchorMin: {x: 0, y: 0}
   m_AnchorMax: {x: 0, y: 0}
   m_AnchoredPosition: {x: 2.672, y: -2.779125}
@@ -578,11 +613,11 @@ GameObject:
   m_ObjectHideFlags: 0
   m_PrefabParentObject: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
-  serializedVersion: 4
+  serializedVersion: 5
   m_Component:
-  - 224: {fileID: 1847717248}
-  - 222: {fileID: 1847717250}
-  - 114: {fileID: 1847717249}
+  - component: {fileID: 1847717248}
+  - component: {fileID: 1847717250}
+  - component: {fileID: 1847717249}
   m_Layer: 5
   m_Name: Health Text
   m_TagString: Untagged
@@ -599,10 +634,10 @@ RectTransform:
   m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
   m_LocalPosition: {x: 0, y: 0, z: 0}
   m_LocalScale: {x: 1, y: 1, z: 1}
-  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
   m_Children: []
   m_Father: {fileID: 1695530253}
   m_RootOrder: 1
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
   m_AnchorMin: {x: 0.5, y: 0.5}
   m_AnchorMax: {x: 0.5, y: 0.5}
   m_AnchoredPosition: {x: -13, y: 343}

+ 59 - 41
spine-unity/Assets/Examples/Scripts/MixAndMatch.cs

@@ -32,28 +32,35 @@ using UnityEngine;
 using Spine.Unity.Modules.AttachmentTools;
 
 namespace Spine.Unity.Examples {
+
+	// This is an example script that shows you how to change images on your skeleton using UnityEngine.Sprites.
 	public class MixAndMatch : MonoBehaviour {
 
 		#region Inspector
+		[SpineSkin]
+		public string baseSkinName = "base";
+		public Material sourceMaterial; // This will be used as the basis for shader and material property settings.
+
 		[Header("Visor")]
 		public Sprite visorSprite;
 		[SpineSlot] public string visorSlot;
-		[SpineAttachment(slotField:"visorSlot")] public string visorKey = "goggles";
+		[SpineAttachment(slotField:"visorSlot", skinField:"baseSkinName")] public string visorKey = "goggles";
 
 		[Header("Gun")]
 		public Sprite gunSprite;
 		[SpineSlot] public string gunSlot;
-		[SpineAttachment(slotField:"gunSlot")] public string gunKey = "gun";
+		[SpineAttachment(slotField:"gunSlot", skinField:"baseSkinName")] public string gunKey = "gun";
 
 		[Header("Runtime Repack")]
 		public bool repack = true;
-		public Material sourceMaterial;
 
 		[Header("Do not assign")]
 		public Texture2D runtimeAtlas;
 		public Material runtimeMaterial;
 		#endregion
 
+		Skin customSkin;
+
 		void OnValidate () {
 			if (sourceMaterial == null) {
 				var skeletonAnimation = GetComponent<SkeletonAnimation>();
@@ -63,53 +70,64 @@ namespace Spine.Unity.Examples {
 		}
 
 		void Start () {
+			Apply();
+		}
+
+		void Apply () {
 			var skeletonAnimation = GetComponent<SkeletonAnimation>();
 			var skeleton = skeletonAnimation.Skeleton;
 
-			// All attachment changes will be applied to the skin. We use a clone so other instances will not be affected.
-			var newSkin = skeleton.UnshareSkin(true, false, skeletonAnimation.AnimationState);
-
-			// NOTE: sprite textures need to be full rect.
-			var originalSkin = skeleton.Data.DefaultSkin;
-
-			int visorSlotIndex = skeleton.FindSlotIndex(visorSlot);
-			RegionAttachment originalVisor = originalSkin.GetAttachment(visorSlotIndex, visorKey) as RegionAttachment;
-			RegionAttachment newVisor = visorSprite.ToRegionAttachmentPMAClone(sourceMaterial);
-			newVisor.X = originalVisor.X;
-			newVisor.Y = originalVisor.Y;
-			newVisor.Rotation = originalVisor.Rotation;
-			newSkin.AddAttachment(visorSlotIndex, visorKey, newVisor);
-
-
+			// STEP 0: PREPARE SKINS
+			// Let's prepare a new skin to be our custom skin with equips/customizations. We get a clone so our original skins are unaffected.
+			customSkin = customSkin ?? new Skin("custom skin"); // This requires that all customizations are done with skin placeholders defined in Spine.
+			//customSkin = customSkin ?? skeleton.UnshareSkin(true, false, skeletonAnimation.AnimationState); // use this if you are not customizing on the default skin and don't plan to remove 
+			// Next let's 
+			var baseSkin = skeleton.Data.FindSkin(baseSkinName);
+
+			// STEP 1: "EQUIP" ITEMS USING SPRITES
+			// STEP 1.1 Find the original attachment.
+			// Step 1.2 Get a clone of the original attachment.
+			// Step 1.3 Apply the Sprite image to it.
+			// Step 1.4 Add the remapped clone to the new custom skin.
+
+			// Let's do this for the visor.
+			int visorSlotIndex = skeleton.FindSlotIndex(visorSlot); // You can access GetAttachment and SetAttachment via string, but caching the slotIndex is faster.
+			Attachment baseAttachment = baseSkin.GetAttachment(visorSlotIndex, visorKey);  // STEP 1.1
+			Attachment newAttachment = baseAttachment.GetRemappedClone(visorSprite, sourceMaterial); // STEP 1.2 - 1.3
+			customSkin.SetAttachment(visorSlotIndex, visorKey, newAttachment); // STEP 1.4
+
+			// And now for the gun.
 			int gunSlotIndex = skeleton.FindSlotIndex(gunSlot);
-			RegionAttachment originalGun = originalSkin.GetAttachment(gunSlotIndex, gunKey) as RegionAttachment;
-			RegionAttachment newGun = gunSprite.ToRegionAttachmentPMAClone(sourceMaterial);
-			newGun.x = originalGun.x;
-			newGun.y = originalGun.y;
-			newGun.Rotation = originalGun.Rotation;
-			newSkin.AddAttachment(gunSlotIndex, gunKey, newGun);
-
-			// Case 3: Change an existing attachment's backing region.
-//			if (applyHeadRegion) {
-//				AtlasRegion spineBoyHead = headSource.GetAtlas().FindRegion(headRegion);
-//				int headSlotIndex = skeleton.FindSlotIndex(headSlot);
-//				var newHead = newSkin.GetAttachment(headSlotIndex, headAttachmentName).GetClone(true);
-//				newHead.SetRegion(spineBoyHead);
-//				newSkin.AddAttachment(headSlotIndex, headAttachmentName, newHead);
-//			}
-
-			// Case 4: Repacking a mixed-and-matched skin to minimize draw calls.
-			// Repacking requires that you set all source textures/sprites/atlases to be Read/Write enabled in the inspector.
-			if (repack) {
-				newSkin = newSkin.GetRepackedSkin("repacked", sourceMaterial, out runtimeMaterial, out runtimeAtlas);
+			Attachment baseGun = baseSkin.GetAttachment(gunSlotIndex, gunKey); // STEP 1.1
+			Attachment newGun = baseGun.GetRemappedClone(gunSprite, sourceMaterial); // STEP 1.2 - 1.3
+			if (newGun != null) customSkin.SetAttachment(gunSlotIndex, gunKey, newGun); // STEP 1.4
+
+			// customSkin.RemoveAttachment(gunSlotIndex, gunKey); // To remove an item.
+			// customSkin.Clear()
+			// Use skin.Clear() To remove all customizations.
+			// Customizations will fall back to the value in the default skin if it was defined there.
+			// To prevent fallback from happening, make sure the key is not defined in the default skin.
+
+			// STEP 3: APPLY AND CLEAN UP.
+			// Recommended: REPACK THE CUSTOM SKIN TO MINIMIZE DRAW CALLS
+			// 				Repacking requires that you set all source textures/sprites/atlases to be Read/Write enabled in the inspector.
+			// 				Combine all the attachment sources into one skin. Usually this means the default skin and the custom skin.
+			// 				call Skin.GetRepackedSkin to get a cloned skin with cloned attachments that all use one texture.
+			//				Under the hood, this relies on 
+			if (repack)	{
+				var repackedSkin = new Skin("repacked skin");
+				repackedSkin.Append(skeleton.Data.DefaultSkin);
+				repackedSkin.Append(customSkin);
+				repackedSkin = repackedSkin.GetRepackedSkin("repacked skin", sourceMaterial, out runtimeMaterial, out runtimeAtlas);
+				skeleton.SetSkin(repackedSkin);
+			} else {
+				skeleton.SetSkin(customSkin);
 			}
-			
-			skeleton.SetSkin(newSkin);
+
 			skeleton.SetSlotsToSetupPose();
 			skeletonAnimation.Update(0);
 
 			Resources.UnloadUnusedAssets();
 		}
-
 	}
 }

+ 12 - 2
spine-unity/Assets/Examples/Scripts/Sample Components/BoneLocalOverride.cs

@@ -2,9 +2,8 @@
 using Spine;
 using Spine.Unity;
 
-namespace Spine.Unity.Modules {	
+namespace Spine.Unity.Examples {	
 	public class BoneLocalOverride : MonoBehaviour {
-		
 		[SpineBone]
 		public string boneName;
 
@@ -15,12 +14,23 @@ namespace Spine.Unity.Modules {
 		public bool overridePosition = true;
 		public Vector2 localPosition;
 
+		[Space]
 		public bool overrideRotation = true;
 		[Range(0, 360)] public float rotation = 0;
 
 		ISkeletonAnimation spineComponent;
 		Bone bone;
 
+		#if UNITY_EDITOR
+		void OnValidate () {
+			if (Application.isPlaying) return;
+			spineComponent = spineComponent ?? GetComponent<ISkeletonAnimation>();
+			if (spineComponent == null) return;
+			if (bone != null) bone.SetToSetupPose();
+			OverrideLocal(spineComponent);
+		}
+		#endif
+
 		void Awake () {
 			spineComponent = GetComponent<ISkeletonAnimation>();
 			if (spineComponent == null) { this.enabled = false; return; }

+ 33 - 0
spine-unity/Assets/Examples/Scripts/Sample Components/CombinedSkin.cs

@@ -0,0 +1,33 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Spine.Unity.Modules.AttachmentTools; 
+
+namespace Spine.Unity.Examples {
+	public class CombinedSkin : MonoBehaviour {
+		[SpineSkin]
+		public List<string> skinsToCombine;
+
+		Skin combinedSkin;
+
+		void Start () {
+			var skeletonComponent = GetComponent<ISkeletonComponent>();
+			if (skeletonComponent == null) return;
+			var skeleton = skeletonComponent.Skeleton;
+			if (skeleton == null) return;
+
+			combinedSkin = combinedSkin ?? new Skin("combined");
+			combinedSkin.Clear();
+			foreach (var skinName in skinsToCombine) {
+				var skin = skeleton.Data.FindSkin(skinName);
+				if (skin != null) combinedSkin.Append(skin);
+			}
+
+			skeleton.SetSkin(combinedSkin);
+			skeleton.SetToSetupPose();
+			var animationStateComponent = skeletonComponent as IAnimationStateComponent;
+			if (animationStateComponent != null) animationStateComponent.AnimationState.Apply(skeleton);
+		}
+	}
+
+}

+ 12 - 0
spine-unity/Assets/Examples/Scripts/Sample Components/CombinedSkin.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: e864d2963143ec04eb4f905e6add115e
+timeCreated: 1495176902
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 2 - 2
spine-unity/Assets/spine-unity/Mesh Generation/Arrays.meta → spine-unity/Assets/Examples/Scripts/Sample Components/Legacy.meta

@@ -1,7 +1,7 @@
 fileFormatVersion: 2
-guid: ce7d323baddaa244c8f9f45b38b68193
+guid: d8033179a12443e4eaef33b35fed074c
 folderAsset: yes
-timeCreated: 1457398522
+timeCreated: 1495179724
 licenseType: Free
 DefaultImporter:
   userData: 

+ 19 - 57
spine-unity/Assets/Examples/Scripts/Sample Components/AtlasRegionAttacher.cs → spine-unity/Assets/Examples/Scripts/Sample Components/Legacy/AtlasRegionAttacher.cs

@@ -40,11 +40,8 @@ namespace Spine.Unity.Modules {
 
 		[System.Serializable]
 		public class SlotRegionPair {
-			[SpineSlot]
-			public string slot;
-
-			[SpineAtlasRegion]
-			public string region;
+			[SpineSlot] public string slot;
+			[SpineAtlasRegion] public string region;
 		}
 
 		[SerializeField] protected AtlasAsset atlasAsset;
@@ -53,69 +50,34 @@ namespace Spine.Unity.Modules {
 
 		Atlas atlas;
 
-		void Start () {
-			
-		}
-
 		void Awake () {
-			GetComponent<SkeletonRenderer>().OnRebuild += Apply;
+			var skeletonRenderer = GetComponent<SkeletonRenderer>();
+			skeletonRenderer.OnRebuild += Apply;
+			if (skeletonRenderer.valid) Apply(skeletonRenderer);
 		}
 
+		void Start () { } // Allow checkbox in inspector
+
 		void Apply (SkeletonRenderer skeletonRenderer) {
 			if (!this.enabled) return;
+
 			atlas = atlasAsset.GetAtlas();
 			float scale = skeletonRenderer.skeletonDataAsset.scale;
 
 			foreach (var entry in attachments) {
-				var slot = skeletonRenderer.Skeleton.FindSlot(entry.slot);
-				var region = atlas.FindRegion(entry.region);
-				ReplaceAttachment(slot, region, scale, inheritProperties);
-			}
-		}
-
-		static void ReplaceAttachment (Slot slot, AtlasRegion region, float scale, bool inheritProperties) {
-			var originalAttachment = slot.Attachment;
-
-			// Altas was empty
-			if (region == null) {
-				slot.Attachment = null;
-				return;
-			}
-
-			// Original was MeshAttachment
-			if (inheritProperties) {
-				var originalMeshAttachment = originalAttachment as MeshAttachment;
-				if (originalMeshAttachment != null) {
-					var newMeshAttachment = originalMeshAttachment.GetLinkedClone(); // Attach the region as a linked mesh to the original mesh.
-					newMeshAttachment.SetRegion(region);
-					slot.Attachment = newMeshAttachment;
-					return;
+				Slot slot = skeletonRenderer.Skeleton.FindSlot(entry.slot);
+				Attachment originalAttachment = slot.Attachment;
+				AtlasRegion region = atlas.FindRegion(entry.region);
+
+				if (region == null) {
+					slot.Attachment = null;
+				} else if (inheritProperties && originalAttachment != null) {
+					slot.Attachment = originalAttachment.GetRemappedClone(region, true, true, scale);
+				} else {
+					var newRegionAttachment = region.ToRegionAttachment(region.name, scale);
+					slot.Attachment = newRegionAttachment;
 				}
 			}
-
-			// Original was RegionAttachment or empty
-			{
-				var originalRegionAttachment = originalAttachment as RegionAttachment;
-				var newRegionAttachment = region.ToRegionAttachment(region.name, scale);
-				if (originalRegionAttachment != null && inheritProperties) {
-					newRegionAttachment.X = originalRegionAttachment.X;
-					newRegionAttachment.Y = originalRegionAttachment.Y;
-					newRegionAttachment.Rotation = originalRegionAttachment.Rotation;
-					newRegionAttachment.ScaleX = originalRegionAttachment.ScaleX;
-					newRegionAttachment.ScaleY = originalRegionAttachment.ScaleY;
-					newRegionAttachment.UpdateOffset();
-
-					newRegionAttachment.R = originalRegionAttachment.R;
-					newRegionAttachment.G = originalRegionAttachment.G;
-					newRegionAttachment.B = originalRegionAttachment.B;
-					newRegionAttachment.A = originalRegionAttachment.A;
-				}
-
-				slot.Attachment = newRegionAttachment;
-				return;
-			}
-
 		}
-
 	}
 }

+ 0 - 0
spine-unity/Assets/Examples/Scripts/Sample Components/AtlasRegionAttacher.cs.meta → spine-unity/Assets/Examples/Scripts/Sample Components/Legacy/AtlasRegionAttacher.cs.meta


+ 0 - 0
spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysMeshGenerator.cs → spine-unity/Assets/Examples/Scripts/Sample Components/Legacy/CustomSkin.cs


+ 0 - 0
spine-unity/Assets/Examples/Scripts/Sample Components/CustomSkin.cs.meta → spine-unity/Assets/Examples/Scripts/Sample Components/Legacy/CustomSkin.cs.meta


+ 0 - 0
spine-unity/Assets/Examples/Scripts/Sample Components/SpriteAttacher.cs → spine-unity/Assets/Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs


+ 0 - 0
spine-unity/Assets/Examples/Scripts/Sample Components/SpriteAttacher.cs.meta → spine-unity/Assets/Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs.meta


+ 7 - 4
spine-unity/Assets/Examples/Scripts/Sample Components/SkeletonColorInitialize.cs

@@ -17,19 +17,19 @@ namespace Spine.Unity.Examples {
 			public Color color = Color.white;
 		}
 
+		#if UNITY_EDITOR
 		void OnValidate () {
 			var skeletonComponent = GetComponent<ISkeletonComponent>();
 			if (skeletonComponent != null) {
 				skeletonComponent.Skeleton.SetSlotsToSetupPose();
-
 				var animationStateComponent = GetComponent<IAnimationStateComponent>();
 				if (animationStateComponent != null && animationStateComponent.AnimationState != null) {
 					animationStateComponent.AnimationState.Apply(skeletonComponent.Skeleton);
 				}
 			}
-
 			ApplySettings();
 		}
+		#endif
 
 		void Start () {
 			ApplySettings();
@@ -41,8 +41,11 @@ namespace Spine.Unity.Examples {
 				var skeleton = skeletonComponent.Skeleton;
 				skeleton.SetColor(skeletonColor);
 
-				foreach (var s in slotSettings)
-					skeleton.FindSlot(s.slot).SetColor(s.color);
+				foreach (var s in slotSettings) {
+					var slot = skeleton.FindSlot(s.slot);
+					if (slot != null) slot.SetColor(s.color);
+				}
+					
 			}
 		}
 

+ 1 - 2
spine-unity/Assets/Examples/Scripts/Sample Components/SlotTintBlackFollower.cs

@@ -1,14 +1,13 @@
 using UnityEngine;
 using Spine.Unity;
 
-namespace Spine.Unity.Modules {
+namespace Spine.Unity.Examples {
 
 	/// <summary>
 	/// Add this component to a Spine GameObject to apply a specific slot's Colors as MaterialProperties.
 	/// This allows you to apply the two color tint to the whole skeleton and not require the overhead of an extra vertex stream on the mesh.
 	/// </summary>
 	public class SlotTintBlackFollower : MonoBehaviour {
-
 		#region Inspector
 		/// <summary>
 		/// Serialized name of the slot loaded at runtime. Change the slot field instead of this if you want to change the followed slot at runtime.</summary>

+ 36 - 37
spine-unity/Assets/Examples/Scripts/Sample Components/CustomSkin.cs → spine-unity/Assets/Examples/Scripts/Sample Components/SpineEventUnityHandler.cs

@@ -1,4 +1,4 @@
-/******************************************************************************
+/******************************************************************************
  * Spine Runtimes Software License v2.5
  *
  * Copyright (c) 2013-2016, Esoteric Software
@@ -28,56 +28,55 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
+using System.Collections.Generic;
 using UnityEngine;
-using Spine;
-using Spine.Unity;
+using UnityEngine.Events;
 
 namespace Spine.Unity.Modules {
-	public class CustomSkin : MonoBehaviour {
 
-		[System.Serializable]
-		public class SkinPair {
-			/// <summary>SpineAttachment attachment path to help find the attachment.</summary>
-			/// <remarks>This use of SpineAttachment generates an attachment path string that can only be used by SpineAttachment.GetAttachment.</remarks>
-			[SpineAttachment(currentSkinOnly: false, returnAttachmentPath: true, dataField: "skinSource")]
-			[UnityEngine.Serialization.FormerlySerializedAs("sourceAttachment")]
-			public string sourceAttachmentPath;
-
-			[SpineSlot]
-			public string targetSlot;
+	public class SpineEventUnityHandler : MonoBehaviour {
 
-			/// <summary>The name of the skin placeholder/skin dictionary entry this attachment should be associated with.</summary>
-			/// <remarks>This name is used by the skin dictionary, used in the method Skin.AddAttachment as well as setting a slot attachment</remarks>
-			[SpineAttachment(currentSkinOnly: true, placeholdersOnly: true)]
-			public string targetAttachment;
+		[System.Serializable]
+		public class EventPair {
+			[SpineEvent] public string spineEvent;
+			public UnityEvent unityHandler;
+			public AnimationState.TrackEntryEventDelegate eventDelegate;
 		}
 
-		#region Inspector
-		public SkeletonDataAsset skinSource;
+		public List<EventPair> events = new List<EventPair>();
 
-		[UnityEngine.Serialization.FormerlySerializedAs("skinning")]
-		public SkinPair[] skinItems;
-
-		public Skin customSkin;
-		#endregion
-
-		SkeletonRenderer skeletonRenderer;
+		ISkeletonComponent skeletonComponent;
+		IAnimationStateComponent animationStateComponent;
 
 		void Start () {
-			skeletonRenderer = GetComponent<SkeletonRenderer>();
-			Skeleton skeleton = skeletonRenderer.skeleton;
+			skeletonComponent = skeletonComponent ?? GetComponent<ISkeletonComponent>();
+			if (skeletonComponent == null) return;
+			animationStateComponent = animationStateComponent ?? skeletonComponent as IAnimationStateComponent;
+			if (animationStateComponent == null) return;
+			var skeleton = skeletonComponent.Skeleton;
+			if (skeleton == null) return;
 
-			customSkin = new Skin("CustomSkin");
 
-			foreach (var pair in skinItems) {
-				var attachment = SpineAttachment.GetAttachment(pair.sourceAttachmentPath, skinSource);
-				customSkin.AddAttachment(skeleton.FindSlotIndex(pair.targetSlot), pair.targetAttachment, attachment);
+			var skeletonData = skeleton.Data;
+			var state = animationStateComponent.AnimationState;
+			foreach (var ep in events) {
+				var eventData = skeletonData.FindEvent(ep.spineEvent);
+				ep.eventDelegate = ep.eventDelegate ?? delegate(TrackEntry trackEntry, Event e) { if (e.Data == eventData) ep.unityHandler.Invoke(); };
+				state.Event += ep.eventDelegate;
 			}
+		}
 
-			// The custom skin does not need to be added to the skeleton data for it to work.
-			// But it's useful for your script to keep a reference to it.
-			skeleton.SetSkin(customSkin);
+		void OnDestroy () {
+			animationStateComponent = animationStateComponent ?? GetComponent<IAnimationStateComponent>();
+			if (animationStateComponent == null) return;
+
+			var state = animationStateComponent.AnimationState;
+			foreach (var ep in events) {
+				if (ep.eventDelegate != null) state.Event -= ep.eventDelegate;
+				ep.eventDelegate = null;
+			}
 		}
-	}
 
+	}
 }
+

+ 12 - 0
spine-unity/Assets/Examples/Scripts/Sample Components/SpineEventUnityHandler.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 90293750f472d3340b452cec6fea2606
+timeCreated: 1495263964
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff